It's all about completing your goals! I'm partly talkin about life, but I'm mostly talking about my latest project app.
Lately I've been thinking a lot about the things I've been working towards, and the things that I still have left to do. I've found that long term goals and huge life projects are easier to manage when broken into smaller more concrete tasks. The problem is then that, it becomes easy to forget the significance these smaller tasks have on the bigger picture.
That's why when I set out to do a React/Redux project, I decided to make an app designed to track your goals. It's like a to-do list maker, where each list also tracks how close it is to be completed. Users can sign up, create goals and then add tasks to those goals. As they mark their tasks completed, a progress bar for the goal indicates how close they are to completing the entire goal (as a measure of the task that the user set).
As this was a huge project (for me at least), I'm not going to be covering everything that went into this application. I am planning on doing more regular, and more a more specific series of posts as I work on my next project, but for this one I will be focusing on specific parts of the project that I learned the most on. So, what I am going to focus on in this post are some of the key concepts in using Redux and how I was able to conceptualize the Redux data flow model.
The backend
The backend was built using Rails 6 running in API mode. It is set up almost the same as it was in my plain JavaScript project, so I'm not going to repeat myself. like I said, I'll go into details like this during my next project. For now, I'll just say that I set up Rails to handle CRUD actions for users, goals, and tasks and to communicate data to and from my frontend.
Getting Started With React
React is a library for JavaScript that allows us to use JSX and Components to build out an interactive frontend. JSX is really cool, and allows you to write JS and html together, declaratively. Components are a very object-oriented approach to creating UI elements, and we can control the component data through local state and/or props passed between parent and child components.
I found React to be pretty easy to learn if you are familiar with vanilla JavaScript and html. The documentation is very good and there are a ton of other resources. For this reason, I am not going to go any deeper into using React, for now.
Redux
The biggest hurdle for me, and the thing that became the key to building this project, was truly understanding the "redux flow".
At first all the vocabulary (reducer, mapStateToProps, dispatch, actions, etc.) made Redux seem like an overly complicated way to handle data. And for small apps with only a couple of components, it probably is. Redux makes sense in apps with multiple components that have state and/or deep parent/child component trees. As I was learning the redux concepts, all the tutorials and documentation explained the concepts with isolated examples and implementations, but it wasn't until working on a larger project where the data flow of React actually makes sense, and the benefits start to appear.
Redux data flow
You don't really want to track a bunch of props and callback functions. Maybe from one parent to child, but grandparents? cousins? Nah. What you really want is for a component to just grab it's own data. You want components that are flexible enough to use anywhere. The more components there are, the more sense it makes to use redux to keep the hierarchy flat and have one easily accessible place to store all the data that the frontend needs.
The Store
In fact, that's what finally made redux click for me. Redux is all about using one place to store all your data. That leads us to the createStore() function, and the redux store. It could be thought of as a "global app state" or "where I store all my component states". This is where the data flow will start and stop.
Connect
To get data, we want our components to connect to the store and grab some data. This is the next important piece of the redux puzzle: connect(). By using connect(), a component gets access to the store almost as if it were a direct parent. Your component is still going to receive the data as props and use callback functions to change state, only it now is going to get the data from the store instead of a parent.
Mapping State to Props
Okay, So how does connect know what data to send from the store? or how do we send data back to the store? That's where our two 'match' functions come in! mapStateToProps and mapDispatchToProps are going to let us get store values and change store values, respectively.
With mapStateToProps, we use an object to define what data we want to grab from the store. The key of the object will become a key in the props of the component, and the value will be derived from the state of the redux store. Hence, they call it mapStateToProps.
So, That's how we get our data from our store down into a component. That's the first half of our Redux flow, and the easiest to understand.
Mapping Dispatch to Props
We're using mapStateToProps to get our values from the store and use them as props in our component. But, sometimes we might want to change the state of the store. Instead of values, we're going to want some callback functions that do something to the store. For this, we use mapDispatchToProps. There are different ways to implement the dispatch function, but the basic idea is that we are going to send (or dispatch) something called an "action" with the all the info a component needs to send to the store.
Action Creators
Since our store is going to want to receive actions, and we want callback functions to pass to our components, we can just make functions that create our different actions and use those. Obviously, they're called action creators and mapDispatchToProps will send them down to the component as props.
As a note, I'm using the redux-thunk middleware so that I can pass dispatch functions as well as objects. Since I've decided to focus on the data flow, just know that the goal is still to create an action to send to the store.
Now, once the component does whatever it's going to do, it uses the action creator function that it received as props, to make an action that gets dispatched to the store. This action is going to have to tell the store how to change, and maybe even provide some data that the store would need. For this reason, the action is going to be an object with a type property (a string to tell the store what to do) and a payload property (the data that the store
will need), if necessary.
Reducers
I've been saying that we're going to send an action to "the store", but, if you think about it, so far "the store" is just our application state, a pool of data. If we send an action object to it, it'll just sit there inside our state object. We need someway to take the action we just received, look at the current state of the the store (or a part of it) and give us back an updated state. These functions are called reducers, and they are the last part of our Redux data flow.
The Reducer is going to look at every action that gets dispatched, and examine its type property. If it matches a type specified in the reducer, the reducer will return a new state based on that action type and other action properties, if necessary.
Redux Benefits
That's how we get our data from our Redux store, out to our components and get other information back from our components to modify our store. As I mentioned in the beginning, this can seem like a lot more work than simply passing down props, but there are some key advantages to this method.
For one, we can connect to any component, no matter how deep in the component tree we are rendering it. This means that our components that are connected to the store, can have the same data and functionality no matter where they are. Another bonus is that because they are all connected to the store, sibling and even cousin components can stay in sync without complicated routing and passing props through multiple levels. There is also the fact that our action creators can be used by any component, giving us a lot of flexibility in how we can interact with the app.
Final Thoughts
It's not always the best solution, and when you learn about redux using a single component or two, it can definitely seem overwhelming and unnecessarily complicated. However, the bigger and more moving parts an app has, the more sense it makes to set up a central place to handle the storage and modification of that data. Once you understand how the data flows through the application, and why we are using actions and reducers and all the redux stuff, It becomes much easier to work with.
Top comments (0)