In React apps, there are at least seven ways to handle the state. Let us briefly explore a few of them in this part.
URL
We can use URL to store some data e.g.
- The id of the current item, being viewed
- Filter parameters
- Pagination offset and limit
- Sorting data
Keeping such data in the URL allows users to share deep links with others.
It is recommended to avoid storing such information in the app’s state to avoid the URL in our app getting out of sync. The URL should be used as the system of record, Read from it as needed for information related to sorting, pagination, etc. Update the URL as required when the settings change
React Router is a great tool to handle routes and manage the params.
We do not need to store the id in a state
or pass it as props to the ProductDetails
component instead,
it can be fetched using useParams()
.
Web Storage
The second option is to store the state in the browser via web storage. This is useful when we want to persist state between reloads and reboots. Examples include cookies, local storage, and IndexedDB. These are native browser technologies.
Data persisted in the browser is tied to a single browser. So, if the user loads the site in a different browser, the data will not be available.
We avoid storing sensitive data in the browser since the user may access the app on a shared machine. Some examples of where web storage might be most useful include storing a user’s shopping cart, saving partially completed form data or storing JWT token in HttpOnly Cookie.
Here is an example of saving user preferences locally in the browser or even persist the complete state for one or more of our components.
Local State
The third option is to use store state locally. It is useful when one component needs the state. Examples include a toggle button, a form, etc.
Lifted State
The Fourth option is to define the state in the parent component. Often, the same state is used across multiple components. In those cases, it is useful to lift the state to a common parent. The lifting state is a two‑step process. First, we declare the state in a common parent component, and then we pass the state down to child components via props. This pattern should be considered any time a few related components need to use the same state. The lifting state avoids duplicating states in multiple components. It helps to assure that our components all consistently reflect the same state.
In the below example, we have lifted the state and
the handleChange
event in the parent component, helping to maintain consistency.
Derived State
The fifth option is to compute the new state based on the available state
and we do not need to declare a state at all.
If there are existing values that can be composed to give us the information we need,
then we can calculate that information on each render instead of storing it.
Some examples include calling .length
on an array to determine the number of records
instead of storing a separate numItems
variable in the state or deriving an errorsExist
boolean
by checking if the errors array is empty.
So, why bother deriving the state? Well, deriving the state avoids our state values getting out of sync. It simplifies our code since we do not have to remember to keep separate values in sync. When we update the state, derived values are automatically recalculated in the render.
For example, we can calculate the items added to the cart and show it on the cart icon like this,
this.state.cart.items.length
and pass it as a prop to Badge Component.
We do not need to store the itemsCount
key in a cart state.
Each time the cart state gets changed,
the count on the Badge will be calculated automatically.