# A Practical Use Case for React’s componentDidUpdate

Let's take a scenario where we're building a blog app. The blog will be very minimal, with these features:

1. When a user visits the homepage, they should see a list of posts and should be able to click on any of the posts to be taken to the blog post's page.
    
2. When a user visits a blog post page, they should see the blog details i.e. *title* and *content*, and a list of recommended posts at the bottom of the page.
    
3. Clicking on a recommended blog post's link should take the user to that post's page.
    

Now that we've figured out the requirements, let's proceed to build the app.

### **Building the App**

We will need 3 components for our app: the home page, the blog post component and the app's root component, which decides which page to render depending on the route. For this example, we'll fetch the posts from a local file rather than making network requests.

Let's start off with the root component, which is where we'll mount the app to a specific DOM node.

```javascript
import React from "react"
import ReactDOM from "react-dom"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom"
import Home from "./Home"
import PostDetails from "./PostDetails"
import "./index.css"

const App = () => (
  <Router>
    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/posts/:postId" component={PostDetails} />
    </Switch>
  </Router>
)

ReactDOM.render(<App />, document.getElementById("root"))
```

We're using the ​[​react-router-dom](https://www.npmjs.org/package/react-router-dom) package to render either the `Home` or `PostDetails` component depending on the route. A request with a URL such as `/posts/2` will be routed to the `PostDetails` component and the `postId` variable will take the value of 2.

Let's look at the `Home` component, which will act as our homepage:

```jsx
import React from "react"
import { Link } from "react-router-dom"
import { posts } from "./posts"
import "./Home.css"

const Home = () => {
  return (
    <div className="App">
      <header className="App-header">
        <h1 className="App-title">Blog Posts</h1>
      </header>
      <div>
        {posts.map(post => (
          <div key={post.id}>
            <Link to={`/posts/${post.id}`}>
              <h4>{post.title}</h4>
            </Link>
          </div>
        ))}
      </div>
    </div>
  )
}

export default Home
```

