@workpop/optimistic-middleware
Advanced tools
Comparing version 0.3.0 to 1.0.0
@@ -15,58 +15,59 @@ 'use strict'; | ||
var OPTIMISTIC = { | ||
START: 'OPTIMISTIC_UPDATE_START', | ||
ERROR: 'OPTIMISTIC_UPDATE_FAILURE', | ||
SUCCESS: 'OPTIMISTIC_UPDATE_SUCCESS' | ||
}; | ||
function optimisticMiddleware(store) { | ||
return function (next) { | ||
return function (action) { | ||
var storeState = store.getState(); | ||
var simulate = action.simulate; | ||
var onSuccess = action.onSuccess; | ||
var onError = action.onError; | ||
var errorType = action.errorType; | ||
var mutation = action.mutation; | ||
var async = action.async; | ||
var stateKey = action.stateKey; | ||
var type = action.type; | ||
var data = action.data; | ||
var type = action.type; | ||
var optimistic = action.optimistic; | ||
var rest = _objectWithoutProperties(action, ['onSuccess', 'onError', 'errorType', 'mutation', 'stateKey', 'data', 'type']); | ||
// get the previous state before we start | ||
var rest = _objectWithoutProperties(action, ['simulate', 'onSuccess', 'onError', 'async', 'stateKey', 'type', 'data', 'optimistic']); | ||
// if we don't have a mutation, proceed like normal | ||
var previousState = storeState && storeState[stateKey] && storeState[stateKey].data; | ||
// if we don't have a mutation, proceed like normal | ||
if (!mutation) { | ||
if (!optimistic) { | ||
return next(action); | ||
} | ||
// get state of the world before mutation | ||
var storeState = store.getState(); | ||
// get the previous state before we start | ||
var previousState = storeState && storeState[stateKey] && storeState[stateKey].data; | ||
// apply the optimistic update first. This is described in our reducer for this actionType | ||
next(_extends({ | ||
type: type, | ||
data: data, | ||
optimisticState: OPTIMISTIC.START | ||
}, rest)); | ||
// if there is a simulation function, run the simulation with dispatch and data from the action bound to it. | ||
if (isFunction(simulate)) { | ||
simulate(next, data); | ||
} else { | ||
// otherwise dispatch the type with the data passed to the action creator | ||
next(_extends({ | ||
type: type, | ||
data: data | ||
}, rest)); | ||
} | ||
// next we're going to call our mutation, because we're in a Meteor context, we are expecting a callback with e,r | ||
return mutation(function (error, result) { | ||
return async(function (error, result) { | ||
// if there is an error we need to revert our state back to the initial state before middleware ran | ||
if (error) { | ||
// if the user supplies an onError callback as a function, call their function with | ||
// dispatch, the previousState, and the error we received | ||
if (isFunction(onError)) { | ||
onError(error); | ||
return onError(next, previousState, error); | ||
} | ||
// if not, automatically pass the original type action the previousState as data | ||
return next({ | ||
error: error.reason, | ||
data: previousState, | ||
optimisticState: OPTIMISTIC.ERROR, | ||
type: errorType || type | ||
type: type | ||
}); | ||
} | ||
// apply our update again but this time, change the OPTIMISTIC state | ||
if (isFunction(onSuccess)) { | ||
onSuccess(result); | ||
// if the user supplies a onSuccess callback as a function, call their function with | ||
// dispatch and the result of our async cal | ||
if (!isFunction(onSuccess)) { | ||
return false; | ||
} | ||
return next({ | ||
type: type, | ||
optimisticState: OPTIMISTIC.SUCCESS, | ||
data: data | ||
}); | ||
return onSuccess(next, result); | ||
}); | ||
@@ -73,0 +74,0 @@ }; |
{ | ||
"name": "@workpop/optimistic-middleware", | ||
"version": "0.3.0", | ||
"version": "1.0.0", | ||
"description": "Optimistic Methods Middleware for Redux", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
110
README.md
@@ -42,2 +42,3 @@ # Optimistic Middleware | ||
#### Generic Optimistic Action Creator | ||
```js | ||
@@ -47,11 +48,97 @@ function optimisticAddTodo(text) { | ||
type: 'ADD_TODO', | ||
data: text, | ||
stateKey: 'todos', | ||
mutation(cb) { | ||
async(cb) { | ||
return Meteor.call('addTodo', text, cb); | ||
}, | ||
data: text | ||
} | ||
} | ||
} | ||
``` | ||
#### Functional Simulation | ||
the simulate function will allow you to customize your simulations. | ||
```js | ||
function optimisticAddTodo(text) { | ||
return { | ||
type: 'ADD_TODO', | ||
data: text, | ||
simulate(dispatch, data) { | ||
dispatch({ | ||
type: 'ADD_TODO_ID', | ||
data: _.get(data, '_id'); | ||
}); | ||
return dispatch({ | ||
type: 'ALL_TODOS', | ||
data | ||
}); | ||
} | ||
stateKey: 'todos', | ||
async(cb) { | ||
return Meteor.call('addTodo', text, cb); | ||
} | ||
} | ||
} | ||
``` | ||
#### Custom Errors | ||
```js | ||
function optimisticAddTodo(text) { | ||
return { | ||
type: 'ADD_TODO', | ||
data: text, | ||
onError(dispatch, prevState, error) { | ||
if (error.reason === 'you suck') { | ||
dispatch({ | ||
type: 'TODO_ERROR', | ||
data: error.reason | ||
}); | ||
} | ||
return dispatch({ | ||
type: 'ADD_TODO', | ||
data: prevState | ||
}); | ||
} | ||
stateKey: 'todos', | ||
async(cb) { | ||
return Meteor.call('addTodo', text, cb); | ||
} | ||
} | ||
} | ||
``` | ||
#### Custom onSuccess | ||
```js | ||
function someThunk(result) { | ||
return (dispatch) => { | ||
someOtherAsync(result,(e, result) => { | ||
if (e) { | ||
console.error('ERROR'); | ||
} | ||
dispatch({ | ||
type: 'TOGGLE_TODO_LIST', | ||
data: result | ||
}); | ||
}); | ||
} | ||
} | ||
function optimisticAddTodo(text) { | ||
return { | ||
type: 'ADD_TODO', | ||
data: text, | ||
onSuccess(dispatch, result) { | ||
dispatch(someThunk(result)); | ||
} | ||
stateKey: 'todos', | ||
async(cb) { | ||
return Meteor.call('addTodo', text, cb); | ||
} | ||
} | ||
} | ||
``` | ||
Let's break down our action shape: | ||
@@ -63,3 +150,3 @@ | ||
stateKey: string, | ||
mutation: Function, | ||
async: Function, | ||
data: any | ||
@@ -73,5 +160,9 @@ } | ||
2. `[stateKey] - key of reducer related to action` | ||
3. `[mutation] - asynchronous function intended to mutate some data on the server` | ||
3. `[async] - asynchronous function intended to make mutation/remote call to the server` | ||
4. `[data] - data needed to change the state in the reducer` | ||
5. `[onSuccess] - function to be called when async function returns *optional` | ||
6. `[simulate] - function be called to simulate the optimistic update *optional` | ||
7. `[onError] - function to be called when the async function returns an error *optional` | ||
## How this works: | ||
@@ -81,20 +172,15 @@ | ||
We start the dispatch process immediately executing the data change in the action to the reducer. The state is appended with `OPTIMISTIC_UPDATE_START`, to signal the start of our dispatch. | ||
We start the dispatch process immediately executing the data change in the action to the reducer. | ||
From there, we call our asynchronous method. | ||
If the method returns an error, we append several pieces of meta data with the error reason and `OPTIMISTIC_UPDATE_FAILURE` (helpful for development). | ||
If the method returns an error, we append several pieces of meta data with the error reason. | ||
```js | ||
type OptimisticErrorType = { | ||
error: string, | ||
data: any | ||
optimisticState: string, | ||
type: string | ||
} | ||
``` | ||
If successful, we append `OPTIMISTIC_UPDATE_SUCCESS` and finish the dispatch process. | ||
## Caveats | ||
Because we are reverting our state when errors occur based on the reducer state, this middleware is confined to the reducer `stateKey` passed into the action. Optimistic Middleware does not currently support updates that affect multiple stateKeys. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
37215
17
72
1
182