• Stars
    star
    167
  • Rank 218,736 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Asynchronously resolve data for your components, with support for server side rendering.

react-jobs 💼

Asynchronously resolve data for your components, with support for server side rendering.

npm MIT License Travis Codecov

export default withJob({
  work: (props) => fetch(`/categories/${props.categoryID}`).then(r => r.json()),
  LoadingComponent: (props) => <div>Loading...</div>, // Optional
  ErrorComponent: ({ error }) => <div>{error.message}</div>, // Optional
})(Category)

TOCs

Introduction

This library provides you with a generic mechanism of attaching jobs to asynchronously resolve data for your React Components.

Features

  • Asynchronously resolve data for your components.
  • Show a LoadingComponent whilst data is being fetched.
  • Show an ErrorComponent if data fetching fails.
  • Simple function and Promise based API which allows you to easily compose additional features such as caching or 3rd party integrations (e.g. Redux).
  • Separate data loading concerns from your components to ease testing.
  • Support for server sider rendering applications, with:
    • data preloading on the server.
    • "job" deferring (i.e. insist that job only gets resolved in the browser).
    • rehydration API for the browser/client to prevent React checksum issues.
    • provides interoperability with react-async-component for your code splitting needs.

Installation

npm

npm i react-jobs -S

yarn

yarn add react-jobs

Usage

In the naive example below we will use the fetch API (you may need to polyfill it for older browsers) to retrieve data from an API endpoint.

import React from 'react'
import { withJob } from 'react-jobs' // 👈
import Product from './Product'

// When the work has completed your component will be rendered
// with a "jobResult" prop containing the result of the work.
//                               👇
function Products({ categoryID, jobResult }) {
  return (
    <div>
      { jobResult.map(product =>
          <Product key={product.id} product={product} />
        )}
    </div>
  )
}

// You use the "withJob" function to attach work to your component.
//             👇
export default withJob({
  work: (props) =>
    fetch(`/products/category/${props.categoryID}`)
      .then(response => response.json())
})(Products)

This component can then be used like so:

<Products categoryID={1} />

API

withJob(config)

Attaches a "job" to a target Component.

When the job has completed successfully your component will be rendered and provided a jobResult prop containing the result of the job.

Arguments

  • config (Object) : The configuration object for the async Component. It has the following properties available:
    • work ((props) => Promise|Result): A function containing the actual work that needs to be done for a job. It will be provided the props that are given to your component. It can return a Promise to asynchronously resolve the result of the job, or anything else in order to resolve synchronously.
    • LoadingComponent (Component, Optional, default: null) : A Component that will be displayed until the work is complete. All props will be passed to it.
    • ErrorComponent (Component, Optional, default: null) : A Component that will be displayed if any error occurred whilst trying to execute the work. All props will be passed to it as well as an error prop containing the Error.
    • shouldWorkAgain ((prevProps, nextProps, jobStatus) => boolean, Optional, default: null): A function that is executed with every componentWillReceiveProps lifecycle event. It receives the previous props, next props, and a jobStatus object. If the function returns true then the work function will be executed again, otherwise it will not. If this function is not defined, then the work will never get executed for any componentWillReceiveProps events. The jobStatus object has the following members:
      • completed (Boolean): Has the job completed execution?
      • data (Any): The result if the job succeeded, else undefined.
      • error (Error): The error if the job failed, else undefined.
    • serverMode (Boolean, Optional, default: 'resolve') : Only applies for server side rendering applications. Please see the documentation on server side rendering. The following values are allowed.
      • 'resolve' - The work will be executed on the server.
      • 'defer' - The work will not be executed on the server, being deferred to the browser.

Important notes regarding behaviour

The work will fire under the following conditions:

  • Any time componentWillMount fires. i.e. any time your component mounts. If your component is mounted and then remounted later, it will execute the work again. You may want work to only be executed once, in which case I suggest you store your work result in a cache or state management system such as redux. You can then check to see if the result exists in cache/state and resolve the existing value rather than perform a fetch for data.

OR

  • Any time the componentWillReceiveProps fires AND shouldWorkAgain returns true.

Returns

A React component.

Examples

