Kasra Khosravi Chief Problem Solver @ MyToolBox.Dev

Component state: local state, Redux store, and loaders

5 min read 1580

In React, components are considered first-class citizens and therefore it is essential to be familiar with their inner working mechanism. The behavior of a component mainly depends on its props or state. The difference between them is that state is private to a component and is not visible to the outside world. In other words, state is responsible for the behavior of a component behind the scenes and can be considered the source of truth for it.

There are multiple ways to manage the state for a component like local state, Redux store, and even the use of this. However, each method has its own advantages and disadvantages when it comes to managing the state.

Local state

Local state in React allows you to instantiate a plain JavaScript object for a component and hold information that might affect its rendering. Local state is managed in isolation within the component without other components affecting it.

Keep in mind that using local state in the context of React requires you to create your components using the ES6 classes which come with a constructor function to instantiate the initial requirements of the component. Additionally, you have the option of using the useState Hook when creating functional components.

In a component built with ES6 classes, whenever the state changes (only available through setState function), React triggers a re-render which is essential for updating the state of the application. Here is an example:

import React from 'react';

Class FlowerShop extends React.Component {
  
  constructor(props) {
    super(props);
    this.state = {
      roses: 100
    }
    this.buyRose = this.buyRose.bind(this);
  }
  
  buyRose() {
    this.setState({
      roses: this.state.roses + 1
    })
  }
  
  render() {
    return (
      <div>
        <button
          onClick={ this.buyRose }>
          Buy Rose
        </button>
        { this.state.roses }
      </div>
    )
  }
  
}

Imagine that the above component acts like a flower shop that has its internal tracking system to see how many roses the store has at a given time. This can work properly if the FlowerShop component is the only entity that should have access to this state. But imagine if this store decides to open a second branch. In that case, the second flower shop will need access to the number of available roses as well (AKA this.state). Something that is not possible with the usage of local state.

So now we have realized a big disadvantage of the local state which is the shared state. On the other hand, if we want to keep track of an isolated state of the component that will not be shared with other parts of the outside world (like UI state), then the local state will be a perfect tool for that use case.

Redux store

So we get to the second use case which is the shared state between components. This is where Redux store comes into play. In a nutshell, Redux has a global store that acts as the source of truth for your application. To extend this to the flower shop example, imagine a main headquarters for a flower shop. Now this headquarter knows everything about the flower shop chain stores and if any of them would need access to the available number of roses, it would be able to provide that information for them. Here is an example:

import React from 'react';
import { connect } from 'react-redux'
import Events from './Events.js'

Class FlowerShop extends React.Component {
  
  constructor(props) {
    super(props);
    this.buyRose = this.buyRose.bind(this);
  }
  
  buyRose() {
    this.props.dispatch(Events.buyRose())
  }
  
  render() {
    return (
      <div>
        <button
          onClick={ this.buyRose }>
          Buy Rose
        </button>
        { this.state.roses }
      </div>
    )
  }
  
}

const mapStateToProps = (store) => {
  return { roses: store.roses }
}

export default connect(mapStateToProps)(FlowerShop)

For the purpose of our current discussion, the important takeaways from the Redux structure above are the mapStateToProps and connect functions. In this scenario, when an event like buyRose function is triggered, an event is dispatched and the Redux’s global store gets updated.

As a result, we use the mapToState function to get access to the Redux’s global store and use it as props in the FlowerShop component. The great thing about this structure is that whenever we update the props, React will trigger a re-render, just like updating the state.

We made a custom demo for .
No really. Click here to check it out.

Finally, connect is the magical function that glues everything together, so our FlowerShop components and its props will be mapped to the global store and its state.

Redux is a powerful tool with logical concepts that make it easier to understand and manipulate the structure of the application state; especially for the application that is larger in scope. But it can introduce many hassles for simpler and smaller applications which might not be necessary. Also, it is not the only solution that you can have to manage your application’s global state. As a developer or software architect, it is more important for you to understand the reasoning behind Redux structure. In that case, you might be able to use it in a more efficient way for your application or even create your own minimalistic solution which is more efficient. We will cover that next.

Loaders

As introduced by Dan Abramov, there appears to be two types of React Components, presentational and container components. For instance, presentational components are supposed to be dumb or stateless while container components should act as smart or stateful. But as hinted in the article, it is wrong to assume any component only belongs to one of these categories. It is sometimes totally ok (and necessary) to ignore this distinction, but adopting this mental model of separating complex stateful logic can pay off in a large codebase.

It is known that reusing stateful logic between React components is hard. There have been solutions for this particular problem like Hooks, render props, and higher-order components, but they each come with different shades of complexity, advantages, and disadvantages. In this article, I am not comparing these solutions with each other, as it can vary based on your project needs. Instead, I will discuss a specific use case of using higher-order components to solve a repeating issue in one of my previous projects.

Imagine that there is a certain type of entity in your project (like a list of available flowers in our flower shop example) that several components might need. In this scenario, all of the parents of those components need to do the same API call and update their individual states with the returned API result. But we did not want to repeat ourselves and decided it would best to extract the functionality and move them to new entities which we called loaders.

To continue our work with component state management, let us build a simple loader example. A loader is an entity that is responsible for making API call outside of the scope of the presentational component and then wraps that component (hence a higher-order component) and maps its internal state to the component props. In this case, the component does not need to know anything about how its props are derived. Simple and fancy. Right! 🙂

import React from 'react';
import { FlowerLoader } from './loaders';

// Functional Component for simplicity
const FlowerShop = (props) => {

const { roses } = props;

  return (
    <div>
      <button>
        Buy Rose
      </button>
      { roses }
    </div>
  )
};

let Wrapper = FlowerShop;
Wrapper = FlowerLoader(FlowerShop);
import React from 'react';

// API Call to get the Flowers based on a key
const { GetFlowers } = require('./api');

const NOP = () => null;

const FlowerLoader = (component, placeholder, key = 'roses') => {

placeholder = placeholder || NOP;

// Acts as a higher order function
class Wrapper extends React.Component {
    constructor(props) {
    super(props);
    this.state = { };
  }

  UNSAFE_componentWillMount = () => {
    let roses = this.props[key];
    // We can also add more states here like
    // let lily = this.props[lily];

    if (roses != null) {
      GetFlowers(this.onFlower, roses);
    }
  }

  // The state needs to be updated when receiving newProps
  UNSAFE_componentWillReceiveProps = (newProps) => {
    let roses = newProps[key];

    if (roses != null) {
      GetFlowers(this.onFlower, roses);
    }
  }

  // Callback function to setState if API call was successful
  onFlower = (err, roses) => {
    if (err || !roses) {
      // Do nothing
    } else {
      this.setState({ [key]: roses });
    }
  }

  render() {
    // Mapping state to props
    const localProps = Object.assign({}, this.props, this.state);

    // Extra check to see if the component should be rendered or the placeholder
    const hasRoses = localProps[key] != null;

    // https://reactjs.org/docs/react-api.html#createelement
    return React.createElement(
      hasRoses ? component : placeholder,
      localProps
    );
  }
}

return Wrapper;

};

As you can see in the code example above, the whole processes of fetching data from an API call and setting it as props is hidden away in loader. So when a component like FlowerShop get wrapped around by FlowerLoader , it has access to roses props without the need to keep it in a local state or redux store state and updating it after each new API call.

Conclusion

Use local state when …

  • You have a very simple application and do not want to bother setting up a tool like Redux
  • You need to use and set short term states like the typed value in a text input
  • The state does not need to be shared with other components

Use Redux store when …

  • Your application is more complex and the need to break apart the state into different sections seems necessary
  • You need to use and set longer-term states like the result of an API call
  • The state needs to be shared with other components

Use loaders when …

  • You are repeating yourself by setting the same type of state and state updater over and over. Using a loader would end that repetition

 

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    Full visibility into production React apps

    Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

    LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

    The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

    Modernize how you debug your React apps — .

    Kasra Khosravi Chief Problem Solver @ MyToolBox.Dev

    2 Replies to “Component state: local state, Redux store, and loaders”

    1. Good Catch Henrique. I use hooks now so much that forgot to update the name of these lifecycles when writing this article. I make sure to update the name of both componentWillMount and componentWillReceiveProps

    Leave a Reply