【问题标题】:How to wait for React useEffect hook finish before moving to next screen如何在移动到下一个屏幕之前等待 React useEffect 钩子完成
【发布时间】:2020-09-19 08:54:27
【问题描述】:

我遇到了在多个文件中使用钩子的问题,并且在第一个异步方法完成之前它被调用两次用于 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>
)

大厅挂钩

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 中,我猜当第一个钩子被调用并且 useEffect 正在执行异步以将用户添加到房间并将 active 设置为 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

【问题讨论】:

  • 您的意思是对两个屏幕使用单独的 useReducer 还是希望共享 isActive 状态?

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


【解决方案1】:

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

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

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

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

【讨论】:

  • 我更改了代码以匹配您答案的第一和第二部分。它现在只调用一次。我在 StackNavigator 中使用 useContext 来访问 state 进行条件渲染。异步函数在Component 中,并被useEffect 调用,但是,当调度和状态发生变化时,它不会重新渲染。我应该将异步函数移动到 useMemo 还是只在父组件内或在每个单独的屏幕上执行以呈现数据?
  • 查看日志后,似乎调度发生在异步调用完成之前,即使传递的有效负载已等待。此外,状态仍然没有被共享,只有提供者组件和队列在刷新后具有正确的状态。 StackNavigator 保持初始状态,因此不呈现
  • 你能显示那段代码是什么发送的吗?你确定 StackNavigator 也在使用上下文吗
  • 刚刚更新为包含我拥有的 3 个文件的编辑代码
  • 你还没有在 reducer 中处理过 SET_ROOM 并且在 reducer 中也没有默认状态
猜你喜欢
  • 2021-11-21
  • 1970-01-01
  • 2020-06-11
  • 1970-01-01
  • 2021-05-18
  • 2020-10-30
  • 2020-12-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多