Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot call one action from another in a React-Redux app

I have a login modal. I use an action creator to turn isLoginModalVisible: true in the reducers. Calling the action creator inside a container, inside a component's onClick, like so:

<TextButtonRight onClick={()=> this.props.showLoginModal(true)}> 

Inside the login modal, I have a login button that submits a form. I want the modal to disappear when 200OK otherwise, I display an error message in the modal. calling this action creator on login button click:

export function logInUser(){
    console.log("Actions--> logInUser");
    //Imagine I got a 200OK
    dispatch(showLoginModal(false));

    return({
        type: UPDATE_USER_LOGGED_IN,
        payload: true
    });
}

I'm calling the action creator to hide the modal from logInUser action creator and it's not working. The showLoginModal action creator does get hit but it does not dispatch to a reducer.

Here's the showLoginModal action creator:

export function showLoginModal(bool){
    console.log("Actions--> showLoginModal:"+bool);
    return({
        type: UPDATE_LOGIN_MODAL_VISIBLE,
        payload: bool
    });
}

(I'm sorry if I'm making a rookie mistake)

Edit:

part of my reducer.It does work because that's how I show the login modal. I pass false when I want to hide it. There's no problem with the payload either. The console.log isn't getting get when I call it via another action creator. works okay when I pass it to onClick on submit button this way: onClick={()=> this.props.showLoginModal(false)}

case UPDATE_LOGIN_MODAL_VISIBLE:
            console.log("REDUCER-->"+UPDATE_LOGIN_MODAL_VISIBLE);
            return {...state, loginModalVisible: action.payload};
            break;

CombineReducers:

const rootReducer = combineReducers({
    posts: postsReducer, 
    displayComps: displayComponentsReducer,
    form: formReducer,
    searchForm: searchFormReducer
});

Edit 2:

Sorry, I should have mentioned that I am using thunk. I wanted to know if there was a way to call an action from this one, like so:

req.then((response)=>{
    console.log("REQ_COMPLETE");
    dispatch({type: UPDATE_USER_LOGGED_IN, payload: true});
    //like so:
    showLoginModal(false);
    localStorage.setItem('userLoggedIn', true);
 })

instead of adding this:

dispatch({type: UPDATE_LOGIN_MODAL_VISIBLE, payload: false});

dispatching the action from another action creator seemed kinda hacky.

like image 555
ishan Vadwala Avatar asked Apr 08 '17 01:04

ishan Vadwala


2 Answers

Clarifying some terminology might help. Apologies in advance if this is stuff you already know.

When you want to dispatch multiple actions with redux-thunk, you dispatch an operation. That's a common pattern. It looks like your logInUser() function is an operation. For the canonical example, see fetchPostsIfNeeded() in Dan Abramov's documentation here.

An action creator simply returns an object and does nothing. An operation does stuff (often async) and then dispatches actions, which it generally builds by calling action creators. There's nothing hacky about dispatching actions from an operation.

However, calling any function from an action creator is not recommended. Action creators should return a simple object and have no side effects, like your showLoginModal() function. Note that there is NO BEHAVIORAL DIFFERENCE between calling an action creator and creating an object manually. So doing this:

var action = { type: UPDATE_LOGIN_MODAL_VISIBLE, payload: false }

is the same as

var action = showLoginModal(false)

Critical to note: Neither of these pieces of code does anything! They create an object but don't use it for anything. Nothing will happen in your app until you dispatch(action).

Both action creators and operations must be dispatch()ed.

  • The operation must be dispatched because that's how its inner function gets passed the dispatch function variable that it in turn uses to dispatch actions.
  • Actions must be dispatched because they have no functionality of their own; they are just simple objects. Only when they are dispatched to the Redux store do they affect the store state.

It looks like this code of yours is calling an action creator but not dispatching it:

showLoginModal(false);

Changing that to

dispatch(showLoginModal(false));

might fix your issue.

It can be helpful to name action creators in a way that will help anyone reading the code (including you) to see/remember that they just create an action. For example you could rename showLoginModal to showLoginModalAction or even createShowLoginModalAction.

The logInUser() function is an operation (since it dispatches an action) that, for no clear reason, also returns an action. The action it returns is never dispatched to the store, at least in the code that is posted, so it would have no effect. Probably your intention is to dispatch that action to the store, so why not dispatch it right there?

logInUser(){
  dispatch( sendingLoginRequestAction() );
  someAsyncLoginFuncThatReturnsAPromise(userName, password)
  .then(result=>{
    dispatch( loginSucceededAction(result) );
    dispatch( hideLoginModalAction() );
  });
}

Those action creators would look like:

sendingLoginRequestAction() { return { type: UPDATE_SENDING_LOGIN_REQUEST }; } // You don't have anything like this, but it could be used to show a Waiting animation

loginSucceededAction() { return { type: UPDATE_USER_LOGGED_IN, payload: true }; }

hideDialogModalAction { return { type: UPDATE_LOGIN_MODAL_VISIBLE, payload: false }; }

Does that help?

like image 94
stone Avatar answered Oct 21 '22 15:10

stone


I think part of your problem is that you are not using Redux Middleware. Here is a good tutorial from redux's creator himself: Building React Applications with Idiomatic Redux

For your purposes, if you incorporate Redux Thunk, you should be able to dispatch multiple actions from one action creator.

Here's how you can do it:

install: npm install --save redux-thunk

put this in your store.js file

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

Now, your action creator will have dispatch and getState available.

export const showLoginModal = (bool) => {
    console.log("Actions--> showLoginModal:"+bool);
    return({
        type: UPDATE_LOGIN_MODAL_VISIBLE,
        payload: bool
    });
}

export const loginUser = () => (dispatch, getState) => {
  console.log("Actions--> logInUser");
  // you have complete state available as getState(), but this is
  // not a good design pattern, so use with caution
  //Imagine I got a 200OK
  dispatch(showLoginModal(false));
  dispatch({
        type: UPDATE_USER_LOGGED_IN,
        payload: true
    });
}

I hope it helps. Have fun hacking.

like image 35
shobhit1 Avatar answered Oct 21 '22 15:10

shobhit1