React Redux and Redux Toolkit for state management:
App.tsx:
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import { Provider } from "react-redux"; import { store } from "@/state/store.ts"; import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; import CounterPage from "./pages/CounterPage"; import "./App.css"; function App() { return ( <Provider store={store}> <div className="container"> <Router> <Navbar /> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/counter" element={<CounterPage />} /> </Routes> </Router> </div> </Provider> ); } export default App;
/state/store.ts:
import { configureStore } from "@reduxjs/toolkit"; import counterReducer from "./counter/counterSlice"; export const store = configureStore({ reducer: { counter: counterReducer, }, }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;
/state/counter/counterSlice.ts:
import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit"; interface CounterState { value: number; } const initialState: CounterState = { value: 0, }; const counterSlice = createSlice({ name: "counter", initialState, reducers: { decrement: (state) => { state.value -= 1; }, increment: (state) => { state.value += 1; }, incrementByAmount: (state, action: PayloadAction<number>) => { state.value += action.payload; }, }, extraReducers: (builder) => { builder .addCase(incrementAsync.pending, () => { console.log("incrementAsync.pending"); }) .addCase( incrementAsync.fulfilled, (state, action: PayloadAction<number>) => { console.log("incrementAsync.fulfilled"); state.value += action.payload; } ); }, }); export const incrementAsync = createAsyncThunk( "counter/incrementAsync", async (amount: number) => { await new Promise((resolve) => setTimeout(resolve, 3000)); return amount; } ); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export default counterSlice.reducer;
/pages/CounterPage/index.tsx:
import { useDispatch, useSelector } from "react-redux"; import { AppDispatch, RootState } from "@/state/store"; import { decrement, increment, incrementByAmount, incrementAsync, } from "@/state/counter/counterSlice"; import { Button } from "@/components/ui/button"; const CounterPage = () => { const count = useSelector((state: RootState) => state.counter.value); const dispatch = useDispatch<AppDispatch>(); return ( <section className="CounterPage"> <h1 className="text-2xl font-bold mb-4">Counter: {count}</h1> <div> <Button onClick={() => dispatch(decrement())} className="mr-3"> -1 </Button> <Button onClick={() => dispatch(increment())} className="mr-3"> +1 </Button> <Button onClick={() => dispatch(incrementByAmount(5))} className="mr-3"> +5 </Button> <Button onClick={() => dispatch(incrementAsync(10))} className="mr-3"> +10 async (3 seconds) </Button> </div> </section> ); }; export default CounterPage;
React Redux for state management:
Redux actions:
export const increment = () => ({ type: 'INCREMENT', }); export const decrement = () => ({ type: 'DECREMENT', });
Redux reducer:
const counterReducer = (state = { count: 0 }, action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } }; export default counterReducer;
Redux store:
import { createStore } from 'redux'; import counterReducer from './reducer'; const store = createStore(counterReducer); export default store;
Counter component:
import { useDispatch, useSelector } from 'react-redux'; import { increment, decrement } from './actions'; const Counter = () => { const count = useSelector((state) => state.count); const dispatch = useDispatch(); return ( <div> <p>Count: {count}</p> <button onClick={() => dispatch(increment())}>Increment</button> <button onClick={() => dispatch(decrement())}>Decrement</button> </div> ); }; export default Counter;
Main App component:
import { Provider } from 'react-redux'; import Counter from './Counter'; import store from './store'; const App = () => { return ( <Provider store={store}> <Counter /> </Provider> ); }; export default App;
Regular React reducer for state management:
import { useReducer } from 'react'; // Reducer function const counterReducer = (state, action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } }; // Counter component const Counter = ({ dispatch }) => { return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button> </div> ); }; // Parent component managing state const CounterApp = () => { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <Counter dispatch={dispatch} /> ); }; export default CounterApp;