How to provide a history instance to a saga?

2020-07-10 10:51发布

I would like to redirect to a new page after successful login. The routes (V4) are used like this:

import { browserHistory } from '....browser_history_signleton';
...

class App extends Component {
  render() {
    const { authentication: { isSignedIn } } = this.props;
    return (
      <ConnectedRouter history={browserHistory}>
        <div>
          <Header/>
          <Route exact path="/" component={Home}/>
          <PrivateRoute isAuthorized={isSignedIn} path="/page1" component={PageOne}/>
          <PrivateRoute isAuthorized={isSignedIn} path="/page2" component={PageTwo}/>
        </div>
      </ConnectedRouter>
    );
  }
}

The saga looks like:

import { browserHistory } from '....browser_history_signleton';

export function* loginSaga() {
  while (true) { // eslint-disable-line no-constant-condition
    try {
      const payload = yield take(LOGIN_SUBMIT);
      const raceResult = yield race({
        signin: call(loginRequest, payload),
        logout: take('LOGOUT')
      });
      if (raceResult.signin) {
        const { data }  = raceResult.signin;
        yield put(loginRequestSucceeded(data));
        const redirectUrl = `.....based on location.state.from.pathname`
        browserHistory.push(rediretUrl);
        ...

My main issue is how to share browserHistory. createHistory from history module is not a signleton, so I had to add:

// browser_history_signleton.js
import createHistory from 'history/createBrowserHistory';

export const browserHistory = createHistory();

What is the most efficient way to provide a history instance to a saga?

2条回答
放我归山
2楼-- · 2020-07-10 11:20

As you pointed out, making history directly available as a singleton is one option. Another similar (if not controversial) approach is to add history to the redux state. So in your saga, you'd use a select effect to get the history instance.

Both approaches effectively treat history as singletons, but by storing it into the redux state, you can mock the history object a bit easier in unit tests. What I mean by easier is that you can employ the same technique to mock the history object as mocking any other object in the redux state.

Also, you would presumably instantiate the history object along with other initial state so your logic to initialize history wouldn't be a one off exception as to how application state is initialized.

查看更多
爷的心禁止访问
3楼-- · 2020-07-10 11:47

I've found two options that felt ok and I've used both. I'm curious to see if anyone has issues with either.

Option 1: Pass the history object around to sagas.

Its not obvious, but the sagaMiddleware.run function takes a second parameter that's forwarded to the sagas. Ie:

/wherever/you/start/saga.js

import { createBrowserHistory } from "history";
import saga1 from "./saga1.js";

const  function* rootSaga({ history }) {
  yield all([saga1({ history })])
}

const sagaTask = sagaMiddleware.run(rootSaga, { history: createBrowserHistory() });

I learned this here: https://github.com/ReactTraining/react-router/issues/3972#issuecomment-251189856

This is a clean-ish way of accessing history functionality. In your actual sagas, you'd use the history object like normal.

./saga1.js

export default ({ history }) => [
  takeEvery(actions.DO_SOMETHING_THEN_NAVIGATE, function*({ payload }) {
    ...do something
    history.push("/somewhere");
  }),
];

Option 2: Have a single saga manage the history object & navigate using actions

This is an extension of Option 1. Dedicate a saga to "manage" the history object - pushing/replacing using actions. Ie:

/my/history/saga.js


export default ({ history }) => [ // history is passed in ala option 1.
  takeEvery(actions.HISTORY_PUSH, function*({ payload }) {
    const pathname = payload.fooParam;
    yield history.push(pathname);
  }),
  takeEvery(actions.HISTORY_REPLACE, function*({ payload }) {
    yield history.replace({ pathname: payload.barParam });
  }),
];

This keeps your redux store and actions clean, free of the weird hacks some of the community proposes - like passing the history object around in actions.

Let me know what you think.

查看更多
登录 后发表回答