Redux: Handle an Action in the Store - Redux Shortcuts?

Tell us what’s happening:
I’ve found the answer but don’t understand how it works. The ‘reducer’ function takes two parameters. The second parameter is ‘action’. This is somehow calls into the ‘reducer’ function the ‘loginAction’ function. How does the code know that ‘action’ means ‘loginAction’ if they have different names?

Likewise, we return ‘state’, but I don’t see ‘state’ defined anywhere. We’ve defined a constant called ‘defaultState’. Does Redux somehow automatically connect the two?

Your code so far


const defaultState = {
login: false
};

const reducer = (state = defaultState, action) => {
// change code below this line
if (action.type === 'LOGIN'){
return {login: true}
} else {return state}
// change code above this line
};

const store = Redux.createStore(reducer);

const loginAction = () => {
return {
  type: 'LOGIN'
}
};

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36.

Challenge: Handle an Action in the Store

Link to the challenge:
https://www.freecodecamp.org/learn/front-end-libraries/redux/handle-an-action-in-the-store

1 Like

Because the the function you called reducer is just a function. You can call parameters whatever you want. It could be function reducer (smjhkkjn, vfgnjfvkh) rather than function reducer(state, action).

But the second argument is going to be what’s called an action in Redux terminology.

An action is a plain object (with a type property + any number of other properties that can have any value as long as it it serialisable) that describes some event that occurred.

The action has to have a type field.

The purpose of the function is to check what that type field is and return a new state. “When the LOGIN event has happened the state should be…”.

