Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apollo client issues in next.js , javascript, graphql, react js

If I visit home page at first or refresh the home page, I can't get profile in resolver because cache.readQuery is not working.

As well as it calls the api infinitely.

If I move to another page and comeback to home page again, cache.readQuery works and get the profile and voteStatus of post come correctly.

Has anyone faced this issue before? home.tsx is main page of my project.

As well as, useQuery(@apollo/react-hooks) get old data on every page.

Please help me if you have experiences.

  • home.tsx
    ...
    const GET_POSTS = graphql`
      query posts($accountname: String!, $page: Int, $pathBuilder: any, $postsStatus: String) {
        posts(accountname: $accountname, page: $page, postsStatus: $postsStatus)
          @rest(type: "Post", pathBuilder: $pathBuilder) {
          post_id
          author
          voteStatus(accountname: $accountname) @client
          created_at
        }
      }
    `;
    interface Props {
      author: string;
    }
    const Home: NextPage<Props> = ({ author }) => {
      const { data, fetchMore, loading } = useQuery(GET_POSTS, {
        variables: {
          accountname: author,
          page: 1,
          postsStatus: 'home',
          pathBuilder: () => `posts/?Page=1&Limit=5&domainId=1`,
        },
      });
      const loadMorePosts = () => {
        fetchMore({
          variables: {
            page: page + 1,
            pathBuilder: () => `posts/?Page=${page + 1}&Limit=5&domainId=1`,
          },
          updateQuery: (previousResult, { fetchMoreResult }) => {
            if (!fetchMoreResult) {
              return previousResult;
            }
            setPage(page + 1);
            return Object.assign({}, previousResult, {
              posts: [...previousResult.posts, ...fetchMoreResult.posts],
            });
          },
        });
      };
      return (
        <div></div>
      );
    };
    interface Context extends NextPageContext {
      apolloClient: ApolloClient<NormalizedCacheObject>;
    }
    Home.getInitialProps = async (ctx: Context) => {
      const cookies = nextCookie(ctx);
      const author = cookies[encodeURIComponent(KARMA_AUTHOR)];
      ctx.apolloClient.writeData({
        data: {
          accountName: author,
        },
      });
      return {
        layoutConfig: { layout: labels.DEFAULT },
        meta: {
          title: 'Home',
        },
        author,
      };
    };
    export default withAuthSync(withApollo({ ssr: true })(Home));
  • withApollo.tsx
    import { ApolloClient } from 'apollo-client';
    import { withClientState } from 'apollo-link-state';
    import serverFetch from 'node-fetch';
    import graphql from 'graphql-tag';
    const GET_PROFILE = graphql`
      query Profile($accountname: String!, $domainID: number) {
        profile(accountname: $accountname, domainID: $domainID)
          @rest(type: "Profile", path: "profile/{args.accountname}?domainID={args.domainID}") {
          author
          followers_count
          following_count
        }
      }
    `;
    const cache = new InMemoryCache({
      cacheRedirects: {
        Query: {
          post: (_, { post_id }, { getCacheKey }) => getCacheKey({ __typename: 'Post', post_id }),
        },
      },
      dataIdFromObject: object => {
        switch (object.__typename) {
          case 'Post':
            return getUniquePostId(object.post_id);
          case 'Comment':
            return getUniqueCommentId(object.cmmt_id);
          case 'Profile':
            return object.author;
          default:
            defaultDataIdFromObject(object);
        }
      },
    });
    const resolvers = {
      Post: {
        voteStatus: async ({ post_id }, args, { cache }, info) => {
          const { profile } = cache.readQuery({
            query: GET_PROFILE,
            variables: {
              accountname: args.accountname,
              domainID: 1,
            },
          });
          console.log(profile); // can't make console log because profile is not coming from readQuery
          if (profile) {
            return 1;
          } else {
            return 0;
          }
        },
      },
    };
    const stateLink = withClientState({
      cache,
      resolvers,
    });
    const restLink = new RestLink({
      uri: `${SERVER_URL}/`,
      serverFetch,
    });
    const createApolloClient = (initialState: NormalizedCacheObject, ctx: NextPageContext) => {
      return new ApolloClient({
        ssrMode: true,
        link: ApolloLink.from([stateLink, restLink]),
        cache,
      });
    }
    ...
    export const withApollo = ({ ssr = false } = {}) => (PageComponent: NextPage) => {
      const client = createApolloClient(initialState, ctx);
      ...
      return {
        ...pageProps,
        apolloState: apolloClient.cache.extract(),
        apolloClient: ctx.apolloClient,
      };
    }
like image 567
i-Guru Avatar asked May 07 '20 19:05

i-Guru


2 Answers

I haven't worked with this setup and just assuming that your app might be clearing cache so when initially trying to get that query on home page it fails.

Again I might be wrong but according to these docs: https://www.apollographql.com/docs/react/caching/cache-interaction/#readquery

If your cache doesn't contain all of the data necessary to fulfill a specified query, readQuery throws an error. It never attempts to fetch data from a remote server.

It looks like it make sense to add a try/catch block to your readQuery

let profile;
try {
  result = cache.readQuery({
    query: GET_PROFILE,
    variables: {
      accountname: args.accountname,
      domainID: 1,
    },
  });
  profile = result.profile;
} catch(err) {
  console.error(err);
  // do something like printing an error in console, or nothing
}
like image 144
Uma Avatar answered Sep 27 '22 22:09

Uma


cache.readQuery can't read not existing data - data must be queried first.

Probably one of pages you're switching queries this data (profile) making it available when you go back to <Home/>.

Solution - query for profile before querying posts:

const Home: NextPage<Props> = ({ author }) => {
  const { data: dataProfile, error: errorProfile, loading: loadingProfile } = useQuery(GET_PROFILE);
  const { data, fetchMore, loading } = useQuery(GET_POSTS, {
    skip: !dataProfile,
    variables: {
      accountname: author,
      page: 1,
      postsStatus: 'home',
      pathBuilder: () => `posts/?Page=1&Limit=5&domainId=1`,
    },
  });

skip option is used for block query until condition is met.

Posts are fetched after receiving profile data (and writing it into cache). This way local resolver (voteStatus) can use cache.readQuery() to access required data.

like image 29
xadm Avatar answered Sep 27 '22 22:09

xadm