Prompt

How do I build a dynamic survey in React?

Technology · Developer Tools / Developer tools3 observationsLast seen Jul 16, 2026

Latest observation

Aug 27, 2026 · OpenAI APIWeb search: off

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:

  1. a multi-step survey wizard
  2. a version using React Hook Form
  3. 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.