Custom ApolloProvider for access to React Context inside

烈酒焚心 提交于 2020-06-01 05:08:40

问题


I created ApolloCustomProvider because I want to use addNotification function from my AppContext for graphQL error handling.

The problem is that this custom provider seems to work while querying or mutating API but errorLink's functions like addNotification and console.log() doesn't work. Also console.log() from request function doesn't print anything.

But when I put everything from ApolloCustomProvider to standard TypeScript file (.ts - not React Component) and then use it like:

import { ApolloProvider } from '@apollo/react-hooks'
import client from 'api/client'

...

<ApolloProvider client={client}>
  <App />
</ApolloProvider>

everything works. All console.logs prints etc. Of course without addNotification which comes from AppContext. Allow to use AppContext is reason that's why I'm creating custom provider.

Files:

  • index.ts
import React, { ReactElement } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import AppContextProvider from 'contexts/AppContext'
import ApolloCustomProvider from 'components/ApolloCustomProvider'

const AppContainer = (): ReactElement => (
    <AppContextProvider>
      <ApolloCustomProvider>
        <App />
      </ApolloCustomProvider>
    </AppContextProvider>
)

ReactDOM.render(<AppContainer />, document.getElementById('root'))
  • ApolloCustomProvider.tsx
import React, { useContext, useEffect } from 'react'
import { ApolloProvider } from '@apollo/react-hooks'
import { ApolloClient, DefaultOptions } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink, Observable, Operation, NextLink } from 'apollo-link'
import { createUploadLink } from 'apollo-upload-client'
import { onError } from 'apollo-link-error'
import { AppContext } from 'contexts/AppContext'
import { Subscription } from 'apollo-client/util/Observable'
import { NotifierType } from 'components/Notifier'
import i18n from 'i18n'

const ApolloCustomProvider: React.FC = ({ children }) => {
  const { addNotification } = useContext(AppContext)

  useEffect(() => {
    console.log('ApolloCustomProvider render')
  })

  const request = (operation: Operation): void => {
    console.log('ApolloCustomProvider request')

    const token = localStorage.getItem('at')

    operation.setContext({
      headers: {
        authorization: token ? `Bearer ${token}` : '',
      },
    })
  }

  const requestLink = new ApolloLink(
    (operation: Operation, nextLink: NextLink) =>
      new Observable(observer => {
        console.log('gql requestLink observer')
        let handle: Subscription

        Promise.resolve(operation)
          .then((o: Operation): void => void request(o))
          .then(() => {
            handle = nextLink(operation).subscribe({
              next: observer.next.bind(observer),
              error: observer.error.bind(observer),
              complete: observer.complete.bind(observer),
            })
          })
          .catch(observer.error.bind(observer))

        return (): void => {
          if (handle) handle.unsubscribe()
        }
      })
  )

  const errorLink: ApolloLink = onError(({ graphQLErrors, networkError }): void => {
    console.log('ApolloCustomProvider errors', graphQLErrors, networkError)

    if (graphQLErrors)
      addNotification(
        NotifierType.ERROR,
        graphQLErrors.map(({ message }) => message)
      )

    if (networkError)
      addNotification(NotifierType.ERROR, [
        `${i18n.t('notification.networkError')}: ${networkError.message}`,
      ])
  })

  const uploadLink: ApolloLink = createUploadLink({ uri: process.env.REACT_APP_CORE_URI })

  const defaultOptions: DefaultOptions = {
    watchQuery: {
      fetchPolicy: 'no-cache',
    },
    query: {
      fetchPolicy: 'no-cache',
    },
  }

  const client = new ApolloClient({
    link: ApolloLink.from([requestLink, errorLink, uploadLink]),
    cache: new InMemoryCache(),
    defaultOptions,
  })

  return <ApolloProvider client={client}>{children}</ApolloProvider>
}

export default ApolloCustomProvider

回答1:


Here i'm trying to answer your question
The changes that i make is:
1. Change your function component to Class Component, the reason is when u add a notification, your whole component won't rerender. But it will rerender if u use functional component and causing spam notification on your onError.
2. Change the order of apolloLink. Here is the source https://github.com/apollographql/apollo-link/issues/133. The author said LogLink is running first before httpLink. He put the LogLink after httpLink.

import React, { useContext, useEffect } from 'react'
import { ApolloProvider } from '@apollo/react-hooks'
import { ApolloClient, DefaultOptions } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink, Observable, Operation, NextLink } from 'apollo-link'
import { createUploadLink } from 'apollo-upload-client'
import { onError } from 'apollo-link-error'
import { AppContext } from 'contexts/AppContext'
import { Subscription } from 'apollo-client/util/Observable'
import { NotifierType } from 'components/Notifier'
import i18n from 'i18n'

class ApolloCustomProviderWithContext extends React.Component {
  constructor(props) {
  
    // The reason i change to class component is when u add a notification, your whole component won't rerender. But it will rerender if u use functional component and causing spam notification on your onError. 
    const request = (operation: Operation): void => {
      console.log('ApolloCustomProvider request')

      const token = localStorage.getItem('at')

      operation.setContext({
        headers: {
          authorization: token ? `Bearer ${token}` : '',
        },
      })
    }

    const requestLink = new ApolloLink(
      (operation: Operation, nextLink: NextLink) =>
        new Observable(observer => {
          console.log('gql requestLink observer')
          let handle: Subscription

          Promise.resolve(operation)
            .then((o: Operation): void => void request(o))
            .then(() => {
              handle = nextLink(operation).subscribe({
                next: observer.next.bind(observer),
                error: observer.error.bind(observer),
                complete: observer.complete.bind(observer),
              })
            })
            .catch(observer.error.bind(observer))

          return (): void => {
            if (handle) handle.unsubscribe()
          }
        })
    )

    const errorLink: ApolloLink = onError(({ graphQLErrors, networkError }): void => {
      console.log('ApolloCustomProvider errors', graphQLErrors, networkError)

      if (graphQLErrors)
        addNotification(
          NotifierType.ERROR,
          graphQLErrors.map(({ message }) => message)
        )

      if (networkError)
        addNotification(NotifierType.ERROR, [
          `${i18n.t('notification.networkError')}: ${networkError.message}`,
        ])
    })

    const uploadLink: ApolloLink = createUploadLink({ uri: process.env.REACT_APP_CORE_URI })

    const defaultOptions: DefaultOptions = {
      watchQuery: {
        fetchPolicy: 'no-cache',
      },
      query: {
        fetchPolicy: 'no-cache',
      },
    }

    const client = new ApolloClient({
      // I change the order of this ApolloLink.from
      link: ApolloLink.from([errorLink, uploadLink, requestLink]),
      cache: new InMemoryCache(),
      defaultOptions,
    })
    
    this._client = client
  }
  
  render () {
    return (
      <ApolloProvider client={this._client}>
        {this.props.children}
      </ApolloProvider>
    )
  }
}

const ApolloCustomProvider = props => {
  const { addNotification } = useContext(appContext)
  return <ApolloCustomProviderWithContext {...props} addNotification={addNotification} />
}

export default ApolloCustomProvider
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


来源:https://stackoverflow.com/questions/62086440/custom-apolloprovider-for-access-to-react-context-inside

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!