npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

later.js

v1.1.1

Published

Setup util for universal react/redux applications

Readme

later.js 👋

A small framework to alleviate the pain in creating universal React / Redux / React-Router applications.

Motivation

Creating a universal React / Redux / React-Router application from scratch is still somewhat a pain. after.js is almost perfect, but I wanted something that doesn't abandon redux, or seem like an afterthought that gets tacked on (i.e. I don't like passing in the redux store into getInitialProps of the parent containers).

later.js aims to fill the gap (or glue together) of React/Redux/React-Router as separate libraries and the work required to have a complete universal-rendering application. It is built with razzle in mind, so some assumptions are made but it should be somewhat simple to apply to any context.

With after.js in mind, I set out to:

  • Make initial render/hydration easier
  • Provide a way to easily and sensibly plug-in a redux store to data-fetching.
  • Leave route-data-resolution to the user but make it easy to do.
  • Make code-splitting easy and loading the split-chunks even easier.
  • Make route transitions simple and don't require placeholders.

Getting Started

later.js lists all react-* / redux dependencies as peerDependencies, they must be installed first (with later.js). You can get started even faster with create-later.js-app!

npm i --save react \
  react-dom \
  react-helmet \
  react-router-dom \
  react-router-config \
  redux \
  react-redux \
  later.js

Documentation

Routing

Routing is provided by react-router v4 and react-router-config.

loadData Declarations

A user-provided resolveRoute function will be called with an array of the functions/objects that are declared alongside the route. So two instance types are supported within the loadData property:

  • Functions - Will be called with the route context. See react-router's documentation for more information on the match object.
  • Any - Anything that is not a function will be passed through as itself.

An example better explains this process. In the following route setup:

[{
  path: '/about',
  component: About,
  loadData: loadAbout,
  routes: [{
    path: '/about/me',
    component: Me,
    loadData: [loadAboutMe]
  }]
}]

If a user visited /about/me the provided resolveRoute method would be used to create a Promise thats awaited on route changes, structured as:

Promise.all([
  ...resolveRoute(store, loadAbout, { match, req, }),
  ...resolveRoute(store, [loadAboutMe], { match, req, }),
])

It is expected that the store is populated with data and it is not passed into the component.

Note - It is assumed resolveRoute will return a Promise.:

resolveRoute([store], [loadData], [ctx])

Parameters

  • store - The redux store created using createStore.
  • loadData - The matched loadData match.
  • ctx - Some request context for loadData. Including the react-router match and the request where possible.

resolveRoute is what is behind connecting the store to all of the loadData properties. And is solely responsible for populating the redux store from the calls.

asyncComponent / code-splitting

An asyncComponent is provided that makes code-splitting (and loading) super simple. When declaring a component within the routing setup, just wrap the provided component in a simple loader function using dynamic imports.

import { asyncComponent } from 'later.js';

const routes = [{
  path: '/about',
  component: asyncComponent(() => import('./asAbout')),
  routes: [{
    path: '/about/me',
    component: Me,
  }]
}]

If a user visited /about/me the server/client will load the chunk required for the ./asAbout page automatically. This automatically hooks up to the connectLink method, so that progress can be shown on some event, rather than through some global progress bar or a placeholder.

render([options])

The render function makes server-side rendering easier, by building in data-resolution and loading of asyncComponents. It will return an html document or redirect the user (using the res option).

options

  • res - Express response.
  • req - Express request.
  • routes - Routes configuration.
  • assets - Assets manifest.
  • createStore - Function that creates a redux store.
  • resolveRoute - Function that converts loadData properties to a Promise.
  • appendToHead - optional - Function that returns a react-component to append to the default Document head.
  • renderMethod - optional - Alternative render method, use this to add wrapped components
  • document - optional - Alternative Document.

A simple and shortened example:

import { render } from 'later.js';
import createStore from './createStore';
import resolveRoute from './resolveRoute';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

...
  .get('/*', async (req, res) => {
    try {
      const html = await render({
        req,
        res,
        routes,
        assets,
        createStore,
        resolveRoute,
      });
      res.send(html);
    } catch (error) {
      res.json(error);
    }
  })

hydrate([options])

The hydrate function replicates render on the client. Making it easier to populate the store/load initial data that was prefetched on the server.

options

  • routes - Routes configuration.
  • createStore - Function to create a redux store.
  • resolveRoute - Function that converts loadData properties to a Promise.
  • hydrateMethod - Optional method to override the default react hydration. Add required provider components here.

connectLink([Component], [eventHandler], [onError])

connectLink makes it easy to connect components to the later.js data-loading and asyncComponent-loading setup. Wrap a component with connectLink to load the data/components before re-routing.

If the specified eventHandler option exists on the connected component and it is a function/Promise it will call/await the provided handler. After the handler is done (and it doesn't return/resolve to false!) it will route to the to property that is provided on the Component.

The onError function is called when an error occurs. A routeError is also passed to the child if an error occurs during the fetch.

import { connectLink } from 'later.js';

const LoadingLink = ({ to, children, onClick, isRouteLoading, routeError}) => (
  <a href={to} onClick={onClick}>
    {isRouteLoading ? 'loading' : children}
    {routeError ? 'error!' : null}
  </a>
);

const ConnectedLoadingLink = connectLink(LoadingLink, 'onClick');

...

// In some render method...
<ConnectedLoadingLink to="/about" onClick={isOkToContinue}>
  About Me
</ConnectedLoadingLink>

(Un)handled Route Changes

Although connectLink makes it easy to connect to route changes, there may be cases where a link/button/event is not causing the route to change! In those cases later.js will fallback to a global route change handler.

Don't fret, asyncComponent's will still get loaded and route-data will be fetched. Want status updates on that global handler? Check out the StatusConsumer.

StatusConsumer

The <StatusConsumer/> is a react-context-api consumer that provides an isRouteLoading property for use in progress bars/indicators.

import { StatusConsumer } from 'later.js';

const ProgressIndicator = () => (
  <StatusConsumer>
    {({ isRouteLoading }) => (
      {isRouteLoading ? 'loading!' : null}
    )}
  </StatusConsumer>
);

Alternative Document

The document component used to render the page can be replaced. See the Document component provided for what is required though. Make use of react-helmet where possible instead.

What's left?

We probably shouldn't re-fetch all the data for a route on a parent -> child route transition where the location change does not cause the parent fetches to change. This would reduce a ton of needless requests for some shared state.

Inspiration