React Hooks review #1: useState

This is chapter one of the React Hooks series. The goal is simple: summarize all the eleven official React hooks:

useState

Returns a stateful value, and a function to update it.

react js org

Since React re-renders any component multiple times, useState provides a was to have stateless variables in the component.

Syntax:

const [state, setState] = useState(initialState);

Simple example:

const Counter: React.FC<{}> = () => {
    let nonCounter: number = 0;
    const [counter,setCount] = React.useState(0);

    const add = () => {
        setCount(counter + 1);
        nonCounter = nonCounter + 1;
    }
    return (
        <div>
            NoneCount: {nonCounter}, 
            Counter: {counter},  
            (<span onClick={add}> + </span>) 
        </div>
    )
}

As can be seen, the re-rendering resets the value of nonCounter, so it never increases. But the counter saves as a stateful value, is increased.

Here is a simplistic example of how the useState works:

function useState(initialValue) {
    var _val = initialValue;
    function state() {
      return _val;
    }
    function setState(newVal) {
      _val = newVal;
    }
    return [state, setState];
  }

I wanted to check the claim that when updating a state hook with the same value, React will not rendering the children or firing effects. And even if the element will be rerendered, it still will not go deeper in the tree.

So, here is the same counter with a log to track re-renders. And a nested component, that writes a log each re-render:

type LogRenderProps = {
    counter : number
}

const LogRender:React.FC<LogRenderProps> = ({counter})=> {
    console.log('LogRender');
    return <p>Log renderer - {counter}</p>
}

const Counter: React.FC<{}> =  ({}) => {
    console.log('counter');
    const [count,setCount] = React.useState(0);
    
    return (
        <div>
            Count {count}: 
            (<span onClick={()=>{setCount(count + 1)}}> + </span>) 
            (<span onClick={()=>{setCount(count)}}> 0 </span>)
            <LogRender counter={count} />
        </div>
    )
}

As can be seen in the log, changing the counter, triggers both re-renders. But setting the useState to the same value re-renders the component once, and don’t change the nested components.

Doing the same example with the same object will not re-render at all:

const Counter: React.FC<{}> =  ({}) => {
    console.log('counter')
    const [counter,setCount] = React.useState({number:0});

    return (
        <div>
            Count {counter.number}: 
            (<span onClick={()=>{ setCount({n: counter.nunber+1}); }}> same </span>)
            (<span onClick={()=>{ setCount(counter); }}> change </span>)
            <LogRender counter={counter} />
        </div>
    )
}

useState setter method

The useState setter is an async function, so the following code will not work correctly, and only the second setter will have an effect, and the component will render only once.

const Counter: React.FC<{}> = () => {
    console.log('rerender');
    const [count,setCount] = React.useState('Who to show?');

    const add = () => {
        setCount('Will never be displayed');
        setCount('Im the winner');
    }
    return (
        <div>
            Count {count}: 
            (<span onClick={add}> + </span>) 
        </div>
    )
}

As seen above, Since React triggers rendering on state change, if you change elements in an array or change values inside an object, the state will not re-render:

const Counter: React.FC<{}> = () => {
    const [obj,setObj] = React.useState({name:'default name'});

    const changeName = () => {
        obj.name = "A new name you will never see";
        setObj(obj);
    }
    return (
        <div>
            {obj.name}: 
            (<span onClick={changeName}> change name </span>) 
        </div>
    )
}

Resetting value inside array or object

To re-render you need to reset the state value. But changing an inner value will not trigger the component. The most simple way is using spread:

const Counter: React.FC<{}> = () => {
    const [obj,setObj] = React.useState({name:'default name'});

    const changeName = () => {
        obj.name = "This name you can see";
        setObj({...obj});
    }
    return (
        <div>
            {obj.name}: 
            (<span onClick={changeName}> change name </span>) 
        </div>
    )
}

Or for an an array:

    const addValue = () => {
        obj.push("This name you can see");
        setObj([...obj]);
    }