Why is fragment data fetched but not accessible by Relay mutation - graphql

I have mutation (code) in which I want to delete a node. It has a dependency on the post rowId — which is the primary key of the row in the database — and the viewer id. When the pertaining component (code) gets rendered. The following queries are sent
query Queries {
viewer {
id,
...F1
}
}
fragment F0 on Viewer {
id
}
fragment F1 on Viewer {
id,
...F0
}
and
query Queries($id_0:ID!) {
post(id:$id_0) {
id,
...F2
}
}
fragment F0 on Post {
id,
rowId
}
fragment F1 on Post {
rowId,
id
}
fragment F2 on Post {
headline,
body,
id,
...F0,
...F1
}
The response I get includes the viewer.id and post.rowId. As you can see here,
{
"data": {
"post": {
"id": "cG9zdDo0",
"headline": "You hit me with a cricket bat.",
"body": "Hello.",
"rowId": 4
}
}
}
and here,
{
"data": {
"viewer": {
"id": "viewer"
}
}
}
However when I want to pass them to the DeletePostMutation like so this.props.post.id they are undefined. When I inspect this.props.post, I get the following

The error suggests that the props passed down to DeletePostMutation is not data fetched by Relay, and looking at the code it seems you are constructing a new object for the post and the viewer as opposed to sending the post and viewer fetched by relay.
I see you are doing this:
handleDelete(event) {
this.props.relay.commitUpdate(
new DeletePostMutation({
post: { rowId: this.props.post.rowId },
viewer: { id: this.props.viewer.id },
})
)
}
Try this instead:
handleDelete(event) {
this.props.relay.commitUpdate(
new DeletePostMutation({
post: this.props.post,
viewer: this.props.viewer,
})
)
}
Since you are already composing the GraphQL fragments of DeletePostMutation inside the Post Relay Container then inside DeletePostMutation each prop should have the fields defined in the fragments accessible.

Related

How to wire nested queries in Graph QL?

I would like to include the token names in results when querying the Uniswap v3 Subgraph, using the following query:
{
pools (top: 10) {
id,
feesUSD,
token0 {
id // do something like Token(id: token0.id) {symbol, name},
},
token1 {
id
}
}
}
Renders data like this:
{
"data": {
"pools": [
{
"feesUSD": "0.001849193372604300017804758202164034",
"id": "0x0001fcbba8eb491c3ccfeddc5a5caba1a98c4c28",
"token0": {
"id": "0xbef81556ef066ec840a540595c8d12f516b6378f"
},
"token1": {
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
}
},
The token0 and token1 hash IDs are returned and would like to return the Token.symbol values by passing in the token ID.
I only see single-level type queries on the Uniswap Subquery Examples page. How can this be accomplished?
Response from Graph QL is that this feature has been requested and is being considered.

Dynamically create pages with Gatsby based on many Contentful references

I am currently using Gatsby's collection routes API to create pages for a simple blog with data coming from Contentful.
For example, creating a page for each blogpost category :
-- src/pages/categories/{contentfulBlogPost.category}.js
export const query = graphql`
query categoriesQuery($category: String = "") {
allContentfulBlogPost(filter: { category: { eq: $category } }) {
edges {
node {
title
category
description {
description
}
...
}
}
}
}
...
[React component mapping all blogposts from each category in a list]
...
This is working fine.
But now I would like to have multiple categories per blogpost, so I switched to Contentful's references, many content-type, which allows to have multiple entries for a field :
Now the result of my graphQL query on field category2 is an array of different categories for each blogpost :
Query :
query categoriesQuery {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
Output :
{
"data": {
"allContentfulBlogPost": {
"edges": [
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
},
{
"id": "568r9e48-t1i8-sx4t8-9742-cdf70c4ed789vtu",
"name": "Test2",
"slug": "test-2"
}
]
}
},
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
}
]
}
},
...
Now that categories are inside an array, I don't know how to :
write a query variable to filter categories names ;
use the slug field as a route to dynamically create the page.
For blogposts authors I was doing :
query authorsQuery($author__slug: String = "") {
allContentfulBlogPost(filter: { author: { slug: { eq: $author__slug } } }) {
edges {
node {
id
author {
slug
name
}
...
}
...
}
And creating pages with src/pages/authors/{contentfulBlogPost.author__slug}.js
I guess I'll have to use the createPages API instead.
You can achieve the result using the Filesystem API, something like this may work:
src/pages/category/{contentfulBlogPost.category2__name}.js
In this case, it seems that this approach may lead to some caveats, since you may potentially create duplicated pages with the same URL (slug) because the posts can contain multiple and repeated categories.
However, I think it's more succinct to use the createPages API as you said, keeping in mind that you will need to treat the categories to avoid duplicities because they are in a one-to-many relationship.
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
`)
let categories= { slugs: [], names: [] };
result.data.allContentfulBlogPost.edges.map(({node}))=> {
let { name, slug } = node.category2;
// make some checks if needed here
categories.slugs.push(slug);
categories.names.push(name);
return new Set(categories.slugs) && new Set(categories.names);
});
categories.slugs.forEach((category, index) => {
let name = categories.names[index];
createPage({
path: `category/${category}`,
component: path.resolve(`./src/templates/your-category-template.js`),
context: {
name
}
});
});
}
The code's quite self-explanatory. Basically you are defining an empty object (categories) that contains two arrays, slugs and names:
let categories= { slugs: [], names: [] };
After that, you only need to loop through the result of the query (result) and push the field values (name, slug, and others if needed) to the previous array, making the needed checks if you want (to avoid pushing empty values, or that matches some regular expression, etc) and return a new Set to remove the duplicates.
Then, you only need to loop through the slugs to create pages using createPage API and pass the needed data via context:
context: {
name
}
Because of redundancy, this is the same than doing:
context: {
name: name
}
So, in your template, you will get the name in pageContext props. Replace it with the slug if needed, depending on your situation and your use case, the approach is exactly the same.

