In my last blog post I discussed how to use the React lifecycle method componentDidMount. It’s a great way to implement logic into a component when it is first mounted to the DOM. One downside of componentDidMount is that it is only available to class components. If you’re shying away from, or haven’t yet learned OOP, there is still hope. We have the React hook useEffect!

In a class component, if we wanted to pull information in from an API when the component is mounted we would do something like this.

class StarWars extends React.Component {constructor(props){
super(props)
this.state = {character: {}}
}
componentDidMount(){
fetch('https://swapi.dev/api/people/1')
.then(resp => resp.json())
.then(data => this.setState({character: data}))
}
render(){
return (
<div>
<h1>Hello World</h1>
<h1>{this.state.character.name}</h1>
</div>
)}}

Here we are triggering a fetch inside componentDidMount which will set the state of the character to the data we are fetching from the API. We will be using useEffect very similarly.

const Swapi = () => { const [character, setCharacter] = useState('') useEffect(() => {
fetch('https://swapi.dev/api/people/4')
.then(resp => resp.json())
.then(data => setCharacter(data))
}, [])
return (
<>
<h1>Hello Deathstar</h1>
<h1>{character.name}</h1>
</>
)
}

In the functional component we are using useState to set our initial state of ‘character’ to an empty string, just like we would set initial state in a class component. useEffect then takes in a function, in this case an anonymous one, which runs our fetch request. The fetch ends up setting our state of character to the object we are grabbing from the API. The second argument in useEffect is an empty array. This empty array is meant to hold dependencies or situations in which you would want that useEffect to run again. In this case we want to use it like componentDidMount, therefore we only need it to run once, when the component if first added to the DOM tree. To show you how the dependancies work take a look at a few additions to the above component.

const Swapi = () => {  const [character, setCharacter] = useState('')  const [fetchCount, setFetchCount] = useState(0)  const [count, setCount] = useState(1)  useEffect(() => {
setFetchCount(fetchCount + 1)
fetch('https://swapi.dev/api/people/4')
.then(resp => resp.json())
.then(data => setCharacter(data))
}, [count])
return (
<>
<h1>Hello Deathstar</h1>
<h1>{fetchCount}</h1>
<h1>{character.name}</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
</>
)
}

If you were to copy this code into a React app or sandbox, you would see that every time you click the button the fetchCount (second <h1>) would go up by one every time. Inside useEffect setFetchCount(count + 1) triggers every time the useEffect is run. That is because we have added ‘count’ to our dependancies array. Every time count is updated (whenever the button is clicked) everything in our useEffect runs.

One more thing I would like to get into before I go is cleanup. If you use useEffect to do something such as add event listeners to a page, you need to also unmount those listeners to avoid any memory leaks. To do this we simply need to return the cleanup inside of our useEffect.

useEffect( () => {  window.addEventListener('keydown', handleKeyEvent)  return () => {
window.removeEventListener('keydown', handleKeyEvent)
}
}, [])

The return of the useEffect is now an anonymous function that will remove the keydown event. This will replace the componentWillUnmount lifecycle method and in my opinion add clarity to your code since the event listener is being added and removed in the same place.

I hope this added some clarity to using useEffect. I highly encourage anyone reading this to check up on the official React docs to learn more about what useEffect is capable of!