I've been stuck for hours and I have no idea what's wrong. I'm new to GraphQL and Gatsby.
I have a JSON file that I'm trying to render. Each object has its own url path. I'm able to query the data with GraphQL, set up CreatePages in my gatsby-node.js file and then query again in my template file, but for some reason I'm only getting null in the template file.
I'm pretty sure I'm using gatsby-plugin-transformer-json correctly too. I'm at a loss right now. Any help is much appreciated.
gatsby-node.js
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
// You can delete this file if you're not using it
const path = require('path');
exports.createPages = ({actions, graphql}) => {
const { createPage } = actions;
const postTemplate = path.resolve(`src/templates/post.js`);
const projectTemplate = path.resolve(`src/templates/project.js`);
return graphql(`{
allProjectsJson {
edges {
node {
id
name
description
path
}
}
}
allMarkdownRemark {
edges {
node {
html
id
frontmatter {
path
title
date
}
}
}
}
}`)
.then(res => {
if(res.errors) {
return Promise.reject(res.errors);
}
// blogs
res.data.allMarkdownRemark.edges.forEach(({node}) => {
createPage({
path: node.frontmatter.path,
component: postTemplate
})
})
// projects
res.data.allProjectsJson.edges.forEach(({node}) => {
createPage({
path: node.path,
component: projectTemplate
})
})
})
}
templates/project.js
import React from 'react';
import { graphql } from 'gatsby'
import Layout from "../components/layout"
// import Helmet from 'react-helmet';
export default function Project({data}) {
const { projectsJson: project } = data;
// this results in null???
console.log(data)
return (
<Layout>
<div>
<h1>Projects!!!</h1>
<p>One single</p>
</div>
</Layout>
);
}
export const projectQuery = graphql`
query ProjectByPath($path: String!) {
projectsJson(path: { eq: $path }) {
name
path
description
}
}
`
I decided to simply list the projects rather than be able to link to each one. With that, I simply had to query in the file rather than pass each object through gatsby-node.js and grab the right one in the template file.
import React from "react"
// import { Link } from "gatsby"
import { graphql } from 'gatsby'
import Layout from "../components/layout"
import SEO from "../components/seo"
const Projects = ({data}) => (
<Layout>
<SEO title="Projects" />
<h1>Personal Projects</h1>
{data.allProjectsJson.edges.map((project, index) => {
console.log(project)
return (
<a
key={index}
style={projectContainerStyles}
href={project.node.url}
target="_blank"
rel="noopener noreferrer"
>
<h2>{project.node.name}</h2>
{project.node.description}
</a>
)
})}
<p>You can see my external portfolio
<a
href="https://anthonyzamarro.github.io/az_portfolio/"
target="_blank"
rel="noopener noreferrer">
here!
</a>
</p>
</Layout>
)
const projectContainerStyles = {
marginBottom: '2rem',
background: 'turquoise',
padding: '8px',
borderRadius: '5px',
boxShadow: '1px 3px 2px rgb(155,155,155)',
'display': 'block',
'color': '#000',
textDecoration: 'none'
}
export const projectQuery = graphql`
query projectQuery {
allProjectsJson {
edges {
node {
id
name
description
url
}
}
}
}`
export default Projects
Related
I would like to nest two queries on my Gatsby index.js page. I have front matter on my posts that includes the name and directory of the featured image. Query #1 is to retrieve those strings and query #2 is to build a childImageSharp using a combination of those strings.
What I have tried is:
import React from "react"
import { css } from "#emotion/core"
import { graphql, compose } from "gatsby"
import { rhythm } from "../utils/typography"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
function Home({ mdHeaderData, introImage }) {
return (
<div>
{mdHeaderData.allMarkdownRemark.edges.map(({ node }) => (
<h4>node.frontmatter.gallery</h4>
<GatsbyImage image={getImage(introImage.node)} />
</div>
);
};
const mdHeaderQuery = graphql`
query mdHeaderQuery {
allMarkdownRemark {
edges {
node {
id
frontmatter {
gallery
introimage
}
}
}
}
`
const introImageQuery = graphql`
query introImageQuery {
allFile(
filter: {
sourceInstanceName: {eq: "galleries"},
relativePath: {eq: $introImagePath}
}
) {
nodes {
childImageSharp {
gatsbyImageData(
width: 500
)
}
}
}
}
`
export default compose(
graphql(mdHeaderQuery, {
name: 'mdHeaderData'
}),
graphql(introImageQuery, {
name: 'introImage',
options: ({ mdHeaderData }) => ({
variables: {
introImagePath: mdHeaderData.allMarkdownRemark.edges.node.frontmatter.gallery + "/" + mdHeaderData.allMarkdownRemark.edges.node.frontmatter.introimage
}
})
})
) (Home)
I am getting SSR errors saying that graphql is no defined and pointing at the export default compose line.
As I see there is no json option anymore when querying the contentfulBlogPost only raw. I was able to make some changes to get everything from the body, except the image from that post.
If I made a test in GraphQL Playground I can get the image id and url but that's it.
query {
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
I tried to find an example how to get embedded images but no luck....
import React from 'react'
import { graphql } from 'gatsby'
import { documentToReactComponents } from '#contentful/rich-text-react-renderer'
import Layout from '../components/layout'
export const query = graphql`
query($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
}
}
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
`
const Blog = (props) => {
const options = {
renderNode: {
"embedded-asset-block": (node) => {
const alt = node.data.title
const url = node.file.url
return <img alt={alt} src={url}/>
}
}
}
return (
<Layout>
<h1>{props.data.contentfulBlogPost.title}</h1>
<p>{props.data.contentfulBlogPost.publishedDate}</p>
{documentToReactComponents(JSON.parse(props.data.contentfulBlogPost.body.raw, options))}
</Layout>
)
}
export default Blog
Plugins:
...
'gatsby-plugin-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
'gatsby-remark-relative-images',
{
resolve: 'gatsby-remark-images-contentful',
options: {
maxWidth: 750,
linkImagesToOriginal: false
}
}
]
}
}
],
}
Hi I saw this solution in a Youtube comment. First thing you have to do is change your Graphql query to something like this:
query ($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
id
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
references {
... on ContentfulAsset {
contentful_id
title
file {
url
}
}
}
}
}
}
Then change your options constant to:
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
console.log(node);
const imageID = node.data.target.sys.id;
const {
file: {url},
title
} = props.data.contentfulBlogPost.body.references.find(({contentful_id: id}) => id === imageID);
return <img src={url} alt={title} />
}
}
}
Use something like:
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: node => {
return (
<>
<h2>Embedded Asset</h2>
<pre>
<code>{JSON.stringify(node, null, 2)}</code>
</pre>
</>
)
},
},
}
renderRichText(node.bodyRichText, options)
Source: https://www.contentful.com/developers/docs/tutorials/general/rich-text-and-gatsby/
The return statement in BLOCKS.EMBEDDED_ASSET entry will contain your data, adapt to your needs. If you go inside the dependency, you'll see all the exposed methods, so you will have also a BLOCKS.EMBEDDED_ENTRY entry for your embedded entries. Apply it like:
[BLOCKS.EMBEDDED_ENTRY]: node => {
// your logic to manipulate the entry here
return (
<>
<div>whatever</div>
</>
)
},
For anyone that is still struggling to find the "references" field in graphql, remember that you HAVE TO first create an entry in contentful by adding at least one image. Otherwise, the references field will not show up in graphql, hence you can not query it.
I am trying to display a small image in a gatsby app, however, I am not able to figure out the error here.
Code
import React from "react"
import Img from "gatsby-image"
import { graphql } from "gatsby"
const Footer = ({ data }) => (
<div>
<h1>Hello gatsby-image</h1>
<Img fluid={data.footerHeart.file.childImageSharp.fluid} alt="footer" />
</div>
)
export default Footer
export const query = graphql`
query {
footerHeart: file(relativePath: { eq: "love.png" }) {
childImageSharp {
fluid(maxWidth: 40) {
...GatsbyImageSharpFluid
}
}
}
}
`
Error
You have defined an entry point to your file field So use that entry point as the direct path like so
<Img fluid={data.footerHeart.childImageSharp.fluid} alt="footer" />
I am trying to add an image sourced from graphql. I've run into this problem a few times now and always end up lucking out into a fix.
When querying the image I get this response:
The "path" argument must be one of type string, Buffer, or URL.
Received type undefined
The code is as follows:
import React from 'react'
import { Link, graphql, useStaticQuery, StaticQuery } from 'gatsby'
import Img from 'gatsby-image'
import './longCard.css';
const CardData = props => {
const slug = props.slug;
return (
<StaticQuery
query={
graphql`
query($slug: String) {
sanityProduct(slug: {current: {eq: $slug}}) {
slug{
current
}
title
featured_image {
asset {
childImageSharp {
fixed {
...GatsbyImageSharpFixed
}
}
}
}
}
}
`}
render={data => <LongCard />}
/>
)
}
export default CardData
export const LongCard = ({ data }) => {
return (
<div className="long-card">
<div className="long-card-inner">
<Link to={data.sanityProduct.slug.current}>{data.sanityProduct.title}</Link>
{/* Add image */}
<Img fixed={data.featured_image.asset.childImageSharp.fixed} />
</div>
</div>
)
}
I didn't need the ChildImageSharp section, I think this is only for querying the file-system.
When I'm using an apollo provider with redux server side rendering,
https://github.com/reactjs/redux/blob/master/docs/recipes/ServerRendering.md
I get the following warning and it breaks the server side output
Warning: Failed context type: The context `client` is marked as required in `Apollo(Home)`, but its value is `undefined`.
in Apollo(Home) (created by Connect(Apollo(Home)))
in Connect(Apollo(Home)) (created by RouterContext)
in RouterContext
in Provider
However this renders fine client side.
app
window.webappStart = () => {
const initialState = window.__PRELOADED_STATE__;
const store = createStore(rootReducer, initialState);
const client = new ApolloClient({
networkInterface: createNetworkInterface({ uri: 'https://api.graph.cool/simple/v1/foo' }),
});
render(
<ApolloProvider store={store} client={client}>
<Router>{routes}</Router>
</ApolloProvider>,
document.querySelector(".js-content")
);
};
Here's the boilerplate apollo code
import React from 'react';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
// The data prop, which is provided by the wrapper below contains,
// a `loading` key while the query is in flight and posts when it is ready
function PostList({ data: { loading, posts } }) {
if (loading) {
return <div>Loading</div>;
} else {
return (
<ul>
{posts.map(post =>
<li key={post.id}>
{post.title} by {' '}
{post.author.firstName} {post.author.lastName} {' '}
({post.votes} votes)
</li>
)}
</ul>
);
}
}
// The `graphql` wrapper executes a GraphQL query and makes the results
// available on the `data` prop of the wrapped component (PostList here)
export default graphql(gql`
query allPosts {
posts {
id
title
votes
author {
id
firstName
lastName
}
}
}
`)(PostList);
The PostList component looks alright to me, as does the client-side initiation of your app.
If you're getting that error in your server logs, then I think you'll want to check your routing middleware to ensure you're passing the client to ApolloProvider before rendering your app.
I'm using Express v4.* and react-router v4. My setup looks like this:
import React from 'react'
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import ApolloClient, { createNetworkInterface } from 'apollo-client'
import { ApolloProvider, renderToStringWithData } from 'react-apollo'
import routes from '../app/routes.js'
import { store } from 'app/store/index.js'
const Html = ({ title = 'App', content }) => (
<html>
<head>
<title>{title}</title>
<link href="/main.css" rel="stylesheet"/>
</head>
<body>
<div id="root" dangerouslySetInnerHTML={{ __html: content }} />
<script src='/index.js'/>
</body>
</html>
)
module.exports = (req, res) => {
match(
{
location: req.originalUrl,
routes,
},
(error, redirectLocation, renderProps) => {
if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search)
} else if (error) {
console.error('ROUTER ERROR:', error)
res.status(500)
} else if (renderProps) {
const client = new ApolloClient({
ssrMode: true,
networkInterface: createNetworkInterface({
uri: 'http://localhost:8888/graphql',
}),
})
/**
* Make sure client is added here. Store is optional */
const App = (
<ApolloProvider client={client} store={store}>
<RouterContext {...renderProps} />
</ApolloProvider>
)
/**
* Render and send response
*/
renderToStringWithData(App).then(content => {
const html = <Html content={content}/>
res.status(200).send(`<!DOCTYPE html>\n${ renderToString(html) }`)
}).catch((err) => console.log(`INITIAL RENDER (SSR) ERROR:`, err))
} else {
res.status(404).send('Not found')
}
}
)
}