When does useQuery trigger a re-render? - react-apollo

Client side state:
type State {
a: Int!
b: Int!
}
extend type Query {
state: State!
}
React Component A:
query {
state #client {
a
}
}
Will the following code trigger a re-render on component A?
client.writeData({
data: {
state: {
...prevState,
b: prevState.b + 1,
},
},
})

Using writeData to manually update the cache will change the data in the result object returned from useQuery. However, React is smart about re-renders and will only rerender if values from your query are actually being used in your JSX. Hope this helps.

Related

Is it possible to `readFragment` from apollo cache if it has no id?

I have a nested component in my app.
At the top of the page, I have a query like
const REPOSITORY_PAGE_QUERY = gql`
query RepositoryPageQuery($name: String!, $owner: String!) {
repository(name: $name, owner: $owner) {
...RepositoryDetailsFragment
}
}
${REPOSITORY_DETAILS_FRAGMENT}
`;
RepositoryDetailsFragment then includes
// list of branches
refs(first: 2, refPrefix: "refs/heads/") {
...BranchesFragment
}
and finally
fragment BranchesFragment on RefConnection {
totalCount
pageInfo {
...PageInfoFragment
}
edges {
node {
id
name
}
}
}
${PAGE_INFO_FRAGMENT}
Obviously, I am not happy, because I need to pass BranchesFragment info around 3 levels deep.
Instead, it would be great if I could read it from the cache directly in my BranchesList component.
I tried to use
client.cache.readFragment({
fragment: BRANCHES_FRAGMENT,
fragmentName: "BranchesFragment"
});
But the problem is that this fragment does not have any id. Is there any way to deal with it and get the fragment info?
Alright, I suddenly came to the solution. Maybe it could be useful for others.
Imagine we have a hierarchy of query -> fragments and components -> subcomponents like this:
RootPageComponent
query
query RepositoryPageQuery(
$name: String!
$owner: String!
$count: Int!
$branchSearchStr: String!
) {
repository(name: $name, owner: $owner) {
...RepositoryDetailsFragment
}
}
${REPOSITORY_DETAILS_FRAGMENT}
component returns the following
<RepositoryDetails repository={data.repository} />
RepositoryDetails
Has a fragment
fragment RepositoryDetailsFragment on Repository {
name
descriptionHTML
defaultBranchRef {
id
name
}
# the branches repository has
refs(first: $count, refPrefix: "refs/heads/", query: $branchSearchStr) {
...BranchesFragment
}
}
${BRANCHES_FRAGMENT}
and returns <BranchesList /> component.
So, instead of passing branch.info from RootPage to RepositoryDetails and then to BranchesList;
You can do the following in BranchesList
const client = useApolloClient();
client.cache.readFragment({
fragment: BRANCHES_FRAGMENT,
fragmentName: "BranchesFragment",
id: "RefConnection:{}" // note this {} - apollow cache adds it when no id is present for the object
})
IMPORTANT!
Make sure to also update type policy for the field and set keyArgs to []
So in this particular case:
RefConnection: {
keyFields: []
...
}
This will give the same result, but you won't have to pass props to nested components and instead can read from cache directly (just like one would do using redux)

Can Apollo read partial fragments from cache?

I have a simple mutation editPerson. It changes the name and/or description of a person specified by an id.
I use this little snippet to call the mutator from React components:
function useEditPerson(variables) {
const gqlClient = useGQLClient();
const personFragment = gql`fragment useEditPerson__person on Person {
id
name
description
}`;
return useMutation(gql`
${personFragment}
mutation editPerson($id: ID!, $description: String, $name: String) {
editPerson(id: $id, description: $description, name: $name) {
...useEditPerson__person
}
}
`, {
variables,
optimisticResponse: vars => {
const person = gqlClient.readFragment({
id: vars.id,
fragment: personFragment,
});
return {
editPerson: {
__typename: "Person",
description: "",
name: "",
...person,
...vars,
},
};
},
});
}
This works well enough unless either the name or description for the indicated person hasn't yet been queried and does not exist in the cache; in this case person is null. This is expected from readFragment - any incomplete fragment does this.
The thing is I really need that data to avoid invariant errors - if they're not in the cache I'm totally okay using empty strings as default values, those values aren't displayed anywhere in the UI anyway.
Is there any way to read partial fragments from the cache? Is there a better way to get that data for the optimistic response?
I guess you use the snippet in the form that has all the data you need. So, you can pass the needed data to your useEditPerson hook through the arguments and then use in optimistic response, and then you won't need to use gqlClient.

