Prompt
How do I build a dynamic survey in React?
Latest observation
Building a dynamic survey in React usually means:
- rendering questions from a data model
- storing answers in state
- showing/hiding questions based on previous answers
- validating inputs
- submitting the final result
Here’s a practical way to build it.
1. Define your survey as data
Instead of hardcoding each question, keep the survey in an array or object.
const survey = [
{
id: "name",
type: "text",
label: "What is your name?",
required: true,
},
{
id: "age",
type: "number",
label: "How old are you?",
required: true,
},
{
id: "student",
type: "radio",
label: "Are you a student?",
options: ["Yes", "No"],
required: true,
},
{
id: "school",
type: "text",
label: "What school do you attend?",
dependsOn: { questionId: "student", value: "Yes" },
},
];
This makes the survey easy to change without rewriting components.
2. Store answers in state
Use useState to keep track of responses.
import React, { useState } from "react";
export default function SurveyForm() {
const [answers, setAnswers] = useState({});
}
3. Render questions dynamically
Create a component that loops through your survey config and renders the correct input based on type.
function SurveyForm() {
const [answers, setAnswers] = useState({});
const handleChange = (id, value) => {
setAnswers((prev) => ({
...prev,
[id]: value,
}));
};
return (
<form>
{survey.map((question) => (
<Question
key={question.id}
question={question}
value={answers[question.id] || ""}
onChange={handleChange}
answers={answers}
/>
))}
</form>
);
}
4. Build a reusable Question component
This component decides what input to render.
function Question({ question, value, onChange, answers }) {
const { id, type, label, options, dependsOn } = question;
if (dependsOn) {
const parentAnswer = answers[dependsOn.questionId];
if (parentAnswer !== dependsOn.value) {
return null;
}
}
return (
<div style={{ marginBottom: "1rem" }}>
<label>
{label}
<br />
{type === "text" && (
<input
type="text"
value={value}
onChange={(e) => onChange(id, e.target.value)}
/>
)}
{type === "number" && (
<input
type="number"
value={value}
onChange={(e) => onChange(id, e.target.value)}
/>
)}
{type === "radio" &&
options.map((option) => (
<label key={option} style={{ display: "block" }}>
<input
type="radio"
name={id}
value={option}
checked={value === option}
onChange={(e) => onChange(id, e.target.value)}
/>
{option}
</label>
))}
</label>
</div>
);
}
5. Add validation
You can validate on submit by checking required fields.
const validate = (survey, answers) => {
const errors = {};
survey.forEach((q) => {
if (q.required && !answers[q.id]) {
errors[q.id] = "This field is required";
}
});
return errors;
};
Then in your form:
const [errors, setErrors] = useState({});
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = validate(survey, answers);
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
console.log("Submitted answers:", answers);
}
};
6. Show error messages
{errors[question.id] && (
<p style={{ color: "red" }}>{errors[question.id]}</p>
)}
Pass errors into the Question component if you want inline validation.
7. Full example
import React, { useState } from "react";
const survey = [
{
id: "name",
type: "text",
label: "What is your name?",
required: true,
},
{
id: "student",
type: "radio",
label: "Are you a student?",
options: ["Yes", "No"],
required: true,
},
{
id: "school",
type: "text",
label: "What school do you attend?",
dependsOn: { questionId: "student", value: "Yes" },
},
];
export default function SurveyForm() {
const [answers, setAnswers] = useState({});
const [errors, setErrors] = useState({});
const handleChange = (id, value) => {
setAnswers((prev) => ({ ...prev, [id]: value }));
};
const validate = () => {
const newErrors = {};
survey.forEach((q) => {
if (q.required && !answers[q.id]) {
newErrors[q.id] = "This field is required";
}
if (
q.dependsOn &&
answers[q.dependsOn.questionId] === q.dependsOn.value &&
q.required &&
!answers[q.id]
) {
newErrors[q.id] = "This field is required";
}
});
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = validate();
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
console.log("Submitted:", answers);
}
};
return (
<form onSubmit={handleSubmit}>
{survey.map((question) => {
if (
question.dependsOn &&
answers[question.dependsOn.questionId] !== question.dependsOn.value
) {
return null;
}
return (
<div key={question.id} style={{ marginBottom: "1rem" }}>
<label>{question.label}</label>
<br />
{question.type === "text" && (
<input
type="text"
value={answers[question.id] || ""}
onChange={(e) => handleChange(question.id, e.target.value)}
/>
)}
{question.type === "radio" &&
question.options.map((option) => (
<label key={option} style={{ display: "block" }}>
<input
type="radio"
name={question.id}
value={option}
checked={answers[question.id] === option}
onChange={(e) => handleChange(question.id, e.target.value)}
/>
{option}
</label>
))}
{errors[question.id] && (
<p style={{ color: "red" }}>{errors[question.id]}</p>
)}
</div>
);
})}
<button type="submit">Submit</button>
</form>
);
}
8. Tips for a cleaner architecture
If your survey grows, consider:
- separating config from UI
- using Formik or React Hook Form
- using Yup or Zod for validation
- handling branching logic in a dedicated function
- storing survey state in a reducer if it becomes complex
9. If you need advanced behavior
For large surveys, you may want:
- conditional branching based on answers
- pagination / one question per step
- save and resume
- dynamic question loading from backend
- progress bar
- answer types like select, checkbox, date, rating, textarea
If you want, I can also show you:
- a multi-step survey wizard
- a version using React Hook Form
- a fully dynamic branching survey engine with nested conditions
Observation history
See 2 earlier observations
Create a free Obsurfable account to unlock historical responses, model comparisons, and deeper brand intelligence.
How did Obsurfable measure this prompt?
Obsurfable records AI answers to buyer-style prompts in its research corpus (3 observations for this page). Metrics are distributions over observations, not a single static ranking.
Which AI systems does Obsurfable collect answers from?
OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, and Meta AI.