damd
damd

Reputation: 6977

Side effects in Redux reducer

Redux reducers should be without side-effects. But what if an action should trigger the download of a file in the browser where the content is based on the state of the store? Surely this should count as a side effect? Would something like the following be fine or should I be looking for alternative methods?

case 'SAVE_GRID': {
  const { json } = state
  fileDownload(json, 'data.json', 'application/json')
  return state
}

Upvotes: 8

Views: 10719

Answers (4)

Nick Manning
Nick Manning

Reputation: 2989

I use the hook useReducer in lieu of redux. I'm not a fan of putting side effects in action creators - then it is unclear what the purpose of the action creator is: build an action, or run side effects?

I found a library here that fulfills my purpose:

https://github.com/Jibbedi/use-reducer-effect

Upvotes: 0

mmr
mmr

Reputation: 134

Reducers can drive effects, in fact they do it all the time via state changes which trigger UI rendering — arguably the application's most important effect.

Driving non-visual effects isn't as common but there are at least two solutions: redux-loop and redux-agent (which I wrote).

From the respective sites:

redux-loop:

A port of the Elm Architecture to Redux that allows you to sequence your effects naturally and purely by returning them from your reducers. https://redux-loop.js.org/

redux-agent:

Redux Agent extends React’s model to non-visual I/O: describe a network request, a storage operation, a websocket message, … and let the machine worry about performing it. Logic stays in the reducer, components stay lightweight, and it’s easy to see what state triggers which effect. https://redux-agent.org/

(The quotes also hint at a major difference between the two: redux-loop returns effect descriptions from the reducer, thereby changing its API and requiring a store enhancer. redux-agent works with vanilla Redux API.)

Upvotes: 0

Davin Tryon
Davin Tryon

Reputation: 67306

Unless you have very complex state transitions, the actual fileDownload should happen in an action creator, not in the reducer. The reducer should be responsible for merging/reducing state and that is all.

action:

export const saveGrid = (json) => {
   return (dispatch) => {  
       fileDownload(json, 'data.json', 'application/json')
           .then(() => {
              dispatch({ type: 'SAVE_GRID', json });
           });
   }
}

reducer:

case 'SAVE_GRID': {
    return {
        ...state,
        json: action.json
    }
}

Upvotes: 8

Kev
Kev

Reputation: 5452

There are libraries to handle such "side" effects.

For example:

  • redux-observables: they use RX observables under the hood.

https://redux-observable.js.org/docs/basics/Epics.html

  • redux-saga

https://redux-saga.js.org/

Upvotes: 1

Related Questions