

5 Best Practices for Handling State Structure in React
source link: https://blog.bitsrc.io/5-best-practices-for-handling-state-structure-in-react-f011e842076e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

5 Best Practices for Handling State Structure in React
Five best practices that can help to structure the state well
When we write a component in React that holds some state, we will have to make choices about how many state variables we want, and how to structure it.
Although it’s possible to write working code even with a standard state structure, here are a few steps to help you make better choices.
1. Group Related State
If two state variables change together, it is a good practice to combine them into a single state variable.
Example:
Unify the two variables:
const [x, setX] = useState(0);
const [y, setY] = useState(0);
Into the below structure:
const [position, setPosition] = useState({ x: 0, y: 0 });
Technically, both have the same approach. but it lessens the lines of code.
Note: If your state is an object, you cannot update only one field in it, hence you can do something like this setPosition({...position, x:100})
.
2. Avoid Duplicate in State
When the same data is duplicated between multiple state variables or within nested objects, it is difficult to keep in sync
Example:
import { useState } from 'react';const initialItems = [
{ id: 0, name: 'Indian Cuisine', },
{ id: 1, name: 'Italian Cuisine', },
{ id: 2, name: 'Chinese Cuisine' },
];export default function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedItem, setSelectedItem] = useState(
items[0]
);return (
<>
<h2>What's your Favourite Cuisine?</h2>
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
{' '}
<button onClick={() => {
setSelectedItem(item);
}}>Choose</button>
</li>
))}
</ul>
<p>You picked {selectedItem.name}.</p>
</>
);
}
Currently, it stores the selected item as an object in the selectedItem
state variable. However, this is not great: the contents of the selectedItem
is the same object as one of the items inside the items
list. This means that the information about the item itself is duplicated in two places.
Although you could update selectedItem
too, an easier fix is to remove duplication. In below this example, instead of a selectedItem
object (which creates a duplication with objects inside items
), you hold the selectedId
in state, and then get the selectedItem
by searching the items
array for an item with that ID .
Example:
import { useState } from 'react';const initialItems = [
{ id: 0, name: 'Indian Cuisine', },
{ id: 1, name: 'Italian Cuisine', },
{ id: 2, name: 'Chinese Cuisine' },
];export default function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedId, setSelectedId] = useState(0);const selectedItem = items.find(item =>
item.id === selectedId
);function handleItemChange(id, e) {
setItems(items.map(item => {
if (item.id === id) {
return {
...item,
name: e.target.value,
};
} else {
return item;
}
}));
}return (
<>
<h2>What's your travel snack?</h2>
<ul>
{items.map((item, index) => (
<li key={item.id}>
<input
value={item.name}
onChange={e => {
handleItemChange(item.id, e)
}}
/>
{' '}
<button onClick={() => {
setSelectedId(item.id);
}}>Choose</button>
</li>
))}
</ul>
<p>You picked {selectedItem.name}.</p>
</>
);
}
You don’t need to store the selected item in state, because only the selected ID is essential.
3. Avoid Redundant State
If you can calculate some information from the component’s props or its existing state variables during rendering, you should not put that information into that component’s state.
Example:
import { useState } from 'react';export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');function handleFirstNameChange(e) {
setFirstName(e.target.value);
setFullName(e.target.value + ' ' + lastName);
}function handleLastNameChange(e) {
setLastName(e.target.value);
setFullName(firstName + ' ' + e.target.value);
}return (
<>
<h2>Let’s check you in</h2>
<label>
First name:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
Your ticket will be issued to: <b>{fullName}</b>
</p>
</>
);
}
This form has three state variables: firstName
, lastName
, and fullName
. However, fullName
is redundant. You can always calculate fullName
from firstName
and lastName
during render, so remove it from state.
This is how you can do it:
import { useState } from 'react';export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');const fullName = firstName + ' ' + lastName;function handleFirstNameChange(e) {
setFirstName(e.target.value);
}function handleLastNameChange(e) {
setLastName(e.target.value);
}return (
<>
<h2>Let’s check you in</h2>
<label>
First name:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
Your ticket will be issued to: <b>{fullName}</b>
</p>
</>
);
}
4. Avoid Contradiction in State
When the state is structured in a way that several pieces of state may contradict, you probably have a chance to make mistakes. Try to avoid this.
Example:
import { useState } from 'react';export default function FeedbackForm() {
const [text, setText] = useState('');
const [isSending, setIsSending] = useState(false);
const [isSent, setIsSent] = useState(false);async function handleSubmit(e) {
e.preventDefault();
setIsSending(true);
await sendMessage(text);
setIsSending(false);
setIsSent(true);
}if (isSent) {
return <h1>Thanks for feedback!</h1>
}return (
<form onSubmit={handleSubmit}>
<p>How was your stay at The Prancing Pony?</p>
<textarea
disabled={isSending}
value={text}
onChange={e => setText(e.target.value)}
/>
<br />
<button
disabled={isSending}
type="submit"
>
Send
</button>
{isSending && <p>Sending...</p>}
</form>
);
}// Pretend to send a message.
function sendMessage(text) {
return new Promise(resolve => {
setTimeout(resolve, 2000);
});
}
While this code works, it leaves you in confusion of the what state to update exactly. For example, if you forget to call setIsSent
and setIsSending
together, you may end up in a situation where both isSending
and isSent
are true
at the same time.
Since isSending
and isSent
should never be true
at the same time, it is better to replace them with one status
state variable that may take one of three valid states: 'typing'
(initial), 'sending'
, and 'sent'
import { useState } from 'react';export default function FeedbackForm() {
const [text, setText] = useState('');
const [status, setStatus] = useState('typing');async function handleSubmit(e) {
e.preventDefault();
setStatus('sending');
await sendMessage(text);
setStatus('sent');
}const isSending = status === 'sending';
const isSent = status === 'sent';if (isSent) {
return <h1>Thanks for feedback!</h1>
}return (
<form onSubmit={handleSubmit}>
<p>How was your stay at The Prancing Pony?</p>
<textarea
disabled={isSending}
value={text}
onChange={e => setText(e.target.value)}
/>
<br />
<button
disabled={isSending}
type="submit"
>
Send
</button>
{isSending && <p>Sending...</p>}
</form>
);
}// Pretend to send a message.
function sendMessage(text) {
return new Promise(resolve => {
setTimeout(resolve, 2000);
});
}
The more complex your component is, the harder it will be to understand what happened.
5. Avoid Deeply Nested State
Deeply hierarchical state is not very easy to update, hence it is a best practice to make the state structure flat.
Example:
import { useState } from 'react';const initialTravelPlan = {
id: 0,
title: '(Root)',
childPlaces: [{
id: 1,
title: 'Earth',
childPlaces: [{
id: 2,
title: 'Africa',
childPlaces: [{
id: 3,
title: 'Botswana',
childPlaces: []
}, {
id: 4,
title: 'Egypt',
childPlaces: []
}, {
id: 5,
title: 'Kenya',
childPlaces: []
}, {
id: 6,
title: 'Madagascar',
childPlaces: []
}, {
id: 7,
title: 'Morocco',
childPlaces: []
}, {
id: 8,
title: 'Nigeria',
childPlaces: []
}, {
id: 9,
title: 'South Africa',
childPlaces: []
}]
}, {
id: 10,
title: 'Asia',
childPlaces: [{
id: 11,
title: 'China',
childPlaces: []
}, {
id: 12,
title: 'Hong Kong',
childPlaces: []
}, {
id: 13,
title: 'India',
childPlaces: []
}, {
id: 14,
title: 'Singapore',
childPlaces: []
}, {
id: 15,
title: 'South Korea',
childPlaces: []
}, {
id: 16,
title: 'Thailand',
childPlaces: []
}, {
id: 17,
title: 'Vietnam',
childPlaces: []
}]
},{
id: 18,
title: 'Mars',
childPlaces: [{
id: 19,
title: 'Corn Town',
childPlaces: []
}, {
id: 20,
title: 'Green Hill',
childPlaces: []
}]
}]
};function PlaceTree({ id, placesById }) {
const place = placesById[id];
const childIds = place.childIds;
return (
<>
<li>{place.title}</li>
{childIds.length > 0 && (
<ol>
{childIds.map(childId => (
<PlaceTree
key={childId}
id={childId}
placesById={placesById}
/>
))}
</ol>
)}
</>
);
}export default function TravelPlan() {
const [plan, setPlan] = useState(initialTravelPlan);
const root = plan[0];
const planetIds = root.childIds;
return (
<>
<h2>Places to visit</h2>
<ol>
{planetIds.map(id => (
<PlaceTree
key={id}
id={id}
placesById={plan}
/>
))}
</ol>
</>
);
}
You can nest state as much as you like, but making it “flat” can solve numerous problems. It makes state easier to update, and it helps ensure you don’t have duplication in different parts of a nested object.
Conclusion
Choosing the state structure is very essential to avoid confusions and duplications. Taking care of minor things while writing the code leads to perfect optimized code.
Did you know that State can be a component?
Build modular, reusable React components with Bit. Monorepos are a great way to speed up and scale app development, with independent deployments, decoupled codebases, and autonomous teams.
Bit offers a great developer experience for building component-driven monorepos. Build components, collaborate, and compose applications that scale. Our GitHub has over 14.5k stars!

Recommend
-
16
React Error Handling and Logging Best PracticesHow to handle errors and properly log them using the right tools
-
13
New issue [ WIP | Discussion ] React / Sutil inspired state handling #179 ...
-
9
<?xml encoding="utf-8" ??>Introduction This guide explains error handling in Go and the best practices for handling API errors in Go. You should have a working knowledge of Go and understand how...
-
7
React State and Form HandlingReact State and Form Handling08/06/2022Forms are really important in any website for login, signup, or whatever. It is...
-
7
Best Practices to Structure Accessibility Testing Presented on July 20, 2022 at 12pm ET This webinar will cover common gaps in how organizations implement accessibility testing. These gaps complicate communication with...
-
9
Blazor Best Practices: Handling Errors
-
12
Quick Microsoft .NET 6 Overview Microsoft .NET 6 is a cross-platform framework that merges the .NET Core, .NET Framew...
-
4
Introduction One of the challenges of working with any graphics API is that under the hood they tend to be very asynchronous in nature. The work you send to the GPU isn’t completed the moment you call draw or dispatch,...
-
9
5 Best Practices in Handling HTTP Errors in JavaScript Errors are an everyday obstacle that all developers and clients face. A typical error is when applications or a particular fun...
-
13
Dependency Handling Best Practices in a React ComponentsUse Dev Deps, Peer Deps and more when building a React Components
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK