Mr.Pe
Mr.Pe

Reputation: 739

Nested reducers

To learn Redux, I am starting a simplistic web application.

If there is an "authentification" action validating username and password. I then want a "login" or "fail" action to be called. I can not figure out how. I can call the authentification step, but the nested/following call are not executed.

How is it possible to call a function from within another function? (That's how frustrating my problem boils down ^^)

My code looks like the following:

function authenticateClient(state, client){
    if (client.password === 'perfectsec')
        return dispatch(Actions.connectClient(client));
    else
        return dispatch(Actions.refuseClient(client));
}

Calling dispatch in a reducer is incorrect. The simplest would be to call directly the functions connectClient(state, client) or refuseClient(state, client). But then this step does not go through the redux flow.


context code:

/* actions.js */

export function authenticateClient(client) {
  return { type: Types.authenticateClient, client };
}

export function connectClient(client, isNew) {
  return { type: Types.connectClient, client, isNew };
}

export function refuseClient(client) {
  return { type: Types.refuseClient, client };
}


/* main.js */

/* reducer */
const reducer = (state = loadState(), action) => {
  console.log(chalk.red(`*${action.type}*`));
  switch (action.type) {
    case Actions.Types.authenticateClient:
      return authenticateClient(state, action);
    case Actions.Types.connectClient:
      return connectClient(state, action);
    case Actions.Types.refuseClient:
      return refuseClient(state, action);
    default:
      return state;
  }
};


/* store */
store = createStore(reducer);


/* app */
store.dispatch(Actions.authenticateClient({username, password}));

Upvotes: 0

Views: 325

Answers (1)

Joe Clay
Joe Clay

Reputation: 35797

Reducers should never dispatch actions, or do anything else that has side effects. All a reducer should ever be is a pure function of state and actions (effectively state + action = newState) - the entire Redux library is built around this assumption, and I believe it's specifically designed to throw an error if you try to call dispatch inside a reducer.

The best way to go about conditionally dispatching an action is to create a new 'action creator' - you have a few of these in your application already:

export function authenticateClient(client) {
  return { type: Types.authenticateClient, client };
}

export function connectClient(client, isNew) {
  return { type: Types.connectClient, client, isNew };
}

export function refuseClient(client) {
  return { type: Types.refuseClient, client };
}

Note that standard action creators don't dispatch the action themselves. They're just functions that return an action, which you can dispatch yourself at some point (this is handy if you want to create an action and hold on to it for a little while before actually sending it off). The waters get muddied somewhat once you start writing async action creators, but that's not really in the scope of your problem so I'll skip over it for now (I recommend reading the Async section of the documentation, though! You can do some really cool things once you get your head around it).

What you're trying to express here is a function that says, 'if a condition is true, give me a connectClient action, if not, give me a refuseClient action'. We already know that a function that gives you an action is an action creator, which leads us to the main point of this answer: action creators can have logic. While 90% of them will just take in data and immediately return an object, this isn't the only form they can take. Here's an example of how this functionality could be represented:

export function connectClientIfValid(client) {
  if (client.valid === true) {
    return connectClient(client);
  }
  else {
    return refuseClient(client);
  }
}

Again, nothing actually gets dispatched from this function - it just returns an action, which you can dispatch at your leisure, like so:

dispatch(connectClientIfValid(client));

Your logic runs, and the appropriate action gets dispatched, without having to compromise the pure/synchronous nature of your reducer. All your reducer has to do is read the actions as they come in, and update the state appropriately - it doesn't have to care about the logic that caused the actions to happen.

Upvotes: 2

Related Questions