In this file, we fetch posts locally from `posts.js` and use the `Home` component render a list of links to each of the posts. Each post has an `id` attribute which is used to construct the URL using the [Link](https://reacttraining.com/react-router/web/api/Link) helper from `react-router-dom​`

This is how our sample ​post data looks:

```javascript
export const posts = [
  {
    id: 22,
    title: "Welcome to React",
    body: `React makes it painless to create interactive UIs.
      Design simple views for each state in your application,
      and React will efficiently update and render just the
      right components when your data changes.
      Declarative views make your code more predictable and easier to debug.`,
  },
  {
    id: 23,
    title: "An Introduction to React Router",
    body: `There are three types of components in React Router:
          router components, route matching components, and navigation components.
          All of the components that you use in a web application should be
          imported from react-router-dom.`,
  },
  {
    id: 24,
    title: "Welcome to Redux",
    body: `It helps you write applications that behave consistently,
          run in different environments (client, server, and native), and are easy to test.
          On top of that, it provides a great developer experience, such as live code editing
          combined with a time traveling debugger.`,
  },
]

export const recommendedPosts = [
  {
    id: 30,
    title: "Angular",
    body: `Learn one way to build applications with Angular and reuse your code
          and abilities to build apps for any deployment target.
          For web, mobile web, native mobile and native desktop.`,
  },
  {
    id: 31,
    title: "Ember.js",
    body: `Ember.js is built for productivity. Designed with developer ergonomics in mind,
          its friendly APIs help you get your job done—fast.`,
  },
  {
    id: 32,
    title: "Backbone.js",
    body: `Backbone.js gives structure to web applications by providing models with
          key-value binding and custom events, collections with a rich API of
          enumerable functions, views with declarative event handling, and connects
          it all to your existing API over a RESTful JSON interface.`,
  },
]

export const allPosts = posts.concat(recommendedPosts)
```

The final piece of the puzzle is the ​`​PostDetails.jsx` component:

![carbon_2018-08-11_14-20-13]( align="left")

[3](https://kevgathuku.files.wordpress.com/2018/06/carbon_2018-08-11_14-20-13.png?w=700)

[Link to PostDetails.jsx](https://gist.github.com/kevgathuku/8bd81a6e94ee7db37df284bc912aa386)

In this component, we'll store the post's details in the component state, and all the values are initially empty. To fetch the post's data, we'll need to extract the `postId` parameter from the URL. `​​​​​​​​​​​​​​react-router-dom` stores the route params in the component's `props` and it enables us to access this parameter via `this.props.match.params.postId`. Once we have the `postId`, we then find the post with a matching ID in the posts array, and set it in the component state. This is handled in the`fetchPostData` function.

In the `​​render` method, we show the post's details and a `Recommeded Posts` section. When you click on one of the posts in this section, you should be taken to that post's URL and shown that post's details.

You can try out the app in it's current state at [CodeSandbox](https://r1ywq3kkmn.codesandbox.io/) and also view the live code in the [Live Editor](https://codesandbox.io/s/r1ywq3kkmn)

However, we have a bug lurking somewhere in our code. Try clicking on one of the recommended posts and see what happens.

### **Figuring out the Problem**

If you haven't tried out the [live app](https://r1ywq3kkmn.codesandbox.io/), the issue we have is that when we're in a blog post's page, clicking on a recommended post doesn't seem to do anything. If you're keen though, you'll notice that the URL does change but the page content doesn't change 🤔

When you click on a recommended blog post link, React Router re-renders the `PostDetails` route component with a new `postId`parameter. This parameter is passed to the component as `props`. We can be sure the component does receive new props since the URL changes. But why does the component fail to display the updated data?

The root of the issue here is that we fetch data only on `componentDidMount` which is not called when a component re-renders.

What we need to do to solve the problem is to fetch new post data when we detect that the `postId` parameter has changed. This is where `componentDidUpdate` comes in.

### **Solution**

Here's a comment I came across in the react-router [issue tracker](https://github.com/ReactTraining/react-router/issues/1982#issuecomment-172173634) that let me down the right path.

> You probably want to just respond to prop changes with componentDidUpdate to refetch data when the same route is active but with different parameters
> 
> [Ryan Florence](https://github.com/ReactTraining/react-router/issues/1982#issuecomment-172173634)

Therefore, to fix the issue, we have to add `componentDidUpdate`, where we'll check if the `postId` has been updated and then fetch the details of the new post. Once we have the new details and store them in the component state, the component re-renders and shows the updated content.

Here's the small addition we need to make to fix the bug:

```jsx
componentDidUpdate(prevProps) {
    const {
      match: {
        params: { postId }
      }
    } = this.props;
    const prevPostId = prevProps.match.params.postId;
    if (prevPostId !== postId) {
      this.fetchPostData(postId);
    }
```

The `componentDidUpdate` lifecycle method takes in the previous props as the first argument. We extract the `postId` from the previous props, and compare that with the `postId` from the current props. If there's a change, we fetch new data using the current `postId` parameter. This helps us update the page content when the component re-renders with a new `postId` parameter, and this fixes our bug!

You can try out the new bug-free version on [CodeSandbox](https://x729mr1qjw.codesandbox.io/) and check out the code on the [Live Editor](https://codesandbox.io/s/x729mr1qjw). If you're interested in the accompanying code samples, you can access them on [GitHub](https://github.com/kevgathuku/react-router-quirks).

And thus ends the story of how I learned how to use `componentDidUpdate` on a real practical problem. This was an interesting discovery for me and I hope you've learnt something new too!

Cover Image: “[Those Who Affected Me](https://www.flickr.com/photos/malinbobeck/24292994416)” by [Malin Bobeck](https://www.flickr.com/photos/malinbobeck/), [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/deed.en)
