Skip to content
Yeabsira on GitHub Yeabsira on Twitter

Closure in React

In JavaScript, a closure is formed when an inner function is returned from an outer function, allowing the inner function to access the outer function’s variables and parameters even after the outer function has completed execution. Closures are widely used in React to manage state and implement event handlers.

Here’s a simple example of closure in React:

import { useState } from 'react'

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

	function handleClick() {
		setCount(count + 1)
	}

	return (
		<div>
			<p>You clicked {count} times</p>
			<button onClick={handleClick}>Click me</button>
		</div>
	)
}

In this example, useState is a hook that returns an array with two elements: the current state value, and a function to update it. The count variable and the setCount function are declared using destructuring assignment.

The handleClick function is defined inside the Counter function, and it has access to the count and setCount variables through closure. When the user clicks the button, the handleClick function is called, and it updates the state by calling setCount with the new value of count.

Using closure in this way allows us to manage states in a functional component without having to use class components or global variables. It’s a powerful tool for building complex UIs in React.

Another use case of closure in React is for creating reusable components with private states.

For example, let’s say you want to create a component that displays a list of items and allows the user to add and remove items from the list. You could use closure to keep track of the list of items internally, without exposing it to the outside world.

// App.js

import { useState } from 'react'
import AddItemForm from './AddItemForm'

function List() {
	const [items, setItems] = useState([]) // private states

	// simply setter function, this can be passed as prop into another component for modifying internal state without exposing the internal state values
	function addItem(item) {
		setItems([...items, item])
	}

	function removeItem(index) {
		// we don't need the item that's why we add _ in the filter first parameter.
		setItems(items.filter((_, i) => i !== index))
	}
	return (
		<div>
			<ul>
				{items.map((item, index) => (
					<li key={index}>
						{item} <button onClick={() => removeItem(index)}>Remove</button>
					</li>
				))}
			</ul>
			<AddItemForm onAdd={addItem} />
		</div>
	)
}

// AddItemForm.js
function AddItemForm({ onAdd }) {
	const [value, setValue] = useState('')

	function handleSubmit(event) {
		event.preventDefault()
		onAdd(value)
		setValue('')
	}

	return (
		<form onSubmit={handleSubmit}>
			<input
				type='text'
				value={value}
				onChange={(e) => setValue(e.target.value)}
			/>
			<button type='submit'>Add</button>
		</form>
	)
}

In this example, the List component declares a state variable item using useState, and defines two functions addItem and removeItem to modify the state. These functions are passed down to the AddItemForm component as props.

The AddItemForm component also uses useState to declare a state variable value, which represents the current value of the input field. When the form is submitted, the handleSubmit function is called, which adds the new item to the list by calling onAdd with the current value of value.

Because the items, addItem, and removeItem variables are declared inside the List component, they are not visible or modifiable from the outside. They are only accessible to the List component and its children, thanks to closure. This makes it easy to reuse the List component in other parts of your application, without worrying about name collisions or unintended side effects.

let’s see the implementation real quick

function useState(initialValue) {
	let value = initialValue
	const state = value
	const setState = (newValue) => (value = newValue)
	return [state, setState]
}
const [message, setMessage] = useState('Hello')
console.log(message) // ??
setMessage('World')
console.log(message) // ??

when we run this code the result will be

const [message, setMessage] = useState('Hello')
console.log(message) // Hello
setMessage('World')
console.log(message) // Hello

wait what? what happened?

That wasn’t the correct implementation even if I said that was the correct one, sorry but

here is the catch, or if you spotted it good for you, you have understood what closure means.

let me show you where we got wrong when we implemented the useState or you can say bug

function useState(initialValue){
	let value = initialValue
	-- const state = value  ❌
	++ const state = () => value ✅ // making this as getter function
	const setState = (newValue) => value = newValue // this is actual setter function
	return [state,setState]
}
const [message,setMessage] = useState('Hello')
console.log(message()) // Hello, we calling message cuz it's a getter function now
setMessage('World')
console.log(message()) // World

The problem was the variable value is a private state/value. we are mutating the variable value using setMessage but we want to access the newly passed value through setMessage so we have to use closure to access the internal state/value, in this case, the variable value using variable state in the useState function.

In conclusion, Closures are a fundamental concept in JavaScript that can be used in react in many different ways. A private variable is one example of closure.