How to query Wordpress relational fields with GraphQL? - reactjs

I am facing an issue while trying to query Custom Post Type of type "relation ship" from Wordpress with GraphQL.
When I try to query this kind of field of type "post" it just results into this error, and it breaks all the query:
"message": "Abstract type WpPostObjectUnion must resolve to an Object type at runtime for field WpPage_HomepageContent_heroSlider.buttonLink with value { typename: "ActionMonitorAction" }, received "undefined". Either the WpPostObjectUnion type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.",
This is the part of the query making troubles:
query HomePageQuery {
allWpPage(filter: {slug: {eq: "home"}}) {
nodes {
slug
homepage_content {
heroSlider {
buttonText
buttonLink {
__typename
... on WpPost {
id
}
}
}
}
}
}
}
Any idea about how to solve this or a direction to do some researches ?
Thanks!

I found a solution to get the datas I need for Acf field of type "Relational : Link to page or article" like that:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type WpPage_HomepageContent_heroSlider implements Node {
buttonLink: WpPost
}
`
createTypes(typeDefs)
}
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
Query: {
allButtonLink: {
type: ["WpPage_HomepageContent_heroSlider"],
resolve(source, args, context, info) {
return context.nodeModel.getAllNodes({ type: "ActionMonitorAction" })
},
},
},
}
createResolvers(resolvers)
}
But it's an heavy solution and I think that would involve do the same for each Acf field of this type...
The best solution: I finnaly decided to set our ACF link field to type "Relational: Link".
This way it's simpler, I don't need any "createSchemaCustomization" or "createResolvers" workaround to get my slug in my buttonLink field in GraphQL and contributors can still easely select articles in Wordpress backoffice.
buttonLink now return me:
"buttonLink": {
"url": "/2020/09/04/article-test/",
"target": "",
"title": "Article test avec image en avant"
}
And that's all what I need

Related

Passing ID in GraphQL query not returning data

so i'm trying to use Apollo GraphQL with React to get specific product data by its ID, but it seems to be returning undefined. I read the Apollo docs and researched, so I'm not sure what I'm doing wrong. Also, I'm able to return data from other queries that don't require an ID (like all products, for instance). Would greatly appreciate some help!
Query
export const PRODUCT = gql`
query GetProduct($itemID: String!) {
product(id: $itemID) {
id
name
inStock
gallery
description
category
attributes {
id
name
type
items {
displayValue
value
}
}
prices {
currency {
label
symbol
}
}
brand
}
}
`;
This is where I try to return data using the ID, but to no avail:
let myID = "ps-5";
const { productLoading, productError, productData } = useQuery(PRODUCT, {
variables: { itemID: myID },
});
useEffect(() => {
if (productData) {
console.log("data: " + productData) // logs nothing. "Undefined" when if statement is removed
}
}, [])
It looks like the React client for Apollo uses the same API for useQuery as for Vue (with which I'm more familiar), in which case it should be used like this:
useQuery(PRODUCT, { itemID: myID })
(not { variables : { itemID : myID }})
I would have expected the backend to return an error though, because $itemID is declared as non-nullable.
It seems that you are destructing the object that useQuery() returns with the wrong object keys.
// instead of
const { productLoading, productError, productData } = '...'
// you can either use the regular keys as variables
const { loading, error, data } = '...'
// or assign aliases (useful when you use more queries on the same page)
// this way you can use the same variables as in your example
const { loading:productLoading, error:productError, data:productData } = '...'

Apollo Client is not reading variables passed in using useQuery hook

Having a weird issue passing variables into the useQuery hook.
The query:
const GET_USER_BY_ID= gql`
query($id: ID!) {
getUser(id: $id) {
id
fullName
role
}
}
`;
Calling the query:
const DisplayUser: React.FC<{ id: string }> = ({ id }) => {
const { data, error } = useQuery(GET_USER_BY_ID, {
variables: { id },
});
return <div>{JSON.stringify({ data, error })}</div>;
};
Rendering the component:
<DisplayUser id="5e404fa72b819d1410a3164c" />
This yields the error:
"Argument \"id\" of required type \"ID!\" was provided the variable \"$id\" which was not provided a runtime value."
Calling the query from GraphQL Playground returns the expected result:
{
"data": {
"getUser": {
"id": "5e404fa72b819d1410a3164c",
"fullName": "Test 1",
"role": "USER"
}
}
}
And calling the query without a variable but instead hard-coding the id:
const GET_USER_BY_ID = gql`
query {
getUser(id: "5e404fa72b819d1410a3164c") {
id
fullName
role
}
}
`;
const DisplayUser: React.FC = () => {
const { data, error } = useQuery(GET_USER_BY_ID);
return <div>{JSON.stringify({ data, error })}</div>;
};
Also returns the expected result.
I have also attempted to test a similar query that takes firstName: String! as a parameter which also yields an error saying that the variable was not provided a runtime value. This query also works as expected when hard-coding a value in the query string.
This project was started today and uses "apollo-boost": "^0.4.7", "graphql": "^14.6.0", and "react-apollo": "^3.1.3".
[Solved]
In reading through the stack trace I noticed the issue was referencing graphql-query-complexity which I was using for validationRules. I removed the validation rules and now everything works! Granted I don't have validation at the moment but at least I can work from here. Thanks to everyone who took the time to respond!
I had also ran into a similar issue and was not really sure what was happening.
There seems to be similar problem reported here - https://github.com/apollographql/graphql-tools/issues/824
We have 2 options to fix the issue.
- First one is a simple fix, where in you don't make the ID mandatory when it takes only a single parameter ( which is not an object )
const GET_USER_BY_ID= gql`
query($id: ID) {
Second option is to use an object as a parameter instead of a primitive. I went ahead with this and it seemed to work fine for me even though I made the object and the property inside to be required.
// On the client
const GET_USER_BY_ID= gql`
query($input: GetUserInput!) {
getUser(input: $input) {
id
fullName
role
}
}`;
const { data, error } = useQuery(GET_USER_BY_ID, {
variables: { input: { id }},
});
// In the server, define the input type
input GetUserInput {
id: ID!
}
Try
const { data, error } = useQuery(GET_USER_BY_ID, { id });

How to query relational data with GraphQL, Firebase and Gatsby

I'm building a Gatsby.js site.
The site uses the gatsby-source-firestore plugin to connect to the Firestore data source.
My question is this. How can I query relational data? As in, fetch data from two models at once, where modelA[x] = modelB[y]
I don't really understand resolvers. I don't think I have any.
Note, I am not considering graph.cool currently. I'd like to stick with Firebase. I will do the relational data matching in pure JS if I have to (not GraphQL).
Here is what my gatsby-config.js looks like:
{
resolve: 'gatsby-source-firestore',
options: {
credential: require('./firebase-key.json'),
databaseURL: 'https://testblahblah.firebaseio.com',
types: [
{
type: 'Users',
collection: 'users',
map: user => ({
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
ownsHat: user.ownsHat,
hatId: user.hatId
})
},
{
type: 'Hats',
collection: 'hats',
map: hat => ({
hatType: hat.hatType,
hatUserId: hat.hatUserId,
hatId: hat.hatId
})
}
]
}
},
This pulls in two flat data models. I can query like this in-page:
any-page.js
export const query = graphql`
query {
allUsers {
edges {
node {
...UserFragment
}
}
}
}
`
What I'm looking for is a query that lets me write one query inside another i.e. a relational data query within a query.
export const query = graphql`
query {
allUsers {
edges {
node {
...UserFragment {
hats (user.userId == hat.userId){
type
hatId
}
}
}
}
}
}
`
As you can understand, this amounts to: How to run multiple GraphQL queries of relational data.
Given the nature of Firestore's flat JSON, this makes the relational aspect of GraphQL difficult.
I'm really keen to understand this better and would really appreciate being pointed down the right path.
I am really keen on sticking with GraphQL and Firebase.
Thanks!
I'm not sure this works in graphql but in Gatsby you can use gatsby-node to create and alter your nodes and inject hats to each user node. Here's an example code I'm using to add authors to a Post node:
const mapAuthorsToPostNode = (node, getNodes) => {
const author = getPostAuthorNode(node, getNodes);
if (author) node.authors___NODES = [author.id];
};
exports.sourceNodes = ({actions, getNodes, getNode}) => {
const {createNodeField} = actions;
getCollectionNodes('posts', getNodes).forEach(node => {
mapAuthorsToPostNode(node, getNodes);
});
};
This is one way to do it provided the records are not in huge numbers. If they are, you should create a hats page to display user hats where you query just the hats filtered by user id which is received via a paceContext param such as user id.
export const pageQuery = graphql`
query($userId: String) {
hats: allHats(
filter: {
userId: {eq: $userId}
}
) {
edges {
node {
...hatFragment
}
}
}
}
`;

refetchContainer gives same results with or without force in refetchOptions in Relay Modern

In Relay Modern refetchContainer gives same results with refetchOptions: { force: true } and refetchOptions: { force: false }.
I have a refetch container like following:
export default createRefetchContainer(radium(UserPicker), graphql`
fragment UserPicker_company on Company
#argumentDefinitions(
searchString: { type: "String", defaultValue: "" }
) {
userSearch(text: $searchString, first: 10) {
edges {
node {
id
name
email
picture
}
}
}
}
`, graphql `
query UserPickerRefetchQuery($companyId: ID, $searchString: String) {
company(id: $companyId) {
...UserPicker_company #arguments(searchString: $searchString)
}
}
`);
in the onChange method for input tag I have the following code:
onInputChange = (e) => {
const value = e.target.value;
this.props.relay.refetch({ searchString: value });
}
Relay does network query when I type new character and also when I delete a character. Ideally when characters are deleted it should use the same data that it queried previously.
Relay Modern does not implement a client side cache configuration by default for network request. The idea is to give users more control by allowing them to choose which queries to cache and which not.
Some implementation proposition using RelayQueryResponseCache can be found here: https://github.com/facebook/relay/issues/1687#issuecomment-302931855

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