apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.38k stars 2.66k forks source link

useQuery data property not updating after forwarding an operation #9340

Open clairestolp opened 2 years ago

clairestolp commented 2 years ago

Intended outcome: When making a GraphQL request, I try to make a GraphQL request with an invalid token. I use error link to intercept the operation, refresh the token, then forward the operation. The expectation is that when I forward the operation after successfully fetching the token, it should retry the operation and the response data should populate in the data property of the useQuery hook.

// Error Link

let isRefetchingToken = false;

const errorLink = (store: Store) =>
  onError(({ graphQLErrors, networkError, operation, forward }) => {
    if (graphQLErrors) {
      const { message } = graphQLErrors[0];
      // if the token is refetching, pause the query until we know a new token has been fetched
      if ((isRefetchingToken && _.includes(message, '401')) || _.includes(message, 'Forbidden')) {
        setTimeout(() => {
          forward(operation);
        }, 1000);
      }

      // if error message includes a 401 we're going to kick off a refresh token action
      // and then retry the query
      if (!isRefetchingToken && _.includes(message, '401')) {
        isRefetchingToken = true;
        new Promise((resolve, reject) => {
          const state = store.getState();
          const {
            authTokenService: { refreshToken },
          } = state;

          store.dispatch(
            refreshTokenAction(refreshToken, {}, {
              resolve,
              reject,
            }),
          );
        }).then(() => {
          isRefetchingToken = false;
          forward(operation);
        });
      }
    }

    if (networkError) {
      const { operationName = 'Unknown' } = operation || {};
      if (__DEV__) {
        throw new Error(
          `[Apollo] ${operationName} Network Error: ${networkError}\nGQL errors: ${graphQLErrors}`,
        );
      } else {
        console.error(`[Apollo] ${operationName} GQL error: `, graphQLErrors);
      }
    }
  });
// Use Query and UseEffect inside react component

const { error, data, refetch, loading } = useQuery(tripsQuery, {
  variables: {
    isInPast: false,
    first: maxTripsToShow,
    sort: ['start_date_asc', 'start_time_asc'],
    startDateTime: `lte:(${currentDate})`,
  },
  notifyOnNetworkStatusChange: true,
});

useEffect(() => {
  setTripList(
    _.map(_.get(data, 'myMobileTrips.edges'), ({ node }) => ({
      tripEndDate: moment.tz(node.endDateTime, timeZone),
      tripStartDate: moment.tz(node.startDateTime, timeZone),
      tripLabel: node.label,
      tripLogoUrl: node.logoUrl,
      tripId: node.tripId,
    })) || [],
  );
}, [data, setTripList, timeZone]);

Actual outcome:

The error is caught by onError, the token is refetched successfully, the GraphQL operation is successfully performed, and we can see the successful response in our network logs. However the useQuery hook does not pick up the new data.

Screen Shot 2022-01-21 at 4 50 32 PM

Screen Shot 2022-01-21 at 4 51 00 PM

Here I log the output on change of data or error Screen Shot 2022-01-20 at 10 05 44 PM After the error is thrown and data is updated with undefined there is no additional logging, even though the retry request returned successfully

Versions Note: This project is a react-native project using 0.65.1 and react 17.0.2. Also I tried upgrading to v3.5.7 and still could observe the same behavior.

System: OS: macOS 12.1 Binaries: Node: 14.18.2 - /usr/local/bin/node npm: 6.14.15 - /usr/local/bin/npm Browsers: Chrome: 97.0.4692.71 Safari: 15.2 npmPackages: @apollo/client: 3.4.11 => 3.5.7 apollo-upload-client: 16.0.0 => 16.0.0 apollo3-cache-persist: 0.13.0 => 0.13.0

ThierryVC commented 1 year ago

@apollo/client: 3.7.1 here

I am not on React-Native right now, but I have had a similar problem today and found out that I had to return an Observable inside onError. Simply calling forward(operation) does the retry but the response isn't picked up anywhere.

However I had to do some asynchronous stuff before forwarding (like refreshing an access token). This Stackoverflow post helped me a lot for that. I hope this helps someone else.