React Hooks - Is it recommended to use a separate state handler for every button/toggle switch?

506 Views Asked by At

Super new to React and trying out something. I have 3 toggle switches on a single screen. Should I be using a separate state handler for every switch?

Right now I have used just one, but obviously this will not work as expected because isChecked is being shared by all the three toggle switches.

const [isChecked, setIsChecked] = useState(false)
const data = [
  ['Sample Text 1', <ToggleSwitch showLabel={true} checked={isChecked} onChange={() => setIsChecked(!isChecked)}/>],
  ['Sample Text 2', <ToggleSwitch showLabel={true} checked={isChecked} onChange={() => setIsChecked(!isChecked)}/>],
  ['Sample Text 3', <ToggleSwitch showLabel={true} checked={isChecked} onChange={() => setIsChecked(!isChecked)}/>]
]

Can I use some sort of id to change the state of individual switches using the same handler(isClicked)? Or is it recommended to use separate state handlers for every switch?

2

There are 2 best solutions below

0
On

It really deepends what you want to achive.

The useState React hook controlls of one state. The useReducer can control multiple states which related.

1
On

Yes, if it's a static form, usually you just use several useState. Because you can make sure the variable(state) names are readable, and it's easy to add new fields (or remove unneeded ones). It might look dumb but the maintainability and readability are better than any kind of "clever code".

But sometimes we may have a more dynamic form. For example, all the fields are from some backend APIs, or you have a huge list/table and the toggle fields don't have any semantic meanings, then of course you have to write the code in a more dynamic way.

Here is an example that uses a useReducer:

const initialState = {
  1: { checked: false },
  2: { checked: false },
  3: { checked: false },
  4: { checked: false },
}
function reducer(state, action) {
  const { type, payload } = action
  switch (type) {
    case 'toggle':
      return {
        ...state,
        [payload.id]: { checked: !state[payload.id].checked },
      }
  }
}
const YourComponent = () => {
  const [toggleState, dispatch] = useReducer(reducer, initialState)
  return (
    <div>
      {Object.keys(toggleState).map((id) => (
        <ToggleSwitch
          key={id}
          checked={toggleState[id].checked}
          onChange={() => dispatch({ type: 'toggle', payload: { id } })}
        />
      ))}
    </div>
  )
}

I'm using objects here. An array of objects is also fine, or even a simple array: [false, false, false, false]. I think it depends on your use case.

It's also possible to use just one setState, but useReducer in this case is flexible. You can easily add new actions like checkAll or uncheckAll, and put your logics in the reducer.

And then, you can even extract this to a custom hook. Please check this CodeSandbox https://codesandbox.io/s/stackoverflow-toggles-l9b2do?file=/src/App.js for a full working example.

And just for your reference, I also tried to modify your code and used just useState and an array:

const YourComponent = () => {
  const [toggles, setToggles] = useState([
    ['text 1', true],
    ['text 2', false],
    ['text 3', false],
    ['text 4', false],
  ])
  const handleChange = (index) => {
    const newState = [...toggles]
    newState[index] = [newState[index][0], !newState[index][1]]
    setToggles(newState)
  }
  return (
    <div>
      {toggles.map(([text, checked], index) => (
        <ToggleSwitch
          key={index}
          checked={checked}
          onChange={() => handleChange(index)}
        />
      ))}
    </div>
  )
}