react-boilerplate: How to run a saga for a container that does not use react-router ?

I am a bit stuck in plugging in a saga that was required for a container component that does NOT need react-router. It seems to me that the saga for a container that uses react-router is taken care of automatically in this react-boilerplate code, because I don’t need to do anything other than run the built-in generator for a router.

I find the following code in store.js:

  store.runSaga = sagaMiddleware.run;

and I tried to run my custom saga this way, but it does not work:

store.runSaga = sagaMiddleware.run(mySaga); // note: mySaga

Could someone help me understand how to plugin a saga to the store for a container component that does not require react-router?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 4
  • Comments: 24 (10 by maintainers)

Most upvoted comments

I’ve had some trouble while implementing @bobkocisko solution and @learntoswim solution mainly because:

  • injectSagasis now expecting an Array
  • the store was not valid right after store.runSaga = sagaMiddleware.run; => asyncInjectors: Expected a valid redux store)
  • sagaMiddleware.run(...globalSagas); wouldn’t accept more than one saga.

What I finally ended up to have multiple global sagas was to create a *sagas.js on the app folder:

import { getAsyncInjectors } from './utils/asyncInjectors';

import { loginSaga } from './containers/Login/sagas';
import { categorySelectorSaga } from './containers/CategorySelector/sagas';

export function injectGlobalSagas(store) {
  const { injectSagas } = getAsyncInjectors(store);

  injectSagas([
    loginSaga,
    categorySelectorSaga
  ]);
};

And call the injectGlobalSagas right before creating the routes on app.js:

import createRoutes from './routes';

import { injectGlobalSagas } from './sagas';
injectGlobalSagas(store);

const rootRoute = {
  component: App,
  childRoutes: createRoutes(store),
};

I think there’s a slightly more elegant way to do global sagas, though not different in principle than some of the answers already mentioned here. I thought I’d mention my approach in case anyone finds it helpful.

app/store.js, insert the following at the end of the import statements

import { getAsyncInjectors } from 'utils/asyncInjectors';

// Global sagas
import yourSagas from 'containers/YourContainer/sagas';
import yourOtherSagas from 'containers/YourOtherContainer/sagas';

app/store.js, insert the following right after the store.runSaga = sagaMiddleware.run; line

// Run any global sagas
const { injectSagas } = getAsyncInjectors(store);

injectSagas(yourSagas);
injectSagas(yourOtherSagas);

This allows you to define sagas as you normally would, exporting an array as the output of your sagas.js file and this automatically runs all the sagas in the array.

@bobkocisko I like your approach.

I hacked together my solution, as follows:

app/sagas.js

// Export all your sagas here so the sagaMiddleware imports them
import { getUserAuthenticated } from './containers/App/sagas';
export default [
  getUserAuthenticated,
];

app/store.js

// Import global sagas
import globalSagas from './sagas';

Then inside export default function configureStore(initialState = {}, history){

// Run Global Saga automatically
sagaMiddleware.run(...globalSagas);

// Extensions
store.runSaga = sagaMiddleware.run;

@garajo I ran into this same issue today. And I fixed it by removing below code from the generator function in sagas.js as it suspends its execution. Hope it helps you also.

// Suspend execution until location changes
yield take(LOCATION_CHANGE);
yield cancel(watcher);

You were close, but changing the store.runSaga reference will break all other sagas, since they call store.runSaga to point to sagaMiddleware.run.

Can you try adding that line above the assignment? That should work!

sagaMiddleware.run(yourSaga);
store.runSaga = sagaMiddleware.run;

I want to run a saga in the container/App level, and I landed on this question thread, which at first seemed to solve my problem. But the saga never gets invoked. I created a container/App/saga.js file and hook it up as mentioned above sagaMiddleware.run(yourSaga);. I’ve verified the saga gets included. But when I try to dispatch an action from App/index.js, and though the state does get changed in redux, the listening saga doesn’t seem to ‘hear’ the dispatch. Is this the correct way to go about this? My motivation is that I want a saga that is globally triggered once, regardless of the route the user lands on.

Having the “nested” container have its Saga’s loaded by it’s parent container couples it to the parent though, no?

To take @Augustin82’s example, you can’t just throw in a <AlertsList /> component anywhere, unless you first go and explicitly put a reference to it’s Saga(s) in the parent container. Likewise, you’ve now also got to remember to remove the Alerts Sagas from the parent container if you remove the Alert component from the parent container.

Not the end of the world by any means, but definitely something that could bite you given a large enough project.