Reputation: 25081
I've been trying to integrate Redux-Saga into my React/Redux app for a while now and there is some key part of this that I'm missing (as it doesn't want to work).
My understanding of how this is supposed to work is as follows:
rootSaga
function, I fork off a "Watcher" which watches for a particular action to occur.put
or call
to dispatch an action for the watcher to see.Below is the closest I've come (conceptually) to getting it to work (code hastily anonymized, so please forgive any typos).
My sagas.ts file:
import { all, call, fork, put, takeLatest} from 'redux-saga/effects';
import { fromJS } from 'immutable';
import AppRedux, { IAction } from 'app/containers/App/reducers';
const fetchData = async (id: string, name: string): Promise<any> => {
console.log('calling fetchData');
const resource = `someurl?id=${id}`;
const data = await((await fetch(resource)).json()); // request(resource);
console.log('returning fetchData');
return fromJS({
id: id,
name,
data,
});
};
const callFetchData = function* callFetchData(action: IAction) {
console.log('calling callFetchData*');
try {
const result = yield call(fetchData, action.id, action.name);
yield put({
type: AppRedux.Action.DATA_FETCHED,
result,
});
} catch (error) {
yield put({
type: AppRedux.Action.FETCH_FAILED,
error,
});
}
console.log('exiting callFetchData*');
};
const watchCallFetchData = function* watchCallFetchData(action: IAction): IterableIterator<any> {
console.log('calling watchCallFetchData*');
yield* takeLatest(AppRedux.Action.FETCH_DATA, callFetchData, action)[Symbol.iterator];
console.log('exiting watchCallFetchData*');
};
export function* rootSaga(action: IAction): IterableIterator<any> {
console.log('calling rootSaga*');
const watcher = yield all([
fork(watchCallFetchData, action),
]);
console.log('exiting rootSaga*');
}
export default [
rootSaga,
];
My routes.ts file:
import { RouterState } from 'react-router';
import { ComponentCallback, errorLoading, loadModule } from 'app/routes';
import AppRedux from 'app/containers/App/reducers';
import { call, put } from 'redux-saga/effects';
path: '/somepath/:id',
async getComponent(nextState: RouterState, cb: ComponentCallback) {
try {
const renderRoute = loadModule(cb);
const [reducer, sagas, component] = await importModules();
const id = nextState.params.id;
const name = '';
const action = {
id,
name,
type: AppRedux.Action.FETCH_DATA,
};
console.log('routes.ts pre-put fetch_data');
const putResult = put(action);
console.log('routes.ts post-put fetch_data');
return renderRoute(component);
} catch (err) {
errorLoading(cb)(err);
}
},
My app.tsx file:
import * as React from 'react';
import * as ReactRouter from 'react-router';
import { connect, DispatchProp } from 'react-redux';
import AppRedux from 'app/containers/App/reducers';
import { createStructuredSelector } from 'reselect';
import { selectid, selectResponses, selectname } from 'app/containers/App/selectors';
import { ISection } from './data';
export class App extends React.Component<IProps, {}> {
constructor(props: Readonly<IProps>) {
super(props);
console.log('app-constructor');
}
public render() {
console.log('app-render');
return (<div className="app" />);
}
}
export default connect<{}, {}, IProps>(
createStructuredSelector({
id: selectId(),
data: selectData(),
name: selectName(),
}),
(dispatch) => ({
fetchData: (id: string, name: string) => dispatch(AppRedux.fetchData(id, name)),
dispatch,
}),
)(App);
Here is the output from the console.log
calls:
calling rootSaga*
calling watchCallFetchData*
exiting watchCallFetchData*
exiting rootSaga*
routes.ts pre-put fetch_data
routes.ts post-put fetch_data
app-constructor
app-render
EDIT: Clarification
What I expect to happen:
put(action)
to dispatch the saga action and for Redux-Saga to actually do something.callFetchData
to be called, and like-wise, for it to call fetchData
.exiting watchCallFetchData*
in the console.log
.What's actually happening:
rootSaga*
function gets called.watchCallFetchData*
gets called twice? (Assumption based on what the yield
statements are supposed to do).rootSaga*
function gets called again. (Assumption based on what the yield
statements are supposed to do).Upvotes: 0
Views: 4264
Reputation: 1569
I can see 2 issues:
1. Use yield
instead of yield*
: You should be using yield
without the *
(superstar) in:
yield* takeLatest(AppRedux.Action.FETCH_DATA,...
yield*
is a spread operator that simply takes all the calls inside
a subroutine and expands them into the macro-function yield* docs. It's not used
with takeLatest
.
2. Don't pass action
as parameter: The action parameter is passed automatically, so it should just be:
yield* takeLatest(AppRedux.Action.FETCH_DATA, callFetchData)
As action
is not passed to rootSaga() the argument would be undefined
. When takeLatest
will try to automatically pass the action
parameter to callFetchData, it will append it to already existing list of arguments (see the Notes section in takeLatest docs) & then callFetchData will get the real action as the 2nd parameter, which the function isn't expecting, so it won't get passed.
A suggestion: yield all()
internally runs all calls in parallel, so we should use call
instead of fork
inside it:
const watcher = yield all([
call(watchCallFetchData, action),
]);
Difference between takeLatest()
& while(take())
: the take
in while loop is synchronous and blocking, so it doesn't move to the next line until it sees that a desired action is dispatched. It pauses the whole saga. Whereas, takeLatest()
forks an asynchronous non-blocking thread to wait for the desired action, & keeps executing the next lines of the saga.
Upvotes: 2
Reputation: 14501
The changes suggested by @Ali Saeed look correct to me, but I believe that the main issue is your usage of put
inside of routes.ts
.
Put only works inside of a generator (saga) run by redux-saga.
In your routes.ts file
const putResult = put(action);
That line does nothing. If you look at the return value from that you'll see that it simply returns an object.
You want to do
store.dispatch(action);
Based on your code, the store does not seem available, so you'll have to update that too.
Upvotes: 0