a reducer enhancer to enable type-agnostic optimistic updates
npm install redux-optimistic-ui


yarn add redux-optimistic-uicombineReducers) or a nested one.| redux-optimistic-ui | redux-optimist |
|--------------------------------------------------------|-------------------------------------------------------------------|
| reducerEnhancer (wraps your state) | reducerExtender (adds an optimist to your state) |
| can use immutable.js or anything else | must use plain JS objects for your state |
| only uses 1 state copy | saves an extra copy of your state for every new optimistic action |
| FSA compliant | not FSA compliant |
| must wrap your state calls in ensureState | no change necessary to get your state |
``js`
import {optimistic} from 'redux-optimistic-ui';
return optimistic(reducer);
This will transform your state so it looks like this:
`js`
state = {
history: [],
beforeState:
current:
}state.history.length === 0
If the client is not waiting for a response from the server, the following are guaranteed to be true:
- state.beforeState === undefined
-
If you don't need to know if there is an outstanding fetch, you'll never need to use these.
Since your state is now wrapped, you need state.current.ensureState
But that sucks. What if you don't enhance the state until the user hits a certain route?
Lucky you! There's a function for that. will give you your state whether it's enhanced or not.state
Just wrap all your references to and getState with it & you're all set!
`js
// Before
getState().counter
// After (whether you've enhanced your reducer or not)
import {ensureState} from 'redux-optimistic-ui'
ensureState(getState()).counter
`
Now comes the fun! Not all of your actions should be optimistic.
Just the ones that fetch something from a server and have a high probability of success.
I like real-world examples, so this middleware is a little bit longer than the bare requirements:
`js
import {BEGIN, COMMIT, REVERT} from 'redux-optimistic-ui';
//All my redux action types that are optimistic have the following suffixes, yours may vary
const _SUCCESS = '_SUCCESS';
const _ERROR = '_ERROR';
//Each optimistic item will need a transaction Id to internally match the BEGIN to the COMMIT/REVERT
let nextTransactionID = 0;
// That crazy redux middleware that's 3 functions deep!
export default store => next => action => {
// FSA compliant
const {type, meta, payload} = action;
// For actions that have a high probability of failing, I don't set the flag
if (!meta || !meta.isOptimistic) return next(action);
// Now that we know we're optimistically updating the item, give it an ID
let transactionID = nextTransactionID++;
// Extend the action.meta to let it know we're beginning an optimistic update
next(Object.assign({}, action, {meta: {optimistic: {type: BEGIN, id: transactionID}}}));
// HTTP is boring, I like sending data over sockets, the 3rd arg is a callback
socket.emit(type, payload, error => {
// Create a redux action based on the result of the callback
next({
type: type + (error ? _ERROR : _SUCCESS),
error,
payload,
meta: {
//Here's the magic: if there was an error, revert the state, otherwise, commit it
optimistic: error ? {type: REVERT, id: transactionID} : {type: COMMIT, id: transactionID}
}
});
})
};
`
in other parts? Write a little something like this and call it on your asychronous route:`js
export default (newReducers, reducerEnhancers) => {
Object.assign(currentReducers, newReducers);
const reducer = combineReducers({...currentReducers})
if (reducerEnhancers){
return Array.isArray(reducerEnhancers) ? compose(...reducerEnhancers)(reducer) : reducerEnhancers(reducer);
}
return reducer;
}
``To see how it all comes together, check out https://github.com/mattkrick/meatier.