Mastering and Implementing State in React applications using the useState Hook

Mastering and Implementing State in React applications using the useState Hook

A beginner's guide to mastering state in React and using the useState hook to manage state in your React applications.

As a React front-end developer, you always wonder or ask yourself this question: How can I make my components more interactive and dynamic? How can I make them respond to clicks and hovers in a better way? How can I make them display data more dynamically? Well, the concept of state in React answers these questions for you!

State management is a very crucial aspect of React applications, influencing how components of your website or app behave and display data on screen. This article provides a deep exploration of state in React, with a specific focus on the useState hook. Throughout this article, you’ll learn how to use the useState hook in React to effectively manage the state of the components in your React applications, making them more interactive, and dynamic in the display of data.

Prerequisites

  • You should be comfortable programming with JavaScript and have a basic knowledge of programming with React (core concepts).

  • You should be familiar with functional components and hooks in React.

  • You should know JavaScript ES6's latest features (import/export modules, object and array destructuring, arrow functions, spread operator, etc.)

What is state in React?

Before we delve into the useState hook, let's first understand what the term state means in the context of React.

In all your React applications, the different components or user interfaces are a function of the state of your application. That is, they change the way they are displayed on the screen based on the state of your app. For example, let's say you have a button component in your app with the color blue. You desire that when the button is clicked, its color should turn black. You will need a way to track the state of that button such that when the state is unclicked, the color will be blue, and when the state changes to clicked, the button color should turn black.

This is the concept of state in React. Essentially, state can be defined as data that is tracked or monitored in an application. The data can change over time, and it is this change that influences a component's behavior and rendering. That is, when the data changes, the component behaves or displays differently on the screen, as explained in the example above. When the state of the button changes to clicked, it displays differently by changing its color to black.

Managing Component State in React: UseState Hook

The useState hook in React is a built-in feature that gives you the ability to access and track the state of a component in your application. With this hook, you will be able to initiate and manage state in your components; thus enhancing their interactivity and dynamism, as you will see in the following sections. Let's get started!

Initializing State with useState

To use the useState hook, you need to do the following two things:

  1. Import the useState library into your React application using the import keyword in JavaScript.

  2. Call the useState method in your functional component to initialize state in the component.

Below is the code to achieve this:

import {useState} from 'react'; //Importing the useState library

function Counter(){
    const [count,setCount] = useState(0); //Initializing the state

    //...
}

