3

I am trying to understand which way of using useState hook is a better practise. Please consider these two examples of a simple React component:

  1. multiple useState hooks

    const Cake = () => {
        const [topping, setTopping] = useState('');
        const [icing, setIcing] = useState('');
        const [fruit, setFruit] = useState('');
        const [base, setBase] = useState('');
        const [cake, setCake] = useState({topping: '', icing: '', fruit: '', base: ''});
    
        const createCake = () => {
            setCake({
                topping: topping,
                icing: icing,
                fruit: fruit,
                base: base
            });
            console.log(cake);
        }
    
        return (
            <div>
                <p>Choose from possible toppings:</p>
                <Select options={toppings} onChange={e => setTopping(e.target.value)} value={topping}/>
                <p>Choose from possible icings:</p>
                <Select options={icings} onChange={e => setIcing(e.target.value)} value={icing}/>
                <p>Choose from possible fruits:</p>
                <Select options={fruits} onChange={e => setFruit(e.target.value)} value={fruit}/>
                <p>Choose from possible bases:</p>
                <Select options={bases} onChange={e => setBase(e.target.value)} value={base}/>
    
                    <button onClick={createCake}>Create</button>
                </div>
            );
        }
    
        export default Cake;
    
    
  2. One useState hook, but all properties of a whole object are updated multiple times

    import React, {useState} from 'react';

     const Cake = () => {
         const [cake, setCake] = useState({topping: '', icing: '', fruit: '', base: ''});
    
         const setTopping = (e) => {
             setCake({
                 topping: e.target.value,
                 icing: cake.icing,
                 fruit: cake.fruit,
                 base: cake.base
             });
         };
    
         const setIcing = (e) => {
             setCake({
                 topping: cake.topping,
                 icing: e.target.value,
                 fruit: cake.fruit,
                 base: cake.base
             });
         };
    
         const setBase = (e) => {
             setCake({
                 topping: cake.topping,
                 icing: cake.icing,
                 fruit: cake.fruit,
                 base: e.target.value
             });
         };
    
         const setFruit = (e) => {
             setCake({
                 topping: cake.topping,
                 icing: cake.icing,
                 fruit: e.target.value
                 base: cake.base
             });
         };
    
         const createCake = () => {
             console.log(cake);
         };
    
         return (
             <div>
                 <p>Choose from possible toppings:</p>
                 <Select options={toppings} onChange={setTopping}/>
                 <p>Choose from possible icings:</p>
                 <Select options={icings} onChange={setIcing} value={icing}/>
                 <p>Choose from possible fruits:</p>
                 <Select options={fruits} onChange={setFruit} value={fruit}/>
                 <p>Choose from possible bases:</p>
                 <Select options={bases} onChange={setBase} value={base}/>
    
                 <button onClick={createCake}>Create</button>
                 </div>
             );
         }
    
         export default Cake;
    

Which of this option would be consider as a better one? Are there any mistakes/bad pracices here that I should avoid in the future?

Zuzu
  • 75
  • 7

3 Answers3

5

It depends on how the values change. If they change together keep them in one variable, if not then split the object.

One major difference in setState of hooks and classes is: For class components this.setState auto merges the state object.

this.setState({ topping: 'newTopping' });

But in hooks, you have to replace the state variable with a new one and there is no merging.

That is the reason you would see spread operator ... being used to create copy of object and then replace the object with setState.

setCake(cake => { ...cake, topping : 'newTopping' });

So if your state is logically supposed to be grouped together and not very nested, you can keep it together.

There is a section in hooks FAQ, that directly answers this. Directly from the docs:

we recommend to split state into multiple state variables based on which values tend to change together.

Tushar Shahi
  • 16,452
  • 1
  • 18
  • 39
  • Multiple use states are however difficult to test. Imagine where you have to mock a value of the 5th state variable. You have to mock the values of the 1st to 4th state before you can mock the 5th state variable. What if someone changes the order of the useState variables? What if someone adds a variable in between the existing variables? Then, you would have to update again your unit test implementations for React.useState unless anyone knows a better approach to mocking useState hooks in Jest. – Michael Boñon Jul 12 '22 at 08:50
1

You can choose any approach based on your use case. But according to the official document, it is recommended to split the state into multiple state variables based on which values tend to change together.

Here is a link talking about the use of single or multiple variables you can refer to it as well.

https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

I would like to suggest if you are going for single useState then try to write code as follows:

setCake((cake) => ({
  ...cake,
  fruit: e.target.value
}));
poo
  • 1,080
  • 3
  • 10
0

You can use either of the methods to update the state, but it depends on your use case at the end of the day. In a real-world application, you will encounter both scenarios. Just make sure which component type you are using i.e: class or functional. Class component this.setState auto-merge the state object while functional component need spread operator(...) to merge the remaining states

Atabic Umer
  • 152
  • 4