DEV Community

Cover image for Let's Talk About Hooks - Part 1 (useState and useEffect)
Atif Aiman
Atif Aiman

Posted on • Updated on

Let's Talk About Hooks - Part 1 (useState and useEffect)

Salam and holla!

Today, I will explain React, specifically the hooks. I will write the hooks in series, so you can take it slowly to understand how hooks work under the hood, and use it appropriately on your code.

Bear in mind, that these writings are of my own, and there might be a better way to use hooks, but my focus will be on understanding how each React hooks work.

And for this one, I will focus on the most basic hooks of all - useState and useEffect.

Let's start, shall we?

In this article, these are the topics covered:

  1. What Is Hook, By The Way?
  2. useState - The Pillar of States
  3. useEffect - The Lifecycle of Components
  4. The Reusability of Hooks
  5. Conclusion

What Is Hook, By The Way?

In the beningging.... Uhhh, I mean,

In the beginning, React components are created using class components. So, the methods of React components are extended from Component object, which contains several setters and getters for states and other stuff like the lifecycle of the component. This is good, but there are some concerns addressed by the React, which are:

  • The Reusability: When you write functions, and you want to use it in other components, you are required to restructure your code to adapt the scalability, especially when you deal with higher-order components.
  • Verbosity: Using methods in class components can be a hassle, especially when you want to separate different concerns in your component, but class components only allow you to only use the method from Component class such as componentDidMount, componentDidUpdate and others. With those, it is hard to read the code and find the relation between the concerns and only view it from a component lifecycle perspective only.
  • The Nature of OOP: Object-oriented Programming (OOP) is one of the coding paradigms and is also available in Javascript, however, React found that OOP becomes a barrier for beginners to learn.

You can learn more about the motivation behind hooks in React.

With these, the hooks are established, with a condition that we are moving from class components to functional components.

Here's the general difference between class components and functional components.

// Class Component

class MyComponent extends React.Component {
  constructor() {
    super();
    this.state = {
      // Your states here
    };
  }

  componentDidMount() {
    // Your lifecycle actions
  }

  render() {
    return (
      <YourJSXHere />
    );
  }
}

------

// Functional Component (Arrow Function)

const MyComponent = () => {
  const [myState, setMyState] = useState();

  useEffect(() => {
    // Your lifecycle actions
  }, []);

  return (
    <YourJSXHere />
  );
};
Enter fullscreen mode Exit fullscreen mode

Disclaimer: This article won't discuss class components vs functional components. Instead, will focus on the usage of hooks, and thus will show only functional components at this point.


useState - The Pillar of States

Well, I did write about the basics of states and props, and you can take a look at it in my article here:

And, to also understand a bit about destructuring, you can just read about destructuring below.

But, to simplify it, the state is something that changes, so that React can trigger rerender when it happens.

The hook that does the thing is useState. Let us take a look at its usage.

import { useState } from 'react'; 

const MyComp = () => {
  const [myState, setMyState] = useState();

  // ...
}
Enter fullscreen mode Exit fullscreen mode

useState is a hook to create the state for you. It returns 2 values, which are the value of the state and the state mutator. You will pass a value inside useState, which is the initial value.

Using the example above, myState is the state itself, so you can use it in your JSX. When you trigger the mutator, React will trigger the rerender, and then you can see the changes in your render.

State can be any name you want. myState is just an example, so you can use something like teacher, isToggle or anything that reflects on what changes you want to monitor.

And setMyState is the mutator function, for you to change the state. It can be any value, such as string, number, boolean, and even object. Let's see how we can use the mutator function to change the state.

const [isActive, setActive] = useState(false);

// This is function to handle click events
const handleToggle = () => {
  setActive((prevState) => {
    return !prevState;
  };
};
Enter fullscreen mode Exit fullscreen mode

Okay, it is time to slow down the clock and let's see what we have here.

setActive in this example is the mutator function for the state isActive. Assuming that the user presses the button to toggle the active state, we call the mutator function to change from false to true, and vice versa.

So, we pass a function to the setActive which returns a new value for the state. For the function, it will accept one parameter, which is the previous state, and then you can do whatever mutations you want, and finally returns the new value to the mutator function.

This is an example of string manipulation.

const [myName, setMyName] = useState('');

const updateName = (newName) => {
  setMyName(() => {
    return newName;
  }

  // Can also be shorter
  setMyName(() => newName);

  // Make it shorter!
  setMyName(newName);
};
Enter fullscreen mode Exit fullscreen mode

Noticed that I omit the prevState thing? Yeah, that is actually optional, and you can make it shorter!

So, that is how you use useState. Three things to pay attention to:

  • Initialisation: You can initialise the value by passing it to useState. So it will be useState(initialValue)
  • Your state: You can call the state later to get the value anywhere in your code, as long as it is inside the functional component.
  • The state mutator: To change state, use mutators. If you try to change state directly without using a mutator, React will just ignore your existence, and just don't pay any attention. Sad life.

useEffect - The Lifecycle of Components

Before introducing useEffect, let us revise what is the lifecycle of components inside React.

React Lifecycle Methods

Across render cycles, there are 4 main phases, which are mount, update, error handling and unmount.

The mount phase is the born of the component in the render. When the user opens a page, the mount phase will be triggered.

The update phase is when there is a change in state. Remember that I mentioned rerenders when the state changes? Yes, this is the phase that is responsible for the changes to the render.

The error handling phase is when there is some problem with the lifecycle. For example, somehow there is an error regarding your state, and later React will send a signal (or error) for logging, and will handle the render for you.

And finally, unmount is the end of your component's life, which happens when you close the page or are redirected away from the page.

In class components, there are a lot of methods available for you to use to allow granular control of the lifecycle. Functional components, however, only require one hook to manage it.

Let's get into the usage of useEffect, then!

When Should I use useEffect?

When you need to update values that are reflected in the DOM, then you need to use useEffect where it will be triggered when its dependencies have changed. Wait, what is a dependency? I will get to it soon. Let's see the anatomy of the useEffect.

useEffect(yourActionsDuringChanges, [dependencies]);
Enter fullscreen mode Exit fullscreen mode

There are two things that you should pass to useEffect, which are your function to be triggered during dependency updates, and the array of dependencies.

useEffect(() => {
  // Any functions you want during dependency updates
  // componentDidMount, componentDidUpdate, componentDidCatch

  return () => {
    // componentWillUnmount
  }
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode

As you can see above, functions that you put inside useEffect will run as soon as React detect changes to any dependency. Comparable to class methods such as componentDidMount, componentDidUpdate and componentDidCatch, but now it can be packed into one useEffect function. While the componentWillUnmount method is comparable to the function's return, it will run during the component unmounting.

Then, what is a dependency? Well, dependency is the variable that you want useEffect to listen for changes. Let's see an example then.

const [humanName, setHumanName] = useState("Atif");
const [catName, setCatName] = useState("Armel");

useEffect(() => {
  console.log(`My cat's name is ${catName}`);

  return () => {
    console.log(`${humanName} says goodbye.`);
  }
}, [catName]);
Enter fullscreen mode Exit fullscreen mode

Using the example above, there are 2 states declared, which are humanName and catName. And for useEffect, I pass a function and one dependency only, that is catName. Okay, I have questions for you.

  1. When I open the page, what happened?
  2. If I update catName to "Akamaru", what happened?
  3. If I update humanName to "Kiba", what happened?
  4. When I close the page, what happened?

Well, do you get the answer? Here's the answer.

  1. When I open the page, the component will be mounted. For the class component, this is the phase we called componentDidMount. So, the console will print out My cat's name is Armel.

  2. If I update catName to "Akamaru", useEffect will be triggered, since catName is included as a dependency for it. For the class component, this is the phase we called componentDidUpdate. So, the console will print out My cat's name is Akamaru.

  3. If I update humanName to "Kiba", useEffect won't be triggered, since humanName is not one of the dependencies of useEffect. Nothing happened.

  4. When I close the page, the component will unmount. For the class component, this is the phase we called componentWillUnmount, getting ready for a cleanup. The console will print out Kiba says goodbye. Remember that in number 3, I've updated humanName, that is why the console print out "Kiba" instead of "Atif". useEffect won't be triggered by the change of humanName, but it will still refer to the current value of the humanName.

Can we include more than one dependency? Well, sure you can! If I want to track changes to both catName and humanName, I can just add it, so useEffect will be triggered when any one of the dependency from the array of dependencies changes.

Can we add other than states as dependencies? For your information, you can also include things like props, refs and others as well, as long as the value changes. But be mindful of what you include as dependency, because in some cases, rerenders can be quite expensive.


The Reusability of Hooks

Remember when I said that hooks are reusable? Yes, hooks are indeed reusable. Notice in previous example, I used two useStates? You can declare as many states you want. Same applied to useEffect as well!

const [humanName, setHumanName] = useState('Atif');
const [catName, setCatName] = useState('Armel');

useEffect(() => {
  console.log(`I've changed my name to ${humanName}`);
}, [humanName]);

useEffect(() => {
  console.log(`I've changed my cat's name to ${catName}`);
}, [catName]);
Enter fullscreen mode Exit fullscreen mode

As you can see, there are 2 useEffect, where the first one will be listening to humanName, while the latter will be listening to catName. With this, you can seperate the concern, while dealing with the same lifecycle phases. Do as much as you want!

"Well, this is interesting. But how about I just want to run it only once, during the mount, or maybe during the page closure?"

Well, I got just the thing for you!

useEffect(() => {
  console.log("Just open the page!");
}, []);

useEffect(() => {
  return () => {
    console.log("Will close the page");
  }
}, []);
Enter fullscreen mode Exit fullscreen mode

Notice that I didn't include any dependency, but remember that useEffect will always trigger during mounting, so the console only prints out once.

For the second one, I just log the console in the return, which means that it will only happen during unmount, so the console only prints when you close the page.

So, there are three ways (actually!) to use useEffect.

// Runs once during mount
useEffect(() => {
  // Anything
}, []);

// Runs during dependency update
useEffect(() => {
  // Anything
}, [yourDependency]);

// Runs as long as there is rerenders
useEffect(() => {
  // Anything
});
Enter fullscreen mode Exit fullscreen mode

For the first and the second, I've already explained how it works, but the third one will run as long as there is rerenders.

I would recommend you to add an empty array or array of dependencies unless you know what you are doing. Rerenders are already costly, triggering useEffect every time will be expensive too.

There is another thing that you need to bear in mind.

// Don't do this
const [humanName, setHumanName] = useState('Atif');

useEffect(() => {
  setHumanName(() => 'Ahmad');
}, [humanName]);
Enter fullscreen mode Exit fullscreen mode

If you make an attempt to update your state, which happens to be included as your dependency, this will trigger and runs indefinitely, and your computer will enter a phase of "Dormamu, I've come to bargain!" thing. Nope, don't do this!

Same as the following example.

// Don't do this
const [humanName, setHumanName] = useState('Atif');
const [catName, setCatName] = useState('Armel');

useEffect(() => {
  setCatName(() => 'Akamaru');
}, [humanName]);

useEffect(() => {
  setHumanName(() => 'Kiba');
}, [catName]);
Enter fullscreen mode Exit fullscreen mode

I know, some of you might have weird ideas, but this also triggers an infinite loop! Updating each other's dependency will throw you into the unknown!


Conclusion

Well, that's it for useState and useEffect! These 2 hooks are the basic hooks which can be used in React functional components. If you understand every behaviour of these states, you can already develop your own hooks by just using useState and useEffect! You can head to useHooks to see them in action on how you can make your own hooks.

Before I close this article, there is a similar hook as useEffect we called useLayoutEffect. So what is the difference then? There are still a lot of hooks that you can discover, but it will be for other articles.

Any questions, or if you detect some error, please comment down below, and share (if you will) for more understanding of how hook works in React.

Until next time, adios, and may be peace upon ya!

Oldest comments (0)