DEV Community

Felix Owino
Felix Owino

Posted on

Understanding React States for Proper State Management.

The most interesting thing any beginner gets to learn about React is the React state. Now the question is, what is the React state? Why do we call it a state? And most importantly, why do we need it in React applications? Not to forget about how we use it in React applications. In this article, we will learn about the React state, what it is, what it does, why we need it, and how to use it in React applications.

We want to make our work in this learning exercise as easy as possible. For that reason, we will break down this topic into the following sections:

  1. Foundational understanding of React States.
  2. The difference between States and Props.
  3. Immutability in React states and why is it important to keep states immutable.

By covering the above subtopics, you will have more than what you need to start using React states in your components. Let us begin by looking at the foundations of the React state.

1. Foundational Understanding of React State.

Now if you are wondering about what React state is, this section will help you relax your mind about it. We will define what a state is with an example and discuss why React states are important.

Before thinking about states in the technical sense, consider a state as an English word. What comes into your mind when you hear the word state? The current condition of something? What is currently happening around you? Well, all those make sense as states in the real world.

In React, the state is a feature that enables React to keep track of the nature of data being rendered in each component. State enables a component to remember what it is supposed to render on every render. React provides an inbuilt hook called the usesState hook that is used to handle states.

Take for example a simple component that takes input of product quantity from the user and displays it on the screen:

Initial product quantity

The following code snippet describes the component that renders the elements shown in the screenshot above:

import { useState } from 'react'

const ProductQuantity = () => {
    const [quantity, setQuantity] = useState(0)

    return (
        <div>
            <input type="number" value={ quantity } onChange={
                (e) => setQuantity(Number(e.target.value))
            } />

            <h1> Current Quantity: { quantity }</h1>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above does the following:

i). It initializes the state called quantity with the value 0 using the useState hook

ii). The useState hook returns an array containing the following two items:

  • A variable that keeps the initial value of the state
  • A function that can be used to update the state.

In this case, the state is stored in the variable named quantity and the function for updating the quantity is called is stored in a variable called setQuantity.

ii). The component renders a number input field with a default value of 0. The text input field has a change event handler; an anonymous function that calls the updater function, setQuantity with the value of the input.

iv). Finally, the component renders the value of the quantity in a heading tag.

The value of the quantity can be changed to any number and it will be displayed instantly, for example, say you want to input a new quantity as 10000. It would look something like this:

Update quantity

Without the state variable and the state setter function, the component would not know that the value of quantity has changed and therefore it should rerender the new value. States tell a component what to render.

2. The difference between props and states.

If you are sequentially learning React, you must have used props to supply data to components. Now you might be thinking that props also tell components what to render, why do we need to learn about this new concept called states? The thing is, props tell a component about the changes that occur outside the component and states tell a component what happens to the data inside itself.

Let us adjust our ProductQuantity component to accept a prop. This time, let us supply a new value called unitPrice to the component using a prop. We want to use this value to calculate the price of the products when we update the quantity. The final rendered component with a unitPrice of 240 would look like the screenshot below:

Quantity with unit price

We can adjust our components to render the above information as follows:

import { useState } from 'react'

