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;