Search code examples
reactjsgraphqlreact-apolloreact-context

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


For a React project, I'm using multiple context providers running GraphQL queries, providing all, or some components with my needed data. For this example, I have a BookProvider that queries all the books for a specific user.

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

I made sure the query wasn't loading and is available in the data object. For retreiving my data, I have a dashboard component wrapped in this provider. In here, there are other components loading the corresponding data.

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

The data is loaded like this:

const { id, name } = useContext(BookContext)

In the same way, I have a AuthenticationProvider, wrapping the whole application.

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

For my application routes, checking if the user is authenticated, I use a PrivateRoute component. The Dashboard component is loaded by this PrivateRoute.

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

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

When logging in, and setting the user object, there is no problem, however, when logging out, which is a component on the Dashboard, it re-directs me to the login page, but I receive the following error:

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

For logging out, I'm using the following code:

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

This gets called like const [logout] = useLogout()

Questions

  • Why is this error occuring?
  • How can this be fixed?
  • Is it a good practice running queries in context providers?

Solution

  • I'll give it another try ;-)

    You would use useLazyQuery instead, that seem to allow avoiding the bug that plagues useQuery:

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

    And then manage the polling manually:

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

    (untested code, but you get the idea)

    This was suggested on the same thread (https://github.com/apollographql/apollo-client/issues/6209#issuecomment-676373050) so maybe you already tried it...