Apollo client fragments not embedding data - graphql

This is the first time I've ventured into fragments and I can't see where I'm screwing up, but it definitely isn't working! In GraphiQL it's working fine:
query Tasks($taskIds: [String]!) {
tasks(taskIds: $taskIds) {
...taskDisplay
}
}
fragment taskDisplay on Task {
_id
name
description
status
children {
_id
}
}
Here's what's in my client app:
import { gql } from "#apollo/client";
export const TASK_FRAGMENT = gql`
fragment taskDisplay on Task {
_id
name
description
status
children {
_id
}
}
`;
export const TASKS = gql`
query Tasks($taskIds: [String]!) {
tasks(taskIds: $taskIds) {
...taskDisplay
}
}
${TASK_FRAGMENT}
`;
So, the server returns the data correct as I can see in the Network tab of Chrome, but the data received by the useQuery result is an empty object. What gives?
Using #apollo/client#3.2.0-beta.2 (I have downgraded to 3.1.0 with same results)
EDIT:
Adding more info. My code is about as simple as it could be using a hook. Here's what's happening:
import { useQuery, gql } from "#apollo/client";
import { TASK_FRAGMENT } from "../pages/task/queries";
const ROOT_TASK_QUERY = gql`
query Project($projectId: String!) {
rootTask(projectId: $projectId) {
...taskDisplay
}
}
${TASK_FRAGMENT}
`;
const useProject = ({ variables }) => {
return useQuery(ROOT_TASK_QUERY, {
variables,
});
};
export default useProject;
And this is just logging the query itself:

Your returned data is missing the __typename field

Related

Graphql-codegen nested fragment use inside a React component

The nested fragment regularUserResponseFragmentDocument
import { graphql } from '../../generated/graphql';
export const regularErrorFragmentDocument = graphql(`
fragment regularError on FieldError {
field
message
}
`);
export const regularUserFragmentDocument = graphql(`
fragment regularUser on User {
id
username
}
`);
export const regularUserResponseFragmentDocument = graphql(`
fragment regularUserResponse on UserResponse {
errors {
...regularError
}
user {
...regularUser
}
}
`);
is used in a component like so:
if (response.data?.login) {
const data = useFragment(regularUserResponseFragmentDocument, response.data.login);
const errorsData = useFragment(regularErrorFragmentDocument, data.errors);
if (errorsData) {
setErrors(toErrorMap([...errorsData]));
}
const userData = useFragment(regularUserFragmentDocument, data.user);
if (userData) {
await router.push('/');
}
}
Is there a way to avoid use of useFragment on the nested fragments?
No there is not; this is the proper way to get nested masked fragments.
You will find a similar example here where useFragment() was renamed to getFragmentData() (from codegen config) to avoid React hooks rules issues: https://github.com/charlypoly/codegen-repros/blob/master/client-preset-nested-fragments-interface/src/App.tsx

Multiple queries in a component for Gatsby

I'm using Gatsby to createPages and then in the createPage method I'm referencing a specific component for the page
createPage({
path: node.path.alias,
component: path.resolve(`./src/layouts/custom-page/custom-page.js`),
context: {
articleId: node.id,
authorId: node.relationships.field_author.id
},
})
Within the layout (custom-page.js) I'm trying to do 2 queries but it's not working for me. When I test this query in GraphiQL it works fine.
export const query = graphql `
query ($articleId: String!, $authorId: String!) {
article: nodeArticle(id: {eq: $articleId}) {
title
body {
value
}
path {
alias
}
}
author: nodePerson(id: {eq: $authorId}) {
title
path {
alias
}
}
}
`;
Within my component I would then expect to be able to access the data with either data.author.title or data.article.title.
Is it possible to do this in Gatsby?
Your query is seemed to be bit wrong or some missing in the query.
Query should look like.
export const query = graphql `
query Articles($articleId: String!, $authorId: String!) {
article: nodeArticle(id: {eq: $articleId}) {
title
body {
value
}
path {
alias
}
}
author: nodePerson(id: {eq: $authorId}) {
title
path {
alias
}
}
}
`;
Better to use the package graphql-tag.
Your query will look like.
import gql from 'graphql-tag';
export const query = gql`
query Articles($articleId: String!, $authorId: String!) {
article: nodeArticle(id: {eq: $articleId}) {
title
body {
value
}
path {
alias
}
}
author: nodePerson(id: {eq: $authorId}) {
title
path {
alias
}
}
}
`;

#client Apollo GQL tag breaks query

