Formik Field radio button is clicked and value got updated, but does not showing radio button as selected

Viewed 4059

I have been working on quiz app with multiple radio buttons using React along with Formik. I don't want the radio buttons to have defaultCheckedattribute. Form submit is working the way I wanted, the values of each radio buttons are shown on alert. But the radio button is not showing as selected in the browser.

import React, { useState } from 'react';
import { Formik, Form, Field } from 'formik';
import {
  Button,
  ButtonGroup,
  Col,
  Container,
  FormGroup,
  Input,
  Label,
  Row,
} from 'reactstrap';
import { Questions, initialValues } from './dummy';
import ModalConfirm from './ModalConfirm';
import './FormQuiz.css';
import ButtonGrid from './ButtonGrid';

function FormQuiz() {
  const [isOpen, setIsOpen] = useState(false);
  const [confirm, setConfirm] = useState(false);

  const [step, setStep] = useState(1);
  const [questions, setQuestions] = useState(Questions);

  const toggle = (e) => setIsOpen(!isOpen);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!confirm) {
      toggle();
    }
  };

  return (
    <Container className='mt-5'>
      <Row className='mx-2'>
        <Col md={{ size: '6' }} className='m-auto'>
          
          <Formik
            initialValues={initialValues}
            onSubmit={async (values) => {
              await new Promise((r) => setTimeout(r, 500));
              alert(JSON.stringify(values, null, 2));
            }}
          >
            {({ values }) => (
              <Form>
                <div role='group' aria-labelledby='my-radio-group'>
                  {questions.map((question, questionIndex) => (
                    <div
                      key={questionIndex}
                      hidden={questionIndex + 1 === step ? false : true}
                    >
                      <h5 className='pl-0 my-5'>
                        {questionIndex + 1}
                        {'. '}
                        {question.questionText}
                      </h5>
                      {question.options.map((option, optionIndex) => (
                        <FormGroup
                          key={optionIndex}
                          role='group'
                          className='row'
                        >
                          <Field
                            className='form-control-sm'
                            type='radio'
                            id={`q${questionIndex + 1}o${optionIndex + 1}`}
                            name={`option${questionIndex + 1}`}
                            value={optionIndex + 1}
                          />
                          <Label
                            htmlFor={`q${questionIndex + 1}o${optionIndex + 1}`}
                          >
                            {option}
                          </Label>
                        </FormGroup>
                      ))}
                      <div>{values[`option${questionIndex + 1}`]}</div>
                    </div>
                  ))}
                </div>
                <Button
                  color='info'
                  disabled={step === 1 ? true : false}
                  onClick={(e) => setStep(step - 1)}
                  className='mr-1'
                >
                  Previous
                </Button>
                <Button
                  color='primary'
                  disabled={step === questions.length ? true : false}
                  onClick={(e) => setStep(step + 1)}
                  className='mr-1'
                >
                  Next
                </Button>

                <Button color='warning' type='submit'>
                  Submit
                </Button>
              </Form>
            )}
          </Formik>
        </Col>
      </Row>
    </Container>
  );
}

export default FormQuiz;

Here is the hard coded Questions and initialValues dummy.js

export const Questions = [
  {
    questionText: 'HTML?',
    options: [
      'Hyper Tech Machine Learning',
      'How To Mitigate Linda',
      'How To Master Linguistics',
      'Hyper Text Markup Language',
    ],
    correctOption: 4,
  },
  {
    questionText: 'NodeJS invented by',
    options: ['Carls Linus', 'Ryan Dahl', 'Siraj Raval', 'Bill Gates'],
    correctOption: 2,
  }
];

let values = {};
Questions.forEach((question, index) => {
  values[`option${index + 1}`] = '';
});
export let initialValues = values;

Please help me with this. And if there is any better way to implement this, let me know.

2 Answers

Having been caught out by this myself, I can tell you that the issue is probably due to the value attribute for your JSX <Field /> tag being a number type, as opposed to being a string (yes, I know that might be by design, but bear with me...).

If you change the value of the value attribute to be the string representation of the number, the UI should update and show the radio button as selected.

OPs code:

<Field
  className='form-control-sm'
  type='radio'
  id={`q${questionIndex + 1}o${optionIndex + 1}`}
  name={`option${questionIndex + 1}`}
  // TODO: value attribute should be the string representation of the numerical value
  value={optionIndex + 1} 
/>

I used Yup for schema validation (as recommended by the Formik documentation on validation) and this still passed validation for a nullable required number:

Yup.number().required('Required').nullable()

If you're interested in using Yup for schema validation, it looks like Yup simply takes the value, and attempts to parse it as a number. If the value is not able to be parsed as a number, Yup will serve up a NaN error message and the value will fail validation.

Beware, however as this is obviously stored in the values object as a string, so if this is going to catch you out later on in your code, make sure you account for it.

Use like this: 1st mistake: use same name for radio input 2nd mistake: pass checked prop when to show it selected

<Field
          name="gender"
          render={({ field }) => (
            <>
              <div className="radio-item">
                <input
                  {...field}
                  id="male"
                  value="male"
                  checked={field.value === 'male'}
                  name="type"
                  type="radio"
                />
                <label htmlFor="male">Male</label>
              </div>

              <div className="radio-item">
                <input
                  {...field}
                  id="female"
                  value="female"
                  name="type"
                  checked={field.value === 'female'}
                  type="radio"
                />
                <label htmlFor="female">Female</label>
              </div>
            </>
          )}
        />
Related