How to pass data from a GraphQL query onto a programmatically generated page? - graphql

I am a beginner with GraphQL and I'm having trouble accessing the data from my GraphQL query. I have been able to console log the data, which shows that the query is working but when I try to access it I get the error message "Cannot read property 'page_name' of undefined"
See images of variations I have tried:
https://i.ibb.co/6JS20K5/Graph-QL-1.png
https://i.ibb.co/wMq1V6g/Graph-QL-2.png
https://i.ibb.co/MN5g5Pk/Graph-QL-3.png
I can see my data in the console when I use:
console.log(data.allPagesJson.edges)
My understanding is that, in order to access the exact data I need, I should use:
console.log(data.allPagesJson.edges.node.page_name)
However, this now gives me the error message "Cannot read property 'page_name' of undefined". The reason for this seems to be that "node" is undefined but I'm not sure why...
This is my pages template:
import React from "react"
import { graphql } from "gatsby"
export default ({ data }) => {
console.log(data.allPagesJson.edges.node.page_name)
return (
<div>
<h1>Test</h1>
</div>
)
}
export const query = graphql`
query($page_url: String!) {
allPagesJson(filter: { page_url: { eq: $page_url } }) {
edges{
node{
page_url
page_name
}
}
}
}
`
This is my pages gatsby-node.js file:
const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return graphql(`
{
allPagesJson {
edges {
node {
page_url
}
}
}
}
`
).then(result => {
result.data.allPagesJson.edges.forEach(({ node }) => {
createPage({
path: node.page_url,
component: path.resolve(`./src/templates/product-pages.js`),
context: {
page_url: node.page_url,
},
})
})
})
}

I managed to work it out. I can access the data using:
<div>
{data.allPagesJson.edges.map(({ node }) => (
<div key={node.id}>
<h1>{node.page_name}</h1>
</div>
))}
</div>

Related

strapi-girdsome starter issues

When following official strapi-gridsome tutorial, trying to create new pages from ID numbers
Steps to reproduce:
npm: '6.14.9',
node: '14.14.0',
strapi '3.5.0'
Expected result:
http://localhost:8080/categories/1
should show data from API but instead shows 404 page
Actual result:
When running gridsome develop...
ERROR Failed to compile with 1 errors
This dependency was not found:
..\my-gridsome-site\src\templates\Category.vue in ./src/.temp/routes.js
gridsome.server.js
module.exports = function (api) {
api.loadSource(({ addCollection }) => {
// Use the Data Store API here: https://gridsome.org/docs/data-store-api/
})
api.createPages(async ({ graphql, createPage }) => {
const { data } = await graphql(`
{
allStrapiCategory {
edges {
node {
id
name
}
}
}
}
`);
const categories = data.allStrapiCategory.edges;
categories.forEach(category => {
createPage({
path: `/categories/${category.node.id}`,
component: './src/templates/Category.vue',
context: {
id: category.node.id,
},
});
});
});
};
src\templates\category.vue
<template>
<Layout>
<div>
<h1>{{ $page.category.name }}</h1>
<ul>
<li v-for="restaurant in $page.category.restaurants">{{ restaurant.name }}</li>
</ul>
</div>
</Layout>
</template>
<page-query>
query Category($id: ID!) {
category: strapiCategory(id: $id) {
name
restaurants {
id
name
}
}
}
</page-query>
I had this same issue.
I was running yarn develop and hot-reload was working for general playing with data, layout etc. But when editing gridsome.server.js these changes aren't reflected.
This is solved by re-starting the server. And now you'll have your dynamic pages.

Gastby - Add a GraphQL query with parameters in gastby-node.js

