pwshub.com

Refreshing Server-Side Props

In a Next.js application I'm working on, I have an "admin" page, which lets me manage registered users:

A screenshot of a dashboard table, filled with users and their information (emails, purchases). Includes an “edit” button for each user as well.

One of my favourite features about Next.js is that individual routes can opt-in to server-side rendering. While I tend to be a pretty big advocate for static generation, this is a perfect use-case for server-side rendering; I can fetch and inject the database data on first render, simplifying my front-end code.

At least, that's what I thought… And then I hit a snag. 😬

Here's my problem: in my dashboard, I'm able to edit users, to bestow purchases or update their name:

Notice how the updated name isn't shown in the table? This is because the page doesn't know that the underlying data has changed. After the page is server-rendered, those props are immutable.

How do we tell Next.js to re-fetch the data, on demand, without doing a hard refresh of the whole page?

In this short tutorial, I'll share the Nifty Trick I learned to solve this problem. We'll also learn how Next.js works under-the-hood, and cover a couple related problems + solutions.

Allons-y!French for “Let's go!”

Link to this headingThe solution

Here's the solution, for busy beavers looking for a copy-paste win:

import { useRouter } from 'next/router';

function SomePage(props) {
  const router = useRouter();

  // Call this function whenever you want to refresh props!
  const refreshData = () => {
    router.replace(router.asPath);
  }
}

export async function getServerSideProps(context) {
  // Database logic here
}

The refreshData function would be called whenever you want to pull new data from the backend. It'll vary based on your usecase. As an example, here's how you'd refresh the data right after modifying a user:

async function handleSubmit() {
  const userData = /* create an object from the form */

  const res = await fetch('/api/user', {
    method: 'PUT',
    body: JSON.stringify(userData),
  });

  // Check that our status code is in the 200s,
  // meaning the request was successful.
  if (res.status < 300) {
    refreshData();
  }
}

But why does this work? Why are we involving the router in this process, and what the heck is it doing?

In fact, this solution requires a bit of context-setting. Let's talk a bit about how server-rendered routes have a secret alter-ego 🦸🏾‍♀️

Link to this headingWhy it works

When we think about getServerSideProps, we typically imagine a flow that looks like this:

  • User follows a link from Google (or wherever) to our site.

  • Next.js calls our getServerSideProps method, and uses it to generate an HTML file.

  • The user receives that HTML file, and React hydrates on the client.

