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 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.
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.
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.
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.
Use local state when …
Use Redux store when …
Use loaders when …
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
2 Replies to "Component state: local state, Redux store, and loaders"
A React article from 2020 using componentWillMount ????
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