javascriptreactjsreact-nativereact-hooks

How can I force a component to re-render with hooks in React?


Considering below hooks example

   import { useState } from 'react';

   function Example() {
       const [count, setCount] = useState(0);

       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => setCount(count + 1)}>
                  Click me
               </button>
          </div>
        );
     }

Basically we use this.forceUpdate() method to force the component to re-render immediately in React class components like below example

    class Test extends Component{
        constructor(props){
             super(props);
             this.state = {
                 count:0,
                 count2: 100
             }
             this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component 
        }
        setCount(){
              let count = this.state.count;
                   count = count+1;
              let count2 = this.state.count2;
                   count2 = count2+1;
              this.setState({count});
              this.forceUpdate();
              //before below setState the component will re-render immediately when this.forceUpdate() is called
              this.setState({count2: count
        }

        render(){
              return (<div>
                   <span>Count: {this.state.count}></span>. 
                   <button onClick={this.setCount}></button>
                 </div>
        }
 }

But my query is How can I force above functional component to re-render immediately with hooks?


Solution

  • This is possible with useState or useReducer, since useState uses useReducer internally:

    const [, updateState] = useState();
    const forceUpdate = useCallback(() => updateState({}), []);
    

    forceUpdate isn't intended to be used under normal circumstances, only in testing or other outstanding cases. This situation may be addressed in a more conventional way.

    setCount is an example of improperly used forceUpdate, setState is asynchronous for performance reasons and shouldn't be forced to be synchronous just because state updates weren't performed correctly. If a state relies on previously set state, this should be done with updater function,

    If you need to set the state based on the previous state, read about the updater argument below.

    <...>

    Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.

    setCount may not be an illustrative example because its purpose is unclear but this is the case for updater function:

    setCount(){
      this.setState(({count}) => ({ count: count + 1 }));
      this.setState(({count2}) => ({ count2: count + 1 }));
      this.setState(({count}) => ({ count2: count + 1 }));
    }
    

    This is translated 1:1 to hooks, with the exception that functions that are used as callbacks should better be memoized:

       const [state, setState] = useState({ count: 0, count2: 100 });
    
       const setCount = useCallback(() => {
         setState(({count}) => ({ count: count + 1 }));
         setState(({count2}) => ({ count2: count + 1 }));
         setState(({count}) => ({ count2: count + 1 }));
       }, []);