Using React, why is redux saga not intercepting the action?
Asked Answered
G

1

6

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.

  1. A component calls an action creator
  2. The action creator then emits an action using a specific type
  3. 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.
  4. The worker saga makes an API call and dispatches an action to the reducers with the type of action and the payload.
  5. The reducer listens for any dispatched actions and if it matches, it then uses the supplied data to update the state in the store.


enter image description here


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.

Gast answered 23/10, 2017 at 4:2 Comment(0)
S
15

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.)

Stringendo answered 23/10, 2017 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.