I have a vue-apollo (using nuxt) query that is supposed to have a local client field show. However, when I have the show #client line included in the query the component does not render. For some reason it also seems to fail silently.
query myAccounts {
accounts: myAccounts {
email
calendars {
id
name
hex_color
is_enabled
show #client
}
}
}
I am extending the Calendar type in an extensions.js file (pasted below) with two mutations.
import gql from 'graphql-tag'
export const typeDefs = gql`
extend type Calendar {
show: Boolean
}
type Mutation {
showCalendar(id: ID!): Boolean
hideCalendar(id: ID!): Boolean
}
`
Here is the resolver that sets the value, along with the Apollo config:
import { InMemoryCache } from 'apollo-cache-inmemory'
import { typeDefs } from './extensions'
import MY_ACCOUNTS_QUERY from '~/apollo/queries/MyAccounts'
const cache = new InMemoryCache()
const resolvers = {
Mutation: {
showCalendar: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: MY_ACCOUNTS_QUERY })
const found = data.accounts
.flatMap(({ calendars }) => calendars)
.find(({ id }) => id === '1842')
if (found) {
found.show = true
}
cache.writeQuery({ query: todoItemsQuery, data })
return true
}
}
}
export default context => {
return {
cache,
typeDefs,
resolvers,
httpLinkOptions: {
credentials: 'same-origin'
},
}
}
along with the nuxt config:
apollo: {
defaultOptions: {
$query: {
loadingKey: 'loading',
fetchPolicy: 'cache-and-network',
},
},
errorHandler: '~/plugins/apollo-error-handler.js',
clientConfigs: {
default: '~/apollo/apollo-config.js'
}
}
Querying local state requires the state to exist (i.e. it should be initialized) or for a local resolver to be defined for the field. Apollo will run the resolver first, or check the cache directly for the value if a resolver is not defined. There's not really a good way to initialize that value since it's nested inside a remote query, so you can add a resolver:
const resolvers = {
Calendar: {
show: (parent) => !!parent.show,
},
// the rest of your resolvers
}
See the docs for additional examples and more details.

How to get Gatsby Images based on results of PageQuery?

I'd like to do something like the following so I can get Gatsby Images dynamically:
const image = 'gastby-astronaut.png';
export const imageQuery = graphql`
{ allImageSharp (
filter: {
fluid: {
originalName: {
regex: "/${image}/"
}
}
}
){
edges {
node {
fluid {
originalName
}
}
}
}
}
`;
However, I can't figure out how to connect this query to an initial query that would get the 'gatsby-astronaut.png', or perform this query from a subcomponent with a . I get this error when I try this:
Error: BabelPluginRemoveGraphQL: String interpolations are not allowed
in graphql fragments. Included fragments should be referenced as
`...MyModule_foo`.
Any suggestions on the proper way to return Gatsby Images dynamically?
Ah, yeah Gatsby extracts GraphQL queries from your pages through static analysis: they load the file as text, parse it, and extract the query, all before the actual file gets executed. This means that your typical tagged-template literal functionality isn't there.
The only way to filter is through context provided when createPage is called from gatsby-node.js. I.e. if you do this:
exports.createPages = ({ graphql, actions }) =>
graphql(`some query here`).then(result => {
actions.createPage({
path: "/output-path/",
component: path.resolve(`./src/templates/your_template.jsx`),
context: { image: result.data.yourImage },
})
})
Then you can do this in your page query:
query SomePage($image: String!) {
allImageSharp (
filter: {
fluid: {
originalName: {
regex: $image
}
}
}
){
edges {
node {
fluid {
originalName
}
}
}
}
}
Here's a solution I came up with... pretty janky, but it works:
import PropTypes from 'prop-types';
import React from 'react';
import Img from 'gatsby-image';
import { useStaticQuery, graphql } from 'gatsby';
const Image = ({ imageYouWant }) => {
const data = useStaticQuery(
graphql`
query allTheImagesQuery{
allImageSharp {
edges {
node {
fluid(maxWidth:1000) {
...GatsbyImageSharpFluid
originalName
}
}
}
}
}`,
);
const TheImageYouWant = data.allImageSharp.edges
.filter(edge => edge.node.fluid.originalName === imageYouWant)
.map(myImage => <Img fluid={myImage.node.fluid} />);
return (
<>
{ TheImageYouWant }
</>
);
};
Image.propTypes = {
imageYouWant: PropTypes.string,
};
Image.defaultProps = {
imageYouWant: '',
};
export default Image;

Using writeFragment to Update a Field Belonging to an Object?

