blog

  • Home
  • blog
  • To Redux or Not: the Art of Structuring State in React Apps

To Redux or Not: the Art of Structuring State in React Apps

One common trend I find among most Redux developers is a hatred towards setState(). A lot of us (yes, I’ve fallen into this trap many times before) flinch at the sight of setState() and try to keep all the data in our Redux store. But, as the complexity of your application grows, this poses several challenges.

In this post, I’ll walk you through various strategies to model your state, and dive into when each of them can be used.

Getting Started

Redux works with the principle of being the single source of truth for your application state. A new Game of Thrones season is airing now, and I’m sure everyone’s excited to know how this is going to unfold. Let’s build a fun Game of Thrones fan listing page, to understand these concepts in detail.

Note: I’ll be using yarn to run the app. If you don’t have yarn set up, replace yarn with npm.

Before we dive in, download the basic skeleton from the repo and run:

yarn install
yarn run start

You should see a basic list page with some of your favorite GoT characters listed.

Note: We’ll be using the ducks pattern to write our application. It reduces unnecessary module imports and cuts down on a lot of boilerplate.

Intro to Redux

The scope of this article is to help you structure your Redux apps. It assumes a basic knowledge of the library. I’ll give a brief overview of Redux concepts that will help you follow the rest of the article better. If you’re familiar with how these works, feel free to skip this section.

All Redux apps make use of four important constructs: actions, reducers, a store, and containers.

Actions

An action is an intent to update the state. It could be triggered by a network call, or a user clicking a button. Actions have two parts:

  1. Action type. A unique identifier representing an action.
  2. Payload. Any metadata that’s associated with the action. For instance, if we make a network request to fetch a list of movies, the response from the server is the payload.

For this example, we’ll be using a library called redux-actions to create actions.

Reducers

A reducer is a function that listens for an action and returns a new state representation.

Store

An application can be divided into many reducers, representing various parts of the page. A store brings all these together and keeps the app state intact.

Containers

Containers connect your app state and actions with the component, passing them down as props.

To get a deep understanding of how this works, I’d encourage you to first look at the free introduction series by Dan Abramov.

Split App Data and UI State

The list page is nice, but the names don’t give any context to people who are new to the GoT universe. Let’s extend the component to render the character description as well:

//GoTCharacter.js

export const CharacterRow = ({character}) => (
  <div className="row">
    <div className="name">{character.name}</div>
    <div className="description">{character.description}</div>

  </div>
);

While this solves the problem, our designers feel that the page looks clumsy, and it’s a better idea to collapse this information till users want it. There are three different approaches we can take to solve this problem.

The setState approach

The simplest way to achieve this in React is using setState() to store the data within the component itself:

//GoTCharacter.js

export class StatefulCharacterRow extends Component {
  constructor() {
    super();
    this.state = {
      show_description: false
    }
  }

  render() {
    const {character} = this.props;
    return (<div className="row">
      <div className="name">{character.name}</div>
      <a href="#" onClick={() => this.setState({
        show_description: !this.state.show_description})} >
        {this.state.show_description ? 'collapse' : 'expand'}
      </a>
      {this.state.show_description &&
        <div className="description">{character.description}</div>}

    </div>);
  }
};

The Redux approach

Using setState() is fine as long as the state we’re dealing with is only local to the component. If, for instance, we want to put in place an “expand all” function, it will be difficult to handle this with just React.

Let’s see how we can move this to Redux:

// FlickDuck.js

// …
export const toggleCharacterDescription = createAction(
  FlixActions.TOGGLE_CHARACTER_DESCRIPTION, (character) => ({character})
);

export default (current_state, action) => {
  const state = current_state || default_state;

  switch (action.type) {
    case FlixActions.TOGGLE_CHARACTER_DESCRIPTION:
      return {...state, characters: state.characters.map(char => {
        if (char.id === action.payload.character.id) {
          return {...char,show_description: !char.show_description};
        }

        return char;
      })}
    default:
      return state
  }
}
// GoTCharactersContainer.js

import { connect } from 'react-redux';
import GoTCharacters from './GoTCharacters';
import {toggleCharacterDescription} from './FlickDuck';

const mapStateToProps = (state) => ({
  ...state.flick
});

const mapDispatchToProps = (dispatch) => ({
  toggleCharacterDescription : (data) => dispatch(toggleCharacterDescription(data))
});

export default connect(mapStateToProps, mapDispatchToProps)(GoTCharacters);
// GoTCharacters.js

const GoTCharacters = ({characters,toggleCharacterDescription}) => {
  return (
    <div className="characters-list">
      {characters.map(char => (
        <CharacterRow
          character={char}
          toggleCharacterDescription={toggleCharacterDescription}
          key={char.id}/>
      ))}
    </div>
  );
};

export const CharacterRow = ({character, toggleCharacterDescription}) => (
  <div className="row">
    <div className="name">{character.name}</div>
    <a href="#" onClick={toggleCharacterDescription.bind(null, character)} >
      {character.show_description ? 'collapse' : 'expand'}
    </a>
    {character.show_description &&
      <div className="description">{character.description}</div>}

  </div>
);

We’re storing the state of the description field inside the character object. Our state will look like this now:

state = {
  characters: [{
    id: 1,
    name: "Eddard Ned Stark",
    house: "stark",
    description: "Lord of Winterfell - Warden of the North - Hand of the King - Married to Catelyn (Tully) Stark",
    imageSuffix: "eddard-stark",
    wikiSuffix: "Eddard_Stark",
    show_description: true
  },
  {
    id: 2,
    name: "Benjen Stark",
    house: "stark",
    description: "Brother of Eddard Stark - First ranger of the Night's Watch",
    imageSuffix: "benjen-stark",
    wikiSuffix: "Benjen_Stark",
    show_description: false
  }]
}

This is a general pattern a lot of developers follow when they’re starting out with Redux. There’s nothing wrong with this approach, and it works great for smaller apps.

So far, we’ve been dealing with the characters from the first chapter of GoT, and the universe is about to get a whole lot bigger. When it does, our app will become slow. Imagine looping through 1000 characters to update one row.

Let’s see how to scale this for a larger dataset:

// FlickDuck.js

// …
case FlixActions.TOGGLE_CHARACTER_DESCRIPTION:
  const {character} = action.payload;
  return {
    ...state,
    character_show_description: {
      ...state.character_show_description,
      [character.id]: !state.character_show_description[character.id]
    }
  }
// …

And in GoTCharacters.js:

export const CharacterRow = ({character, character_show_description, toggleCharacterDescription}) => (
  <div className="row">
    <div className="name">{character.name}</div>
    <a href="#" onClick={toggleCharacterDescription.bind(null, character)} >
      {character_show_description[character.id] ? 'collapse' : 'expand'}
    </a>
    {character_show_description[character.id] &&
      <div className="description">{character.description}</div>}
  </div>
);

When the user clicks on the expand link, we update the character_show_description with the current character id. The state looks like this now:

state = {
  characters: [...],
  character_show_description: {
    1: true,
    2: false
  }
}

Now we can update the UI state without looping over all the characters.

Continue reading %To Redux or Not: the Art of Structuring State in React Apps%

LEAVE A REPLY