Data from Gatsby GraphQL always returns undefined? - javascript

Gatsby noob here so please bear with me. I have a component that accepts props from the index.js where it is supposed to receive data from an array of objects but will always receive the error TypeError: Cannot read property 'map' of undefined where it's referring to the Hero.js component index.js is calling for.
My assumption is that the data being queried in index.js is either not specific enough or that it is rendering the component before data is received. Here is the index.js file:
import { graphql } from 'gatsby';
import { Layout, SEO, Hero } from 'components';
const IndexPage = ({ data }) => {
const dataFetch = data.contentfulTemplateIndex.heroes;
let tester = () => {
for (let count = 0; count < dataFetch.length; count++) {
return <Hero {...props} />;
}
};
console.log(dataFetch);
let props = {
impactText: dataFetch.impactText,
labels: dataFetch.labels,
primaryLabel: dataFetch.primaryLabel,
location: dataFetch.location
// supportingText: dataFetch.supportingText.json
};
return (
<Layout>
{dataFetch && tester()}
</Layout>
);
};
export const query = graphql`
query {
contentfulTemplateIndex {
heroes {
image {
fluid {
src
}
}
impactText
labels
location
primaryLabel
supportingText {
json
}
}
}
}
`;
export default IndexPage;
Here is the Hero.js component which index.js is calling:
import { Link } from 'gatsby';
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
import cx from 'classnames';
import styles from './hero.module.scss';
const Hero = (props) => {
return (
<div>
<ul>
<Link className={styles.pills}>{props.primaryLabel}</Link>
{props.labels.map((label) => {
return <Link className={styles.pills}>{label}</Link>;
})}
</ul>
<div className={styles.grid}>
<h1>{props.impactText}</h1>
</div>
</div>
);
};
export default Hero;

