react-loader-factory-immutable
Version:
A factory for creating custom functional react loading screen components (immutableJS version).
131 lines (97 loc) • 4.72 kB
Markdown
# react-loader-factory-immutable
A factory for producing Redux-driven loading screens. (ImmutableJS version.)
*For a version that doesn't use ImmutableJS, check out
[react-loader-factory](https://www.npmjs.com/package/react-loader-factory).*
## Example
```
npm install --save react-loader-factory-immutable
```
Say you have an asynchronous request that provides data through Redux to a pure
functional component. You'd like to display a loader while waiting for the
request, but you don't want to pollute your beautiful pure function.
With react-loader-factory, you can do this instead:
```js
import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory-immutable';
import ChildComponent from './ChildComponent';
const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);
const LoadingChild = loaderWrapper(ChildComponent);
const containingComponent = props => {
// Do whatever you need to do with your usual containing component
const childProps = { someProps: 'props' };
return <LoadingChild { ...childProps } />;
}
```
You'll also need a reducer that tracks which requests are active. Something like
this:
```js
import Immutable from 'immutable';
export function activeRequests(state = Immutable.List([]), action) {
// regex that tests for an API action string ending with _REQUEST
const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
// regex that tests for a API action string ending with _SUCCESS
const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);
// if a _REQUEST comes in, add it to the activeRequests list
if (reqReg.test(action.type)) {
return state.push(action.type);
}
// if a _SUCCESS comes in, delete its corresponding _REQUEST
if (sucReg.test(action.type)) {
const reqType = action.type.split('_')[0].concat('_REQUEST');
const deleteInd = state.indexOf(reqType);
if (deleteInd !== -1) {
return state.delete(deleteInd);
}
}
return state;
}
```
As long as none of the requests specified in `monitoredStates` have come back
with a `SUCCESS` (or whatever you use to specify a successful request), the
loader will continue to display its default throbber, or a `throbber` prop you
pass into the returned loading component.
## The guts
1. `loaderFactory(actionsList, monitoredStates)` returns a higher-order
component that connects to the Redux store and monitors the `activeRequests`
state branch for values it's been told to monitor. It expects
`activeRequests` to have a `.some()` method to test with. It also takes
responsibility for dispatching the Redux actions specified in `actionsList`
exactly once.
2. If any of its monitored active requests are present, it displays
`this.props.throbber` or a default `<div>` with a class
`this.props.throbberClass` (or `loader layout--flex` if none is specified).
3. If there are no more active requests the wrapped component cares about, the
throbber component gets out of the way and returns the originally wrapped
component, with all props passed through.
### Optional state injector
You can also pass in a function as the third argument to `loaderFactory` that
accepts `state` as its sole argument. The function should return an object with
an array of strings called `activeRequests`. If any of `monitoredStates` are
present in the array, the throbber will be rendered instead of the dependent
content.
```js
const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates, function(state) {
return { activeRequests: state.get('customKey') };
});
```
### Optional state dependency
If you have even more granular requirements for what the state needs to be
before the loader should pass the application through, pass in a boolean
function of `state` as the fourth argument to `loaderFactory`. When this
function returns `true` **and** the state injector function above return true,
the loader will render the wrapped component.
### Why a factory?
The factory pattern is needed to set up the `connect()` call that hooks the
component up to your Redux store. There's no way for a component to dynamically
`connect()` itself when evaluated, so the factory pattern gives you that
convenience.
## Things like this
- [react-loader](https://github.com/TheCognizantFoundry/react-loader): Stateful
single component version without any particular connection to Redux.
- [React Higher Order Components in depth](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.nwgftq1ft):
My reference for HOCs.