React State Management Made Easy: Basics, Essentials and Best Practices

A typical React application is built relying on reusable components and they can be simply considered as the application building blocks. In general, the React components interact unidirectionally from a parent component to a child component through React props. However, if required, the child to parent communication can be achieved through callback functions. While this blog will mainly focus on React state management, it is crucial to have a basic understanding of how a React application operates and the related technical terms. 

React Component

Components represent a part of the UI, which can be reused within the application as needed. For example, a contact card which includes name, email and mobile number can be created as a component and reused in multiple places in a web application.  

React Props

React props are used to pass information from a parent component to a child component. They act as arguments to a function (or a method). If we take the contact card component described above, the props could be the name, email and the mobile number, provided by the parent component 

React Component States

React states are variables in the components which can be read and updated. They can be used to update the component based on the user interactions, API calls etc. When the state is updated, React ensures that the component re-renders to show the relevant UI changes. Imagine that there is a fourth prop in our contact card component called contactTimes, and it is only shown if a button is pressed. This can be achieved by tracking the state of the button and showing or hiding the contact times based on it.  

useState Hook

UseState hook is a popular and a well-known way to manage React component states.  

const [showContactTimes, setShowContactTimes] = useState(false); 

In the code above, we define  showContactTimes state and a setter function to update it called setShowContactTimes. We also set the initial state value to be false.

We can change the state by calling the setter function, i.e., setShowContactTimes(true)or setShowContactTimes(false).

 

If we need to implement a toggle action for the button in our use case, we can call the setter as follows, 

setShowContactTimes(!showContactTimes); 

 

Contact card component:

useReducer Hook

In React, useReducer hook could be used to update the states similar to useState hook. However, while useState has a more direct and simple approach to update the state variables, useReducer is more suitable when complex logic is involved. Updating a component state using useReducer hook involves defining a reducer function which sets the new state value based on the argument passed (i.e., action type) to a dispatch function call. Let’s see the use of useReducer in our contact card implementation. 

useReducer initilization:

The reducer function handles the state updating logic as follows: 

The dispatch function can be called when the user presses the button to see the contact times. 

Please note that we can have multiple action types in the reducer function and set the state variable to different values based on it.  

Best Practices in State Management

Now that we have a basic understanding of how React component states work and how to implement them, let’s look at some of the best practices of using states.  

State grouping

React component states can be single values or objects with multiple values. While it is possible to use individual values to maintain the component states, sometimes it is useful to contain multiple values in state objects if they are related to each other. For instance, instead of creating firstName and lastName two state variables, it is a good idea to create firstName: John, lastName: Doe}state object. This practice improves the code readability and reduces the possibility of coding mistakes

Avoid logically impossible state combinations

Logically impossible states are the state values that cannot exist together at the same time. These contradictory situations can happen when there is a logical error in the code or when the source data that the states rely on are inconsistent. However, the risk of this happening could be reduced by cautiously defining your component states. For example, it is not good to have radioButtonAClicked and radioButtonBClicked two states if both belong to the same radio button group. The reason being, even though technically both those states can be true, it is contradictory to the behavior of radio button groups, where only a single radio button can be selected at a time.  

Use props directly when possible

A common mistake in React development is reading component props and assigning them to component states even when they are not expected to be changed. This should be avoided as it unnecessarily complicates the code and does not provide any benefit.  

State lifting - sharing states

State lifting is a concept in React where you move/lift the state from a child component to its parent component. This is done when multiple child components are required to share the same state. State lifting makes the state accessible to all child components as props, ensuring shared state management. 

State lifting - updating the shared state

If a child component or multiple of them require updating the shared state, it can be achieved by parent component passing the state setter function (as a callback function) to the child component(s) as a prop. Then the child component will call this setter method as required and the change of the shared state will be reflective throughout all the child components that share the state.  

State management beyond the component scope

So far, we have mainly discussed managing states within scope of a React component. If we require to consider state management beyond a particular component, we will have to use props and callback functions as mentioned in the State lifting section. While this is possible to do in more straightforward scenarios, it becomes a tedious task when it comes to more complex cases, such as when the component tree is deeply nested. In a scenario like this, the props will flow through the tree into the deeply nested child components which require them, resulting in passing those props through intermediate components that don’t necessarily need to use them. This pattern of using the props in a deeply nested component tree is referred to as prop drilling. 

React Context

React Context provides a convenient method to pass data through the component tree without having to pass props down manually at every level. It’s used for sharing global state or data within the component tree and a good alternative to the prop drilling explained above.  

Let’s look at the use of React context to manage states beyond the component scope easily by applying it to our contact card application. Here as an example usage, we will use the context to redact the mobile number from displaying. 

We can create a redactContext.tsx as follows:

The contactCard.tsx can be updated to use the redact context to decide if the mobile number should be redacted or not. 

This context can be provided by the parent component to the contact card component.

Note how the RedactContext.Provider is used with the value set to true 

While this provides context with a hardcoded value, we can easily pass a component state as the context value to make it dynamic. Note how a new button is added to the parent component to redact or display the mobile number shown in the child component.  

The final view of this application looks like below:

Conclusion

Effective state management is crucial for developing efficient and maintainable React applications. For simple, component-level state, useState is often the most straightforward solution. As your application logic becomes more complex, useReducer provides a more structured way to handle state transitions, especially when multiple actions or conditions are involved. Additionally, when state needs to be shared across multiple components, React context allows for easy global level state management without the hassle of prop drilling. 

React state management is a broad area in frontend application development, and beyond the built-in tools, there are major third-party frameworks like Redux, MobX, Recoil etc, offering advanced solutions for handling complex states across large applications, which we did not cover in this blog.  

At Cevo Australia, our highly capable frontend and fullstack consultants are specialised in modern web development using frontend frameworks like React for Cevo’s wide range of customers. Whether it is a greenfield application, uplifting an existing application or new feature implementation, Cevo’s frontend and fullstack experts can provide the guidance and development expertise you need to ensure the success of your project. 

In addition to the modern React development, our consultants also specialise in deploying and scaling React applications using AWS services. React apps can be seamlessly hosted on AWS S3, served via CloudFront, or deployed in ECS as containerised applications for more complex use cases. Cevo’s muti-skilled consultant teams can guide you through the process of designing, development, hosting, scaling and managing your React applications in the cloud. 

Enjoyed this blog?

Share it with your network!

Move faster with confidence