A component with poorly structured state can lead to a plethora of issues and standing at the top of these issues is the fact that it can be a never ending source of errors, bugs and misconfigurations.However when a component has well structured state, you get a less error prone component that is easy to modify and is quite straightforward to debug. Lets look at what it takes to ensure a component has properly structured state.
Fundamentals of structuring data
When you are building a react component it will very likely use state to some extent, in the course of building the component the decision of how many state variables to use is up to you, and if the state is an object you also decide what properties it contains. Even though you recognize that your state is not structured properly it is still possible for the component to work decently well. However we can do better, the points below will serve as a guide when making decisions on the state structure of your components.
Linked or Interconnected states should be merged. There are times when we change the value of more than one state at a time. If these states regularly change at the same time then they are good candidates to be merged.if you decide to leave the state splitted that is fine however a major benefit to grouping interconnected states is that the chances of forgetting to keep those states synchronised reduces significantly.An example of such states could be aspect ratio.
const [height, setHeight] = useState(500);
const [width, setWidth] = useState(500);
in the example above it is fine to separate the state but if you have a regularly update both states say to maintain aspect ratio then you could benefit greatly from merging them like so.
const [aspectRatio, setAspectRatio] = useState({width:500,height:500)
Note that if your state variable is merged into an object you cant just update one property without copying the other property.like in the example above you would not update it like so
setAspectRatio({width:750})
if you do it this way then you state would have height missing. so what you would want to do is
setAspectRatio({...aspectRatio,width:750})
States should not clash
A good case for this is a submit button, when you clik on a submit button to send a request you would usually use state to trigger a visual indication that the request is being sent and is not resolved yet and the same would be done for when the request is resolved.If you implement a similar functionality and your state struture looks like the one below, well there is a problem you are likely to run into.
export function ApiResult() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [resolved, setResolved] = useState(true);
async function onSubmitClick(event) {
event.preventDefault();
setLoading(true);
setResolved(false);
await requestToServer(text);
setLoading(false);
setResolved(true);
}
return (
<>
...
</>
);
}
The code above will work but the state structure above sets us up to get unreasonable and erroneous states. There is a chance that you will forget to update the state for either the loading state or the resolved state, meaning that we could have a state where the request is still loading and and the same time the request is already resolved. The more dense or complicated your component is the harder it will be to track down the source of the error.A good solution to this would be to pair constants that would represent each state of the request with a state variable like in the example below.
export function ApiResult() {
const STATE = {
loading:'loading',
error:'error',
resolved:'resolved',
}
const [requestState, setRequestState] = useState(STATE.resolved)
async function onSubmitClick(event) {
event.preventDefault();
setRequestState(STATE.loading);
await requestToServer(text);
setRequestState(STATE.resolved);
}
return (
<>
...
</>
);
}
The above approach ensures that you can still set the three states needed in a manner that is less error prone such that no contradictory states can be set and checks can still be performed without the fear of misspelled strings.
Don't create unnecessary state This might seem like an obviouse thing to not do, but I find that it is quite common that developers make this mistake.Before you create some state make sure that its values cannot already be gotten from the Prop being passed in, a State or a combination of state values during rendering. if it can be derived you should avoid adding that information as component state.
export function Tracker() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const [position, setPosition] = useState({x:0,y:0});
function onXchange(value) {
setX(()=>value);
setPosition((initial)=>({...initial,x:value}));
}
return (
<>
...
</>
);
}
can you see what the issue is in the code above? The position can already be calculated using the x and the y state already in the component or the position state could have being retained getting rid of the other 2 states but all three should not exist in the component.
Do not repeat state
When state is duplicated in a component you will quickly find that it becomes very hard to keep them in sync with each other. The moment you find yourself using more than one state to help your component persist some data then you should immediately find a way to resolve that even if it means raising that state above the component.
Minimize the nesting of your state
When building a component, especially when building novel features the last thing you want us to add an extra layer of complexity, this is the challenge that an overly nested state poses. When state is overly nested it can become a nightmare to update. So how does on resolve the issue of an overly nested state? The solution is to make it "flatten" or "normalize" it. The following steps could help to normalize most nested data. Rather than a tree-like structure whereby a property has an array of child states, you could instead have the parent states hold an array of its child state IDs. Then create a mapping from each child state ID to the corresponding parent state.In the end how deep you chose to nest your state is up to you but a fact is that making your state “flat” will help you avoid a lot of problems beacuse It will make your state updates that much easier besides tha flatter your state the more assured you can be that there is no state duplication in your code.
NOTE! you can use immer which is a library that lets you manage immutable state, you might want to make use of this library for you nested states it can make nested state updates easier for you.
If you do have any question or need clarification on any of the points above please leave a comment and i'll reply as soon as possible.
Top comments (0)