I have a relay container that conditionally fetches some fields using #include(if: $variable), but I have to write the directive for each individual field:
const relayOptions = {
initialVariables: {
expandDetails: false,
},
fragments: {
company: Relay.QL`
fragment on Company {
id
name
employees #include(if: $expandDetails) {
fullName
}
admins #include(if: $expandDetails) {
fullName
}
departments #include(if: $expandDetails) {
name
}
}
`,
},
}
Is there any way that I could write the directive only once, for example something like this (and I know this code won't work):
const relayOptions = {
initialVariables: {
expandDetails: false,
},
fragments: {
company: Relay.QL`
fragment on Company {
id
name
#include(if: $expandDetails) {
employees {
fullName
}
admins {
fullName
}
departments {
name
}
}
}
`,
},
}
I tried some other crazy ways but none of them worked, I got this error:
'#include' can't be applied to fragment definitions (allowed: fields, fragment spreads, inline fragments)
Edit: Note: I need to do that to avoid fetching data unnecessarily. I'd like to only fetch the data if the collapsible component displaying the data is expanded (planning to do that using setVariables after component is mounted).
In this example there are only 3 conditional fields, but in the actual case I'm trying to solve for there is a lot more than that.
You can put the directive on an inline fragment, and just use the same type as the outer field:
fragment on Company {
... on Company #include(if: $expandDetails) {
employees { fullName }
admins { fullName }
departments { fullName }
}
}
Related
I have query GetPosts:
query GetPosts {
posts {
id
description
owner {
id
}
files {
id
}
isPublished
}
}
I want to not fetch files and description fields if isPublished is false. I know I can use the #skip directive for it but not sure how to pass the field to it instead of the variable.
query GetPosts {
posts {
id
isPublished
description #skip(if: isPublished)
owner {
id
}
files #skip(if: isPublished) {
id
}
}
}
(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
}
}
}
}
}
}
}
Hi all I have a query where I am trying to get messages from a user with a specific uuid or a role that matches the users role. I am unsure of how to properly use the _ilike or % in this instance. I've tried a myriad of combinations yet the role messages never get returned. My query as it sits now and the hook used in the component are below.
I appreciate any feedback.
Here is my query
query getUserMessages($userId: String!) {
messageReceivers(
where: { _or: [{ userId: { _eq: $userId } }, { message: { roles: { _ilike: "%" } } }] }
) {
messageId
userId
message {
id
audioLink
body
videoLink
user {
firstName
lastName
photo
title
specialty
profession
location
}
}
}
}
Using the lazyquery hook in component
const [getUserMessages, { error, called, loading, data }] = useGetUserMessagesLazyQuery()
const userRole = `%${user.role}%`
useEffect(() => {
getUserMessages({
variables: { userId: user?.id, message: { roles: { _ilike: userRole } } },
})
}, [user])
You are incorrectly passing userRole to the query. To fix it, apply userId's pattern to userRole.
In the query definition, add $userRole in the operation signature (You are currently hardcoding _ilike to % in the query, but you want set it dynamically as $userRole).
In the calling function, send the variables correctly variables: { userId: user?.id, userRole: userRole}.
The GraphQL Variable docs neatly describe how this fits together.
Thanks #fedonev! Though I didn't see your solution you were absolutely correct. I was able to work it out a little differently and I hope this helps someone who's run into the same issue.
By creating the variable $role in the query I was able to use the same syntax as was being used by the userId variable. If anyone has this issue please feel free to comment I will happily help if I can.
Query
query getUserMessages($userId: String!, $role: String = "%") {
messages(
where: {
_or: [{ roles: { _ilike: $role } }, { messageReceivers: { userId: { _eq: $userId } } }]
}
order_by: { createdAt: desc }
) {
createdAt
id
body
audioLink
videoLink
roles
}
Call from in component
useEffect(() => {
getUserMessages({
variables: { userId: user?.id, role: user?.role },
})
}, [user])
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.
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>
);
}
}