A clean and lightweight alternative to Redux using React's built-in Context + useReducer. Great for small to medium applications that need global state management without the overhead of additional libraries.
Example demonstrates a counter implementation but can be extended for more complex state management needs.
This guide shows a clean and type-safe approach to implementing global state management in React without external state management libraries. Perfect for small to medium-sized applications where Redux might be overkill.
- TypeScript support with full type safety
- Clean and maintainable code structure
- Scalable pattern for adding more state management features
- Easy to understand counter example
- Works with Next.js (and other React frameworks)
- Zero external dependencies (uses only React built-in features)
Created by @ethaizone
Last Updated: 2025-03-24 04:17:12 UTC
// File: src/reducers/app/actions.tsx
import { type AppStates } from '.'
export enum AppReducerAction {
INCREASE_COUNTER = 'INCREASE_COUNTER',
DECREASE_COUNTER = 'DECREASE_COUNTER',
}
// Add new actions below this line
interface IncreaseCounterAction {
type: AppReducerAction.INCREASE_COUNTER
}
interface DecreaseCounterAction {
type: AppReducerAction.DECREASE_COUNTER
}
export type AppReducerActions = IncreaseCounterAction | DecreaseCounterAction
// File: src/reducers/app/index.tsx
import { useReducer } from 'react'
import { AppReducerAction, type AppReducerActions } from './actions'
export * from './actions'
export type AppStates = {
counter: number
}
export const initialAppState: AppStates = {
counter: 0,
}
const reducer = (state: AppStates, action: AppReducerActions): AppStates => {
switch (action.type) {
case AppReducerAction.INCREASE_COUNTER:
return { ...state, counter: state.counter + 1 }
case AppReducerAction.DECREASE_COUNTER:
return { ...state, counter: state.counter - 1 }
default:
return state
}
}
export const useAppReducer = () => {
return useReducer(reducer, initialAppState)
}
// File: src/contexts/store-context/index.tsx
import { createContext } from 'react'
import { type useAppReducer, initialAppState } from '@/reducers/app'
interface StoreContextStates {
app: {
state: ReturnType<typeof useAppReducer>[0]
dispatch: ReturnType<typeof useAppReducer>[1]
}
}
export const StoreContext = createContext<StoreContextStates>({
app: {
// Actual value will be set by the provider
state: initialAppState,
dispatch: () => {},
},
})
// File: src/providers/store-context-provider/index.tsx
import { StoreContext } from '@/contexts/store-context'
import { useAppReducer } from '@/reducers/app'
interface Props {
children: React.ReactNode
}
export function StoreContextProvider({ children }: Props) {
const [appState, appDispatch] = useAppReducer()
// Note: Even when reducer values change, React context won't re-render child components
// automatically. Therefore, using React.memo or other caching mechanisms here would be
// counterproductive.
return (
<StoreContext.Provider
value={{ app: { state: appState, dispatch: appDispatch } }}
>
{children}
</StoreContext.Provider>
)
}
// File: src/pages/_app.tsx
import { type AppProps } from 'next/app'
import { StoreContextProvider } from '@/providers/store-context-provider'
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<StoreContextProvider>
<Component {...pageProps} />
</StoreContextProvider>
)
}
// File: src/pages/Counter.tsx
import { useContext } from 'react'
import { StoreContext } from '@/contexts/store-context'
import { AppReducerAction } from '@/reducers/app'
export default function Counter() {
const {
app: { state: appState, dispatch: appDispatch },
} = useContext(StoreContext)
return (
<div className="counter-container">
<h1>Counter Example</h1>
<div className="counter-display">
<p>Counter Value: {appState.counter}</p>
<div className="counter-controls">
<button
onClick={() => {
appDispatch({ type: AppReducerAction.DECREASE_COUNTER })
}}
>
Decrease
</button>
<button
onClick={() => {
appDispatch({ type: AppReducerAction.INCREASE_COUNTER })
}}
>
Increase
</button>
</div>
</div>
</div>
)
}
MIT License