Scenario 1: Update a child property or value of an object (or array) property of State.

Use Hook Function Component

function App() {
    const [arr, updateArr] = useState([]);
    const addList = () = > {
        arr.push('Hello React');
        updateArr(arr);
    };
    return (
        <div>
            {
                arr.map((item, index) => (
                    <p key={index}>{index} {item}</p>))}<button onClick={addList}>Add the List</button>
        </div> 
    );
}
Copy the code

Use the Class Component

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            arr: []
        }
    }

    addList = () = > {
        let arr = this.state.arr;
        arr.push('Hello React');
        this.setState({
            arr: arr
        }, () = > {
            console.log(this.state.arr);
        });
    };

    deleteList = () = > {
        const { arr } = this.state;
        arr.splice(0.1);
        this.setState({
            arr: arr
        }, () = > {
            console.log(this.state.arr);
        });
    };

    render() {
        const { arr } = this.state;
        return (
            <div>
                {
                    arr.map((item, index) => (
                        <p key={index}>{index} {item}</p>))}<button onClick={this.addList}>Add the List</button>
            <button onClick={this.deleteList}>Delete the List</button>
        </div>); }}Copy the code

Result: Hook Function Component push array length does not change, Class Component is fine.

Cause: Rerendering is not triggered by directly modifying a child property or value of an object (or array) property of State in Hook and then directly setting it.

  • For Class Component, state is Immutable, and setState must generate a new state reference. It reads state in this.state, so every time the code executes it gets the latest reference to state.
  • For Hook Function Component, the data generated by useState is also Immutable. When a new value is Set as the second parameter in the array, the original value will form a new reference for the next rendering.

The solution

Change reference address

function App() {
    const [arr, updateArr] = useState([]);
    const addList = () = > {
        arr.push('Hello React');
		// Deep copy is implemented by extending operators
        updateArr([...arr]);
    };
    return (
        <div>
            {
                arr.map((item, index) => (
                    <p key={index}>{index} {item}</p>))}<button onClick={addList}>Add the List</button>
        </div> 
    );
}
Copy the code

Scenario 2: Change state in setTimeout.

Use Hook Function Component

function App() {
    const [count, updateCount] = useState(0);

    useEffect(() = > {
        let timer = setTimeout(() = > {
            updateCount(1);
            getCount();
        }, 1000);
        return () = > {
            clearTimeout(timer); }} []);const getCount = () = > {
        console.log(count); // result: 0
    };
    
    return (
        <div>{count}</div> 
    );
}
Copy the code

Use the Class Component

let timer = null;
export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0}}componentDidMount() {
        timer = setTimeout(() = > {
            this.setState({
                count: 1
            })
            this.getCount();
        }, 1000);
   }


   getCount = () = > {
        console.log(this.state.count); // result: 1
   }

   componentWillUnmount() {
        clearTimeout(timer);
   }

    render() {
        const { count } = this.state;
        console.log(count); // result: 1
        return (
            <div>{count}</div>); }}Copy the code

Result: Change count with Hook Function Component and the page displays 1. Change count with Class Component and the page displays 1 with getCount.

Cause: Hook Function Comoponent failed to pass this because state was read. , so that each setTimeout reads the data of the current render closure environment. Although the latest value changes with the latest render, the state of the old render is still the old value.

The solution

Use the ref

function App() {
    const [count, updateCount] = useState(0);

    useEffect(() = > {
        let timer = setTimeout(() = > {
            updateCount(1);
            getCount();
        }, 1000);
        return () = > {
            clearTimeout(timer); }} []);let ref = useRef();
    ref.current = count;
    const getCount = () = > {
        console.log(ref.current); // result: 1
    };
    
    return (
        <div>{count}</div> 
    );
}
Copy the code

The End~

Attached are the relevant articles that I think are worth reading:

1.react-hook-usestate-setState 2. Function Component Primer