0

I am new to coding and would very much appreciate any assistance as i've been struggling with this for a while.

I have a form with over 10 fields, some are multiple checkboxes, some are not. Every field is its own component and has its own onChange. The problem is that in a multiple checkbox field, if i choose a number of options, submitting will only log the number minus 1. So if i choose 3, it will log only 2. This is the case for all multiple checkboxes.

I have learnt that useState is asynchronous and that useEffect should be used instead, i just don't know how to go about it for something like this.

import "./styles.css";
import Availability from "./components/Availability"
import { useState } from "react";

export default function App() {
  const [formData, setFormData] = useState()

  const addData = (id, value) => {
    setFormData((prevValue) => {
      return {
        ...prevValue,
        [id]: value,
      };
    });
  };

  const onSubmit = (e) => {
    e.preventDefault()

    console.log(formData)
  }

  return (
    <div className="App">
      <form onSubmit={onSubmit}>
      <Availability handleAddData={addData} />
      <button>Submit</button>
        </form>
      
    </div>
  );
}

And this is the component with the days of the week as an example:

import { useState } from "react";

export default function Availability({ handleAddData }) {
  const [data, setData] = useState([]);

const availabilityArray = [
  {
    id: "availability",
    title: "Monday",
    value: "monday",
    type: "checkbox",
  },
  {
    id: "availability",
    title: "Tuesday",
    value: "tuesday",
    type: "checkbox",
  },
  {
    id: "availability",
    title: "Wednesday",
    value: "wednesday",
    type: "checkbox",
  },
  {
    id: "availability",
    title: "Thursday",
    value: "thursday",
    type: "checkbox",
  },
  {
    id: "availability",
    title: "Friday",
    value: "friday",
    type: "checkbox",
  },
  {
    id: "availability",
    title: "Saturday",
    value: "saturday",
    type: "checkbox",
  },
  {
    id: "availability",
    title: "Sunday",
    value: "sunday",
    type: "checkbox",
  },
];


  const onChange = (e) => {
    const { id, value, checked } = e.target;
    if (!checked) {
      setData(data.filter((word) => word !== value));
      return;
    }
    if (checked) {
      setData((prevValue) => {
        return [...prevValue, value];
      });
    }

    handleAddData(id, data);
  };
  return (
    <>
      <h3>Please confirm your availability</h3>
      <div onChange={onChange}>
        <ul>
          {availabilityArray.map((item) => (
            <li
              key={item.value}
            >
              <div>
                <label>
                  <input
                    id={item.id}
                    type={item.type}
                    value={item.value}
                  />
                  <span>{item.title}</span>
                </label>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

Here is a codesandbox so that you can see how it works. The console.log of -1 whatever has been selected when submitting is my problem.

Coolkid
  • 407
  • 1
  • 4
  • 13
  • Did you mean to give `onChange` to the `input`? – Henry Woody Feb 10 '23 at 12:33
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – Konrad Feb 10 '23 at 12:34
  • `setData` won't change the `data` immediately so `handleAddData(id, data);` will use the old `data`. Deduplicate the state, use `useEffect` or a library that will handle forms like `react-hook-form` – Konrad Feb 10 '23 at 12:36

1 Answers1

1

In the onChange function in availability component, you are setting the value for the state and passing the value to the addData function in the same onChange function.

Since useState is a async operation. It will have the old value when you pass the value first and when you pass the next value only the last value will be updated and so on.

so I have created a useEffect which listens to the changes made for the data state and call the handleAddData. Now whenever the data changes in the data state. It will call the handleAddData with the updated value.

checkout the codesandbox link: https://codesandbox.io/s/serene-einstein-xhf1qc?file=/src/components/Availability.js

Availability.js

  import { useEffect, useState } from "react";

export default function Availability({ handleAddData }) {
  const [data, setData] = useState([]);
  const [dataid, setId] = useState();

  const availabilityArray = [
    {
      id: "availability",
      title: "Monday",
      value: "monday",
      type: "checkbox"
    },
    {
      id: "availability",
      title: "Tuesday",
      value: "tuesday",
      type: "checkbox"
    },
    {
      id: "availability",
      title: "Wednesday",
      value: "wednesday",
      type: "checkbox"
    },
    {
      id: "availability",
      title: "Thursday",
      value: "thursday",
      type: "checkbox"
    },
    {
      id: "availability",
      title: "Friday",
      value: "friday",
      type: "checkbox"
    },
    {
      id: "availability",
      title: "Saturday",
      value: "saturday",
      type: "checkbox"
    },
    {
      id: "availability",
      title: "Sunday",
      value: "sunday",
      type: "checkbox"
    }
  ];

  useEffect(() => {
    if (data.length !== 0) {
      handleAddData(dataid, data);
    }
  }, [dataid, data]);

  const onChange = (e) => {
    const { id, value, checked } = e.target;
    if (!checked) {
      setData(data.filter((word) => word !== value));
      return;
    }
    if (checked) {
      setData((prevValue) => {
        return [...prevValue, value];
      });
    }
    setId(id);
  };
  return (
    <>
      <h3>Please confirm your availability</h3>
      <div onChange={onChange}>
        <ul>
          {availabilityArray.map((item) => (
            <li key={item.value}>
              <div>
                <label>
                  <input id={item.id} type={item.type} value={item.value} />
                  <span>{item.title}</span>
                </label>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}