Straightforward Redux, no strings attached!
Wanna know how to implement redux without additional weight? Tired of tutorials explaining to you how to use something in addition to 3 other packages and with little explanation about the necessary but bloated boilerplate code?
Say no more, I’ve got you covered.
First of all, you might not need Redux. It’s as simple as that. But since you are here, let’s dive in and see how to use it. I’ll split this tutorial into multiple parts. In this part, I’ll explain HOW to use redux as simple as possible while the followups are reserved for more complex scenarios and some explanations about certain decisions in my workflow and why I think it’s a good way to do things this way or that.
As usual, I’ve got some code prepared for this tutorial so if you go to GitHub, you can grab my setup at this tag. I’ve built a simple copy of a cookie clicker game that I’ve dubbed copy clicker. Every time the user clicks on the floppy disc, he will create our virtual currency of copies and using said currency, he can increase the CpC (copies per click) to get more bang for his clicks. We’ll use Redux to move the app’s state from the react code to the redux store.
Open the code from my GitHub Project and navigate to the project root to install our packages in the command line via npm or yarn.
>> npm i redux react-redux
To get started, let’s remove the current state from our app as well as all the code that translates user interaction into state changes. We’ll replace it with our new redux store in a moment.
We will also get our copies and CpC (CopiesperClick) from our props instead of the state now. You could hand them in from the app.jsx as props but we’ll come to that in a few lines anyway.
A Redux store works on a simple principle. The store handles your data and you can read from said store but you can’t change it directly. To do so, you dispatch an action and a reducer reads the action and changes the data for you. This way, your Redux store is a single point of truth. You send actions in and the store reduces them. Simple as that.
Moving over to our app.jsx, let’s create our store using the createStore function from Redux. We will also write our first reducer for handling clicks on our CopyClicker.
A reducer expects two parameters, the state and an action and returns the state when it’s done. As our reducer passes its state to the store and we want to set our initial values upfront, we’ll create a simple object for our initialState and pass it to our reducer as a default value. To check if our code is working so far, we can read the current state from the store using the store.getState() function.
To better monitor and understand what the action part of our reducer is about, let’s add it to our console.log. For now, you will only see one action with a type starting with “@@redux/INIT” so that’s something the store does when it’s created initially. Let’s dispatch our own action with a type of COPY_CLICK as that’s what we are currently trying to implement.
As you see, our dispatched action gets passed to the router but right now our router doesn’t know what to do with it. Let’s change that. A switch case will help us filter out only the types of actions we are listening for and default by returning the original state. Returning the original state as a default is important as you can use multiple reducers in redux and each of them either passes on the old state (default) or a new state.
It’s important to note here that a reducer should always either return the unchanged current state or a new state object. We should never change the state object. If a reducer is triggered by an action, it should create a new state object as a copy of the old state with some or all of the values changed. Immutability is important here for redux to work as expected and prevent bugs that are hard to track.
In case our action type “COPY_CLICK” is found, we will increase the current amount of copies in the state by one and pass this new state object back as a return value. If we add another console.log after our dispatch, we can see that the state has indeed changed.
If you were to duplicate our store.dispatch() line of code twice, the resulting state would be 3 copies instead of 1.
We can now start passing on those values to our CopyClicker component. This is where the second npm package we installed comes into play. React-redux offers us a Provider and connect component. The Provider wraps our app and allows us to make our store available to the rest of our app. We simply put it around our current App code and pass in our store as a prop.
Connect on the other hand allows us to connect parts of our app to the provided store. We can wrap our component in the “higher-order” connect component to do so.
As a Higher Order Component, connect expects something from us and will return a function that then accepts our CopyClicker component to connect it to the store. At this point, connect doesn’t make any guesses about what exactly has to be connected, it only offers us the current state. We need to provide that mapping object on our own so let’s write a function that accepts the state and maps our props accordingly.
As we are passing the state to our arrow function, we can now access state.copies directly, map it to our pre-existing “copies” prop and return a mapStateToProps object that we can hand over to our connect. This means we are now no longer exporting the CopyClicker component directly but a connected version of it.
Depending on the type of project you are working on, you will most likely only connect a few components to the store directly. Try to keep your other components as dumb as possible. That’s a rule of thumb that has worked well for me.
Right now you should see that our App shows the number of copies as 1 (or 3 if you previously left in all 3 dispatch calls) but the buttons are not yet working. We need to wire up our buttons inside the CopyClicker component to the store as well.
First of all, get rid of the dispatch call in App.jsx and all console.logs except for the one inside the reducer, then head over to our CopyClicker.jsx again. Until now, we only used dispatch in the place where we’ve defined the store. Luckily for us, connect adds the dispatch function to our prop so we can use it via props.dispatch(). A simple solution would be to do so inside our onCopyClicked function.
If you did this right now, you should be able to increase our amount of copies by 1 per click on the floppy disc icon and see our action in the console like before.
There is one last thing I’d like to do now that is not 100% necessary but for me it’s something I prefer to do to keep things clean and easier to read. Scroll down to the line where we created our mapStateToProps object and create a second one called mapDispatchToProps. As we did with the mapped state before, we now add a new prop called copyClicked to our object and map it to our dispatch function. Remember to add this new prop to our propTypes as a PropTypes.func.isRequired as well. We can use this new copyClicked() function from our props to handle the user input.
While this step is not necessary, it allows us to keep our code clean and readable and limits interaction with the store to the connect component, not spreading it all over our original CopyClicker component.
And that’s it. We have successfully added redux to our app and wired both our state and our functionality to the store. We can clean up our code a bit more and remove the last remaining console.log and we’re done.
“But hey!” you might say, “I can’t upgrade my CpC anymore. We’ve left that out!”. You are absolutely right. I’ve reserved that bit for the next part of this tutorial series. Mea culpa!
Recap and outlook:
We used Redux to create a store object, defined a reducer that listens for our actions and then connected our app with React-Redux by wrapping the app code in our provider and connecting the component that needs to interact with the store via connect.
You can check out and compare the final result of our project as the tag v0.1.1 on GitHub.
In the next part of the series, I’ll go a bit more into detail about the concept of redux middleware and project structure. In our simple and working example, we managed to get Redux working in as little as 30 lines of code in 2 files but as our app grows, it’s a good idea to split the Redux code into separate files and import only the necessary parts directly into our React code. So far, what we’ve created here is only slightly opinionated. Prepare for stronger opinions AND my reasons behind them in the next episode.
Some words about me:
In case you are interested in this tutorial’s game idea, see my article [Taking games apart: How to design a simple idle clicker]. If you want to see more of my work and progress, feel free to check out my series on building [a React Native Web App with Hooks, Bells, and Whistles with Expo SDK33]. I’m also currently working on a new series about building more complex and scalable apps using React/Redux, where I’ll go into details about how and why I do stuff the way I do as well as some articles on my experience in building games for web and mobile with React. This article is only the start of it so stay tuned for future updates.
And if you feel really supportive right now, you can always support me on patreon, thus allowing me to continue to write tutorials and offer support in the comments section.