reactjs - 如何在移动到下一个屏幕之前等待 React useEffect Hook 完成

标签 reactjs react-native async-await react-hooks

我遇到了在多个文件中使用钩子(Hook)的问题,并且在第一个异步方法完成之前它被调用两次用于 useEffect (这应该阻止第二个钩子(Hook)调用,但事实并非如此)。请参见以下 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>
)

大堂 Hook
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(); }
  })
}

队列屏幕
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"
...
)

在场景 1 中,我猜当第一个钩子(Hook)被调用并且 useEffect 正在执行异步以将用户添加到房间并将 active 设置为 true 时。同时,条件渲染部分直接移动到队列屏幕,该屏幕再次调用钩子(Hook)并执行 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

最佳答案

您必须注意,自定义 Hook 将在每次调用时创建一个新的状态实例。

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

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

const LobbyStateContext = React.createContext();

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

并像使用它一样
<Component>
  <StackNavigator />
</Component>

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};
}

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>
)

关于reactjs - 如何在移动到下一个屏幕之前等待 React useEffect Hook 完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62125737/

相关文章:

javascript - 如何在 React-Native 中按顺序运行异步函数?

reactjs - 禁用 react 导航中的后退按钮

javascript - 将相同 Prop 和文本渲染到不同组件类型的 DRY React 方法

javascript - React Native 视频无法播放本地文件

c# - 异步/等待或使用 TcpListener 开始/结束?

javascript - 日期类型(对象或数字)问题

javascript - 类型错误 : Cannot read property 'map'

reactjs - UseApolloClient 查询不会返回 fetchMore

c# - 任务等待和线程终止导致内存泄漏

node.js - 在Node.js中,使用async/await处理错误的正确方法是什么?