{"id":15632732,"url":"https://github.com/ctrlplusb/react-jobs","last_synced_at":"2025-04-09T08:12:58.415Z","repository":{"id":57688530,"uuid":"77915772","full_name":"ctrlplusb/react-jobs","owner":"ctrlplusb","description":"Asynchronously resolve data for your components, with support for server side rendering.","archived":false,"fork":false,"pushed_at":"2021-08-26T07:52:24.000Z","size":551,"stargazers_count":165,"open_issues_count":12,"forks_count":34,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T06:07:08.412Z","etag":null,"topics":["component","data-fetching","react","server-side-rendering","ssr"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ctrlplusb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-03T12:35:55.000Z","updated_at":"2025-02-11T15:49:03.000Z","dependencies_parsed_at":"2022-08-25T18:20:40.503Z","dependency_job_id":null,"html_url":"https://github.com/ctrlplusb/react-jobs","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrlplusb%2Freact-jobs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrlplusb%2Freact-jobs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrlplusb%2Freact-jobs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrlplusb%2Freact-jobs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ctrlplusb","download_url":"https://codeload.github.com/ctrlplusb/react-jobs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247999864,"owners_count":21031046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["component","data-fetching","react","server-side-rendering","ssr"],"created_at":"2024-10-03T10:45:08.982Z","updated_at":"2025-04-09T08:12:58.400Z","avatar_url":"https://github.com/ctrlplusb.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-jobs 💼\n\nAsynchronously resolve data for your components, with support for server side rendering.\n\n[![npm](https://img.shields.io/npm/v/react-jobs.svg?style=flat-square)](http://npm.im/react-jobs)\n[![MIT License](https://img.shields.io/npm/l/react-jobs.svg?style=flat-square)](http://opensource.org/licenses/MIT)\n[![Travis](https://img.shields.io/travis/ctrlplusb/react-jobs.svg?style=flat-square)](https://travis-ci.org/ctrlplusb/react-jobs)\n[![Codecov](https://img.shields.io/codecov/c/github/ctrlplusb/react-jobs.svg?style=flat-square)](https://codecov.io/github/ctrlplusb/react-jobs)\n\n```js\nexport default withJob({\n  work: (props) =\u003e fetch(`/categories/${props.categoryID}`).then(r =\u003e r.json()),\n  LoadingComponent: (props) =\u003e \u003cdiv\u003eLoading...\u003c/div\u003e, // Optional\n  ErrorComponent: ({ error }) =\u003e \u003cdiv\u003e{error.message}\u003c/div\u003e, // Optional\n})(Category)\n```\n\n## TOCs\n\n  - [Introduction](#introduction)\n  - [Features](#features)\n  - [Installation](#installation)\n  - [Usage](#usage)\n  - [API](#api)\n  - [Server Side Rendering](#server-side-rendering)\n  - [FAQs](#faqs)\n  - [Changelog](https://github.com/ctrlplusb/react-jobs/releases)\n\n## Introduction\n\nThis library provides you with a generic mechanism of attaching jobs to asynchronously resolve data for your React Components.\n\n## Features\n\n - Asynchronously resolve data for your components.\n - Show a LoadingComponent whilst data is being fetched.\n - Show an ErrorComponent if data fetching fails.\n - Simple `function` and `Promise` based API which allows you to easily compose additional features such as caching or 3rd party integrations (e.g. Redux).\n - Separate data loading concerns from your components to ease testing.\n - Support for server sider rendering applications, with:\n    - data preloading on the server.\n    - \"job\" deferring (i.e. insist that job only gets resolved in the browser).\n    - rehydration API for the browser/client to prevent React checksum issues.\n    - provides interoperability with [`react-async-component`](https://github.com/ctrlplusb/react-async-component) for your code splitting needs.\n\n## Installation\n\n__npm__\n\n```bash\nnpm i react-jobs -S\n```\n\n__yarn__\n\n```bash\nyarn add react-jobs\n```\n\n## Usage\n\nIn the naive example below we will use the `fetch` API (you may need to [polyfill it](https://github.com/github/fetch) for older browsers) to retrieve data from an API endpoint.\n\n```js\nimport React from 'react'\nimport { withJob } from 'react-jobs' // 👈\nimport Product from './Product'\n\n// When the work has completed your component will be rendered\n// with a \"jobResult\" prop containing the result of the work.\n//                               👇\nfunction Products({ categoryID, jobResult }) {\n  return (\n    \u003cdiv\u003e\n      { jobResult.map(product =\u003e\n          \u003cProduct key={product.id} product={product} /\u003e\n        )}\n    \u003c/div\u003e\n  )\n}\n\n// You use the \"withJob\" function to attach work to your component.\n//             👇\nexport default withJob({\n  work: (props) =\u003e\n    fetch(`/products/category/${props.categoryID}`)\n      .then(response =\u003e response.json())\n})(Products)\n```\n\nThis component can then be used like so:\n\n```jsx\n\u003cProducts categoryID={1} /\u003e\n```\n\n## API\n\n### `withJob(config)`\n\nAttaches a \"job\" to a target Component.\n\nWhen the job has completed successfully your component will be rendered and provided a `jobResult` prop containing the result of the job.\n\n#### Arguments\n\n  - `config` (_Object_) : The configuration object for the async Component. It has the following properties available:\n    - `work` (_(props) =\u003e Promise\u003cResult\u003e|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.\n    - `LoadingComponent` (_Component_, Optional, default: `null`) : A Component that will be displayed until the `work` is complete. All props will be passed to it.\n    - `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`.\n    - `shouldWorkAgain` (_(prevProps, nextProps, jobStatus) =\u003e 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:\n      - `completed` (_Boolean_): Has the job completed execution?\n      - `data` (_Any_): The result if the job succeeded, else undefined.\n      - `error` (_Error_): The error if the job failed, else undefined.\n    - `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.\n      - __`'resolve'`__ - The `work` will be executed on the server.\n      - __`'defer'`__ - The `work` will not be executed on the server, being deferred to the browser.\n\n#### Important notes regarding behaviour\n\nThe `work` will fire under the following conditions:\n\n - 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.\n\n_OR_\n\n - Any time the `componentWillReceiveProps` fires AND `shouldWorkAgain` returns `true`.\n\n#### Returns\n\nA React component.\n\n#### Examples\n\n##### Asynchronous\n\n```js\nexport default withJob({\n  work: (props) =\u003e new Promise('/fetchSomething')\n})(YourComponent);\n```\n\n##### Synchronous\n\n```js\nexport default withJob({\n  work: (props) =\u003e 'foo'\n})(YourComponent);\n```\n\n##### Using `shouldWorkAgain`\n\n```js\nexport default withJob({\n  work: ({ productId }) =\u003e getProduct(productId),\n  shouldWorkAgain: function (prevProps, nextProps, jobStatus) {\n    // We will return true any time the productId changes\n    // This will allow our work to re-execute, and the\n    // appropriate product data can then be fetched.\n    return prevProps.productId !== nextProps.productId;\n  }\n})(YourComponent);\n```\n\n##### Naive Caching\n\n```js\nlet resultCache = null;\n\nexport default withJob({\n  work: (props) =\u003e {\n    if (resultCache) {\n      return resultCache;\n    }\n    return new Promise('/fetchSomething')\n      .then((result) =\u003e {\n        resultCache = result;\n        return result;\n      });\n  }\n})(YourComponent);\n```\n\n##### Retrying work that fails\n\nYou could use something like @sindresorhus's [`p-retry`](https://github.com/sindresorhus/p-retry) within your work.\n\n```js\nimport pRetry from 'p-retry';\n\nexport default withJob({\n  work: ({ productId }) =\u003e {\n    const run = () =\u003e fetch(`https://foo.com/products/${productId}`)\n      .then(response =\u003e {\n        // abort retrying if the resource doesn't exist\n        if (response.status === 404) {\n          throw new pRetry.AbortError(response.statusText);\n        }\n        return response.json();\n      });\n\n    return pRetry(run, {retries: 5}).then(result =\u003e {});\n  }\n})(YourComponent);\n```\n\n## Server Side Rendering\n\nThis library has been designed for interoperability with [`react-async-bootstrapper`](https://github.com/ctrlplusb/react-async-bootstrapper).\n\n`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`](https://github.com/ctrlplusb/react-async-component)).\n\nFirstly, install `react-async-bootstrapper`:\n\n```\nnpm install react-async-bootstrapper\n```\n\nNow, let's configure the \"server\" side.  You could use a similar `express` (or other HTTP server) middleware configuration:\n\n```jsx\nimport React from 'react'\nimport { JobProvider, createJobContext } from 'react-jobs' // 👈\nimport asyncBootstrapper from 'react-async-bootstrapper' // 👈\nimport { renderToString } from 'react-dom/server'\nimport serialize from 'serialize-javascript'\n\nimport MyApp from './shared/components/MyApp'\n\nexport default function expressMiddleware(req, res, next) {\n  //    Create the job context for our provider, this grants\n  // 👇 us the ability to track the resolved jobs to send back to the client.\n  const jobContext = createJobContext()\n\n  // 👇 Ensure you wrap your application with the provider.\n  const app = (\n    \u003cJobProvider jobContext={jobContext}\u003e\n      \u003cMyApp /\u003e\n    \u003c/JobProvider\u003e\n  )\n\n  // 👇 This makes sure we \"bootstrap\" resolve any jobs prior to rendering\n  asyncBootstrapper(app).then(() =\u003e {\n      // We can now render our app 👇\n      const appString = renderToString(app)\n\n      // Get the resolved jobs state. 👇\n      const jobsState = jobContext.getState()\n\n      const html = `\n        \u003chtml\u003e\n          \u003chead\u003e\n            \u003ctitle\u003eExample\u003c/title\u003e\n          \u003c/head\u003e\n          \u003cbody\u003e\n            \u003cdiv id=\"app\"\u003e${appString}\u003c/div\u003e\n            \u003cscript type=\"text/javascript\"\u003e\n              // Serialise the state into the HTML response\n              //                                 👇\n              window.JOBS_STATE = ${serialize(jobsState)}\n            \u003c/script\u003e\n          \u003c/body\u003e\n        \u003c/html\u003e`\n\n      res.send(html)\n    });\n}\n```\n\nThen on the \"client\" side you would do the following:\n\n```jsx\nimport React from 'react'\nimport { render } from 'react-dom'\nimport { JobProvider } from 'react-jobs'\n\nimport MyApp from './shared/components/MyApp'\n\n// Get any \"rehydrate\" state sent back by the server\n//                               👇\nconst rehydrateState = window.JOBS_STATE\n\n// Surround your app with the JobProvider, providing\n// the rehydrateState\n//     👇\nconst app = (\n  \u003cJobProvider rehydrateState={rehydrateState}\u003e\n    \u003cMyApp /\u003e\n  \u003c/JobProvider\u003e\n)\n\n// Render 👍\nrender(app, document.getElementById('app'))\n```\n\n## FAQs\n\n\u003e Let me know if you have any questions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctrlplusb%2Freact-jobs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fctrlplusb%2Freact-jobs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctrlplusb%2Freact-jobs/lists"}