DEV Community

Cover image for State Management, React Hooks and Component Lifecycles
Kosi Iyiegbu
Kosi Iyiegbu

Posted on • Updated on

State Management, React Hooks and Component Lifecycles

State management is simply a way to communicate and share data across components. You can simply say, State is a JavaScript object that represents the part of a component that can change based on the action of a user.
In React, there are different kinds of state as well as ways for managing each of them. There are 4 kinds of React state to manage which are:

  1. Local State
  2. Global State
  3. Server State
  4. URL State

Let's cover what each of them are,

Local State: This is the state we manage in one or another component. It allows to instantiate a plain JavaScript object for a component and hold information that might affects its rendering. This is managed in React using the useState Hook. For example, local state would be needed to track values for a form component, such as form submission or can be used to show or hide a modal component.

In a component built with ES6 classes, whenever the state changes (only available through setState function), React triggers a re-render which is essential for updating the state of the application.

Global State: The global state is the data we manage across multiple components. Sometimes state we think should be local would become global.

For example, the authenticated state of a user. If a user is logged into the app, it is necessary to get and change their data throughout the application.

Server State: This is a simple concept, but can be hard to manage alongside the global and local UI state. Here, data comes from an external server which must be integrated with the UI state.

Fortunately, tools such as SWR and React Query help to make managing the server state easier.

URL State: Data exists on the URL such as query parameters and pathnames. In many cases, a lot of major parts of our application rely upon accessing URL state. Imagine building a blog without being able to fetch a post based off of its id that is located in the URL.

These States are the most which are focused on for most applications that are being built.

Managing Local State in React

The local state is probably the easiest to manage in React, this is because of the tools built into the React library used for it.

One of the tools is the useState hook, it can accept any valid data value which includes object and primitive values. It's setter function can be passed to other components as a callback function. Let's look at a quick example of using it

import React, { useState } from "react";

const StateTutorial = () => {
  const [inputValue, setInputValue] = useState("Kosi");

  let onChange = (event) => {
    const newValue = event.target.value;
    setInputValue(newValue);
  };

  return (
    <div>
      <input placeholder="enter something..." onChange={onChange} />
      {inputValue}
    </div>
  );
};

export default StateTutorial;
Enter fullscreen mode Exit fullscreen mode

So here we have the inputValue state which is a variable and we set the initial value to a string, then the setInputValue function which is it's setter function.

So basically, what the code does is whenever we write in the input, the value of the state will change to match what we are writing. We're basically grabbing the value of the input on the onChange function and changing it to match what we're typing with the setter function.

This is just a small example of how we manage states locally. There are other scenarios which we can make use of the useState hook which you can look up.

React Hooks

Hooks are new features which was introduced in 16.8 version of React. They helped to bring "state" to functional components and also help us to use other react features without writing a class component.

We've see one of the React hooks which is the useState hook, I'll talk about the useEffect and useMemo hooks.

useEffect

The useEffect hook is used to perform side effects in react components. A functional React component uses props and/or state to calculate the output. If the functional component makes calculations that don't target the output value, then these calculations are named side-effects. Some examples of side-effects are: fetching data, directly updating the DOM, etc.

The useEffect hook accepts 2 arguments which are a callback and the dependencies. The callback function contains the side-effect logic, while the dependecy is used to control when you want the side-effect to run.

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

function EffectTutorial() {
  const [data, setData] = useState("");
  const [count, setCount] = useState(0);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/comments")
      .then((response) => {
        setData(response.data[0].email);
      });
  }, []);

  return (
    <div>
      <h1>{data}</h1>
      <h1>{count}</h1>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click
      </button>
    </div>
  );
}

export default EffectTutorial;
Enter fullscreen mode Exit fullscreen mode

The code above uses Axios to fetch data from an api. If you didn't know, In ReactJS, Axios is a library that we use to get data from an eternal source by creating HTTP requests. So we grab the response from the api and display part of the data which is the email.

The count state is used to show that when the state changes, the useEffect will make the call once since we gave it an empty dependency array. We can use the useEffect for many things, this is just an example of a use for it.

useMemo

The useMemo is used to return a cached value so that it does not need to be recalculated. The whole point of using it is to improve performance and decrease latency on huge computations made throughout the application.

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

