The Good/The Bad of React & Redux: And Why Ducks Might Be The Solution

Before I dive deep into the world of React and Redux, allow me to provide some context:

My journey to tech was rather unconventional or non-traditional if you will. I didn’t study Computer Science in college but actually was an English teacher for 7 years before embarking on my journey of learning to code.

During those years in the classroom, I spent a great deal of my time lecturing about structure. And by that I mean:

  • the 5 paragraph essay
  • the art of writing an outline
  • rules of grammar
  • & proper sentence construction
Essentially all things structure and organization when it comes to a high school lit class.

This all combined with the fact that I’m also a Virgo means that I like really really like structure. Specifically, organized structure.

Hi, my name is Lauren & I’ll never not color code my notes

At Ada Developers Academy, a tuition-free year-long program that trains women and gender diverse folk to code in Seattle WA, my peers were constantly giving me a hard time over my color-coded notes.

But in my defense, I had just quit my job and had taken this massive risk on learning to code, and this sort of stuff suits me! It brings me peace, joy, and sanity.

And while learning to code, just like my old classroom, I loved when everything had its place.

I love a thoughtfully organized codebase, it genuinely makes me smile.

So it should be no surprise that later on down the road when I became a software engineer at Amazon, I fell really hard for React.

I’m sure a lot of readers may already know, but React has many powerful features including:

  • the Virtual DOM
  • native compatibility
  • a relatively quick learning curve
  • helpful developer tools

but most of all:

  • the reusability of modularized components

Ever since learning about it, I’ve always been really drawn to the component driven architecture that’s the foundation of React.

TL;DR React’s modular components are freaking awesome but sometimes state management can get wackadoodle

There are tons of articles/resources out there that provide details on React’s awesome features so I won’t dive deep into those here, but I will pause to make sure we’re on the same page when it comes to the modularized and reusable components.

When you build an app with React, you create a bunch of independent, isolated, and reusable components.

You then compose them all together to build complex user interfaces.

And the former grammar obsessed rule following teacher that I am LOVES the encouraged organization and division of the components this implies. I find the reusability of the components to be so helpful for engineers. Plus it always makes it so easy to find things within your code.

To visualize this concept, here’s a screenshot of a React app that aggregates viewing data to create a “Most Binged TV Shows” app:

The app itself can be divided into components:

  • the nav
  • the show item
  • the show list all together

The architecture of that app is pretty straight forward, but as we all know, things can get complicated and tricky and really quickly go from this to this:

And thus developers at this point are forced to discover the sticky or not so glamorous part of React.

Oftentimes, developers want to pass state as props around the app quite a bit. React advocates for a single directional flow and things can get messy when you want to keep your data “in sync” when 2 (or more) components share that same data.

Ideally, the source-of-truth of the data is only in one place. The React docs encourage you to “lift state up” if you have two children that need to access the same data. This means putting the data in the nearest ancestor of the 2 components. But if these two components are very far apart in the tree, the “nearest ancestor” component could be at the TOP LEVEL of the component tree.

And to complicate things even more- the intermediate components may have absolutely NO USAGE for the props it’s being passed- it just happens to be stuck in the middle and has to pass it along. No thank you.

My favorite analogy for this concept is the idea of me wanting to tell my cousin a story. But instead of being able to tell her directly that story, I also need to tell my aunt. Now that’s fine if it’s a little, non-embarrassing story, but say it’s about something I don’t necessarily want my aunt to know about?

Well, bummer dude, I still have to pass it through her regardless.

So the top ancestor has to pass the data down through several intermediate components along the way to get to the proper component, and just like the game telephone we played as kids, this creates a million different opportunities for developer error or unclarity for future users. The original story can get muddled and someone could easily get confused as they try to trace the props as they get passed between the many components.

–aka no bueno-

To view it another way, once state is being passed back and forth and up and down between the component trees, it’s easy to imagine that things could get complicated real quick.

And to add insult to injury, my girl Sandi Metz taught me to fear coupling and that’s happening big time between each component and its parent. So to try and move a component around would become super complicated. There is coupling between the component and its parent AND between the component’s children that it’s passing props to!

Thus, to no one’s surprise, this impacts performance as every update to the data made will cause ALL of the children to re-render- which can cause massive performance and speed issues!

🤷🏼‍So it’s really a balancing act. 🤷🏼‍

