Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 379
zeckdude Avatar asked Oct 23 '17 04:10

zeckdude


People also ask

How do Redux sagas work?

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.

How do you dispatch actions in Saga?

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.

Is Redux saga still used?

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.

Who is responsible for dispatching the action in Redux?

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.


1 Answers

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

like image 80
markerikson Avatar answered Nov 14 '22 23:11

markerikson