Skip to content

Releases: redux-loop/redux-loop

3.1.1

05 Aug 05:54
Compare
Choose a tag to compare

Added getEffect to improve backwards compatibility with v2.
Changed the way deprecated effects are created so that they are easier to compare in tests.

v3.1.0

15 Jul 19:32
Compare
Choose a tag to compare

Add Effects that are backwards compatible with v2

The old Effect types from version 2 have been added back and should work like before. They will have a deprecation notice and be removed in version 4.0.

v3.0.0

12 Jul 05:35
Compare
Choose a tag to compare

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).
  • 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

20 Jul 15:29
Compare
Choose a tag to compare

Fixes:

  • Dispatching more than one effectful action in the same tick will not cause any of the prior effects to be dropped. All effects created in the same tick are batched and executed simultaneously. Thanks to @akiellor for getting the fix together!

v2.2.1

19 Jul 19:26
Compare
Choose a tag to compare

Other:

  • Update URLs in package.json and release them so they get onto npm page

v2.2.0

18 Jul 16:21
Compare
Choose a tag to compare

Additions:

  • Expose isLoop function for determining if an object returned by a reducer was created with an effect.

v2.1.1

19 Apr 20:48
Compare
Choose a tag to compare

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

04 Apr 20:11
Compare
Choose a tag to compare

Additions:

  • Effects.call(factory, ...args) allows you to write synchronous effect functions like writing to localStorage 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, including Immutable.Maps.

Fixes:

  • Various documentation and example fixes

Thank you to @jarvisaoieong, @tarjei, @timarney, @claudiuandrei, and @leonaves for your contributions to this release!

v2.0.0

15 Mar 16:26
Compare
Choose a tag to compare

Breaking changes:

  • loop() now returns an array [model, effect] to allow for easier destructuring when manually composing reducers with Effects.lift() and the Elm architecture. There is no change for those using combineReducers all the way through. Tests that inspect loop() 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 a loop(model, Effects.none()) when no effect is intended, but guarantees that the return value will always be a loop() 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 by loop() 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 by loop() it will return the second element in the array, otherwise it will return null.

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

17 Feb 03:20
Compare
Choose a tag to compare

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!