How to reload Next.js page initial props without reloading the whole page

Viewed 7653

I have a Next.js page that fetches data in the getInitialProps function and I want to reload this data from the browser without reloading the page, which would be unperformant and lose the user's scroll position. Is there some way to reload the initial props without reloading the entire page?

2 Answers

This can be done by calling router.replace:

import { useRouter } from 'next/router';

function YourPage({ someProps }) {
  const router = useRouter();

  // Call this function when you want to refresh the data
  const refreshData = () => router.replace(router.asPath);

  // Your JSX
}

router.replace is a method to update the URL without adding a new entry to the history. router.asPath is the current URL. So it's like doing a client-side redirect to the same page, and client-side redirects re-fetch the props.

The answer above won't work, I don't believe, since functions can't be serialized and sent from server to client.

You could use a higher order component that stores the last initial props as state and can reload those by calling getInitialProps and setting the state to its return value. Here's a HOC that would do that:

import { NextPage, NextPageContext } from 'next';
import React, { useState, PropsWithChildren, ComponentType } from 'react';

/**
 * Removes never-used context values to reduce bloat. Context values may come from server but then
 * be used client-side because they are saved in initial props.
 */
function minifyContext(context) {
  return { ...context, req: undefined, res: undefined };
}

const withSoftReload = Page => {
  async function getInitialProps(ctx) {
    return { context: minifyContext(ctx), ...(await Page.getInitialProps(ctx)) };
  }
  const omitContextFromProps = ({
    context,
    ...props
  }) => props;
  const NewPage = props => {
    // set inner page initial props to wrapper initial props minus context
    const [initialProps, setInitialProps] = useState(omitContextFromProps(props));
    async function softReload() {
      setInitialProps({ children: null, ...(await Page.getInitialProps(props.context)) });
    }
    return (
      <Page
        {...{ ...initialProps, softReload }}
      />
    );
  };
  NewPage.getInitialProps = getInitialProps;
  NewPage.displayName = `withSoftReload(${Page.displayName})`;
  return NewPage;
};

export default withSoftReload;

In your pages you would use it like this:

const MyPage = ({ data, softReload }) => (
  <div>
    {data}
    <button onClick={softReload}>Refresh</button>
  </div>
);

MyPage.getInitialProps = async (ctx) => {
  // fetch data
};

export default withSoftReload(MyPage);
Related