salsita / prism

React / Redux action composition made simple http://salsita.github.io/prism/
495 stars 24 forks source link

Sagas not getting unmounted #69

Closed vasyl-purchel closed 7 years ago

vasyl-purchel commented 7 years ago

Hi, I found a problem while trying to use react-router with redux-elm.

If I have simple components without sagas all works as in this example.

Through if I'm creating components with sagas as mentioned in tutorial I get a problem when changing location more then once to component with saga:

WARNING: The Saga instance has already been mounted, this basically mean that your Updaters do not form a tree, please be sure that every Updater is wrapped by another Updater (except root updater). It does not make sense to use combineReducers for Updaters.
log @ logger.js:8
warn @ logger.js:14
mount @ SagaRepository.js:49
(anonymous) @ Updater.js:132
(anonymous) @ storeEnhancer.js:54

I've used components from tutorial:

router/view.js:

import React from 'react';
import { connect } from 'react-redux';
import { forwardTo, view } from 'redux-elm';
import { Router, Route } from 'react-router';

import AppLayout from '../AppLayout';
import GifViewerPair from '../gif-viewer-pair/view';
import HelloWorld from '../HelloWorld/view';

const connectView = (View, modelKey, ...nesting) =>
  connect(appState => ({ model: appState.root[modelKey] }))(
    props => <View {...props} dispatch={forwardTo(props.dispatch, ...nesting)} />,
  );

const AppLayoutView = connectView(view(AppLayout), 'appLayout', 'AppLayout');
const GifViewerPairView = connectView(GifViewerPair, 'gifViewerPair', 'GifViewerPair');
const MonitorView = connectView(HelloWorld, 'helloWorld', 'HelloWorld');

export default view(({ history }) => (
  <Router history={history}>
    <Route path="/" component={AppLayoutView}>
      <Route path="gifViewer" component={GifViewerPairView} />
      <Route path="helloWorld" component={HelloWorldView} />
    </Route>
  </Router>
));

router/updater.js:

import { Updater } from 'redux-elm';
import gifViewerPairUpdater, { init as gifViewerPairInit } from '../gif-viewer-pair/updater';
import helloWorldUpdater, { init as helloWorldInit } from '../hello-world/updater';

export const initialModel = {
  gifViewerPair: gifViewerPairInit(),
  helloWorld: helloWorldInit(),
};

export default new Updater(initialModel)
  .case('GifViewerPair', (model, action) => (
    { ...model, gifViewerPair: gifViewerPairUpdater(model.gifViewerPair, action) }
  ))
  .case('HelloWorld', (model, action) => (
    { ...model, helloWorld: helloWorldUpdater(model.helloWorld, action) }
  ))
  .toReducer();

gif-viewer-pair/updater.js:

import { Updater, wrapAction } from 'redux-elm';
import { takeEvery } from 'redux-saga';
import { put } from 'redux-saga/effects';

import gifViewerUpdater, { init as gifViewerInit, requestMore } from '../gif-viewer/updater';

const initialModel = {
  top: gifViewerInit('ducks'),
  bottom: gifViewerInit('dinosaur'),
};

function* fetchAll() {
  yield put(wrapAction(requestMore(), 'Top'));
  yield put(wrapAction(requestMore(), 'Bottom'));
}

function* saga() {
  yield* takeEvery('Load', fetchAll);
}

export const init = () => initialModel;

export default new Updater(initialModel, saga)
  .case('Top', (model, action) =>
    ({ ...model, top: gifViewerUpdater(model.top, action) }))
  .case('Bottom', (model, action) =>
    ({ ...model, bottom: gifViewerUpdater(model.bottom, action) }))
  .toReducer();

gif-viewer-pair/view.js:

import React from 'react';
import { forwardTo, view } from 'redux-elm';

import GifViewer from '../gif-viewer/view';

export default view(({ model, dispatch }) => (
  <div>
    <GifViewer model={model.top} dispatch={forwardTo(dispatch, 'Top')} />
    <GifViewer model={model.bottom} dispatch={forwardTo(dispatch, 'Bottom')} />
    <br />
    <button onClick={() => dispatch({ type: 'Load' })}>Load Both!</button>
  </div>
));

So from some little investigation I found that when saga is created it is using action.matching.id (in my case it was GifViewerPair.) and adds saga into this.sagas["GifViewerPair."] (with dot at the end), while unmount saga looks by id GifViewerPair (with no dot at the end) and for sure it doesn't find it and not unmounting the saga.

So as the result when using react-router when component is Unmounted saga stay in the saga repository, and when we mount same component again by navigating back to page with that component it try to mount saga again, but saga is already there as same id with dot at the end is used.

namjul commented 7 years ago

Also mentioned in https://github.com/salsita/redux-elm/issues/54#issuecomment-256596244

tomkis commented 7 years ago

Closing because of irrelevance. prism v4 does not deal with Sagas anymore.