如何在移动到下一个屏幕之前等待 React useEffect 钩子完成

Ric*_*cek 5 async-await reactjs react-native react-hooks

我遇到了在多个文件中使用钩子的问题,并且在第一个异步方法完成之前它被两次调用以用于 useEffect(这应该阻止第二个钩子调用,但事实并非如此)。请参阅以下 2 种情况:

堆栈导航器

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>
)
Run Code Online (Sandbox Code Playgroud)

大堂挂钩

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(); }
  })
}
Run Code Online (Sandbox Code Playgroud)

队列屏幕

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"
...
)
Run Code Online (Sandbox Code Playgroud)

在场景 1 中,我猜虽然调用了第一个钩子并且 useEffect 正在执行异步操作以将用户添加到房间并将活动设置为 true。同时,条件渲染部分直接移动到队列屏幕,它再次调用钩子并执行 useEffect (因为第一个还没有完成并且isActive仍然是假的)。

如何正确设置 useReducer 和 useMemo 以便它根据状态呈现屏幕。

根据答案编辑代码

/* 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
Run Code Online (Sandbox Code Playgroud)

Shu*_*tri 0

您必须注意,自定义挂钩每次调用时都会创建一个新的状态实例。

例如,您在 StackNavigator 组件中调用挂钩,然后在 QueueScreen 中再次调用,因此将调用 2 个不同的 useReducers,而不是它们共享状态。

您应该在 StackNavigator 的父级中使用 useReducer ,然后将其用作 useLobby 挂钩中的上下文

const LobbyStateContext = React.createContext();

const Component =  ({children}) => {
   const [state, dispatch] = React.useReducer(...)
   return (
      <LobbyStateContext.Provider value={[state, dispatch]]>
         {children}
     </LobbyStateContext>
   )
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它

<Component>
  <StackNavigator />
</Component>
Run Code Online (Sandbox Code Playgroud)

useLobby 看起来像

export const useLobby = () => {
  const [state, dispatch] = React.useContext(LobbyStateContext)

  const assignRoom = async () => {
    // dispatch room id
  }

  const context = React.useMemo(() => ({
    join: () => { assignRoom(); }
  })

  return { context, assignRoom, state};
}
Run Code Online (Sandbox Code Playgroud)

StackNavigator 将利用 useLobby 并具有 useEFfect 逻辑

const { context, state, assignRoom } = useLobby(); 

React.useEffect(() => {
    if (!state.isActive) assignRoom();
}, [state.isActive])

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>
)
Run Code Online (Sandbox Code Playgroud)