Redux is an open-source javascript library used for managing the states of our project. Reduxtoolkit (RTK) is the recommended way to write redux. RTK makes writing redux more easier. We can also manage state using context in Reactjs but Redux makes it easier to manage state and RTK is preferred more in big projects. Let's not waste any time and go to code.
First of all, let's install the redux toolkit in our Nextjs project with this command:
npm install @reduxjs/toolkit react-redux
Now we'll create a folder named redux on the root directory and will write all the redux toolkit code in there.
After that, create a store.ts file and import configureStore
Now we need to create reducers.
reducer is a function that takes an action and previous states of the application then makes some changes in them and returns the new value for that state.
We will create reducers for our app in the redux/features directory. Inside the features directory, we'll create slices. Slices are the collection of reducer logic, action, and states for a single feature of our app.
We are going to create a slice for auth. Create a file name it auth-slice.ts
We will import createSlice & PayloadAction in it.
Now create an auth slice like this:
We have to specify a couple of things about the slice. The first is the name, let's give it "auth". Then we pass an object called InitialState. InitialState contains all the initial values of all the states related to this slice.
In one piece of slice, we can only store one piece of data in the initialState. It can be an object, string, boolean, etc. We need to store a lot of data like auth-status, username, etc so let's create an object with the name "initialState" that will contain all of that data and pass it to the initialState of the auth slice.
First of all, let's create types for the InitialState as we're using Typescript
Now let's create InitialState object and assign it to its type.
Now let's give this object to initialState inside the slice. In javascript we don't have to do initialState: initialState, we just write initialState, and the "initialState" object will be assigned. Like this:
Now let's create reducers. As we know reducer is a function that takes in a state and an action and returns the updated state
We're going to have functionality like login, logout, etc. First, let's write a function to log out. It's easy to create a logout function because we just have to erase all the data and set it to initialState (As you can see, initialState default value is just data with no value):
Now we create a reducer function to logOut and it will return whatever new value of the state we want to have. In our case, we want to logout that means we want to set the value to initialState
So whenever we call the logOut function, it will return and turn the state back to initialState
Now, we'll create a login function. Login will be a little complicated because it will take two arguments to the function. We will have a state argument and an action argument. The action will be of PayLoadAction (Typescript type) that will be a string cause we'll just send back to here a string containing the username so we can specify it as a string
Now whenever we log in, we want to return its new value for the state. The new value contains an object that has the following keys.
isAuth will be true cause we are logging in. Username which is equal to action.payload because of action.payload contains whatever we pass in an argument to the login function. And have a user id (Just give random values to id for now)
Now we can use them in other places by exporting them. We have to export the login, logOut functions and reducer like this:
Now go to the store.tsx file, import the auth reducer, and pass it to the reducer object inside the store function.
Now because we are using typescript, we need to get the types specifically for this store.
This is what is happening in line no.11:
export type RootState
declares a custom type namedRootState
.ReturnType<typeof store.getState>
uses TypeScript'sReturnType
utility to figure out the exact shape of the data returned by thestore.getState()
function, which represents the entire state of the Redux store.This means
RootState
will mirror the structure of the actual state data, providing a clear and reliable way to work with it throughout your application.
Now we're gonna export another type that will be used in our dispatch function which is part of Redux and we just want to get the type of store.dispatch in line no.12
Now we have to pass in a provider with our store. We'll put that inside our layout file. For that, we have to create our provider component that will handle all of that inside the redux folder
We'll create a file provider.tsx inside our redux folder. In nextjs, if it's a server component we can't access stuff like browser events, react states as well as redux states so we have to make a component a client component to access stuff related to Redux.
Inside provider.tsx, we'll import the Provider from react-redux and store it from our store inside the redux folder. Then we will create a function, let's name it ReduxProvider, this function will be a wrap-around component in which we want to use Redux and pass redux states. Inside the ReduxProvider function, we return Provider. We also have to pass the store in the Provider component (It's the syntax)
Inside layout.tsx, we have to import and wrap the ReduxProvider component around our children and component that we want to pass redux states to:
Now, let's use it! We will use it inside the Home directory. This component is imported in the root page.tsx which makes this component wrapped around ReduxProvider
We first need to import logIn and logOut functions from auth-slice. Also, we have to make this a client component if we want to use redux here.
However we can't call these functions directly, we have to use the useDispatch hook from react-redux. useDispatch hook allows us to use a function called a dispatch function which can be used to dispatch functions in our reduce. So we'll create a dispatch variable and set it equal to the use dispatch function.
Do you remember we created an AppDispatch type in the store earlier? We have to use it here. Now we can finally use the functions!
Now, let's create a state variable named userName
, input tag in which the input's data is saved into the userName state, and then log in and log out button
Now when we type something on the input, its value will be sent to the userName state and then we want to send it back to our auth-slice because in our logIn function, we need to get that value and set the username of the user equal to that.
Let's create a function that will be triggered when we click on the login button. Inside it, we'll call the dispatch function, call the logIn function from it, and pass our username state there.
Now when you click the log-in button, it should trigger the function and save the username state in our store. Now we're going to check if that's the correct information. We want to redirect our user to the dashboard and display their username when we log in so let's create a dashboard
Now to make it work, we have to get the current value of the initial state. Now we'll use useSelector to do this.
We will import useSelector from react-redux and then create a const variable username which equals to useSelector. With useSelector, we can grab a state directly from here, return that state specify what part of that state we want to get, and then set it equal to this username variable
To do that, we have to specify what reducer we want to use and right here it authReducer. We're accessing its value which is username so it goes like this:
We're getting an error because we didn't specify the type of useSelector, let create a type for it in store.tsx
First import useSelector in store.tsx then create a name for the type, let's call it "useAppSelector", and then we have to give it TypedUseSelectorHook again we have to give it type <RootState> to tell that it is related to state and then set it equal to useSelector
Little confusing right? me too! but remember, you've been in situations like this a lot of times, you know this will make sense after a while so keep going :)
Now let's import use "useAppSelector" instead of useSelector in the previous Dashboard code and now see the error is also gone:
Now we will display the username value on the page like this:
Let's create use useRouter to redirect us to the dashboard when we click to log in, and if we log in, our input will be shown as a username on the dashboard. You can see, that we imported useRouter (line 9), store assigned it to the router variable (line 17), and used it to go to the dashboard when we clicked on the login button (line 21)
Finally!
Now let's type our name, I'll type my name, and let's click login to check if it works,
It worked!!!
This is it! This is how we pass state with the Redux toolkit. Now you can add more slices, and functions and use them using the same way.
Let's create a logout functionality now. So first, when we log in and go to our dashboard, we will have a logout button there and we'll make it work. The logout functionality will remove the username and set its value to the initial state which is an empty string.
On the dashboard page, we'll use the same way how we used the login functionality.
First, we'll import these three:
import { logOut } from '@/redux/features/auth-slice'
import { useDispatch } from "react-redux"
import { AppDispatch } from "@/redux/store"
Now, dispatch function to call our logOut function:
const dispatch = useDispatch<AppDispatch>();
Now, we'll create an on-click function that will call logOut and then use it in the logout button:
const onClickLogOut = () =>{ dispatch(logOut()) }
<button onClick={onClickLogOut}>log out</button>
Now these are the things we added to make our logOut function work in the dashboard:
Let's test it now!
We're in the dashboard now, I've logged in with the "Saran" username, let's test the logout. It will replace my name with an empty string if it works.
And it worked!
Now let's make it take us to the login section again when we log out like the same way we made our project take us to the dashboard when we log in. Now we will be back to the login page when we click logout:
After this, let's create a toggle moderator status inside the Dashboard, and it will display the text "Moderator" if we are, and then also a button from which we can toggle our moderator status.
In Dashboard, let's get the value of moderator status the same way we got the value of username. In the code below, we've stored the value of moderator status in a variable, then a <h2> that will display text if we're a moderator, and at last, a button to toggle moderator status:
Now to add toggle moderator functionality, let's go to auth-slice and create a function just like our logIn and logOut function, so below the logIn function, we create the toggleModerator function.
It will toggle the isModerator value between true and false. Now, let's export it so we can use it in our Dashboard.
auth-slice.ts:
It's simple from there now! We just have to import the toggleModerator function from auth-slice and use it the same way we used the username earlier (See line no 14).
Now we just have to create a function which will toggle moderator status (Line no. 29)
Now we create a <h2> which will show a text if our moderator status is true (Line no. 36). Also, we give the onClickToggleModerator function to the Toggle Moderator Status button (line no. 38):
That's it! Now let's check it. When we log in, we now get the option to toggle moderator. Let's click if now:
It worked! Now the text will hide if we click the toggle moderator status again. We have successfully created the toggle moderator functionality now.
So this is it guys. You've now learned the basics of Redux Toolkit and this will be enough to get you started using RTK on your projects.
I've learned this from the PedroTech YouTube channel so do check them out too! (I've changed many things in the blog that will not match the tutorial too but the overall concept are same, just made some things different so that it will be easier to understand)
I'm available on LinkedIn and Twitter if you want to ask any questions.
Thanks for reading!