There are many great things that React brings to the table, but as you can see, it’s important to find a solution for managing an application’s state if you’re hoping to build anything more complex than just a to-do list!

When you’re in the designing phase of a project, more often than not, a few things are crucial:

  • for the app to be able to scale
  • to be able to create something that’s maintainable many months or years into the future
  • and dare I say, it’d be nice to maintain your sanity when it comes to state management!

Which brings me to Redux- the state container superhero that saves the day!

So, remember the chaos that was the mess of passing data around the components? That is what Redux successfully helps you make sense of.

Redux is a state management tool for JavaScript applications. Meaning that I can pass data (or the embarrassing story about my most recent Bumble dating escapade) to my cousin without having to also tell my aunt about it as well. Aka, I get to avoid the chaos of that story being bounced between all of the components (or my many crazy uncles), just to update or change one of them.

And this all becomes possible because of Redux’s most important principle: the global store!

So let’s talk about that. The big thing to remember is that the entire state of an application is stored in one central location, called the store.

Meaning that each component of your React app gets to have direct access to the state of the application without having to send props down to a child component or using callback functions to send data back up to a parent! And that’s pretty dreamy.

Redux provides a central store that can hold data from ANYWHERE in the application.

To put it differently, Redux completely eliminates this messy tunneling that can happen when you’re passing data from a parent down to many different subcomponents and manipulating it in all those places.

Now, this is a diagram of how that all happens:

And I’ll show how to add each piece of it to a React app to illustrate how it all works together.

So for the app I showed earlier, you can imagine that each color here is a different React component.

And currently with just React, the data in this app flows directionally as so:

BUT I want to add redux so that the state and data transfer looks more like this:

And to make that happen, the first thing to do is to build the store.


HOW TO ADD REDUX TO YOUR REACT APP

STORE

It’s crucial to remember that the store in Redux is like the human brain. It’s absolutely fundamental: the state of the whole application lives inside the store. So to start, you should create a store for wrapping up the state.

Create a directory for redux within your src folder:

src/redux/

Create a store folder within that:

src/redux/store/

Then create a new file named index.jsand finally initialize the store:

// src/redux/store/index.js 
import { createStore } from ”redux”;
import rootReducer from “../reducers/index”;
const store = createStore(rootReducer);
export default store;

The createStore()is the function for creating the Redux store and while you may also pass an initial state to createStore(), most of the times you don’t have to. Although passing an initial state can be useful for server-side rendering, traditionally, the state comes from reducers, which is what I did here- it takes a reducer (rootReducer) as the first argument.

BUT WAIT. I haven’t really explained what reducers do yet!

Remember, the state comes from the reducer. What matters now is understanding what reducers do.

REDUCERS

Let’s go back to our diagram.

In Redux, reducers produce the state. The state is not something you create by hand.

Reducers specify how the application’s state changes. One of the principles of Redux is that the state is immutable and cannot change in place.

In plain React, the local state changes in place with the function setState().

But in Redux, you cannot do that.

A reducer is just a Javascript function- it takes two parameters: the current state and an action, which is why the reducer must be pure- meaning that it returns the exact same output for the given input.

And creating a reducer is actually pretty simple.

Here’s how:

Create a directory for the root reducer within redux:

src/redux/reducers/

Create a new index.js file within that folder.

In that file:

// src/redux/reducers/index.js
const initialState = {
shows: []
};
const rootReducer = (state = intitialState, action) => state;
export default rootReducer;

This reducer is sort of a silly one because it returns the initial state without doing anything else.

But definitely notice how the initial state is passed as a default parameter.

Now, reducers are without a doubt the most important concept in Redux. Let me say it again, Reducers produce the state of the application.

But this should then beg the question, how does a reducer know when to produce the next state?
ACTIONS

Well, that’s where actions come in!

One of the principles of Redux is that the only way you can change the state is by sending a signal to the store. This signal is an action.

“Dispatching an action” is the process of sending out a signal.

And, how do you change an immutable state? Well, you don’t. The resulting state is a copy of the current state plus the new data.

And you may be thinking, woah Lauren, that’s a lot of data to know.

But the reassuring thing is that Redux actions are nothing more than Javascript objects! This is an example of what it might look like:

{
type: 'ADD_SHOW';
payload: { title: 'Marvelous Mrs. Maisel', rank: 1, picture: 'https://image.jpg' }
}

