Understanding  the REACT useState and useEffect HOOKS

Understanding the REACT useState and useEffect HOOKS

The two most common React hooks.

The inspiration

When I started learn learning React, at first I had some troubles in getting to grasps how these hooks work until I finally grasped it after using it to build some applications and studying documentation. The React useState and useEffect hooks are very important hooks. TheuseState hook is the most used React hook. By the end of this article you will understand how the useState and useEffect hooks work and how to use them in React, buckle up and lets dive in 😎🎉.

INTRODUCTION

I assume before coming to find this article you already know what React is, just incase you don't, React is a free and open-source front-end JavaScript library for making/building user interfaces. It is currently being maintained by Meta.

WHAT ARE HOOKS ?

  • Hooks are functions that lets you “hook into” React state and lifecycle features from function components, and make use of React features.

  • Hooks don’t work inside classes but rather they let you use React without classes.

  • Hooks give power to React functional components, making it possible to develop an entire application with it.

The Hooks Convention And Rules

Before delving into hooks, it could be helpful to take a look at the convention and rules that apply to them. Here are some of the rules that apply to hooks.

  • The naming convention of hooks should start with the prefix use. So, we can have useState, useEffect,useReducer etc. If you are using modern code editors like Atom and VSCode, the ESLint plugin could be a very useful feature for React hooks. The plugin provides useful warnings and hints on the best practices.

  • Hooks must be called at the top level of a component, before the return statement. They can’t be called inside a conditional statement, loop, or nested functions.

  • Hooks must be called from a React function (inside a React component or another hook). It shouldn’t be called from a Vanilla JS function.

Now lets get into the main reason for this article :

1) THE useState HOOK

The useState hook is the most basic and useful React hook. Like other built-in hooks, this hook must be imported from 'react' to be used in our applications.

import {useState} from 'react'

To initialize the state, we must declare both the state and its updater function and pass an initial value.

const [state, updaterFn] = useState('')

Furthermore, we are free to call our state and updater function whatever we want but by convention, the first element of the array will be our state while the second element will be the updater function. It is a common practice to prefix our updater function with the prefix set followed by the name of our state in camel case form.

For instance, let’s set a state to hold count values.

const [count, setCount] = useState(0)

Notice that the initial value of our count state is set to "0" and not an empty string. In other words, we can initialize our state to any kind of JavaScript variables, namely number, string, boolean, array and objects. There is a clear difference between setting state with the useState hook and class-based component states. It is noteworthy that the useState hook returns an array, also known as state variables and in the example above, we destructured the array into state and the updater function.

More on useState hooks and how they differ from classes.

** 🚀 What does calling useState do ?** It declares a “state variable”. Our variable is called count but we could call it anything else, like banana. This is a way to “preserve” some values between the function calls, useState is a new way to use the exact same capabilities that this.state provides in a classes. Normally, variables “disappear” when the function exits but state variables are preserved by React.

** 🚀 What do we pass to useState as an argument?** The only argument to the useState() Hook is the initial state. Unlike with classes, the state doesn’t have to be an object. We can keep a number or a string if that’s all we need. In our example, we just want a number for how many times the user clicked, so pass 0 as initial state for our variable. If we wanted to store two different values in state, we would call useState() twice.

** 🚀 What does useState return?** It returns a pair of values: the current state and a function that updates it. This is why we write const [count, setCount] = useState(). This is similar to this.state.count and this.setState in a classes, except you get them in a pair. If you’re not familiar with classes do not bother you can always go to the React's official website to read more.

Now that we know what the useState Hook does, our example below should make more sense:

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we will call "count"

  const [count, setCount] = useState(0);

We declared a state variable called count, and set it to 0. React will remember its current value between re-renders, and provide the most recent one to our function. If we want to update the current count, we can call setCount.

Rerendering Components : Setting state with the useState hook causes the corresponding component to rerender. However, this only happens if React detects a difference between the previous or old state and the new state.

📌 Note

You might be wondering why is useState not named createState instead?

“create” wouldn’t be quite accurate because the state is only created the first time our component is being rendered. During the next renders, useState gives us the current state. Otherwise it wouldn’t be “state” at all! There’s also a reason why Hook names always start with use Learn more.

Bonus Tip : What Do the Square Brackets '[]' Mean?

You might have noticed the square brackets when we declare a state variable what do they really mean why are we not using normal brackets ?

const [count, setCount] = useState(0);

The names on the left aren’t a part of the React API. You can name your own state variables:

  const [fruit, setFruit] = useState('banana');

This JavaScript syntax is called “array destructuring”. It means that we’re making two new variables fruit and setFruit, where fruit is set to the first value returned by useState, and setFruit is the second. It is equivalent to this code:

  var fruitStateVariable = useState('banana'); // Returns a pair
  var fruit = fruitStateVariable[0]; // First item in a pair
  var setFruit = fruitStateVariable[1]; // Second item in a pair

When we declare a state variable with useState, it returns a pair — an array with two items. The first item is the current value, and the second item is a function that lets us update it.

2) THE useEffect HOOK

  • The useEffect is another important React hook used in most projects. The useEffect provides us an opportunity to write imperative code that may have side effects on the application. Examples of such effects include logging, subscriptions, mutations, etc. It does a similar thing to the class-based component’s componentDidMount, componentWillUnmount, and componentDidUpdate lifecycle methods but that is not our concern in this article.

  • The user can decide when the useEffect will run, however, if when the effect is to be ran is not set, the side effects will run on every rendering or rerendering.

Consider the example below👇🏾:

import {useState, useEffect} from 'react'

const App = () =>{
  const [count, setCount] = useState(0)
  useEffect(() =>{
    console.log(count)
  })

  return(
    <div>
      ...
    </div>
  )
}

In the code above, we simply logged count in the useEffect. This will run after every render of the component.

Sometimes, we may want to run the hook once (on the mount) in our component. We can achieve this by providing a second parameter to useEffect hook.

import {useState, useEffect} from 'react'

const App = () =>{
  const [count, setCount] = useState(0)
  useEffect(() =>{
    setCount(count + 1)
  }, [])

  return(
    <div>
      <h1>{count}</h1>
      ...
    </div>
  )
}
  • The useEffect hook has two parameters, the first parameter is the function we want to run while the second parameter is called the dependencies array . If the second parameter(dependencies array) is not provided, the hook will run continuously, which is not what we want.

By passing an empty square bracket to the hook’s second parameter, we instruct React to run the useEffect hook only once, which is upon mount. This will display the value 1 in the h1 tag because the count will be updated once, from 0 to 1, when the component mounts.

We could also make our side effect run whenever some dependent values change. This can be done by passing these values in the list of dependencies.

For instance, we could make the useEffect to run whenever count changes as follows.

import { useState, useEffect } from "react";
const App = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(count);
  }, [count]);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};
export default App;

The useEffect above will run when either of these two conditions is met.

  • On mount — after the component is rendered or,

  • When the value of count changes.

On mount, the console.log expression will run and log count to 0. Once the count is updated, the second condition is met, so the useEffect runs again, this will continue whenever the button is clicked.

Once we provide the second argument to useEffect, it is expected that we pass all the dependencies to it. If you have ESLINT installed, it will show a lint error if any dependency is not passed to the parameter list. This could also make the side effect behave unexpectedly, especially if it depends on the parameters that are not passed.

Cleaning up the Effect

useEffect hook also allows us to clean up resources before the component unmounts. This may be necessary to prevent memory leaks and make the application more efficient. To do this, we’d return the clean-up function at the end of the hook.

useEffect(() => {
  console.log('mounted')

  return () => console.log('unmounting... clean up here')
})

The useEffect hook above will log mounted when the component is mounted. Unmounting… clean up here will be logged when the component unmounts. This can happen when the component is removed from the UI.

The clean-up process typically follows the form below.

useEffect(() => {
  //The effect we intend to make
  effect

  //We then return the clean up
  return () => the cleanup/unsubscription
})

While you may not find so many use cases for useEffect subscriptions, it is useful when dealing with subscriptions and timers. Particularly, when dealing with web sockets, you may need to unsubscribe from the network to save resources and improve performance when the component unmounts.

Fetching and refetching data with useEffect hook.

One of the commonest use cases of the useEffect hook is fetching and refetching data from an API.

To illustrate this, we’ll use fake user data I created from JSONPlaceholder to fetch data with the useEffect hook.

import { useEffect, useState } from "react";
import axios from "axios";

export default function App() {
  const [users, setUsers] = useState([]);
  const endPoint =
    "https://my-json-server.typicode.com/ifeanyidike/jsondata/users";

  useEffect(() => {
    const fetchUsers = async () => {
      const { data } = await axios.get(endPoint);
      setUsers(data);
    };
    fetchUsers();
  }, []);

  return (
    <div className="App">
      {users.map((user) => (
            <div>
              <h2>{user.name}</h2>
              <p>Occupation: {user.job}</p>
              <p>Sex: {user.sex}</p>
            </div>
          ))}
    </div>
  );
}

In the code above, we created a users state using the useStatehook. Then we fetched data from an API using Axios. This is an asynchronous process, and so we used the async/await function, we could have also used the dot then the syntax. Since we fetched a list of users, we simply mapped through it to display the data.

Notice that we passed an empty parameter to the hook. This ensures that it is called just once when the component mounts.

We can also refetch the data when some conditions change. We’ll show this in the code below.

import { useEffect, useState } from "react";
import axios from "axios";

export default function App() {
  const [userIDs, setUserIDs] = useState([]);
  const [user, setUser] = useState({});
  const [currentID, setCurrentID] = useState(1);

  const endPoint =
    "https://my-json-server.typicode.com/ifeanyidike/userdata/users";

  useEffect(() => {
    axios.get(endPoint).then(({ data }) => setUserIDs(data));
  }, []);

  useEffect(() => {
    const fetchUserIDs = async () => {
      const { data } = await axios.get(`${endPoint}/${currentID}`});
      setUser(data);
    };

    fetchUserIDs();
  }, [currentID]);

  const moveToNextUser = () => {
    setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId));
  };
  const moveToPrevUser = () => {
    setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1));
  };
  return (
    <div className="App">
        <div>
          <h2>{user.name}</h2>
          <p>Occupation: {user.job}</p>
          <p>Sex: {user.sex}</p>
        </div>

      <button onClick={moveToPrevUser}>Prev</button>
      <button onClick={moveToNextUser}>Next</button>
    </div>
  );
}

Here we created two useEffect hooks. In the first one, we used the dot then syntax to get all users from our API. This is necessary to determine the number of users.

We then created another useEffect hook to get a user based on the id. This useEffect will refetch the data whenever the id changes. To ensure this, we passed the id in the dependency list.

Next, we created functions to update the value of our id whenever the buttons are clicked. Once the value of the id changes, the useEffect will run again and refetch the data.

If we want, we can even clean up or cancel the promise-based token in Axios, we could do that with the clean-up method discussed above.

Conclusion

In other to learn any programming concept, it is best to go hands on and practice using these concepts in building real world projects this will allow you understand indepth how things really work.

Hope you have learnt allot happy coding 😎🚀🎉.