I'm trying to log out the raw and so far I can't get anything in the console. It just shows up as an empty array. There must be something wrong with my Query as all i can find on the docs is how to render ContentfulAsset but not actual text which i can format with CSS
/* eslint-disable */
import React from 'react';
import { Helmet } from 'react-helmet';
import { graphql } from 'gatsby';
import Layout from '../components/layout';
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
export const query = graphql`
query MyQuery($slug: String) {
contentfulLongPost(Slug: { eq: $slug }) {
title
updatedAt(formatString: "MMMM Do, YYYY")
body {
raw
references {
... on ContentfulAsset {
contentful_id
__typename
}
}
}
free
contentType
}
}
`;
const caseStudy = (props) => {
console.log(props);
return (
<Layout>
</Layout>
);
};
export default caseStudy;
What do I put after __typename to get the raw?
query MyQuery($slug: String) {
contentfulLongPost(Slug: {eq: $slug}) {
title
updatedAt(formatString: "MMMM Do, YYYY")
body {
raw
}
}
}
`;
There are a few things wrong there... caseStudy must be CaseStudy since it's a React component, otherwise, it will be interpreted as an HTML element which obviously will break your code (<caseStudy> doesn't exist... yet).
Even if ContentfulAsset fragment is wrong, if your query is correct, you should get something inside the raw field (located at props.data.contentfulLongPost.raw) so check it again. If you are just trying to print the rich text, while your query doesn't break, you can print what's inside raw without data in the ContentfulAsset fragment.
Maybe what it's wrong if the filter that is hold by the $slug variable, so even if the query is correct, you are not able to fetch the data because there's not any data to fetch.
Once you ensure that your data is being fetched properly (you have data inside props.data) you can customize the output by lifting your data to:
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/
A full customizable example can be checked (almost copy/pasted) at: https://github.com/contentful/rich-text/tree/master/packages/rich-text-react-renderer
The idea is to create a custom object that will parse your fetched data with a custom HTML/React component:
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>
</>
)
},
},
}
function BlogPostTemplate({ data }) {
return <div>{data.contentfulBlogPost && renderRichText(data.contentfulBlogPost, options)}</div>
}
Where BlogPostTemplate stands for your CaseStudy and contentfulBlogPost for your contentfulLongPost.
Summarizing:
Fix your component naming
Check what's inside props.data.contentfulLongPost.raw
Check that you have any data with the provided slug. You can force the value of the slug in localhost:8000/___graphql to check what data is fetching your query.
Customize the output using the options object and the renderRichText helper
Related
I'm building a nextjs-application and I crossed an issue with getStaticPaths. Inside the pages-folder, I have a file called [slug].tsx which contains this code:
import { Image } from "react-datocms";
import { request } from "../lib/datocms";
import { GetStaticProps, GetStaticPaths } from "next";
export default function Page({ pageData }) {
return (
<div>
<h1>{pageData.title}</h1>
</div>
);
}
const PATHS_QUERY = `
query MyQuery {
allPages {
slug
}
}
`;
export const getStaticPaths: GetStaticPaths = async (context) => {
const slugQuery = await request({
query: PATHS_QUERY,
preview: context.preview,
});
let paths = [];
slugQuery.allPages.map((path) => paths.push(`/${path.slug}`));
return {
paths,
fallback: false,
};
};
const PAGE_QUERY = `
query MyQuery($slug: String) {
page(filter: {slug: {eq: $slug}}) {
title
slug
id
}
}
`;
export const getStaticProps: GetStaticProps = async ({ params }) => {
const page = {
query: PAGE_QUERY,
variables: { slug: params.slug },
};
return {
props: {
pageData: page,
}
};
};
This gives me the error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.
I have no clue what this means, so can anyone help me out?
****** UPDATE ******
I suspect my Navbar could have something to do with this. In my components folfer, I have a nav folder with a Navbar.tsx-file which looks like this:
const Navbar = ({ topNav }) => {
const menu_items = topNav.menuItems[0];
return (
<nav>
{menu_items.topNavigationItems.map((navitem, idx) => (
<div key={navitem.text}>
<NavItem {...navitem} />
</div>
))}
</nav>
)
}
export default Navbar;
the NavItem looks like this:
const NavItem = ({ text, path, active }) => {
return (
<Link href={path.slug}>
<a>
{text}
</a>
</Link>
);
};
export default NavItem;
The way you are building your paths array inside getStaticPaths is not quite right according to the new standards. You have to "push" an object with a key of params, which then contains an object with your slug.
Rewriting your getStaticPaths function would result in the following.
export const getStaticPaths: GetStaticPaths = async (context) => {
const slugQuery = await request({
query: PATHS_QUERY,
preview: context.preview,
});
const paths = slugQuery.allPages.map(path => {params: {slug: path.slug} });
return {
paths,
fallback: false,
};
};
You can read more about the getStaticPaths function in the official documentation.
EDIT: To be more specific on the error you're getting, you are trying to render an object as a JSX element, thus generating an error. Try and find the source of that error and fix it this way.
I'm making a Gatsby blog as a side project.
I want to use the "featureImage" optional field in mdx frontmatter.
I tried to refer to the https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization#creating-type-definitions document according to the error message, but it was difficult to understand.
This is part of my code.
index.js
import React, { useEffect } from "react";
import { graphql } from "gatsby";
import { App, ContentCard, TagsNavi, User } from "../components";
import { ContentWrapper } from "../elements";
import { useDispatch, useTag } from "../store/StoreContext";
import { ALL, SET_TAG, TAG } from "../constants";
const IndexPage = ({ location, data }) => {
const {
allMdx: { edges },
} = data;
const tag = useTag();
const dispatch = useDispatch();
useEffect(() => {
const tagName = new URLSearchParams(location.search).get(TAG);
dispatch({ type: SET_TAG, tag: tagName || ALL });
}, [dispatch, location.search]);
return (
<App title="Home">
<div>
<ContentWrapper>
<User />
<TagsNavi />
{edges
.filter(
(edge) => edge.node.frontmatter.tags.includes(tag) || tag === ALL
)
.map((edge) => {
const { node } = edge;
return <ContentCard key={node.slug} {...node} />;
})}
</ContentWrapper>
</div>
</App>
);
};
export default IndexPage;
export const pageQuery = graphql`
query AllPostsQuery {
allMdx(sort: { fields: frontmatter___date, order: DESC }) {
edges {
node {
slug
excerpt(pruneLength: 140, truncate: true)
frontmatter {
date(formatString: "MMMM DD, YYYY")
tags
title
featureImage {
childImageSharp {
gatsbyImageData
}
}
}
}
}
}
}
`;
gatsby-node.js
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type MdxFrontmatter implements Node {
featureImage: File
}
`;
createTypes(typeDefs);
};
I tried this and it works when featureImage is not present.
But if I have featureImage it doesn't work.
How can I create the functionality I want? Please help me.
I'm not good at English, so I use a translator so my words may be a little weird. Sorry.
The Codesandbox address is here.
https://codesandbox.io/s/gatsby-starter-ki-demo-8fyty?file=/gatsby-node.js
When tested locally,
GraphiQL playground output: "Cannot read property 'contentDigest' of undefined"
terminal message: ERROR getNodeAndSavePathDependency failed for node id: undefined as it was not found in cache
warn You can't use childImageSharp together with undefined.undefined — use publicURL instead. The childImageSharp portion of the query in this file will return null:
undefined
Browser console: Warning: Failed prop type: The prop image is marked as required in GatsbyImage, but its value is undefined.
[gatsby-plugin-image] Missing image prop
You may find this GitHub thread insightful. Following it, try using:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
createTypes(`
type Mdx implements Node {
frontmatter: MdxFrontmatter!
}
type MdxFrontmatter {
featuredImage: File #fileByRelativePath
}
`)
}
The problem I assume in your implementation is that there were no internal nodes for Gatsby to treat your image so the resolver can't find the path to create childImageSharp. That's why the output was undefined.
Other useful threads:
https://github.com/gatsbyjs/gatsby/issues/21426
Browser console: Warning: Failed prop type: The prop image is marked
as required in GatsbyImage, but its value is undefined.
This one is because you are trying to render GatsbyImage whether exists or not, so you are not passing any image prop. You've not shared the logic in the component but add a simple ternary condition like:
{data.someNode.featuredImage && <GatsbyImage image={data.someNode.featuredImage} /> }
P.S: it's a pity the CodeSandbox issue and thanks for trying it
I'm using Reactjs and Relay. I want to create something if "this" item is not created yet / array is empty. somehow, the relay query returns an empty array if the data has not been loaded, therefore it keeps created "this item" since it's considered empty. how do we determine if relay query data is actually empty or not loaded / pending?
i.e query:
QueryRenderer(MyComponent, {
query: graphql`
query MyComponentQuery($id: ID!) {
items(containerId: $id) {
id
title
}
}
`,
vars: ({ containerId }) => ({
id: containerId
})
})
handle create "this" item:
useEffect(() => {
if(!props.items){
// Create "this" item
}
}, [props.items]);
I am guessing from your snippet that QueryRenderer is an HOC you are using wrapping relay's QueryRender component?
If so, this is how you normally determine if a query is in loading state:
// Example.js
import React from 'react';
import { QueryRenderer, graphql } from 'react-relay';
const renderQuery = ({error, props}) => {
if (error) {
return <div>{error.message}</div>;
} else if (props) {
return <div>{props.page.name} is great!</div>;
}
return <div>Loading</div>;
}
const Example = (props) => {
return (
<QueryRenderer
environment={environment}
query={graphql`
query ExampleQuery($pageID: ID!) {
page(id: $pageID) {
name
}
}
`}
variables={{
pageID: '110798995619330',
}}
render={renderQuery}
/>
);
}
This snippet is taken from the relay.dev docs.
Notice how the render renderProp is getting the props field once the query is no longer loading.
If you are using the new hooks-api, it will be different based on which of the query-hooks you are using.
I'm trying to generate Gatsby pages based on data in my CMS (Sanity).
I have created three pages in my CMS under the umbrella term "Second page", I use createPage in gatsby-node to generetate pages with the correct slugs.
All the pages are generated according to their slug from the CMS, but in my template component I cannot filter out data. I get the result for all three pages when I only need the result for the one page that matches the slug. My console.log in secondPage.js shows three arrays corresponding to the three items in my CMS
gatsby-node.js
// Create pages for docs
exports.createPages = ({ actions, graphql }) => {
const path = require(`path`);
const { createPage } = actions;
//const docTemplate = path.resolve("src/templates/docTemplate.js");
const secondPageTemplate = path.resolve("src/templates/secondPage.js");
return graphql(`
{
allSanitySecondPage {
edges {
node {
slug
}
}
}
}
`).then((result) => {
if (result.errors) {
Promise.reject(result.errors);
}
result.data.allSanitySecondPage.edges.forEach(({ node }) => {
createPage({
path: node.slug,
component: secondPageTemplate,
context: {
slug: node.slug,
},
});
});
});
};
secondPage.js (template)
import React from "react";
import { graphql } from "gatsby";
import Layout from "../components/layout";
const BlockContent = require("#sanity/block-content-to-react");
const secondPage = ({ data }) => {
// const pageData = data.sanitySecondPage.edges.node;
return (
<Layout>
<h1>Hello from the second page!</h1>
{console.log(data.sanitySecondPage)}
{/* <BlockContent blocks={pageData._rawBlockContent} /> */}
</Layout>
);
};
export const query = graphql`
query($slug: String!) {
sanitySecondPage(slug: { eq: $slug }) {
_rawBlockContent
}
}
`;
export default secondPage;
It turns out that all I needed was to write a stack overflow post to solve my own issue. Everything was correct, I miss understood the return of block content, it was supposed to be three array elements.
We are building an offline first React Native Application with Apollo Client. Currently I am trying to update the Apollo Cache directly when offline to update the UI optimistically. Since we offline we do not attempt to fire the mutation until connect is "Online" but would like the UI to reflect these changes prior to the mutation being fired while still offline. We are using the readQuery / writeQuery API functions from http://dev.apollodata.com/core/read-and-write.html#writequery-and-writefragment. and are able to view the cache being updated via Reacotron, however, the UI does not update with the result of this cache update.
const newItemQuantity = existingItemQty + 1;
const data = this.props.client.readQuery({ query: getCart, variables: { referenceNumber: this.props.activeCartId } });
data.cart.items[itemIndex].quantity = newItemQuantity;
this.props.client.writeQuery({ query: getCart, data });
If you look at the documentation examples, you will see that they use the data in an immutable way. The data attribute passed to the write query is not the same object as the one that is read. Mutating this object is unlikely to be supported by Apollo because it would not be very efficient for it to detect which attributes you modified, without doing deep copies and comparisons of data before/after.
const query = gql`
query MyTodoAppQuery {
todos {
id
text
completed
}
}
`;
const data = client.readQuery({ query });
const myNewTodo = {
id: '6',
text: 'Start using Apollo Client.',
completed: false,
};
client.writeQuery({
query,
data: {
todos: [...data.todos, myNewTodo],
},
});
So you should try the same code without mutating the data. You can use for example set of lodash/fp to help you
const data = client.readQuery({...});
const newData = set("cart.items["+itemIndex+"].quantity",newItemQuantity,data);
this.props.client.writeQuery({ ..., data: newData });
It recommend ImmerJS for more complex mutations
Just to save someones time. Using the data in an immutable way was the solution. Agree totally with this answer, but for me I did something else wrong and will show it here. I followed this tutorial and updating the cache worked fine as I finished the tutorial. So I tried to apply the knowledge in my own app, but there the update didn’t work even I did everything similar as showed in the tutorial.
Here was my approach to update the data using the state to access it in the render method:
// ... imports
export const GET_POSTS = gql`
query getPosts {
posts {
id
title
}
}
`
class PostList extends Component {
constructor(props) {
super(props)
this.state = {
posts: props.posts
}
}
render() {
const postItems = this.state.posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
const PostListQuery = () => {
return (
<Query query={GET_POSTS}>
{({ loading, error, data }) => {
if (loading) {
return (<div>Loading...</div>)
}
if (error) {
console.error(error)
}
return (<PostList posts={data.posts} />)
}}
</Query>
)
}
export default PostListQuery
The solution was just to access the date directly and not using the state at all. See here:
class PostList extends Component {
render() {
// use posts directly here in render to make `cache.writeQuery` work. Don't set it via state
const { posts } = this.props
const postItems = posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
Just for completeness here is the input I used to add a new post and update the cache:
import React, { useState, useRef } from 'react'
import gql from 'graphql-tag'
import { Mutation } from 'react-apollo'
import { GET_POSTS } from './PostList'
const ADD_POST = gql`
mutation ($post: String!) {
insert_posts(objects:{title: $post}) {
affected_rows
returning {
id
title
}
}
}
`
const PostInput = () => {
const input = useRef(null)
const [postInput, setPostInput] = useState('')
const updateCache = (cache, {data}) => {
// Fetch the posts from the cache
const existingPosts = cache.readQuery({
query: GET_POSTS
})
// Add the new post to the cache
const newPost = data.insert_posts.returning[0]
// Use writeQuery to update the cache and update ui
cache.writeQuery({
query: GET_POSTS,
data: {
posts: [
newPost, ...existingPosts.posts
]
}
})
}
const resetInput = () => {
setPostInput('')
input.current.focus()
}
return (
<Mutation mutation={ADD_POST} update={updateCache} onCompleted={resetInput}>
{(addPost, { loading, data }) => {
return (
<form onSubmit={(e) => {
e.preventDefault()
addPost({variables: { post: postInput }})
}}>
<input
value={postInput}
placeholder="Enter a new post"
disabled={loading}
ref={input}
onChange={e => (setPostInput(e.target.value))}
/>
</form>
)
}}
</Mutation>
)
}
export default PostInput