-
Notifications
You must be signed in to change notification settings - Fork 2
Loading States
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.
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:
- Hit the join code shuffling button and dispatch
SHUFFLE_JOIN_CODE
- Add
'join-code'
to the loading array in state - Display a loading indicator in the
JoinCode
component while'join-code'
is in the loading array - 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
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.
- 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.
- 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
orsubmitting-cards
). Make sure not to replace the whole loading array. It should be something likeloading: [...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.
- 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
-
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
-
Yes? Go to the reducer (not reducerMiddleware) and check if there is a case for that action.
- Find the case where state gets updated with whatever was loading. In the join code example it's
UPDATE_JOIN_CODE
. - 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'),
- Go to the component where you want to indicate that something is loading and import the appropriate context if it isn't already
- 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
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.
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)
A generalized diagram