A brief discussion on the pitfalls of react useEffect closure

A brief discussion on the pitfalls of react useEffect closure

Problem code

Look at a closure problem code caused by useEffect

const btn = useRef();
const [v, setV] = useState('');

useEffect(() => {
    let clickHandle = () => {
        console.log('v:', v);
    }
    btn.current.addEventListener('click', clickHandle)
    
    return () => {
        btn.removeEventListener('click', clickHandle)
    }
}, []);
    
const inputHandle = e => {
    setV(e.target.value)
}

return (
        <>
            <input value={v} onChange={inputHandle} />
            <button ref={btn} >Test</button>
        </>
    )

The dependency array of useEffect is empty, so the internal code will only be executed once after the page rendering is completed, and once again when the page is destroyed. At this time, enter any character in the input box and click the test button, the output is empty. No matter what characters you enter afterwards and click the test button again, the output result is still empty.

Why is this happening? In fact, it is caused by closure.

Cause

The scope of a function is determined when the function is defined.

When registering a click event for btn, the scope is as follows:

The accessible free variable v is still null at this time. When a click event is triggered, the click callback function is executed. At this time, the execution context is created first, and the scope chain is copied to the execution context.

  • If no characters are entered in the input box, the v you get when you click it is still the original v
  • If a character is entered in the input box, setV is called to modify the state, the page triggers render, and the internal code of the component is re-executed, re-declaring a v, and v is no longer the original v. The v in the scope of the click event is still the old v. These are two different v

Generate scene

  • Event binding. For example, in the sample code, the event is only bound once after the initial rendering of the page is completed. For example, if echarts is used, the instance of echarts is obtained in useEffect and the event is bound.
  • Timer. Registering a timer after the page is loaded will also cause such a closure problem in the function within the timer.

Solution

Here are 5 solutions to this closure problem:

1. Modify v directly by assignment, and wrap the method that modifies v with useCallback

Wrap the method that modifies v with useCallback. The function wrapped by useCallback will be cached. Since the array of dependencies is empty, the v modified by direct assignment here is the old v. This method is not recommended because setState is the officially recommended way to modify state. Here, setV is still used just to trigger rerender.

// Change the declaration of v from const to var to facilitate direct modification var [v, setV] = useState('');

const inputHandle = useCallback(e => {
    let { value } = e.target
    v = value
    setV(value)
}, [])

2. Add v to useEffect's dependencies

This may be the first solution that most people think of. Since v is old, why not re-register the event every time v is updated? However, this will result in re-registration every time v is updated. In theory, an event that only needs to be registered once will become multiple times.

3. Avoid redeclaration of v

Declare a variable instead of v with let or var, and modify the variable directly instead of triggering render with setState related functions. This way, the variable will not be re-declared, and the "latest" value can be obtained in the click callback function. However, this method is not recommended. For this example, the input component always displays an empty value because there is no rerender, which does not meet the expected operation.

4. Use useRef instead of useState

const btn = useRef();
const vRef = useRef('');
const [v, setV] = useStat('');

useEffect(() => {
    let clickHandle = () => {
        console.log('v:', vRef.current);
    }
    btn.current.addEventListener('click', clickHandle)
    
    return () => {
        btn.removeEventListener('click', clickHandle)
    }
}, []);

const inputHandle = e => {
    let { value } = e.target
    vRef.current = value
    setV(value)
}

return (
        <>
            <input value={v} onChange={inputHandle} />
            <button ref={btn} >Test</button>
        </>
    )

The reason why the useRef solution is effective is that each input change modifies the current property of the vRef object, and vRef is always that vRef, even if it is rerendered. Since vRef is an object, the value of the variable stored in the stack memory is the address of the object in the heap memory, which is just a reference. Only a certain property of the object is modified, and the reference will not change. So the scope chain in the click event always accesses the same vRef

5. Replace v with an object type

In fact, just like using useRef, as long as it is an object, modifying only a certain attribute will not change the address pointed to by the state.

Code address

Click here to see the test code

This is the end of this article about the pitfalls of react useEffect closure. For more relevant react useEffect closure content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Understanding and using React useEffect
  • The difference between useEffect and useLayoutEffect in React

<<:  Linux exposes Sudo privilege escalation vulnerability, any user can also run root commands

>>:  How to specify parameter variables externally in docker

Recommend

CSS to achieve compatible text alignment in different browsers

In the front-end layout of the form, we often nee...

JavaScript imitates Jingdong magnifying glass effect

This article shares the specific code for JavaScr...

Detailed explanation of the use of various MySQL indexes

1. Slow query log 1.1 MySQL log types Logs are us...

How to change the encoding to utf-8 in mysql version 5.7 under windows

Preface I just started learning MySQL and downloa...

How to set the page you are viewing to not allow Baidu to save its snapshot

Today, when I searched for a page on Baidu, becaus...

Drop-down menu and sliding menu design examples

I found a lot of websites that use drop-down or sl...

Two methods to implement Mysql remote connection configuration

Two methods to implement Mysql remote connection ...

Minimalistic website design examples

Web Application Class 1. DownForEveryoneOrJustMe ...

Details on how to use class styles in Vue

Table of contents 1. Boolean 2. Expression 3. Mul...

Two ways to export csv in win10 mysql

There are two ways to export csv in win10. The fi...

No-nonsense quick start React routing development

Install Enter the following command to install it...

Three examples of nodejs methods to obtain form data

Preface Nodejs is a server-side language. During ...