Data Provider
JavaScript Async Data Provider
Cache and memoization
Optimized. Don't care about how many times do you call simultaneously to read a provider.
Data, loading and error states
Handling asynchronies implies handling error and loading states. Data Provider makes this job for you.
Selectors inspired by Reselect
Compute data derived from other Providers or Selectors with a familiar and very powerful syntax.
UI binding addons
Data Provider is not concerned about the views, but UI binding addons are available.
The @data-provider/react package gives you hooks to easily retrieve and provide data and other data-provider states to React components:
It also provides HOCs like
Optimized, it takes care of reading the data and re-renders the component only when your desired props have changed.
The @data-provider/react package gives you hooks to easily retrieve and provide data and other data-provider states to React components:
useData
, useError
, useLoading
, useLoaded
,...
It also provides HOCs like
withData
, withLoading
, etc.
Optimized, it takes care of reading the data and re-renders the component only when your desired props have changed.
import { useData, useLoading, useError } from "@data-provider/react";
import { booksProvider } from "data/books";
import ErrorComponent from "components/error";
const Books = () => {
const error = useError(booksProvider);
const data = useData(booksProvider);
const loading = useLoading(booksProvider);
if (error) {
return <ErrorComponent error={error}/>
}
return <BooksList data={data} loading={loading} />;
};
export default Books;
Agnostic about data origins
The Provider class provides the cache, state handler, etc., but not the
There are different origins available, such as Axios, LocalStorage, Memory, etc. and creating your own is easy. Read "creating origin addons" for further info.
Sharing the same interface for all origins, and being able to build Selectors combining all of them implies that your logic will be completely isolated about from WHERE the data is being retrieved.
read
method.
The read
behavior is implemented by different Data Provider Origins addons.
There are different origins available, such as Axios, LocalStorage, Memory, etc. and creating your own is easy. Read "creating origin addons" for further info.
Sharing the same interface for all origins, and being able to build Selectors combining all of them implies that your logic will be completely isolated about from WHERE the data is being retrieved.
import { Axios } from "@data-provider/axios";
import { LocalStorage } from "@data-provider/browser-storage";
export const books = new Axios({
id: "books",
url: "/api/books"
});
export const favoriteBooks = new LocalStorage({
id: "favorite-books"
});
Selectors inspired by Reselect
Selectors are recomputed whenever any dependency changes.
Exposing the same interface than providers make consumers agnostic about what type of Provider or Selector are they consuming.
As in Reselect, Selectors are composable. They can be used as input to other selectors.
Powerful dependencies api: Catch dependencies errors, retrieve them in parallel, declare them as functions returning other providers or selectors, etc.
Exposing the same interface than providers make consumers agnostic about what type of Provider or Selector are they consuming.
As in Reselect, Selectors are composable. They can be used as input to other selectors.
Powerful dependencies api: Catch dependencies errors, retrieve them in parallel, declare them as functions returning other providers or selectors, etc.
import { Selector } from "@data-provider/core";
import { booksProvider } from "data/books";
import { authorsProvider } from "data/authors";
export const booksWithAuthor = new Selector(
booksProvider,
authorsProvider,
(queryValue, books, authors) => {
return books.map(book => ({
...book,
author: authors.find(
author => author.id === book.authorId
)
}))
}
);
Cache and memoization
The built-in cache ensures that Providers are computed only once.
Don't care about when a data has to be retrieved. Simply retrieve it always, Data Provider will do the optimization. Avoid orchestrators and build fully modular pieces.
Cache can be cleaned on-demand, and some specific origins providers implementations even do it automatically when needed.
Don't care about when a data has to be retrieved. Simply retrieve it always, Data Provider will do the optimization. Avoid orchestrators and build fully modular pieces.
Cache can be cleaned on-demand, and some specific origins providers implementations even do it automatically when needed.
import Books from "views/books";
const RenderBooksTwice = () => {
return (
<div>
<Books />
<Books />
</div>
);
};
export default RenderBooksTwice;
Queryable
Providers and selectors instances can be queried, which returns a new child instance with its own
Different origins can use the
When the parent provider cache is cleaned, also the children are. (For example, cleaning the cache of an API origin requesting to
queryValue
. Each different child has a different cache, different state, etc.
Different origins can use the
queryValue
for different purposes (API origins will normally use it for adding different params or query strings to the provider url, for example)
When the parent provider cache is cleaned, also the children are. (For example, cleaning the cache of an API origin requesting to
/api/books
, will also clean the cache for /api/books?author=2
)
import { useData, useLoading } from "@data-provider/react";
import { bookProvider } from "data/books";
import BookCard from "components/book-card";
const Book = ({ id }) => {
const provider = bookProvider.query({ id });
const book = useData(provider);
const loading = useLoading(provider);
if (loading) {
return <Loading />;
}
return <BookCard title={book.title} author={book.author} />;
};
export default Book;
Event emitter and Redux store
In most of cases, the integrations addons available (like @data-provider/react will save you having to interact directly with the providers, but, for most complex use cases you can listen to its events.
If this is not enough, as Data Provider uses Redux to handle providers states, it also provides an
If this is not enough, as Data Provider uses Redux to handle providers states, it also provides an
storeManager
that allows to migrate it to your own store using combineReducers
for debugging purposes, for example.
Every single provider also has a getter for retrieving its own state
directly.
import {
authorsProvider,
authorProvider
} from "data/authors";
authorsProvider.on("readStart", () => {
console.log("Authors request started");
});
authorProvider.onChild("*", eventName => {
if (["update", "delete", "create"].includes(eventName)) {
console.log("An author has been modified, cleaning cache");
authorsProvider.cleanCache();
}
});
Motivation
As a front-end developer and what some call an "architect", I've worked in
very big projects, so I spent lot of time of last years trying to achieve a fully modular system in which the team could reuse pieces across many applications...