Multiple Choice Questions (MCQs) are widely used in web applications for various reasons, including Standardized Testing (Student Examination), Interactive Learning, Immediate Feedback, Adaptive Learning, User-Friendly, and Mobile Compatibility.
Managing survey forms is challenging because it requires complex data management. The survey form may contain MCQs, and when the user fills in the details, we need to show which ones are correct and which are wrong.
This article is a thorough guide on creating a survey form with Formik and MUI to build an MCQ application in React.
Prerequisites
This tutorial assumes that you have installed the latest version of Node.js and npm on your device. We will use Vite build tools to create a new React project.
Project Overview
We will create a simple web application in which we will have a survey form that contains two questions with four options each. From those four options, one option would be correct, and all options would be a radio button component from Material-UI.
After selecting an option, we will submit the form, which will display the result below the form. The result displays whether the question you answered is correct or wrong.
If the selected option(answer) is wrong, we will highlight the correct answer for that question by turning that radio button red. It is a simple quiz app built on React application.
Our final project looks like the following:
Here is the step-by-step guide:
Step 1: Setup a React project
Let’s create a new project using Vite by the command below:
npm create vite@latest survey-app -- --template react
Navigate to the project folder.
cd survey-app
Install the necessary modules:
npm install
Step 2: Install Formik, Yup, and MUI
Install Formik, Yup, and MUI using the command below:
npm install formik yup @mui/material @emotion/react @emotion/styled @mui/icons-material
Step 3: Create a Theme Configuration file
Under the src folder, create a theme.js file. This file is responsible of an entire theme of our application.
// src/theme.js import { createTheme } from "@mui/material"; const theme = createTheme(); export default theme;
In this code, we exported this theme to use it across our application, ensuring consistency in styling.
Step 4: Create a ColoredTypography Component
Under the “src” folder, create a new folder called “components”.
Inside the “components” folder, create a new file called “ColoredTypography.jsx” and add the below code in it:
// src/components/ColoredTypography.jsx import { Typography } from "@mui/material"; import { styled } from "@mui/system"; const ColoredTypography = styled(Typography, { shouldForwardProp: (prop) => prop !== "isCorrect", })(({ theme, isCorrect }) => ({ color: isCorrect ? theme.palette.success.main : theme.palette.error.main, })); export default ColoredTypography;
Step 5: Create a validation schema
Under the “src” folder, create a new folder called “validation”, and inside that folder, create a new file called validationSchema.js and add the code below:
// src/validation/validationSchema.js import * as Yup from 'yup'; const validationSchema = Yup.object({ question1: Yup.string().required('Please select an option for Question 1'), question2: Yup.string().required('Please select an option for Question 2'), }); export default validationSchema;
Step 6: Create a Component for the MCQ Question
Since we want to create MCQ-type questions, we will create a reusable component called “QuestionField.jsx” under the “components” folder to render each question field with options.
// src/components/QuestionField.jsx import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, FormHelperText, } from "@mui/material"; import { Field } from "formik"; import { styled } from "@mui/system"; import { useTheme } from "@mui/material/styles"; const StyledFormControlLabel = styled(FormControlLabel)( ({ theme, correct }) => ({ "& .MuiRadio-root": { color: correct === "correct" ? theme.palette.success.main : correct === "wrong" ? theme.palette.error.main : undefined, }, }) ); // eslint-disable-next-line react/prop-types const QuestionField = ({ field, label, options, errors, touched, result }) => { const theme = useTheme(); return ( <FormControl component="fieldset" error={Boolean(errors[field] && touched[field])} fullWidth margin="normal" > <FormLabel component="legend">{label}</FormLabel> <Field as={RadioGroup} aria-label={field} name={field}> { // eslint-disable-next-line react/prop-types options.map((option) => ( <StyledFormControlLabel key={option.value} value={option.value} control={<Radio />} label={option.label} correct={ result && result[field] === "Correct" && // eslint-disable-next-line react/prop-types option.value === result.correct[field] ? "correct" : result && result[field] === "Wrong" && // eslint-disable-next-line react/prop-types option.value === result.correct[field] ? "wrong" : undefined } theme={theme} /> )) } </Field> <FormHelperText>{touched[field] && errors[field]}</FormHelperText> </FormControl> ); }; export default QuestionField;
In this code, we customized the radio button colors based on correctness using the StyledFormControlLabel component. This QuestionField.jsx component displays a fieldset with radio buttons and handles displaying errors. This component integrates Field from Formik with form-state management.
If you submit the form without selecting any option in any questions, it will display the validation error messages.
Step 7: Create a SurveyForm Component
This Survey Form is our main application form and we will isolate the main form logic and UI in a separate component file, improving readability and maintainability.
Create a new file called “SurveyForm.jsx” under the “components” folder and add the code below in it:
// src/components/SurveyForm.jsx import { useState } from "react"; import { Formik, Form } from "formik"; import { Button, Typography, ThemeProvider } from "@mui/material"; import theme from "../theme"; import validationSchema from "../validation/validationSchema"; import QuestionField from "./QuestionField"; import ColoredTypography from "./ColoredTypography"; const SurveyForm = () => { const [result, setResult] = useState(null); const initialValues = { question1: "", question2: "", }; const correctAnswers = { question1: "option2", // Correct answer for Question 1 question2: "option3", // Correct answer for Question 2 }; const handleSubmit = (values) => { const results = { question1: values.question1 === correctAnswers.question1 ? "Correct" : "Wrong", question2: values.question2 === correctAnswers.question2 ? "Correct" : "Wrong", correct: correctAnswers, }; setResult(results); }; return ( <ThemeProvider theme={theme}> <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit} > {({ errors, touched }) => ( <Form> <QuestionField field="question1" label="Question 1: What is the capital of France?" options={[ { value: "option1", label: "Berlin" }, { value: "option2", label: "Paris" }, { value: "option3", label: "Rome" }, { value: "option4", label: "Madrid" }, ]} errors={errors} touched={touched} result={result} /> <QuestionField field="question2" label="Question 2: Which planet is known as the Red Planet?" options={[ { value: "option1", label: "Earth" }, { value: "option2", label: "Jupiter" }, { value: "option3", label: "Mars" }, { value: "option4", label: "Venus" }, ]} errors={errors} touched={touched} result={result} /> <Button type="submit" variant="contained" color="primary"> Submit </Button> {result && ( <div> <Typography variant="h6">Results:</Typography> <ColoredTypography variant="body1" isCorrect={result.question1 === "Correct"} > Question 1: {result.question1} </ColoredTypography> <ColoredTypography variant="body1" isCorrect={result.question2 === "Correct"} > Question 2: {result.question2} </ColoredTypography> </div> )} </Form> )} </Formik> </ThemeProvider> ); }; export default SurveyForm;
In this code, we used the useState hook to manage the state of the result.
In the next step, we defined two JavaScript objects:
- initialValues: This object holds the initial form values.
- correctAnswers: This object holds the correct answers for each question.
The handleSubmit() function handles the form submission and sets the results.
To manage the form state, validation, and submission, we used the formik library.
While building this survey form, we used two custom components:
- QuestionField: It renders each question with proper styling and validation.
- ColoredTypography: It displays the formatted output. Whether the answer is right or wrong, it styles the result text respectively. For a wrong answer, it will display in red text. For the correct answer, it will display the green text.
When the user submits the response, it will display the result below.
If the user’s selected answer is wrong, we will display which option is correct by making that radio button red.
Step 8: Update the App.jsx file
We created SurveyForm.jsx and must include that form in our src/App.jsx file to register in our main React application.
// src/App.jsx import SurveyForm from "./components/SurveyForm"; import { Container } from "@mui/material"; const App = () => { return ( <Container maxWidth="sm"> <h1>General Knowledge Survey</h1> <SurveyForm /> </Container> ); }; export default App;
And our src/main.jsx file looks like below:
// src/main.jsx import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.jsx"; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <App /> </React.StrictMode> );
Our final project structure looks like this:
src/ ├── components/ │ ├── ColoredTypography.jsx │ ├── QuestionField.jsx │ └── SurveyForm.jsx ├── validation/ │ └── validationSchema.js ├── theme.js ├── main.jsx └── App.jsx
Step 9: Run the application
Start the Vite development server using the command below:
npm run dev
Let’s test out the validation of our application:
If you provide all the correct details, it will display the output like this:
If you select wrong answers from the quiz, it will output like the below image:
Here is the complete code on Github.