Scope and closures are two of the most important concepts in JavaScript. They are also the most misunderstood. In this chapter, we will learn about closures and how they are used in React.
Scope and Closures
In JavaScript, scope is the set of variables, objects, and functions you have access to at a given time. There are two types of scope: global scope and local scope.
- Global scope: variables declared outside of a function are in the global scope. They can be accessed from anywhere in the code.
- Local scope: variables declared inside a function are in the local scope. They can only be accessed from inside the function.
Every time you create a function, a new scope is created. This is called function scope. In JavaScript, variables are scoped to the function in which they are declared. This means that variables declared inside a function are not accessible from outside the function.
Closure is a function that has access to its parent scope, even after the parent function has returned. In other words, a closure is a function that remembers the environment in which it was created. This means that a closure can access variables from its parent scope, even after the parent function has returned.
Stale Closures
Let’s take a look at this example:
const something = value => {
const inside = () => {
console.log(value);
};
return inside;
};
something("Hello")(); // Hello
something("World")(); // World
This works as expected. But what if we cached the inside
function and called it later?
const cache = {};
const something = value => {
if (!cache.current) {
cache.current = () => {
console.log(value);
};
}
return cache.current;
};
something("Hello")(); // Hello
something("World")(); // Hello
Hmmm 🤔. This is not what we expected. The inside
function is supposed to remember the environment in which it was created. But it doesn’t. It remembers the environment in which it was called. This is called a stale closure.
A function takes a snapshot of its parent scope when it is created, and because we are caching the function, it is not being recreated. So, it is not taking a new snapshot of its parent scope. It is using the same snapshot every time it is called. This is why it is called a stale closure.
Closures in React
We use closures in React all the time, but we don’t realize it.
Let’s take a look at this example:
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
Components are just functions. So, when we call the Counter
component, a new scope is created. This scope contains the count
variable and the increment
function. The increment
function is a closure. It has access to the count
variable, even after the Counter
component has returned.
useCallback & useMemo
The useCallback
hook is used to memoize functions. Every time we use useCallback , we create a closure, and the function that we pass to it is cached.
That dependency array is used to determine whether the function should be memoized or not. This is how we escape the stale closure trap for useCallback. Same thing for useMemo.
Refs
in the following example , We want onClick function that is stable between re- renders but also has access to the latest state without re-creating itself.
You can escape the stale closure in some useRef
use cases by using the ref.current property. Let’s take a look at this example:
const Form = () => {
const [value, setValue] = useState();
const ref = useRef();
useEffect(() => {
ref.current = () => {
// will be latest
console.log(value);
};
});
const onClick = useCallback(() => {
// will be latest
ref.current?.();
}, []);
return (
<>
<input
type="text"
value={value}
onChange={e => setValue(e.target.value)}
/>
<HeavyComponentMemo title="Welcome closures" onClick={onClick} />
</>
);
};
Notice how onClick is stable between re-renders but also has access to the latest state without re-creating itself.
Summary
This was a lot of mental gymnastics, but it’s worth it. If you want to learn more about closures, I highly recommend you to read the book You Don’t Know JS: Scope & Closures.
If you have any suggestions or questions, feel free to reach out to me on Twitter or LinkedIn.
P.S. this post is inspired by the book, but it’s not a summary of it. I’m just sharing what I learned from it. If you want to learn more about React, I highly recommend you to read it.