Asynchronous
export default withJob({
  work: (props) => new Promise('/fetchSomething')
})(YourComponent);
Synchronous
export default withJob({
  work: (props) => 'foo'
})(YourComponent);
Using shouldWorkAgain
export default withJob({
  work: ({ productId }) => getProduct(productId),
  shouldWorkAgain: function (prevProps, nextProps, jobStatus) {
    // We will return true any time the productId changes
    // This will allow our work to re-execute, and the
    // appropriate product data can then be fetched.
    return prevProps.productId !== nextProps.productId;
  }
})(YourComponent);
Naive Caching
let resultCache = null;

export default withJob({
  work: (props) => {
    if (resultCache) {
      return resultCache;
    }
    return new Promise('/fetchSomething')
      .then((result) => {
        resultCache = result;
        return result;
      });
  }
})(YourComponent);
Retrying work that fails

You could use something like @sindresorhus's p-retry within your work.

import pRetry from 'p-retry';

export default withJob({
  work: ({ productId }) => {
    const run = () => fetch(`https://foo.com/products/${productId}`)
      .then(response => {
        // abort retrying if the resource doesn't exist
        if (response.status === 404) {
          throw new pRetry.AbortError(response.statusText);
        }
        return response.json();
      });

    return pRetry(run, {retries: 5}).then(result => {});
  }
})(YourComponent);

Server Side Rendering

This library has been designed for interoperability with react-async-bootstrapper.

react-async-bootstrapper allows us to do a "pre-render parse" of our React Element tree and execute an asyncBootstrap function that are attached to a components within the tree. In our case the "bootstrapping" process involves the resolution of our jobs prior to the render on the server. We use this 3rd party library as it allows interoperability with other libraries which also require a "bootstrapping" process (e.g. code splitting as supported by react-async-component).

Firstly, install react-async-bootstrapper:

npm install react-async-bootstrapper

Now, let's configure the "server" side. You could use a similar express (or other HTTP server) middleware configuration:

import React from 'react'
import { JobProvider, createJobContext } from 'react-jobs' // 👈
import asyncBootstrapper from 'react-async-bootstrapper' // 👈
import { renderToString } from 'react-dom/server'
import serialize from 'serialize-javascript'

import MyApp from './shared/components/MyApp'

export default function expressMiddleware(req, res, next) {
  //    Create the job context for our provider, this grants
  // 👇 us the ability to track the resolved jobs to send back to the client.
  const jobContext = createJobContext()

  // 👇 Ensure you wrap your application with the provider.
  const app = (
    <JobProvider jobContext={jobContext}>
      <MyApp />
    </JobProvider>
  )

  // 👇 This makes sure we "bootstrap" resolve any jobs prior to rendering
  asyncBootstrapper(app).then(() => {
      // We can now render our app 👇
      const appString = renderToString(app)

      // Get the resolved jobs state. 👇
      const jobsState = jobContext.getState()

      const html = `
        <html>
          <head>
            <title>Example</title>
          </head>
          <body>
            <div id="app">${appString}</div>
            <script type="text/javascript">
              // Serialise the state into the HTML response
              //                                 👇
              window.JOBS_STATE = ${serialize(jobsState)}
            </script>
          </body>
        </html>`

      res.send(html)
    });
}

Then on the "client" side you would do the following:

import React from 'react'
import { render } from 'react-dom'
import { JobProvider } from 'react-jobs'

import MyApp from './shared/components/MyApp'

// Get any "rehydrate" state sent back by the server
//                               👇
const rehydrateState = window.JOBS_STATE

// Surround your app with the JobProvider, providing
// the rehydrateState
//     👇
const app = (
  <JobProvider rehydrateState={rehydrateState}>
    <MyApp />
  </JobProvider>
)

// Render 👍
render(app, document.getElementById('app'))

FAQs

Let me know if you have any questions.

More Repositories

1

easy-peasy

Vegetarian friendly state for React
JavaScript
5,022
star
2

react-sizeme

Make your React Components aware of their width and height!
JavaScript
1,932
star
3

react-universally

A starter kit for universal react applications.
JavaScript
1,699
star
4

react-async-component

Resolve components asynchronously, with support for code splitting and advanced server side rendering use cases.
JavaScript
1,447
star
5

react-tree-walker

Walk a React (or Preact) element tree, executing a "visitor" function against each element.
JavaScript
346
star
6

react-component-queries

Provide props to your React Components based on their Width and/or Height.
JavaScript
183
star
7

react-async-bootstrapper

