Velidan
Velidan

Reputation: 6004

Apollo on local client doesn't trigger the local resolvers

Apollo doesn't trigger the resolvers in the case of Local state Client (frontent local state). Apollo 2.7

Does anyone have any idea why it happens?

Here is the setup:

Apollo client

import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import fetch from 'isomorphic-unfetch'

import { resolvers, typeDefs } from './resolvers';
import { initCache } from './init-cache';

export default function createApolloClient(initialState, ctx) {
  // The `ctx` (NextPageContext) will only be present on the server.
  // use it to extract auth headers (ctx.req) or similar.
  return new ApolloClient({
    ssrMode: Boolean(ctx),
    link: new HttpLink({
      uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
      credentials: 'include', // Additional fetch() options like `credentials` or `headers`
      fetch,
    }),
    typeDefs,
    resolvers,
    connectToDevTools: true,
    cache: initCache({
                   robot: {
                     __typename: 'Robot',
                     name: 'Robbie',
                     status: 'live',
                    },

                  member: {
                     __typename: 'Member',
                     name: 'RFesagfd',
                     }
                   }),
     })
  }

Types & resolvers (resolvers.js)

import gql from 'graphql-tag';

export const typeDefs = gql`
  type Robot {
    name: String!
    status: String!
  }

  type Member {
    name: String!
    isLogged: Boolean!
  }

`;

export const resolvers = {
  Member: {
    isLogged: (...args) => {
      console.log('args', args); // THIS NEVER TRIGGERS SOMEHOW
      return true;
    }
  }
};

Query

const GET_IS_MEMBER_LOGGED = gql`
  query isMemberLogged {
    member @client {
      name
      isLogged
    }
  }
`;

Thanks for any help!

Upvotes: 1

Views: 739

Answers (2)

Velidan
Velidan

Reputation: 6004

I found a possible solution. Maybe this info will be useful for someone. If we want to omit the Query Resolver + Field resolvers and we want to have the only Field resolver we need to use @client(always: true).

The in deep explanation

In general, there is a problem with how the Apollo client works with Cache. By default, it caches the response, and next time it'll fetch the cached result from the cache (eg. optimistic UI). This behavior is the same even in the case of the Client.

It means when we have the initial model in cache Apollo will fetch in from the cache and ignores the resolvers, even if we pass the @client directive. To solve this problem and let Apollo know that we need to use Local resolvers EVEN if we have a cached object, we need to use @client(always: true) for the preferred field or the whole object. I made an example below.

P.S. Unfortunately I didn't find how to force Apollo to work with non-existing field so if we want to have some resolver for a specific field, we still need to define the initial field value it the initial Cached Model to let the Apollo know about this field. After that, Apollo will use resolver for it to generate some high-calculated output for this particular field, thanks to @client(always: true).
In general, it's ok, because we should know what kind of dynamic field we'll have in our model.

Apollo client

import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import fetch from 'isomorphic-unfetch'

import { resolvers, typeDefs } from './resolvers';
import { initCache } from './init-cache';

export default function createApolloClient(initialState, ctx) {
  // The `ctx` (NextPageContext) will only be present on the server.
  // use it to extract auth headers (ctx.req) or similar.
  return new ApolloClient({
    ssrMode: Boolean(ctx),
    link: new HttpLink({
      uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
      credentials: 'include', // Additional fetch() options like `credentials` or `headers`
      fetch,
    }),
    typeDefs,
    resolvers,
    connectToDevTools: true,
    cache: initCache({
                     author: {
                     __typename: 'Author',
                     posts: 0,
                     name: '' // NEED TO SET AN INITIAL VALUE
                     }
     })
  }

Types & resolvers (resolvers.js)

import gql from 'graphql-tag';
import { print  } from 'graphql';

export const typeDefs = gql`
  type Author {
    posts: Int!
    name: String
  }

`;


export const resolvers = {
  Author: {
    name(author) {
      console.log('Author name resolver', author). // WORKS
      return 'NAME';
    },
  },

};

Query

const GET_AUTHOR = gql`
  query getAuthor {
    author {
      posts
      name @client(always: true)
    }
  }
`;

Upvotes: 0

xadm
xadm

Reputation: 8428

You need to define result type of local queries:

const typeDefs = gql`
  extend type Query {
    robot: Robot
    member: Member
  }

... and resolver for your query - not type (as you decorated entire query as local)... but you have to return typed data:

export const resolvers = {
  Query: {
    member: (...args) => {
      console.log('args', args); 
      return {
        __typename: 'Member',
        name: 'some name', // read from cache
        isLogged: true // function result
      };
    }
  }
};

You should also use __typename for cache writes.

update

assuming you have a Memeber in cache ... you can:

// read (initialized with permanent) data:
const memberData = cache.readQuery(....
// f.e. it should have `__typename` and 'name`
// ... and 'decorate' it with derived properites
memberData.age = currentYear - memberData.birthYear;
memberData.isLogged = someFuncReturningBool();
return memberData; // Member type shaped object

It's about shape/data organization - typed (return type shaped object with defined properties) or simple (return all properties separately) or mixed, f.e. (some global app state)

const GET_IS_MEMBER_LOGGED = gql`
  query profileViewData {
    member @client {
      name
      isLogged
    }
    isProfilePanelOpen @client
    termsAccepted @client
  }
`;

Upvotes: 1

Related Questions