Below is the sample client side code using Apollo client.
I am providing data from nodemon express server.
My query works fine in graphiQL.
import React from "react"
import { graphql } from 'react-apollo'
import gql from "graphql-tag";
const CounterpartyQuery = gql`
query data{
counterparty {
name
}
}
`;
export class ExchangeRates extends React.Component {
render() {
console.log(this.props.data) // Giving undefined
return (
<div>hiii{this.props.data.counterparty.map(x => x.name)}</div> //Error
)
}
}
const counterpartyData = graphql(CounterpartyQuery, { name: 'data' })(ExchangeRates)
export default counterpartyData
This is happening because your network call isn't over but the component gets rendered using the variable not available.
You need to use networkStatus to see whether the network call is over only then the data would be available to render. Until then you need to show a loader.
So replace
graphql(CounterpartyQuery, { name: 'data' })
by
graphql(CounterpartyQuery, {
name: 'data' ,
props : ({data}) => {
return {
data,
isLoading:data['networkStatus']==1 || data['networkStatus']==2 || data['networkStatus']==4
}
}
}),
and then use
this.props.isLoading
variable in the render method to decide whether to show loading or to show the list.
For eg:
render() {
console.log(this.props.data, this.props.isLoading)
return (
{this.props.isLoading ? <div>Loading Data</div>:<div>hiii{this.props.data.counterparty.map(x => x.name)}</div>
}
)
}
https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-networkStatus
Apollo client HOC passes a loading prop to each of your queries, you'll need it in almost every case in order to wait for the response before rendering your data (you also need to handle request errors):
export class ExchangeRates extends React.Component {
render() {
if (this.props.data.loading) {
return <div> Loading </div>; // rendering an animated loading indicator here is good practice to show an activity
}
if (this.props.data.error) {
return <div> Error </div>;
}
return (
<div>hiii{this.props.data.counterparty.map(x => x.name)}</div>
)
}
}
Related
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>
)
}}
/>
);
}
}
" I made a call to externalapi using react redux and store is loaded with data from API.But when I am trying to use map function"Cannot read property 'map' of undefined" error is displayed
{this.props.carDetails.array.map(([key,value])=>[key,value])}
carDetails is loaded with following dat
open_cars: Array(2)
0: {carid: 1, title: “bmw”} 1: {carid:2,title:”audi”}"
Here is my class
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { fetchVehicleDetails } from '../actions'
import Post from '../components/Post/Post.js'
class FetchRandomUser extends Component {
componentDidMount() {
this.props.fetchVehicleDetails()
}
render() {
return (
<div>
{this.props.carDetails.array.map(([key, value]) => [key, value])}
</div>
)
}
}
FetchRandomUser.propTypes = {
// carDetails:PropTypes.object,
fetchVehicleDetails: PropTypes.func
}
function mapStateToProps(state) {
return {
carDetails: state.carDetails
}
}
function loadData(store) {
return store.dispatch(fetchVehicleDetails())
}
export default {
loadData,
component: connect(mapStateToProps, { fetchVehicleDetails })((FetchRandomUser))
}
What I can guess from here is you are not mapping over the array
Change your map as
{this.props.carDetails.open_cars.map((value)=>value)}
Considering you do have an array in open_cars.
If this is an object you can use Object.keys(yourObject).map() to loop over properties in an object.
You are reading "this.props.carDetails.array", where in the response you have "
open_cars: Array(2)".
But you are accessing "this.props.carDetails.array.map" where the "array" is not in actual response.
So try like this.
<div>
{this.props.carDetails && this.props.carDetails.open_cars ?
this.props.carDetails.open_cars.map(([key, value]) => [key, value])
:'' }
</div>
Because when you fetch data from API server, this is an async. When your Component render the first time, carDetails not yet updated. In the next render of Component (the second time), carDetails will be updated.
So, in your component, check like this:
class FetchRandomUser extends Component {
componentDidMount() {
this.props.fetchVehicleDetails()
}
render() {
const { carDetails } = this.props;
return (
<div>
{
// The data: `open_cars: Array(2) 0: {carid: 1, title: “bmw”} 1: {carid:2,title:”audi”}"`
// So you must access to `open_cars` not `array` here
carDetails && carDetails.open_cars && carDetails.open_cars.length &&
carDetails.open_cars.map(([key,value])=>[key,value])
}
</div>
)
}
}
I am trying to implement email verification system in react-apollo application and running into an issue. The problem is that I want to fire a GraphQL mutation on page load when a user visits a link with a verification token. The mutation currently is fired on a button click, but I want it to happen on page load.
I tried to return the mutation from render but it sent the application into an infinite loop.
return (
<Mutation
mutation={VERIFY_EMAIL_MUTATION}
variables={{ id }}
onCompleted={() => this.setState({ userVerified: true })}
>
{(verifyEmail, { loading, error }) => {
verifyEmail();
}
</Mutation>
How can I implement firing this mutation on page load?
Use compose and pass it down as a function to your component. The following method allows you to pass multiple mutations/queries, you can use them where-ever you want without and with triggers.
import React from "react";
import { compose, graphql } from "react-apollo";
import {
VERIFY_EMAIL_MUTATION
} from "../GraphqlQueries/ServerQueries";
class YourComponent extends React.Component {
componentWillMount() {
this.props.verifyEmail({variables: {email: "your_email", variable2: "variable2" }});
}
render() {
return (
<React.Fragment>
Your Render
</React.Fragment>
);
}
}
export default compose(
graphql(VERIFY_EMAIL_MUTATION, {
name: "verifyEmail"
})
)(YourComponent);
Rather than compose you can do it like this
useEffect(() => {
MutationName({
variables: {
variable1: variable1Value
}
});
}, []);
useEffect behave as on pageload.
I'm using prismic as a CMS for a website built with gatsby.
I need to manipulate the data returned by graphql queries before rendering it in the react component.
The website works fine in dev but the build fails because the variables I'm using are not defined at build time.
I've tried using componentDidMount and the hooks equivalent to define my variables only at mount time but it didn't work. I've also tried assigning the variable to the state of the component at mount time but that failed as well. See the code below, where I tried to make a simple example, for a better idea:
import { graphql } from 'gatsby';
import Layout from "../components/layout"
export const data = graphql`
query allData {
allPrismicNews{
edges {
node {
id
}
}
}
}
`;
class IndexPage extends Component {
render() {
return (
<Layout>
<p>{this.state.newsId ? this.state.newsId : null}</p>
</Layout>
);
}
componentDidMount() {
if (typeof window === 'undefined') {
return;
}
this.setState(() => ({ newsId: this.props.data.allPrismicNews.edges.map(article=>article.node.id).flat() }));
}
}
export default IndexPage;```
For this example, I expect to see the ids of the news output in the template, this works in development but not in production.
What am I doing wrong?
What you could do is set an initial state to your newsId so that this.state.newsID is never undefined:
class IndexPage extends Component {
state = {
newsId: null,
}
componentDidMount() {
if (typeof window === "undefined") {
return
}
this.setState({
newsId: this.props.data.allPrismicNews.edges
.map(article => article.node.id)
.flat(),
})
}
render() {
return (
<Layout>
<p>{this.state.newsId ? this.state.newsId : null}</p>
</Layout>
)
}
}
After installing ReactJS again after a few months not working with it, I noticed the latest version (16) is now using getDerivedStateFromProps and there is no more will receive props functions and stuff.
Currently I have my environment running with react-redux included. My new data gets into the mapStateToProps function of my container script, but I want to update the view accordingly. Basically a loading screen, and after the data is fetched via an API call, update the view with the API's response data.
However, I don't seem to be able to find a solution to update my view anywhere up till now.
I noticed that the getDerivedStateFromProps only gets triggered once.
Am I missing some functions or anything?
Short example:
import React from 'react';
import { connect } from "react-redux";
import Files from '../components/files';
class ProjectContainer extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.getFilesByShare('sharename');
}
componentDidUpdate (prevProps) {
console.warn('does not get here?');
}
render() {
const { loading, files } = this.props;
let content = (
<div className="loading">Loading... Requesting file urls</div>
);
if (!loading && files && files.length) {
content = (
<div>
File urls requested!
<Files files={files} />
</div>
);
}
return (
{content}
);
}
}
const mapStateToProps = state => {
console.warn(state, 'this shows the new data');
return {
files: state.files,
loading: state.files_loading,
};
};
export default connect( mapStateToProps, {
getFilesByShare,
}) (ProjectContainer);