Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Modern] Query only the local client extension types will cause compiling error #2471

Open
nightspirit opened this issue Jun 15, 2018 · 35 comments
Open

Comments

@nightspirit
Copy link
Contributor

@nightspirit nightspirit commented Jun 15, 2018

Let's say I declare the client schema extensions

client.graphql

type Token {
  accessToken: String
  refreshToken: String
}

extend type Query {
  token: Token
}

And then in the QueryRenderer I made the query like this

query RootQuery {
  token {
    accessToken
    refreshToken
  }
}

The relay-compiler will fire the error

GraphQLCompilerContext: Unknown document `RootQuery`.

My temporary workaround is adding extra fields that can be fetched from remote.

query RootQuery {
  viewer {
    me {
      id
    }
  }
  token {
    accessToken
    refreshToken
  }
}

In this way, compiler can successfully compile the static query.

My assumption is relay-compiler will remove whatever client extensions from the query string, if I only query the local type, the query string will be stripped out entirely.

My question is "Is it possible for relay to query ONLY the local type?" People like me are excited about the client extensions is because it gives a way to get rid of the redux store. It will be really helpful if we can directly query the local type and use commitLocalUpdate for the state management.

@hisapy
Copy link

@hisapy hisapy commented Jun 19, 2018

I can confirm the error and I'm also looking for the same functionality.

In my case is even a little bit more complex because my GraphQL API requires an accessToken and I have a UserConfirmationForm which I'd like to feed using a query to data in local schema. Currently, if I add server fields, then the compiler does not error but since the request lacks the accessToken when I render the component it receives a 401 response from server, without the data I expect from the local schema

@renatobenks-zz
Copy link

@renatobenks-zz renatobenks-zz commented Sep 16, 2018

anyone can we help with this one? @leebyron @alloy

@hisapy
Copy link

@hisapy hisapy commented Sep 16, 2018

In order to overcome this limitation I just add __typename to my local only queries.

@alloy
Copy link
Collaborator

@alloy alloy commented Sep 17, 2018

@hisapy Can you elaborate on that by giving an example? (I’m not using this right now, so I’m only asking for the next reader that stumbles on this ticket.)

@hisapy
Copy link

@hisapy hisapy commented Sep 17, 2018

In the following example, using __typename because currently relay-compiler can't query local extensions only. It can be any server field though but __typename fits because it's domain agnostic and it's just a String. By domain agnostic I mean you can use it no matter what your schema represents ... it's an introspection field.

const newUserFormQuery = graphql`
  query routes_NewUserQuery {
    __typename
    state: userForm {
      ...UserForm_state
    }
  }
`;

@nightspirit
Copy link
Contributor Author

@nightspirit nightspirit commented Sep 22, 2018

@hisapy Thanks for the workaround. It works for me.

Hopefully in the future we don't need the hack though.

@sibelius
Copy link
Collaborator

@sibelius sibelius commented May 28, 2019

Relay v4 https://github.com/facebook/relay/releases/tag/v4.0.0

it has full support to client schema extensions: full GraphQL types as well

Added full support for client schema extensions: now full GraphQL types can be defined in the client schema (as opposed to just extending existing server types), and they will be properly retained during garbage collection when rendered

can you test this again? or add a repro so we can investigate further?

@webican
Copy link

@webican webican commented Jul 19, 2019

@sibelius I ran into this bug with v5.0.0

@sibelius
Copy link
Collaborator

@sibelius sibelius commented Jul 19, 2019

can you provide a sample repro?

@webican
Copy link

@webican webican commented Jul 20, 2019

Types in local schema file:

type LocalState {
  id: ID!
  whatever: String
}

extend type Query {
  localState: LocalState
}

Use local-schema-only fields in a query in js:

import { graphql } from 'relay-runtime';

const query = graphql`
  query state_Query {
    localState {
      whatever
    }
  }
`;

when running relay-compiler you get:
GraphQLCompilerContext: Unknown document `state_Query`.

@sibelius
Copy link
Collaborator

@sibelius sibelius commented Jul 20, 2019

does your file is called state.js?

where are you using this query?

@webican
Copy link

@webican webican commented Jul 20, 2019

yes, it's called state.js

I'm using it in the same file:

import { fetchQuery } from 'react-relay';
import { graphql } from 'relay-runtime';

const query = graphql`
  query state_Query {
    localState {
      whatever
    }
  }
`;

const getState = async environment => {
  return await fetchQuery(environment, query, {});
};

I also tried moving the query outside of the state.js, but that didn't help. I don't think it matters, where the query is.

hisapy's workaround works though:

query state_Query {
  __typename
  localState {
    whatever
  }
}

@webican
Copy link

@webican webican commented Jul 20, 2019

I don't know what the recommended way is for querying local-only fields, as the documentation is lacking in that part, but for now I have settled on this:

