What Pain does redux solves
- Redux is a global state.
- Redux may not be necessary for every project.
- You may need Redux if you don’t want to do props drilling (i.e. passing props too deep).
- If you are still confused about Redux, just think about the React state. The only difference is you can access the state from anywhere.
In complex web applications like Facebook and Instagram, managing data and state across different components is crucial. Without a proper mechanism in place, the application can quickly become chaotic. You can think of Redux as a frontend database that helps keep everything organized.
Project Structure:
src
- redux
- slices
- anyname.js
- store.js
Rules:
- One application cannot have more than one store.
- Action, Reducers & Subscriptions can be upto any number.
Main Processes in Redux
Actions: Actions are plain JavaScript objects that represent an intention to change the state. Action’s purpose is to signal that something happened in the application. For Ex: Think of them as “messages” sent to the store — indicating that a state change should occur.
Reducers: Specify how the state changes in response to actions. Reducers are pure functions that take the current state and an action as arguments, and return a new state.
Store: Store is a central object that holds the entire state of your application.
Subscriptions: When a Component subscribes to the store, it start to receive updates. When anything changes in the state, the components are notified, if its new state, it will re-render the UI.
REDUX 1ST TIME SETUP
Step 1: Install 2 Packages
npm install react-redux @reduxjs/toolkit
Step 2: CREATE THIS Structure
Create a ‘redux’ folder and create structure like this:
redux
- store.js
- slices
- slice1.js
If you are using React, then create like this in src folder.
If you are using Next, then create like this in src/app folder.
Step 3: Create a Global Store
redux
- store.js
// store.js (put these code)
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: { },
})
Step 4: Wrap your whole app with Redux Provider
// app/layout.js (Next)
// src/main.jsx (React)
(providing store to our complete react app). wrap your whole app inside provider. wrapping will give access all your files to react redux
in React
import {Provider} from "react-redux"
import {store} from "./redux/store.js"
const root= ReactDOM.createRoot(document.getElementById("root"))
root.render(
<Provider store={store}>
<App />
</Provider>
)
in NextJS
In Next.js App Router, there is no need for ReactDOM.createRoot
or root.render
. Instead, you wrap your entire app with the Redux Provider
directly inside the layout.js
file.
// app/layout.js
"use client"; // This component must be client-side
import { Provider } from "react-redux";
import { store } from "../redux/store"; // Adjust path if necessary
import "./globals.css";
export const metadata = {
title: "Next.js App with Redux",
description: "Using Redux with the App Router",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Provider store={store}>{children}</Provider>
</body>
</html>
);
}
If you do like this, there will be error of use client on importing store and metadata.
#ERROR
Solution: Split Client and Server Logic
We can handle this issue by splitting the Redux logic into a separate client component while keeping the RootLayout as a server component.
Step-by-Step Fix
- Create a
ReduxProvider
Component for Client Logic
create this Provider as component in components folder
// components/ReduxProvider.js
"use client"
import { Provider } from "react-redux";
import { store } from "../redux/store";
export default function ReduxProvider({ children }) {
return <Provider store={store}>{children}</Provider>;
}
- Update the Layout Without
use client
Directive
This layout will remain a server component, allowing the metadata export.
// app/layout.js
import ReduxProvider from "../components/ReduxProvider";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<ReduxProvider>{children}</ReduxProvider>
</body>
</html>
);
}
NOW REAL WORK STARTS:
Step 1: Create a Slice
redux folder > slices folder > new slice
//slice1.js
A Slice takes 3 things:
- name
- inital state
- reducers
- actions
- action creators
import { createSlice } from "@reduxjs/toolkit"
export const cartSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => { // Actions
state.value += 1 // Reducers
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
}
})
export const {
increment,
decrement
} = cartSlice.actions //exporting actions
export default cartSlice.reducer // exporting reducer
Step 2: (store.js) Add the Slice in store.js
redux folder > store.js
This is your global/redux store
we will make
src
- redux
- store.js
import { configureStore } from '@reduxjs/toolkit'
import cartSlice from './slices/cartSlice'
const store = configureStore({
reducer: {
cart: cartSlice,
// "slice name" : "slice function"
},
})
export default store
Step 3: UI using — useDispatch & useSelector
// app/page.js (in Next)
// src/app.jsx (in React)
using useSelector and useDispatch in react components
'use client'
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement } from "../redux/slices/counterSlice";
export default function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
< >
<button aria-label="Increment value"
onClick={() => dispatch(increment())} >
Increment
</button>
<span>{count}</span>
<button aria-label="Decrement value"
onClick={() => dispatch(decrement())} >
Decrement
</button>
< />
)
}
For React, it’s same as above.
For Next JS, make this client component by ‘use client’
Note: wherever you use useDispatch or useSelector, don’t forget to put ‘use client’.
# (useDispatch) Send Redux Action
Normal useDispatch
import { useDispatch} from 'react-redux'
import { decrement, increment } from './counterSlice'
export default function Counter() {
const dispatch = useDispatch()
return (
< >
<button aria-label="Increment value" onClick={() => dispatch(increment())} >
Increment
</button>
<button aria-label="Decrement value" onClick={() => dispatch(decrement())} >
Decrement
</button>
< />
)}
Action with Payload
Dispatch with payload
import { useDispatch} from 'react-redux'
import { decrement, increment } from './counterSlice'
export default function Counter() {
const dispatch = useDispatch()
return(..........)
<button id="btnDecrement" onClick={btnDecre}> - </button>
function btnDecre(){
dispatch(btnDecrement())
}
directly: onClick={() => dispatch(decrement())}
# (useSelector) Use Redux State
import { useSelector } from 'react-redux'
export default function Counter() {
const count = useSelector((state) => state.counter.value)
return (
< >
<span>{count}</span>
< />
)
- state.counter.value :
- state – global state
- counter – slice name
- value – variable name
export const cartSlice= createSlice({
name: 'counter',
initialState: {
value:0
},
ERROR NOTES:
Error 1: This function is not supported in React Server Components. Please only use this export in a Client Component.
This error happens because you’re using Provider
from Redux, which is meant for client-side rendering, inside a server component. By default, files in Next.js’s app
directory are treated as server components unless explicitly marked with "use client"
.
Since your RootLayout
needs to provide the Redux store (which must be managed client-side), you need to convert it into a client component.
Answer: So Provide ‘use client’ in the provider
OR: You haven’t put ‘use client’ where you are using useDispatch & useSelector
The error you’re encountering occurs because useSelector
and useDispatch
hooks from Redux cannot be used in React Server Components.
Yahoo! after the fix, it’s working now:
Error 2: You are attemting to export ‘metadata’ from a component marked with “use client”, which is disallowed.
Solution: Split Client and Server Logic
We can handle this issue by splitting the Redux logic into a separate client component while keeping the RootLayout as a server component.
Step-by-Step Fix
- Create a
ReduxProvider
Component for Client Logic
create this Provider as component in components folder
// components/ReduxProvider.js
"use client"; // This component must be client-side
import { Provider } from "react-redux";
import { store } from "../redux/store";
export default function ReduxProvider({ children }) {
return <Provider store={store}>{children}</Provider>;
}
- Update the Layout Without
use client
Directive
This layout will remain a server component, allowing the metadata export.
// app/layout.js
import ReduxProvider from "../components/ReduxProvider"; // Adjust path if necessary
import "./globals.css";
export const metadata = {
title: "Next.js App with Redux",
description: "Using Redux with the App Router",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<ReduxProvider>{children}</ReduxProvider>
</body>
</html>
);
}
Error 3: Cannot read properties of undefined (reading ‘getState’)
Its already saying TypeError.
In your store.js
, you are doing Default Export
export default store;
But in ReduxProvider
, you have done Named Import
import { store } from "./../../../redux/store";
// Named import
Answer: Use Default Import in ReduxProvider
:
import store from "./../../../redux/store";
Questions?
How to not put so long like /../../ “import store from “./../../../redux/store” ?
Solution: Using Path Aliases
Step 1: Configure Path Aliases in jsconfig.json
or tsconfig.json
Create or modify the jsconfig.json
or tsconfig.json
file at the root of your project:
jsonCopy code{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@redux/*": ["redux/*"],
"@components/*": ["components/*"]
}
}
}
In the example above:
@redux/*
refers to theredux
folder.@components/*
refers to thecomponents
folder.
Step 2: Update Imports Using the Aliases
Now, instead of using ../../../redux/store
, you can import the store like this:
javascriptCopy codeimport store from "@redux/store";
For components, the path will look like this:
javascriptCopy codeimport ReduxProvider from "@components/ReduxProvider";
Step 3: Restart the Next.js Development Server
After making changes to the configuration, restart your server:
bashCopy codenpm run dev