Mimic React life cycle methods with Hooks
Tags: react, javascript, hooks
Published: 2019-10-13 12:40:28

> PS - This was originally posted in my [blog](https://akhilaariyachandra.com/mimic-react-life-cycles-methods-with-hooks/). Check it out if you want learn more about React and JavaScript!

Until somewhat recently, if you wanted to use state in React you had to use a class component extending from either `React.Component` or `React.PureComponent`. The release of React 16.8 brought **hooks** which allowed state to be used in functional components.

If you wanted to do something like converting an existing class component to a functional component or fetch data in a functional component, you might be wondering how we can bring over the functionality of the life cycle methods. Three of the more popular methods, namely `componentDidMount`, `componentWillUnmount` and `componentDidUpdate`, can all be implemented with a single hook, `useEffect`.

# componentDidMount

Say we have a component like this.

```jsx
import React from "react"

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      posts: [],
    }
  }

  componentDidMount = async () => {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/posts"
    ).then(response => response.json())

    this.setState({ posts: response })
  }

  render = () => {
    return (
      <div>
        {this.state.posts.map(post => (
          <div key={post.id}>
            <h1>{post.title}</h1>

            <p>{post.body}</p>
          </div>
        ))}
      </div>
    )
  }
}

export default App
```

The example above is relatively straightforward. We fetch a list of posts once the component loads, the state with response and list them out.

If we write the same thing as a functional component.

```jsx
import React from "react"

const App = () => {
  const [posts, setPosts] = React.useState([])

  React.useEffect(async () => {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/posts"
    ).then(response => response.json())

    setPosts(response)
  }, [])

  return (
    <div>
      {posts.map(post => (
        <div key={post.id}>
          <h1>{post.title}</h1>

          <p>{post.body}</p>
        </div>
      ))}
    </div>
  )
}

export default App
```

Basically all we're doing is taking the code that was inside `componentDidMount` and running it inside an effect.

The thing to remember with useEffect if that they run after each render. The second argument in `useEffect` is used to control when to run the effect. The argument is an array of states which to run the effect after one of them is updated. To make sure the effect only runs once, an empty array is passed as the argument.

> If you don't set the 2nd argument in a `useEffect` where the state is updated, an infinite loop will occur.

While the above effect will run as is, React will show a warning saying that "An effect function must not return anything besides a function, which is used for clean-up." since our effect returns a Promise. To fix this, we'll move the data fetching code to an asynchronous function outside the effect.

```javascript
const fetchData = async () => {
  const response = await fetch(
    "https://jsonplaceholder.typicode.com/posts"
  ).then(response => response.json())

  setPosts(response)
}

React.useEffect(() => {
  fetchData()
}, [])
```

The final code will look like this.

```jsx
import React from "react"

const App = () => {
  const [posts, setPosts] = React.useState([])

  const fetchData = async () => {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/posts"
    ).then(response => response.json())

    setPosts(response)
  }

  React.useEffect(() => {
    fetchData()
  }, [])

  return (
    <div>
      {posts.map(post => (
        <div key={post.id}>
          <h1>{post.title}</h1>

          <p>{post.body}</p>
        </div>
      ))}
    </div>
  )
}

export default App
```

# componentWillUnmount

To show how we can implement `componentWillUnmount` with hooks, let's consider the following example where we create a event listener to check for the window dimensions after the component has mounted and removing the listener when the component is about to unmount.

```jsx
import React from "react"

export default class App extends React.Component {
  state = { width: 0, height: 0 }

  updateDimensions = () => {
    this.setState({ width: window.innerWidth, height: window.innerHeight })
  }

  componentDidMount = () => {
    window.addEventListener("resize", this.updateDimensions)
  }

  componentWillUnmount = () => {
    window.removeEventListener("resize", this.updateDimensions)
  }

  render = () => {
    return (
      <span>
        Window size: {this.state.width} x {this.state.height}
      </span>
    )
  }
}
```

First let's create the functional component with just the state and jsx.

```jsx
const App = () => {
  const [width, setWidth] = React.useState(0)
  const [height, setHeight] = React.useState(0)

  return (
    <span>
      Window size: {width} x {height}
    </span>
  )
}

export default App
```

Next we'll create the function used to update the states.

```javascript
const updateDimensions = () => {
  setWidth(window.innerWidth)
  setHeight(window.innerHeight)
}
```

After that we'll create an event listener using `useEffect` like we did using `componentDidMount` in the class component.

```javascript
React.useEffect(() => {
  window.addEventListener("resize", updateDimensions)
}, [])
```

Notice how we set the second argument as an empty array for `useEffect` to make sure is runs only once.

Once we set up the event listener its important that we remember to remove the listener when needed to prevent any memory leaks. In the class component this was done in `componentWillUnmount`. We can achieve the same thing in hooks with the cleanup functionality in `useEffect`. `useEffect` can return a function which will be run when it is time to cleanup when the component unmounts. So we can remove the listener here.

```javascript
React.useEffect(() => {
  window.addEventListener("resize", updateDimensions)

  return () => {
    window.removeEventListener("resize", updateDimensions)
  }
}, [])
```

The return of `useEffect` being reserved for the cleanup function is the reason why we got an error in the `componentDidMount` example initially when when we made the function inside `useEffect` `async` as it was returning a Promise.

The final code will be like this.

```jsx
import React from "react"

const App = () => {
  const [width, setWidth] = React.useState(0)
  const [height, setHeight] = React.useState(0)

  const updateDimensions = () => {
    setWidth(window.innerWidth)
    setHeight(window.innerHeight)
  }

  React.useEffect(() => {
    window.addEventListener("resize", updateDimensions)

    return () => {
      window.removeEventListener("resize", updateDimensions)
    }
  }, [])

  return (
    <span>
      Window size: {width} x {height}
    </span>
  )
}

export default App
```

# componentDidUpdate

Finally for `componentDidUpdate` let's look at this component.

```jsx
import React from "react"

export default class App extends React.Component {
  state = {
    id: 1,
    post: {},
  }

  getPost = async id => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts/${id}`
    ).then(response => response.json())

    this.setState({ post: response })
  }

  setId = id => this.setState({ id })

  componentDidMount = () => {
    this.getPost(this.state.id)
  }

  componentDidUpdate = (prevProps, prevState) => {
    if (this.state.id !== prevState.id) {
      this.getPost(this.state.id)
    }
  }

  render = () => {
    return (
      <div>
        <span>
          <button
            disabled={this.state.id === 1}
            onClick={() => this.setId(this.state.id - 1)}
          >
            -
          </button>
          {this.state.id}
          <button
            disabled={this.state.id === 100}
            onClick={() => this.setId(this.state.id + 1)}
          >
            +
          </button>
        </span>

        <h1>{`${this.state.post.id} - ${this.state.post.title}`}</h1>

        <p>{this.state.post.body}</p>
      </div>
    )
  }
}
```

In the above example we fetch a post once when the component mounts in `componentDidMount` and then every time the id gets updated in `componentDidUpdate`.

To get started on turning this into a functional component, let's first write the following code declaring the states and the returning jsx.

```jsx
import React from "react"

