React offers two types of components as you already know which are:
- Class component
- Functional component
Those who are familiar with class components, know it uses life cycle methods to keep track of the states and work with side effects
. React is solely responsible for UI rendering and reacting to user actions by rendering JSX code, managing states and props, managing events, and evaluating state/props change. Side effect means anything that is not react’s responsibility like(fetching data from an API, updating the DOM, setting any subscriptions or timers)
Three lifecycle methods: componentDidMount, componentDidUpdate and componentWillUnmount are extremely useful from loading the components for the first time in the DOM to unmount the components(navigating to other pages, conditional rendering of certain components of a page, etc). Here is a short description of those three life cycle methods.
- componentDidMount: This method triggers when a component loads for the first time. We may need to connect with an external API, perform certain DOM operations ( a side effect), perform asynchronous tasks i.e. setting intervals, etc.
- ComponentDidUpdate: As we have certain states in our component, often we need to update them. We update states by emitting events, input changes in the form, etc. So in these cases, we must update our states, that’s where componentDidMount lifecycle comes to play.
- componentWillUnmount: This method is necessary to prevent memory leaks and performance optimization. We mostly need this to perform certain operations like clearing timer and subscription which will not be needed when the page will unmount.
These life cycle methods are very useful and necessary, and we couldn’t think not to use them. But these methods have some disadvantages as well. Some of them:
- Duplicate code
- Sharing same logic
- Complex states
- Using the tiresome lifecycle methods repeatedly(some may disagree)
I will discuss the disadvantages by demonstrating a class component base application and later, I will use the useEffect
hook to avoid using those lifecycle methods.
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
state = {
time: new Date(),
count: 0,
show: true,
};
componentDidMount() {
document.title = `you have count ${this.state.count} times`;
this.interval = setInterval(this.tick, 1000);
}
componentDidUpdate() {
document.title = `you have count ${this.state.count} times`;
}
componentWillUnmount() {
clearInterval(this.interval);
}
tick = () => {
this.setState({ time: new Date() });
};
handleCount = () => {
this.setState(({ count }) => ({
count: count + 1,
}));
};
handleShow = () => {
this.setState(({ show }) => ({
show: !show,
}));
};
render() {
const { time, show } = this.state;
return (
<div className="container">
<div className="post">
{show && (
<div>
<p>Current time {time.toLocaleTimeString()} </p>
<button onClick={this.handleCount}>
Count start
</button>
</div>
)}
</div>
<div className="controlPost">
<button onClick={this.handleShow}>
{show ? "Hide post" : "Show post"}
</button>
</div>
</div>
);
}
}
This is a simple application that shows the clicks user performed by clicking the "count start" button in the web page title and has a clock that shows the current time. Additionally, a button "Hide post" that shows or hides "the clock "and "count start" button.
Pretty simple application right? Here are the things we need to understand,
- The clock will start ticking when the page is loaded
- At each click in the "count start" button, it will increase the count in the web page title.
- When clicking the "hide post" button it will hide the "clock" and "count start" button and display the "show post" button and clicking again, the "clock" and "count start" button will be visible again
- Finally, When we hide post, the clock shall be unmounted as it leads to memory leaks and performance issues.
So we clearly see, we have three states 1. count 2. time 3.show . In the above code snippet, I have loaded the clock by setting a time interval in the componetDidMount
method and initialized the document title, as both of them are side effects((I have mentioned earlier about the side effects ) and needed to render at the first time page loading. I have used a click event(handleCount
) to increase the value of count at each click using setState
.
Now the state is changed but that will still not be rendered to the UI unless the componentDidUpdate
lifecycle method is used. Using this method we updated the document title.
Clicking the hide post button will unmount the "clock" and "count start" button, but still, the clock will be running in the background unless we use the componentWillUnmount
method to clear the interval. Using this method we have cleared the interval
If we have a closer look at the above code snippet we may notice that we didn’t follow the DRY principle and repeated code in the life cycle methods, we also had to share same logic between the life cycle methods. Also, the states aren’t handled properly, we see a mixture of complex states and using the states in the same lifecycle method(i.e. In componentDidMount
setting interval and setting the document title) which are not desirable.
Before the introduction of useEffect
, we had to handle the side effects and different component life cycle methods using those life cycle methods. But now most of the problems that we encountered using class component is taken care of by using useEffect hook and functional components.
So let’s see how we can use the useEffect hook and git rid of those life cycle methods.
At first let’s see, what actually a useEffect hook is!
useEffect(() => {
effect
return () => {
cleanup
}
}, [dependency])
useEffect hook is actually a function which takes two parameters. 1. A callback function 2. An array of dependency(optional).The rules are:
- The callback function is the side effect that we need to perform which loads at the first rendering of the component. It is much like the
componentDidMount
life cycle method. If the second parameter is not given this callback function will render every time if any state or props change. - The second parameter is the dependency(state) which tells react to render the callback function only when the dependency changes. So when the dependency changes or updates, it will re-render. This is much like
componentDidMount
lifecycle method. It can be empty, if it is empty the callback function will render only once when the component mounts for the first time. - The callback function can also have a return value(cleanup). The return value only fires when the component will be unmount. So it will serve the
componentWillUnmount
cycle methods purpose. So in these way, we achieve the three cycle methods only using a single hook and that is useEffect hook. Now let’s do the application again but this time using functional component and useEffect hook.
import { useEffect, useState } from "react";
import "./App.css";
const App = () => {
const [count, setCount] = useState(0);
const [time, setTime] = useState(new Date());
const [show, setShow] = useState(true);
const tick = () => {
setTime(new Date());
};
useEffect(() => {
document.title = `you have clicked ${count} times`;
}, [count]);
useEffect(() => {
const interval = setInterval(tick, 1000);
return () => clearInterval(interval);
}, []);
const handleCount = () => {
setCount((count) => count + 1);
};
const handleShow = () => {
setShow((show) => !show);
};
return (
<div className="container">
<div className="post">
{show && (
<div>
<p>Current time {time.toLocaleTimeString()} </p>
<button onClick={handleCount}>Count start</button>
</div>
)}
</div>
<div className="controlPost">
<button onClick={handleShow}>
{show ? "Hide post" : "Show post"}
</button>
</div>
</div>
);
};
export default App;
This will give the same output as the previous one. Now you may notice, unlike class components we have used useEffect hook two times(we can use life cycle methods only once in a component). Also, we defined states, using three different useState hooks and used separate useEffect hooks for separate states. This is the recommended way to handle states by isolating states.
We used count
dependency in the first useEffect hook because whenever the state of count changes the function will re-render. And in the second useEffectHook, we returned a cleanup function to clear the timer when it will unmount.
Now let’s discuss how we accomplished the three life cycle methods using only one hook.
- When component first mount,
useEffect
hook sets the document title and starts the clock which serves thecomponentDidMount
purpose. - When the
count
is updated, It will again re-render the component using thecount
dependency. Again it serves thecomponentDidUpdate
method's purpose - In the second
useEffect
hook, the callback function returns a clean-up function that clears the interval of the clock at component unmount. So this time useEffect hook works like thecomponentWillUnmount
method.
The summary is useEffect hook gives us the advantage of using a single function to perform three different lifecycle methods which help us perform side effects in functional components and solves all the problems of lifecycle methods in class components.
Top comments (0)