React Formik checkbox group does not turn into an array of invidual checked or unchecked elements

11,990

Solution 1

It's pretty easy to accomplish using the FieldArray component.

Place your values in an array, something like this:

const tagCollection = [
  { value: "one", label: "One" },
  { value: "two", label: "Two" },
  { value: "three", label: "Three" }
];

And then use the FieldArray like this:

<FieldArray
    name="tags"
    render={arrayHelpers => (
        <div>
            {tagCollection.map(tag => (
                <label key={tag.value}>
                    <input
                        name="tags"
                        type="checkbox"
                        value={tag}
                        checked={values.tags.includes(tag.value)}
                        onChange={e => {
                          if (e.target.checked) {
                            arrayHelpers.push(tag.value);
                          } else {
                            const idx = values.tags.indexOf(tag.value);
                            arrayHelpers.remove(idx);
                          }
                        }}
                    />
                    <span>{tag.label}</span>
                </label>
            ))}
        </div>
    )}
/>

Working sandbox


Update:

You can also do this by writing your own component.

const MyCheckbox = ({ field, form, label, ...rest }) => {
  const { name, value: formikValue } = field;
  const { setFieldValue } = form;

  const handleChange = event => {
    const values = formikValue || [];
    const index = values.indexOf(rest.value);
    if (index === -1) {
      values.push(rest.value);
    } else {
      values.splice(index, 1);
    }
    setFieldValue(name, values);
  };

  return (
    <label>
      <input
        type="checkbox"
        onChange={handleChange}
        checked={formikValue.indexOf(rest.value) !== -1}
        {...rest}
      />
      <span>{label}</span>
    </label>
  );
};

// And using it like this:
<Field component={MyCheckbox} name="tags" value="one" label="One" />
<Field component={MyCheckbox} name="tags" value="two" label="Two" />
<Field component={MyCheckbox} name="tags" value="three" label="Three" />

Solution 2

With Formik 2 without any Formik specific components:

import React from "react";
import { useFormik } from "formik";

export default function App() {
  return <ProjectsForm></ProjectsForm>;
}

const tags = ["one", "two", "three"];

const ProjectsForm = () => {
  const formik = useFormik({
    enableReinitialize: true,
    initialValues: {
      tags: []
    },
    onSubmit: (values) => {
      console.log(values);
    }
  });

  const handleChange = (e) => {
    const { checked, name } = e.target;
    if (checked) {
      formik.setFieldValue("tags", [...formik.values.tags, name]);
    } else {
      formik.setFieldValue(
        "tags",
        formik.values.tags.filter((v) => v !== name)
      );
    }
  };

  return (
    <form onSubmit={formik.handleSubmit}>
      {tags.map((tag) => (
        <div key={tag}>
          <input
            id={tag}
            type="checkbox"
            name={tag}
            checked={formik.values.tags.includes(tag)}
            onChange={handleChange}
          />
          <label htmlFor={tag}>{tag}</label>
        </div>
      ))}
      <button type="submit">Submit</button>
    </form>
  );
};

I started by creating a simple array of tags.

Then I used useFormik hook to bind the form for an array of checkboxes. Whether a checkbox is checked, depends on whether it's in the tags array. Hence: formik.values.tags.includes(tag)

I created a custom change handling function to set or remove a tag from Formik values depending on whether a checkbox is checked or not.

Full working Sandbox example

Share:
11,990
niklasbuhl
Author by

niklasbuhl

Updated on June 08, 2022

Comments

  • niklasbuhl
    niklasbuhl almost 2 years

    I am using Formik to create a form on a website. In this form I have a set of tags which I want to be checkable with individual checkboxes, but keep them in an array tags. Formik claims to be able to do so by using the same name attribute on the Field elements, but I can not get it to work.

    I am using this code example as an reference.

    Here is my code:

    import React from "react";
    import ReactDOM from "react-dom";
    import { Formik, Form, Field } from "formik";
    
    import "./styles.css";
    
    function App() {
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen!</h2>
          <ProjectsForm />
        </div>
      );
    }
    
    const ProjectsForm = props => (
      <Formik
        initialValues={{
          search: "",
          tags: []
        }}
        onSubmit={values => {
          console.log(values);
        }}
        validate={values => {
          console.log(values);
        }}
        validateOnChange={true}
      >
        {({ isSubmitting, getFieldProps, handleChange, handleBlur, values }) => (
          <Form>
            <Field type="text" name="search" />
            <label>
              <Field type="checkbox" name="tags" value="one" />
              One
            </label>
            <label>
              <Field type="checkbox" name="tags" value="two" />
              Two
            </label>
            <label>
              <Field type="checkbox" name="tags" value="three" />
              Three
            </label>
          </Form>
        )}
      </Formik>
    );
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    Also on CodeSandbox

    I am expecting to receive thought the console.log(values) to display something like:

    Object {search: "", tags: ['one', 'three']}

    or

    Object {search: "", tags: ['one': true, 'two': false, 'three': true]}

    Hope there is a simple thing I am missing to add the Formik checkbox group functionality, as it claims to be possible.