So let’s build the action

Create a directory for the actions:

src/redux/actions/

Create a new index.jsfile within that folder

In that file:

// src/redux/actions/index.js
export const addShow = show => ({ type: “ADD_SHOW”, payload: show });

Every action requires a type property for describing how the state should change (and it’s really just a string). The reducer will use that string to determine how to calculate the next state. And you can specify a payload as I did if you like.

Referring back to the diagram and because types are just strings and strings are prone to typos and duplicates, it’s better to have action types declared as constants.

ACTION TYPES

It’s a best practice to wrap every action within a function which definitely helps to avoid errors that would be difficult to debug.

So let’s also build a simple action-creator.

Create a directory for the constants within redux:

src/redux/constants/

Create a new action-types.js file within that folder.

In that file:

// src/redux/constants/action-types.js
export const ADD_SHOW = “ADD_SHOW”;

Then open upsrc/redux/actions/index.jsand update the action to use action types:

// src/redux/actions/index.js
import { ADD_SHOW } from “../constants/action-types”;
export const addShow = show => ({ type: ADD_SHOW, payload: show });

Hold up, let me check in. Readers, are you still with me??

No? Okay maybe it would help, before going any further, if I recap the main Redux concepts and everything that just happened:

  • the Redux store is in charge of orchestrating all the moving parts
  • all of the state lives as a single, immutable object
  • as soon as the store receives an action, it triggers a reducer
  • the reducer returns the next state

The order sort of goes like this:

  1. An action first occurs within a component, such as someone trying to add a show, filter the view, see the details of a component, or even deleting a component, etc.
  2. That then calls the action and action type
  3. Which then grabs the particular reducer that will update or modify the state
  4. And once the state is changed, the view is rerendered
Boom! And there you have it! That is how Redux works!

Congrats! You now know everything there is to know about how to add Redux to a simple app and should feel ready to scale it to an app on your own. 
Except, wait, you may have noticed that while I was setting up just the beginnings of the Redux architecture, I had to create a bunch of additional folders and files.

Now imagine adding in ALL of the functionality of a traditional app. If I was to continue down this path, it’d be easy to imagine how things could get pretty messy and/or confusing real quick.

You’d have to edit the constants in one file, then edit the reducer in another, then edit the actions in another, and finally, edit the action creators in yet another.

It’s like a boring tennis match no one cares to watch!

And it means that even adding just a small feature might equate to editing and adding several different files! Which is kind of a headache.

Me bouncing back and forth between all of the different folders and files!

This way of organizing your Redux is the most common and is essentiallyBy Type.

But as was just demonstrated, you end up hopping back and forth between files for a single piece of related functionality because the constants, actions, and action-creators are imported into the reducer file and the action-creators are also imported into the container to be dispatched. And all of this becomes a little annoying, wouldn’t you agree?

What I found to be really wild was that there is actually no prescribed one way of organizing your Redux files. ThisBy Typemethod is super common and is taught in a ton of tutorials, but obviously, it’s sorta flawed.

My theory is that often times when you’re learning about Redux and the roles of actions and reducers, you start off with really simple examples. And most tutorials available don’t take you to the next level. But if you’re building something with Redux that’s more complicated than a todo list, you realize pretty quickly that you may need a smarter way of scaling your codebase over time.

These actions, constants, and reducers are all related but are in a fractured state and honestly, switching from one file to the next not only takes a few seconds, but can make your head a little fuzzy, and I feel like it really slows down my production efficiency.

Simply put, it’s hard to maintain! 🙅🏼

So obviously I felt frustrated by this when I was first learning React and absentmindedly set up an app’s architecture in this manner.

All of my English teacher organizational habits came rushing back to me. I KNEW that there was a way to strategic and thoughtful with the organization of a codebase’s architecture.

I dove into the research and Medium articles knowing that there had to be something out there that would offer me some piece of mind. And I was right, there were lots of ideas out there about it.

Some people were suggesting online that a solution to this frustration might be to organize your code by feature. As in:

  • encapsulating a component (the container)
  • its state (the store)
  • and its behavior (the actions) into a single folder

All following the React’s component concept.

But this means that you’d have to tie a slice of the Redux store to a container and that is completely counterintuitive to the core of what Redux promotes!

So I’m not about to advocate for this pattern to you.

And so back to the drawing board I went and further research led me to ultimately discovering…