Input type for array of objects as mutation input messes with optimistic response

On the server I built a schema where I used and input type to obtain the ability to pass an array of objects. I've done this in a couple of places but this is by far the most simple one:
export default gql`
input SkillInput{
skill: String!
}
extend type Mutation {
createSkill(input: [SkillInput]): [Skill]!
}
type Skill {
id: ID!
skill: String!
created_at: DateTime!
}
`;
On the frontend, I'm able to execute the mutation with said array of objects just fine. The issue comes when I try to incorporate optimistic response.
This isthe mutation in question:
this.$apollo
.mutate({
mutation: CREATE_SKILL_MUTATION,
variables: { input: skillArrOfObj },
optimisticResponse: {
__typename: "Mutation",
createSkill: skillArrOfObj.map(entry => ({
__typename: "Skill",
id: -1,
skill: entry.skill
}))
},
update: (store, { data: { createSkill } }) => {
const data = store.readQuery({
query: SKILLS_QUERY
});
console.log(createSkill);
data.skills.push(...createSkill);
store.writeQuery({
query: SKILLS_QUERY,
data
});
}
})
I've tried to add to each entry of skillArrOfObj the __typename and id, however the mutation fails.
Another thing to mention is that update runs twice and the log on createSkill yields two different results on update:
First run
{__typename: "Skill", id: -1, skillObj: Array(2)}
id: -1
skillObj: (2) [{…}, {…}]
__typename: "Skill"
Second run shows an array of just the id and the __typename with no skill attribute
Is there a special __typename needed for arrays? Or something I need to do before running the mutation?

How to chain mutations in apollo-client while using a form component

