【问题标题】:Can't perform a React state update on an unmounted component using context provider无法使用上下文提供程序对未安装的组件执行 React 状态更新
【发布时间】:2021-08-13 13:41:40
【问题描述】:

对于一个 React 项目,我使用多个上下文提供程序运行 GraphQL 查询,为所有或部分组件提供我需要的数据。对于这个例子,我有一个 BookProvider 可以查询特定用户的所有书籍。

export const BookProvider = ({ children }) => {
  // GraphQL query for retrieving the user-belonged books
  const { loading, data } = useQuery(gql`{
    books {
        id,
        name,
        description
    }
  }
`, {
    pollInterval: 500
  })  

  if(!data?.books) return null

  return (
    <BookContext.Provider value={data?.books}>
      {!loading && children}
    </BookContext.Provider>
  )
}

我确保查询未加载并且在 data 对象中可用。为了检索我的数据,我在此提供程序中包装了一个仪表板组件。在这里,还有其他组件加载相应的数据。

const Dashboard =  () => {
  return (
    <BookProvider>
      // More components for loading data
    </BookProvider>
  )
}

数据是这样加载的:

const { id, name } = useContext(BookContext)

以同样的方式,我有一个AuthenticationProvider,包装了整个应用程序。

export const MeQuery = gql`{ me { id, email } }`

export const AuthenticationProvider = (props) => {
  const { loading, data } = useQuery(MeQuery)

  if(loading) return null
  
  const value = {
    user: (data && data.me) || null
  }
    
  return <AuthenticationContext.Provider value={value} {...props} />
}

对于我的应用程序路由,检查用户是否经过身份验证,我使用PrivateRoute 组件。 Dashboard 组件由此 PrivateRoute 加载。

const PrivateRoute = ({ component: Component, ...rest }) => {
  const { user } = useAuthentication()

  return user ? <Route {...rest} render={props => <Component {...props} />} /> : <Redirect to={{ pathname: '/login' }} />
}

登录时,设置用户对象,没有问题,但是,注销时,这是Dashboard上的一个组件,它将我重定向到登录页面,但我收到以下错误:

index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    at BookProvider (http://localhost:3000/static/js/main.chunk.js:2869:3)
    at Dashboard
    at Route (http://localhost:3000/static/js/vendors~main.chunk.js:85417:29)
    at PrivateRoute (http://localhost:3000/static/js/main.chunk.js:4180:14)
    at Switch (http://localhost:3000/static/js/vendors~main.chunk.js:85619:29)
    at Router (http://localhost:3000/static/js/vendors~main.chunk.js:85052:30)
    at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:84672:35)
    at Router

对于注销,我使用以下代码:

export function useLogout() {
  const client = useApolloClient()

  const logout = useMutation(gql`mutation Logout { logout }`, { update: async cache => { 
    cache.writeQuery({ query: MeQuery, data: { me: null }})
        
    await client.resetStore()
  }})

  return logout
}

export default useLogout

这被称为const [logout] = useLogout()

问题

  • 为什么会出现这个错误?
  • 如何解决这个问题?
  • 在上下文提供程序中运行查询是一种好习惯吗?

【问题讨论】:

    标签: reactjs graphql react-apollo react-context


    【解决方案1】:

    您的 graphql 查询钩子似乎正在轮询数据,这将更新其内部状态,或者尝试这样做,即使组件已卸载。

    我读过类似的问题 in that thread(它看起来像一个错误,因为 useQuery 钩子应该检测组件何时被卸载并停止轮询)

    似乎适合您的问题的解决方案之一是控制轮询,以便您可以在组件卸载后停止它。

    应该是这样的:

    export const BookProvider = ({ children }) => {
      // GraphQL query for retrieving the user-belonged books
      const { loading, data, stopPolling, startPolling } = useQuery(gql`{
        books {
            id,
            name,
            description
        }
      }`, {fetchPolicy: "network-only"})
    
      React.useEffect(function managePolling() {
        startPolling(500)
        return () => stopPolling()
      })
    
      if(!data?.books) return null
    
      return (
        <BookContext.Provider value={data?.books}>
          {!loading && children}
        </BookContext.Provider>
      )
    }
    

    它会抑制警告吗?

    注意:不确定是否需要 {fetchPolicy: "network-only"} 选项

    【讨论】:

    • 不幸的是它没有,我也尝试过没有投票。这也不是必需品
    • 您使用的是什么版本的 apollo 客户端?
    • ^3.3.12。不过感谢您的努力。
    【解决方案2】:

    我会再试一次;-)

    您可以改用useLazyQuery,这似乎可以避免困扰useQuery 的错误:

    const [ executeQuery, { data, loading } ] = useLazyQuery(gql`${QUERY}`);
    

    然后手动管理轮询:

    
    useEffect(function managePolling() {
      const timeout = null
      const schedulePolling = () => {
        timeout = setTimeout(() => {
          executeQuery()
          schedulePolling()
        }, 500)
      }
      executeQuery()
      schedulePolling()
      return () => clearTimeout(timeout)
    }, [executeQuery])
    

    (未经测试的代码,但你明白了)

    这是在同一个线程 (https://github.com/apollographql/apollo-client/issues/6209#issuecomment-676373050) 上建议的,所以也许您已经尝试过了...

    【讨论】:

      猜你喜欢
      • 2020-03-11
      • 2021-07-01
      • 2020-11-21
      • 2019-11-09
      • 2019-05-30
      • 2021-05-30
      相关资源
      最近更新 更多