The moment you’ve all been waiting for, based on the title of this article, Ducks to the rescue!

But what are Ducks?

Well, Erik Rasmussen, who coined the term, realized that he was often creating and editing one piece of functionality at a time.

He kept needing to add {actionTypes, actions, reducer}tuples for each use case. Just as I was, he was keeping these in separate files and separate folders, however, 95% of the time, it was only one reducer/actionpair that ever needed their associated actions.

Thus, it made more sense for these pieces to be bundled together in an isolated module that’s self-contained and can even be packaged easily into a library.

Ducks is essentially a proposal for bundling reducers, action types, and actions all into the same file.

But WHY specifically would this organization structure be the solution to this architectural problem?

Or rather, why get excited about Ducks?

Beyond their adorable-ness factor that is 😉

Well, Ducks seeks to solve the issue of the toggled back and forth repetitive nature of organizing by type. With Ducks, rather than splitting up all related code, I can package it into redux modules!

So let’s refactor the React app you just created and convert our Redux to DUCKS.

Remember that the earlier example created the functionality of adding a show, and now you’ll need to move the actionTypes, actions,andreducers all into just one file!

HOW TO CONVERT YOUR REDUX TO DUCKS

  1. Create a directory for the Ducks within redux:
src/redux/ducks/

2. Create a newindex.js file within that folder:

3. Grab the actionTypes from the constants folder and put it into the new ducksfile:

// src/redux/ducks/index.js
export const actionTypes = {
addShow: {
ADD_SHOW = “ADD_SHOW”
}
};

4. Let the deleting begin and delete the entire constants folder!

5. Next up is actions file. Grab the actions:

// src/redux/actions/index.js
addShow = show => ({ type: “ADD_SHOW”, payload: show });

6. And put that into the ducks file:

// src/redux/ducks/index.js
...
export const actions = {
addShow = show => ({type: ADD_SHOW, payload: show});
};

7. Continue the fun and delete the entire actions folder!

8. Lastly, tackle the reducers.

9. First grab this reducer:

// src/redux/reducers/index.js
const initialState = {
shows: []
};

10. And actually, let’s change the reducer because the one from the first example does nothing other than returning the initial state, which is easy to fix by making it a switch statement.

11. In the ducks file add:

// src/redux/ducks/index.js
...
export const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_SHOW:
return { …state, shows: […state.shows, action.payload] };
default:
return state;
}
};

12. And of course, don’t forget to delete the reducer folder!

13. TA-DA! You’ve just created a modularized Ducks file!

Your Ducks file should look a little something like this!

This file now officially contains ALL of the functionality that was built out before. It runs exactly the same way, but now, it is modularized into a clean, comprehensible file, or duck, for optimized state management.

Just to really bring things home, remember when you created all of those folders and all of those files that you separated and fragmented from one another?

Well, compare that mess to this new file, which contains all of that same capability in a legible fashion, and I’ll call that a win!

Plus, it’s pretty easy and painless to create.

Thus, the art of ducks-ing is a way of structuring an app to become more modular, which yes, is a real sexy buzz word in the world of software engineering, but it’s really great because it now has become super obvious which piece of Redux is handling which functionality.

On a completely practical level, you no longer have to scroll through masses of files before finding the one you need to work on.

But this is only one way of structuring redux. There are plenty of other options.

And the simple fact that I’m encouraging readers to explore ways to creatively define what might work best for you may, in fact, mean that the structure-obsessed type-A person that I once was is leveling up to become someone who embraces ambiguity and discovers that sometimes you can define your own rules and doing whatever is best for your code and your team is ultimately the right choice!

What I love most about Ducks is how clean and clear it is.

It removes a lot of unnecessary boilerplate and you can easily add a simple action or even an app’s entire functionality by changing a single file.

I’ve been on teams that have adopted this structure and we were really happy with it.

Maybe you will be too?!
Presenting at CascadiaJS 2018
Give it a shot and let me know what you think.

Hit me up on Twitter- @LoLoCoding and tell me all about your own quacky architectural solutions!

And be sure to check out Rasmussen’s original proposal to learn more too!

Also, if you’re a visual/audio learner, here’s a video of me presenting this information at CascadiaJS in 2018.

This story is published in The Startup, Medium’s largest entrepreneurship publication followed by +413,678 people.

Subscribe to receive our top stories here.