Releases: redux-loop/redux-loop
3.1.1
v3.1.0
v3.0.0
Switching from Effects to Cmds
The main goal of this release was to decouple side effect methods from redux as much as possible. With the Effect API, side effect methods were tightly coupled with redux-loop because the method was expected to return an action (for Effects.call) or return a promise that resolved (and never rejected) with an action (Effects.promise). This meant that the same method could not be used to make a request from 2 different parts of the same app if you wanted to handle the response differently.
To accomplish this, effects were redesigned (and renamed) to resemble Elm cmds. The definition of what actions to dispatch was moved from the side effect method to the reducer, where the side effect is described.
The new syntax looks like this.
return loop(newState, Cmd.run(fetchItems, {
successActionCreator: fetchItemsSuccess,
failActionCreator, fetchItemsFailed,
args: [id]
}));
By shifting the decision of what to do with the response from the side effect method to the reducer, we are able to make a completely redux-independent side effect method.
fetchItems can go from
import {successActionCreator, failActionCreator} from 'actions';
function fetchItems(userid){
return fetch(`/users/${userId}/items`)
.then(successActionCreator, failActionCreator);
}
to
function fetchItems(userid){
return fetch(`/users/${userId}/items`);
}
The new version knows nothing about redux, and can easily be reused anywhere you want to fetch items, no matter what you do with the response. The logic for the flow of control remains in the reducer.
Reducers are still pure. Cmds are still just a description of what will happen, and calling the reducer does not execute the function.
Simplifying Cmd types
Besides moving the action creators to the Cmd definitions like described above, there are a few other notable changes to the old effect types.
- Effects.promise and Effects.call have been merged into Cmd.run
- The main difference (besides taking action creators as options) is that it works with both synchronous and asynchronous functions. Return values will be passed to action creators, but if a promise is returned, the resolution value or rejection value will be passed. None of the options are required. If you want to just ignore the return value of a function (and it takes no parameters), you can say
Cmd.run(func)
.
- The main difference (besides taking action creators as options) is that it works with both synchronous and asynchronous functions. Return values will be passed to action creators, but if a promise is returned, the resolution value or rejection value will be passed. None of the options are required. If you want to just ignore the return value of a function (and it takes no parameters), you can say
- Effects.Constant was renamed to Cmd.action (it still just takes an action)
- Effects.lift was renamed to Cmd.map (to better match the elm map Cmd). Its behavior is unchanged.
- Effects.batch and none are now Cmd.batch and Cmd.none, with the same behavior
- Cmd.sequence was created. It's similar to batch, but each Cmd runs in series, not parallel.
The details of the api can be seen here.
Accessing Redux from Side effects
While it seems to go against the main goal of this release, in practice it's not always possible to make your side effects completely independent from redux. Or it might just be more inconvenient than it's worth. For example, you might want to dispatch an action in the middle of another side effect, before the first one is finished. Or you might want to pull an arbitrary slice of state out of the store that the calling reducer didn't have access to, and it was inconvenient to pass it all the way from the component to the reducer.
To accomplish this, Cmd.getState and Cmd.dispatch were created. They are just es6 symbols, but when passed to a side effect as an arg, they are replaced at the time the function is called with the actual getState and dispatch methods from the store.
//reducer.js
return loop(newState, Cmd.run(func, {args: [123, Cmd.getState, Cmd.dispatch]}))
//func.js
function func(id, getState, dispatch){
console.log(getState().auth.token);
dispatch(someAction(id));
}
It doesn't matter what order you pass these symbols in (or even if you pass them at all). They can be used along with normal parameters however you'd like. They are just replaced at the time of the function call with their corresponding function.
NOTE: This does not allow you to dispatch actions from a reducer or to access the global state from there. Cmd.dispatch and Cmd.getState are not functions themselves. Just symbols.
v2.2.2
v2.2.1
Other:
- Update URLs in package.json and release them so they get onto npm page
v2.2.0
Additions:
- Expose
isLoop
function for determining if an object returned by a reducer was created with an effect.
v2.1.1
Bug fixes:
- Default mutator now creates a new object on each pass, resolving issues with pure-render-style update checking in React. Thanks to @Egor-Sapronov for #46!
Internal improvements:
- The current effect is no longer left in store state, and is now tracked entirely separately. This should resolve a lot of issues with enhancer ordering and others, and paves the way toward removing the custom
dispatch()
implementation as well.
v2.1.0
Additions:
Effects.call(factory, ...args)
allows you to write synchronous effect functions like writing tolocalStorage
without the awkwardness of creating a Promise.combineReducers(reducerMap, [initialState, accessor, mutator])
allows you to use any kind of data structure to represent your state, includingImmutable.Map
s.
Fixes:
- Various documentation and example fixes
Thank you to @jarvisaoieong, @tarjei, @timarney, @claudiuandrei, and @leonaves for your contributions to this release!
v2.0.0
Breaking changes:
loop()
now returns an array[model, effect]
to allow for easier destructuring when manually composing reducers withEffects.lift()
and the Elm architecture. There is no change for those usingcombineReducers
all the way through. Tests that inspectloop()
results will need to be updated. Thanks to @jgoux for this suggestion! Closes #27.
Additions:
liftState()
is exposed so it can be composed with reducers. This allows for using the shorthand of returning just a model instead of aloop(model, Effects.none())
when no effect is intended, but guarantees that the return value will always be aloop()
result when testing and manually composing. Thanks to @mxdubois for the suggestion! Closes #26.getModel()
can be called on an object, and if it is an array returned byloop()
it will return the first element in the array, otherwise it will return the object itself (identity)getEffect()
can be called on an object, and if it is an array returned byloop()
it will return the second element in the array, otherwise it will returnnull
.
Patches:
- Improves the error message printed to the console when a promise effect throws an uncaught exception. Thanks to @danielmohacsi for helping to find out that the old error wasn't very helpful! Closes #24.
Add Effects.lift
The addition of Effects.lift
brings even closer to the Elm architecture by allowing the result of an effect to be piped through another action creator for fractal-like nesting of reducers.
Full documentation and example usage can be found in the API Docs
Thanks to @jarvisaoieong for contributing the implementation of this feature!