# 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.