const ProductQuantity = ( { unitPrice }) => {
    const [quantity, setQuantity] = useState(0)

    return (
        <div>
            <input type="text" value={ quantity } onChange={
                (e) => setQuantity(Number(e.target.value))
            } />

            <h1> Current Quantity: { quantity }</h1>
            <h2>Unit Price: { unitPrice }</h2>
            <h3> Total Cost: {quantity * unitPrice }</h3>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above does the following new things:

  • The component accepts a unit price prop (unitPrice) of type number
  • The component displays the unit price in a level 2 heading tag (h2)
  • Finally, the component calculates the total cost of the quantity of products entered and displays it in a level 3 heading tag h3.

As we said earlier, props are values supplied to a component by its parent component. We can call our updated component in a parent component to supply the value of the unitPrice prop, as shown below:

    import ProductQuantity from './ProductQuantity'

    function App() {
        const unitPrice = 240

        return (
            <div>
                <ProductQuantity unitPrice={unitPrice} />
            </div>

        )
    }
Enter fullscreen mode Exit fullscreen mode

From the above code snippets, we can see that props transfer information about changes that occur in the parent component (App) while a state takes care of the changes that occur in the current component (ProductQuantity). The prop (unitPrice) is initialized and maintained in the parent component while the state (quantity) is initialized and maintained by the current component.

Now with the above setup, we can update the quantity of products and the total will be displayed instantly. For example, say we change the quantity of products to 29, the updated total will be displayed as shown below.

Updated total

3.) Immutability in React states and why is it important to keep states immutable.

What does it mean for something to be immutable? An immutable object is an object that cannot be changed. React states are technically mutable but it is recommended to treat them as if they are immutable. This means you should not modify a state during an update but you should replace it.

For example, we can create a new component called PurchasedProduct that takes, name, quantity, and unitPrice input from the user. The product renders the values of the product properties including gross total on the screen as shown below.

Inititial purchased product

The code snippet below renders the elements shown in the screenshot above:


import { useState } from 'react'

const PurchasedProduct = () => {
    const [product, setProduct] = useState({
        name: '',
        quantity: 0,
        unitPrice: 0
    })
    return (
        <div>
            <div>
                Name: <input type="text" />
                Quantity: <input type="number" />
                Unit price: <input type="number" />
            </div>
            <h1>Name: {product.name}</h1>
            <h1>Quantity: { product.quantity}</h1>
            <h1>Unit price: {product.unitPrice }</h1>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Notice that we have not handled any change events in the input fields. To learn the importance of keeping states immutable, we will break down this section into the following:

  • a) Problems associated with mutating states
  • b) How to maintain immutability while updating states
  • c) Using the spread operator to update states

a) Problems associated with mutating states.

One way to update the state of the product with the changes in input values would be to update every single property that is related to each input field. The code snippet is shown below.

import { useState } from 'react'

