I'm very new to redux-saga and am trying to get a simple demo working that makes an API call and sends the response data to a reducer so it can be saved to the store. It is my understanding that the redux-saga flow should work as follows.
I have laid out my code to follow that flow but things aren't working quite right. Let me show my code and then i'll elaborate on the problem.
components/PostsIndex.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions';
class PostsIndex extends Component {
componentDidMount() {
this.props.fetchPosts();
}
renderPosts() {
console.log(this.props.posts);
}
render() {
return (
<div>
{this.renderPosts()}
</div>
);
}
}
const mapStateToProps = state => ({
posts: state.posts
});
export default connect(mapStateToProps, { fetchPosts })(PostsIndex);
actions/index.js
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const fetchPosts = () => {
console.log('fetchPosts() in actions');
return {
type: FETCH_POSTS
};
};
sagas/index.js
import 'regenerator-runtime/runtime';
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
import axios from 'axios';
import { FETCH_POSTS } from '../actions';
const ROOT_URL = 'https://reduxblog.herokuapp.com/api';
const API_KEY = '?key=asdsd1234';
// Watcher sagas
// Listen for an action and run the appropriate Worker saga
export function* watchFetchPosts() {
yield takeEvery(FETCH_POSTS, workFetchPosts);
}
// Worker sagas
// Respond to the actions that are caught by the watcher sagas
export function* workFetchPosts() {
try {
console.log('workFetchPosts() in sagas');
// Try to call the API
console.log('Attempting to call the posts API');
const uri = `${ROOT_URL}/posts${API_KEY}`;
const response = yield call(axios.get, uri);
console.log('Fetched Posts Response in saga worker: ', response);
yield put({
type: FETCH_POSTS,
payload: response
});
} catch (error) {
// Act on the error
console.log('Request failed! Could not fetch posts.');
console.log(error);
}
}
// Root sagas
// Single entry point to start all sagas at once
export default function* rootSaga() {
console.log('redux saga is running');
yield [watchFetchPosts()];
}
reducers/PostsReducer.js
import { mapKeys } from 'lodash';
import { FETCH_POSTS } from '../actions';
export default (state = {}, action) => {
switch (action.type) {
case FETCH_POSTS:
console.log(action);
// Create a new state object that uses an AJAX request response and grabs the 'id' property from each object in the response to use as its key
return mapKeys(action.payload.data, 'id');
}
return state;
};
It seems like the reducers are still picking up the emitted actions, which is wrong. I did notice that if I run the AJAX call in the action creator as well, then the saga will run, but the saga should intercept the communication between the action creator and the reducer, so something isn't set up quite right. Any ideas?
A full environment of my workflow can be edited at https://stackblitz.com/edit/react-redux-saga-demo. It might be easier to see the problem there.
Redux Saga is a middleware library used to allow a Redux store to interact with resources outside of itself asynchronously. This includes making HTTP requests to external services, accessing browser storage, and executing I/O operations. These operations are also known as side effects.
Create a plain JavaScript Object to instruct the middleware that we need to dispatch some action, and let the middleware perform the real dispatch. This way we can test the Generator's dispatch in the same way: by inspecting the yielded Effect and making sure it contains the correct instructions.
Such a powerful & elegant tool as Redux-Saga, a Redux side effect manager, is said to be deprecated, and no longer being maintained, starting from Jan 27, 2021.
A Redux app really only has one reducer function: the "root reducer" function that you will pass to createStore later on. That one root reducer function is responsible for handling all of the actions that are dispatched, and calculating what the entire new state result should be every time.
Sagas do not stop actions from reaching the reducers. The saga middleware explicitly does the equivalent of:
next(action); // pass the action onwards to the reducers
processSagas(action);
So, the reducers will always see an action first, and the saga behavior will be excecuted after that.
The other issue is that it looks like you're trying to use the same action type to trigger the fetch behavior in the saga, and process the results in the reducer. I find that if you're using sagas, you generally have some actions that are "signals" meant to trigger saga behavior, and others that are intended to actually be handled by the reducers and update state. So, in your case, I would suggest using "FETCH_POSTS"
as the signal to kick off the fetching, and have the saga then dispatch "FETCH_POSTS_SUCCESS"
once the data is received and have the reducer respond to that action instead. (And, after noticing that you had that StackBlitz example up, I confirmed that just dispatching the results as "FETCH_POSTS_SUCCESS"
does indeed work as I'd expect it too.)
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