How to pass an argument to a components query (non-static)? - reactjs

I am trying to create a component that I later can reuse on my website like so <TimeToRead id={someId}/>. My idea was to pass that id further down into the query.
However that does not work and ends up in: TypeError: Cannot read property 'edges' of undefined. Why is that so and what am I doing wrong here?
Is createPage(... context: id: someId) inside gatsby-node.js the only way to pass arguments? But that would only apply to templates...
How can I pass arguments to components?
import React from "react"
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
import { faClock } from "#fortawesome/free-solid-svg-icons"
import { graphql } from "gatsby"
const TimeToRead = ({id}) => {
console.log(id)
return (
<React.Fragment>
<FontAwesomeIcon icon={faClock} /> {timeToReadQuery.allMarkdownRemark.edges.node.timeToRead} ~ min.
</React.Fragment>
)
}
export const timeToReadQuery = graphql`
query timeToReadQuery($id: String!) {
allMarkdownRemark(
filter: { id: { eq: $id } }
) {
edges {
node {
timeToRead
}
}
}
}
`
export default TimeToRead

In Gatsby there are two types of queries. Page queries that can be defined in page components only and accept arguments passed as context in createdPage() and static queries which don't access variables and can be used in everywhere but are limited to one per file.
If you TimeToRead component file is not a page component then you have 2 options:
Use a static query - you just can't have variables in it.
Define a graphql fragment to use into parent page component.
// in child component
export const remarkTimeToReadFragment = graphql`
fragment RemarkTimeToRead on Query {
postTimeToRead: markdownRemark(id: { eq: $id }) {
timeToRead
}
}`
// in page component
export const pageQuery = graphql`
query PageQuery($id: String!) {
...RemarkTimeToRead
}
`
This particular example may produce a warning because $id param is not used directly in the page query and the linter just won't account for it being used by the fragment.

Related

GraghQL query returning integer instead of object

