fetching additional information for a particular list item in relay/graphql - graphql

Using Relay and GraphQL, let's say that I have a schema that returns a viewer, and an embedded list of associated documents. The root query (composed with fragments) would look like something like this:
query Root {
viewer {
id,
name,
groups {
edges {
node {
id,
name,
}
}
}
}
}
This will allow me to display the user, and a list of all of its associated groups.
Now let's say that I want the user to be able to click on that list item, and have it expand to show the comments associated with that particular list item. How should I restructure my query for the relay route such that I can receive those comments? If I add a comments edge to my groups edge, then won't it fetch the comments for all of the groups?
query Root {
viewer {
id,
name,
groups {
edges {
node {
id,
name,
comments {
edges {
node {
id,
content
}
}
}
}
}
}
}
}
Or should I alter the route query to find a specific group?
query Root {
group(id: "someid"){
id,
name,
comments {
edges {
node {
id,
content
}
}
}
},
viewer {
id,
name,
groups {
edges {
node {
id,
name,
}
}
}
}
}
My concern is, in particular, using this within the context of relay. I.e., how can I efficiently construct a route query that will only fetch the comments for the expanded list item (or items), while still taking advantage of the cached data that already exists, and will be updated when doing mutations? The above example might work for a specific expanded group, but I'm not sure how I could expand multiple groups simultaneously without fetching those fields for all of the group items.

Relay 0.3.2 will support the #skip and #include directives.
Group = Relay.createContainer(Group, {
initialVariables: {
numCommentsToShow: 10,
showComments: false,
},
fragments: {
group: () => Relay.QL`
fragment on Group {
comments(first: $numCommentsToShow) #include(if: $showComments) {
edges {
node {
content,
id,
},
},
},
id,
name,
}
`,
},
});
In your render method, only render comments if this.props.group.comments is defined. Invoke this.props.relay.setVariables({showComments: true}) in the Group component to cause the comments field to be included (and fetched, if necessary).
class Group extends React.Component {
_handleShowCommentsClick() {
this.props.relay.setVariables({showComments: true});
}
renderComments() {
return this.props.group.comments
? <Comments comments={this.props.group.comments} />
: <button onClick={this._handleShowCommentsClick}>Show comments</button>;
}
render() {
return (
<div>
...
{this.renderComments()}
</div>
);
}
}

Related

Contentful graphql one too many relationship

I am trying to achieve this: getArticleBySlugWithFilteredTags('tag1', 'tag2', 'tag3') using 1 query ( 1 request ) and avoid clientside filtering ( grab many and filter out with javascript ).
I have content type Article that has an entry type as list: Tag ( another custom content type ).
So there is a one too many relationship: an Article can have multiple Tags.
Now getting back to this: getArticleBySlugWithFilteredTags('tag1', 'tag2', 'tag3').
Attempt using custom content type: Tag
Query:
data: articleCollection(limit: 1, where: {
slug: "article-unique-1",
}) {
items {
title
tagsCollection(limit: 5) { // here it would be nice if I can use "where": {name: "tag1"}
items {
name
value
linkedFrom {
relatedArticles: articleCollection(limit: 7) { // other related articles that has the same tag as parent Article
items {
slug
title
category
}
}
}
}
}
}
}
}
The only thing that is missing here is the that I need to filter out the tagsCollection ( based on some property: name or value ).
I see that I am limited to use "where" on tagsCollection.
Attempt using contentfulMetadata tags
Query
{
data: articleCollection(where:
{
slug: "article-unique-1",
contentfulMetadata: {
tags_exists: true,
tags: {
id_contains_some: ["tag1", "tag2"]
}
}
}) {
items {
contentfulMetadata {
tags {
id
name
linkedFrom { // I can't use this here
relatedArticles: articleCollection(limit: 7) {
items {
slug
title
category
}
}
}
}
}
slug
title
publicationDate
}
}
}
With this approach I am not able to use the linkedFrom in order to get also other related articles that have the same contentfulMetadata tags. What should I do in other to achieve this making 1 query and no clientside filtering with javascript ?

GraphQL query filtering multiple relations

(From Strapi) I am trying to get all "acts" with a certain age (can return multiple) and with a certain place (can return multiple). I can't figure out how to filter that.
This is what I am trying in GraphQL-playground (works without the variables), but it says "Unknown argument "age" on field "Act.ages"." (and "place" respectively).
query GetActs ($age:Int, $place:String) {
acts {
data {
id
attributes {
Title
ages (age: $age) {
data {
id
attributes {
age
}
}
}
places (place: $place) {
data {
id
attributes {
place
}
}
}
}
}
}
}
I just ran into this same issue. I can't make out the error you're reporting, but here is what worked for me.
You can use filter at the collection level to drill down to nested fields for the corresponding attributes. This follows the GraphQL example at the bottom of this Strapi resource on filtering nested fields.
Solution
query GetActs ($age:Int, $place:String) {
acts (filters: {ages: {age: {eq: $age}}, places: {place: {eq: $place}}}) {
data {
id
attributes {
Title
ages {
data {
id
attributes {
age
}
}
}
places {
data {
id
attributes {
place
}
}
}
}
}
}
}

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.

Shopify GraphQL partial matching on query filter

I'm just getting started with the new Shopify GraphQL Admin API. I'm trying to retreive all products, where the title field contains a certain word.
Currently I can successfully retrieve a product by including the full product title (exact match):
{
shop {
id
name
}
products(first: 10, query:"title:'RAVEN DUSTY OLIVE/SILVER MESH'") {
edges {
node {
productType
title
}
}
}
}
However, I want to partially match the title to display all products with the word "Raven" anywhere in the title, but the following returns no results:
{
shop {
id
name
}
products(first: 10, query:"title:'RAVEN'") {
edges {
node {
productType
title
}
}
}
}
Any ideas on how to get the partial matching working?
Bjorn! This should work:
{
shop {
id
name
}
products(first: 10, query:"title:RAVEN*") {
edges {
node {
productType
title
}
}
}
}
Check out the docs: https://help.shopify.com/en/api/getting-started/search-syntax
Also you can try with
query: "title:*${searchText}*"
you can see two * at the initial and the end

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