The only way it “knows” is because you’ve written if (action.type === 'LOGIN'){.

No.

Redux basically doesn’t do anything automatically (it doesn’t in fact really “do” anything at all, it’s a container that provides specific patterns for interacting with whatever is stored in the container).

You’ve explicitly defied and returned the state:

You’ve defined a function called reducer which takes a parameter called state as the first argument.

This is what it looks like by default (and if any action except one with the type “LOGIN” happens):

const defaultState = {
  login: false
};

You’re returning a new state {login: true} when an action with the type “LOGIN” occurs:

if (action.type === 'LOGIN'){
return {login: true}
1 Like

Just for clarification, this is a minimal implementation of Redux’ createStore function:

function createStore (reducer) {
  // the state is stored here
  let state = undefined;

  // the getState function just returns the
  // current state
  const getState = () => state;

  // the dispatch function just replaces the
  // state with whatever the result of 
  // running the reducer function is
  const dispatch = (action) => {
    state = reducer(state, action);
    return action;
  };

  // run the reducer with a dummy
  // action to create the state
  dispatch ({ type: "SETUP_ACTION" });

  // expose the getState and
  // dispatch functions
  return {
    getState,
    dispatch
  };
}

This is basically Redux, the actual library is barely more than that function

So

You define your reducer. It has a default value for the state, so if you just call reducer() with either no arguments or something it doesn’t recognise, it’s going to return {login: false}.

When you call createStore, it runs dispatch with that dummy argument, which sets state to {login: false}, then returns dispatch and getStore.

So

const store = createStore(reducer);
// store is now an object with two functions
// attached, getStore and dispatch.
// The state is stored in the closure
store.getStore() // {login: false}
store.dispatch(loginAction())
store.getStore() // {login: true}

Thanks so much for the comprehensive answer. Am I right in thinking then, that the following reducer function will search all objects available for a key:value of " type: ‘LOGIN’ " ?:

const reducer = (state = defaultState, action) => {
    // change code below this line
    if (action.type === "LOGIN") {
...

In the challenge we only have one object that matches type: ‘LOGIN’, so this seems logical to me. This would permit an infinite number of other object or actions within the code.

Yes, but bear in mind every single one of those has to be defined explicitly in the code. This is one of the great benefits of Redux, but also means it’s repetitive write the code and the code is quite verbose.

Here’s a slightly more realistic example. Still very naïve, as amongst other things it doesn’t clear error states, but it is a lot closer to how Redux is actually used:

const defaultAuthState = {
  isLoading: false,
  isAuthorised: false,
  isErrored: false,
  errorMsg: null,
  requiresPasswordReset: false,
};

reducer (state = defaultAuthState, action) {
  switch (action.type) {
    case "login-request":
      return {...state, isLoading: true };
    case "login-success":
      return {...state, isLoading: false, isAuthorised: true };
    case "login-error":
      return {...state, isErrored: true, errorMsg: action.error.messsage };
    case "requires-pword-reset":
       return {...state, requiresPasswordReset: true };
    case "password-reset-sent":
      return {...state, requiresPasswordReset: false };
  }
}

And bear in mind there are normally lots of reducer functions defined, “slices” of the overall state object that are joined together into one big reducer for the store. This allows you to specify at a granular level what affects specific actions have.

I get what you mean: basically yes. But careful with the terminology (this is JS, not Redux-specific). There is no searching going on – the function takes an object with a type property, and if that type matches it returns an object

1 Like

I’ve had a good think over all of this, and I think I have two problems: 1. I don’t understand much about redux (as anticipated), and 2. I don’t understand much about basic javascript (somewhat surprising given the time I’ve spent learning it!).

I get what you mean: basically yes. But careful with the terminology (this is JS, not Redux-specific). There is no searching going on – the function takes an object with a type property, and if that type matches it returns an object

So, is this standard javascript behaviour? Any function with the following code will search the rest of the code for any available object with a key/value of ‘type: ‘LOGIN’’?

if (action.type === 'LOGIN'){

I’m confused as to what is triggering what. I can see that the reducer function is being fired as soon as the code is being run. The if(action.type===‘LOGIN’) is then searching the rest of the code for an action, as per the ‘action’ parameter. But my understanding of javascript is that action could only be defined if we called a function, eg:

reducer (aStateParameter1, anAction1);

But we’re not doing that here. There is something javascript is performing that I don’t understand.

It’s not searching for anything, you are calling the reducer function (that you have written) with a parameter. That’s why I say it doesn’t “search” anywhere, because you are literally just calling a [simple] function with a very specific parameter.

const loginAction = { type: "LOGIN" }

reducer(state, action) {
  if (action.type === "LOGIN") {
    // do thing

// ...later, when `dispatch` gets called, which passes the
// `loginAction` to your `reducer` function, it runs:

reducer(the_current_state, {type: "LOGIN"})

So say you have an application which lets people write articles. And admins can log in, review the articles, and publish them. Your admin has logged in, and can see the articles they need to review, and maybe they have some notifications as well. The state of the app (what it looks like at a specific moment in time given a specific set of user actions) is kinda like

  • a user has logged in
  • their own personal details have been loaded
  • they have an authorisation level of level of “admin”
  • they have access to a list of posts wating for review
  • they have some unread notifications

You want to keep track of the state of an app (because it’s the most important thing about any given app). You could do it on an ad-hoc basis: when stuff happens, do other stuff. But this is error-prone and gets messy very very quickly. So one approach is to put all the important state in one place. You could model the state in JS using an object:

const state = {
  auth: {
    isLoggedIn: true,
  },
  user: {
    authLevel: "admin",
    name: "Amy the Admin",
    email: "amytheadmin@example.com",
  },
  postsToReview:[
    "696e3f62-57ca-11ea-82b4-0242ac130003",
    "696e4174-57ca-11ea-82b4-0242ac130003",
  ],
 notifications: [
    { id: "bd5b65dc-57ca-11ea-8e2d-0242ac130003", type: "FLAGGED_POST" },
  ],
}

Now, you can make that available all through your app. Like, literally just have the state object with all possible default values as the first thing in your code, then have the rest of your code read from it and modify it. This is a bit difficult though:

  • you need to set up getters and setters for everything
  • what happens when a value updates? How do you make sure the UI understands it
  • how do you stop multiple things modifying it at the same time, or accidentally deleting bits of the state?
  • how do you debug it?
  • etc etc

So what Redux does is provide a safe interface for this. You have that object that represents your state at any one point in time. But you cannot modify the state directly. It is stored in another object (the “store”), and the only way to change the state is by sending messages (“actions”) to that store. You define what those messages are, and you also define a set of functions (reducers) that all work the same way: given a message of a specific type, blow the entire state away and return a new state with some value or values that are different to the previous state.

So

  • The state is basically always a plain object. It can be a single primitive value or a single array or whatever – eg it could be a single number. But because Redux is designed for complex state, you’re unlikely to ever see this in practice outside of the very simplest of tutorial examples.
  • To create a new state based on the previous state, you write a function which accepts two parameters. First, the current state. Second a plain object with a type property (and optionally any number of other properties). If the value of type passed to the function matches what you want, return a new state, based on the current state (normally exactly the same shape but with some of the values modified).
  • to keep hold of the current state, you use the function Redux provides (createStore), to which you pass your reducer. That looks like as I described – internally, it keeps hold of the current state, and returns some functions for reading and updating (if that didn’t exist, to keep updating the state you’d have to keep writing state = reducer(state, {type: "SOME_ACTION"}) all through your app which would sorta defeat the point of keeping it all in one place).

The thing I missed off the createStore example is that it allows you to subscribe to updates to the store. The is another function, addListener, which lets you add listeners, which just needs to be functions that take no arguments and do something. Every
time I update the store, every listener runs.

function createStore(reducer) {
  let state;
  let listeners = [];

  return {
    getState: () => {
      return state;
    },
    dispatch: (action) => {
      state = reducer(state, action);
      listeners.forEach((listener) => listener());
      return action;
    },
    addListener: (listener) => {
      listeners.push(listener);
    },
  };
}

So say you have the example from my first post. You have the reducer, and the login action, and you set up the store. But you also define two listener functions:

const store = createStore(reducer);
// store is now an object with three functions
// attached, getStore, addListener and dispatch.

// I'll define a couple of listener functions
const stateLogger  = () => console.log(store.getState());
const sayHi = () => console.log("HI!");

Now:

> store.addListener(stateLogger)
> store.addListener(sayHi)
> store.dispatch(loginAction())
{ login: true }
HI!
> store.dispatch({ type: "WHATEVER"})
{ login: false }
HI!
> store.dispatch(loginAction())
{ login: true }
HI!

This is when it becomes more useful – libraries for SPA frameworks (for example react-redux) use the addListeners function to connect components up to the state. When a component is connected, it subscribes to the store. Every time you dispatch, the listener function fires, which will update the component’s props.

1 Like

I had the same problem(or question) for the second argument. I couldn’t understand how the argument ‘action’ knows how to get the ‘type’ property from ‘loginAction’. As you tell ‘state’ that it needs to get the state from ‘defaultState’ there must be something in the background telling ‘action’ to search for an object with a type property and a value ‘LOGIN’. Otherwise, it wouldn’t know where to get it the information from.

||

The function says "okay I have 2 parameters and the second one is an action parameter. If you see an action(like real action so that it would trigger the if statement) with property/value pair of type: ‘LOGIN’ , do this and that bla bla. So, the reducer function waits for an action to happen and than checks if the type/value pair matches with it’s if statement. Kind of makes sense but I don’t know. May be I am just imagining things.

I think the beginning of the challenge answers it. It says

After an action is created and dispatched, the Redux store needs to know how to respond to that action. This is the job of a reducer function

I think dispatch() is being performed behind the scenes here.

I also think, dispatch() makes the action loginAction available for the reducer function by somehow assigning it to a variable called action under the hood or something.

No. This is not magic, there isn’t anything “under the hood”, everything is out in the open.

“State” is just some values you would like kept in one place in an app that are probably things that users of the app will change during use of the app. JavaScript provides a useful data structure that would be useful for this, and object:

{
  loggedIn: false
}

This is an action:

{
  type: "login",
}

Another one:

{
  type: "logout"
}

Those are just objects. They aren’t magically created, you need to define them. There isn’t anything special about them except that they must have a type property. They can have any other properties you want.

You want to update state by using an action. Because state is a simple object and an action is a very simple object, it’s very easy to write a function that does this.

const defaultState = {
  loggedIn: false,
}

function session (state = defaultState, action) {
  if (action.type === "login") {
    return { loggedIn: true }
  } else if (action.type === "logout") {
    return { loggedIn: false }
  } else {
    return defaultState;
  }
}

This is a function that takes the state and an action and returns a new state based on the action.

You need to store the state object somewhere, so use another function:

function createStore (reducer) {
  let currentState;

  return {
    getState () {
      return currentState;
    },
    dispatch (action) {
      currentState = reducer(currentState, action);
    },
  }
}

Then now:

> const store = createStore(session);
> store.getState()
{ loggedIn: false }
> store.dispatch({ type: "login" })
> store.getState()
{ loggedIn: true }
> store.dispatch({ type: "logout" })
> store.getState()
{ loggedIn: false }

I think I understand (after may edits to this comment) that createStore method contains an object with a bunch of methods already attached in its return statement. My solution actually looks like this.

const defaultState = {
  login: false
};

const reducer = (state = defaultState, action) => {
  // Change code below this line
  return (action.type === "LOGIN") ? {login: true} : state;
  // Change code above this line
};

const store = Redux.createStore(reducer);

const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};

so if any dispatch is performed after this to the store with the loginAction action creator, then reducer returns {login: true} as the new state. Am I right? If I am that was actually what I was naively alluding to when I said “Under The Hood”.