Matt
Matt

Reputation: 8942

Redux saga function is not being called

I tried to use redux-saga in my React project, but redux-saga function is not being called. toggleLoginModal action in access.actions.js is called, but toggleLoginModal in access.sagas.js is not called.

Header.jsx

import React, { Component } from 'react';
import { withRouter } from 'react-router';
import accessActions from 'actions/access.actions';
import { connect } from 'react-redux';
import './Header.scss';

const { toggleLoginModal, toggleRegisterModal } = accessActions;

class Header extends Component {
  handleLoginClick = () => {
    toggleLoginModal(true);
  };

  render() {
    return (
        <div className="navbar-right">
            <button type="button" className="btn btn-link" onClick={this.handleLoginClick}>Log in</button>
            <button type="button" className="btn btn-primary">Register</button>
        </div>
    );
  }
}

export default withRouter(connect(null, { toggleLoginModal, toggleRegisterModal })(Header));

access.action.js

const actions = {
  TOGGLE_LOGIN_MODAL: 'TOGGLE_LOGIN_MODAL',
  TOGGLE_LOGIN_MODAL_RETURN: 'TOGGLE_LOGIN_MODAL_RETURN',
  toggleLoginModal: newState => ({
    type: actions.TOGGLE_LOGIN_MODAL,
    state: newState,
  }),
  toggleLoginModalReturn: (newState) => {
    return (dispatch) => {
      dispatch({
        type: actions.TOGGLE_LOGIN_MODAL_RETURN,
        newState,
      });
    };
  },
};
export default actions;

access.sagas.js

import { all, takeEvery, put } from 'redux-saga/effects';
import actions from 'actions/access.actions';

export function* toggleLoginModal({ state }) {
  yield put(actions.toggleLoginModalReturn(state));
}

export default function* rootSaga() {
  yield all([
    takeEvery(actions.TOGGLE_LOGIN_MODAL, toggleLoginModal),
  ]);
}

access.reducers.js

export function toggleModals(state = { login: false, register: false }, action) {
  switch (action.type) {
    case 'TOGGLE_LOGIN_MODAL_RETURN':
      return { login: action.newState };
    default:
      return state;
  }
}

reducers.js

import { toggleModals } from 'state/access.reducers.js';

export default {
  toggleModals,
};

sagas.js

import { all } from 'redux-saga/effects';
import accessSagas from 'sagas/access.sagas';

export default function* rootSaga() {
  yield all([accessSagas()]);
}

index.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import createHistory from 'history/createBrowserHistory';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
import { Provider } from 'react-redux';
import createSagaMiddleware from 'redux-saga';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';

import App from 'App';
import reducers from 'reducers';
import sagas from 'sagas';

const history = createHistory();
const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer,
  }),
  composeWithDevTools(applyMiddleware(
    routerMiddleware(history),
    sagaMiddleware,
  )),
);

sagaMiddleware.run(sagas);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('app'),
);

package.json

"dependencies": {
    "bootstrap": "^4.1.1",
    "jquery": "^3.3.1",
    "js-cookie": "^2.2.0",
    "lodash": "^4.17.10",
    "moment": "^2.22.1",
    "popper.js": "^1.14.3",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-loadable": "^5.4.0",
    "react-modal": "^3.4.4",
    "react-redux": "^5.0.7",
    "react-router": "^4.2.0",
    "react-router-dom": "^4.2.2",
    "react-router-redux": "^5.0.0-alpha.9",
    "react-tippy": "^1.2.2",
    "react-toastify": "^4.0.1",
    "redux": "^4.0.0",
    "redux-saga": "^0.16.0"
  },

Upvotes: 3

Views: 10629

Answers (3)

Animesh Singh
Animesh Singh

Reputation: 9282

Just dropping this in case someone finds it useful. I was using connect from react-redux to bind mapStateToProps and mapDispatchToProps.

For some reason when I was using mapDispatchToProps as a function, the actions dispatched was never able to reach the saga listener. All I had to do was shift the variable from the function to a simple object instead.

FROM

const mapDispatchToProps = (dispatch) => ({
  onChange: (settings) => saveUserSettings(settings),
  fetchDataOnMount: getUserSettings,
});

export default connect(mapStateToProps, mapDispatchToProps)(Settings);

TO :

const mapStateToProps = (state) => ({
  userSettings: selectUserSettings(state),
  userSettingsStatus: selectUserSettingsStatus(state),
});

const mapDispatchToProps = {
  onChange: (settings) => saveUserSettings(settings),
  fetchDataOnMount: getUserSettings,
};

export default connect(mapStateToProps, mapDispatchToProps)(Settings);

while my action file exporting:

export const getUserSettings = () => ({type: actions.FETCH_USER_SETTINGS});

export const saveUserSettings = (settings) => ({type: actions.SAVE_USER_SETTINGS, settings});

I realized there was no necessity for having a function defined for mapDispatchToProps as I was not using the dispatch parameter on the function. Moreover, each field of my returning object was utilizing the action creators directly and in this case, we can Object Shorthand form where:

React-Redux binds the dispatch of your store to each of the action creators using bindActionCreators.

Read more about defining mapDispatchToProps as an object.

Upvotes: 0

maham.shahid
maham.shahid

Reputation: 301

Just leaving this here because it might help someone. I came across an issue when configuring sagas for a project where the saga would not trigger even though everything was configured and set up just right. The console didn't throw any errors either. But the actual problem was that when defining the default values for the state, where I was supposed to give default values for objects, I just passed null instead of constructing whole objects with the properties (lazy me).

So that actually causes errors where if you are trying to access properties from those objects but you pass the initial value as null, you'll be trying to access properties from null - which would mess up the project. And in some cases, it throws errors and some cases it doesn't. So this was the reason that the saga wasn't working correctly either.

Upvotes: 1

Martin Kadlec
Martin Kadlec

Reputation: 4975

There are two problems.

First you need to use toggleLoginModal from props - as it is wrapped in dispatch thanks to connect. To cite https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options

[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.

So in Header.jsx use:

handleLoginClick = () => {
    this.props.toggleLoginModal(true);
};

Second problem is that you use toggleLoginModalReturn as a thunk without having thunk middleware. Add redux-thunk to your list of middlewares.

Upvotes: 3

Related Questions