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:
- What Is Hook, By The Way?
useState
- The Pillar of StatesuseEffect
- The Lifecycle of Components- The Reusability of Hooks
- 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 ascomponentDidMount
,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 />
);
};
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:
[ReactJS] The Basic of State and Props
Atif Aiman ・ Sep 5 '21 ・ 6 min read
And, to also understand a bit about destructuring, you can just read about destructuring below.
Javascript - Destructuring the Thing!
Atif Aiman ・ Oct 16 '21 ・ 7 min read
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();
// ...
}
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 liketeacher
,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;
};
};
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);
};
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 beuseState(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.
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]);
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]);
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]);
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.
- When I open the page, what happened?
- If I update
catName
to "Akamaru", what happened? - If I update
humanName
to "Kiba", what happened? - When I close the page, what happened?
Well, do you get the answer? Here's the answer.
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 outMy cat's name is Armel
.If I update
catName
to "Akamaru",useEffect
will be triggered, sincecatName
is included as a dependency for it. For the class component, this is the phase we calledcomponentDidUpdate
. So, the console will print outMy cat's name is Akamaru
.If I update
humanName
to "Kiba",useEffect
won't be triggered, sincehumanName
is not one of the dependencies ofuseEffect
. Nothing happened.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 outKiba says goodbye
. Remember that in number 3, I've updatedhumanName
, that is why the console print out "Kiba" instead of "Atif".useEffect
won't be triggered by the change ofhumanName
, but it will still refer to the current value of thehumanName
.
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 useState
s? 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]);
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");
}
}, []);
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
});
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]);
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]);
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!
Top comments (0)