How to handle multiple interdependent sagas when rendering server-side?

老子叫甜甜 提交于 2020-02-22 12:21:12

问题


I am implementing server-side rendering using redux-saga.

I am following the "real world" example provided in the redux-saga repository.

  1. node.js entry-point uses react.js renderToString to render the application.
  2. rendering the application triggers componentWillMount, which dispatches actions GET_GEOLOCATION and GET_DATE. These async actions will resolve with SET_GEOLOCATION and SET_DATE.
  3. renderToString finishes rendering the application; END action terminates the saga listeners

The problem is that SET_GEOLOCATION and SET_DATE themselves are used to put a new action GET_MOVIES. However, by the time the SET_GEOLOCATION and SET_DATE are called, the saga listeners are no longer active (we terminated it after renderToString). Therefore, while GET_MOVIES will be dispatched, the GET_MOVIES action will not be picked and SET_MOVIE will never happen.

Server code:

app.get('*', (req, res) => {
  const history = createMemoryHistory({
    initialEntries: [
      req.url
    ]
  });
  const store = configureStore(undefined, history);
  const context = {};

  const rootComponent = <Provider store={store}>
    <StaticRouter context={context} location={req.url}>
      <Route component={RootRoute} />
    </StaticRouter>
  </Provider>;

  store
    .runSaga(rootSaga).done
    .then(() => {
      const body = renderToString(rootComponent);
      const response = renderHtml(body, store);

      res
        .send(response);
    })
    .catch((error) => {
      res
        .status(500)
        .send(error.message);
    });

  // Force componentWillMount to issue saga effects.
  renderToString(rootComponent);

  store.close();
});

Sagas:

const watchNewSearchCriteria = function *(): Generator<*, *, *> {
  yield takeLatest([
    SET_GEOLOCATION,
    SET_DATE
  ], function *() {
    const {
      coordinates,
      date
    } = yield select((state) => {
      return {
        coordinates: state.movieEventsView.location ? state.movieEventsView.location.coordinates : null,
        date: state.movieEventsView.date
      };
    });

    if (!coordinates || !date) {
      return;
    }

    yield put(getMovies({
      coordinates,
      date
    }));
  });
};

const watchGetMovies = function *() {
  yield takeLatest(GET_MOVIES, function *(action) {
    const result = yield call(getMovies, action.payload);

    yield put(setMovies(result));
  });
};

How to delay store.close until after there are no sagas that are in the state other than take?


回答1:


How to delay store.close until after there are no sagas that are in the state other than take?

To answer my own question, I need to observe resolution of anything thats been put. I can do this using the Saga Monitor.

Saga Monitor can be configured at the time of creating the redux-saga middleware. For our use case, it needs to track whenever an action has been put and remove it from the index when it has been resolved/ rejected/ cancelled.

const activeEffectIds = [];

const watchEffectEnd = (effectId) => {
  const effectIndex = activeEffectIds.indexOf(effectId);

  if (effectIndex !== -1) {
    activeEffectIds.splice(effectIndex, 1);
  }
};

const sagaMiddleware = createSagaMiddleware({
  sagaMonitor: {
    effectCancelled: watchEffectEnd,
    effectRejected: watchEffectEnd,
    effectResolved: watchEffectEnd,
    effectTriggered: (event) => {
      if (event.effect.CALL) {
        activeEffectIds.push(event.effectId);
      }
    }
  }
});

We need to access this from the consumer of the store, therefore I assign activeEffectIds to the store instance:

store.runSaga = sagaMiddleware.run;

store.close = () => {
  store.dispatch(END);
};

store.activeEffectIds = activeEffectIds;

Then instead of synchronously stopping the saga...

renderToString(rootComponent);
store.close();

we need to delay store.close until store.activeEffectIds.length is 0.

const realDone = () => {
  setImmediate(() => {
    if (store.activeEffectIds.length) {
      realDone();
    } else {
      store.close();
    }
  });
};

// Force componentWillMount to issue saga effects.
renderToString(rootComponent);

realDone();

Now store.close is called only when all the asynchronous effects are resolved.



来源:https://stackoverflow.com/questions/43834756/how-to-handle-multiple-interdependent-sagas-when-rendering-server-side

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!