How to updateQueries when the Query contain edges and node with Apollo? - reactjs

I use react-apollo on my React application for request a GraphQL API. I want to add an item and update my query after this add.
The item is added correctly but I can't update my query. I have to refresh my page to see my new item. I follow this guide.
This is my query:
export const allCodeReviews = gql`
query allCodeReviews {
allCodeReviews {
edges {
node {
id
reference
reviewer
revisionDate
redmineUrl
flow {
id
name
}
}
}
}
}
and this is my mutation which add my new item:
this.props.mutate({
variables: {
"reviewer": "john",
"revisionDate": "2016-11-11",
"flow": "foo",
"redmineUrl": "foo.com",
"reference": "#bar"
},
updateQueries: {
allCodeReviews: (prev, {mutationResult}) => {
const addedReview = mutationResult.data.addCodeReview.codeReview;
console.log(addedReview);
console.log(prev);
return update(prev, {
allCodeReviews: {
edges: {
$unshift: [addedReview]
}
}
});
}
}
});
How to update this query correctly?

I think you should try with: $unshift: [{node: addedReview}]. Depending on your addCodeReview mutation.

Related

GraphQL on component load, Query will sometimes return populated properties as null

I am using graphQl with react with apollo client and mongoose. Sometimes when I click on a component. rand om data will return from this useQuery as null.
// must define novel as a state to use useEffect correctly
const [novel, setNovel] = useState({});
const { loading, data } = useQuery(GET_NOVEL, {
variables: { _id: novelId }
});
// use effect ensures that all novel data is completely loaded
// before rendering the SingleNovel page
useEffect(() => {
console.log(data?.novel);
// if there's data to be stored
if (data) {
setNovel(data.novel)
}
}, [data, loading, novel]);
export const GET_NOVEL = gql`
query getNovel($_id: ID!) {
novel(_id: $_id) {
_id
title
description
penName
user {
_id
username
email
}
favorites{
_id
}
createdAt
reviews {
_id
reviewText
rating
createdAt
user{
_id
username
}
}
chapterCount
reviewCount
}
}
`
Specifically, the novel.user.username and reviews.rating property come back as null. On reload of the page however, the data seems to populate the fields normally.
How can I fix this?
Heres the resolver
novel: async (parent, { _id }) => {
// returns single novel from the novel id given
const novel = await Novel.findOne({ _id })
.populate('user')
// populate the reviews for the novel but also populate
// the info within the reviews of the user who made each review.
.populate({
path: 'reviews',
populate: {path: 'user'}
})
.exec();
console.log(novel)
return novel;
},

how to make currentPage dynamic on pageInfo gatsby graphql?

good day. Im using gatsby in the front and strapi as backend. Im trying to make a media gallery with simple pagination to select images for a custom component.
I get the images and using this query
query MyQuery($currentPage: Int!) {
allStrapiMedia(limit: 20,currentPage:$currentPage ) {
edges {
node {
name
}
}
pageInfo {
itemCount
hasNextPage
pageCount
currentPage
perPage
totalCount
hasPreviousPage
}
}
}
but i get this error:
{
"errors": [
{
"message": "Unknown argument \"currentPage\" on field \"Query.allStrapiMedia\".",
"locations": [
{
"line": 2,
"column": 28
}
],
"stack": [
"GraphQLError: Unknown argument \"currentPage\" on field \"Query.allStrapiMedia\".",
" at Object.Argument (/Users/alexeigarban/Documents/projects/platformable/platformablecodegenerator copy/node_modules/graphql/validation/rules/KnownArgumentNamesRule.js:46:29)",
" at Object.enter (/Users/alexeigarban/Documents/projects/platformable/platformablecodegenerator copy/node_modules/graphql/language/visitor.js:323:29)",
" at Object.enter (/Users/alexeigarban/Documents/projects/platformable/platformablecodegenerator copy/node_modules/graphql/utilities/TypeInfo.js:370:25)",
" at visit (/Users/alexeigarban/Documents/projects/platformable/platformablecodegenerator copy/node_modules/graphql/language/visitor.js:243:26)",
" at validate (/Users/alexeigarban/Documents/projects/platformable/platformablecodegenerator copy/node_modules/graphql/validation/validate.js:69:24)",
" at /Users/alexeigarban/Documents/projects/platformable/platformablecodegenerator copy/node_modules/express-graphql/index.js:121:32",
" at processTicksAndRejections (internal/process/task_queues.js:95:5)"
]
}
],
"extensions": {}
}
if i remove the currentPage var it works but im not able to go to currentPage:2 , ...+, that means that i only can see the first 20.
What am i missing?
thanks for your time
Ensure you are passing the required arguments from teh apollo client
ex.
this.queryArticles = this.apollo
.watchQuery({
query: ARTICLES_QUERY,
variables: {
start: start,
limit: this.limit
}
})
.valueChanges.subscribe(result => {
this.data = result.data;
this.articles = this.data.articles;
this.loading = result.loading;
this.errors = result.errors;
});
}
Ref: https://www.codinghub.net/article/pagination-integration-with-strapi-angular-and-graphql
While filtering pls use offset i.e, start index for the record index
articles(start:$start, limit:$limit, sort:"published_at:desc")
Ref: https://www.codinghub.net/article/pagination-integration-with-strapi-angular-and-graphql
It seems that you are not passing the currentPage value in a specific loop (maybe the edge cases, first or last), from your page creation to your templates. In addition, you are not using this value to filter the query in allStrapiMedia.
In your gatsby-node.js file, you should get the currentPage but this information is missing in the question.
Assuming a markdown approach:
const path = require("path")
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const queryResults = await graphql(`
query allPages{
allPages {
nodes {
id
currentPage
}
}
}
`)
const productTemplate = path.resolve(`src/templates/product.js`)
queryResults.data.allPages.nodes.forEach(node => {
createPage({
path: `/page/${node.id}`,
component: pageTemplate,
context: {
currentPage: currentPage
},
})
})
}
Note: tweak it to adapt it to your Strapi environment but get the idea.
The idea is to pass via context the currentPage value extracted from the GraphQL query for all cases, included the edge cases.

ApolloClient v3 fetchMore with nested query results

I'm using ApolloClient 3 the GitHub GraphQL API to retrieve all releases from a repo.
This is what the query looks like:
query ($owner: String!, $name: String!, $first: Int, $after: String, $before: String) {
repository(owner: $owner, name: $name) {
id
releases(orderBy: {field: CREATED_AT, direction: DESC}, first: $first, after: $after, before: $before) {
nodes {
name
publishedAt
resourcePath
tagName
url
id
isPrerelease
description
descriptionHTML
}
totalCount
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
}
This is what the result payload looks like:
This returns me the first x entries (nodes). So far, all good.
I need to implement pagination and I make use of the fetchMore function provided by ApolloClient useQuery. Calling fetchMore fetches the next x entries successfully but these are not displayed in my component list.
According to the ApolloClient Pagination documentation, it seems necessary to handle the merging of the fetchMore results with the ApolloClient caching mechanism. The documentation is understandable for simple situations but I am struggling to implement a solution for the situation where the actual array of results that needs to be merged togeher is deeply nested in the query result (repository -> releases -> nodes).
This is my implementation of the InMemoryCache options merge:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
ReleaseConnection: {
fields: {
nodes: {
merge(existing, incoming, options) {
const previous = existing || []
const results = [...previous, ...incoming]
return results
}
}
}
},
}
}
The results array here contains the full list, including the existing entries and the new x entries. This is essentially the correct result. However, my component list which is using the useQuery and fetchMore functionality does not get the new entries after the fetchMore is called.
I have tried various combinations in the inMemoryCacheOptions code above but so far I have been unsuccessful.
To add more context, this is the related component code:
export default function Releases() {
const { loading, error, data, fetchMore } = useQuery(releasesQuery, {
variables: {
owner: "theowner",
name: "myrepo",
first: 15
}
});
if (loading) return null;
if (error) {
console.error(error);
return null;
}
if (data) {
console.log(data?.repository?.releases?.pageInfo?.endCursor);
}
const handleFetchMore = () => {
fetchMore({
variables: {
first: 15,
after: data?.repository?.releases?.pageInfo?.endCursor
}
});
};
return (
<div>
<ul>
{data?.repository?.releases?.nodes?.map(release => (
<li key={release.id}>{release.name}</li>
))}
</ul>
<button onClick={handleFetchMore}>Fetch More</button>
</div>
);
}
After fetchMore the component doesn't rerender with the new data.
If anyone has any other ideas that I could try, I'd be grateful.
I finally managed to solve this. There was no change to the react component code but the InMemoryCacheOptions now looks like this:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
Repository: {
fields: {
releases: {
keyArgs: false,
merge(existing, incoming) {
if (!incoming) return existing;
if (!existing) return incoming;
const { nodes, ...rest } = incoming;
// We only need to merge the nodes array.
// The rest of the fields (pagination) should always be overwritten by incoming
let result = rest;
result.nodes = [...existing.nodes, ...nodes];
return result;
}
}
}
}
}
};
The main change from my original code is that I now define the typePolicy for the releases field of the Repository type. Previously I was trying to get directly to the nodes field of the Release type. Since my Repository type the root of the gql query and used in the component, it now reads the merged results from the cache.
If I specified the typePolicy for Query as mentioned in the docs, I would not be able to specify the merge behaviour for the releases field because it would be one level too deep (i.e. Query -> repository -> releases). This is what lead to my confusion in the beginning.

GatsbyJS getting data from Restful API

I am new in both React and GatsbyJS. I am confused and could not make figuring out in a simple way to load data from third-party Restful API.
For example, I would like to fetch data from randomuser.me/API and then be able to use the data in pages.
Let’s say something like this :
import React from 'react'
import Link from 'gatsby-link'
class User extends React.Component {
constructor(){
super();
this.state = {
pictures:[],
};
}
componentDidMount(){
fetch('https://randomuser.me/api/?results=500')
.then(results=>{
return results.json();
})
.then(data=>{
let pictures = data.results.map((pic,i)=>{
return(
<div key={i} >
<img key={i} src={pic.picture.medium}/>
</div>
)
})
this.setState({pictures:pictures})
})
}
render() {
return (<div>{this.state.pictures}</div>)
}
}
export default User;
But I would like to get the help of GraphQL in order to filter & sort users and etc…..
Could you please help me to find the sample to how I can fetch data and insert them into GraphQL on gatsby-node.js?
If you want to use GraphQL to fetch your data, you have to create a sourceNode. The doc about creating a source plugin could help you.
Follow these steps to be able to query randomuser data with GraphQL in your Gatsby project.
1) Create nodes in gatsby-node.js
In your root project folder, add this code to gatsby-node.js:
const axios = require('axios');
const crypto = require('crypto');
exports.sourceNodes = async ({ actions }) => {
const { createNode } = actions;
// fetch raw data from the randomuser api
const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
const res = await fetchRandomUser();
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last,
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail,
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(userNode))
.digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
return;
}
I used axios to fetch data so you will need to install it: npm install --save axios
Explanation:
The goal is to create each node for each piece of data you want to use.
According to the createNode documentation, you have to provide an object with few required fields (id, parent, internal, children).
Once you get the results data from the randomuser API, you just need to create this node object and pass it to the createNode() function.
Here we map to the results as you wanted to get 500 random users https://randomuser.me/api/?results=500.
Create the userNode object with the required and wanted fields.
You can add more fields depending on what data you will want to use in your app.
Just create the node with the createNode() function of the Gatsby API.
2) Query your data with GraphQL
Once you did that, run gatsby develop and go to http://localhost:8000/___graphql.
You can play with GraphQL to create your perfect query. As we named the internal.type of our node object 'RandomUser', we can query allRandomUser to get our data.
{
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
3) Use this query in your Gatsby page
In your page, for instance src/pages/index.js, use the query and display your data:
import React from 'react'
import Link from 'gatsby-link'
const IndexPage = (props) => {
const users = props.data.allRandomUser.edges;
return (
<div>
{users.map((user, i) => {
const userData = user.node;
return (
<div key={i}>
<p>Name: {userData.name.first}</p>
<img src={userData.picture.medium} />
</div>
)
})}
</div>
);
};
export default IndexPage
export const query = graphql`
query RandomUserQuery {
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
`;
That is it!
Many thanks, this is working fine for me, I only change small parts of the gastbyjs-node.js because it makes an error when use sync & await, I think I need change some section of a build process to use babel to allow me to use sync or await.
Here is the code which works for me.
const axios = require('axios');
const crypto = require('crypto');
// exports.sourceNodes = async ({ boundActionCreators }) => {
exports.sourceNodes = ({boundActionCreators}) => {
const {createNode} = boundActionCreators;
return new Promise((resolve, reject) => {
// fetch raw data from the randomuser api
// const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
// const res = await fetchRandomUser();
axios.get(`https://randomuser.me/api/?results=500`).then(res => {
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto.createHash(`md5`).update(JSON.stringify(userNode)).digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
resolve();
});
});
}
The accepted answer for this works great, just to note that there's a deprecation warning if you use boundActionCreators. This has to be renamed to actions to avoid this warning.
You can get data at the frontend from APIs using react useEffect. It works perfectly and you will no longer see any error at builtime
const [starsCount, setStarsCount] = useState(0)
useEffect(() => {
// get data from GitHub api
fetch(`https://api.github.com/repos/gatsbyjs/gatsby`)
.then(response => response.json()) // parse JSON from request
.then(resultData => {
setStarsCount(resultData.stargazers_count)
}) // set data for the number of stars
}, [])
The answers given above work, except the query in step 2 seems to only return one node for me. I can return all nodes by adding totalCount as a sibling of edges. I.e.
{
allRandomUser {
totalCount
edges {
node {
id
gender
name {
first
last
}
}
}
}
}

