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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@reactistry/react-data-cache

v0.1.1

Published

A React library fetching and caching data from an API with simple and easy to use loading and error states.

Downloads

8

Readme

React-Data-Cache

React-Data-Cache looks to leverage the React Suspense API in order to create a configurable hook that can be used to build a clean, easy to use interface to fetch data and handle loading and error states.

NPM

Is this safe to use?

React.Suspense is a part of the core React library, but it is still listed as experimental. It is subject to change so this library could become obsolete in time. One of the goals of this library is to stay as closely tied to the progress that is being made to by the core React team with experiments that are happening on things like react-cache and react-fetch, so that if the core team rolls out an official version, then this could be adapted easily to their format.

None of the logic in this library uses experimental React logic, but because it is trying to push the edges of where React is going, it is possible that your application might need to rework some details if React moves away from the Suspense approach.

Using React-Data-Cache

Quickstart

npm install @reactistry/react-data-cache --save

or with yarn

yarn add @reactistry/react-data-cache

Defining your resources

At a high level place in your React tree, wrap your code with:

import { ResourceProvider } from "@reactistry/react-data-cache

<ResourceProvider resources={[
  { name: 'api', resolver: (path, options) => makeRequest(path, options) },
  { name: 'graphQLApi', resolver: (...args) => makeGraphQLRequest(...args) }
]}>
  <MyComponents />
</ResourceProvider>

In this example, when you use api, it will use makeRequest to resolve any requests that you make. This example also shows how you might also access to a different API that takes different types of arguments and a different resolver. Both can be used in the same app/tree and keep their data independent.

You could also define your resources as specific endpoints, for example with a RESTful API.

import { ResourceProvider } from "@reactistry/react-data-cache"

<ResourceProvider resources={[
  { name: 'episodes', resolver: (id, options) => makeRequest(`http://mysite/api/episodes${id ? `/${id}` : ""}`, options) },
  { name: 'characters', resolver: (id, options) =>  makeRequest(`http://mysite/api/characters${id ? `/${id}` : ""}`, options) }
]}>
  <MyComponents />
</ResourceProvider>

This would allow you to create multiple resolvers for each resource and can help prevent having to repeat knowing how to call each of your resource calls in the implementation details.

Wrap Places where you will fetch data with React.Suspense

Because react-data-cache relies on concurrent mode in React, you will need to make sure to have any places where you fetch data wrapped in React.Suspense. It is generally best practice to add React.Suspense at key places in your app to prevent the whole page from going to a loading screen each time you load anything. You can put multiple React.Suspense calls in your app, even nested within one another, and the lowest one will catch any loading requests that are going out. (NOTE: this must be wrapped at a higher level than any requests that are made, but not necessarily higher than your ResourceProvider)

<React.Suspense fallback={<LoadingSpinner>}>
  <EpisodeLoaderAndDisplayer>
</React.Suspense>

Fetching data

To fetch the data you want to use:

import { useResource } from "@reactistry/react-data-cache"

function EpisodeLoaderAndDisplayer() {
  const { fetch } = useResource('api')
  const episodes = fetch('path/to/episodes')

  return <EpisodeDisplayer episodes={episodes} />
}

This will load the episodes, and while it is loading, it will show the loader from React.Suspense. When it is finished loading, it will show the EpisodeDisplayer with the episodes loaded from the resolver. No more tracking loading states manually!

Preloading data

When you want to load multiple requests for a page, you want to start requesting the data as soon as possible, but don't want to prevent displaying things to the users for all the data to load. It is good practice to set up preload to load data at the highest place where you know it will be used, but it doesn't need to be referenced in the code at that level. Just use preload and it will load the data so that it is ready for where you want to use it:

import { useResource } from "@reactistry/react-data-cache"

function Page() {
  const { preload } = useResource('api')
  preload('path/to/episodes')
  preload('path/to/characters')

  return <PageToShowAllThatDataSomewhere>
}

In this scenario, both episodes and characters will load on loading of Page, but references within will still wait for the loading to complete. No waterfalls!

Clearing cache results / Refreshing Data

Sometimes you want to make a request clear its cache so that the request will go out again. Due to the nature of the library, if you clear the cache of a cached record, if it is referenced, it will act like it did on its first render and refetch the request, so for this reason, clearing and refreshing are one and the same. Here's how you do it:

import { useResource } from "@reactistry/react-data-cache"

function Page() {
  const { clear } = useResource('api')

  return <div>
    <button onClick={() => clear()}>Clear/Refresh All Requests</button>
    <button onClick={() => clear((key) => key.includes("/episodes"))}>Clear/Refresh episodes</button>
  </div>
}

NOTE: As of writing, I know that clearing specific resources is not ideal. I would love thoughts on how to make it better.

Ingesting data

Sometimes you might have data that is loaded in the format of a request from the API, but is loaded in a different way, for instance, through server rendered props. This will add data to the cache for the given arguments so that calling fetch for the data will reference that data.

import { useResource } from "@reactistry/react-data-cache"

function Component({ episodeData, id }) {
  const { ingest } = useResource("api")

  ingest(episodeData, `path/to/episode/${id}`)

  return <Episode episodeId={id} />
}

function Episode({ episodeId }) {
  const { fetch } = useResource("api")
  const episode = fetch(`path/to/episode/${id}`)
  // episode === episodeData and does not make request to resolver
  return <EpisodeDisplay episode={episode}/>
}

Handling Errors

React can use Error Boundaries to handle errors from your requests so that you don't have to build special functionality into your resolvers. When doing so, it is important to clear out failed requests because they will be stored in the cache.

Examples

  • API resource based example - This demonstrates an example of a simple api resource that has a resolver that is simply a wrapper around fetch. In it, you can see how loading states are handled, as well as how caching keeps data in store so that extra requests don't need to be made when navigating back to previous pages. Also, you can see that characters that have been loaded from other episode views use the cached values rather than making new requests. In addition, there is a button to test clearing the cache and seeing how that forces the reload. The last button gives the example of an error boundary and how it can be used to handle scenarios where an API request fails (NOTE: there is an extra layer that is shown on errors from CodeSandbox, but you can close it to see the error).
  • RESTful resource based example - Very similar to the approach above, but instead of using just the wrapper around fetch, it shows how the implementation can be easier/different if you define extra resolvers in the ResourceProvider
  • Preloading vs not Preloading example - Its important to think about how your data will load on your page, and although react-data-cache provides some simple ways of handling that data, its important that you load your data in the right place. This is an example that shows the difference in load times between a Waterfall (not using preload) approach and

API

ResourceProvider

Component to define resources

Props | Prop | type / shape | required? | |---|---|---| | resources | Array of objects with keys(name: string required, resolver: function required, keyGenerator: function optional) | yes | | children | node | yes |

Resource object shape |key | value type | required? | example | |---|---|---|---| | name | string | yes | "api" | | resolver | function (args can be whatever and are received when calling fetch/preload/etc) | yes | (path, options) => fetch(path, options).then(result => result.json()) | | keyGenerator | function (args are same as resolver), required to return string | yes | keyGenerator: (path, options) => "my-key-for-${path}-${JSON.stringify(options)}" |

useResource

Hook returning functions to interact with resources

  const { fetch, preload, clear, ingest, readFromCache } = useResource(resourceName)

| function name | simple explanation | arguments | returns | triggers suspense loading | makes request | |---|---|---|---|---|---| | fetch | requests and returns data from resolver | any (will be sent to resolver) | resolved result of the resolver | yes | yes | | preload | requests data from resolver without triggering load | any (will be sent to resolver) | undefined | no | yes | | clear | clears cache of data and refreshes any requests | function(optional) return true for each key in the cache that you want to clear | undefined | only if fetch is being called for the resources that have had their cache cleared | <--- | | ingest | loads data into the cache without making a request | value result that would come from resolver, any (args that would be sent to the resolver) | value | no | no | | readFromCache| reads data from the cache without making a request | any (same args that would be used for fetch) | cached result for the args | no | no |

How does this vary from the current React experimental approach?

Currently, react-cache is being unified with the concept of react-fetch, which connects this process with native fetch. While this is handy, it can be difficult in some circumstances where there are already predefined functions that are used to fetch data. By using resolvers, you can wrap all the fetching, serialization/deserialization, etc in your resolver, or you can simply make the resource a wrapper around fetch, but we don't want to couple it any closer to your implementation. In addition, the current react-fetch implementation does not have the extra features like ingesting and clearing data.