Execute a bootstrap method on your React/Preact components. Useful for data prefetching and other activities.
JavaScript
118
star
8

code-split-component

Declarative code splitting for your Wepback bundled React projects, with SSR support.
JavaScript
116
star
9

prisma-pg-jest

Example showcasing how to use Prisma + Postgres + Jest, where each test has its own unique DB context
TypeScript
106
star
10

react-virtual-container

Optimise your React apps by only rendering components when in proximity to the viewport.
JavaScript
53
star
11

cra-monorepo

Example of a now 2.0 monorepo containing Create React App and Node Lambdas
TypeScript
52
star
12

vercel-node-server

An unofficial package allowing you to create http.Server instances of your Vercel Node lambdas.
TypeScript
50
star
13

react-injectables

Explicitly inject Components into any part of your React render tree.
JavaScript
43
star
14

easy-peasy-typescript

An example of using Easy Peasy with Typescript
TypeScript
33
star
15

react-universally-skinny

A "when size matters" adaptation of the react-universally boilerplate.
JavaScript
30
star
16

lerna-cola

Superpowers for your Lerna monorepos.
JavaScript
23
star
17

gun-most

Extends gunDB with the ability to chain into most.js observables.
JavaScript
14
star
18

i-theme

A minimalist theme for vscode
12
star
19

preact-compat-hmr

Example HMR with preact, preact-compat and webpack 2.
JavaScript
12
star
20

prisma2-template

A scratchpad for Prisma2
JavaScript
12
star
21

vercel-node-dev

An unofficial development CLI for Vercel applications targeting the Node.js runtime.
TypeScript
10
star
22

npm-library-starter

A starter kit for npm libraries.
JavaScript
9
star
23

vue-zod-form

A composition based API forms helper for Vue 3 projects that utilise TypeScript.
Vue
9
star
24

vue3-typescript-strategy

An example of a sound TS experience in Vue 3 via .tsx files
TypeScript
8
star
25

cinderella

[WIP] A tiny transformation library.
JavaScript
8
star
26

demo-react-async-and-jobs

Demo on how to use react-async-component and react-jobs together in an SSR project.
JavaScript
7
star
27

react-universally-opinionated

[WIP] An opinionated version of React, Universally.
JavaScript
7
star
28

simee

Ultra simple symlinking of local npm packages.
JavaScript
7
star
29

remix-aws-edge

An adapter for running Remix on AWS CloudFront Lambda@Edge
TypeScript
6
star
30

easy-peasy-hot-reload

Easy Peasy hot reloading example
HTML
6
star
31

templatiser

Apply templates to a file tree.
JavaScript
4
star
32

react-hey-fela

Alternative React bindings for Fela
JavaScript
4
star
33

react-app-rewire-modernizr

Adds the modernizr-loader to your react-app-rewired config.
JavaScript
3
star
34

reason-advent-2017

https://adventofcode.com solved using ReasonML
OCaml
2
star
35

code-split-match

A Webpack 2 code splitting Match component for React Router v4.
2
star
36

comickult.holding

RxJS powered holding page for comickult.
JavaScript
2
star
37

flowcheatsheet

An interactive cheat sheet for Facebook's Flow type system.
JavaScript
2
star
38

lerna-cola-sample

A sample application showcasing Lerna Cola
JavaScript
2
star
39

react-app-rewire-css-blocks

Adds support for CSS Blocks to your react-app-rewired config.
JavaScript
2
star
40

react-universally-archive

JavaScript
2
star
41

now-dev-issue

JavaScript
1
star
42

reason-react-gof

[WIP] Conway's Game of Life implemented using ReasonML/Bucklescript/React.
OCaml
1
star
43

sst-cors-example

An example of CORS configuration in the context of a Serverless Stack application.
TypeScript
1
star
44

react-responsive-element

A responsive element component for React web applications.
1
star
45

monilla

A CLI to manage monorepos with predictability and stability.
TypeScript
1
star
46

approachable-fp-in-scala

An Approachable Guide to Functional Programming in Scala
1
star
47

react-router-bundlesize

Debugging react-router bundle size optimisation.
JavaScript
1
star
48

blank

Blank project for fiddling with JS whilst having all the tooling I like
JavaScript
1
star
49

splice

A malleable content management system.
1
star
50

react-fractal-grid

A fractal grid Component set for React.
1
star