String interpolation in gql tag - reactjs

I've already posted this as an issue in the graphql-tag repositoy but I'll post it here as I hope somebody on here has a workable solution.
What am I trying to do?
I'd like to dynamically define typedefs of local types based on props passed to my react component.
Why am I doing this?
I realise this is not the intended way of defining gql, however, I'm in the process of creating a React wrapper component around the Apollo Provider. The purpose of it is to make the process of mocking out data locally (as described here https://www.apollographql.com/docs/react/development-testing/client-schema-mocking/) more seamless with less boilerplate.
I'm going for a declarative approach where the user can simply define an array of fields (name, graphQL type and optional implementation that will default to a sensible faker implementation for the graphQL type) that will make local fields available directly on the Query type as well as an array of types (name, fields and an optional nested type) which should make it possible to define arbitrary local schemas declaratively with no boilerplate.
What's the outcome at the moment?
Just to establish a baseline, the following is working just fine and allows me to run codegeneration from Apollo CLI and query the new local test field (with a #client directive) in my application
const localTypeDefs = gql`
extend type Query {
test: String
}
`;
const client = new ApolloClient({
cache: new InMemoryCache(),
uri: "https://localhost:4000/graphql",
typeDefs: localTypeDefs,
...apolloClientOptions,
});
If I change the qgl definition to look like this instead
const name = "name";
const graphQLType = "String";
const localTypeDefs = gql`
extend type Query {
${name}: ${graphQLType}
}
`;
I get the following error when running codegen Syntax Error: Expected Name, found ":". If I change the gql definition to this
const testString = "test: String";
const localTypeDefs = gql`
extend type Query {
${testString}
}
`;
I get this error Syntax Error: Expected Name, found "}". on codegen. In general, it seems like everything after the interpolated string causes the compiler to emit this error on the next character. If I include the } in the testString, I get the error Syntax Error: Expected Name, found <EOF>..
Now, If I try to change to the function syntax instead so the localTypeDefs definition looks as follows:
const testString = "test: String";
const localTypeDefs = gql(`extend type Query { ${testString} } `);
The typedef is actually generated without any error, however, the codegeneration still fails with the error GraphQLError: Cannot query field "test" on type "Query" when I query for this field. The query hasn't change at all from the working baseline I posted at the top and if I change back to that without touching anything else, the codegen no longer complains about the query.
Curiously, if I change the above back to the baseline implementation but keep the explicit function call instead of the implicit `` string as the following:
const localTypeDefs = gql(`
extend type Query {
test: String
}
`);
I still get the error GraphQLError: Cannot query field "test" on type "Query" but as soon as I change back to the base case, everything is working just fine.

I was was able to get something similar to this working for a query by wrapping an input with double quotes:
const query = gql`
mutation RegenerateAgreement {
agreementsRegenerate(input: { userId: "${userId}" }) {
agreements {
body
prompt
}
}
}
`;

Related

React query invalidateQueries partial match not working

I have a few useQuery() calls in my react component like this:
const {...} = useQuery(["person.getAll", ....
const {...} = useQuery(["person.getCounts", ....
Then later in a click event after some DELETE/POST request is finished I try to invalidate above queries:
queryClient.invalidateQueries("person");
But it does not trigger the re-fetch.
I thought it's some state management issues from my part but then I tried invalidating a specific query like
queryClient.invalidateQueries("person.getAll");
and it works fine..
Is partial query key matching not working in react-query ?
React-Query invalidation works off based array prefixes, not string prefixes.
Your useQuery calls should look like this:
const {...} = useQuery(["person", "getAll", ....
const {...} = useQuery(["person", "getCounts", ....
And then you invalidateQueries call will work, with a slight change:
queryClient.invalidateQueries({
queryKey: ["person"]
}); // invalidate all query keys which start with "person"
queryClient.invalidateQueries(["person"]); // this also works
Alternative, if you are locked into your current syntax, you can accomplish the same using a predicate:
queryClient.invalidateQueries({
predicate: query =>
query.queryKey[0].startsWith("person"),
})
But this breaks the React-Query convention.
If you see the last example in the invalidateQueries docs, it provides an option for maximum granularity
If you find yourself wanting even more granularity, you can pass a predicate function to the invalidateQueries method. This function will receive each Query instance from the query cache and allow you to return true or false for whether you want to invalidate that query:
So, for your scenario the following should work
queryClient.invalidateQueries({
predicate: query =>
query.queryKey[0].startsWith('person'),
})
This is a matter of how you are structuring the queryKey array. The first constant value of your queryKey array is a whole string, based on what you provided the partial matching won't work if you try to invalidate the queries based on just a part of that string. For this to work, you'd need to structure the queryKey array like so:
const {...} = useQuery(["person", "getAll", ....
const {...} = useQuery(["person", "getCounts", ....
Now the invalidation should work as expected because it's matching all queries that start with "person" in their query key:
queryClient.invalidateQueries(["person"]);
Reference of this on the docs

Using variables in typeScript graphQL code generator

I followed this tutorial to auto generate the schema of my graphql endpoint in my front application.
However now I need to use variables, so I did something like this:
export const GET_TODO = gql`
query GetTodo($id: Int!) {
todos(id: $id) {
id
text
completed
}
}
}
I want to know if there is a way I can get the variable type "automatically" without having to redefine it in the front.
E.g.
...
query GetTodo($id: AutoGeneratedTodoIdType) {
...
PS: I am using react, in case there are framework/library specific solutions.
Yes you can, you will usually have exported types in the generated file such as GetTodoVariables

Use i18next translation hook for error messages

So I have a form which works fine and for error messages I have created a schema file which contains errors defined like this
export const dataInputCreateSchema = yupObject({
company: yup.object().required('This field is required').nullable
})
In my component I am initializing my i18next translation variable like this const { t } = useTranslation('common'). As to show the error messages in case if user touch the textbox and does not write anything then required field error will be shown and its working like this
const companyFieldError = _.get(errors,'company', '');
_.get is a lodash method that takes in an object, path and default value.
I need to know how I can pass my translation defined in common.json file to companyFieldError? Translation in common.json is something like
"errorMessages": {
"requiredField": "This is required Field"
}
I don't want to discard my schema file but I want to provide key there and that key must get translated. Is there a way to do that?
Here are some changes I will make to support translation of errors.
Use the message in the schema definition.
export const dataInputCreateSchema = yupObject({
company: yup.object().required('errorMessages.requiredField').nullable
})
Use the message to get the translated text.
const message = _.get(errors,'company', '');
const companyFieldError = t(message);

Typescript & Gatsby: gatsby-plugin-ts + graphQL queries integration

I'm using gatsby-plugin-ts to generate types for my graphql page queries.
The issue I have is that all types generated return a T | undefined type for all fields, so I would need to check all query subfields before using them in any component, otherwise the compiler will throw an error.
Take gatsby-plugin-image for example. Given a query:
export const query = graphql`
query IndexPage {
banner: file(relativePath: { eq: "banner.jpg" }) {
childImageSharp {
gatsbyImageData(width: 1920)
}
}
}
`;
The result data.banner should be passed to the getImage function, though if you try to do so typescript understandably throws the following error, since undefined is not assignable to IGAtsbyImageData expected by getImage
And this is even worse when it comes to more complex queries, like the ones from the markdownremark plugin: all subfields of one query result would need to be manually checked every time. Is there a work around for this?
type of data.banner should be ImageDataLike
export interface DataBannerProps {
banner: ImageDataLike;
alt: string;
}
This appears to be caused by a bug in the gatsby-plugin-image typings. Unfortunately the typings for getImage, getSrc, etc. don't match their null-safe behaviour. This also looks like it was only partially fixed, since the troublesome | null type is down one level on { childImageSharp: { gatsbyImageData: IGatsbyImageData } | null } (at least in the GraphQL Typegen typings - YMMV).
The easiest workaround is to use a type assertion:
const result = getImage(data.banner as ImageDataLike);
This tells Typescript: "Hey, I know these types don't exactly match, but trust me, data.banner will always be an ImageDataLike".
Of course, you can avoid all this if you're happy to define your typings manually, as in #Alexey's answer.
If you want to stick with auto-generated types, I'd recommend upgrading to the official GraphQL Typegen which is built into Gatsby and kept up-to-date.

SharePoint React pnpjs

I am working on a react project for SharePoint framework.
My question is a more or less general question.
I came across a certain problem which I don't understand:
I tried to use sp.search with an object matching the SearchQuery interface, but did this in a class extending react.component
Code:
public search(query: string, properties: Array<string>, rowLimit: number): Promise<SearchResults> {
return new Promise<SearchResults>(async (resolve) => {
let result = await sp.search(<SearchQuery>{
Querytext: query,
SelectProperties: properties,
RowLimit: rowLimit
});
resolve(result);
});
}
As you can see, this is just a basic search function. If I use sp.search in a class that does not extend react.component or if I don't use the searchquery instance, but a plain querystring everything works fine.
I seem to be a type error, but honestly, I don't get what's happening.
Error:
[ts]Argument of type 'Element' is not assignable to parameter of type 'SearchQueryInit'. Type 'Element' is not assignable to type 'ISearchQueryBuilder'.
Property 'query' is missing in type 'Element'. [2345]
[ts] JSX element 'SearchQuery' has no corresponding closing tag. [17008]
[ts] 'SearchQuery' only refers to a type, but is being used as a value here. [2693]
So I decided to just write a service that's using pnpjs and that's totally fine, especially since I didn't plan to use pnpjs in a component, that was just to be a bit faster and have something to test and to work with. but nevertheless, I really would like to understand what is going on. Btw I'm using TypeScript, if that's of interest.
Apparently those errors occur due to a limitation cased by the use of <T> for generic type parameter declarations combined with JSX grammar, refer this issue for a more details (which is marked as a Design Limitation)
To circumvent this error, create the instance of SearchQuery:
const searchQuery: SearchQuery = {
Querytext: query,
SelectProperties: properties,
RowLimit: rowLimit
};
and then pass it into sp.search method:
let result = await sp.search(searchQuery);

Resources