A comprehensive guide to understanding state management in React applications, focusing on local state management techniques and best practices.
4 min read
When building a website with React, state management is one of the most crucial concepts you'll rely on.
In this series, I'll show you the best practices and techniques for managing state across your applications and websites.
Over a decade ago, developers built websites using vanilla JavaScript, which involved writing lots of redundant code and lengthy development processes. The introduction of JavaScript frameworks based on UI components revolutionized web development, making it faster and easier to create modern websites with reusable components. React, created by Facebook, emerged from this evolution and has become the most popular JavaScript framework today.
Each React component has a built-in state object where you can store data that persists between component renderings. You can manage component state either locally or through an external state management library.
In this part of the series, we'll explore local state management.
React comes with several useful hooks, with useState()
being one of the most essential.
But what exactly is a hook? According to the official docs, it's a special function that lets you "hook into" React features. The useState
hook specifically allows you to add state management to function components.
Let's say we have a button that toggles specific data on and off. To maintain the button's status, we need to use the useState
hook to track its state. Here's an example:
import { useState } from 'react'
function ToggleExample() {
const [isVisible, setIsVisible] = useState(false)
return (
<>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle
</button>
{isVisible && <div>Content is now visible!</div>}
</>
)
}
When we call the useState
hook, we declare a new "state variable." It returns two values: the current state (isVisible
in our example) and a function to update it (setIsVisible
). The argument passed to useState
is the initial state—in this case, a Boolean value of false.
In our button's onClick
handler, we use a callback function that calls setIsVisible(prev => !prev)
. This toggles the Boolean value to its opposite whenever the button is clicked.
When a user first clicks the toggle button, it changes isVisible
from false to true, displaying the message Content is now visible!
. A second click hides the message by setting the value back to false.
Now let's take another example:
import {useState} from 'react'
const Message = ({user}) => {
return (
<h1>User Name: {user.name}</h1>
<h2>User Age: {user.age}<h2>
)
}
export default function App() {
const [user, setUser] = useState({
name: 'Sam',
age: '24'
})
return (
<>
<p>User information:</p>
<Message user={user} >
</div>
)
}
In the example above, the App component (parent) declares a new state variable user
using the useState
hook. The initial state is an object containing user information (simulating data fetched from an auth API). We then create a child component Message
and pass the user
state variable as a prop. The Message component destructs the user data from its props and displays the user's name and age in a simple message.
This example demonstrates how state variables pass from a parent component to its child—a concept related to prop drilling. Remember that this data flow only moves downward: while a parent component can pass state to its children, a child component cannot declare state that will be consumed by its parent.
In more complex applications, simple prop-drilling can complicate our workflow. This happens when we need to pass state not just to direct children, but to deeply nested components. Finding better ways to handle state from distant ancestor components becomes necessary. This is where state management libraries become valuable. In the following parts of this series, I'll demonstrate my approaches to handling global state management.
Understanding local state management is crucial for React development. It forms the foundation for more advanced state management patterns and helps in building more efficient React applications.
💡 Pro Tip: Always start with local state management before considering more complex state management solutions. It's often sufficient for smaller applications and simpler component hierarchies.