I'm trying to get my first writeFragment working.
Here's the object shape:
resolutions {
_id
name
completed
goals {
_id
name
completed
}
}
I've just run a mutation on the client that successfully adds a new goal, and now I'm trying to get the client page to auto-update and show the new goal that was just added.
I've got readFragment working. It reads in the Resolution successfully. I'm reading in the Resolution, rather than the goals, because as a field belonging to resolution, the goals don't have an id of their own.
Here's my update function, showing readFragment and writeFragment:
<Mutation
mutation={CREATE_GOAL}
update={(cache, { data: { createGoal } }) => {
let resId = 'Resolution:' + resolutionId;
const theRes = cache.readFragment({
id: resId,
fragment: GET_FRAGMENT_GOAL,
});
theRes.goals = theRes.goals.concat([createGoal]); //<== THIS WORKS
cache.writeFragment({
id: resId,
fragment: SET_FRAGMENT_GOAL,
data: { __typename: 'Resolution', goals: theRes.goals },
});
}}
>
...and here's the gql for the fragments:
const GET_FRAGMENT_GOAL = gql`
fragment targetRes on resolutions {
name
completed
goals {
_id
name
completed
}
}
`;
const SET_FRAGMENT_GOAL = gql`
fragment targetGoal on resolutions {
__typename
goals
}
`;
Here's a console error I'm getting:
You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types.
Apollo Client will not be able to able to accurately map fragments.To make this error go away, use the IntrospectionFragmentMatcher as described in the docs: http://dev.apollodata.com/react/initialization.html#fragment-matcher
I read up on IntrospectionFragmentMatcher and it looks like mega-overkill for my situation. It appears I'm doing something else wrong. Here's the other error I'm getting at the same time:
Uncaught (in promise) TypeError: Cannot read property 'data' of undefined
What's wrong with my call to writeFragment?
After quite a few hours of study, I learned a lot about fragments!
I got it working. Here are the updated fragment and query definitions:
import gql from "graphql-tag";
let resolutionQueryFragments = {
goalParts: gql`
fragment goalParts on Goal {
_id
name
completed
}
`,
};
resolutionQueryFragments.resolutionGoals = gql`
fragment resolutionGoals on Resolution {
goals{
_id
name
completed
}
}
`;
const GET_RESOLUTIONS = gql`
query Resolutions {
resolutions {
_id
name
completed
...resolutionGoals
}
user {
_id
}
}
${resolutionQueryFragments.resolutionGoals}
`;
const CREATE_RESOLUTION = gql`
mutation createResolution($name: String!) {
createResolution(name: $name) {
__typename
_id
name
...resolutionGoals
completed
}
}
${resolutionQueryFragments.resolutionGoals}
`;
const GET_RESOLUTIONS_FOR_MUTATION_COMPONENT = gql`
query Resolutions {
resolutions {
_id
name
completed
...resolutionGoals
}
}
${resolutionQueryFragments.resolutionGoals}
`;
const CREATE_GOAL = gql`
mutation createGoal($name: String!, $resolutionId: String!) {
createGoal(name: $name, resolutionId: $resolutionId) {
...goalParts
}
}
${resolutionQueryFragments.goalParts}
`;
export {resolutionQueryFragments, GET_RESOLUTIONS, GET_RESOLUTIONS_FOR_MUTATION_COMPONENT, CREATE_RESOLUTION, CREATE_GOAL}
...and here's the updated Mutation component:
import React, {Component} from "react";
import gql from "graphql-tag";
import {graphql} from "react-apollo";
import {Mutation} from "react-apollo";
import {withApollo} from "react-apollo";
import {resolutionQueryFragments, CREATE_GOAL} from '../../imports/api/resolutions/queries';
const GoalForm = ({resolutionId, client}) => {
let input;
return (
<Mutation
mutation={CREATE_GOAL}
update={(cache, {data: {createGoal}}) => {
let resId = 'Resolution:' + resolutionId;
let currentRes = cache.data.data[resId];
let theGoals = cache.readFragment({
id: resId,
fragment: resolutionQueryFragments.resolutionGoals
});
theGoals = theGoals.goals.concat([createGoal]);
cache.writeFragment({
id: resId,
fragment: resolutionQueryFragments.resolutionGoals,
data: {goals: theGoals}
});
}}
>
{(createGoal, {data}) => (
<div>
<form
onSubmit={e => {
e.preventDefault();
createGoal({
variables: {
name: input.value,
resolutionId: resolutionId
}
});
input.value = "";
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Submit</button>
</form>
</div>
)}
</Mutation>
)
;
};
export default withApollo(GoalForm);

Resources