0

I have a bunch of checkbox components and I want to update a state of the parent component each time the checkbox components are toggled. Right now I'm doing this:

   const selChangeHandler = useCallback(
    (checkboxId, isSel) => {
      if (isSel) setSelectedCheckboxes((prevState) => [...prevState, checkboxId]);
      else
        setSelectedCheckboxes((prevState) =>
          prevState.filter((x) => x != checkboxId)
        );
    },
    [setSelectedCheckboxes]
  );

This function is passed into each checkbox. I don't think that this is a particularly good approach as each checkbox can add anything to the array rather than only its id. So I tried this but I got an error:

  const GetSelChangeHandler = (checkboxId) => {
return useCallback(
  (isSel) => {
    if (isSel) setSelectedCheckboxes((prevState) => [...prevState, checkboxId]);
    else
      setSelectedCheckboxes((prevState) =>
        prevState.filter(
          (x) => x != checkboxId
        )
      );
  },
  [setSelectedCheckboxes]
); };

I executes this inline and passed the returned handler in as a callback prop to my checkbox components. I'm not sure what the error was but something to do with violating the rules of hooks. What is the correct way of doing this?

Haziq
  • 73
  • 8

2 Answers2

0

Rules of Hooks

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns.

You should not use useCallback() inside GetSelChangeHandler, in case you're calling it conditionally.

const GetSelChangeHandler = (checkboxId) => {
  return (isSel) => {
    if (isSel) setSelectedCheckboxes((prevState) => [...prevState, checkboxId]);
    else
      setSelectedCheckboxes((prevState) => prevState.filter(x => x !=checkboxId));
  };
};
hendra
  • 2,531
  • 5
  • 22
  • 34
  • So how do I optimize performance without the useCallback hook? – Haziq Feb 25 '22 at 16:48
  • You should only use `useCallback` if you really experience performance issues, not as premature optimization. In your case (really simple function) it would probably even decrease performance. If you really need it though, you have to make sure that you're calling always the same amount of hooks in the same order every rerender. Hard to make an example without seeing your complete component. – hendra Feb 26 '22 at 08:04
0

You cannot call hooks in loops or conditionals. How about something like a useToggle custom hook that makes it easier to manage the state of many checkboxes? Run the code below, click some and see how the application state reflects the changes.

function useToggle(initState = false) {
  const [state, setState] = React.useState(initState)
  return [state, _ => setState(!state)]
}

function App() {
  const [inStock, toggleInStock] = useToggle(true)
  const [freeShip, toggleFreeShip] = useToggle(true)
  const [onSale, toggleOnSale] = useToggle(true)
  return <div>
    In Stock: <Checkbox checked={inStock} onClick={toggleInStock} /><br/>
    Free Shipping: <Checkbox checked={freeShip} onClick={toggleFreeShip} /><br/>
    On Sale: <Checkbox checked={onSale} onClick={toggleOnSale} on="" /><br/>
    <pre>{JSON.stringify({ inStock, freeShip, onSale })}</pre>
  </div>
}

function Checkbox({ checked, onClick, on = "✅", off = "❌" }) {
  // use <input type="checkbox"> or any custom representation
  return <div className="checkbox" onClick={onClick}>
    {checked ? on : off}
  </div>
}

ReactDOM.render(<App/>, document.querySelector("#app"))
.checkbox { display: inline-block; cursor: pointer; }
pre { background-color: #ff8; padding: 0.5rem; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

If the amount of checkboxes is unknown, a useToggles variation can solve the problem.

function useToggles(init = {}) {
  const [states, setStates] = React.useState(init)
  return [
    states,
    key => event => setStates({...states, [key]: !Boolean(states[key])})
  ]
}

function App() {
  const [checkboxes, toggle] = useToggles({
    inStock: true,
    freeShip: true,
    onSale: true
  })
  return <div>
    In Stock:
    <Checkbox checked={checkboxes.inStock} onClick={toggle("inStock")}/><br/>
    Free Shipping:
    <Checkbox checked={checkboxes.freeShip} onClick={toggle("freeShip")}/><br/>
    On Sale:
    <Checkbox checked={checkboxes.onSale} onClick={toggle("onSale")} on=""/><br/>
    <pre>{JSON.stringify(checkboxes)}</pre>
  </div>
}

function Checkbox({ checked, onClick, on = "✅", off = "❌" }) {
  // use <input type="checkbox"> or any custom representation
  return <div className="checkbox" onClick={onClick}>
    {checked ? on : off}
  </div>
}

ReactDOM.render(<App/>, document.querySelector("#app"))
.checkbox { display: inline-block; cursor: pointer; }
pre { background-color: #ff8; padding: 0.5rem; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
よつば
  • 467
  • 1
  • 8