README
snowbox
Opinionated Redux abstractions for faster development.
Motivation
Most of the time, for each type of entity in your redux store, you have to implement the same actions, reducers, and selectors, over and over again. The same story holds true when it comes to calling your remote server.
Solution
Snowbox is a small collection of tools that hide the repetitiveness in your code and lets you focus on writing code that is trully important for your app. They are built on top of redux, normalizr, reselect, and immer.
Table of Contents
Install
npm install snowbox
Quick Start
- Add the snowbox reducer and middleware to your store. The key where the snowbox reducer is mounted must be
snowbox
.
// File: app-store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { snowboxReducer, snowboxMiddleware } from 'snowbox';
const store = createStore(
combineReducers({
snowbox: snowboxReducer,
/* app reducers */
}),
preloadedState,
applyMiddleware(
snowboxMiddleware,
/* app middlewares */
)
);
- Configure the
api
andprovider
services.
// File: app-provider.js
import { api, provider } from 'snowbox';
import store from './app-store';
import selectAuthToken from './app-selectors';
export const appApi = api({
baseUrl: 'http://localhost:3000/api',
tokenHeader: 'auth-token',
getAuthToken: () => selectAuthToken(store.getState()), // when your token is stored in the state
});
export const appProvider = provider(api);
export default appProvider;
- Define an entity
// File: entities/todo.js
import { entity } from 'snowbox';
import appProvider from '../app-provider';
export const todoProvider = appProvider({
particle: 'todos',
});
export const todo = entity('todos', todoProvider);
export default todo;
- Build your awesome app
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetch, selectCollection } from 'snowbox';
import todo from 'entities/todo'; // Import the todo entity
import Todo from './Todo';
const todosSelector = selectCollection(todo); // Create the selector for todos
export default function MyTodos({ filter }) {
const dispatch = useDispatch();
const todos = useSelector(todosSelector); // Select your todos
useEffect(() => {
dispatch(fetch(todo)(filter)); // Request todos
}, [dispatch, fetch, todo, filter]);
return todos.map(item => <Todo key={item.id} todo={item} />);
}
API
api(options)
options
: objectbaseUrl
: string required The base url where all the api requests go (e.g.https://my.awesome.server/api
).tokenHeader
: string The name of the header for the authentication token. Must be string. required whengetAuthToken
is defined.getAuthToken
: function Returns the authentication token. required whentokenHeader
si defined.
Api methods:
get(path, params)
Makes a GET HTTP request and returns the response.
Params:
path
: string required Together withoptions.baseUrl
it forms the destination url.params
: object The query params of the request.
Returns:
object
Server response.
post(path, data = {}, params, contentType)
Makes a POST HTTP request and returns the
serverResponse
.Params:
path
: string required Together withoptions.baseUrl
it forms the destination url.data
: object The body of the request.params
: object The query params of the request.contentType
: string The content type of the request.
Returns:
object
Server response.
: put(path, data = {}, params, contentType)
Makes a PUT HTTP request and returns the response.
Params:
path
: string required Together withoptions.baseUrl
it forms the destination url.data
: object The body of the request.params
: object The query params of the request.contentType
: string The content type of the request.
Returns:
object
Server response.
: patch(path, data = {}, params, contentType)
Makes a PATCH HTTP request and returns the response.
Params:
path
: string required Together withoptions.baseUrl
it forms the destination url.data
: object The body of the request.params
: object The query params of the request.contentType
: string The content type of the request.
Returns:
object
Server response.
: remove(path)
Makes a DELETE HTTP request and returns the response.
Params:
path
: string required Together withoptions.baseUrl
it forms the destination url.
Returns:
object
Server response.
: request(method, path, params, data, contentType)
Makes a HTTP request and returns
serverResponse
. It is the function called by all the otherapi
functions.Params:
method
: string required The HTTP method of the request.path
: string required Together withoptions.baseUrl
it forms the destination url.params
: object The query params of the request.data
: object The body of the request.contentType
: string The content type of the request.
Returns:
object
Server response.
provider(api)(options)
api
: object required Theapi
service that will make the HTTP requests.options
: objectparticle
: string required The resource name in a RESTful HTTP request (e.g. thetodos
inhttp://localhost:3000/api/todos/9
).idField
: string The field where the unique ID for each of this entity can be found. Defaults to'id'
.entityPath
: string The path where the entity data is found in nonfetch
responses. Useslodash.get
behind the scenes. Defaults to'data'
.entitiesPath
: string The path where the entities data is found in thefetch
responses. Useslodash.get
behind the scenes. Defaults to'data'
.entitiesFieldName
: string The field name for the entities in the result offetch
requests. Defaults to'records'
.hasMeta
: boolean Whether the fetch response has metadata or not. Defaults tofalse
.metaPath
: string The path where the metadata is found in thefetch
response. Useslodash.get
behind the scenes. Defaults to''
. IfmetaPath
is a parent ofentitiesPath
, the entities will be removed from the metadata object.metaFieldName
: string The field name for the metadata in the result offetch
request. Defaults to'meta'
.findPath(filter, options)
: function Returns the HTTP path forfind
requests. Defaults to/<particle>/<filter[idField]>
. The function is called with:filter
: object Thefilter
passed tofind
.options
: object The provider options.
findParams(filter, options)
: function Returns the query params forfind
requests. Defaults to everything in thefilter
object but without the<idField>
. The function is called with:filter
: object Thefilter
passed tofind
.options
: object The provider options.
fetchPath(filter, options)
: function Returns the HTTP path forfetch
requests. Defaults to/<particle>
. The function is called with:filter
: object Thefilter
passed tofetch
.options
: object The provider options.
fetchParams(filter, options)
: function Returns the query params forfetch
requests. Defaults to everything in thefilter
object but the<idField>
. The function is called with:filter
: object The filter passed tofetch
.options
: object The provider options.
createMethod
: string The HTTP method for create requests (when the<idField>
is missing in thedata
sent toupsert
). It can bepost
,put
, orpatch
. Defaults topost
.updateMethod
: string The HTTP method for update requests (when the<idField>
is present in thedata
sent toupsert
). It can bepost
,put
, orpatch
. Defaults toput
.upsertContentType
: string The content type of the create and update requests. Can be one ofsnowbox.contentTypes.JSON
andsnowbox.contentTypes.FORM_DATA
. When the content type isFORM_DATA
, theapi
will transform thedata
object sent toupsert
intoFormData
. Defaults toJSON
.upsertPath(data, options)
: function Returns the HTTP path forupsert
requests. Defaults to/<particle>
for create and/<particle>/<data[idField]>
for update. The function is called with:data
: object Thedata
passed toupsert
.options
: object The provider options.
upsertMethod
: string The HTTP method for upsert requests. Defaults tooptions.createMethod
for create andoptions.updateMethod
for update.removeMethod
: sring The HTTP method for remove requests. It can bedelete
,post
,put
, orpatch
. Defaults todelete
.removePath(data, options)
: function Returns the HTTP path forremove
requests. Defaults to/<particle>/<data[idField]>
. The function is called with:data
: Thedata
passed toremove
.options
: object The provider options.
Provider methods:
: find(filter)
Calls
[GET] <api.options.baseUrl/options.findPath?options.findParams
and returnsResponse
.Params:
filter
: object The filter for the requested resource.
Returns:
Response
: fetch(filter)
Calls
[GET] api.options.baseUrl/options.fetchPath?options.fetchParams
and returnsResponse
.Params:
filter
: object The filter for the requested resources.
Returns:
Response
: upsert(data, params)
Calls
[options.upsertMethod] api.options.baseUrl/options.upsertPath?params
and returnsResponse
.Params:
data
: object The body of the request.params
: object The HTTP query params.
Returns:
Response
: remove(data)
Calls
[options.removeMethod] api.options.baseUrl/options.removePath
and returnsResponse
.Params:
data
: object Usually the deleted resource.
Returns:
Response
Response(response, options, isFetch)
response
: object required The response object received from the server.options
: objectentityPath
: string The path where the entity data is found in thefind
response. This will be the result of thefind
request. Useslodash.get
behind the scenes. Defaults to'data'
.entitiesPath
: string The path where the entities data is found in thefetch
response. Useslodash.get
behind the scenes. Defaults to'data'
.entitiesFieldName
: string The field name for the entities in the result offetch
request. Defaults to'records'
.hasMeta
: boolean Whether the fetch response has metadata or not. Defaults tofalse
.metaPath
: string The path where the metadata is found in thefetch
response. Useslodash.get
behind the scenes. Defaults to''
. IfmetaPath
is a parent ofentitiesPath
, the entities will be removed from the metadata object.metaFieldName
: string The field name for the metadata in the result offetch
request. Defaults to'meta'
.
isFetch
: boolean Whether the response belongs to afetch
request. Defaults tofalse
.
Instance fields
data
: anyserverResponse[options.entitiesPath]
forfetch
requests andserverResponse[options.entityPath]
for everything else. WhenentitiesPath
orentittPath
is not defined it returns theserverResponse
.meta
: anyserverResponse[options.metaPath]
forfetch
requests whereoptions.hasMeta == true
andundefined
for everything else.original
: object Returns theserverResponse
.converted
: object When the request is notfetch
it returnsresponse.data
. When the request isfetch
it returns:
{
<options.entitiesFieldName>: serverResponse[options.entitiesPath],
<options.metaFieldName>: serverResponse[options.metaPath],
}
entity(key, provider, definition = {}, options = {})
key
: string required The key name under which all entities of this type will be listed in the normalized response. See normalizr.provider
: object A provider object that will be used to make requests to a remote server. Defaults to undefined.definition
: object A definition of the nested entities found within this entity. Defaults to empty object. See normalizr.options
:- all the options available for the normalizr Entity.
staleTimeout
: number Amount of milliseconds that the entity will be considered fresh.fetch
andfind
actions won’t make remote calls to load entities resources unless fresh data is specifically requested. Defaults toundefined
(i.e. all the requests are made).singleton
: boolean Whentrue
, it means that there is only one entity of that type (useful for the logged user). Defaults tofalse
.
actions
find(entity)(payload, meta = {})
: Requests a resource from the remote server and populates the state with the result.meta
: objectrefresh
: boolean Force the request for fresh data even when the state is not stale. Defaults tofalse
.
fetch(entity)(payload, meta = {})
: Requests a collection from the remote server and populates the state with the result.meta
: objectrefresh
: boolean Force the request for fresh data even when the state is not stale. Defaults tofalse
.
upsert(entity)(payload, meta = {})
: Sends a create or update request to the remote server and populates the state with the result.remove(entity)(payload, meta = {})
: Sends a delete request to the remote server and populates the state with the result.clearAll(payload)
: Clears the entire snowbox state. Useful when the user logs out.
selectors
selectOne(entity, hydrationLevels = 0, idField)(state, props)
: Selects the entity record that has the ID equal toprops[idField || entity.idField]
.entity
: object required Anentity
.hydrationLevels
: number Defines how many levels of nested entities will be denormalized. Defauls to zero.idField
: string The unique ID field name of the entity. Usually, the defaultentity.fieldId
should be used.state
: object required Redux state.props
: object required Component props.
selectCollection(entity, hydrationLevels = 0)(state, filter)
: Selects a collection that was requested with thefetch
action. Thefilter
passed here must match thepayload
of thefetch
action.entity
: object required Anentity
.hydrationLevels
: number Defines how many levels of nested entities will be denormalized. Defauls to zero.state
: object required Redux state.filter
: object required The filter used for selecting the collection.
selectAll(entity, hydrationLevels = 0)(state)
: Selects all the records of the entity that have been loaded on the state.entity
: object required Anentity
.hydrationLevels
: number Defines how many levels of nested entities will be denormalized. Defauls to zero.state
: object required Redux state.
selectMeta(entity)(state, filter)
: Selects themeta
object that was loaded with thefetch
action. Thefilter
passed here must match thepayload
of thefetch
action.entity
: object required Anentity
.state
: object required Redux state.filter
: object required The filter used for selecting the collection.
Hooks
: useList(entity, initialFilters = {})
entity
: object required AnEntity
.initialFilters
: object The initial filters for the list. Defaults to{}
.
Returns: { items, meta, status, error, prevItems, prevMeta, setFilters, upsert, remove, }
Constants
contentTypes
JSON
:FORM_DATA
actions
FIND
FETCH
UPSERT
REMOVE
CLEAR
statuses
PENDING
SUCCEEDED
FAILED