export default function MemoTutorial() {
  const [data, setData] = useState(null);
  const [toggle, setToggle] = useState(false);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/comments")
      .then((response) => {
        setData(response.data);
      });
  }, []);

  const findLongestName = (comments) => {
    if (!comments) return null;

    let longestName = "";
    for (let i = 0; i < comments.length; i++) {
      let currentName = comments[i].name;
      if (currentName.length > longestName.length) {
        longestName = currentName;
      }
    }


    return longestName;
  };

  const getLongestName = useMemo(() => findLongestName(data), [data]);

  return (
    <div className="App">
      <div> {getLongestName} </div>

      <button
        onClick={() => {
          setToggle(!toggle);
        }}
      >
        {" "}
        Toggle
      </button>
      {toggle && <h1> toggle </h1>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

So here, we have the same api call we used in the useEffect that returns a list of comments and we set the state of the data to the actual list. Then, we have a function findLongestName which calculates the longest name among the author of the comments

The useMemo hook is used here in the getLongestName variable to store(memoize) the value that the function calculates and put the data state in the dependecy array so that the calculation is only made whenever the data changes. This means even if the toggle state that is set was to be changed, the function wouldn't have to make calculation again.

There are more React hooks you can look up to know their uses.

Component Lifecycles

React web applications are a collection of independent components that run according to the interactions made with them. There are different lifecycle methods that React provides at different phases of a component's existence.

A component's lifecycle can be classified into 4 parts:

  • Initialization
  • Mounting
  • Updating
  • Unmounting

Let's see what happens at each phase

Initialization

This is where the component starts it's journey by setting up the state and it's props. This is generally done in the constructor of the component. The following code describes the process

class Clock extends React.Component { 
    constructor(props) 
    { 
        // Calling the constructor of 
        // Parent Class React.Component 
        super(props); 

        // Setting the initial state 
        this.state = { date : new Date() }; 
    } 
} 
Enter fullscreen mode Exit fullscreen mode

Mounting

This is the phase in which the React component is created and inserted into the DOM and rendered for the first time. The two methods available in this phase are:

  1. componentWillMount()
    This method as the name suggests is called just before the component is mounted on the DOM or before the render method is called. API calls or any data changes shouldn't be made using this.setstate in this method because it is called before the render method. Nothing can be done with the DOM (i.e. updating the data with API response) as it has not been mounted. Therefore, we can’t update the state with the API response.

  2. componentDidMount()
    This method is called after the component is mounted on the DOM. It is called once in a lifecycle and before it's execution the render method is called.

This is how this phase would look:

class LifeCycle extends React.Component {
  componentWillMount() {
      console.log('Component will mount!')
   }
  componentDidMount() {
      console.log('Component did mount!')
      this.getList();
   }
  getList=()=>{
   // method to make api call
  }
  render() {
      return (
         <div>
            <h3>Hello mounting methods!</h3>
         </div>
      );
   }
}
Enter fullscreen mode Exit fullscreen mode

Updating

This is the phase where the component state changes and a re-render takes place. In this phase, the states and props of a component are updated following user events such as clicking, typing, etc. The methods in this phase are:

  1. componentWillReceiveProps() This is a props exclusive function. It is invoked before a mounted component gets its props reassigned. The function is passed the new state of props which may or may not be identical to the original props. This is a use-case:
componentWillReceiveProps(newProps) 
{ 
    if (this.props !== newProps) { 
        console.log(" New Props have been assigned "); 
        // Use this.setState() to rerender the page. 
    } 
} 
Enter fullscreen mode Exit fullscreen mode
  1. shouldComponentUpdate()
    By default, every state or prop update re-renders the page but you may not always want to do this. This method lets React know whther the component will be affected by the update or not. t receives arguments like nextProps and nextState which help us decide whether to re-render by doing a comparison with the current prop value. It is invoked before rendering an already mounted component when new props or state are being received. If returned false then the subsequent steps of rendering will not be carried out.

  2. componentWillUpdate()
    This method is called before the component is re-rendered. It is invoked once before the render() function is executed after updating the State or Props.

  3. componentDidUpdate()
    Similarly, this function is called just after the component is re-rendered. This method receives arguments like prevProps and prevState.

To better understand the updating methods, it would look like this

class LifeCycle extends React.Component {
      constructor(props)
      {
        super(props);
         this.state = {
           date : new Date(),
           clickedStatus: false,
           list:[]
         };
      }
      componentWillMount() {
          console.log('Component will mount!')
       }
      componentDidMount() {
          console.log('Component did mount!')
          this.getList();
       }
      getList=()=>{
       // method to make api call
       fetch('https://api.mydomain.com')
          .then(response => response.json())
          .then(data => this.setState({ list:data }));
      }
       shouldComponentUpdate(nextProps, nextState){
         return this.state.list!==nextState.list
        }
       componentWillUpdate(nextProps, nextState) {
          console.log('Component will update!');
       }
       componentDidUpdate(prevProps, prevState) {
          console.log('Component did update!')
       }
      render() {
          return (
             <div>
                <h3>Hello Lifecycle Methods!</h3>
             </div>
          );
       }
}
Enter fullscreen mode Exit fullscreen mode

Unmounting

This is the last phase of a component's lifecycle which is when the component is unmounted from the DOM. There is only one method in this phase and that is:

  1. componentWillUnmount() This method is invoked before the unmounting of the component takes place. It denotes the end of the components lifecycle

That's all there is to the Component Lifecycles.

Hope you enjoyed the whole write-up, let me know if it helped in the comment section and also let me know if there was anything I missed.

Thanks.

Top comments (2)

Collapse
 
andrewbaisden profile image
Andrew Baisden

Great article here good work!

Collapse
 
killerkvothe117 profile image
Kosi Iyiegbu

Thanks Andrew