I have read A Complete Guide to useEffect - Swimming Against the Tide at Overreacted.
The example shows that if we want to get the latest count
, we can use useRef
to save the mutable variable, and get it in async function laster:
function Example() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => { // Set the mutable latest value latestCount.current = count; setTimeout(() => { // Read the mutable latest value console.log(`You clicked ${latestCount.current} times`); }, 3000); }); // ... }
However, I can do the same thing by creating a variable outside the component function, such as:
import React, { useState, useEffect, useRef } from 'react'; // defined a variable outside function component let countCache = 0; function Counter() { const [count, setCount] = useState(0); countCache = count; // set default value useEffect(() => { setTimeout(() => { // We can get the latest count here console.log(`You clicked ${countCache} times (countCache)`); }, 3000); }); // ... } export default Counter;
Are both ways practical, or is there anything bad if I define the variable outside function component?
The useRef Hook allows you to persist values between renders. It can be used to store a mutable value that does not cause a re-render when updated. It can be used to access a DOM element directly.
useRef can be used to store local mutable value in a component. It doesn't participate in rerendering (unline state data). useMemo is used to memoize (like we do in Dynamic Programming, concept wise) and skip recalculation.
If the useRef is initialized with null and the initial value does not belong to the provided type, the current property is immutable.
useRef returns a mutable ref object whose .current property is initialized to the passed argument ( initialValue ). The returned object will persist for the full lifetime of the component. Essentially, useRef is like a “box” that can hold a mutable value in its .current property.
useRef
will assign a reference for each component, while a variable defined outside a function component scope will only assigned once.
useRef
reference life span is component's life span (it "dies" when the component unmounts, while JS variables are scope-blocked).
Hence, define constant purpose variables outside of the component scope:
// This statement will only called once const DEFAULT_VALUE = 5; function Component() { // use DEFAULT_VALUE. }
Defining the same statement inside component's scope, will redefine it on every render:
// We can do better function Component() { // Redefined on every render const DEFAULT_VALUE = 5; }
Now for the question:
First, we can't actually reflect UI changed with outer scoped variables since changing them does not trigger render (only React API does).
Therefore the reflected value is its closure value.
let countCache = 0; function Counter() { ... countCache = 0; useEffect(() => { countCache = count; }); ... // closure value of countCache return <div>{countCache}</div> }
Now, whats special with outer scope variables that they are global to the module itself, so using its value is global to all components referencing it (in the module).
For example if you want to count how many times the component mounted in your whole application life span, increase the variable inside
useEffect
on mount (couldn't find any other possible use-case).
let howMuchMounted = 0; function Component() { useEffect(() => { howMuchMounted += 1, [] }; }
To reflect the differences of outer variable and useRef
reference, in the next example, on button click, you may notice that the variable
is global for both of the components, while the reference
is always updated to current state value.
import React, { useEffect, useRef, useReducer } from "react"; import ReactDOM from "react-dom"; // defined a variable outside function component let countCache = 0; function Counter() { const [num, count] = useReducer((num) => num + 1, 0); const countRef = useRef(count); useEffect(() => { // set count value on count change countCache = num; countRef.current = num; }, [num]); return ( <> <button onClick={count}>Count</button> <h3>state {num}</h3> <h3>variable {countCache}</h3> <h3>reference {countRef.current}</h3> </> ); } export default function App() { return ( <> <Counter /> <hr /> See what happens when you click on the other counter <hr /> <Counter /> </> ); }
Please see a follow up question on useEffect
use cases, there are many common mistakes when working with useRef
references inside useEffect
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With