Tuesday, April 23, 2019

Refreshing Next.js Page Data

Something that I struggled wrapping my head around when learning Next.js was refreshing data on a given page. It seemed that the only option was to trigger the browser to reload the page, which isn't at all what I was going for. Next.js does a fantastic job of populating the page with data, whether it's rendered on the server or in the browser. The initial page data and the plumbing needed to fetch it is something I just don't need to think about.

In this post, I'll walk through my stumbling points for refreshing page data and the approach that I took to get around the issue. Let's start off by looking at a trivial page in a Next.js app:

async function getInitialProps() {
  const response = await fetch('/api/some/endpoint');
  const myFetchedData = await response.json();

  return { myFetchedData };
}

export default function MyPage(props) {
  return <strong>{props.myFetchedData}</strong>;
}

MyPage.getInitialProps = getInitialProps;

The getInitialProps() function fetches the data that our page needs. It makes a fetch() call to some imaginary API endpoint, gets the JSON response by calling response.json(), and returns an object with a myFetchedData property. The object that is returned is passed to the MyPage component as props. You can see that our component uses the props.myFetchedData value when rendering its content. Finally, the getInitialProps() function is assigned to MyPage.getInitialProps. This is how Next.js knows how to populate our page data. It will call this function on the server if this page is requested, or in the browser if this page is navigated to after the initial load.

At this point, we have a page that loads data on initial render. But what happens if the data returned by the API endpoint has changed somehow? For example, some other component in our application changes this value or creates a new resource, etc. There are any number of reasons that you might want to refresh the data that's passed to MyPage as properties. This is where I got stuck.

To illustrate the issue, let's add a refresh button to our page that simply calls the getInitialProps() function, thus calling the API again and getting updated data:

async function getInitialProps() {
  const response = await fetch('/api/some/endpoint');
  const myFetchedData = await response.json();

  return { myFetchedData };
}

export default function MyPage(props) {
  return (
    <main>
      <strong>{props.myFetchedData}</strong>
      <button onclick={getInitialProps}>Refresh</button>
    </main>
  );
}

MyPage.getInitialProps = getInitialProps;

The onClick handler of the new refresh button gets us partway there. It makes the API call, which responds with updated data, but then it doesn't do anything with it. At this point, I realized that if your Next.js page has data that needs to be refreshed, it needs to be stateful. Thankfully, you can still use the getInitialProps mechanism of Next.js to handle data fetching for the initial page load. Here's what the pattern looks like:

import { useState } from 'react';

async function fetchData() {
  const response = await fetch('/api/some/endpoint');
  const myFetchedData = await response.json();

  return { myFetchedData };
}

export default function MyPage(props) {
  const [
    myFetchedData,
    setMyFetchedData
  ] = useState(props.myFetchedData);

  async function refresh() {
    const refreshedProps = await fetchData();
    setMyFetchedData(refreshedProps.myFetchedData);
  }

  return (
    <main>
      <strong>{myFetchedData}</strong>
      <button onclick="{refresh}">Refresh</button>
    </main>
  );
}

MyPage.getInitialProps = fetchData;

The MyPage component now has myFetchedData as state. The original myFetchedData property is still passed to the component because this is how the Next.js mechanism for data fetching passes data to our components. However, the useState() hook uses the myFetchedData property value as default state value for the myFetchedValue state. This means that you don't need to worry about the myFetchedValue property - just use the myFetchedValue state instead since it has the same value.

Now that we've setup myFetchedValue to be stateful, we can implement a proper refresh function. But first, take note that we've renamed the getInitialProps() function to fetchData() because it's now more generic than fetching the initial page data. The refresh() function calls fetchData(), then calls setMyFetchedData() to update the state and re-render the page.

To summarize how state can help you with refreshing data used by your Next.js pages, just remember:

  1. The default value passed to useState() is the value that is fetched initially by Next.js.
  2. You can reuse your data fetching function any time you need fresh data. Just update the state when you have the new data.

No comments :

Post a Comment