{"id":13481663,"url":"https://github.com/jxom/react-loads","last_synced_at":"2025-03-27T12:31:15.327Z","repository":{"id":48134561,"uuid":"118452309","full_name":"jxom/react-loads","owner":"jxom","description":"React Loads is a backend agnostic library to help with external data fetching \u0026 caching in your UI components.","archived":true,"fork":false,"pushed_at":"2023-11-10T02:51:07.000Z","size":3383,"stargazers_count":284,"open_issues_count":4,"forks_count":16,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-23T00:31:44.726Z","etag":null,"topics":["hook","loading","loads","promise","react","state"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/jxom.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,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-01-22T12:09:22.000Z","updated_at":"2025-03-07T04:16:17.000Z","dependencies_parsed_at":"2024-01-06T20:54:37.013Z","dependency_job_id":null,"html_url":"https://github.com/jxom/react-loads","commit_stats":{"total_commits":277,"total_committers":7,"mean_commits":39.57142857142857,"dds":0.03610108303249093,"last_synced_commit":"20d6a9e583891377e0d73532702664bfde1a0350"},"previous_names":[],"tags_count":111,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxom%2Freact-loads","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxom%2Freact-loads/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxom%2Freact-loads/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxom%2Freact-loads/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jxom","download_url":"https://codeload.github.com/jxom/react-loads/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245844884,"owners_count":20681795,"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":["hook","loading","loads","promise","react","state"],"created_at":"2024-07-31T17:00:53.912Z","updated_at":"2025-03-27T12:31:14.547Z","avatar_url":"https://github.com/jxom.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Components"],"sub_categories":["Misc"],"readme":"# React Loads\n\n\u003e React Loads is a backend agnostic library to help with external data fetching \u0026 caching in your UI components.\n\n## Features\n\n- **Hooks** and **Render Props** to manage your async states \u0026 response data\n- **Backend agnostic.** Use React Loads with REST, GraphQL, or Web SDKs\n- **Renderer agnostic.** Use React Loads with React DOM, React Native, React VR, etc\n- **Automated caching \u0026 revalidation** to maximise user experience between page transitions\n- **React Suspense** support\n- **SSR** support\n- **Preloading** support\n- **Polling** support to load data every x seconds\n- **Request deduping** to minimise over-fetching of your data\n- **Focus revalidation** to re-fetch your data when the browser window is focused\n- **Resources** to allow your to hoist common async functions for built-in caching \u0026 reusability\n- **Finite set of state variables** to avoid cryptic ternaries and impossible states\n- **External cache** support\n- **Optimistic responses**\n- Pretty small – **5kB gzipped**\n\n## Table of Contents\n\n- [Features](#features)\n- [Table of Contents](#table-of-contents)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Guides](#guides)\n  - [Starting out](#starting-out)\n  - [Deferring](#deferring)\n  - [Configuration](#configuration)\n  - [Variables](#variables)\n  - [Conditional loaders](#conditional-loaders)\n  - [Dependant loaders](#dependant-loaders)\n  - [Caching](#caching)\n  - [Slow connections](#slow-connections)\n  - [Polling](#polling)\n  - [Deduping](#deduping)\n  - [Suspense](#suspense)\n  - [Optimistic responses](#optimistic-responses)\n  - [Resources](#resources)\n  - [External cache providers](#external-cache-providers)\n  - [Preloading (experimental)](#preloading-experimental)\n- [API](#api)\n  - [useLoads](#useloads)\n  - [useDeferredLoads](#usedeferredloads)\n  - [useCache](#usecache)\n  - [useGetStates](#usegetstates)\n  - [\u0026lt;Provider\u0026gt;](#provider)\n  - [createResource](#createresource)\n  - [preload (experimental)](#preload-experimental-1)\n  - [Config](#config)\n- [Happy customers](#happy-customers)\n- [Acknowledgments](#acknowledgments)\n- [License](#license)\n\n## Installation\n\n```\nyarn add react-loads\n```\n\nor npm:\n\n```\nnpm install react-loads\n```\n\n## Quick start\n\n### With Hooks\n\n```jsx\nimport React from 'react';\nimport * as Loads from 'react-loads';\n\nasync function fetchRandomDog() {\n  // Dog fetcher logic here!\n  // You can use any type of backend here - REST, GraphQL, you name it!\n}\n\nexport default function RandomDog() {\n  const {\n    response,\n    error,\n    isPending,\n    isResolved,\n    isRejected\n  } = Loads.useLoads('randomDog', fetchRandomDog);\n  return (\n    \u003cdiv\u003e\n      {isPending \u0026\u0026 'Loading...'}\n      {isResolved \u0026\u0026 \u003cimg src={response.imgSrc} /\u003e}\n      {isRejected \u0026\u0026 `Oh no! ${error.message}`}\n    \u003c/div\u003e\n  )\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-basic-example-38biz)\n\nThe `useLoads` function accepts three arguments: a **context key**, an **async function**, and a **config object** (not used in this example). The **context key** will store the response of the `fetchRandomDog` function in the React Loads cache against the key. The **async function** is a function that returns a promise, and is used to fetch your data.\n\nThe `useLoads` function also returns a set of values: `response`, `error`, and a finite set of states (`isIdle`, `isPending`, `isResolved`, `isRejected`, and a few others). If your **async function** resolves, it will update the `response` \u0026 `isResolved` values. If it rejects, it will update the `error` value.\n\n\u003e IMPORTANT NOTE: You must provide useLoads with a memoized promise (via `React.useCallback` or **bounded outside of your function component as seen in the above example**), otherwise useLoads will be invoked on every render.\n\u003e\n\u003e If you are using `React.useCallback`, the [`react-hooks` ESLint Plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks) is incredibly handy to ensure your hook dependencies are set up correctly.\n\n### With Render Props\n\nIf your codebase isn't quite hook ready yet, React Loads provides a Render Props interface which shares the same API as the hook:\n\n```jsx\nimport React from 'react';\nimport { Loads } from 'react-loads';\n\nasync function fetchRandomDog() {\n  // Dog fetcher logic here!\n  // You can use any type of backend here - REST, GraphQL, you name it!\n}\n\nclass RandomDog extends React.Component {\n  render() {\n    return (\n      \u003cLoads context=\"randomDog\" fn={fetchRandomDog}\u003e\n        {({ response, error, isPending, isResolved, isRejected }) =\u003e (\n          \u003cdiv\u003e\n            {isPending \u0026\u0026 'Loading...'}\n            {isResolved \u0026\u0026 \u003cimg src={response.imgSrc} /\u003e}\n            {isRejected \u0026\u0026 `Oh no! ${error.message}`}\n          \u003c/div\u003e\n        )}\n      \u003c/Loads\u003e\n    )\n  }\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-basic-example-class-component-3vg8v)\n\n### More examples\n\n- [Basic](./examples/basic)\n- [Top movies](./examples/top-movies)\n- [Resources](./examples/with-resources)\n- [Typescript](./examples/with-typescript)\n- Render-as-you-fetch\n  - [Basic](./examples/render-as-you-fetch/basic)\n  - [Resources](./examples/render-as-you-fetch/with-resources)\n- [Stories](https://jxom.github.io/react-loads/)\n\n## Guides\n\n### Starting out\n\nThere are two main hooks: `useLoads` \u0026 `useDeferredLoads`.\n\n- `useLoads` is called on first render,\n- `useDeferredLoads` is called when you choose to invoke it (it's deferred until later).\n\nLet's focus on the `useLoads` hook for now, we will explain `useDeferredLoads` in the next section.\n\nThe `useLoads` hook accepts 3 parameters:\n\n- A [**\"context key\"**](#optionscontext) in the form of a **string**.\n  - It will help us with identifying/storing data, deduping your requests \u0026 updating other `useLoad`'s sharing the same context\n  - Think of it as the namespace for your data\n- An [**\"async function\"**](#optionsfn) in the form of a **function that returns a promise**\n  - This will be the function to resolve the data\n- An optional [**\"config\"**](#config) in the form of an **object**\n\n```jsx\nimport React from 'react';\nimport * as Loads from 'react-loads';\n\nasync function fetchRandomDog() {\n  // Dog fetcher logic here!\n  // You can use any type of backend here - REST, GraphQL, you name it!\n}\n\nexport default function RandomDog() {\n  const {\n    response,\n    error,\n    load,\n    isPending,\n    isReloading,\n    isResolved,\n    isRejected\n  } = Loads.useLoads('randomDog', fetchRandomDog);\n  return (\n    \u003cdiv\u003e\n      {isPending \u0026\u0026 'Loading...'}\n      {isResolved \u0026\u0026 (\n        \u003cdiv\u003e\n          \u003cimg src={response.imgSrc} /\u003e\n          \u003cbutton onClick={load} disabled={isReloading}\u003eLoad another\u003c/button\u003e\n        \u003c/div\u003e\n      )}\n      {isRejected \u0026\u0026 `Oh no! ${error.message}`}\n    \u003c/div\u003e\n  )\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-basic-example-38biz)\n\nThe `useLoads` hook represents a finite state machine and returns a set of state variables:\n\n- `isIdle` if the async function hasn't been invoked yet (relevant for `useDeferredLoads`)\n- `isPending` for when the async function is loading\n- `isReloading` for when the async function is reloading (typically truthy when data already exists in the cache)\n- `isResolved` for when the async function has resolved\n- `isRejected` for when the async function has errored\n\nIt also returns a `response` variable if your function resolves, and an `error` variable if rejected.\n\nIf you want to reload your data, `useLoads` also returns a `load` variable, which you can invoke.\n\nThe `useLoads` hook returns [some other variables](#returns) as well.\n\n### Deferring\n\nSometimes you don't want your async function to be invoked straight away. This is where the `useDeferredLoads` hook can be handy. It waits until you manually invoke it.\n\n```jsx\nimport React from 'react';\nimport * as Loads from 'react-loads';\n\nasync function fetchRandomDog() {\n  // Dog fetcher logic here!\n  // You can use any type of backend here - REST, GraphQL, you name it!\n}\n\nexport default function RandomDog() {\n  const {\n    response,\n    error,\n    load,\n    isIdle,\n    isPending,\n    isReloading,\n    isResolved,\n    isRejected\n  } = Loads.useDeferredLoads('randomDog', fetchRandomDog);\n  return (\n    \u003cdiv\u003e\n      {isIdle \u0026\u0026 \u003cbutton onClick={load}\u003eLoad a dog\u003c/button\u003e}\n      {isPending \u0026\u0026 'Loading...'}\n      {isResolved \u0026\u0026 (\n        \u003cdiv\u003e\n          \u003cimg src={response.imgSrc} /\u003e\n          \u003cbutton onClick={load} disabled={isReloading}\u003eLoad another\u003c/button\u003e\n        \u003c/div\u003e\n      )}\n      {isRejected \u0026\u0026 `Oh no! ${error.message}`}\n    \u003c/div\u003e\n  )\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-basic-example-usedeferredloads-ev3vi)\n\nIn the above example, the dog image is fetched via the `load` variable returned from `useDeferredLoads`.\n\nThere are also some cases where including a **context key** may not make sense. You can omit it if you want like so:\n\n```js\nconst { ... } = useDeferredLoads(fetchRandomDog);\n```\n\n### Configuration\n\nYou can set configuration on either a global level, or a local `useLoads` level.\n\n#### On a global level\n\nBy setting configuration on a global level, you are setting defaults for all instances of `useLoads`.\n\n```jsx\nimport * as Loads from 'react-loads';\n\nconst config = {\n  dedupingInterval: 1000,\n  timeout: 3000\n};\n\nexport default function App() {\n  return (\n    \u003cLoads.Provider config={config}\u003e\n      ...\n    \u003cLoads.Provider\u003e\n  )\n}\n```\n\n\u003e Warning: The `config` prop must be memoized. Either memoize it using `React.useMemo` or put it outside of the function component.\n\n[See the full set of configuration options here](#config-1)\n\n#### On a `useLoads` level\n\nBy setting configuration on a `useLoads` level, you are overriding any defaults set by `Loads.Provider`.\n\n```jsx\nconst { ... } = useLoads('randomDog', fetchRandomDog, { dedupingInterval: 1000, timeout: 3000 });\n```\n\n[See the full set of configuration options here](#config-1)\n\n### Variables\n\nIf your async function needs some dependant variables (such as an ID or query parameters), use the `variables` attribute in the `useLoads` config:\n\n```jsx\nasync function fetchDog(id) {\n  return axios.get(`https://dog.api/${id}`);\n}\n\nexport default function DogImage(props) {\n  const { ... } = useLoads('dog', fetchDog, { variables: [props.id] });\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-variables-o6l5d)\n\nThe `variables` attribute accepts an array of values. If your async function accepts more than one argument, you can pass through just as many values to `variables` as the function accepts:\n\n```jsx\nasync function fetchDog(id, foo, bar) {\n  // id = props.id\n  // foo = { hello: 'world' }\n  // bar = true\n  return axios.get(`https://dog.api/${id}`);\n}\n\nexport default function DogImage(props) {\n  const { ... } = useLoads('dog', fetchDog, {\n    variables: [props.id, { hello: 'world' }, true]\n  });\n}\n```\n\n#### WARNING!\n\nIt may be tempting to not use the `variables` attribute at all, and just use the dependencies outside the scope of the function itself. While this works, it will probably produce unexpected results as the cache looks up the record against the **context key (`'dog'`)** and the set of **`variables`**. However, in this case, it will only look up the record against the `'dog'` context key meaning that every response will be stored against that key.\n\n```jsx\n// DON'T DO THIS! IT WILL CAUSE UNEXPECTED RESULTS!\n\nexport default function DogImage(props) {\n  const id = props.id;\n  const fetchDog = React.useCallback(() =\u003e {\n    return axios.get(`https://dog.api/${id}`);\n  })\n  const { ... } = useLoads('dog', fetchDog);\n}\n```\n\n### Conditional loaders\n\nIf you want to control when `useLoads` invokes it's async function via a variable, you can use the `defer` attribute in the config.\n\n```jsx\nexport default function RandomDog(props) {\n  // Don't fetch until shouldFetch is truthy.\n  const { ... } = useLoads('randomDog', fetchRandomDog, {\n    defer: !props.shouldFetch\n  });\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-conditional-fetching-bi8b2)\n\n### Dependant loaders\n\nThere may be a case where one `useLoads` depends on the data of another `useLoads`, where you don't want subsequent `useLoads` to invoke the async function until the first `useLoads` resolves.\n\nIf you pass a function to `variables` and the function throws (due to `dog` being undefined), then the async function will be deferred while it is undefined. As soon as `dog` is defined, then the async function will be invoked.\n\n```jsx\nexport default function RandomDog(props) {\n  const { response: dog } = useLoads('randomDog', fetchRandomDog);\n  const { response: friends } = useLoads('dogFriends', fetchDogFriends, {\n    variables: () =\u003e [dog.id]\n  })\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-dependant-loaders-9hojp)\n\n### Caching\n\nCaching in React Loads comes for free with no initial configuration. React Loads uses the \"stale while revalidate\" strategy, meaning that `useLoads` will serve you with cached (stale) data, while it loads new data (revalidates) in the background, and then show the new data (and update the cache) to the user.\n\n#### Caching strategy\n\nReact Loads uses the `context` argument given to `useLoads` to store the data in-memory against a **\"cache key\"**. If `variables` are present, then React Loads will generate a hash and attach it to the **cache key**. In a nutshell, **`cache key = context + variables`**.\n\n```jsx\n// The response of this will be stored against a \"cache key\" of `dog.1`\nconst { ... } = useLoads('dog', fetchDog, { variables: [1] });\n```\n\nReact Loads will automatically revalidate whenever the cache key (`context` or `variables`) changes.\n\n```jsx\n// The fetchDog function will be fetched again if `props.context` or `props.id` changes.\nconst { ... } = useLoads(props.context, fetchDog, { variables: [props.id] });\n```\n\nYou can change the caching behaviour by specifying a [`cacheStrategy` config option](#cachestrategy). By default, this is set to `\"context-and-variables\"`, meaning that the cache key will be a combination of the `context` + `variables`.\n\n```jsx\n// The response of this will be stored against a `dog` key, ignoring the variables.\nconst { ... } = useLoads('dog', fetchDog, { cacheStrategy: 'context-only', variables: [props.id] });\n```\n\n\n#### Stale data \u0026 revalidation\n\nBy default, React Loads automatically revalidates data in the cache after **5 minutes**. That is, when the `useLoads` is invoked and React Loads detects that the data is stale (hasn't been updated for 5 minutes), then `useLoads` will invoke the async function and update the cache with new data. You can change the revalidation time using the [`revalidateTime` config option](#revalidatetime).\n\n```jsx\n// Set it globally:\nimport * as Loads from 'react-loads';\n\nconst config = {\n  revalidateTime: 600000\n}\n\nexport default function App() {\n  return (\n    \u003cLoads.Provider config={config}\u003e\n      ...\n    \u003c/Loads.Provider\u003e\n  )\n}\n\n// Or, set it locally:\nexport default function RandomDog() {\n  const { ... } = useLoads('randomDog', fetchRandomDog, { revalidateTime: 600000 });\n}\n```\n\n#### Cache expiry\n\nReact Loads doesn't set a cache expiration by default. If you would like to set one, you can use the [`cacheTime` config option](#cachetime).\n\n```jsx\n// Set it globally:\nimport * as Loads from 'react-loads';\n\nconst config = {\n  cacheTime: 600000\n}\n\nexport default function App() {\n  return (\n    \u003cLoads.Provider config={config}\u003e\n      ...\n    \u003c/Loads.Provider\u003e\n  )\n}\n\n// Or, set it locally:\nexport default function RandomDog() {\n  const { ... } = useLoads('randomDog', fetchRandomDog, { cacheTime: 600000 });\n}\n```\n\n### Slow connections\n\nOn top of the `isPending` \u0026 `isReloading` states, there are substates called `isPendingSlow` \u0026 `isReloadingSlow`. If the request is still pending after 5 seconds, then the `isPendingSlow`/`isReloadingSlow` states will become truthy, allowing you to indicate to the user that the request is loading slow.\n\n```jsx\nexport default function RandomDog() {\n  const { isPending, isPendingSlow } = useLoads('randomDog', fetchRandomDog);\n  return (\n    \u003cdiv\u003e\n      {isPending \u0026\u0026 `Loading... ${isPendingSlow \u0026\u0026 'Taking a while...'}`}\n    \u003c/div\u003e\n  )\n}\n```\n\nBy default, the timeout is **5 seconds**, you can change this with the [`timeout` config option](#timeout).\n\n### Polling\n\nReact Loads supports request polling (reload data every `x` seconds) with the [`pollingInterval` config option](#pollingInterval).\n\n```jsx\n// Calls fetchRandomDog every 3 seconds.\nconst { ... } = useLoads('randomDog', fetchRandomDog, { pollingInterval: 3000 });\n```\n\nYou can also add a [`pollWhile` config option](#pollWhile) if you wish to control the behaviour of when the polling should run.\n\n```jsx\n// Calls fetchRandomDog every 3 seconds while `shouldPoll` is truthy.\nconst shouldPoll = shouldTheDogBePollingRightNow();\nconst { ... } = useLoads('randomDog', fetchRandomDog, { pollingInterval: 3000, pollWhile: shouldPoll });\n```\n\nYou can also access the `record` as the first parameter of `pollWhile` if you provide a function.\n\n```jsx\n// Calls processImage every 3 seconds while it's status is 'processing'.\nconst { ... } = useLoads(\n  'randomDog',\n  fetchRandomDog,\n  { pollingInterval: 3000, pollWhile: record =\u003e record?.response?.status === 'processing' }\n);\n```\n\n### Deduping\n\nBy default, all your requests are deduped on an interval of **500 milliseconds**. Meaning that if React Loads sees more than one request of the same cache key in under 500 milliseconds, it will not invoke the other requests. You can change the deduping interval with the [`dedupingInterval` config option](#dedupingInterval).\n\n### Suspense\n\nTo use React Loads with Suspense, you can set the [`suspense` config option](#suspense) to `true`.\n\n```jsx\n// Set it globally:\nimport * as Loads from 'react-loads';\n\nconst config = {\n  suspense: true\n}\n\nexport default function App() {\n  return (\n    \u003cLoads.Provider config={config}\u003e\n      ...\n    \u003c/Loads.Provider\u003e\n  )\n}\n\n// Or, set it locally:\nexport default function RandomDog() {\n  const { ... } = useLoads('randomDog', fetchRandomDog, { suspense: true });\n}\n```\n\nOnce enabled, you can use the `React.Suspense` component to replicate the `isPending` state, and use [error boundaries]() to display error states.\n\n```jsx\nfunction RandomDog() {\n  const { response } = useLoads('randomDog', fetchRandomDog, { suspense: true });\n  return \u003cimg src={response.imgSrc} /\u003e;\n}\n\nfunction App() {\n  return (\n    \u003cReact.Suspense fallback={\u003cdiv\u003eloading...\u003c/div\u003e}\u003e\n      \u003cRandomDog /\u003e\n    \u003c/React.Suspense\u003e\n  )\n}\n```\n\n### Optimistic responses\n\nReact Loads has the ability to optimistically update your data while it is still waiting for a response (if you know what the response will potentially look like). Once a response is received, then the optimistically updated data will be replaced by the response. [This article](https://uxplanet.org/optimistic-1000-34d9eefe4c05) explains the gist of optimistic UIs pretty well.\n\nThe `setResponse` function is provided in a `meta` object as seen below.\n\n```jsx\nimport React from 'react';\nimport * as Loads from 'react-loads';\n\nasync function fetchDog(id) {\n  // Fetch the dog\n}\n\nfunction updateDog(id, data) {\n  return async meta =\u003e {\n    meta.setResponse(data);\n    // Fetch the dog\n  }\n}\n\nexport default function RandomDog(props) {\n  const dogRecord = Loads.useLoads('dog', fetchDog, { variables: [props.id] });\n\n  const updateDogRecord = Loads.useDeferredLoads('dog', updateDog);\n\n  return (\n    \u003cdiv\u003e\n      {dogRecord.isPending \u0026\u0026 'Loading...'}\n      {dogRecord.isResolved \u0026\u0026 \u003cimg src={dogRecord.response.imgSrc} /\u003e}\n      {dogRecord.isRejected \u0026\u0026 `Oh no! ${dogRecord.error.message}`}\n      \u003cbutton\n        onClick={() =\u003e updateDogRecord.load(props.id, { imgSrc: 'cooldog.png' })}\n      \u003e\n        Update dog\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Resources\n\nFor async functions which may be used \u0026 invoked in many parts of your application, it may make sense to hoist and encapsulate them into resources.\nA resource consists of one (or more) async function as well as a context.\n\nBelow is an example of a resource and it's usage:\n\n```jsx\nimport React from 'react';\nimport * as Loads from 'react-loads';\n\n// 1. Define your async function.\nasync function getUsers() {\n  const response = await fetch('/users');\n  const users = await response.json();\n  return users;\n}\n\n// 2. Create your resource, and attach the loading function.\nconst usersResource = Loads.createResource({\n  context: 'users',\n  fn: getUsers\n});\n\nfunction MyComponent() {\n  // 3. Invoke the useLoads function in your resource.\n  const getUsersRecord = usersResource.useLoads();\n\n  // 4. Use the record variables:\n  const users = getUsersRecord.response || [];\n\n  return (\n    \u003cdiv\u003e\n      {getUsersRecord.isPending \u0026\u0026 'loading...'}\n      {getUsersRecord.isResolved \u0026\u0026 (\n        \u003cul\u003e\n          {users.map(user =\u003e (\n            \u003cli key={user.id}\u003e\n              {user.name}\n            \u003c/li\u003e\n          ))}\n        \u003c/ul\u003e\n      )}\n    \u003c/div\u003e\n  )\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-resources-dg9xo)\n\nYou can attach more than one loading function to a resource. **But it's return value must be the same schema, as every response will update the cache.**\n\nYou can also provide an array of 2 items to the resource creator (seen below with `delete`); the first item being the async function, and the second being the [config](#config-1).\n\nHere is an extended example using a resource with multiple async functions, split into two files (`resources/users.js` \u0026 `index.js`):\n\n#### `resources/users.js`\n```jsx\nimport * as Loads from 'react-loads';\n\nasync function getUser(id) {\n  const response = await fetch(`/users/${id}`);\n  const user = await response.json();\n  return user;\n}\n\nfunction updateUser(id, data) {\n  return async meta =\u003e {\n    await fetch(`/users/${id}`, {\n      method: 'post',\n      body: JSON.stringify(data)\n    });\n    // `cachedRecord` is the record that's currently stored in the cache.\n    const currentUser = meta.cachedRecord.response;\n    const updatedUser = { ...currentUser, ...data };\n    return updatedUser;\n  }\n}\n\nasync function deleteUser(id) {\n  await fetch(`/users/${id}`, { method: 'delete' });\n  return;\n}\n\nexport default Loads.createResource({\n  context: 'user',\n  fn: getUser,\n  // You can supply either a async function, or an array of async function/config pairs.\n  update: [updateUser, { timeout: 3000 }],\n  delete: deleteUser\n});\n```\n\n#### `index.js`\n\n```jsx\nimport React from 'react';\n\nimport DeleteUserButton from './DeleteUserButton';\nimport UpdateUserForm from './UpdateUserForm';\nimport usersResource from './resources/users';\n\nfunction MyComponent(props) {\n  const { userId } = props;\n\n  const getUserRecord = usersResource.useLoads({\n    variables: [userId]\n  });\n  const user = getUserRecord.response || {};\n\n  const updateUserRecord = usersResource.update.useDeferredLoads({ variables: [userId] });\n  const deleteUserRecord = usersResource.delete.useDeferredLoads({ variables: [userId] });\n\n  return (\n    \u003cdiv\u003e\n      {getUserRecord.isPending \u0026\u0026 'loading...'}\n      {getUserRecord.isResolved \u0026\u0026 (\n        \u003cdiv\u003e\n          Username: {user.name}\n\n          \u003cDeleteUserButton\n            isLoading={deleteUserRecord.isPending}\n            onClick={deleteUserRecord.load}\n          /\u003e\n\n          \u003cUpdateUserForm onSubmit={data =\u003e updateUserRecord.load(userId, data)} /\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/div\u003e\n  )\n}\n```\n\n### External cache providers\n\nIf you would like the ability to persist response data upon unmounting the application (e.g. page refresh or closing window), a cache provider can also be utilised to cache response data.\n\nHere is an example using [Store.js](https://github.com/marcuswestin/store.js/). You can either set the external cache provider on a global level or a `useLoads` level.\n\n#### On a global level\n\n```jsx\nimport * as Loads from 'react-loads';\nimport store from 'store';\n\nconst config = {\n  cacheProvider: {\n    get: key =\u003e store.get(key),\n    set: (key, val) =\u003e store.set(key, val),\n    reset: () =\u003e store.clearAll()\n  }\n}\n\nexport default function App() {\n  return (\n    \u003cLoads.Provider config={config}\u003e\n      ...\n    \u003c/Loads.Provider\u003e\n  )\n}\n```\n\n#### On a `useLoads` level\n\n```jsx\nimport * as Loads from 'react-loads';\nimport store from 'store';\n\nconst cacheProvider = {\n  get: key =\u003e store.get(key),\n  set: (key, val) =\u003e store.set(key, val),\n  reset: () =\u003e store.clearAll()\n}\n\nexport default function RandomDog() {\n  const { ... } = Loads.useLoads('randomDog', fetchRandomDog, { cacheProvider });\n}\n```\n\n### Preloading (experimental)\n\nReact Loads comes with the ability to eagerly preload your data. You can do so using the `preload` function.\n\n```jsx\nconst randomDogLoader = Loads.preload('randomDog', fetchRandomDog);\n```\n\nThe `preload` function shares the same arguments as the `useLoads` function, however, `preload` is not a React Hook and shouldn't be called in your render function. Instead, use it inside event handlers, route preparation, or call it on first render.\n\nThe `preload` function will essentially fetch \u0026 cache your data in the background. It does not return any value apart from a `useLoads` hook. When the `useLoads` hook is invoked, it will read the data from the cache that was previously loaded by `preload`, and won't re-fetch your data. If no cached data exists, it will go ahead and fetch it.\n\n```jsx\nconst randomDogLoader = Loads.preload('randomDog', fetchRandomDog);\n\nfunction RandomDog() {\n  const { response } = randomDogLoader.useLoads({ suspense: true });\n  return \u003cimg src={response.imgSrc} /\u003e;\n}\n\nfunction App() {\n  return (\n    \u003cReact.Suspense fallback={\u003cdiv\u003eLoading...\u003c/div\u003e}\u003e\n      \u003cRandomDog /\u003e\n    \u003c/React.Suspense\u003e\n  )\n}\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-preloading-example-vn1xn)\n\n#### Render-as-you-fetch\n\nThe `preload` function is designed to implement the [\"render-as-you-fetch\" pattern](https://reactjs.org/docs/concurrent-mode-suspense.html#approach-3-render-as-you-fetch-using-suspense). Ideally, `preload` can be invoked when preparing your routes, or inside an event handler, where you can then use the `useLoads` function inside your component.\n\n[Basic example](https://codesandbox.io/s/react-loads-preloading-example-render-as-you-fetch-vvulq)\n\n[Event handler example](https://codesandbox.io/s/jakes-top-actors-concurrent-event-handler-bqus6)\n\n[Routing example](https://codesandbox.io/s/jakes-top-actors-concurrent-routing-gy6wz)\n\n## API\n\n### `useLoads`\n\n```jsx\nconst {\n  response,\n  error,\n  load,\n  isIdle,\n  isPending,\n  isPendingSlow,\n  isReloading,\n  isReloadingSlow,\n  isResolved,\n  isRejected,\n  reset,\n  state,\n  isCached\n} = useLoads(context, fn, config);\n```\n\n#### Parameters\n\n`context`\n\n\u003e `string`\n\nA unique identifier for the request.\n\n`fn`\n\n\u003e `function`\n\nA function that returns a promise to retrieve your data.\n\n`config`\n\n\u003e `object` | optional\n\nA set of [configuration options](#config-1)\n\n#### Returns\n\n##### `response`\n\n\u003e `any`\n\nResponse from the resolved promise (`fn`).\n\n##### `error`\n\n\u003e `any`\n\nError from the rejected promise (`fn`).\n\n##### `load`\n\n\u003e `function`\n\nTrigger to invoke [`fn`](#fn).\n\n##### `isIdle`\n\n\u003e `boolean`\n\nReturns `true` if the state is idle (`fn` has not been invoked).\n\n##### `isPending`\n\n\u003e `boolean`\n\nReturns `true` if the state is pending (`fn` is in a pending state).\n\n##### `isPendingSlow`\n\n\u003e `boolean`\n\nReturns `true` if the state is pending for longer than `timeout` milliseconds.\n\n##### `isReloading`\n\n\u003e `boolean`\n\nReturns `true` if the state is reloading (`fn` is in a pending state \u0026 `fn` has already been invoked or cached).\n\n##### `isReloadingSlow`\n\n\u003e `boolean`\n\nReturns `true` if the state is reloading for longer than `timeout` milliseconds.\n\n##### `isResolved`\n\n\u003e `boolean`\n\nReturns `true` if the state is resolved (`fn` has been resolved).\n\n##### `isRejected`\n\n\u003e `boolean`\n\nReturns `true` if the state is rejected (`fn` has been rejected).\n\n##### `reset`\n\n\u003e `function`\n\nFunction to reset the state \u0026 response back to an idle state.\n\n##### `state`\n\n\u003e `string`\n\nState of the promise (`fn`).\n\n##### `isCached`\n\n\u003e `boolean`\n\nReturns `true` if data exists in the cache.\n\n### `useDeferredLoads`\n\n```jsx\nconst {\n  response,\n  error,\n  load,\n  isIdle,\n  isPending,\n  isPendingSlow,\n  isReloading,\n  isReloadingSlow,\n  isResolved,\n  isRejected,\n  reset,\n  state,\n  isCached,\n  update\n} = useDeferredLoads(context, fn, config);\n// OR\n} = useDeferredLoads(fn, config);\n```\n\n#### Parameters\n\n`context`\n\n\u003e `string` | optional\n\nA unique identifier for the request. This is optional for `useDeferredLoads`.\n\n`fn`\n\n\u003e `function`\n\nA function that returns a promise to retrieve your data.\n\n`config`\n\n\u003e `object` | optional\n\nA set of [configuration options](#config-1)\n\n#### Returns\n\n[Same as `useLoads`](#useloads)\n\n### `useCache`\n\nA hook which enables you to retrieve a record from the cache.\n\n```jsx\n// Including `context` only\nconst randomDogRecord = useCache('randomDog');\n\n// Including `context` \u0026 `variables`\nconst dogRecord = useCache('dog', { variables: [0] });\n```\n\n#### Parameters\n\n##### `context`\n\nThe unique identifier of the record to retrieve.\n\n##### `variables`\n\nAn array of variables (parameters).\n\n#### Returns\n\n##### `response`\n\n\u003e `any`\n\nResponse of the cached record.\n\n##### `error`\n\n\u003e `any`\n\nError of the cached record.\n\n##### `state`\n\n\u003e `any`\n\nState of the cached record.\n\n### `useGetStates`\n\nA hook which composes a set of records, and gives you a singular state.\n\nWithout using `useGetStates`, you may run into situations like this:\n\n```jsx\nconst randomDogRecord = useLoads('randomDog', fetchRandomDog);\nconst dogFriendsRecord = useLoads('dogFriends', fetchDogFriends);\n\n\u003cdiv\u003e\n  {(randomDogRecord.isPending || dogFriendsRecord.isPending) \u0026\u0026 'Loading...'}\n  {randomDogRecord.isResolved \u0026\u0026 dogFriendsRecord.isResolved \u0026\u0026 'Loaded!'}\n\u003c/div\u003e\n```\n\nBut, if you compose your records inside `useGetStates`, you can clean up your state variables:\n\n```jsx\nconst randomDogRecord = useLoads('randomDog', fetchRandomDog);\nconst dogFriendsLoader = useLoads('dogFriends', fetchDogFriends);\n\nconst { isPending, isResolved, isRejected } = useGetStates(randomDogRecord, dogFriendsRecord);\n```\n\n### `\u003cProvider\u003e`\n\nSet global configuration with the `\u003cProvider\u003e` component.\n\n```jsx\nimport * as Loads from 'react-loads';\n\nconst config = {\n  cacheTime: 600000\n}\n\nexport default function App() {\n  return (\n    \u003cLoads.Provider config={config}\u003e\n      {/* ... */}\n    \u003c/Loads.Provider\u003e\n  )\n}\n```\n\n#### Props\n\n##### `config`\n\n\u003e `Object`\n\nAn object of [configuration options](#config-1)\n\n### `createResource`\n\n```jsx\nconst resource = createResource(options);\n```\n\n#### Parameters\n\n##### `options.context`\n\n\u003e `string`\n\nThe context of the resource. Used to generate a cache key.\n\n##### `options.fn`\n\n\u003e `function`\n\nA function that returns a promise to retrieve your data.\n\n##### Any key can be an async function!\n\nAny key you provide to the resource is an async function.\n\n```jsx\nconst dogsResource = createResource({\n  context: 'dogs',\n  fn: getDogs,\n  create: createDog,\n  foo: getDogFoo,\n  bar: getDogBar,\n  baz: getDogBaz\n});\n\n// In your function component - will invoke the `bar` async function in createResource:\ndogsResource.bar.useLoads();\n```\n\n#### Returns\n\n##### `useLoads`\n\nA `useLoads` hook which can be invoked in your function component.\n\nThe arguments are a bit different to the standalone `useLoads` hook - it only optionally accepts a `config` object, and not a `context` or an async function (`fn`).\n\n```jsx\nresource.useLoads(config)\n```\n\n##### `useDeferredLoads`\n\nA `useLoads` hook which can be invoked in your function component.\n\nThe arguments are a bit different to the standalone `useDeferredLoads` hook - it only optionally accepts a `config` object, and not a `context` or an async function (`fn`).\n\n```jsx\nresource.useDeferredLoads(config)\n```\n\n##### `preload` (experimental)\n\nSame as the [`preload` function](#preload-experimental), however only accepts a `config` object as it's only parameter.\n\n```jsx\nresource.preload(config)\n```\n\n[See the CodeSandbox example](https://codesandbox.io/s/react-loads-preloading-resources-example-render-as-you-fetch-p33fs)\n\n### `preload` (experimental)\n\n```jsx\nconst loader = preload(context, fn, config);\n```\n\n#### Parameters\n\n`context`\n\n\u003e `string` | optional\n\nA unique identifier for the request. This is optional for `useDeferredLoads`.\n\n`fn`\n\n\u003e `function`\n\nA function that returns a promise to retrieve your data.\n\n`config`\n\n\u003e `object` | optional\n\nA set of [configuration options](#config-1)\n\n#### Returns\n\n`useLoads`\n\nA `useLoads` hook which can be invoked in your function component.\n\nThe arguments are a bit different to the standalone `useLoads` hook - it only optionally accepts a `config` object, and not a `context` or an async function (`fn`).\n\n```jsx\nloader.useLoads(config)\n```\n\n### Config\n\n```jsx\nconfig = {\n  cacheProvider,\n  cacheStrategy,\n  cacheTime,\n  context,\n  dedupingInterval,\n  delay,\n  defer,\n  initialResponse,\n  loadPolicy,\n  onReject,\n  onResolve,\n  pollingInterval,\n  pollWhenHidden,\n  rejectRetryInterval,\n  revalidateTime,\n  revalidateOnWindowFocus,\n  suspense,\n  throwError,\n  timeout,\n  update,\n  variables\n}\n```\n\n#### `cacheProvider`\n\n\u003e `{ get: function(key), set: function(key, val), reset: function() }`\n\nSet a custom cache provider (e.g. local storage, session storate, etc). See [external cache providers](#external-cache-providers) for an example.\n\n#### `cacheStrategy`\n\n\u003e `string` | Default: `\"context-and-variables\"`\n\nThe caching strategy for your loader to determine the cache key.\n\nAvailable values:\n\n- `\"context-only\"`\n  - Caches your data against the `context` key only.\n- `\"context-and-variables\"`\n  - Caches your data against a combination of the `context` key \u0026 `variables`.\n\n#### `cacheTime`\n\n\u003e `number` | Default: `0`\n\nTime (in milliseconds) that the data remains in the cache. After this time, the cached data will be removed.\n\n#### `dedupingInterval`\n\n\u003e `number` | Default: `500`\n\nInterval (in milliseconds) that requests will be deduped in this time span.\n\n#### `delay`\n\n\u003e `number` | Default: `0`\n\nTime (in milliseconds) before the component transitions to the `\"pending\"` state upon invoking `fn`.\n\n#### `defer`\n\n\u003e `boolean`\n\nIf set to `true`, the async function (`fn`) won't be called automatically and will be deferred until later.\n\nIf `defer` is set to true, the initial state will be `idle`.\n\n#### `initialResponse`\n\n\u003e `any`\n\nSet initial data for the request. Useful for SSR.\n\n#### `loadPolicy`\n\n\u003e `string` | Default: `\"cache-and-load\"`\n\nA load policy allows you to specify whether or not you want your data to be resolved from the Loads cache and how it should load the data.\n\n- `\"cache-only\"`: `useLoads` will only return data from the cache. It will not invoke the async function.\n\n- `\"cache-first\"`: If a value for the loader already exists in the Loads cache, then `useLoads` will return the value that is in the cache, otherwise it will invoke the async function.\n\n- `\"cache-and-load\"`: This is the default value and means that `useLoads` will return with the cached value if found, but regardless of whether or not a value exists in the cache, it will always invoke the async function.\n\n- `\"load-only\"`: This means that `useLoads` will not return the cached data altogether, and will only return the data resolved from the async function.\n\n#### `onReject`\n\n\u003e `function(error)`\n\nA hook that is invoked when the async function (`fn`) rejects.\n\n#### `onResolve`\n\n\u003e `function(response)`\n\nA hook that is invoked when the async function (`fn`) resolves.\n\n#### `pollingInterval`\n\n\u003e `number`\n\nIf set, then `useLoads` will invoke the async function (`fn`) every `x` amount of seconds.\n\n#### `pollWhile`\n\n\u003e `boolean | function(record)`\n\nIf set, then `useLoads` will poll while the condition is truthy.\n\n#### `pollWhenHidden`\n\n\u003e `boolean` | Default: `false`\n\nIf truthy, then `useLoads` will continue to poll when the window is not focused.\n\n#### `rejectRetryInterval`\n\n\u003e `number | function(count)`\n\nIf set, and `useLoads` rejects, then `useLoads` will continue to try and resolve `fn` every `x` seconds. If a function is given, you can determine the interval time with that.\n\nExample:\n\n```js\n// Retry every 1000 milliseconds.\nrejectRetryInterval: 1000\n\n// Retry every \"error count\" * \"2000 milliseconds\".\nrejectRetryInterval: count =\u003e count * 2000\n```\n\n#### `revalidateTime`\n\n\u003e `number` | Default: `300000` (5 minutes)\n\nTime that the data in the cache remains \"fresh\". After this time, data in the cache will be marked as \"stale\", meaning that the data will have to be reloaded on next invocation.\n\n#### `revalidateOnWindowFocus`\n\n\u003e `boolean` | Default: `false`\n\nIf truthy, `useLoads` will load the async function (`fn`), when the browser window is focused again.\n\n#### `suspense`\n\n\u003e `boolean` | Default: `false`\n\nIf truthy, this will enable React Suspense mode.\n\n#### `throwError`\n\n\u003e `boolean` | Default: `false`\n\nIf truthy and the async function (`fn`) rejects, then `useLoads` or `load` will throw the error.\n\n#### `timeout`\n\n\u003e `number` | Default: `5000` (5 seconds)\n\nNumber of milliseconds before the component transitions to the `isPendingSlow` or `isReloadingSlow` state. Set to `0` to disable.\n\nNote: `useLoads` will still continue to try and resolve while in the `isPendingSlow` state\n\n#### `variables`\n\n\u003e `Array\u003cany\u003e`\n\nAn array of variables (parameters) to pass to your async function (`fn`).\n\n## Happy customers\n\n- \"I'm super excited about this package\" - [Michele Bertoli](https://twitter.com/MicheleBertoli)\n- \"Love the API! And that nested ternary-boolean example is a perfect example of how messy React code commonly gets without structuring a state machine.\" - [David K. Piano](https://twitter.com/DavidKPiano)\n- \"Using case statements with React components is comparable to getting punched directly in your eyeball by a giraffe. This is a huge step up.\" - [Will Hackett](https://twitter.com/willhackett)\n- \"I used to get the shakes coding data fetch routines with React. Not anymore. Using react loads, I now achieve data fetching zen.\" - [Claudia Nadalin](https://github.com/thepenskefile)\n- \"After seeing https://twitter.com/dan_abramov/status/1039584557702553601?lang=en, we knew we had to change our loading lifecycles; React Loads was our saviour.\" - [Zhe Wang](https://twitter.com/auzwang)\n\n## Acknowledgments\n\n- [David K. Piano](https://twitter.com/DavidKPiano) for state machine inspiration\n- [React Query](https://github.com/tannerlinsley/react-query) \u0026 [Zeit's SWR](https://github.com/zeit/swr) for design inspiration \u0026 ideas\n\n## License\n\nMIT © [jxom](http://jxom.io)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjxom%2Freact-loads","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjxom%2Freact-loads","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjxom%2Freact-loads/lists"}