const getState = environment => {
  return environment.getStore().getSource().get('state');
};

It's synchronous and avoids the "GraphQLCompilerContext: Unknown document" bug without having to use any workarounds.

@sibelius
Copy link
Collaborator

@sibelius sibelius commented Jul 20, 2019

you can try this one https://github.com/facebook/relay/blob/master/packages/react-relay/ReactRelayLocalQueryRenderer.js

LocalQueryRenderer

@webican
Copy link

@webican webican commented Jul 20, 2019

Ah cool, thanks!

I'll give it a try

@webican
Copy link

@webican webican commented Jul 20, 2019

I see ReactRelayLocalQueryRenderer only exports React component, but I need to access the local store before actually rendering anything.

@enisdenjo
Copy link
Contributor

@enisdenjo enisdenjo commented Sep 27, 2019

This issue is still present.

  • babel-plugin-relay@6.0.0
  • relay-compiler@6.0.0
  • relay-runtime@6.0.0
  • react-relay@6.0.0
  • relay-compiler-language-typescript@7.0.0
# client.graphql

extend type Query {
  isDrawerOpen: Boolean
}
// Drawer.tsx

import { graphql, LocalQueryRenderer } from 'react-relay';
const Drawer = () => {
  return (
    <LocalQueryRenderer
      environment={environment}
      query={graphql`
        query DrawerQuery {
          isDrawerOpen
        }
      `}
      variables={{}}
      render={({ props }) => {
        return <SomeDrawerImpl isOpen={props.isDrawerOpen} />;
      }}
    />
  );
};
# relay-compiler

ERROR:
Cannot find root 'DrawerQuery'.

Moreover, @hisapy's workaround still works (appending __typename to the query).

      ...
      query={graphql`
        query DrawerQuery {
          __typename
          isDrawerOpen
        }
      `}
      ...

mrtnzlml referenced this issue Mar 10, 2020
Reviewed By: josephsavona

Differential Revision: D19953903

fbshipit-source-id: 8863cc4d5215af0016438dd09ddf34e4710eb47b
@enisdenjo
Copy link
Contributor

@enisdenjo enisdenjo commented May 4, 2020

v9.1.0 has added this change which kills this workaround.

Because of the aforementioned change, you must downgrade the compiler back to v9.0.0 for the __typename fix to work.

@sibelius
Copy link
Collaborator

@sibelius sibelius commented May 5, 2020

try this as workaround

... on Query { __typename }

instead of just __typename

@josephsavona
Copy link
Contributor

@josephsavona josephsavona commented May 7, 2020

If you're only reading client fields there's no reason to define a Query at all - you can just use a fragment.

@mrtnzlml
Copy link
Contributor

@mrtnzlml mrtnzlml commented May 7, 2020

@josephsavona Can you please elaborate? I thought that LocalQueryRenderer is exactly for such things and that implies writing a query. This issue would still be visible when there are no server fields needed. Do you have some examples so I can understand how do you mean it? Thanks! :)

@enisdenjo
Copy link
Contributor

@enisdenjo enisdenjo commented May 7, 2020

@josephsavona how do you use a "fragment" for the local lookup? I like the query approach because the compiler would kick in and I get types auto-magically.

@DesertDevErsin
Copy link

@DesertDevErsin DesertDevErsin commented May 20, 2020

@josephsavona what's the "fragment-only" method that you're referring to?

@sibelius
Copy link
Collaborator

@sibelius sibelius commented May 20, 2020

try useLocalQuery

https://gist.github.com/sibelius/43bd345873f84684c66942d9f06eeb2d

const useLocalQuery = <TQuery extends {response: any; variables: any}>(
  environment: Environment,
  query: any,
  inVariables: TQuery['variables'] = {}
): TQuery['response'] | null => {
  const variables = useDeepEqual(inVariables)
  const [dataRef, setData] = useRefState<SelectorData | null>(null)
  const disposablesRef = useRef<Disposable[]>([])
  useEffect(() => {
    const {getRequest, createOperationDescriptor} = environment.unstable_internal
    const request = getRequest(query)
    const operation = createOperationDescriptor(request, variables)
    const res = environment.lookup(operation.fragment, operation)
    setData(res.data || null)
    disposablesRef.current.push(environment.retain(operation.root))
    disposablesRef.current.push(
      environment.subscribe(res, (newSnapshot) => {
        setData(newSnapshot.data || null)
      })
    )
    const disposables = disposablesRef.current
    return () => {
      disposables.forEach((disposable) => disposable.dispose())
    }
  }, [environment, setData, query, variables])
  return dataRef.current
}

based on #1656 (comment)

@DesertDevErsin
Copy link

@DesertDevErsin DesertDevErsin commented May 21, 2020

@sibelius, is that what @josephsavona meant? You're still defining a query with useLocalQuery.

I'm not able to use your suggestion because it contains references to code that doesn't exist (e.g., what is useRefState?) I did a little research and found @babangsund's react-relay-local-query, which I think more or less does the same thing. However, when using useLocalQuery, I get this error: TypeError: undefined is not an object (evaluating 'operation.request.identifier')

I'm using the undocumented LocalQueryRenderer for now. Are there plans to include a hook like useLocalQuery in the main Relay library?

@babangsund
Copy link
Contributor

@babangsund babangsund commented May 21, 2020

I haven't looked at Relay in 6 months, but I recall seeing fetchQuery having parameters for querying offline, so I don't see useLocalQuery specifically becoming a part of relay. :-)

EDIT:

I just took a quick peek at the code - { fetchPolicy: 'store-only' } is probably what you're looking for.

Look at the fetchQuery test for relay-experimental:
https://github.com/facebook/relay/blob/master/packages/relay-experimental/__tests__/fetchQuery-test.js for a partial example.

There's no test for store-only, though (Add one!)

@DesertDevErsin
Copy link

@DesertDevErsin DesertDevErsin commented May 21, 2020

@babangsund thanks for the tip! I am adding this to the list of changes that I want to submit PR's for.

@MatrixNorm
Copy link

@MatrixNorm MatrixNorm commented Aug 2, 2020

how do you use a "fragment" for the local lookup?

would like to know as well

milieu added a commit to milieu/relay that referenced this issue Oct 5, 2020
The "New in Relay Modern" post indicates this support is experimental. This changes the title of the guide to match the announcement.
https://relay.dev/docs/en/new-in-relay-modern#client-schema-extensions-experimental

As of this writing this feature still seems to be experimental.
facebook#2471 (open)
facebook#1656 (closed, related)
@Dirklectisch
Copy link

@Dirklectisch Dirklectisch commented Nov 8, 2020

As a workaround to this limitation I have added query resolver in the backend called ping that simply returns the string "pong". I now simply include this ping field in every query that only has local fields to trick the compiler. I have also added some middleware to the front end network layer to prevent queries going out to the server. I'm using react-relay-network-modern to build the front end middleware stack.

Here's the implementation for that middleware (in TypeScript) if you would like to do something similar.

/*
 * There is an open issue in the Relay Compiler that makes it so that only requesting fields from the local storage will
 * result in an error. You can find out more about the issue here: https://github.com/facebook/relay/issues/2471
 *
 * As a work around we have added a dummy field that queries the ping/pong endpoint. This middleware makes it so that
 * a round trip to the server isn't necessary when that happens.
 *
 * The middleware itself is implemented using a simple regex instead of a proper parser to keep it performant. After all
 * every request passes through it.
 * */

const onlyPingQueryRE = /query\s[a-zA-Z]+\s{\s+ping\s+}\s+/;

const dummyResponse = RelayNetworkLayerResponse.createFromGraphQL({
  data: {ping: 'pong'},
});

const bustRelayLocalCacheMiddleware = (next: MiddlewareNextFn) => async (
  req: RelayRequestAny,
) => {
  const queryString = req.getQueryString();
  if (queryString.match(onlyPingQueryRE)) {
    return dummyResponse;
  }
  return next(req);
};

This solution makes it so we don't have to use the useLocalQuery package and can simply remove the middleware when this problem is fixed.

@Bekaxp
Copy link

@Bekaxp Bekaxp commented Jan 25, 2021

try this as workaround

... on Query { __typename }

instead of just __typename

@sibelius @josephsavona
Hi

your suggestion works but we are getting a warning, can you give us any hints?

Screenshot 2021-01-25 at 11 58 21

@kelvin2200
Copy link

@kelvin2200 kelvin2200 commented Jun 14, 2021

I remember an issue with local queries in Relay, specifically that a query made on (i.e. a single field) of a local type, would always result in an extra round-trip to the backend. Is this still the case in V11?

@tslater
Copy link

@tslater tslater commented Dec 5, 2021

try this as workaround

... on Query { __typename }

instead of just __typename

@sibelius @josephsavona Hi

your suggestion works but we are getting a warning, can you give us any hints?

Screenshot 2021-01-25 at 11 58 21

I was able to get rid of this warning message by adding { fetchPolicy: 'store-only' } to all of my queries. I think using the local renderer component will work too (if you're not using hooks).

@josephsavona
Copy link
Contributor

@josephsavona josephsavona commented Feb 3, 2022

@alunyov We should find a way to make local-only queries work somehow

@alunyov
Copy link
Contributor

@alunyov alunyov commented Feb 4, 2022

@josephsavona yeah - there is a recent report of this too: #3760

We'll add this.

@milieu
Copy link

@milieu milieu commented Feb 18, 2022

@alunyov @josephsavona in the meantime before this is fixed, could this diff be applied to the docs? #3207

Never mind. The page the diff was against was removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests