Search code examples
javascriptreactjsmaterial-uiformikyup

Radio Button Select in Table in React


I'm using React, MUI and Formik. I have a table and how do I allow that only one radio would be selected by row?

This is my codesandbox: CLICK HERE

import { TableCell, TableRow, Radio } from "@mui/material";
import { FastField } from "formik";
import CustomTextField from "./CustomTextField";

const CustomTableRow = ({ row, index }) => {
  return (
    <TableRow
      key={row.id}
      sx={{
        "&:last-child td, &:last-child th": { border: 0 },
      }}
    >
      <TableCell component="th" scope="row">
        <FastField
          name={`rows.${index}.attribute`}
          component={CustomTextField}
          fullWidth
          size="small"
        />
      </TableCell>
      <TableCell>
        <FastField
          // checked={`rows.${index}.radio1`}
          name={`rows.${index}.radio1`}
          component={CustomTextField}
          type="radio"
          fullWidth
        />
      </TableCell>
      <TableCell>
        <FastField
          // checked={`rows.${index}.radio2`}
          name={`rows.${index}.radio2`}
          component={CustomTextField}
          type="radio"
          fullWidth
        />
      </TableCell>
    </TableRow>
  );
};

CustomTableRow.displayName = "CustomTableRow";

export default CustomTableRow;

Solution

    1. The radio elements on each row should share the same name.
    2. Update the initialValues of Formik to have a state for the selectedOption
    3. Update the validationSchema to validate the new selectedOption field
    4. Pass setFieldValue and values to CustomTableRow provided by Formik to CustomTableRow
    5. In the CustomTableRow component make a handleChange function which updates the selectedOption of that row depend on the selected radio.
    6. Make the radio checked attribute update conditionally based on selectedOption of the row.

    demo.js

    import React from "react";
    import Table from "@mui/material/Table";
    import TableBody from "@mui/material/TableBody";
    import TableCell from "@mui/material/TableCell";
    import TableContainer from "@mui/material/TableContainer";
    import TableHead from "@mui/material/TableHead";
    import TableRow from "@mui/material/TableRow";
    import Paper from "@mui/material/Paper";
    import { Box, Button } from "@mui/material";
    import AddIcon from "@mui/icons-material/Add";
    import { Formik, Form, FieldArray } from "formik";
    import CustomTableRow from "./CustomTableRow";
    import { array, object, string } from "yup";
    
    const validationSchema = object().shape({
      rows: array().of(
        object().shape({
          attribute: string().required("Select an attribute"),
          selectedOption: string().required("Select an option"),
        })
      ),
    });
    
    const CustomTable = () => {
      return (
        <TableContainer component={Paper}>
          <Formik
            initialValues={{
              rows: [
                { id: 1, attribute: "", selectedOption: "radio1" },
                { id: 2, attribute: "", selectedOption: "radio2" },
              ],
            }}
            validationSchema={validationSchema}
            onSubmit={(values) => {
              console.log(values);
            }}
          >
            {({ values, setFieldValue }) => (
              <Form>
                <FieldArray
                  name="rows"
                  render={(arrayHelpers) => (
                    <React.Fragment>
                      <Box>
                        <Button
                          variant="contained"
                          onClick={() =>
                            arrayHelpers.push({
                              id: Date.now(),
                              attribute: "",
                              selectedOption: null,
                            })
                          }
                          startIcon={<AddIcon />}
                        >
                          Add New
                        </Button>
                      </Box>
                      <Table sx={{ minWidth: 650 }} aria-label="simple table">
                        <TableHead>
                          <TableRow>
                            <TableCell>Attribute</TableCell>
                            <TableCell>Radio 1</TableCell>
                            <TableCell>Radio 2</TableCell>
                          </TableRow>
                        </TableHead>
                        <TableBody>
                          {values.rows.map((row, index) => (
                            <CustomTableRow
                              key={row.id}
                              row={row}
                              index={index}
                              setFieldValue={setFieldValue}
                              values={values}
                            />
                          ))}
                        </TableBody>
                      </Table>
                    </React.Fragment>
                  )}
                />
                <Button color="primary" variant="contained" type="submit">
                  Submit
                </Button>
              </Form>
            )}
          </Formik>
        </TableContainer>
      );
    };
    
    export default CustomTable;
    

    CustomTableRow.js

    import { TableCell, TableRow, Radio } from "@mui/material";
    import { Field } from "formik";
    import CustomTextField from "./CustomTextField";
    
    const CustomTableRow = ({ row, index, setFieldValue, values }) => {
      const handleChange = (event) => {
        setFieldValue(`rows.${index}.selectedOption`, event.target.value);
      };
    
      return (
        <TableRow
          key={row.id}
          sx={{
            "&:last-child td, &:last-child th": { border: 0 },
          }}
        >
          <TableCell component="th" scope="row">
            <Field
              name={`rows.${index}.attribute`}
              component={CustomTextField}
              fullWidth
              size="small"
            />
          </TableCell>
          <TableCell>
            <Radio
              checked={values.rows[index].selectedOption === "radio1"}
              onChange={handleChange}
              value="radio1"
              name={`rows.${index}.selectedOption`}
            />
          </TableCell>
          <TableCell>
            <Radio
              checked={values.rows[index].selectedOption === "radio2"}
              onChange={handleChange}
              value="radio2"
              name={`rows.${index}.selectedOption`}
            />
          </TableCell>
        </TableRow>
      );
    };
    
    CustomTableRow.displayName = "CustomTableRow";
    
    export default CustomTableRow;
    
    

    CODESANDBOX