Getting Started With Redux, Part 1

Christopher Leja
The Startup
Published in
4 min readOct 2, 2020

If you’ve ever worked with React, you know managing state is central to building interesting apps. But if you’ve ever built anything with complex state in React, you know it can get hectic quickly — different components need different information, and passing props and callbacks from parent components becomes a debugging nightmare, as you sit in front of your illegible mess of code, weeping.

That last part might not be universal, but the fact is tracing the threads of application state across components is messy, inefficient, and stressful.

Me, trying to share a piece of state between two distant cousin components

Fortunately, as you’ve probably guessed, other, smarter people had this problem before me, and came up with tools to simplify it. By far the most popular, at least for React, is a library called Redux. Redux separates your app’s internal state to a central location outside of the component tree. This saves you from having to pass information down from parent components to child components, and makes that information equally accessible from everywhere. It also helps keep your applications predictable, and offers useful tools for debugging.

I’m going to break down Redux piece by piece. This post will cover the most basic pillars of Redux, and set a foundation for moving forward. In the future, I’ll dive into more advanced parts, but in the interest of keeping this short, I decided to break it up.

So how does Redux work?

At its core, Redux is simple — there’s a central store that houses your app’s state. This store is equally accessible from every component. The store is updated by dispatching actions to a reducer.

My reaction when I first heard someone say “the store is updated by dispatching actions to the reducer”

Don’t worry if that sounds confusing — honestly, I think one of the hardest parts of learning Redux is getting used to the vocabulary. But we’ll cover all of it in detail.

First, add redux to your project with either npm or yarn.

npm i redux
// or
yarn add redux

Actions

Basically, actions are events — they’re how we send data from the application to the redux store, whether that data is user input, form submissions, or API calls.

We send actions using the store.dispatch() method. Actions themselves are just JavaScript objects with two key properties — type and payload. Type tells Redux what change we want to make, and payload holds the information we want to change it with. You don’t need to add the payload property in every situation (like, if you just want to increment something by a constant amount), but your actions do always need to have a type property.

// the type is always a string explaining what we'd like to happen
// the payload is an object with the data we'd like to send
{ type: "LOGIN", payload: { username: 'user1', password: 'abc123' }}

Actions are almost always created with action creators, which are simply functions that return an action object. Here’s an example:

const handleLogin = (username, password) => {
return {
type: "LOGIN",
payload: {
username: username,
password: password
}
}
}

Simple enough, right?

Reducers

A reducer is a function that is responsible for translating our action objects into actual changes to our state. A reducer always takes two arguments: our state, and the action we’ve just dispatched. It then returns our new state. On the most basic level, what’s happening is this:

const reducer = (state, action) => {
return newState
}

Here’s a slightly more complex example:

const loginReducer = (state = initialState, action) => {

// by convention, most people use switch/case statements
// in reducers, but if/else works as well

switch(action.type):
//this will handle any action with the type "LOGIN"
case "LOGIN":
return state.map(user => {
if (user.username === action.payload.username){
return user;
}
// note that I'm using the spread operator
// because Redux won't let you directly mutate data
if (user.password === action.payload.password){
return {
...user,
isLoggedIn: true
}
}
})
default:
// in case Redux doesn't recognize the action type,
// I want to be sure to return the original state

return state;
}

There are a few key things to remember about reducers: first, they have to be pure functions. That means they will always give you the exact same results if you enter the same arguments, and they have no side effects (such as data mutation). Basically, Redux doesn’t want you directly changing data in the reducer. You always have to return a copy instead. Also, always remember to return the original state if the action.type isn’t recognized. You can also have multiple reducers, which is often helpful for separating concerns (and avoiding having overwhelmingly long reducers).

Store

The third pillar of Redux is the store. The store is where we house the application state. It has three key methods: getState(), dispatch(), and subscribe(), which we use to get and set the state, respectively. Here’s an example:

import { createStore } from 'redux';const store = createStore(loginReducer, { username: '', password: '', isLoggedIn: false });store.dispatch(handleLogin)

So here, we create the store by importing the createStore function from Redux. Then, we call the function with our reducer as the first argument and the initial state of our app as the second argument. Then we use the dispatch method to call our action, which will be sent to the reducer, and voila! Our store will be updated.

In the next installments I’ll dive deeper into more advanced methods, the react-redux library and the Redux toolkit, clarifying how it can simplify state management and make our lives as software engineers easier. In the meantime, I encourage everyone to read the docs which are very thorough, easy to follow, and illuminating.

Thank you for reading! I hope this helped clarify Redux a little bit. If you want some more insight into using Redux in React applications, check out Part 2.

--

--