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.
- A component calls an action creator
- The action creator then emits an action using a specific type
- The watcher sagas all listen for any actions emitted and intercept an action that it is listening for. It then calls the appropriate worker saga.
- The worker saga makes an API call and dispatches an action to the reducers with the type of action and the payload.
- The reducer listens for any dispatched actions and if it matches, it then uses the supplied data to update the state in the store.
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.