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

The practical process of login status management in the vuex project

Table of contents tool: Login scenario: practice:...

Four modes of Oracle opening and closing

>1 Start the database In the cmd command windo...

Application examples of WeChat applet virtual list

Table of contents Preface What is a virtual list?...

Hello dialog box design experience sharing

"What's wrong?" Unless you are accus...

MySQL database SELECT query expression analysis

A large part of data management is searching, and...

How to implement online hot migration of KVM virtual machines (picture and text)

1. KVM virtual machine migration method and issue...

Detailed Introduction to Nginx Installation and Configuration Rules

Table of contents 1. Installation and operation o...

base target="" controls the link's target open frame

<base target=_blank> changes the target fram...

Detailed explanation of the use of shared memory in nginx

In the nginx process model, tasks such as traffic...

Discussion on the way to open website hyperlinks

A new window opens. Advantages: When the user cli...

How to use Cron Jobs to execute PHP regularly under Cpanel

Open the cpanel management backend, under the &qu...

How to detect file system integrity based on AIDE in Linux

1. AIDE AIDE (Advanced Intrusion Detection Enviro...

Summary of methods for inserting videos into HTML pages

Now if you want to use the video tag in a page, y...

js to achieve a simple magnifying glass effect

This article shares the specific code of js to ac...