It's impossible for an outsider to debug your code without a minimum reproducable example.
The best way to debug GraphQL is to use the GraphiQL interface of your browser.
Run gatsby develop. If it fails because of the TypeError remove the lines of code that cause the type error (but not the code of your GraphQL query!). You need to get your development server runnning.
Open your browser, use the URL: http://localhost:8000/___graphql
Copy your graphQL query from your code and paste it into the GraphiQL query window.
Can you access your data there? If not you made a mistake writing your query or the data is not where it's supposed to be.
This way you can make sure the data exists.
It also helps to console.log(props) so you can examine the data object:
const Hero = (props) => {
console.log(props);
return (

Related

How to use SSR data fetching in nextjs with apollo client?

I am try to use apollo-client with nextjs. Here I want to fetch data in getServerSideProps. Suppose I have 2 components and one page-
section.tsx this is component-1
const Section = () => {
return (
<div>
Section
</div>
);
};
export default Section;
mobile.tsx this is component 2
const Mobile = () => {
return (
<div>
Mobile
</div>
);
};
export default Mobile;
Now I call this two component into home page.
index.tsx-
const Home: NextPage = () => {
return (
<Container disableGutters maxWidth="xxl">
<Section />
<Mobile />
</Container>
);
};
export default Home;
export const getServerSideProps: GetServerSideProps = async () => {
const { data } = await client.query({ query: GET_USER_LIST })
return { props: {} }
}
Here you can see that in getServerSideProps I already fetch my data.
My question is How can I directly access this data form Section component and Mobile component without passing props. I don't want to pass props, because if my component tree will be more longer, then it will be difficult to manage props.
From appollo docs, I alreay know that apollo client do the same with redux state manager. So please tell me how can I access this data from any component that already fetched in getServerSideProps. Is it possible?. If not then how can what is the solutions.
How about using context api if you want to avoid prop drilling? By putting data into context, you can access it from any child component. Get the data from the SSR and put it into the context.
Below is the example
import React, {createContext, useContext} from "react";
export default function Home({data}) {
return <DataContext.Provider value={{data}}>
<div>
<Section/>
<Mobile/>
</div>
</DataContext.Provider>
}
export async function getServerSideProps() {
const data = 'hello world' //Get from api
return {
props: {data},
}
}
function Section() {
return <div>
Section
</div>
}
function Mobile() {
const context = useContext(DataContext);
return <div>
Mobile {context.data}
</div>
}
const DataContext = createContext({});
Now, as long as your tree structure grows within the DataContext provider, each child node will have access to data in the context.
Hope this helps.

Server side prop value undefined only in the component section

I have the following where I am able to print the data coming from getServerSideProps.
But when I pass it on to the component section, the prop value posts is undefined. Why?
The following is under /pages and is inside the base index.tsx file.
import type { NextPage } from 'next'
import Post from '../components/Post'
const Home: NextPage = ({ posts }) => {
// This prints undefined. This is the issue.
console.log(posts)
return (
<div>
<Post posts={posts}/>
</div>
)
}
export default Home
export const getServerSideProps = async () => {
const response = await fetch('http://localhost:8080/posts/all')
const data = await response.json()
// this prints correctly
console.log(data)
return {
props: {
posts: data
}
}
};
Updates to show _app.tsx file
import Home from '../pages';
const TestApp = () => {
return <>
<Home />
</>
}
export default TestApp
Remove the custom _app.tsx file or update it to accept a Component prop and render it as shown in the Nextjs documentation for a Custom App.
The problem is this custom App is always rendering the Home component (with no props) so it's printing undefined. Then, try navigating to / on localhost. It should render the index.tsx file and properly invoke getServerSideProps and pass the result to the component as you expect.

Query values lost on page refresh in Next js? [Example given]

I am making a simple Next Js application which has only two pages..
index.tsx:
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
As per the above code, on click Go to the about page it goes to about page and using query I also receive the passed query values in about page.
about.tsx
import React from "react";
import Router, { withRouter } from "next/router";
function About({ router: { query } }: any) {
return (
<div>
Candidate Id: <b> {query.candidateId} </b>
</div>
);
}
export default withRouter(About);
This displays the value but on page refresh while we are in /about page, the candidateId received gets disappeared.
Requirement: Kindly help me to retain the query value passed down from one page to another page even on page refresh.
Note: As per my requirement I should not display the canidateId on url while navigating and hence I am using as approach.. I know I can achieve it if I remove as but I cannot remove that here in index page while navigating.. Reason is this will lead to displaying candidateId in the url which is not intended..
Tried this solution: https://stackoverflow.com/a/62974489/7785337 but this gives empty query object on refresh of page.
Stuck for very long time with this please kindly help me.
If you do not want to use the query parameter you may need to create a "store" that saves your variable that persist throughout your pages.
Sample code as follows.
//candidatestore.js
export const CandidateStoreContext = createContext()
export const useCandidateStore = () => {
const context = useContext(CandidateStoreContext)
if (!context) {
throw new Error(`useStore must be used within a CandidateStoreContext`)
}
return context
}
export const CandidateStoreProvider = ({ children }) => {
const [candidateId, setCandidateId] = useState(null);
return (
<CandidateStoreContext.Provider value={{ candidateId, setCandidateId }}>
{children}
</CandidateStoreContext.Provider >
)
}
Then you need to wrap the Provider around your app like
<CandidateStoreProvider><App /></CandidateStoreProvider>
This way you can use anywhere as follows both in your index page and your about page.
const { candidateId, setCandidateId } = useCandidateStore()
UseContext
In your codes, it should probably look something like that.
import React from "react";
import Link from "next/link";
import { useCandidateStore } from './candidatestore'
export default function Index() {
const { candidateId, setCandidateId } = useCandidateStore()
useEffect(() => {
setCandidateId(thecandidateId)
})
return (
<div>
<Link
href={{
pathname: "/about",
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
function About({ router: { query } }: any) {
const { candidateId, setCandidateId } = useCandidateStore()
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
Update to Next.JS 10. It comes with Automatic Resolving of href which fixes your problem.
Try to delete the as="about" and then navigate again to the "about" page, the issue should be gone.
Codesandbox
My best bet would be to store the candidateId in an encrypted session on the client side. You could read/verify cookies in getServerSideProps() and pass their contents to the page component. If this sounds feasible, I'd recommend checking out the next-iron-session.
Another approach would be to check if candidateId exists in the query object in getServerSideProps(). If it does then pass it straight to the page component. If not, either get it elsewhere, redirect, or pass some default value. Append the following starter code to your about.tsx:
/* ... */
export function getServerSideProps({ query }: any) {
// if query object was received, return it as a router prop:
if (query.candidateId) {
return { props: { router: { query } } };
}
// obtain candidateId elsewhere, redirect or fallback to some default value:
/* ... */
return { props: { router: { query: { candidateId: 8432 } } } };
}
index.tsx file
Keep the code same as it is.
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
AboutUs.tsx
Code starts from here
Adding router as a dependency in the useEffect the issue should get solved.
import Router, { useRouter } from "next/router";
import React, { useState, useEffect } from 'react';
function About({ router: { query } }: any) {
const route = userRouter();
const [candidateId, setCandidateid] = useState();
useEffect(() => {
const {candidateId} = router.query
if(candidateId) {
setCandidateid(candidateid)
}},[router]) //Here goes the dependency
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
export default (About);

Why does graphql not inject my data into react component prop?

I am using this tutorial to create a gatsby blog that will use Markdown pages.
I am trying to create a 'post-repeater' component that will loop through all my posts and create links to them using frontmatter that looks like this.
---
path: "/blog/my-first-post"
date: "2019-05-04"
title: "My first blog post"
---
I have a graphQl query that is correctly pulling the data.
My component looks like this.
import React from "react"
import "../styles/post-repeater.scss"
import { graphql, Link } from "gatsby"
export default function PostRepeater({postQuery}){
console.log('my data:',postQuery);
return(
<>
{postQuery.map(instance =>
<div className="post">
<h2>{instance.title}</h2>
<span className="datePosted">{instance.date}</span>
<span className="readTime"></span>
<p></p>
<Link to={instance.path}>Read More</Link>
<hr />
</div>
)}
</>
)
}
export const postQuery = graphql`
query MyQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
path
title
date
}
}
}
}
}
`
If I put a console.log of postQuery from inside the PostRepeater component, it comes back as undefined. So it appears that the component is never getting the data even though I followed the same layout from the tutorial above.
If I put a console.log('data', postQuery); from outside the component, I get the following in the console.
data 2575425095
What am I doing wrong?
After making a query gatsby injects it as an object matching the query.
In your case, as you see in GraphiQL you get an object with the initial key data.
{
"data": {
"allMarkdownRemark": {
"edges": [
{
"node": {
"frontmatter": {
"title": "...",
"date": ...
}
}
}
]
}
}
}
What you tried to do, is destructing a not existing key postQuery.
Moreover, you still can log it outside of the component scope as the value exported from export const postQuery
export const postQuery = graphql`
query MyQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
path
title
date
}
}
}
}
}
`;
// v Logs the query id
console.log('my data:', postQuery);
// v Is not defined
export default function PostRepeater({ data, postQuery }) {
// v Shadowing the outter value of postQuery
console.log('my data:', postQuery);
// The query injected as "data" object
console.log('my data:', data);
return (
<>
// v Be aware of which object you query, edges is an array
{data.allMarkdownRemark.edges.map(...)}
</>
);
}
The export of graphql queries which gets automatically attached to props only work for page components. For any non-page components you need to use either the StaticQuery component or the useStaticQuery hook.
Read more here:
https://www.gatsbyjs.com/docs/static-query/
Here is how your code should look like using the useStaticQuery hook
import React from "react"
import "../styles/post-repeater.scss"
import { graphql, useStaticQuery, Link } from "gatsby"
export default function PostRepeater() {
const data = useStaticQuery(graphql`
query MyQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
path
title
date
}
}
}
}
}
`)
// you might have multiple nodes in which case you must use filter
// or find to get node which contains your data.
const postQuery = data.allMarkdownRemark.edges[0].node.frontmatter;
return (
<>
{postQuery.map(instance =>
<div className="post">
<h2>{instance.title}</h2>
<span className="datePosted">{instance.date}</span>
<span className="readTime"></span>
<p></p>
<Link to={instance.path}>Read More</Link>
<hr />
</div>
)}
</>
)
}

Passing component as a function argument

I have a function that take a component as argument, and return another, enhanced component:
import React from 'react';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Layout, DarkBar } from 'SharedComponents/Layouts';
const myCreationFunction = ({
component,
}) => {
const Route = (props) => {
// Some code here
return (
<Layout>
<div><Link to={props.path}>LinkTitleHere</Link></div>
{React.createElement(component, {
...props,
...someOtherPropsHere,
})}
</Layout>
);
}; // The error points here
const mapStateToProps = () => ({});
const enhance = compose(
connect(mapStateToProps, someActionsHere),
)(Route);
return enhance;
};
I invoke that function in this way:
import MyComponent from './MyComponent';
import myCreationFunction from './HOC/myCreationFunction';
const Route = myCreationFunction({
component: MyComponent,
});
When I run it in the development mode, it runs smoothly. But when trying to build the app using npm run build and going through webpack, I get:
Module parse failed: Unexpected token (35:47)
You may need an appropriate loader to handle this file type.
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
> var createListRoute = function myCreationFunction((_temp = _ref, _ref2 = <_Layouts.DarkBar>
| <_Breadcrumbs2.default />
| <_Layouts.RoundAddButton path={addPath} color="white" />
What am I doing wrong?
Edit #1
It seems that the <Link> is causing the problem. Removing it fixed the problem. Also, when trying to replace it with a tag, I get the same issue. Weird.
Edit #2
I have not resolved this issue because of lack of time. I was trying for 1 hour without any progress and decided to go with button tag and onClick method that uses history to push the new url.
It was and is really weird to me, that a Link or <a> tag can break something during the build process itself. I will definitely jump deeper into it in some free time.

Categories