The useState method accepts an initial state as its parameter. In the example above, the initial state is the value 0. The common data types that you can use as the initial state are:

  • Number e.g. useState(0)

  • String e.g. useState("Jude")

  • Boolean e.g. useState(true)

  • Object e.g. useState({name: "Jude", age: 20})

  • Array e.g. useState([1,2,3]

  • Null or undefined, e.g.useState()

After accepting the initial state, the useState method returns an array of two values:

  • The initial state

  • A function that updates the state.

To access these values, you destructure the array as shown in the code sample above. The value of the initial state (0 in the example above) will be assigned to the count variable and the function to update that state will be named setCount.

Note: The variable names in the destructuring array(count and setCount) are not standard; that is, you can change them to any name you want.

Reading State with UseState

Once you initialize the state, you can include it (read its value) anywhere in your component. To do this, you simply call the variable that you assigned the initial state to wherever you want in your component using normal JSX syntax. For example:

import React,{useState} from "react";

function App() {
    const [name,setName] = useState("Jude"); 
  return( 
         <div>
            <h1>My name is {name}</h1>  
         </div>
        );
}

Below is the resulting output for the above code:

Don't worry too much about the styling; it's just some simple CSS styling I did in the background. Focus on the functionality!

Updating State with useState

When dealing with state in React, you will almost always need to update your initial state in response to user actions such as clicks, hovers, and many others. This is what enhances the interactivity and dynamism of your components, as they will re-render and display differently based on the changes in the value(s) of the initial state.

For example, consider the previous code sample above. Suppose you add a particular button in your app and you desire that when you click that button, the value of your initial state should be updated to David, and the h1 should now display "My name is David". Here is how you will achieve this with useState:

   import React,{useState} from "react";

function App() {
    const [name,setName] = useState("Jude"); //Declare initial state

    function changeName(){
        setName("David"); //Update the initial state using the setName function
}

  return( 
         <div>
            <h1>My name is {name}</h1>
            <button onClick = {changeName}>Change my name!</button>
         </div>
        );
}

Below is the resulting output you will get:

You can see from the output that once I click the button that says "Change my name!", the h1 text changes to My name is David. This means that once I click the button, the initial state, which was the string Jude, is updated to the string David. This is what enables the h1 to re-render and display the updated state.

Crazy Awesome right!!?? Let me explain how the above code sample achieved this functionality.

After declaring the initial state and destructuring the useState method, I then defined a function changeName that is called when the button is clicked. Within this function, I called the setName function which is the function to update the state, as I explained earlier in the document. I passed the value of the new state (David) as an argument to this function.

When I click the button, it triggers the changeName function. This function then triggers the setName function that updates the initial state by replacing the name variable's value, which was the initial state, with the argument that was passed to the setName function (David in this case). This then causes the h1 to be re-rendered with the updated state; hence the text "My name is David".

This is the beauty of React useState. You see that by using just a few lines of code, you can completely transform the way your components respond to user interactions and display data dynamically.

Changing Complex State in React

State in React can be more complex than just a single value. Recall, as I explained earlier in the document, that the useState method can take different data types or structures as its initial state. Suppose you have an array or object as your initial state; how will you handle updates to these different data structures? Let's see how to do that in the coming sections.

Updating Arrays with useState

Suppose you have an array of items as your initial state, like this:

function TodoItems(){
    const [items,setItems] = useState(["Buy Milk","Visit the park"]);

    //...
}

Suppose you want to update your state; let's say by adding a new item called "Go to Church" to the array. There are two ways to do this:

The first way is by passing the entire initial array alongside the new item as an argument to the function that updates the initial state(setItems in this case), like this:

 function TodoItems(){
    const [items,setItems] = useState(["Buy Milk","Visit the park"]);

    function addItem(){
        setItems(["Buy Milk","Visit the park","Go to Church"]);
}

    return( 
         <div>
            <p>To-do list item 1: {items[0]}</p>
            <p>To-do list item 2: {items[1]}</p>
            <p>To-do list item 3: {items[2]}</p>
            <button onClick = {addItem}>Add an Item</button>
         </div>
        );
}

The resulting output:

From the output, you see that once I click the "Add an Item" button, the new to-do item "Go to Church" is added to the paragraph text as per the code. This means that the initial state is updated and the new array item is added to the existing initial array. In the code, I passed as an argument to the setItems function a new array containing the existing items from the initial state array as well as the new item to be added. Thus, when the button is clicked, this function is triggered, and it then updates the initial array by overwriting it with the new array that was passed as a parameter.

The second and preferred method you can use to update both arrays and objects with useState is to use the JavaScript spread operator(...), as shown below:

function TodoItems(){
    const [items,setItems] = useState(["Buy Milk","Visit the park"]);

    function addItem(){
        setItems([...items,"Go to Church"]); //Using spread operator
}        
    //...
}

This method is simpler and preferable to the first because, when passing the new array as an argument to the setItems function, you do not have to write out the entire initial array first before adding the new item. All you need to do is just enter the spread operator(...) followed by the name of the initial array, then add the new item you want to add to the array. Essentially, the spread operator just takes the items from the initial array and adds them to the new array as individual items. Thus, a shorter and more efficient method.

Updating Objects with useState

Suppose you have as an initial state an object, like this:

function UserInfo(){
    const [user,setUser] = useState({
        fName: "Mba",
        lName: "Jude",
        email: ""
});    
    //...
}

To update this object with useState; for example, adding a value to the email property, you can again use two methods:

The first method is similar to that of arrays, whereby you pass as an argument to the setUser function the entire initial object and change the property (email in this case) you want to update. Below is the code for this:

 function UserInfo(){
    const [user,setUser] = useState({
        fName: "Mba",
        lName: "Jude",
        email: ""
});   

    function addEmail() {
    setUser({ fName: "Mba",lName: "Jude", email: "123@gmail.com" });
  }
    //...
}

When the addEmail function is called, it triggers the setUser function which updates the initial object by overwriting it with the new object passed as an argument to the setUser function.

The second method you can use to update objects with useState is again the same as with arrays; using the spread operator. Below is how you can achieve this:

function UserInfo(){
    const [user,setUser] = useState({
        fName: "Mba",
        lName: "Jude",
        email: ""
});   

    function addEmail() {
    setUser({ ...user, email: "123@gmail.com" });
  }
    //...
}

The spread operator here functions in the same way as arrays. It takes the properties of the initial object and adds them to your new object in the setUser function as individual properties. You then change just the properties you wish to update by adding them to the new object as key-value pairs, with the values being the updated values.

In your new object in the setUser function, the key name of the property you want to update should be the same as that of the initial object; that is, if you want to update the email property in our example above, you shouldn't come to your new object and change the property key name to emails before updating the value. This will NOT update the email property in the initial object; it will instead create a new property called emails and leave the initial email property unchanged. Hence, you are not performing the functionality you wanted to achieve.

Functional updates with useState

Another way of managing state in complex data structures like objects and arrays is using the functional state update. This method is recommended when you need to update a state based on its previous value; that is, when you don't want to update the entire initial state. It is identical to the spread operator method because, essentially, you are remembering some parts of the state that you don't want to update and only changing those that you wish to update.

Let's see an example to make things clearer.

Consider the object from the above example as your initial state:

function UserInfo(){
    const [user,setUser] = useState({
        fName: "Mba",
        lName: "Jude",
        email: "123@gmail.com"
    });  
    //... 
}

What if you only want to update your first name (fName property) and leave the other two properties unchanged? Here is how you would do this using the functional state update:

function App() {
  const [user, setUser] = useState({
    fName: "Jude",
    lName: "Mba",
    email: "123@gmail.com"
  });

  function updateFname() {
    setUser((prevState) => {  
      return {
        ...prevState,
        fName: "Tweezy"
      };
    });
  }
}

Let me explain how the code above works:

When the updateFname function is called, it triggers the setUser function to update the initial object. In this setUser function, instead of passing a new object to update the initial object as I did in the previous methods, I instead pass an arrow function. What this function does is that it stores the initial state; that is the initial object, in a variable called prevState. It then returns a new object and inserts all the properties of the initial object into it using the spread operator on the prevState variable. Finally, I change the property I want to update (fName in this case) by adding it as a new key-value pair in the returned object and updating its value from Jude to Tweezy. This new object that the arrow function returns overwrites the initial object and there you go!!🥳 Your object state has been updated using the functional state update method!😌

Using Multiple state Hooks

In React, it is possible to use multiple useState hooks in a single component to track different individual values. For example:

function School(){
    const [name,setName] = useState("Lourdes");
    const [location,setLocation] = useState("Bamenda");
    const [studentNo,setStudentNo] = useState(1000);
    const [country,setCountry] = useState("Cameroon");

    //...
}

However, if all the different values are related to each other, as is the case with the above example, it is highly recommended to group them into an object to allow easy management (updating and deleting) of states within the component.

Conclusion

Understanding state in React is extremely crucial for building highly interactive and dynamic user interfaces. The useState hook provides a simple and effective way to manage component states, enabling developers to create interactive web applications with ease. By following the principles and steps outlined in this guide, you'll be well-equipped to handle state in your React projects and become a better React developer.

I hope you are now highly enthused to start implementing state in your React applications and building highly interactive and dynamic components!😎. Happy coding!👨‍💻