I'm using react-router v4 and redux-saga. I'm attempting to make an API call when a page loads. When I visit /detailpage/slug/
, my application seems to get stuck in a loop and makes endless calls to my API endpoint. Here's how my application is setup. Assume my imports are correct.
index.js
const history = createHistory()
const sagaMiddleware = createSagaMiddleware()
const middleware = [routerMiddleware(history), sagaMiddleware]
const store = createStore(
combineReducers({
aReducer
}),
applyMiddleware(...middleware)
)
injectTapEventPlugin();
sagaMiddleware.run(rootSaga)
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/detailpage/:slug" component={Detail} />
<Route path="/page" component={Page} />
</Switch>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
reducers/index.js
const aReducer = (state={}, action) => {
switch(action.type) {
case 'SHOW_DETAIL':
console.log('Reducers: reducer called')
return Object.assign({}, state, { name: 'adfsdf' })
default:
return state;
}
}
export default aReducer;
actions/index.js
export const showDetailInfo = () => {
console.log('action called')
return {
type: 'SHOW_DETAIL'
}
}
saga.js
export function* fetchDetailsAsync() {
try {
console.log('fetching detail info')
const response = yield call(fetch, 'http://localhost:8000/object/1/', {
method: 'GET',
headers: {
'Authorization': 'Token xxxxxxxxxxxx'
}})
console.log(response);
yield put({type: 'SHOW_DETAIL', response: response.data})
} catch (e) {
console.log('error')
}
}
// watcher saga
export function* fetchDetails() {
console.log('watcher saga')
yield takeEvery('SHOW_DETAIL', fetchDetailsAsync)
}
export default function* rootSaga() {
console.log('root saga')
yield [
fetchDetails()
]
}
containers/Detail.js
const mapStateToProps = (state) => {
return {
name: 'Test'
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
console.log('mapDispatchToProps')
return {
showDetailInfo: (payload) => {
console.log('dispatching');
dispatch({ type: 'SHOW_DETAIL' })
}
}
}
const Detail = connect(
mapStateToProps,
mapDispatchToProps
)(DetailPage)
export default Detail;
components/DetailPage.js
class DetailPage extends React.Component {
componentWillMount() {
this.props.showDetailInfo();
}
render() {
return (
<Layout>
<h3>DetailPage</h3>
<p>{ this.props.name }</p>
</Layout>
)
}
}
DetailPage.PropTypes = {
showDetailInfo: PropTypes.func.isRequired,
name: PropTypes.string.isRequired
}
export default DetailPage;
I've spent a few days troubleshooting, trying various ideas including testing different lifecycle methods, removing the routerMiddleware from applyMiddleware.
I thought that my component was updating after every API call, but console.log from any of the lifecycle methods indicates that it's not.
Being new to react ecosystem, there are a lot of moving parts here and it's challenging for me to troubleshoot.
Of course, you explicitly set the infinite loop the next lines:
yield put({type: 'SHOW_DETAIL', response: response.data})
// ...
yield takeEvery('SHOW_DETAIL', fetchDetailsAsync)
The saga doesn't do any magic things for you, and only is a preliminary layer of a subscription and generation on actions and executions coroutines.
SOLUTION:
You shall use different names for actions which you catch from React components, and actions which are used for optimistical and real up-dating of a status.
Use yield takeEvery('SHOW_DETAIL_REQUEST', fetchDetailsAsync)
and name your action in this manner.
Use yield put({type: 'SHOW_DETAIL_SUCCESS', response: response.data})
in success response and name your reducer in this manner
More than that, you can use 'SHOW_DETAIL_FAILURE'
for failed saga request.
All names above are common-used case.
I know you found your answer, that's great. I had the same symptoms but a different problem and a different solution.
I am not using normal constants for my actions, I am using the constants from my actions so that I only need to write it in a single location. It's a setup I have. However I realised an issue today. My code looks like the following
export const deleteArendeAction = {
will: arende => ({
type: "WILL_TA_BORT_ARENDEN",
...arende,
}),
did: payload => ({
type: "DID_TA_BORT_ARENDEN",
...payload,
}),
error: payload => ({
type: "DID_DELETE_ARENDE_ERROR",
...payload,
}),
}
function* deleteArenden(arende) {
try {
yield arendeApi.deleteArende(arende.id)
} catch (error) {
yield put(deleteArendeAction.error(arende))
return
}
yield put(deleteArendeAction.did(arende))
}
export function* deleteArendeSaga() {
yield takeEvery(deleteArendeAction.will().type, deleteArenden)
}
My code looks something like that.
It kept triggering my takeEvery infinitely.
It turns out, yield put(deleteArendeAction.did(arende))
this part was the culprit. Because the variable arende
had the value of { type: "WILL_TA_BORT_ARENDEN", ... }
which caused some sort of bug, triggering the event again.
Technically, that shouldn't happen I think? But it did.
So if you come across this question and the answer doesn't solve your problem. Then double-check what you send into your put
:P
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With