React - Counter example in all the four ways: Prop Drilling, Context API, Redux, and Redux Toolkit.

Pranav Bakare - Sep 15 - - Dev Community

Let's implement the counter example in all the four ways: Prop Drilling, Context API, Redux, and Redux Toolkit.


1. Prop Drilling

In prop drilling, the state and the functions that modify the state are passed down as props through intermediate components.


// App.js
import React, { useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  return <Parent count={count} setCount={setCount} />;
}

function Parent({ count, setCount }) {
  return <Child count={count} setCount={setCount} />;
}

function Child({ count, setCount }) {
  return (
    <div>
      <h2>Counter: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Explanation:

Prop Drilling: The count state and the setCount function are passed from App to Parent, and finally to Child where the UI is updated. This works fine for smaller apps but can get messy when the state is passed through many layers of components.


2. Context API

Using the Context API, we avoid prop drilling by making the state and functions available globally to the component tree.

// App.js
import React, { useState, createContext, useContext } from "react";

// Create Context
const CounterContext = createContext();

function App() {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={{ count, setCount }}>
      <Parent />
    </CounterContext.Provider>
  );
}

function Parent() {
  return <Child />;
}

function Child() {
  const { count, setCount } = useContext(CounterContext);

  return (
    <div>
      <h2>Counter: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Explanation:

Context API: We create a CounterContext that holds the state and the setCount function. The Child component consumes the context via useContext. This approach prevents prop drilling by making the state accessible to any component in the tree.


3. Redux

In the Redux approach, we use a global store to manage the state.

1. Install Redux:


npm install redux react-redux

Enter fullscreen mode Exit fullscreen mode

2. Create Redux Store:


// store.js
import { createStore } from "redux";

// Initial state
const initialState = {
  count: 0,
};

// Reducer function
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1 };
    case "DECREMENT":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

// Create Redux store
const store = createStore(counterReducer);

export default store;

Enter fullscreen mode Exit fullscreen mode

3. App Component:

// App.js
import React from "react";
import { Provider } from "react-redux";
import Parent from "./Parent";
import store from "./store";

function App() {
  return (
    <Provider store={store}>
      <Parent />
    </Provider>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

4. Parent and Child Components:


// Parent.js
import React from "react";
import Child from "./Child";

function Parent() {
  return <Child />;
}

export default Parent;

Enter fullscreen mode Exit fullscreen mode

// Child.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";

function Child() {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h2>Counter: {count}</h2>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
    </div>
  );
}

export default Child;

Enter fullscreen mode Exit fullscreen mode

Explanation:

Redux: The state is centralized in the Redux store, and the Child component accesses the store using useSelector to get the count, and useDispatch to dispatch the INCREMENT and DECREMENT actions.


4. Redux Toolkit

Redux Toolkit simplifies Redux setup by generating actions and reducers automatically.

1. Install Redux Toolkit:


npm install @reduxjs/toolkit react-redux

Enter fullscreen mode Exit fullscreen mode

2. Create Slice:


// counterSlice.js
import { createSlice } from "@reduxjs/toolkit";

// Create a slice
const counterSlice = createSlice({
  name: "counter",
  initialState: {
    count: 0,
  },
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
  },
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;

Enter fullscreen mode Exit fullscreen mode

3. Configure Store:


// store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";

// Configure the store
const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;

Enter fullscreen mode Exit fullscreen mode

4. App Component:


// App.js
import React from "react";
import { Provider } from "react-redux";
import Parent from "./Parent";
import store from "./store";

function App() {
  return (
    <Provider store={store}>
      <Parent />
    </Provider>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

5. Parent and Child Components:


// Parent.js
import React from "react";
import Child from "./Child";

function Parent() {
  return <Child />;
}

export default Parent;

Enter fullscreen mode Exit fullscreen mode
// Child.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement } from "./counterSlice";

function Child() {
  const count = useSelector((state) => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h2>Counter: {count}</h2>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
}

export default Child;

Enter fullscreen mode Exit fullscreen mode

Explanation:

Redux Toolkit: The counterSlice is created using createSlice, which automatically generates the action creators (increment, decrement) and the reducer. The store is set up with configureStore. The Child component accesses the state and dispatches actions in the same way as with traditional Redux, but the setup is more concise.


Summary of All Approaches:

Each method serves a specific use case, and as your app scales in complexity, moving from Prop Drilling to Redux or Redux Toolkit will make managing the state easier.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .