In this article, we will learn how to run an effect conditionally in both class components and functional components.

In the previous article, we had a look at our first example of using the effect hook and we learned one important point.

The useEffect hook is called after every single render. In some cases applying the effect after every render might create a performance problem. So we need a way to conditionally run an effect from a functional component.


Let's take a look at an example to understand how to implement that?

Again, for the benefit of the viewers/readers with a knowledge of class components, I will first quickly walk through the class component implementation and then we can proceed with the effect hook in functional components.

Conditionally run effect in Class Component

For this example, we will continue with the code from the previous article, so we have a class component with a state variable count initialised to 0, in the render function we have a button and on click of that button we increment the count value by 1. When the value increments the state changes which causes the component to re-render and componentDidUpdate will execute setting the document title to the updated counter value.

Now, I'm going to add a text input to this class component which will accept a name from the user.

1. Create a state variable called name initialised to an empty string

import React, { Component } from 'react'

class ClassCounterOne extends Component {
constructor(props) {
super(props)
this.state = {
count: 0,
name: ''
}
}

componentDidMount() {
document.title = `Clicked ${this.state.count} times`
}

componentDidUpdate(prevProps, prevState) {
document.title = `Clicked ${this.state.count} times`
}

render() {
return (
<div>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click {this.state.count} times
</button>
</div>
)
}
}

export default ClassCounterOne

Next, in the JSX add an input element whose value is going to be equal to this.state.name and we need to capture the input element value, so let's add an onChange handler.

2. Add Input Element and onChange Handler in JSX

import React, { Component } from 'react'

class ClassCounterOne extends Component {
constructor(props) {
super(props)
this.state = {
count: 0,
name: ''
}
}

componentDidMount() {
document.title = `Clicked ${this.state.count} times`
}

componentDidUpdate(prevProps, prevState) {
document.title = `Clicked ${this.state.count} times`
}

render() {
return (
<div>
<input type="text" value={this.state.name} onChange={e => this.setState({ name: e.target.value })} />
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click {this.state.count} times
</button>
</div>
)
}
}

export default ClassCounterOne

We basically update the state variable when the text changes.


3.Add log statement in componentDidUpdate

Finally, in componentDidUpdate, I'm going to add a log statement

componentDidUpdate(prevProps, prevState) {
console.log('Updating document title')
document.title = `Clicked ${this.state.count} times`
}

Now, let's save the file include it in App.js and head back to the browser I will also open the browser console, if I click on the button you can see that we have a log statement and the document title updates but if I start typing in the name, we still get the log statement updating document title, the count value however is still 1.

So we are basically setting the document title to the same string seven times which is sort of unnecessary.

To optimise this we can compare the count value before and after the update and if at all the count value changed we then conditionally update the title.

To achieve that, we include the parameters for componentDidUpdate previousProps and previousState,  within the body we check if previousState count is different from current state count and only update the document title.

4. Check if previousState count is different from current state

componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('Updating document title')
document.title = `Clicked ${this.state.count} times`
}
}

It is a simple if condition, now if we go back to the browser click on the button the title is updated and you can see the log statement, and start typing in a name the document title is not updated.

So we are conditionally updating the title only when the appropriate variable changes that is only when the count value changes.

Complete Code of Conditionally run in Class component

import React, { Component } from 'react'

class ClassCounterOne extends Component {
constructor(props) {
super(props)
this.state = {
count: 0,
name: ''
}
}

componentDidMount() {
document.title = `Clicked ${this.state.count} times`
}

componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('Updating document title')
document.title = `Clicked ${this.state.count} times`
}
}

render() {
return (
<div>
<input type="text" value={this.state.name} onChange={e => this.setState({ name: e.target.value })} />
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click {this.state.count} times
</button>
</div>
)
}
}

export default ClassCounterOne

well now that we know what has to be implemented let's see how to implement the same with functional components and the use effect hook

Conditionally run useEffect in functional component?

 I'm going to go back to HookCounterOne.js and begin by creating a new state variable for the name input element

import React, { useState, useEffect } from 'react'

function HookCounterOne() {
const [count, setCount] = useState(0)
const [name, setName] = useState('')
useEffect(() => {
document.title = `You clicked ${count} times`
})
return (
<div>
<input type="text" value={name} onChange={e => setName(e.target.value)} />
<button onClick={() => setCount(count + 1)}>
useEffect - Click {count} times
</button>
</div>
)
}

export default HookCounterOne

On change of the input element we are setting the value to name variable. Finally, with in the useEffect I'm going to add a log statement 

console.log('useEffect - Updating document title ')

Let's save this file go back to App.js  save file.

If you now go back to the browser click on the button and you can see that useEffect is called to update the title and when I enter my name we still are updating the document title and this is not optimal there is no necessity to update the title if it is not even changing between renders.

So the question is how do we tell react to conditionally run useEffect only when the count value changes.

In class components we added a check to compare the previous state with the current state and only update if there is a difference.

Add Second parameter to the useEffect

If this pattern is so common that the react team decided to build this into useEffect. For conditionally executing an effect, we pass in a second parameter this parameter is an array, within this array we need to specify either props or state that we need to watch for, only if those props and States specified in this array were to change the effect would be executed.

import React, { useState, useEffect } from 'react'

function HookCounterOne() {
const [count, setCount] = useState(0)
const [name, setName] = useState('')
useEffect(() => {
console.log('useEffect - Updating document title ')
document.title = `You clicked ${count} times`
}, [count])
return (
<div>
<input type="text" value={name} onChange={e => setName(e.target.value)} />
<button onClick={() => setCount(count + 1)}>
useEffect - Click {count} times
</button>
</div>
)
}

export default HookCounterOne

For our example, we need the effect to be executed only when the count value changes, so within the array we include count.

if you now save this file and go back to the browser click on the button and the effect is run, start typing in a name and you can see that the effect is not run anymore after each render.

So this is the point to take away from this second example, in order to conditionally run an effect specify the second parameter to useEffect. the second parameter is the array of values that the effect depends on.

If the values don't change between renders, the effect is simply not run. So a good optimisation technique to keep in mind.