React State management
6/4/2025

- REACT
React's built-in hooks offer powerful solutions for managing complex application state, and when combined strategically, they can eliminate the need for external state management libraries in many scenarios.
The useReducer hook provides a robust alternative to useState for handling intricate state logic, while useContext enables seamless state sharing across component hierarchies without prop drilling.
When these two hooks work together, they create a scalable state management pattern that's particularly effective for features like banking transactions, user authentication, and other applications requiring centralised state control with predictable updates.

import React, { useReducer } from 'react';
// bank
const reducer = (state: number, action: { type: string; payload?: number }) => {
switch (action.type) {
case 'DEPOSIT':
if (action.payload) return state + action.payload;
default:
return state;
}
};
export default function ComponentWithReducer() {
// state and pigeon
const [amount, dispatch] = useReducer(reducer, 500);
// actions
const deposit = (amount: number) => {
dispatch({
type: 'DEPOSIT',
payload: amount,
});
};
return (
<div>
<h1>{amount}</h1>
<button onClick={() => deposit(500)}>Deposit</button>
</div>
);
}
UseContext
// contexts/bank.ts
import { createContext } from 'react';
export interface IBankProps {
amount: number;
deposit: (amount: number) => void;
}
export const initialState: IBankProps = {
amount: 0,
deposit: () => {},
};
const BankContext = createContext<IBankProps>(initialState);
export const BankContextConsumer = BankContext.Consumer;
export const BankContextProvider = BankContext.Provider;
export default BankContext;
// ComponentWithUseContext.tsx
import React, { useContext } from 'react';
import BankContext from '../contexts/bank';
const ComponentWithUseContext: React.FC = () => {
const bankContext = useContext(BankContext);
return (
<div>
<h1>{bankContext.amount}</h1>
<button onClick={() => bankContext.deposit(200)}>desposit</button>
</div>
);
};
export default ComponentWithUseContext;
Integrate together
// bank.ts
import React, { createContext } from 'react';
// state interface
export interface IBankState {
amount: number;
}
// actions interface
export interface IBankActions {
type: 'DEPOSIT' | 'WITHDRAW';
payload?: any;
}
export interface IBankContextProps {
state: IBankState;
dispatch: React.Dispatch<IBankActions>;
}
export const initialBankState: IBankState = {
amount: 10,
};
const BankContext = createContext<IBankContextProps>({
state: initialBankState,
dispatch: () => {},
});
export const bankReducer: React.Reducer<IBankState, IBankActions> = (
state,
action
) => {
switch (action.type) {
case 'DEPOSIT':
return {
...state,
amount: state.amount + action.payload,
};
case 'WITHDRAW':
if (state.amount - action.payload < 0) {
return state;
}
return {
...state,
amount: state.amount - action.payload,
};
default:
return state;
}
};
export const BankContextConsumer = BankContext.Consumer;
export const BankContextProvider = BankContext.Provider;
export default BankContext;
// app.ts
import React, { useReducer } from 'react';
import './App.css';
import ComponentWithUseContext from './components/ComponentWithUseContext';
import {
BankContextProvider,
bankReducer,
IBankContextProps,
initialBankState,
} from './contexts/bank';
function App() {
const [bankState, bankDispatch] = useReducer(bankReducer, initialBankState);
const bankContextValue: IBankContextProps = {
state: bankState,
dispatch: bankDispatch,
};
return (
<BankContextProvider value={bankContextValue}>
<div className="App">
<ComponentWithUseContext />
</div>
</BankContextProvider>
);
}
export default App;
UseAuth Example
This code implements a custom authentication system using React Context and hooks to manage user state across an application. Here's how each part works:
Context Setup
The AuthContext
is created with TypeScript typing that expects an object containing user
and login
properties. The displayName
is set for better debugging experience in React DevTools.
AuthProvider Component
This component serves as the context provider that wraps parts of your application needing authentication data. It maintains the user state using useState
and defines authentication methods:
-
user
state holds the current authenticated user (or null if not logged in) -
login
function accepts form credentials, calls an external authentication service (auth.login
), and updates the user state with the returned user data - The component returns the Context Provider, making the user state and login function available to child components
-
It calls
useContext
to retrieve the current context value -
Includes error handling to ensure it's only used within an
AuthProvider
- Returns the context object containing user state and authentication methods
The code follows React best practices by combining Context for state sharing with custom hooks for clean component interfaces.
import React, { useState } from 'react';
import * as auth from 'utils/auth-req';
interface AuthForm {
username: string;
password: string;
}
const AuthContext = React.createContext<{user: any; login: any}>(undefined);
AuthContext.displayName = 'AuthContext';
export const AuthProvider = () => {
const [user, setUser] = useState<User | null>(null);
const login = (form: AuthForm) => auth.login(form).then(user => setUser(user));
// register, logout...
return <AuthContext.Provider value={{user, login}}/>
}
export const useAuth = () => {
const context = React.useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used in AuthProvider');
}
return context
}