const App = () => {
  const [id, setId] = React.useState(1)
  const [post, setPost] = React.useState({})

  return (
    <div>
      <span>
        <button disabled={id === 1} onClick={() => setId(id - 1)}>
          -
        </button>
        {id}
        <button disabled={id === 100} onClick={() => setId(id + 1)}>
          +
        </button>
      </span>

      <h1>{`${post.id} - ${post.title}`}</h1>

      <p>{post.body}</p>
    </div>
  )
}
```

Then let's declare a function to retrieve a post.

```javascript
const getPost = async id => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${id}`
  ).then(response => response.json())

  setPost(response)
}
```

Next we need to think about retrieving the first post when the component mounts. We can do this in a `useEffect` with the second argument being an empty array.

```javascript
React.useEffect(() => {
  getPost(id)
}, [])
```

The component should load a new post when the ID is changed. The second argument in `useEffect` is a list of states for which the effect should run when once of them updates. So to run the effect again when the ID changes, we can add the ID to the array.

```javascript
React.useEffect(() => {
  getPost(id)
}, [id])
```

Finally your component should look like this.

```jsx
import React from "react"

const App = () => {
  const [id, setId] = React.useState(1)
  const [post, setPost] = React.useState({})

  const getPost = async id => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts/${id}`
    ).then(response => response.json())

    setPost(response)
  }

  React.useEffect(() => {
    getPost(id)
  }, [id])

  return (
    <div>
      <span>
        <button disabled={id === 1} onClick={() => setId(id - 1)}>
          -
        </button>
        {id}
        <button disabled={id === 100} onClick={() => setId(id + 1)}>
          +
        </button>
      </span>

      <h1>{`${post.id} - ${post.title}`}</h1>

      <p>{post.body}</p>
    </div>
  )
}

export default App
```

As you can see we can take of the both the `componentDidMount` and `componentDidUpdate` functionality in one `useEffect` which cuts down on duplicating code.`

# Wrapping up

I hope you find this post useful on how to achieve some of the functionality in class components in functional components. If you want to learn more about hooks, the React documentation has a great introduction to hooks [here](https://reactjs.org/docs/hooks-intro.html).