React Redux state management

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;

Leave a Comment