Introduction
Redux! The word which you've been hearing over and over again. What is it? Why is it needed? Maybe you've glanced at the daunting Redux docs and thought "Nah, I'll learn that some other day". Well, today is that day and I guarantee that it will be easier than expected.
Many existing Redux tutorials are outdated, but this guide will show you the latest, recommended way to implement the framework into your app using redux-toolkit. We will go over it in 5 simple steps. It is recommended that you follow along as we learn how to set up, read and write data to Redux. The rest, you can only learn through further experimenting.
What is Redux?
Redux is a global state management system for your entire web/react app. Imagine a state which is shared by every component which can be read and updated at any level, on any page. No callback functions.
1. Set-up
Lets get started. For this tutorial we be using NextJs and TypeScript. Start by initialising a project. npx create-next-app@latest --ts
In the root of your project, install the dependencies react-redux and @reduxjs/toolkit. npm i react-redux @reduxjs/toolkit
If you would rather just start playing around with the tutorial code now, the repository can be found on my github.
2. Create a slice
Slices are the functions which define how a global state is managed. In a slice, we define the initial state and also the reducers which define how the data is manipulated. Create the file src/reducers/FooReducer.tsx
containing the code, below.
// fooReducer.tsx
import { createSlice } from '@reduxjs/toolkit';
const initialValue = { name: "Nigel", age: 63 };
export const fooSlice = createSlice({
name: 'foo',
initialState: {value: initialValue},
reducers: {
changeAll: (state, action) => {
state.value = action.payload;
},
agePlusOne: (state, action) => {
state.value.age += 1;
}
}
})
export const { changeAll, agePlusOne } = fooSlice.actions;
export default fooSlice.reducer;
It looks like there is a lot going on, but it will become clearer. Trust me. Let's focus on what matters here.
We have an initialValue
which defines the initial value of an object containing a 'name' and 'age' value.
Under reducers
we have two special functions which show how
the data can be manipulated. We may add as many of these reducer
functions as we need.
The function changeAll
takes in an object with new key values e.g. {name: 'Bob', age: 44}
and replaces the current key values.
The function getOlder
takes no parameters, and increases the age
value by 1.
3. Set up Provider
In order for the Redux state to be synchronised across the app, we must nest everything inside a <Provider/>
component. Copy the code below into pages/_app.tsx
.
// _app.tsx
import type { AppProps } from 'next/app'
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import fooReducer from '../src/reducers/FooReducer'
const store = configureStore({
reducer: {
foo: fooReducer,
}
});
function MyApp({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
export default MyApp
If you are familiar with NextJs, you will know that _app.tsx
is the root component of the application. Any page loaded from /pages
is rendered inside <Component {...pageProps} />
which means that all routes will always be within the <Provider/>
component, allowing access to the global state(s) defined by store
on any page.
4. Reading the global state
Go ahead and copy the following code inside your pages/index.tsx
// index.tsx
import type { NextPage } from 'next'
import { useSelector } from 'react-redux';
const Home: NextPage = () => {
const foo = useSelector(state => state.foo.value);
return (
<main>
<p>{foo.name}</p>
<p>{foo.age}</p>
</main>
)
}
export default Home
When accessing the dev environment, we are now greeted with the text
'Nigel' and '63'. This is the initial state of the object we defined in FooReducer.tsx
!
The useSelector()
function this global state from the store
we set up in our _app.tsx
.
5. Writing to the global state
Edit your index.tsx and add the following code.
// index.tsx
//...
import { useDispatch } from 'react-redux';
import { agePlusOne } from '../src/reducers/FooReducer';
const Home: NextPage = () => {
//...
const dispatch = useDispatch();
return (
<main>
{foo.name}
<button onClick={() => {
dispatch(agePlusOne(null));
}}>Plus One</button>
<br />
{foo.age}
</main>
)
}
useDispatch()
is the function which is used to execute the functions outlined in our FooReducer.tsx
. Here, we have imported the agePlusOne
function which adds 1 to the current age value. When we click the
button, the age will increase by 1. The function takes no arguments.
If we want to do the same with the changeAll
function, we must import it like we did with the agePlusOne
function and call it with an argument of the new state that we want e.g. dispatch(changeAll({name: 'Bob', age: 44}))
.
Wrapping up
If you want to add more reducers, all you need to do is simply create additional components, for example BarReducer.tsx
and then include it in the store
constant defined in _app.tsx
as below.
const store = configureStore({
reducer: {
foo: fooReducer,
bar: barReducer
}
})
After doing this, you can then reference bar
as you did with foo
and have multiple global states!