Skip to content

Loading States

Sher Sheikh edited this page Apr 18, 2021 · 3 revisions

Adding loading states to the application

There are two loading states. One for the Host and one for the Player. The loading state is an array of strings, and the strings will let the application know what is loading. They will also give us better control over how we display loading indicators, since we can have multiple things loading and it won't just be a true or false value. If the loading array contains the string join-code, the JoinCode component will know that a new code is being fetched and that it should display a loading indicator in the meantime. The rest of the application can ignore that string.

The following section will walk you through the process of adding the loading state for the join code, and can serve as an example for other loading states in the application.

Example

Adding a loading state for the Join Code

When the Host clicks the SHUFFLE JOIN CODE button, the SHUFFLE_JOIN_CODE action is dispatched. Currently there is only a case for this in hostReducerMiddleware since state didn't need to be updated originally. Now that we're updating the loading array in state, let's add a case for this in HostReducer as well.

Inside HostReducer.js, we can add this to the switch statement:

case 'SHUFFLE_JOIN_CODE':
   return getJoinCode(state);

All we want getJoinCode to do is to return state with an updated loading array which contains a string that relates to what's loading. In this case it can be 'join-code'.

function getJoinCode(state) {
  return {
    ...state,
    loading: [...state.loading, 'join-code'],
  };
}

Now that 'join-code' is inside the loading array, we can use this to display a loading indicator inside of JoinCode.js


To make testing a little simpler, I won't import the HostContext directly into JoinCode.js. Instead, I'll pass down the loading array from state as a prop from JoinCode's parent components since they're already using the HostContext.

In this example we can just look at WinnerSelectScreen.js, inside the LeftPanel component. We can pass down state.loading to JoinCode.

<JoinCode loading={state.loading} code={lobbyID} />

And now inside of JoinCode.js, after updating propTypes to include the loading array, we can change this line:

      <p className="join-code" data-testid="join-code">
        {code}
      </p>

to this:

      <p className="join-code" data-testid="join-code">
        {loading.includes('join-code') ? <LoadingIndicator secondary /> : code}
      </p>

Now if the Host presses the SHUFFLE JOIN CODE button, we'll see the <LoadingIndicator /> component in place of the join code:


There's one last thing we need to do. Right now the loading indicator will be stuck there, playing forever. We need to remove the 'join-code' string from the loading array at some point. That would make most sense when the new join code is received and updated in state.

We can see that this happens inside of HostReducer.js in the updateJoinCode function. State is being updated here with the new join code:

function updateJoinCode(state, { lobbyID }) {
  return {
    ...state,
    lobbyID
  };
}

We can simply add another line which removes 'join-code' from the loading array:

function updateJoinCode(state, { lobbyID }) {
  return {
    ...state,
    lobbyID,
    loading: state.loading.filter(
      (loadingValue) => loadingValue !== 'join-code', // Right here
    ),
  };
}

So now we have:

  1. Hit the join code shuffling button and dispatch SHUFFLE_JOIN_CODE
  2. Add 'join-code' to the loading array in state
  3. Display a loading indicator in the JoinCode component while 'join-code' is in the loading array
  4. Remove 'join-code' from the loading array when the join code gets updated

Final Result:

Note: I added some code to disable the shuffle button while the join code is loading to prevent multiple clicks

Adding a loading state - Generalized

In this section I'll write out the steps to add a loading state for any part of the application. There may be some special cases, and I'll try to mention them all here, but this is a work in progress so it's possible I might miss something.

Steps

  1. Determine what you want to add a loading state for. This can be on the Player side or the Host side. It doesn't matter since these steps apply to both.
  2. Does the case that you want to add a loading state for already have an action being dispatched? (ex. the join code has the SHUFFLE_JOIN_CODE action being dispatched)
    • Yes? Go to the reducer (not reducerMiddleware) and check if there is a case for that action.
      • If there is: Go to the function that is handling this case. It's likely updating state. Edit the function to add a new string to the loading array in state (this string should be related to what's loading, like getting-deck or submitting-cards). Make sure not to replace the whole loading array. It should be something like loading: [...state.loading, 'your-string']
      • If there isn't: Create a new case and function for this action, and make the function update state to add a new string to the loading array. See above for details.
    • No? Dispatch a new action for this case, add this case to the reducer, create a function for this case, and make the function return state with an updated loading array with your new string
  3. Find the case where state gets updated with whatever was loading. In the join code example it's UPDATE_JOIN_CODE.
  4. Edit the function for this case to remove your string from the loading array in state. Example:
       loading: state.loading.filter((loadingValue) => loadingValue !== 'your-string'),
  5. Go to the component where you want to indicate that something is loading and import the appropriate context if it isn't already
  6. Wherever you want to indicate that something is loading, add something along the lines of
       { state.loading.includes('your-string') ? show loader : show the loaded stuff }

And that's it


Special Cases

1. In this action, the deck GET is done in the same action as the SET happens, so there's no place for us to update the loading state to say the deck is being fetched.

    // hostReducerMiddleware.js

    case 'SET_DECK': {
      const deck = await getDeck(payload);
      return dispatch({
        type: 'SET_DECK',
        payload: { deck },
      });
    }

If we want to be able to show a loading indicator while the deck is being fetched, we can dispatch a new GET_DECK action before the SET_DECK action is dispatched. Inside the reducer we can add the case for GET_DECK and make a function that adds 'getting-deck' to the loading array. We can also add the case for SET_DECK inside the reducer (it's already inside the reducerMiddleware), and make sure 'getting-deck' is removed from the loading array when the deck is set.


2. In cases where loading is completed when Player receives an update message from the Host, we need a way to remove the correct loading state from the loading array. To do this, an optional removeLoading value can be added to the payload on the Host side before the message is sent.

Example: A player joins the lobby and the loading state is set to ['joining-lobby']. When the player successfully joins, the Host sends an update message to the player:

function sendPlayerConnectedMessage(payload) {
  socketInstance.sendMessage({
    event: 'update',
    recipients: [payload.playerId],
    payload: {
      gameState: 'connected',
      message: {
        big: "You've joined the lobby",
        small: 'Please wait for the host to start the game',
      },
    },
  });
}

If we add removeLoading: 'joining-lobby' to the payload, we can let the player reducer know that this value needs to be removed from the loading array. This is handled in the updated update function inside playerReducer.js:

function update(state, payload) {
  const { removeLoading, ...newData } = payload;
  return {
    ...state,
    ...newData,
    loading: removeLoading
      ? state.loading.filter((loadingVal) => loadingVal !== removeLoading)
      : state.loading,
  };
}

Now if there's a removeLoading value present, we know which value to remove from the loading array.


Diagrams

A diagram displaying the process of a user starting a game and shuffling the join code (getting-packs and join-code loading states at the same time)

full diagram

A generalized diagram

general diagram