Nested fragment data always the same in Relay - javascript

I'm new to Relay and am having an issue with nested data on a fragment.
The following query returns the correct data when I test in it graphiql so I am confident my schema is correct.
{
viewer {
customers {
name
billing_address {
city
}
}
}
}
However, when I use the above query with Relay the customer.name will be correct but customer.billing_address.city is the same for every customer. It's confusing to me why some of the data would be correct while the nested data would just be copied.
I would appreciate any help with the issue and if anymore information is needed I would be glad to add it. Below is the only Relay code currently in my project and where I believe the issue may be.
class App extends Component {
render() {
console.log(this.props.viewer.customers);
return (
<div>
{this.props.viewer.customers.map((customer) => (
<div>
<div>{customer.name}</div>
<div>{customer.billing_address.city}</div>
</div>
))}
</div>
);
}
}
class AppHomeRoute extends Relay.Route {
static queries = {
viewer: () => Relay.QL`
query {
viewer
}
`,
};
static routeName = 'AppHomeRoute';
}
const AppContainer = Relay.createContainer(App, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
customers{
name
billing_address{
city
}
}
}`
},
});
ReactDOM.render(
<Relay.RootContainer
Component={AppContainer}
route={new AppHomeRoute()}
/>,
document.getElementById('root')
);

One possible problem could by how you resolve billing_address in your GraphQL schema. Did you include an field :id, !types.ID or globalIdField in your billing_address GraphQL type? Posting your GraphQL schema may also help.

Related

How can I use a field as a filter from one graphql query to get a fluid image thats in a separate query?

I have two graphql sources, one for fluid images and one for characters in a mongo database. I'm mapping the characters to the page and I need to use the character names (the name field in the "allMongodbMobileLegendsdevChamps") as a filter (using originalName filter possibly) to find the fluid img. So I have something like this
query MyQuery {
allMongodbMobileLegendsdevChamps {
nodes {
name
}
}
allImageSharp(filter: {fluid: {originalName: {eq: "characterName.jpg"}}}) {
nodes {
fluid {
...allImageSharpFluid
}
}
}
}
const Index = ({ data }) => {
const {
allMongodbMobileLegendsdevChamps: { nodes: champs },
allImageSharp: { nodes: fluid },
} = data
return (
<section className={grid}>
{champs.map(champ => {
return (
<Link to={`/champ/${champ.name}`}>
<Image fluid={fluid.fluid} />
<h3>{champ.name}</h3>
</Link>
)
})}
</section>
)
}
If I wasnt using graphql, I'd just set the src in image to "champ.name" like this:
<Image src = `/path/to/img/${champ.name}.jpg` />
How can I filter my image query with the champ.name? Should I use Apollo for something like this?
This is Gatsby and currently it is not possible because you can’t pass in variables to queries follow this GH issue https://github.com/gatsbyjs/gatsby/issues/10482
Supposing that passing variables was possible, I would make all characters as a page query that will allow me to restructure the name from props and then a static query to which I would pass in the name as filter.
I used this solution and adapted it slightly for my needs: Variables in graphQL queries
In a parent component I map all the champions and create a child component for each one, and pass it the champions name.
In the child component, I have a variable, name, that stores the correct node for the image I want to display by using the find method to match the champions name to the images relativePath (the images have the same exact name as the champion). I then call the function renderImage which consumes the variable "name" and finds the fluid image in node.childImageSharp.fluid, and creates my image component with the correct fluid image location.
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import { displayName } from "../../utils/championName"
import * as S from "../styled/ChampionGridCard"
const renderImage = file => {
return <S.ChampionImage fluid={file.node.childImageSharp.fluid} />
}
const ChampionGridCard = props => {
const data = useStaticQuery(graphql`
{
allFile(filter: { sourceInstanceName: { eq: "champImages" } }) {
edges {
node {
extension
relativePath
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`)
const name = data.allFile.edges.find(
image => image.node.relativePath === `ssbu/${props.champName}.jpg`
)
return (
<S.ChampionGridCard to={`/champ/${props.champName}`}>
<S.ChampionImageWrapper>{renderImage(name)}</S.ChampionImageWrapper>
</S.ChampionGridCard>
)
}
export default ChampionGridCard

Data from Gatsby GraphQL always returns undefined?

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 (

Gatsby query works in graphql editor but not in react code

I am working on a blog using Gatsby and I am trying to generate pages based on that tag a user clicks on.
When a user clicks on a tag, he should be redirected to a page that displays a list of articles with that tag.
I created a query that does that. However, my GraphQL query works in my GraphQL editor but not in my react code. It returns an empty array in the code but the expected result when I run the query in the Graphql editor.
import React from "react"
import ArticleCard from "../articleCard/ArticleCard"
import Pagination from "../pagination/Pagination"
import articlesStyles from "./Articles.module.scss"
import { useStaticQuery, graphql } from "gatsby"
const Articles = ({ pageTitle }) => {
const blogPosts = useStaticQuery(graphql`
query($tag: String) {
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___publish_date] },
filter: { frontmatter: { tags: { in: [$tag] } } }
) {
nodes {
frontmatter {
title
publish_date
imageUrl
author
category
}
fields {
slug
}
excerpt(pruneLength: 100)
}
}
}
`)
const nodes = blogPosts.allMarkdownRemark.nodes;
console.log(nodes)
return (
<div className={articlesStyles.contentArea}>
<h1 className={articlesStyles.headerTitle}>{pageTitle}</h1>
<div className={articlesStyles.articles}>
{nodes.map(node => {
return (
<ArticleCard
key={node.id}
{...node.frontmatter}
excerpt={node.excerpt}
slug={node.fields.slug}
/>
)
})}
</div>
<Pagination />
</div>
)
}
export default Articles
As mentioned in the comments, you can't use variables in a staticQuery.
However, this may change in the future as many people stumble upon this limitation and there is work in progress to provide 'dynamicQueries'.
In the meanwhile you can use variables in queries in gatsby-node.js and the templates used by the createPage API. So, in your case, you could generate all the pages for all the tags in gatsby-node.js and still use the same article.js as component template.
See: https://www.gatsbyjs.org/docs/adding-tags-and-categories-to-blog-posts/
As #xadm and #ksav noted in their comments:
"Known Limitations useStaticQuery does not accept variables"
Here as a workaround
Query for all blogs, then use Array.prototype.filter() to get the one you need:
// ...
const blogPosts = useStaticQuery(graphql`
query {
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___publish_date] }
// you can't filter here
) {
nodes {
frontmatter {
title
publish_date
imageUrl
author
category
}
fields {
slug
}
excerpt(pruneLength: 100)
}
}
}
`)
// Get all blogs
const { blogPosts: { allMarkdownRemark: { edges } } } = props;
// filter the blogs
const yourFilteredTag = edges.filter(
el => el.node.childMarkdownRemark.frontmatter.tag === yourTagVariable);
// Now you can get the filtered list of blogs
console.log(yourFilteredTag);
// ...
Edit
As #Albert Skibinski mentioned this solution is not scalable. The context in gatsby-node.js might be a better solution in your use case.
The Array.prototype.filter() solution I described should be reserved for components where the context variable is not available and you can make sure that the size of the array does not grow excedeedingly large.

Fetch data only once per React component

I have a simple component that fetches data and only then displays it:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetch( { path: '/load/stuff' } ).then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
Each instance of MyComponent loads the same data from the same URL and I need to somehow store it to avoid duplicate requests to the server.
For example, if I have 10 MyComponent on page - there should be just one request (1 fetch).
My question is what's the correct way to store such data? Should I use static variable? Or I need to use two different components?
Thanks for advice!
For people trying to figure it out using functional component.
If you only want to fetch the data on mount then you can add an empty array as attribute to useEffect
So it would be :
useEffect( () => { yourFetch and set }, []) //Empty array for deps.
You should rather consider using state management library like redux, where you can store all the application state and the components who need data can subscribe to. You can call fetch just one time maybe in the root component of the app and all 10 instances of your component can subscribe to state.
If you want to avoid using redux or some kind of state management library, you can import a file which does the fetching for you. Something along these lines. Essentially the cache is stored within the fetcher.js file. When you import the file, it's not actually imported as separate code every time, so the cache variable is consistent between imports. On the first request, the cache is set to the Promise; on followup requests the Promise is just returned.
// fetcher.js
let cache = null;
export default function makeRequest() {
if (!cache) {
cache = fetch({
path: '/load/stuff'
});
}
return cache;
}
// index.js
import fetcher from './fetcher.js';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetcher().then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
You can use something like the following code to join active requests into one promise:
const f = (cache) => (o) => {
const cached = cache.get(o.path);
if (cached) {
return cached;
}
const p = fetch(o.path).then((result) => {
cache.delete(o.path);
return result;
});
cache.set(o.path, p);
return p;
};
export default f(new Map());//use Map as caching
If you want to simulate the single fetch call with using react only. Then You can use Provider Consumer API from react context API. There you can make only one api call in provider and can use the data in your components.
const YourContext = React.createContext({});//instead of blacnk object you can have array also depending on your data type of response
const { Provider, Consumer } = YourContext
class ProviderComponent extends React.Component {
componentDidMount() {
//make your api call here and and set the value in state
fetch("your/url").then((res) => {
this.setState({
value: res,
})
})
}
render() {
<Provider value={this.state.value}>
{this.props.children}
</Provider>
}
}
export {
Provider,
Consumer,
}
At some top level you can wrap your Page component inside Provider. Like this
<Provider>
<YourParentComponent />
</Provider>
In your components where you want to use your data. You can something like this kind of setup
import { Consumer } from "path to the file having definition of provider and consumer"
<Consumer>
{stuff => <SomeControl>
{ stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
}
</Consumer>
The more convenient way is to use some kind of state manager like redux or mobx. You can explore those options also. You can read about Contexts here
link to context react website
Note: This is psuedo code. for exact implementation , refer the link
mentioned above
If your use case suggests that you may have 10 of these components on the page, then I think your second option is the answer - two components. One component for fetching data and rendering children based on the data, and the second component to receive data and render it.
This is the basis for “smart” and “dumb” components. Smart components know how to fetch data and perform operations with those data, while dumb components simply render data given to them. It seems to me that the component you’ve specified above is too smart for its own good.

GraphQL + Relay modern fragment gives me an array of null posts

Im developing a React Native app using Relay modern, GraphQL, and Graphcool. I'm trying to fetch the posts from the DB, and I have 3 files, Post.js, PostList, and the index.js.
PostList.js:
export default createFragmentContainer(PostList, graphql`
fragment PostList_viewer on Viewer {
allPosts(last: 100, orderBy: createdAt_ASC) #connection(key: "PostList_allPosts", filters: []) {
edges {
node {
...Post_post
}
}
}
}
`)
Post.js
export default createFragmentContainer(Post, graphql`
fragment Post_post on Post {
id
content
createdAt
author {
id
username
}
}
`)
index.js
const Feed = () => (
<QueryRenderer
environment={environment }
query={AllPostQuery}
render={({ error, props }) => {
if (error) {
return <div>{error.message}</div>
} else if (props) {
return <PostList viewer={props.viewer} />
}
return <Text>Loading</Text>
}}
/>
)
When I console log this.props.viewer.allPosts inside PostList.js I get { edges: [ null, null, null ], .... I have 3 posts in the DB so it's finding the posts, but why are they null? Pl
first of all if you run into problems try to log the "root" object which is props not a branch inside of it - this will make easier for you to undestrand data structure.
Also, as far as I know Relay does some magic which makes data available for you only in files where you defined a fragment, so in PostList.js you will not see details of the post. Try printing data from Post.js. That's possibly the reason why you see null values printed.
Also data from GraphQL be will be available for you under variable name derived from fragment name after _ (underscore), so in your example it will be post not allPosts.

Categories