How to use Redux/Toolkit: A Developer's first Interface with @reduxjs/toolkit

How to use Redux/Toolkit: A Developer's first Interface with @reduxjs/toolkit

"Change is constant", that's something I used to hear a lot. What does that even mean? Is it an oxy... You know what? Never mind, I digress. In this article, what change means is, you'll be writing redux quite differently.

What is Redux?

Redux is a predictable state container, which is fancy lingo for saying Redux stores your application state. It is predictable because its behavior is consistent & nothing changes in your application state without your say-so.

Here's an article simplifying what Redux means, you can check that here

Moving on...

In this article, we'll be using reduxjs/toolkit which comes with nifty tools out of the box to build a responsive layout in our React app.

First, let's create a new react app. Open up your terminal & write the following command below:

npx create-react-app redux-tut

Next, let's install all the cool stuff we need. I will be using npm as my package manager, you can use yarn if you so please:

npm install @reduxjs/toolkit react-redux

After installing the packages we need, we're going to set up a folder structure for our application. Create two folders in your src directory, redux & components. Our folder structure should look something like this:

folder ss.png

Inside our redux folder, create a store.js file. This file is going to house our application state, the only way to change the state in our store is to dispatch an action on it.

#store.js

import { configureStore } from "@reduxjs/toolkit";

export default configureStore({
       # some code we'll get to later
    }
})

Ok, what the @#%! is going on?

In the past, configuring our Redux store would involve calling the createStore API, passing a rootReducer, combining reducers & using middleware but using reduxjs/toolkit, which comes with the configureStore API, all of that is done for us by default.

Here, we have imported configureStore from @reduxjs/toolkit & we have also exported it for use throughout our application.

Next, we'll create a slice folder inside our redux folder then a windowDimensionSlice.js file inside our slice folder. Our folder structure will then look something like this:

slice ss.png

Inside windowDimensionSlice.js, let's write some code.

#windowDimensionSlice.js

import { createSlice } from '@reduxjs/toolkit';

export const windowDimensionSlice = createSlice({
  name: 'windowDimension',
  initialState: {
    width: window.innerWidth,
  },
  reducers: {
    resizeWidth: (state) => {
      state.width = window.innerWidth;
    },
  },
});

export const { resizeWidth } = windowDimensionSlice.actions;

export default windowDimensionSlice.reducer;

Let me explain what's going on here.

First, we import createSlice from @reduxjs/toolkit. createSlice is a function that accepts an initial state, an object of reducer functions that automatically generate actions (action creators & action types) that correspond to the reducers & state.

Secondly, we export windowDimensionSlice for use throughout our application. The createSlice object accepts a name parameter, which is the slice name, in our case, it is 'windowDimension' & an initialState parameter which holds the default state value of the slice, in our case, it is width: window.innerWidth

Thirdly, we have a reducers object that contains functions designed to handle action types (similar to a case statement in a switch) so you don't necessarily need to have a constants folder with all the actions types you want to dispatch. In our case, we have named our reducer resizeWidth, & it accepts a parameter state, we then set the state of our application (from the initialState) by calling state.width, we set that to window.innerWidth.

Ok, before we take a break to look at cool cat photos on the internet, let's take a look at the last two lines here. We export { resizeWidth } & set it to windowDimensionSlice.actions. Remember our reducers object contains functions that handles action types, so resizeWidth is an action(type). We also export windowDimensionSlice.reducer. If you also remember, the createSlice API accepts an object of reducer functions. reduxjs/toolkit reduces the amount of logic we have to write by doing all the grunt work for us out of the box.

Cat Break!

We've learned a lot, I bet we feel as cool as these cats.

via GIPHY

Diving back in...

Remember our store.js file? Yes, let's update that.

#store.js

import { configureStore } from "@reduxjs/toolkit";

# updated code
import windowDimensionReducer from "./slice/windowDimensionSlice";

export default configureStore({
    # updated code 
    reducer: {
        windowDimension: windowDimensionReducer
    }
})

Here, we have imported windowDimensionReducer from our windowDimensionSlice.js file then we went further to add it to the reducer object inside our configureStore().

Whew! We have made a ton of progress but our application doesn't know what's going on yet, so let's tell it everything we have been up to. Navigate to your index.js file.

Inside index.js, let's update the code with the following imports & wrap our app component with all the cool Redux stuff we've been up to.

#index.js

import store from './redux/store';
import { Provider } from 'react-redux';

 <Provider store={store}>
     <App />
 </Provider>

Ok, seriously?? What the @#!$ is this? Let me explain.

We imported our store from store.js & Provider from react-redux. Provider - you guessed it - provides our application with every bit of state information it needs to function properly, we then pass it a store prop which acts as the source of all that state information the Provider gives our application.

Last Lap.

Inside our components folder, let's create two files mobile.jsx & desktop.jsx. Our folder structure should now look like this:

components ss.png

Okay, great! Let's add some code to these files.

#mobile.jsx

const Mobile = () => <div>Mobile</div>;

export default Mobile;
#desktop.jsx

const Desktop = () => <div>Desktop</div>;

export default Desktop;

Now, to wrap it up. We go in our App.js file & write a gazillion lines of code like this:

#App.js

import { useEffect } from 'react';
import { resizeWidth } from './redux/slice/windowDimensionSlice';
import { useDispatch, useSelector } from 'react-redux';

# components
import Desktop from './components/desktop';
import Mobile from './components/mobile';

const App = () => {
  const updatedWidth = useSelector((state) => state.windowDimension.width);

  const dispatch = useDispatch();

  useEffect(() => {
    const handleWindowResize = () => {
      const resize = dispatch(resizeWidth({ width: window.innerWidth }));
      console.log(resize);
    };

    window.addEventListener('resize', handleWindowResize);
    return () => window.removeEventListener('resize', handleWindowResize);
  }, [dispatch]);

  return <> {updatedWidth <= 900 ? <Mobile /> : <Desktop />} </>;
};

export default App;

Sheesh! That's a lot of code. Let me walk you through what we've just done. We import useEffect from react, then we import resizeWidth, which is the action we want to dispatch from windowDimensionSlice.js, useDispatch to well, dispatch the action & useSelector to - you guessed it again - select & read the data from the store.

At the top level of the App component, we declare a variable updatedWidth & then call useSelector() to grab the state we want to use from the store. We also declare a variable dispatch = useDispatch().

Inside our useEffect hook, we create a function handleWindowResize & inside that function, we dispatch the resizeWidth action. We go further by adding a resize EventListener for when our application mounts & we pass our handleWindowResize function as a callback.

Finally, we clean up after ourselves by removing the EventListener return () => window.removeEventListener('resize', handleWindowResize); then we pass dispatch into the dependency array of the useEffect hook so our application functions just right.

To test it all out, we use our updatedWidth variable to check that the width of our browser window is less than or equal to 900 updatedWidth <= 900 ? if it is, we render <Mobile />, if it's not, we render <Desktop />

We should have something like this:

video-to-gif-converter.gif

Disclaimer: You will typically have to do this sort of thing with React's ContextAPI but should your application become large, & will need Redux in the long run, you should consider using Redux for the responsive design of your application.

If you want to jump right into the code, the github repository is here