(If you're not sure what I mean by “React hydrates”, I wrote a blog post about hydration!)

This is the meat-and-potatoes of Next.js server routes. This is the clerical work that Clark Kent does in his day-job.

But there's another way that Next.js uses your getServerSideProps—from the client.

Consider a different scenario:

  • User is already on your site, and they click a Next.js Link to navigate to the server-rendered page.

  • Next.js calls your getServerSideProps method on the server, but instead of generating an HTML file, it sends the data as JSON to the client.

  • React uses that data as the initial props when rendering the new page, in-browser.

One of the things that makes Next.js so cool is that the server-rendering code can also be used as a sort-of API. We can have lightning-quick client-side routing with Next because your server-rendered routes can hop into a phone booth, spin into a costume, and become an API endpoint. Server-render by day, JSON-sender by night.

Our solution works because we're performing a client-side transition to the same route. router.asPath is a reference to the current path. If we're on /admin-panel, we're telling Next to do a client-side redirect to /admin-panel, which causes it to re-fetch the data as JSON, and pass it to the current page as props. 🧨

If you're wondering about router.replace: it's like router.push, but it doesn't add an item to the history stack. We don't want this to "count" as a redirect, so that the browser's "Back" button still works as we intend.

In short: Next.js doesn't have a "refetchProps" method, but we can leverage the client-side navigation behaviour to achieve the same goal. 💯

Link to this headingLoading state

When you call this method, there will be no indication in your UI that a re-fetch is happening. This is fine in some cases, but we can't assume a fast network; even if your getServerSideProps call is Blazing Fast™, a user in the woods with 1 bar of 3G will still be stuck waiting for a hot minute.

We need a loading state! Here's how we can create one:

function SomePage({ theData }) {
  const [isRefreshing, setIsRefreshing] = React.useState(false);

  const refreshData = () => {
    router.replace(router.asPath);
    setIsRefreshing(true);
  };

  React.useEffect(() => {
    setIsRefreshing(false);
  }, [theData]);
}

We have a new React state variable, isRefreshing. We set it to true when we make the request, and it gets set back to false when the component re-renders with the new data.

Instead of firing on every single render, we put it in an effect hook, and track our theData prop (theData is a placeholder for whatever your server data looks like). When the server returns fresh data, the hook will fire, and our loading state will terminate. And it protects us against "incidental" renders, if some other bit of state happens to change while we're waiting for data.

Here's what this looks like. Keep an eye on the top-right corner to see the loading indicator:

Link to this headingMutating data

In my admin-dashboard case, I don't need to make any "special" modifications to the server-rendered data. A straightforward refresh is all I'm looking for.

But what if I wanted to change the data in some way? Maybe I want to do an "optimistic update", to show the data in its new state before the server has confirmed it?

In this case, we'd have to transfer the props into state, so that it could be mutated like any other React state:

function SomePage({ initialData }) {
  const [theData, setTheData] = React.useState(initialData);

  // Mutate whenever you want with `setTheData`!
}

Now, you may be thinking: isn't copying props into state an anti-pattern? Don't the React docs tell us not to do this exact thing?

Not exactly. The thing we want to avoid is duplicating the source of truth. If multiple components define the same bit of state, that's usually a sign of a problem.

In this case, we only have a single source of truth, and it's at the very top of our React tree. This smells like Spring Breeze fabric softener to me. A code scent, not a code smell.

It's recommended to prefix the prop with initial (eg. initialUsers instead of users), so that it's clear that the props serve as an initial value, and not a continued source of truth.

Link to this headingAlternatives

Before I discovered the router-refresh trick, my game-plan looked something like this:

  1. Pull the database calls out of getServerSideProps and into a function, getUsers. Call that function in getServerSideProps.

  2. In the page component, use a library like SWR(opens in new tab) to track the data. It'll be initialized from the server-side props, but connect to the new API route for subsequent data-fetches.

  3. Use SWR to mutate the data as needed.

This path felt terribly overengineered for me, in my specific situation; I didn't want to have two separate mechanisms for fetching users! And while SWR is a great library—I'm using it in my app to manage authentication—it feels a bit heavy in this situation.

But, in other situations, I think that this would be the right approach. For example, if you have complex data-fetching or data-mutating requirements, or if you already have an API you could interact with directly on the client.

Link to this headingWrapping up

This is my very-first tutorial on Next.js! 🍾

I've been using Next.js to build a custom platform for my upcoming course, CSS for JavaScript Developers(opens in new tab). It's a course specifically for JS developers who struggle with CSS. I know from experience that CSS becomes a lot more fun once you get comfortable with it, and being able to switch effortlessly between JS and CSS is absolutely wonderful. Follow the link to learn more!

Special thanks to Brandon(opens in new tab) for the router-based solution! Brandon's working on Blitz.js, an exciting framework built on top of Next.js that aims to recreate the Rails experience, and I'm super excited to see where it goes 💯

Last updated on

September 13th, 2024

# of hits

Source: joshwcomeau.com

Related stories
1 month ago - Today we're releasing Authorization for Realtime's Broadcast and Presence. For context, Supabase includes three useful extensions for building...
1 week ago - Web caches play an important role in speeding up our browsing experience. They save copies of web pages and other resources so that users can access them faster. But what happens when these caches become a tool for hackers? Let’s look at...
1 week ago - Building projects is a great way to practice and improve your web development skills. And that's what we'll do in this in-depth tutorial: build a practical project using HTML, CSS, and JavaScript. If you often find yourself wondering...
1 week ago - Event handling in Vue 3 allows developers to respond to user interactions like clicks, key presses, form submissions, and more. Vue provides simple and flexible ways to manage these interactions, enabling you to build dynamic and engaging...
5 days ago - As software developers, we're always learning new things; it's practically the whole gig! If we can learn to quickly pick up new languages/frameworks/tools, we'll become so much more effective at our job. It's sort of a superpower.
Other stories
2 hours ago - Ubuntu 24.10 ‘Oracular Oriole’ is released on October 13th, and as you’d expect from a new version of Ubuntu, it’s packed with new features. As a short-term release, Ubuntu 24.10 gets 9 months of ongoing updates, security patches, and...
4 hours ago - Did you know that CSS can play a significant role in web accessibility? While CSS primarily handles the visual presentation of a webpage, when you use it properly it can enhance the user’s experience and improve accessibility. In this...
5 hours ago - Design thinking workshops are your key to turning big problems into clear solutions. In this blog, I share how to run them efficiently and keep your team aligned. The post How to run a design thinking workshop appeared first on LogRocket...
5 hours ago - New memory-optimized X8g instances offer up to 3 TiB DDR5 memory, 192 vCPUs, and 50 Gbps network bandwidth, designed for memory-intensive workloads like databases, analytics, and caching with unparalleled price/performance and efficiency.
5 hours ago - Gain indispensable data engineering expertise through a hands-on specialization by DeepLearning.AI and AWS. This professional certificate covers ingestion, storage, querying, modeling, and more.