Redux model to capture states of asynchronous operations (i.e. promises)
After you install this module (probably from npm) you can import base class called Progress
import Progress from 'redux-progress';Progress class provides useful utilities to handle different states in your application.
In addition you can import Progress static props and methods as separate functions.
import {none, inProgress, success, fail, all} from 'redux-progress';You can create instance through one of this static methods.
static success: <T>(result: T)=> Progress<T>
static fail: (any) => Progress<any>;
const success = Progress.success({});
const fail = Progress.fail({a: '1'});Once instance is created there is no way to change object status (e.g. success or failed).
Two more statuses are available through static properties Progress.inProgress and Progress.none.
Basically those properties is Progress instances with predefined statuses.
Boolean. True if the instance object has success status.
Boolean. True if the instance object has failed status.
Boolean. True if the instance object has success or failed status.
Boolean. True if the instance object has inProgress status.
Boolean. True if the instance object has inProgress or isCompleted status.
Boolean. True if the instance object has no setted status
(true if isStarted === false).
R | void. Contains value of success operation or undefined.
R | void. Contains error value of failed operation.
map<T>(mapper: (r: R) => T): Progress<T>
Allows to map over a value stored inside Progress object. Mapper applied only to successive instances.
Returns the new Progress object with transformed value inside.
Progress
.success(10)
.map(x => x + 5)
.result; // => 15
Progress
.fail('Failed')
.map(x => x + 5)
.result; // => FailedflatMap<T>(mapper: (r: R) => Progress<T>): Progress<T>
Similar to map method, except that mapper function should return Progress instance.
Useful for chaining.
Mapper applied only to successive instances.
Progress
.success(10)
.flatMap(x => Progress.success(x + 5))
.result; // => 15fold<T>(folder: Folder<R, T>): T | null
Fold receives object (Folder) that specifies different actions for different Progress states.
Useful to applying side effects and reduce boilerplate code.
const requestFolder = {
success: (x) => console.log('Result is - ', x),
failed: (reason) => console.log('Failed to get result. ', reason),
none: () => console.log('There is no data')
};
const makeRequest = () => {
return fetch('/request-url')
.then(request => request.json())
.then(data => data ? Progress.success(data) : Progress.none)
.catch(error => Progress.fail(error));
};
makeRequest()
.fold(requestFolder); // => logs result or error or no data messageifSuccess<T>(func: (r: R) => T): T | null
Works similar to fold method, but handle the only successive case.
Basically .ifSuccess(func) it's a shortcut for .fold({success: func}).
Progress
.success(10)
.ifSuccess(x => x + 5); // => 15
Progress
.fail('Some error')
.ifSuccess(x => x + 5); // nullunwrap(): R
Extract the value from Progress object.
Progress
.success(10)
.unwrap(); // => 15
Progress
.fail('Some error')
.unwrap(); // => Some errorProgress class has two static methods for composing many instances together.
API pretty similar to Promise.all and Promise.race methods.
all: <I: Array<Progress<mixed>>>(...I)=> Progress<$TupleMap<I, ExtractResult>>
Returns first failed or inProgress or none item from passed items.
If all items are success then will return successive Progress object with an array of all items values
Progress.all(
Progress.success({ a: "1" }),
Progress.success({ b: "2" })
); // => the same that Progress.success([ { a: "1" }, { b: "2" } ])
Progress.all(
Progress.success({}),
Progress.fail({a: '1'}),
Progress.inProgress
); // => the same that Progress.fail({a: '1'})race: <T>(...Progress<T>[])=> Progress<T>
Returns first complete item (failed or success).
If there are no complete items then will return the first item from arguments.
If arguments empty will return Progress.none.
Progress.race(
Progress.success({ a: "1" }),
Progress.success({ b: "2" })
); // => the same that Progress.success({ a: "1" })To wire up those utilities with redux you can use the thunkProgress function inside your actions.
You can use this in pair with redux-thunk middleware.
import {thunkProgress} from 'redux-progress';
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
// Reducer to handle async states
const asyncReducer = (state = {}, {type, progress}) => {
switch (type) {
case 'MY_ASYNC_ACTION_NAME':
return {
loading: progress.inProgress,
result: progress.result,
error: progress.error
};
default:
return state;
}
};
const store = createStore(
combineReducers({asyncReducer}),
applyMiddleware(thunk)
);
// Action creator
const doAsyncAction = () => {
return thunkProgress(
'MY_ASYNC_ACTION_NAME',
fetch('/my-url').then(response => response.json())
);
};
// Inside React component
dispatch(doAsyncAction());Also could be useful to save the Progress instance to the state and use all
available instances methods inside redux containers or components.
const asyncReducer = (state = {}, {type, userProgress}) => {
switch (type) {
case 'SET_USER':
return {
user: userProgress
};
default:
return state;
}
};
// Inside React component or redux container
const MyComponent = ({userName}) => {
return (
<div>
{userName.fold({
success: (u) => <div className="user-name">{u}</div>,
failed: (error) => <div className="user-error">{error}</div>,
loading: () => <span>"Loading ..."</span>
})}
</div>
);
};
export default connect(
state => ({
userName: state.user.map(u => u.name.toUpperCase())
})
)(MyComponent);