Inside gatsby-node.jsI have two queries that gets its data from Contentful. I want to add a new query that loads the data for a particular content based on its slug (a field set in the content model in Contentful).
This is what I have:
return graphql(
`
{
allContentfulBlogPost {
edges {
node {
id
slug
}
}
}
allContentfulCaseStudy(filter: { slug: { ne: "dummy-content" } }) {
edges {
node {
id
slug
}
}
}
contentfulCaseStudy(slug: { eq: $slug }) { // <=== Here is the problem
title
overview
}
}
`
)
.then(result => {
if (result.errors) {
console.log("Error retrieving contentful data", result.errors)
}
})
.catch(error => {
console.log("Error retrieving contentful data", error)
})
}
So, I want to query that particular case study passing the slug in contentfulCaseStudy(slug: { eq: $slug }) but it doesn't work. It throws this error when I start npm run develop:
ERROR #85901 GRAPHQL
There was an error in your GraphQL query:
Variable "$slug" is not defined.
File: gatsby-node.js:13:10
Error retrieving contentful data [
GraphQLError: Variable "$slug" is not defined.
at Object.leave (C:\Edited\edited\edited\edited\node_modules\graphql\validation\rules\NoUndefinedVariables.js:38:33)
at Object.leave (C:\Edited\edited\edited\edited\node_modules\graphql\language\visitor.js:345:29)
at Object.leave (C:\Edited\edited\edited\edited\node_modules\graphql\language\visitor.js:395:21)
at visit (C:\Edited\edited\edited\edited\node_modules\graphql\language\visitor.js:242:26)
at validate (C:\Edited\edited\edited\edited\node_modules\graphql\validation\validate.js:73:24)
at GraphQLRunner.validate (C:\Edited\edited\edited\edited\node_modules\gatsby\dist\query\graphql-runner.js:79:44)
at GraphQLRunner.query (C:\Edited\edited\edited\edited\node_modules\gatsby\dist\query\graphql-runner.js:144:25)
at C:\Edited\edited\edited\edited\node_modules\gatsby\dist\bootstrap\create-graphql-runner.js:40:19
at Object.exports.createPages (C:\Edited\edited\edited\edited\gatsby-node.js:13:10)
at runAPI (C:\Edited\edited\edited\edited\node_modules\gatsby\dist\utils\api-runner-node.js:259:37)
at Promise.catch.decorateEvent.pluginName (C:\Edited\edited\edited\edited\node_modules\gatsby\dist\utils\api-runner-node.js:378:15)
at Promise._execute (C:\Edited\edited\edited\edited\node_modules\bluebird\js\release\debuggability.js:384:9)
at Promise._resolveFromExecutor (C:\Edited\edited\edited\edited\node_modules\bluebird\js\release\promise.js:518:18)
at new Promise (C:\Edited\edited\edited\edited\node_modules\bluebird\js\release\promise.js:103:10)
at C:\Edited\edited\edited\edited\node_modules\gatsby\dist\utils\api-runner-node.js:377:12
at tryCatcher (C:\Edited\edited\edited\edited\node_modules\bluebird\js\release\util.js:16:23) {
locations: [ [Object], [Object] ]
}
Is it possible to request a particular case study passing the slug as parameter? If so, how it's done?
The short answer is that you can't directly. You can filter with a hardcoded parameter, not with a dynamic pre-queried value.
However, what you are trying to do with $slug is to pass a variable via context API.
The flow that are you trying to achieve is:
Fetch and create pages from Contentful data for allContentfulCaseStudy
Use the slug of allContentfulCaseStudy in contentfulCaseStudy to filter your query for each contentfulCaseStudy.
So, you need to move your contentfulCaseStudy into your template.js modifying your gatsby-node.js like this:
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const result = await graphql(
`
{
allContentfulCaseStudy(filter: { slug: { ne: "dummy-content" } }) {
edges {
node {
id
slug
}
}
}
}
`
)
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
const caseStudyTemplate= path.resolve(`src/templates/case-study.js`)
result.data.allContentfulCaseStudy.edges.forEach(({ node }) => {
createPage({
path,
component: caseStudyTemplate,
context: {
slug: node.slug,
},
})
})
}
Now, in your case-study.js you have available the slug variable since you are passing it via context in your page query. So:
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
export default function CaseStudy({ data }) {
const caseStudy= data.contentfulCaseStudy
return (
<Layout>
<div>
<h1>{caseStudy.title}</h1>
</div>
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
contentfulCaseStudy(slug: { eq: $slug }) {
title
overview
}
}
`
Check your localhost:8000/___graphql playground to see if the nested title and overview are under contentfulCaseStudy or if you need to modify the query structure.
Further Reading:
Creating Pages from Data Programmatically
How to add query variables to a page query

Apollo response from mutation is undefined

I use Apollo-client 2.3.5 to to add some data and then update the local cache. The mutation works but the return from the mutation is undefined in the Apollo client, but the response in the network request is correct.
So I have two querys, one for fetching all bookings and one for adding a booking.
const addBookingGQL = gql`
mutation createBooking($ref: String, $title: String, $description: String, $status: String!){
createBooking(ref: $ref, title: $title, description: $description, status: $status){
id
ref
title
description
status
}
}
`;
const GET_BOOKINGS = gql`
query {
bookings {
id
ref
title
status
description
}
}
`;
I then have a Apollo mutation wrapper where I use the update prop. addBooking should be populated with the result of the mutation, but unfortunately it is undefined.
<Mutation
mutation={addBookingGQL}
update={(cache, { data: { addBooking } }) => {
const { bookings } = cache.readQuery({ query: GET_BOOKINGS });
console.log("cache read query bookings: ", cache);
cache.writeQuery({
query: GET_BOOKINGS,
data: { bookings: bookings.concat([addBooking]) }
});
}}
>
{(addBooking, { loading, error }) => (
<div>
<Button
onClick={() => {
addBooking({
variables: {
ref: this.state.ref,
title: this.state.title,
description: this.state.description,
status: "BOOK_BOX",
}
});
this.handleClose();
}}
color="primary">
Create
</Button>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
)}
</Mutation>
This results in following error in the console:
errorHandling.js:7 Error: Error writing result to store for query:
{
bookings {
id
ref
title
status
description
__typename
}
}
Cannot read property '__typename' of undefined
at Object.defaultDataIdFromObject [as dataIdFromObject] (inMemoryCache.js:33)
at writeToStore.js:347
at Array.map (<anonymous>)
at processArrayValue (writeToStore.js:337)
at writeFieldToStore (writeToStore.js:244)
at writeToStore.js:120
at Array.forEach (<anonymous>)
at writeSelectionSetToStore (writeToStore.js:113)
at writeResultToStore (writeToStore.js:91)
at InMemoryCache.webpackJsonp../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.write (inMemoryCache.js:96)
I tried running the mutation in the Graphiql dev tool receiving the expected response:
{
"data": {
"createBooking": {
"id": "bd954579-144b-41b4-9c76-5e3c176fe66a",
"ref": "test",
"title": "test",
"description": "test",
"status": "test"
}
}
}
Last I looked at the actual response from the graphql server:
{
"data":{
"createBooking":{
"id":"6f5ed8df-1c4c-4039-ae59-6a8c0f86a0f6",
"ref":"test",
"title":"test",
"description":"test",
"status":"BOOK_BOX",
"__typename":"BookingType"
}
}
}
If i use the Apollo dev tool for chrome i can see that the new data is actually appended to the cache, which confuses me.
Have you checked out this apollo issue comment?
The suggestion is to create an apollo-link that parses the operation variables and omits keys containing __typename:
function createOmitTypenameLink() {
return new ApolloLink((operation, forward) => {
if (operation.variables) {
operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename)
}
return forward(operation)
})
}
function omitTypename(key, value) {
return key === '__typename' ? undefined : value
}

Apollo client mutation error handling

I'm using GraphQL and mongoose on the server.
When a validation error occurs the GraphQL mutation sends a response with status code 200. On the client side the response looks like this:
{
"data": null,
"errors": [{
"message": "error for id...",
"path": "_id"
}]
}
I would like to get access to the validation error using the catch functionality of the apollo-client mutation promise. Something like:
this.props.deleteProduct(this.state.selectedProductId).then(response => {
// handle successful mutation
}).catch(response => {
const errors = response.errors; // does not work
this.setState({ errorMessages: errors.map(error => error.message) });
});
How can this be done?
The previous answer from #stubailo does not seem to cover all use cases. If I throw an error on my server side code the response code will be different than 200 and the error will be handled using .catch() and not using .then().
Link to the issue on GitHub.
The best is probably to handle the error on both .then() and .catch().
const { deleteProduct } = this.props;
const { selectedProductId } = this.state;
deleteProduct(selectedProductId)
.then(res => {
if (!res.errors) {
// handle success
} else {
// handle errors with status code 200
}
})
.catch(e => {
// GraphQL errors can be extracted here
if (e.graphQLErrors) {
// reduce to get message
_.reduce(
e.graphQLErrors,
(res, err) => [...res, error.message],
[]
);
}
})
Note: This answer (and arguably the whole question) is now outdated, since mutation errors show up in catch in more recent versions of Apollo Client.
GraphQL errors from the mutation currently show up in the errors field on the response inside then. I think there's definitely a claim to be made that they should show up in the catch instead, but here's a snippet of a mutation from GitHunt:
// The container
const withData = graphql(SUBMIT_REPOSITORY_MUTATION, {
props: ({ mutate }) => ({
submit: repoFullName => mutate({
variables: { repoFullName },
}),
}),
});
// Where it's called
return submit(repoFullName).then((res) => {
if (!res.errors) {
browserHistory.push('/feed/new');
} else {
this.setState({ errors: res.errors });
}
});
Using graphql tag notation, yo have access to errors:
<Mutation mutation={UPDATE_TODO} key={id}>
{(updateTodo, { loading, error }) => (
<div>
<p>{type}</p>
<form
onSubmit={e => {
e.preventDefault();
updateTodo({ variables: { id, type: input.value } });
input.value = "";
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Update Todo</button>
</form>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
)}
</Mutation>
https://www.apollographql.com/docs/react/essentials/mutations.html

Console error whilst waiting for API response - React/Redux

I am fetching data from a remote API in componentDidMount:
componentDidMount() {
this.props.fetchRemoteData('photos')
}
And then the received data is passed to my component props in mapStateToProps, using a selector to filter a specific object from the received array:
const mapStateToProps = (state, { params }) => {
const photoId = parseInt(params.photoId)
return {
singlePhoto: getSinglePhoto(state.filteredList.photos.jsonArray, photoId),
isFetching: state.filteredList.photos.isFetching
}
}
The content renders, but there is a split second before that, where it seems to be trying to the render the content before the data is successfully retrieved, which brings up the following error in the console:
Uncaught TypeError: Cannot read property 'charAt' of undefined
undefined is here referring to this.props.singlePhoto. But when singlePhoto receives the data payload the content renders.
Here is my container component:
class PhotoSingle extends Component {
componentDidMount() {
this.props.fetchRemoteData('photos')
}
render() {
const {singlePhoto, isFetching} = this.props
const photoTitle = capitalizeFirstLetter(singlePhoto.title)
return (
<div>
<PhotoSingleImg singlePhoto={singlePhoto} photoTitle={photoTitle} isFetching={isFetching}/>
</div>
)
}
}
const mapStateToProps = (state, { params }) => {
const photoId = parseInt(params.photoId)
return {
singlePhoto: getSinglePhoto(state.filteredList.photos.jsonArray, photoId),
isFetching: state.filteredList.photos.isFetching
}
}
import * as actions from '../actions/actionCreators'
PhotoSingle = connect(mapStateToProps, actions)(PhotoSingle)
export default PhotoSingle;
And my presentational component:
const PhotoSingleImg = ({ singlePhoto, photoTitle, isFetching }) => {
if (isFetching) {
return <h4>Fetching data...</h4>
}
return (
<div>
<h1>Single Photo</h1>
<h3>Title</h3>
<hr />
<img className='single-photo' src={singlePhoto.url} />
<p>Album ID: {singlePhoto.albumId} | Photo ID: {singlePhoto.id}</p>
</div>
)
}
export default PhotoSingleImg;
I'm unsure how to make it so the content will only attempt to render after I the API response has been received.
Any help appreciated.
Have you defined initial state in redux store?
You can try this way:
return singlePhoto ?
(<div>
<h1>Single Photo</h1>
<h3>Title</h3>
<hr />
<img className='single-photo' src={singlePhoto.url} />
<p>Album ID: {singlePhoto.albumId} | Photo ID: {singlePhoto.id}</p>
</div>) : null

Resources