const PurchasedProduct = () => {
    const [product, setProduct] = useState({
        name: '',
        quantity: 0,
        unitPrice: 0
    }) 

    const updateName = (e) => { product.name = e.target.value }

    const updateQuantity = (e) =>{ product.quantity = Number(e.target.value) }

    const updateUnitPrice = (e) =>{ product.unitPrice = Number(e.target.value) }

    return (
        <div>
            <div>
                Name: <input type="text" onChange={updateName}/>
                Quantity: <input type="number" onChange={updateQuantity}/>
                Unit price: <input type="number" onChange={updateUnitPrice}/>
            </div>
            <h1>Name: {product.name}</h1>
            <h1>Quantity: { product.quantity}</h1>
            <h1>Unit price: {product.unitPrice }</h1>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Surprisingly, the code snippet above seems to be so simple and straight forward but it does not update any states. Nothing changes in the display at all when you try to change the values in the input field. The screenshot below shows all the input fields populated with input data but nothing new is rendered.

Mutated state

This is what happens when you try to change state directly like you would change the value of any variable. The state does not get updated at all and therefore no rerendering occurs.

Let us try doing it differently, this time we will use, the setProduct function to update the state.

const PurchasedProduct = () => {
    const [product, setProduct] = useState({
        name: '',
        quantity: 0,
        unitPrice: 0
    }) 

    const updateName = (e) => { setProduct({ name: e.target.value }) }

    const updateQuantity = (e) =>{ setProduct({ quantity:Number(e.target.value)} ) }

    const updateUnitPrice = (e) =>{ setProduct({ unitPrice: Number(e.target.value) }) }

    return (
        <div>
            <div>
                Name: <input type="text" onChange={updateName}/>
                Quantity: <input type="number" onChange={updateQuantity}/>
                Unit price: <input type="number" onChange={updateUnitPrice}/>
            </div>
            <h1>Name: {product.name}</h1>
            <h1>Quantity: { product.quantity}</h1>
            <h1>Unit price: {product.unitPrice }</h1>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Let's render our component with the above changes and see what happens. This time, we get something better.

The first value, name, updates as expected as shown below:

Updated name field

Let us try the second value, product quantity:

Updated quantity field

Now we have a problem here, the second value updates well but the first one disappears.

Let's see what happens with the last value, unitPrice:

Updated Unit price

Similarly, the unit price gets updated and rendered as expected but the rest of the values are not rerender as we expected.

What is happening here? According to our code, we are updating a single property of the product object at every input field. This means that we are trying to modify the product partially at every step.

The problem with this approach is that every time we update just one value, the initial object is replaced with a new object with just one property. When we update the name, we end up with a new object, {name: <new name>}. The new object does not know anything about unitPrice or quantity. Updating the next value, unitPrice does the same thing, it replaces the current object with a new object that has no name or quantity properties. Therefore, when the component is being rerendered, only one value gets rendered at a time, the most recent update.

b) How to maintain immutability when updating states.

The correct way to update the state in the PurchasedProduct component is to replace the whole product object with a new object containing all its properties. In other words, copy the object and only change the value you are interested in changing without losing the other values. The code snippet is shown below:

const PurchasedProduct = () => {
//The code that initializes the state goes here

    const updateName = (e) => { 
        setProduct(
            { 
                name: e.target.value,
                quantity: product.quantity,
                unitPrice: product.unitPrice 
            }
        ) 
    }

    const updateQuantity = (e) =>{ 
        setProduct(
            { 
                name: product.name,
                quantity:Number(e.target.value),
                unitPrice: product.unitPrice
            } 
        ) 
    }

    const updateUnitPrice = (e) =>{ 
        setProduct(
            { 
                name: product.name,
                quantity: product.quantity,
                unitPrice: Number(e.target.value) 
            }
        ) 
    }

    return (
        //JSX goes here
    )
}
Enter fullscreen mode Exit fullscreen mode

With the changes described in the above code snippet, we can now get a component that behaves just as we expect. In the screenshot below, all the input fields are filled and all the values are displayed as expected.

All inputs rerenderd

This shows that states are managed better when they are treated as immutable. Every time you are updating a state, make sure you are replacing the whole state variable and not just modifying its parts. This approach applies to array variables too. When working with arrays in states, consider replacing the whole array when adding or removing an item from the array.

c) Using the spread operator.

A Less wordy approach to achieve the same goal above is to use the spread operator. The spread operator can save you a few lines of code and errors that you may introduce while copying the object. Below is a code snippet showing how the spread operator can make your life easier.

const PurchasedProduct = () => {
//State initialization goes here

    const updateName = (e) => { 
        setProduct(
            { 
                ...product,
                name: e.target.value
            }
        ) 
    }

    const updateQuantity = (e) =>{ 
        setProduct(
            { 
                ...product,
                quantity:Number(e.target.value),
            } 
        ) 
    }

    const updateUnitPrice = (e) =>{ 
        setProduct(
            { 
                ...product,
                unitPrice: Number(e.target.value) 
            }
        ) 
    }

    return (
       // JSX goes here
    )
}
Enter fullscreen mode Exit fullscreen mode

The spread operator enables you to override one property of the product object and leave the rest as they are.

Conclusion

In conclusion, understanding the React state as the memory of a component is crucial to the success of your user interface projects. In this article, we have learned the following concepts about React state:

  • How to initialize state using the useState hook
  • How to update state using the set state function
  • The difference between states and props
  • Problems associated with mutating states
  • How to keep states immutable and why it is important to keep them immutable.
  • How to update states using the spread operator.

Visit the react documentation to learn more about how state triggers rerendering in React applications.

Top comments (0)