Не удается выполнить обновление состояния реакции для размонтированного компонента с помощью поставщика контекста

#reactjs #graphql #react-apollo #react-context

Вопрос:

Для проекта React я использую несколько поставщиков контекста, выполняющих запросы GraphQL, предоставляя все или некоторые компоненты с необходимыми мне данными. Для этого примера у меня есть поставщик книг, который запрашивает все книги для определенного пользователя.

 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 amp;amp; 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 amp;amp; 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()

Вопросы

  • Почему возникает эта ошибка?
  • Как это можно исправить?
  • Является ли это хорошей практикой выполнения запросов в контекстных провайдерах?

Ответ №1:

Я попробую еще раз 😉

Вы бы использовали 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) так что, может быть, вы уже пробовали это…

Ответ №2:

Похоже, что ваш крючок запроса graphql опрашивает данные, и это обновит его внутреннее состояние или попытается сделать это, даже если компонент отключен.

Я читал подобные проблемы в этой теме (и это похоже на ошибку, потому 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 amp;amp; children}
    </BookContext.Provider>
  )
}
 

Подавляет ли это предупреждение?

Примечание: не уверен, что эта {fetchPolicy: "network-only"} опция необходима

Комментарии:

1. К сожалению, этого не происходит, я также попробовал это без опроса. Это тоже не нессасери

2. Какую версию клиента apollo вы используете?

3. ^3.3.12 . Все же спасибо за эффективность.