# Closure in React JS

In React closures are mostly commonly encountered within Functional Components, specifically when using Hooks like useState, useEffect and useCallback

Because a React component is just a function which re-runs every time the state changes, closures are responsible for "remembering" data between those renders

### Closures in Hooks

When you define a function inside a React component, that function "closes over" the props and state available at the time it was created

1.  The useState Snapshot
    

Each render has its own "snapshot" of state. A closure created during a specific render will always see the state from that render.

```typescript
function Counter() {
  const [count, setCount] = useState(0);

  const handleAlert = () => {
    // This function is a closure. 
    // It captures 'count' at the moment the user clicks the button.
    setTimeout(() => {
      alert("Count was: " + count);
    }, 3000);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={handleAlert}>Show Alert in 3s</button>
    </div>
  );
}
```

*   Behaviour : If you click "Show Alert" and then quickly click "Increment" three times , the alert will still show 0
    
*   Reason : The closure inside setTimeout captured count when it was 0. It dosen't care that the component re-rendered and updated count to 3
    

### The "Stale Closure" Problem

This is the most common bug in React . It happens when a function (like a event listener or a useEffect) captures an old version of a variable and never gets updated

```typescript
useEffect(() => {
  const interval = setInterval(() => {
    console.log(count); // Will always log '0' if 'count' isn't in dependencies
  }, 1000);
  
  return () => clearInterval(interval);
}, []); 
```

Now to fix this add count in the dependency array

```typescript
useEffect(() => {
  const interval = setInterval(() => {
    console.log(count); // Will always provide the latest state of count
  }, 1000);
  
  return () => clearInterval(interval);
}, [count]); 
```

Now when u provide count in the dependency array of count it creates a new closure with the value that exists during that time if it is incremented it will take that incremented value and create the closure

### How to "Escape" Closures

Sometimes you want a function to always have the "latest" value without re-creating the function. You have two main tools :

1.  Functional Updates
    

If you need to update state based on the previous state, use a function inside a `setCount` :

```typescript
import { useEffect, useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setInterval(() => {
            setCount(count+1) // this won't work because it depends on closure to update and closure holds old value 
            setCount(prev => prev + 1);// this works because it is totally skipping closures and using react's internal state's value  
    }, 1000);
  }, [])

/** But over here when u are are displaying count it is the latest value after re-render **/
  return <h1>{count}</h1>;
}
```

2.  useRef
    

A Ref is an object that stays the same between renders. It does not create a closure "snapshot" , it always provides the latest value

```typescript
const countRef = useRef(0);

const logLatest = () => {
  console.log(countRef.current); // Always the current value
};
```

### Conclusion :

```js
import { useEffect, useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setInterval(() => {
      // Depends on stale closure (count from initial render)
      // setCount(count + 1);

      // Correct: uses latest state from React (not closure)
      setCount(prev => prev + 1);

      // Still stale — closure value (count = 0 forever here)
      console.log(count);
    }, 1000);
  }, []);

  return <h1>{count}</h1>;
}
```

* * *

### 1\. `setCount(count + 1)`

*   Uses `count` **from closure**
    
*   Closure is created once (`useEffect([])`)
    
*   So `count = 0` forever inside interval , Stale → wrong updates
    

* * *

### 2\. `setCount(prev => prev + 1)`

*   Uses **React’s latest state internally**
    
*   Does NOT rely on closure , Always correct
    

> Important correction: It’s not “skipping closure completely” — it just **doesn’t depend on it for the value**

* * *

### 3\. `console.log(count)`

*   Reads from **closure**
    
*   Closure is stale (`count = 0`)
    

* * *

### 4\. Why UI still updates correctly?

```js
return <h1>{count}</h1>;
```

*   This runs on every **re-render**
    
*   Uses **latest state from React** , Always fresh
    

* * *

## About `[count]` dependency

```js
useEffect(() => { ... }, [count]);
```

*   Gives fresh `count` (new closure every render)
    
*   But destroys & recreates interval every time
    

So:

*   Works logically
    
*   Not ideal for intervals
    

* * *

## Final mental model

| Operation | Uses |
| --- | --- |
| `setCount(prev => prev + 1)` | latest state (React) |
| `console.log(count)` | closure (can be stale) |
| UI render (`{count}`) | latest state |

Important points to carefully consider for understanding the article

1.  When a value in the dependency array changes, React re-runs the hook. This causes a new function to be created and the function forms a new closure capturing the latest state and props , *so in our blog examples the function is set setTimeout or setInterval which are creating a closure*
    
2.  Every time a React component renders , all the functions inside the component gets newly created , Each of these function form a closure over the state and props from that specific render (snapshot) , So each specific render has their own closure
    
3.  Each render creates new closures, but old closures can still run if something external (like a timer or listener) holds onto them.
