0

I have a simple react component, the purpose is to create a list in which each item will be toggle in or out on click.

export type Choice= {
  id: string
  value: string
}



function Component({choices}){
    const [selectedChoices, setSelectedChoices] = useState<Choice[]>([])
  const handleSelectChoice = useCallback((choice: Choice) => {
    setSelectedChoices((p) => {
      const idx = p.findIndex((c) => c.id === choice.id)
      if (idx > -1) {
        p.splice(idx, 1)
        console.log(p)

        return [...p]
      } else {
        console.log(p, "SD")
        return p.concat(choice)
      }
    })
  }, [])
  return (
    <div>
            {choices.map((choice) => (
              <button
                key={choice.id}
                type="button"
                className={` ${
                  selectedChoices.find((c) => c.id === choice.id) ? "selected" : ""
                }`}
                onClick={() => handleSelectChoice(choice)}
              >
                {choice.value}
              </button>
            ))}
          </div>
  )
}

After some debugging (using refs and console logs), I found out that react development mode is triggering the set function twice. enter image description here Whenever the user clicks the button in strict mode, the state won't change at all because the setSelectedChoices gets called twice, basically canceling the user's action.

I understand the importance of strict mode and could not make it work yet. I can try and make it work using refs but wanted to know if there is any other way. How can I run such useState setters in strict mode and avoid side-effects? Any help is appreciated.

plutolaser
  • 448
  • 4
  • 17
  • 3
    `p.splice()` is modifying your state, I would use a different method (such as `.filter()`) to create a new array that excludes the index, a few approaches: [How to delete an item from state array?](https://stackoverflow.com/q/36326612) – Nick Parsons Apr 05 '23 at 10:54
  • Thank you @NickParsons! This did solve my problem. I am not sure why it wasn't working earlier, I was providing a new array using the spread operator – plutolaser Apr 05 '23 at 10:57
  • 2
    I'm guessing when strict mode double invokes your state setter function it doesn't take into account what you returned from it, it just passes through the same reference (`p`) for both calls, and since you were modifying it directly you were getting the removed version on the second invocation. – Nick Parsons Apr 05 '23 at 11:00
  • 3
    I think it is worth mentionning that trying to circumvent strictmode's remount is very bad pratice. React does this on purpose, it is not a bug, it is intended to be like that in development mode. React will: mount, unmount then re-mount your component, triggering all the logic and effects as a production environment would along the way. That is meant to help you find issues like the one pointed out by @NickParsons. Component remounting will happen naturally in production aswell and should be thought about in your code. If it doesnt work in dev mode, there is a very real chance it wont in prod – N.K Apr 05 '23 at 12:04

0 Answers0