Resourcerer
Declarative data-fetching and caching framework for REST APIs with React
Install / Use
/learn @noahgrant/ResourcererREADME
resourcerer
resourcerer is a library for declaratively fetching and caching your application's data. Its powerful useResources React hook or withResources higher-order React component (HOC) allows you to easily construct a component's data flow, including:
- serial requests
- prioritized rendering for critical data (enabling less critical or slower requests to not block interactivity)
- delayed requests
- prefetching
- inter-model syncing
- ...and more
Additional features include:
- fully declarative (no more writing any imperative Fetch API calls)
- first-class loading and error state support
- smart client-side caching
- lazy fetching
- refetching
- forced cache invalidation
- updating a component when a resource updates
- zero dependencies
- < 6kB!
Getting started is easy:
- Define a model in your application (these are classes descending from Model or Collection):
// js/models/todos-collection.js
import {Collection} from 'resourcerer';
export default class TodosCollection extends Collection {
url() {
return '/todos';
}
}
- Create a config file in your application and add your constructor to the ModelMap with a key:
// js/core/resourcerer-config.js
import {register} from 'resourcerer';
import TodosCollection from 'js/models/todos-collection';
// choose any string as its key, which becomes its ResourceKey
register({todos: TodosCollection});
// in your top level js file
import 'js/core/resourcerer-config';
-
Use your preferred abstraction (
useResourceshook orwithResourcesHOC) to request your models in any component:-
useResources
import {useResources} from 'resourcerer'; // tell resourcerer which resource you want to fetch in your component const getResources = (props) => ({todos: {}}); function MyComponent(props) { const { isLoading, hasErrored, hasLoaded, todosCollection } = useResources(getResources, props); // when MyComponent is mounted, the todosCollection is fetched and available // as `todosCollection`! return ( <div className='MyComponent'> {isLoading ? <Loader /> : null} {hasErrored ? <ErrorMessage /> : null} {hasLoaded ? ( <ul> {todosCollection.toJSON().map(({id, name}) => ( <li key={id}>{name}</li> ))} </ul> ) : null} </div> ); } -
withResources
import React from 'react'; import {withResources} from 'resourcerer'; // tell resourcerer which resource you want to fetch in your component @withResources((props) => ({todos: {}})) class MyComponent extends React.Component { render() { // when MyComponent is mounted, the todosCollection is fetched and available // as `this.props.todosCollection`! return ( <div className='MyComponent'> {this.props.isLoading ? <Loader /> : null} {this.props.hasErrored ? <ErrorMessage /> : null} {this.props.hasLoaded ? ( <ul> {this.props.todosCollection.map((todoModel) => ( <li key={todoModel.id}>{todoModel.get('name')}</li> ))} </ul> ) : null} </div> ); } }
-
There's a lot there, so let's unpack that a bit. There's also a lot more that we can do there, so let's also get into that. But first, some logistics:
Contents
- Installation
- Nomenclature
- Tutorial
- Intro
- Other Props Returned from the Hook/Passed from the HOC (Loading States)
- Requesting Prop-driven Data
- Changing Props
- Common Resource Config Options
- Data mutations
- Serial Requests
- Canonical Models
- Differences between useResources and withResources
- Using resourcerer with TypeScript
- Caching Resources with ModelCache
- Declarative Cache Keys
- Prefetch on Hover
- Refetching
- Cache Invalidation
- Tracking Request Times
- Configuring resourcerer
- FAQs
- Migrating to v2.0
Installation
$ npm i resourcerer or yarn add resourcerer
resourcerer requires on React >= 16.8 but has no external dependencies.
Note: Resourcerer is written in TypeScript and is compiled to ESNext. It does no further transpiling—including import/export.
If you are using TypeScript yourself, this won't be a problem. If you're not, and you're not babelifying (or similar) your node_modules folder, you'll need to make an exception for this package, ie:
// webpack.config.js or similar
module: {
rules: [{
test: /\.jsx?$/,
exclude: /node_modules\/(?!(resourcerer))/,
use: {loader: 'babel-loader?cacheDirectory=true'}
}]
}
Nomenclature
-
Props. Going forward in this tutorial, we'll try to describe behavior of both the
useResourceshook and thewithResourcesHOC at once; we'll also rotate between the two in examples. Note that if we talking about a passed prop of, for exampleisLoading, that that corresponds to anisLoadingproperty returned from the hook and athis.props.isLoadingprop passed down from the HOC. -
ResourceKeys. These are the keys of the object passed to the
registerfunction in your top-levelresourcerer-config.jsfile (discussed above in the introduction). The object is of typeRecord<ResourceKeys, new () => Model | new () => Collection>. These keys are passed to the executor functions and are used to tell the hook or HOC which resources to request. -
Executor Function. The executor function is a function that both the hook and HOC accept that declaratively describes which resources to request and with what config options. In these docs you'll often see it assigned to a variable called
getResources. It acceptspropsas arguments and may look like, as we'll explore in an example later:const getResources = (props) => ({user: {path: {userId: props.id}}});or
const getResources = (props) => { const now = Date.now(); return { userTodos: { params: { limit: 20, end_time: now, start_time: now - props.timeRange, sort_field: props.sortField } } }; };It returns an object whose keys represent the resources to fetch and whose values are Resource Configuration Objects that we'll discuss later (and is highlighted below).
-
Resource Configuration Object. In the object returned by our executor function, each entry has a key equal to one of the
ResourceKeysand whose value we will refer to in this document as a Resource Configuration Object, or Resource Config for short. It holds the declarative instructions thatuseResourcesandwithResourceswill use to request the resource.
Tutorial
Okay, back to the initial example. Let's take a look at our useResources usage in the component:
// `@withResources((props) => ({todos: {}}))`
const getResources = (props) => ({todos: {}});
export default function MyComponent(props) {
const resources = useResources(getResources, props);
// ...
}
You see that useResources takes an executor function that returns an object. The executor function
takes a single argument: the current props, which are component props when you use withResources, but can be anything when you use useResources. The executor function returns an object whose keys are ResourceKeys and whose values are Resource Config objects. Where do ResourceKeys come from? From the object passed to the register method in the config file we added earlier!
// js/core/resourcerer-config.js
import {register} from 'resourcerer';
import TodosCollection from 'js/models/todos-collection';
// after adding this key, `todos` can be used in our executor functions to reference the Todos resource.
// The 'todos' string value will also be the default prefix for all todos-related return values.
// That's why we have `props.todosCollection`!
register({todos: TodosCollection});
(We can also pass custom prefixes for our prop names in a component, but we'll get to that later.)
Back to the executor function. In the example above, you see it returns an object of {todos: {}}. In general, the object it should return is of type `{[key: ResourceKe