I can't figure out why none of my queries are working on my Gatsby projects.
import React from "react"
import { graphql } from "gatsby"
const site_info = () => {
const query = graphql`
query title {
allSite {
edges {
node {
siteMetadata {
title
}
}
}
}
}
`
console.log(query)
return <p>Test</p>
}
export default site_info
In the console, I'm expecting an object where I can see the title in metadata, however, I'm getting 2641826822.
I copied the query directly from GraphiQL where I'm seeing the expected result, so not sure why it isn't working here.
When you query (using a page query as you've provided) some data using GraphQL in a Gatsby schema, your data is stored as a page prop (not in the query variable itself), so you need to access to them props and iterate through the object until you find your data. An ideal structure data should look something like:
const yourPage = ({ data }) => {
const { title } = data.allSite.edges[0].node.siteMetadata;
return <Layout>
<h1>{title}</h1>
</Layout>;
};
export const yourPageData = graphql`
query title {
allSite {
edges {
node {
siteMetadata {
title
}
}
}
}
}
`;
export default yourPage;
Basically, in the snippet above I'm destructuring data as a prop (instead of doing prop.data) and I do the same with data.allSite.edges[0].node.siteMetadata to get the title.
I would recommend some documentation reading about Querying Data in Pages with GraphQL before you dive into GraphQL on the rocks.

Gatsby-GraphQL - Fetching remote data from a postgres server [duplicate]

There have been a couple of similar questions, but none helped me really understand using a GraphQL inside a (class) component other than the ones in the pages folder.
My project structure looks like that:
-src
--components
---aboutBody
----index.js
--pages
---about.js
I have a page component called about (Prismic single page type) and set up some components to "fill" this page (cleaned up for better readability).
class AboutPage extends Component {
render() {
return (
<LayoutDefault>
<AboutBody
introHeadline={this.props.data.prismicAbout.data.intro_headline.text}
introParagraph={this.props.data.prismicAbout.data.intro_paragraph.text}
/>
</LayoutDefault>
)
}
}
export default AboutPage
This is what my query looks like (had it like this in both files):
export const aboutQuery = graphql`
query About {
prismicAbout {
data {
# Intro Block
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`
(In case I am missing a bracket at the bottom, it's due to cleaning up the query example for SO — as mentioned earlier, it's working in my page component).
My graphql query is at the bottom of the AboutPage page component. It works like a charm and as intended.
But to clean this page up a bit I wanted to create appropriate components and put my query inside each component (e.g. aboutBody, aboutCarousel), again cleaned up a bit:
class AboutBody extends Component {
render() {
return (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)
}
}
export default AboutBody
And I deleted the query from my about page component and put it inside my AboutBody component (exactly the way as shown above).
But with this it always returns the error Cannot read property 'prismicAbout' of undefined (I can't even console log the data, it always returns the same error).
I used import { graphql } from "gatsby" in both files.
Long story short, how can I achieve putting a query inside my class component and render only the component without clarifying the props in my page component like this:
class AboutPage extends Component {
render() {
return (
<LayoutDefault>
<AboutBody />
</LayoutDefault>
)
}
}
Some blogs posts mention GraphQL Query Fragments, but not sure if this is the correct use case or if it's simply a stupid beginner mistake...
That's because you can't use graphql like this in your component.
To use graphql in a component, you've got two options : useStaticQuery function or StaticQuery component, both from graphql
for useStaticQuery :
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
const MyElement = () => {
const data = useStaticQuery(graphql`
query About {
prismicAbout {
data {
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`)
return (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)
}
export default MyElement
with staticQuery
import React from 'react'
import { StaticQuery, graphql } from 'gatsby';
const MyElement = () => {
return(
<StaticQuery
query About {
prismicAbout {
data {
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`}
render={data => (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)}
/>
)
}
export default MyElement
Hope that helps you!
You can only use a query like that in a page component. One option would be to just query it in the page and then pass the data in to your component as a prop. Another is to use a static query in the component.
If your query has variables in it then you can't use a static query. In that case you should either query it all in the page and then pass it in, or you can put the part of the query related to that component in a fragment within that component's file and then use that fragment in the page query.
Example of using fragments in a component and then passing the data into the component:
// MyComponent.js
import React from "react"
import { graphql } from 'gatsby'
const MyComponent = (props) => {
const { myProp: { someData } } = props
return (
<div>
my awesome component
</div>
)
}
export default MyComponent
export const query = graphql`
fragment MyAwesomeFragment on Site {
someData {
item
}
}
`
// MyPage.js
import React from "react"
import { graphql } from "gatsby"
import MyComponent from "../components/MyComponent"
export default ({ data }) => {
return (
<div>
{/*
You can pass all the data from the fragment
back to the component that defined it
*/}
<MyComponent myProp={data.site.someData} />
</div>
)
}
export const query = graphql`
query {
site {
...MyAwesomeFragment
}
}
`
Read more about using fragments in Gatsby docs.
If you need to render the query in a class based component. This worked for me:
import React, { Component } from 'react';
import { StaticQuery, graphql } from 'gatsby';
class Layout extends Component {
render() {
return (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`}
render={data => {
return (
<main>
{!data && <p>Loading...</p>}
{data && data.site.siteMetadata.title}
</main>
)
}}
/>
);
}
}

How to include variables in the React Apollo query and execute it?

I'm using compose to perform multiple queries in a component. I want to be able to use variables for the queries.
1) How do I include the variables in the query?
2) How do I execute the query?
Following is the component:
import React from 'react'
import {
View,
} from 'react-native'
import { graphql, withApollo } from 'react-apollo'
import { compose } from "recompose";
import { GET_ITEM, GET_REVIEWS } from '../graphql/query'
const PostingDetail = props => {
const itemId = props.navigation.getParam('itemId', null)
console.log("props", props.itemQuery)
return (
<View>
</View>
)
}
export default compose(
withApollo,
graphql(GET_ITEM, {
name: 'itemQuery',
options: ({ itemId }) => ({
variables: {
id: itemId
}
})
}),
graphql(GET_REVIEWS, { name: 'reviewsQuery'}),
)(PostingDetail)
I want to be able to use itemId as the variables for the query, however, the above code displays the following error:
"message": "Variable \"$id\" of required type \"ID!\" was not
provided."
This property allows you to configure the name of the prop that gets passed down to your component. By default if the GraphQL document you pass into graphql() is a query then your prop will be named data. If you pass a mutation then your prop will be named mutate. While appropriate these default names collide when you are trying to use multiple queries or mutations with the same component. To avoid collisions you may use config.name to provide the prop from each query or mutation HOC a new name.
Example
export default compose(
graphql(gql`mutation (...) { ... }`, { name: 'createTodo' }),
graphql(gql`mutation (...) { ... }`, { name: 'updateTodo' }),
graphql(gql`mutation (...) { ... }`, { name: 'deleteTodo' }),
)(MyComponent);
function MyComponent(props) {
// Instead of the default prop name, `mutate`,
// we have three different prop names.
console.log(props.createTodo);
console.log(props.updateTodo);
console.log(props.deleteTodo);
return null;
}
And the key of the variable you want to use is not in the query statement, showing the error message.
use variables of options
export default graphql(gql`
query ($width: Int!, $height: Int!) {
...
}
`, {
options: (props) => ({
variables: {
width: props.size,
height: props.size,
},
}),
})(MyComponent);

GraphQL query works in Gatsby page but not inside class component

There have been a couple of similar questions, but none helped me really understand using a GraphQL inside a (class) component other than the ones in the pages folder.
My project structure looks like that:
-src
--components
---aboutBody
----index.js
--pages
---about.js
I have a page component called about (Prismic single page type) and set up some components to "fill" this page (cleaned up for better readability).
class AboutPage extends Component {
render() {
return (
<LayoutDefault>
<AboutBody
introHeadline={this.props.data.prismicAbout.data.intro_headline.text}
introParagraph={this.props.data.prismicAbout.data.intro_paragraph.text}
/>
</LayoutDefault>
)
}
}
export default AboutPage
This is what my query looks like (had it like this in both files):
export const aboutQuery = graphql`
query About {
prismicAbout {
data {
# Intro Block
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`
(In case I am missing a bracket at the bottom, it's due to cleaning up the query example for SO — as mentioned earlier, it's working in my page component).
My graphql query is at the bottom of the AboutPage page component. It works like a charm and as intended.
But to clean this page up a bit I wanted to create appropriate components and put my query inside each component (e.g. aboutBody, aboutCarousel), again cleaned up a bit:
class AboutBody extends Component {
render() {
return (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)
}
}
export default AboutBody
And I deleted the query from my about page component and put it inside my AboutBody component (exactly the way as shown above).
But with this it always returns the error Cannot read property 'prismicAbout' of undefined (I can't even console log the data, it always returns the same error).
I used import { graphql } from "gatsby" in both files.
Long story short, how can I achieve putting a query inside my class component and render only the component without clarifying the props in my page component like this:
class AboutPage extends Component {
render() {
return (
<LayoutDefault>
<AboutBody />
</LayoutDefault>
)
}
}
Some blogs posts mention GraphQL Query Fragments, but not sure if this is the correct use case or if it's simply a stupid beginner mistake...
That's because you can't use graphql like this in your component.
To use graphql in a component, you've got two options : useStaticQuery function or StaticQuery component, both from graphql
for useStaticQuery :
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
const MyElement = () => {
const data = useStaticQuery(graphql`
query About {
prismicAbout {
data {
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`)
return (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)
}
export default MyElement
with staticQuery
import React from 'react'
import { StaticQuery, graphql } from 'gatsby';
const MyElement = () => {
return(
<StaticQuery
query About {
prismicAbout {
data {
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`}
render={data => (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)}
/>
)
}
export default MyElement
Hope that helps you!
You can only use a query like that in a page component. One option would be to just query it in the page and then pass the data in to your component as a prop. Another is to use a static query in the component.
If your query has variables in it then you can't use a static query. In that case you should either query it all in the page and then pass it in, or you can put the part of the query related to that component in a fragment within that component's file and then use that fragment in the page query.
Example of using fragments in a component and then passing the data into the component:
// MyComponent.js
import React from "react"
import { graphql } from 'gatsby'
const MyComponent = (props) => {
const { myProp: { someData } } = props
return (
<div>
my awesome component
</div>
)
}
export default MyComponent
export const query = graphql`
fragment MyAwesomeFragment on Site {
someData {
item
}
}
`
// MyPage.js
import React from "react"
import { graphql } from "gatsby"
import MyComponent from "../components/MyComponent"
export default ({ data }) => {
return (
<div>
{/*
You can pass all the data from the fragment
back to the component that defined it
*/}
<MyComponent myProp={data.site.someData} />
</div>
)
}
export const query = graphql`
query {
site {
...MyAwesomeFragment
}
}
`
Read more about using fragments in Gatsby docs.
If you need to render the query in a class based component. This worked for me:
import React, { Component } from 'react';
import { StaticQuery, graphql } from 'gatsby';
class Layout extends Component {
render() {
return (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`}
render={data => {
return (
<main>
{!data && <p>Loading...</p>}
{data && data.site.siteMetadata.title}
</main>
)
}}
/>
);
}
}

Is there a way to pass a dynamic GraphQL query to a graphql() decorated Component when using Apollo Client 2.0?

I encounter this issue sometimes when I am working with dynamic data. It's an issue with higher-order components which are mounted before the data they need is available.
I am looking to decorate a component with the graphql() HOC in Apollo Client, like this:
export default compose(
connect(),
graphql(QUERY_NAME), <-- I want QUERY_NAME to be determined at run-time
)(List)
The problem is I don't know how to get Apollo to use a query that is determined by the wrapped component at run-time.
I have a file that exports queries based on type:
import listFoo from './foo'
import listBar from './bar'
import listBaz from './baz'
export default {
foo,
bar,
baz,
}
I can access them by listQueries[type], but type is only known inside the component, and it is available as this.props.fromRouter.type.
Is there a strategy I can use to achieve:
export default compose(
connect(),
graphql(listQueries[type]),
)(List)
I think there might be a way to do it like this:
export default compose(
connect(),
graphql((props) => ({
query: listQueries[props.fromRouter.type],
})),
)(List)
Am I on the right track?
Another possible solution could be to make the Component generate its own sub-component that is wrapped with graphql() because the query would be known then.
For example:
const tableWithQuery = graphql(listQueries[props.fromRouter.type])((props) => {
return <Table list={props.data} />
})
I think I figured it out.
I have a Router Component that reads this.props.match.params to get the type of view and requested action.
With this information, I can create just one List, Create, Edit, and View Component and supply each with whatever queries are needed.
I created a function that gets all queries and mutations for a supplied type.
It was actually quite simple to just take the component such as <List /> and wrap it with graphql() and give it the correct query or mutation that was just determined.
Now, the components mount with this.props.data being populated with the correct data
I spread in all the queries and mutations just in case I need them. I suspect I will need them when I go to read this.props.data[listQueryName]. (which will grab the data in, for example, this.props.data.getAllPeople)
Here is the logic (I will include all of it, to minimize confusion of future searchers):
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { compose, graphql, withApollo } from 'react-apollo'
import listQueries from './list/queries'
import createMutations from './forms/create/mutations'
import editMutations from './forms/edit/mutations'
import viewQueries from './forms/view/queries'
import List from './list/List'
import Create from './forms/create/Create'
import Edit from './forms/edit/Edit'
import View from './forms/view/View'
// import Delete from './delete/Delete'
class Router extends Component {
constructor(props) {
super(props)
this.state = {
serverErrors: [],
}
}
getGraphQL = (type) => {
console.log('LIST QUERY', listQueries[type])
console.log('LIST QUERY NAME', listQueries[type].definitions[0].name.value)
console.log('CREATE MUTATION', createMutations[type])
console.log('CREATE MUTATION NAME', createMutations[type].definitions[0].name.value)
console.log('EDIT MUTATION', editMutations[type])
console.log('EDIT MUTATION NAME', editMutations[type].definitions[0].name.value)
console.log('VIEW QUERY', viewQueries[type])
console.log('VIEW QUERY NAME', viewQueries[type].definitions[0].name.value)
return {
listQuery: listQueries[type],
listQueryName: listQueries[type].definitions[0].name.value,
createMutation: createMutations[type],
createMutationName: createMutations[type].definitions[0].name.value,
editMutation: editMutations[type],
editMutationName: editMutations[type].definitions[0].name.value,
viewQuery: viewQueries[type],
viewQueryName: viewQueries[type].definitions[0].name.value,
}
}
renderComponentForAction = (params) => {
const { type, action } = params
const GQL = this.getGraphQL(type)
const {
listQuery, createMutation, editMutation, viewQuery,
} = GQL
// ADD QUERIES BASED ON URL
const ListWithGraphQL = graphql(listQuery)(List)
const CreateWithGraphQL = graphql(createMutation)(Create)
const EditWithGraphQL = compose(
graphql(viewQuery),
graphql(editMutation),
)(Edit)
const ViewWithGraphQL = graphql(viewQuery)(View)
if (!action) {
console.log('DEBUG: No action in URL, defaulting to ListView.')
return <ListWithGraphQL fromRouter={params} {...GQL} />
}
const componentFor = {
list: <ListWithGraphQL fromRouter={params} {...GQL} />,
create: <CreateWithGraphQL fromRouter={params} {...GQL} />,
edit: <EditWithGraphQL fromRouter={params} {...GQL} />,
view: <ViewWithGraphQL fromRouter={params} {...GQL} />,
// delete: <Delete fromRouter={params} {...GQL} />,
}
if (!componentFor[action]) {
console.log('DEBUG: No component found, defaulting to ListView.')
return <ListWithGraphQL fromRouter={params} {...GQL} />
}
return componentFor[action]
}
render() {
return this.renderComponentForAction(this.props.match.params)
}
}
Router.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({ type: PropTypes.string }),
}).isRequired,
}
export default compose(connect())(withApollo(Router))
If this code becomes useful for someone later. I recommend commenting everything out except code necessary to render the List View. Start with verifying the props are coming in to a little "hello world" view. Then, you will be done the hard part once you get correct data in there.

Resources