Skip to content

Instantly share code, notes, and snippets.

@gleuch
Last active November 18, 2020 18:53
Show Gist options
  • Save gleuch/c79298e366ec9e7301571534d5c8e7ee to your computer and use it in GitHub Desktop.
Save gleuch/c79298e366ec9e7301571534d5c8e7ee to your computer and use it in GitHub Desktop.
Nprogress observers & NextJS route event listeners

Advanced nprogress support for NextJS route events

This code allow for smarter handling of nprogress using event observers that check progress of all observing events. This also adds ability to support error conditions when observeable callbacks fail.

Installation

Install nprogress: yarn add nprogress

Copy the nprogress.js file into lib/nprogress.js.

To add default route event observers for NextJS, add into _app.js (preferred):

import React from 'react';

// Loads nprogress & modifications and adds NextJS route observers
import 'lib/nprogress';

const MyApp = ({ Component, pageProps }) => {
  return <Component {...pageProps} />
};

export default MyApp;

Using observers

You can wrap callback functions with nprogress.observe() to have start, stop, and error progress bars displayed while performing actions, such as fetching data from an API or uploading an image.

import nprogress from 'lib/nprogress';

export async function fetchFromApi() {
  return nprogress.observe(async () => {
     const data = await axios.get('https://gleu.ch');
     return data.body;
  });
});

Observable events are handled with Promise.all. Therefore you can await multiple events with nprogress.observe(), e.g. nprogress.observe(fn1, fn2, ...).

import nprogress from 'lib/nprogress';

export async function fetchDataPoints() {
  const [dogs, cats, fish] = nprogress.observe(
    () => axios.get('http://data.tld/results/dogs.json'),
    () => axios.get('http://data.tld/results/cats.json'),
    () => axios.get('http://data.tld/results/fish.json'),
  );
  return { dogs, cats, fish };
};

Styling

A simplified version of the nprogress stylesheet can be used to set CSS vars for changing the color on regular and error events. This CSS does not support optional loading spinner.

If you wish to use the full Nprogress CSS, you will need to make sure to add a case for when error className is added to the #nprogress element.

import Router from 'next/router';
import nprogress from 'nprogress';
// Nprogress config
nprogress.configure({ minimum: 0.16, showSpinner: false });
// Track all pending observables
nprogress.observers = new Set();
// Allow race of observerable actions
// Usage: nprogress.observe(callbackFunction);
nprogress.observe = async (...args) => {
// Can be replaced with UUID or other random generator
const id = Date.now();
startProgress(id);
try {
// Run callback functions, await event results
const callbacks = args.map(fn => fn.call());
const results = await Promise.all(callbacks);
// Stop when completed
stopProgress(id);
// Return array only if more than 1 observed event
return results.length > 1 ? results : results[0];
} catch (err) {
haltProgress(err, id);
throw err;
}
};
// Display progress error, keep progress bar present for 1.5 seconds
nprogress.error = (skip = false) => {
if (typeof window !== 'undefined') {
document.getElementById('nprogress')?.classList?.add('error');
nprogress.set(0.95);
if (!skip) {
setTimeout(nprogress.done, 1500);
}
}
};
// Handler: Start progress bar
const startProgress = id => {
if (typeof window !== 'undefined') {
nprogress.observers.add(id);
nprogress.start();
}
};
// Handler: Stop progress bar
const stopProgress = id => {
if (typeof window !== 'undefined') {
nprogress.observers.delete(id);
if (nprogress.observers.size === 0) {
nprogress.done();
}
}
};
// Handler: Halt progress bar with error
const haltProgress = (err, id) => {
if (typeof window !== 'undefined') {
if (/Route Cancelled/.test(err?.message)) {
stopProgress(id);
} else {
nprogress.observers.delete(id);
nprogress.error(nprogress.observers.size);
}
}
};
// Observe NextJS route events
Router.events.on('routeChangeStart', startProgress);
Router.events.on('routeChangeComplete', stopProgress);
Router.events.on('routeChangeError', haltProgress);
export default nprogress;
// SCSS format
#nprogress {
// Default bar color
--nprogress-color: #0EBDFF;
pointer-events: none;
&.error {
// Error bar color
--nprogress-color: #FF5733;
}
.bar {
position: fixed;
z-index: 10;
top: 0;
left: 0;
width: 100%;
height: 0.4rem;
background: var(--nprogress-color);
}
/* Fancy blur effect */
.peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px var(--nprogress-color), 0 0 5px var(--nprogress-color);
opacity: 1;
transform: rotate(3deg) translate(0px, -4px);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment