Graphql - what constitutes an operation having a unique name? - reactjs

I am having trouble making my code generator run. The reason given is that Not all operations have an unique name: AllIssueGroups.
I am not clear on what an "operation" is exactly.
I have a single issueGroup.resolver, a single issueGroup.service and then more than one component in the front end that tries to read from AllIssueGroups, using the following gql:
const _ = gql`
query AllIssueGroups {
allIssueGroups {
id
title
description
issues
}
}
In each react component, I only define this const once, but I'm wondering if graphql thinks each of those is an "operation".
My resolver has:
#Query(() => [IssueGroup])
async allIssueGroups() {
return await this.issueGroupService.getAllIssueGroups()
}
My service has:
async getAllIssueGroups() {
return (await prisma.issueGroup.findMany({orderBy: {title: 'asc'}}))
}
Each of the service and resolver is defined once, only.
Where can I find the definition of "operation" for graphql's purposes? I can't figure out why it thinks it is not uniquely defined.
In my graphql.tsx, the AllIssueGroup entries are not duplicated.
How can I find what is causing the codegen to think that it is not uniquely named?

Related

GraphQL: Writing Frontend Query with Optional Arguments

I am currently trying to write a GraphQL Query called recipe which can take a number of optional arguments based on a graphQL input called RecipeSearchInput, and uses the input to find a specific recipe matching the attributes passed.
I am struggling to write the frontend query to be able to be able to take the arguments as an object.
Here's my graphQl schema for graphql input RecipeSearchInput.
input RecipeSearchInput {
_id: ID
title: String
cookTime: Int
prepTime: Int
tools: [String!]
ingredients: [String!]
steps: [String!]
videoURL: String
tags: [String!]
country: String
}
And here's my query written in the frontend to access the my mongodb server through graphql:
// gql query that requests a recipe
export const findOne = obj => {
let requestBody = {
query: `
query {
recipe(recipeInput: ${obj}) {
_id
title
cookTime
prepTime
tools
ingredients
steps
videoURL
tags
country
}
}
`
};
return fetchEndpoint(requestBody);
};
When I wrote my frontend query with a simple object that I knew existed in my database:
const displayData = async () => {
const recipeData = await api.recipe.findOne({
title: "Greek Chicken Skewers"
});
console.log(recipeData);
};
This gives me the following error:
message: "Expected value of type "RecipeSearchInput", found [object, Object]."
The problem I'm seeing is that obj is not formatted properly (in String form) to be received by Graphql as an input. The Graphql query params look like this:
Here is what the working query looks like
What's the best approach for making a query that takes more than one argument? How should I package up the argument in the frontend to please GraphQL?
Thanks in advance, and please let me know if any of this was unclear!
Shawn

Reasoning with _raw and normal data in Gatsby, GraphQL and Sanity

I've just started using Gatsby with the Sanity headless CMS.
For the most part it's pretty straight forward; but knowing best practises for querying the data through GraphQL is still bothering me. How I'm doing it currently is just frantically clicking through my CMS structure in the GraphQL playground and finding what I want. This works but the lack of uniformity in this approach is making me uneasy.
For example, if I want a hero image that's in the CMS somewhere, i'll need to do something like:
query SomePageQuery($id: String) {
sanitySomePage(id: { eq: $id }) {
id
heroImage {
asset {
fluid(maxWidth: 1500) {
...GatsbySanityImageFluid
}
}
}
}
}
But if I want some PortableText block then I need to query the corresponding _raw field of whatever type. So, if my type was introText, Gatsby also provides a _rawIntroText. I'm only able to get the full PortableText from this _raw version of the data. Like this:
query SomePageQuery($id: String) {
sanitySomePage(id: { eq: $id }) {
id
_rawIntroText
}
}
It seems that, for some data you can use [Type], and sometimes you have to use _raw[Type].
There's not a great deal of documentation as to why this is the case. And I'm not sure if this is enforced via Sanity or Gatsby.
My question I guess would be, why does _raw[Anything] exist in the Gatsby and/or Sanity world, and how do people decide on which to use (other than just trial and error within the GraphQL playground and at runtime)?
This is coming from the gatsby-source-sanity plugin that Sanity built and maintains. Hopefully someone from Sanity can provide more context, but effectively the _raw[FieldName] entries return the original JSON data for the field. The unprefixed field (e.g. fieldName) is probably not what you want—it'll only contain bits of metadata about the data.
I tend to pull the _raw[FieldName] data and then just pass it straight into the #sanity/block-content-to-react component like so:
import React from "react"
import { graphql } from "gatsby"
import SanityBlockContent from "#sanity/block-content-to-react"
export default ({ data: { page } }) => (
<SanityBlockContent
blocks={page.textContent}
projectId={process.env.GATSBY_SANITY_PROJECT_ID}
dataset={process.env.GATSBY_SANITY_DATASET}
/>
)
export const query = graphql`
query SomePageQuery($id: String) {
page: sanitySomePage(id: { eq: $id }) {
textContent: _rawTextContent
}
}
`
Note that I'm using GraphQL aliasing to continue to refer to the field as textContent in my component rather than coupling the component to the specifics of this GraphQL schema.
You don't need to use Gatsby Image for Sanity images since they have their own image transformation pipeline anyways. Instead you can just fetch asset { _id } and then use #sanity/client like this to generate an image url:
import sanityClient from "#sanity/client"
import sanityImageUrl from "#sanity/image-url"
const client = sanityClient({
dataset: process.env.GATSBY_SANITY_DATASET,
projectId: process.env.GATSBY_SANITY_PROJECT_ID,
useCdn: true,
})
const builder = sanityImageUrl(client)
builder.image({ _id: "..." }).width(400).dpr(2).url()

React Apollo updating client cache after mutation

I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}​​​​​
generated: false​​​​​
id: "DojoType:1"​​​​​
type: "id"​​​​​
typename: "DojoType"​​​​​
<prototype>: Object { … }​​​​
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.

How to send query in React Router properly

I am sorry if this question has already been asked or does not contribute to the community but I am stuck at sending query string in a URL in React Router.
My client wants to have friendly URL, which should consist of the category name and part catalogue number, i.e.
http://localhost:8000/parts/alternators-11.2824.
Right now I redirect to a part show page this way.
this.props.changeRoute(`/${subdomain}/parts/${category}-${partId}`)
Brief explanation:
subdomian could be 'en' or 'de', or 'fr' depending on the language
category is the category name the part belongs to
partId is the part ID in DB.
So I end up having this URL:
http://localhost:8000/parts/alternators-1234
however the url should consist of the catalogue number, not the part ID from DB, like this:
http://localhost:8000/parts/alternators-11.2824
This won't suit me because then there is a Saga middleware that fetches the part from API and it needs the actual ID from DB on the backend so it won't find the part by the catalogue number.
The saga ajax request looks like this:
export function* getPart(action) {
const authOptions = yield call(authorizationHeader);
const location = action.location;
const url = location ? `${API_URL}/parts/${partIdFromUrl(location)}` : '';
const partResponse = yield call(request, url, authOptions);
if (!partResponse.err) {
const response = normalize(partResponse.data.part, part);
yield put(partLoaded(response.entities));
} else {
yield put(partLoadingError(partResponse.err));
}
}
export function partIdFromUrl(location) {
const partDataIndex = 3;
const partIdIndex = 2;
const pathnameArray = location.pathname.split('/');
const partData = pathnameArray[partDataIndex].split('-');
return partData[partIdIndex];
}
So I am pulling the part ID from the URL by parsing the string.
I am not sure how this.props.changeRoute is defined, I wasn't able to find a definition of this function so I am guessing it's defined in React Router core library somewhere. I've tried passing additional parameter, like so:
this.props.changeRoute(`/${subdomain}/parts/${category}-${partId}`, {query: {partId: partId }})
but that didn't work.
I am using React, React Router, Redux and redux-saga middleware in the project. I'm quite new to the project as I've taken in over by someone else and I don't quite understand everything yet.
Is there a way to pass additional query parameter, so that later I can make calls to the API by ID?

Caching in React

In my react App I have a input element. The search query should be memoized, which means that if the user has previously searched for 'John' and the API has provided me valid results for that query, then next time when the user types 'Joh', there should be suggestion for the user with the previously memoized values(in this case 'John' would be suggested).
I am new to react and am trying caching for the first time.I read a few articles but couldn't implement the desired functionality.
You don't clarify which API you're using nor which stack; the solution would vary somewhat depending on if you are using XHR requests or something over GraphQL.
For an asynchronous XHR request to some backend API, I would do something like the example below.
Query the API for the search term
_queryUserXHR = (searchTxt) => {
jQuery.ajax({
type: "GET",
url: url,
data: searchTxt,
success: (data) => {
this.setState({previousQueries: this.state.previousQueries.concat([searchTxt])
}
});
}
You would run this function whenever you want to do the check against your API. If the API can find the search string you query, then insert that data into a local state array variable (previousQueries in my example).
You can either return the data to be inserted from the database if there are unknowns to your view (e.g database id). Above I just insert the searchTxt which is what we send in to the function based on what the user typed in the input-field. The choice is yours here.
Get suggestions for previously searched terms
I would start by adding an input field that runs a function on the onKeyPress event:
<input type="text" onKeyPress={this._getSuggestions} />
then the function would be something like:
_getSuggestions = (e) => {
let inputValue = e.target.value;
let {previousQueries} = this.state;
let results = [];
previousQueries.forEach((q) => {
if (q.toString().indexOf(inputValue)>-1) {
result.push(a);
}
}
this.setState({suggestions: results});
}
Then you can output this.state.suggestions somewhere and add behavior there. Perhaps some keyboard navigation or something. There are many different ways to implement how the results are displayed and how you would select one.
Note: I haven't tested the code above
I guess you have somewhere a function that queries the server, such as
const queryServer = function(queryString) {
/* access the server */
}
The trick would be to memorize this core function only, so that your UI thinks its actually accessing the server.
In javascript it is very easy to implement your own memorization decorator, but you could use existing ones. For example, lru-memoize looks popular on npm. You use it this way:
const memoize = require('lru-memoize')
const queryServer_memoized = memoize(100)(queryServer)
This code keeps in memory the last 100 request results. Next, in your code, you call queryServer_memoized instead of queryServer.
You can create a memoization function:
const memo = (callback) => {
// We will save the key-value pairs in the following variable. It will be our cache storage
const cache = new Map();
return (...args) => {
// The key will be used to identify the different arguments combination. Same arguments means same key
const key = JSON.stringify(args);
// If the cache storage has the key we are looking for, return the previously stored value
if (cache.has(key)) return cache.get(key);
// If the key is new, call the function (in this case fetch)
const value = callback(...args);
// And save the new key-value pair to the cache
cache.set(key, value);
return value;
};
};
const memoizedFetch = memo(fetch);
This memo function will act like a key-value cache. If the params (in our case the URL) of the function (fetch) are the same, the function will not be executed. Instead, the previous result will be returned.
So you can just use this memoized version memoizedFetch in your useEffect to make sure network request are not repeated for that particular petition.
For example you can do:
// Place this outside your react element
const memoizedFetchJson = memo((...args) => fetch(...args).then(res => res.json()));
useEffect(() => {
memoizedFetchJson(`https://pokeapi.co/api/v2/pokemon/${pokemon}/`)
.then(response => {
setPokemonData(response);
})
.catch(error => {
console.error(error);
});
}, [pokemon]);
Demo integrated in React

Resources