无法使用上下文提供程序对未安装的组件执行 React 状态更新

Can't perform a React state update on an unmounted component using context provider

对于 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()

问题

您的 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"}选项

我再试一次;-)

您可以改用 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) 上提出的建议,所以也许您已经尝试过...