GraphQL Filter on Enum Column

Below is my GraphQL Query to Fetch Posts from Strapi backend.
Please note I am running this on my Nuxt app.
Now I want to bring only those posts which have post_status = "Publish"
post_status is a ENUM field with two option as Draft and Publish
query GetPosts{
posts {
id
post_title
post_excerpt
post_featured_image{url}
post_content
post_category{category_name}
postingredients{ingredient{ingredient_name}, ingredient_unit}
updated_at
post_author{username}
post_slug
}
}
I did not understand how can I get
How to bring post_status values on my original Query
How to filter on the post_status where I can get only Published posts.
query GetStatusEnum{
__type(name: "ENUM_POST_POST_STATUS") {
name
enumValues {
name
} } }
Result of the above:
{
"data": {
"__type": {
"name": "ENUM_POST_POST_STATUS",
"enumValues": [
{
"name": "Publish"
},
{
"name": "Draft"
}
]
}
}
}
To add your post_status in your original request you just have to add it in the list of the attributes you want to fetch.
{
posts {
id
post_title
post_status <- here /!\
}
}
Here is the query to fetch Posts that have Publish as post_status
{
posts(where: { post_status: "Publish" }) {
id
post_title,
post_status
}
}
You can play with GraphQL playground in your strapi application:
http://localhost:1337/graphql
You will see in the right of you page a docs button that will show you all the information you need to create your GraphQL request.
I had a similar scenario (though I'm using a Prisma layer as well so keep that in mind) and i'm not sure that you can filter for enum values on the call but you can filter what it returns.
const posts = [the array of all posts]
const isPublished = (post) => {
if (post.post_status.includes('Publish')) {
return post;
}
}
let publishedPosts = posts.filter(isPublished);
return publishedPosts;

Empty data object returns in <Query> after mutation is applied for the same

Github Issue Posted Here
"apollo-boost": "^0.1.13",
"apollo-link-context": "^1.0.8",
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"react-apollo": "^2.1.11",
Current Code Structure
<div>
<Query
query={FETCH_CATEGORIES_AUTOCOMPLETE}
variables={{ ...filters }}
fetchPolicy="no-cache"
>
{({ loading, error, data }) => {
console.log('category', loading, error, data); // _______Label_( * )_______
if (error) return 'Error fetching products';
const { categories } = data;
return (
<React.Fragment>
{categories && (
<ReactSelectAsync
{...this.props.attributes}
options={categories.data}
handleFilterChange={this.props.handleCategoryFilterChange}
loading={loading}
labelKey="appendName"
/>
)}
</React.Fragment>
);
}}
</Query>
<Mutation mutation={CREATE_CATEGORY}>
{createCategory => (
<div>
// category create form
</div>
)}
</Mutation>
</div>
Behavior
Initially, the query fetches data and I get list of categories inside data given in Label_( * ) .
After entering form details, the submission occurs successfully.
Issue: Then, suddenly, in the Label_( * ), the data object is empty.
How can I solve this?
Edit
These are the response:
Categories GET
{
"data": {
"categories": {
"page": 1,
"rows": 2,
"rowCount": 20,
"pages": 10,
"data": [
{
"id": "1",
"appendName": "Category A",
"__typename": "CategoryGETtype"
},
{
"id": "2",
"appendName": "Category B",
"__typename": "CategoryGETtype"
}
],
"__typename": "CategoryPageType"
}
}
}
Category Create
{
"data": {
"createCategory": {
"msg": "success",
"status": 200,
"category": {
"id": "21",
"name": "Category New",
"parent": null,
"__typename": "CategoryGETtype"
},
"__typename": "createCategory"
}
}
}
(I came across this question while facing a similar issue, which I have now solved)
In Apollo, when a mutation returns data that is used in a different query, then the results of that query will be updated. e.g. this query that returns all the todos
query {
todos {
id
description
status
}
}
Then if we mark a todo as completed with a mutation
mutation CompleteTodo {
markCompleted(id: 3) {
todo {
id
status
}
}
}
And the result is
{
todo: {
id: 3,
status: "completed"
__typename: "Todo"
}
}
Then the todo item with id 1 will have its status updated. The issue comes when the mutation tells the query it's stale, but doesn't provide enough information for the query to be updated. e.g.
query {
todos {
id
description
status
owner {
id
name
}
}
}
and
mutation CompleteTodo {
assignToUser(todoId: 3, userId: 12) {
todo {
id
owner {
id
}
}
}
}
Example result:
{
todo: {
id: 3,
owner: {
id: 12,
__typename: "User"
},
__typename: "Todo"
}
}
Imagine your app previously knew nothing about User:12. Here's what happens
The cache for Todo:3 knows that it now has owner User:12
The cache for User:12 contains just {id:12}, since the mutation didn't return the name field
The query cannot give accurate information for the name field for the owner without refetching (which doesn't happen by default). It updates to return data: {}
Possible solutions
Change the return query of the mutation to include all the fields that the query needs.
Trigger a refetch of the query after the mutation (via refetchQueries), or a different query that includes everything the cache needs
manual update of the cache
Of those, the first is probably the easiest and the fastest. There's some discussion of this in the apollo docs, but I don't see anything that describes the specific behavior of the data going empty.
https://www.apollographql.com/docs/angular/features/cache-updates.html
Tip of the hat to #clément-prévost for his comment that provided a clue:
Queries and mutations that fetch the same entity must query the same
fields. This is a way to avoid local cache issues.
After changing fetchPolicy to cache-and-network. It solved the issue. Link to fetchPolicy Documentation
Doing so, I also had to perform refetch query.

Relay mutation fragments intersection

I don't use Relay container, because I'd like to have more control over components. Instead of it I use HOC + Relay.Store.forceFetch, that fetches any given query with variables. So I have the following query:
query {
root {
search(filter: $filter) {
selectors {
_id,
data {
title,
status
}
},
selectorGroups {
_id,
data {
title,
}
}
}
}
}
Then I have to do some mutation on selector type.
export default class ChangeStatusMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation {selectors_status_mutation}`;
}
getVariables() {
return {
id: this.props.id,
status: this.props.status
};
}
getFatQuery() {
return Relay.QL`
fragment on selectors_status_mutationPayload{
result {
data {
status
}
}
}
`;
}
static fragments = {
result: () => Relay.QL`
fragment on selector {
_id,
data {
title,
status
}
}`,
};
getOptimisticResponse() {
return {
result: {
_id: this.props.id,
data: {
status: this.props.status
}
}
};
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
result: this.props.id
},
}];
}
}
Call mutation in component:
const mutation = new ChangeStatusMutation({id, status, result: selector});
Relay.Store.commitUpdate(mutation);
After mutation commitment selector in Relay storage is not changed. I guess that's because of empty Tracked Fragment Query and mutation performs without any fields:
ChangeStatusMutation($input_0:selectors_statusInput!) {
selectors_status_mutation(input:$input_0) {
clientMutationId
}
}
But the modifying selector was already fetched by Relay, and I pass it to the mutation with props. So Relay knows the type, that should be changed, how to find the item and which fields should be replaced. But can not intersect. What's wrong?
So, you're definitely a bit "off the ranch" here by avoiding Relay container, but I think this should still work...
Relay performs the query intersection by looking up the node indicated by your FIELDS_CHANGE config. In this case, your fieldIDs points it at the result node with ID this.props.id.
Are you sure you have a node with that ID in your store? I'm noticing that in your forceFetch query you fetch some kind of alternative _id but not actually fetching id. Relay requires an id field to be present on anything that you later want to refetch or use the declarative mutation API on...
I'd start by checking the query you're sending to fetch whatever this result type is. I don't see you fetching that anywhere in your question description, so I'm just assuming that maybe you aren't fetching that right now?

Resources