
What is State?
Simply put, state is any data that describes the current condition of an application at a given time.
State management is the process of handling changes to state over time. This includes storing a value, reading the stored value, updating it, and deleting it.
Global state is state that is accessible to every component in an application. Global state management, therefore, is the process of handling changes to global state over time.
Why Do We Need State in Our Applications?
Applications decide what to render, compute, or return based on current state values. Therefore, it is essential to manage state in a way that is efficient, easy to retrieve, update, and delete.
The core purpose of global state management is to share and manage state data across components.
Types of State Management
State management can be broadly classified into three distinct categories:
- Local State Management
- Global State Management
- Server-Side State Management
Local state management involves managing state within a single component or a small group of closely related components.
As applications grow more complex, global state management becomes necessary. It involves a central store that any component can access and modify.
Server-side state management involves storing state information on the server and accessing it via APIs. This requires communication between the client and server.
Local State Management Example
export default function StateExample() {
const [name, setName] = useState("");
function updateName(name: string) {
setName(name);
}
return (
<div>
<input
type="text"
value={name}
onChange={(e) => updateName(e.target.value)}
placeholder="Enter your name"
/>
<p>Your name is: {name}</p>
</div>
);
}Local state can also be hoisted to the URL. This allows the state to:
- Persist through page reloads
- Be shared via links
import { useSearchParams } from "react-router-dom";
export default function UrlExample() {
const [searchParams, setSearchParams] = useSearchParams();
// Get state from URL
const name = searchParams.get("name");
// Update URL when state changes
const updateName = (newName: string) => {
setSearchParams({ name: newName });
};
return (
<div>
<p>
Current Name: <strong>{name}</strong>
</p>
<div>
<button onClick={() => updateName("Samuel")}>Samuel</button>
<button onClick={() => updateName("Umoh")}>Umoh</button>
</div>
</div>
);
}
Global State Management
In the Browser
Data can be stored in the browser and accessed from any component throughout the application.
Example: localStorage
export default function ExampleBrowser() {
const [inputValue, setInputValue] = useState("");
const token = getToken();
return (
<div>
<p>current token: {token}</p>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={() => setToken(inputValue)}>update token</button>
</div>
);
}
function getToken() {
return localStorage.getItem("token");
}
function setToken(token: string) {
localStorage.setItem("token", token);
}
Through React Context
React provides a built-in mechanism called Context for global state management. It allows you to share state across components without prop drilling.
import { useState, createContext } from "react";
import { useContext } from "react";
const NameContext = createContext("samuel");
const NameProvider = ({ children }: PropsWithChildren) => {
const [name, setName] = useState("samuel");
const toggleName = () => {
setName(name === "samuel" ? "umoh" : "samuel");
};
return (
<NameContext.Provider value={{ name, toggleName }}>
{children}
</NameContext.Provider>
);
};
// wrap NameProvider around your app layout
export default function ContextExample() {
const { name, toggleName } = useContext(NameContext);
return (
<div>
<p>Current Name: {name}</p>
<button onClick={toggleName}>Toggle Name</button>
</div>
);
}
Server-Side State Management
This involves:
- Fetching endpoints to get current state data
- Integrating it with local state
- Making requests to an external API to update and manage this state
External State Management Libraries
These libraries provide powerful tools for global state:
- Zustand: A minimalistic library that creates global stores with a simple API and no boilerplate.
- Jotai: Introduces a primitive and atomic approach to state management.
- Redux Toolkit: Offers an opinionated, convenient, and beginner-friendly approach to state management.
External State Management Libraries Examples
Zustand
Visit Zustand documentation for a detailed installation guide. take me there
import { create } from "zustand";
// Create store
const useMyStore = create((set) => ({
name: "",
setName: (newName: string) => set((state) => ({ name: newName })),
}));
// Use store in component
export default function App() {
const name = useMyStore((state) => state.name);
const setName = useMyStore((state) => state.setName);
return (
<div>
<h1>My name is: {name}</h1>
<button onClick={() => setName("Samuel")}>Set name Samuel</button>
</div>
);
}
Jotai
Visit Jotai documentation for a detailed installation guide. take me there
import { atom } from "jotai";
// create atom for name state
const nameAtom = atom("");
// use atom in component
import { useAtom } from "jotai";
import { nameAtom } from "./atoms";
const App = () => {
const [name, setName] = useAtom(nameAtom);
return (
<div>
<h1>Name: {name}</h1>
<button onClick={() => setName("Samuel")}>Update name to Samuel</button>
</div>
);
};
export default App;
Redux Toolkit
Visit Redux Toolkit documentation for a detailed installation guide. take me there
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
// create your slice
export interface NameState {
name: string;
}
const initialState: NameState = {
name: "",
};
export const nameSlice = createSlice({
name: "name",
initialState,
reducers: {
setName: (state, action: PayloadAction<string>) => {
state.name = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const { setName } = nameSlice.actions;
export default nameSlice.reducer;
// add your slice to your store (this is usually in a separate file)
import { configureStore } from "@reduxjs/toolkit";
import nameReducer from "../features/name/nameSlice";
export const store = configureStore({
reducer: {
name: nameReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
// use your state in a component
import React from "react";
import type { RootState } from "../../app/store";
import { useSelector, useDispatch } from "react-redux";
import { setName } from "./nameSlice";
export function Counter() {
const name = useSelector((state: RootState) => state.name.value);
const dispatch = useDispatch();
return (
<div>
<div>
<p>Current name: {name}</p>
<button onClick={() => dispatch(setName("Samuel"))}>
Set name to Samuel
</button>
</div>
</div>
);
}
Conclusion
- Use useState for temporary local state.
- Use the URL when you want state to persist across reloads and enable sharing via links.
- For global state, choose a library like Zustand, Jotai, or Redux Toolkit. They also (in most cases) include plugins for persisting data to localStorage (or whatever storage you might need).
- Lastly: If you're ever thinking about using useContext, you definitely need a global state manager instead! (yeah, the praises can come in rn)