readQuery not working with pagination in Apollo & GraphQL app

I've got the following setup for my app. I have a LinkList component that renders a list of Link components. Then I also have a CreateLink component to create new links. Both are rendered under different routes with react-router:
<Switch>
<Route exact path='/create' component={CreateLink}/>
<Route exact path='/:page' component={LinkList}/>
</Switch>
The Link type in my GraphQL schema looks as follows:
type Link implements Node {
url: String!
postedBy: User! #relation(name: "UsersLinks")
votes: [Vote!]! #relation(name: "VotesOnLink")
comments: [Comment!]! #relation(name: "CommentsOnLink")
}
I'm using Apollo Client and want to use the imperative store API to update the list after new Link was created in the CreateLink component.
await this.props.createLinkMutation({
variables: {
description,
url,
postedById
},
update: (store, { data: { createLink } }) => {
const data = store.readQuery({ query: ALL_LINKS_QUERY }) // ERROR
console.log(`data: `, data)
}
})
The problem is that store.readQuery(...) throws an error:
proxyConsole.js:56 Error: Can't find field allLinks({}) on object (ROOT_QUERY) {
"allLinks({\"first\":2,\"skip\":10})": [
{
"type": "id",
"id": "Link:cj3ucdguyvzdq0131pzvn37as",
"generated": false
}
],
"_allLinksMeta": {
"type": "id",
"id": "$ROOT_QUERY._allLinksMeta",
"generated": true
}
}.
Here is how I am fetching the list of links in my LinkList component:
export const ALL_LINKS_QUERY = gql`
query AllLinksQuery($first: Int, $skip: Int) {
allLinks(first: $first, skip: $skip) {
id
url
description
createdAt
postedBy {
id
name
}
votes {
id
}
}
_allLinksMeta {
count
}
}
`
export default graphql(ALL_LINKS_QUERY, {
name: 'allLinksQuery',
options: (ownProps) => {
const { pathname } = ownProps.location
const page = parseInt(pathname.substring(1, pathname.length))
return {
variables: {
skip: (page - 1) * LINKS_PER_PAGE,
first: LINKS_PER_PAGE
},
fetchPolicy: 'network-only'
}
}
}) (LinkList)
I am guessing that the issue somehow has to do with my pagination approach, but I still don't know how to fix it. Can someone point me into the right direction here?
How to read a paginated list from the store depends on how you do the pagination. If you're using fetchMore, then all the data will be stored under the original keys of the query, which in this case I guess was fetched with { first: 2, skip: 0 }. That means in order to read the updated list from the store, you would have to use the same parameters, using { first: 2, skip: 0 } as variables.
PS: The reason Apollo does it this way is because it still allows you to relatively easily update a list via a mutation or update store. If each page was stored separately, it would be very complicated to insert an item in the middle or the beginning of the list, because all of the pages would potentially have to be shifted.
That said, we might introduce a new client-side directive called #connection(name: "ABC") which would let you explicitly specify under which key the connection is to be stored, instead of automatically storing it under the original variables. Happy to talk more about it if you want to open an issue on Apollo Client.

Resources