I'm having issue where hook is being used in multiple files and it is being called twice for useEffect before the 1st one's async method finish (which should block the 2nd hook call, but it's not). See below 2 scenarios:
Stack Navigator
const { context, state } = useLobby(); // Hook is called here 1st, which will do the initial render and checks
return (
<LobbyContext.Provider value={context}>
<LobbyStack.Navigator>
{state.roomId
? <LobbyStack.Screen name="Lobby" component={LobbyScreen} />
: <LobbyStack.Screen name="Queue" component={QueueScreen} />
}
</LobbyStack.Navigator>
</LobbyContext.Provider>
)
Lobby Hooks
export const useLobby = () => {
const [state, dispatch] = React.useReducer(...)
//
// Scenario 1
// This get called twice (adds user to room twice)
//
React.useEffect(() => {
if (!state.isActive) assignRoom();
}, [state.isActive])
const assignRoom = async () => {
// dispatch room id
}
const context = React.useMemo(() => ({
join: () => { assignRoom(); }
})
}
Queue Screen
const { context, state } = useLobby(); // Hook is called here 2nd right after checking state from stack navigator
//
// Scenario 2
// Only does it once, however after state is changed to active
// the stack navigator didn't get re-render like it did in Scenario 1
//
React.useEffect(() => {
roomLobby.join();
}, []);
return (
...
{state.isActive
? "Show the room Id"
: "Check again"
...
)
In scenario 1, I guess while 1st hook is called and useEffect is doing async to add user to the room and set active to true. Meanwhile the conditional render part is moving straight to Queue screen which calls the hook again and doing the useEffect (since 1st haven't finished and isActive is still false).
How can I properly setup useReducer and useMemo so that it renders the screen base on the state.
Edited codes based on the answer
/* LobbyProvider */
const LobbyContext = React.createContext();
const lobbyReducer = (state, action) => {
switch (action.type) {
case 'SET_LOBBY':
return {
...state,
isActive: action.active,
lobby: action.lobby
};
case 'SET_ROOM':
return {
...state,
isQueued: action.queue,
roomId: action.roomId,
};
default:
return state;
}
}
const LobbyProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(lobbyReducer, initialState);
React.useEffect(() => {
console.log("Provider:", state)
if (!state.isActive) joinRoom();
}, [])
// Using Firebase functions
const joinRoom = async () => {
try {
const response = await functions().httpsCallable('getActiveLobby')();
if (response) {
dispatch({ type: 'SET_LOBBY', active: true, lobby: response.data })
const room = await functions().httpsCallable('assignRoom')({ id: response.data.id });
dispatch({ type: 'SET_ROOM', queue: false, roomId: room.data.id })
}
} catch (e) {
console.error(e);
}
}
return (
<LobbyContext.Provider value={{state, dispatch}}>
{ children }
</LobbyContext.Provider>
)
}
/* StackNavigator */
const {state} = React.useContext(LobbyContext);
return (
<LobbyProvider>
// same as above <LobbyStack.Navigator>
// state doesn't seem to be updated here or to re-render
</LobbyProvider>
);
/* Queue Screen */
const {state} = React.useContext(LobbyContext);
// accessing state.isActive to do some conditional rendering
// which roomId does get rendered after dispatch