Apollo-Client 2.0. I am using chained Mutation components. I am trying to pass a returned value from the first Mutation to the second Mutation. I execute the mutations when an onSubmit button is clicked on a form component. The returned value from first mutation is not being passed as one of the "variables" in second mutation
I reviewed solutions in two very similar posts: How to wrap GraphQL mutation in Apollo client mutation component in React and How to chain together Mutations in apollo client. I think my use of a form is adding some additional complexity to my solution. Although the passed value (competitionId) is visible in the handleOnSubmit function (if I console log after createCompetition() in handleOnSubmit), it is not getting passed as a variable in the second Mutation which is called in the handleOnSubmit. The result is a successful execution of the first Mutation and a 400 error on the second mutation: “errors”:[{“message”:“Variable \“$competitionId\” of required type \“ID!\” was not provided.” To be more specific, the value of CompetitionId DOES get passed to the second mutation after the first mutation runs, but it does not get passed as a "variables" to the createMatch function passed as an argument to the handleOnSubmit. It looks like the "variables" passed along with the createMatch function to the handleOnSubmit, only include the variables that are available when the submit button is clicked. The competitionId, is generated after the submit button is clicked and the first mutation returns it as a result.
handleOnSubmit = async(event, createCompetition, createMatch) => {
event.preventDefault();
await createCompetition();
await createMatch();
this.clearState();
this.props.history.push('/home');
}
render () {
const {location, name, action, caliber, rifleName, dateOf,competitionScore} = this.state;
const { matchNumber, targetNumber, relay, distanceToTarget, matchScore} = this.state;
return (
<div className="App">
<h2 className="App">Add Competition</h2>
<Mutation
mutation={CREATE_COMPETITION}
variables={{location, name, action, caliber, rifleName, dateOf, competitionScore}}
refetchQueries={() => [
{ query: GET_ALL_COMPETITIONS, variables:{name: name}}
]}
update={this.updateCache}>
{(createCompetition, {data, loading, error}) => {
if(loading) return <div>loading competition...</div>
if(error) return <div>error: {error}</div>
let competitionId;
if(data) {
competitionId = data.createCompetition._id;
}
return (
<Mutation
mutation={CREATE_MATCH}
variables={{competitionId, matchNumber, targetNumber, distanceToTarget, matchScore}}>
{(createMatch, {_, loading, error}) => {
if(loading) return <div>loading match...</div>
return (
<form
className="form"
onSubmit={event => this.handleOnSubmit (event, createCompetition, createMatch)}>
<label> remaining form deleted for brevity
I expected the value of the CompetitionId to be passed as a variable to the createMatch function called in the handleOnSubmit method. It is not provided.
Seems what you needs is nested mutations :thinkingface;
Q: Are you using prisma?
Well, in GraphQL you can create nodes by a single mutation, this is pretty simple if your Types are related, so I assume this is your case.
And should looks something like this:
datamodel.graphql
type Competition {
id: ID! #unique
name: String!
match: Match! #relation(name: "CompetitionMatch")
}
type Match {
id: ID! #unique
name: String!
campetition: Competition! #relation(name: "CompetitionMatch")
}
So, now in your schema.graphql should looks like this:
type Mutation {
createCompetition (name: String! match: MatchInput): Competition
}
input MatchInput {
name: String!
}
and now when you call your createCompetition mutation, you have to send the match data, like so:
mutation createCompetition (
name: 'Loremp competition'
match: { name: 'child match'}
) {
id
name
match {
id
name
}
}
Ref: https://www.graph.cool/docs/reference/graphql-api/mutation-api-ol0yuoz6go/#nested-create-mutations
Hope this help!
regards
Where the if (data) is, is where you should return the 2nd mutation

GraphQL : query returns no result with no error

What could make a GraphGL Query return no result? Here, I have a component that receives the result of a query in its props via the compose() function of Apollo-React. For a reason I don't understand, the query returns no result for some $companyId and the component do not get the wished object in its props. With other given $companyId it works well.
The error object in props is set to 'undefined'.
When played independently (in the GraphiQl or the Graphcool console), the query works perfectly and returns a result.
When I remove the "aliasMail" field in the query, then it works fine all the time (for all given $companyId).
types.graphql
type Company #model {
id: ID! #isUnique
name: String
dateCreation: DateTime
dateUpdate: DateTime
active: Boolean #defaultValue(value: true)
aliasMail: String #defaultValue(value: "")
defaultClientAccountLabel: String #defaultValue(value: "")
defaultSupplierAccountLabel: String #defaultValue(value: "")
queries.js
query getCompanyForAliasEmailEditForm($companyId: ID) {
Company(id: $companyId) {
id
name
aliasMail
}
}
EmailSettingsContainer.jsx
import { compose, graphql } from "react-apollo";
import Queries from "../../queries/queries";
import EmailSettings from '../../components/Email/EmailSettings';
import React, { Component } from 'react';
class EmailSettingsContainer extends Component {
...
render(){
return <EmailSettings
companyToEdit={this.props.company}
/>
}
}
export default compose(
graphql(Queries.getCompanyForAliasEmailEditForm, { name: "company" })
)(EmailSettingsContainer);
Console
when it works:
PROPS : {companyToEdit: {…}, update: ƒ, sendNotification: ƒ, match: {…}, location: {…}, …}
companyToEdit:
Company:
aliasMail: "CHTIHOUSE"
id: "cje734xv29fzw0112pn6oxvt1"
name: "CHTIHOUSE"
__typename: "Company"
Symbol(id): "Company:cje734xv29fzw0112pn6oxvt1"
error: undefined
when it do not works:
PROPS : {companyToEdit: {…}, update: ƒ, sendNotification: ƒ, match: {…}, location: {…}, …}
companyToEdit: (Nothing)
error: undefined
My mistake was that I did not manage the 'loading data': while for other companyId the query result was already in cache, for others, the query had to get the data from the server. Rendering the component before the data is loaded was leading to my component crashing (as the data was not available yet).
Updating my component as follow solved my problem :
EmailSettingsContainer.jsx
import { compose, graphql } from "react-apollo";
import Queries from "../../queries/queries";
import EmailSettings from '../../components/Email/EmailSettings';
import React, { Component } from 'react';
class EmailSettingsContainer extends Component {
...
render(){
if (this.props.company.loading) {
return ( <div>Loading</div> )
} else {
return (
<EmailSettings
companyToEdit={this.props.company}
update={values => this.save(values)}
sendNotification={this.props.sendNotification}
/>
)
}
}
}
export default compose(
graphql(Queries.getCompanyForAliasEmailEditForm, { name: "company"}),
)(EmailSettingsContainer);

Resources