How to fix or query heuristic fragments in Strapi + React + Apollo - reactjs

I'm building a site using Strapi and React. When I query the data using the graphql playground I am able to see the data that is returned, however in my frontend using apollo, I get this error:
index.js:1 You are using the simple (heuristic) fragment matcher, but
your queries contain union or interface types. Apollo Client will not
be able to accurately map fragments. To make this error go away, use
the IntrospectionFragmentMatcher as described in the docs:
https://www.apollographql.com/docs/react/advanced/fragments.html#fragment-matcher
and
index.js:1 WARNING: heuristic fragment matching going on!
the URL for the docs doesn't work and after google the error, every answer is quite out of the scope for the knowledge I have.
This is the query in my graphql playground:
{
page(id: 1) {
slug
id
title
body {
__typename
... on ComponentContentText {
id
body
}
__typename
... on ComponentContentButton {
id
linkUrl
}
}
}
}
here is the result:
{
"data": {
"page": {
"slug": "home",
"id": "1",
"title": "Home",
"body": [
{
"__typename": "ComponentContentText",
"id": "1",
"body": "# Lorem Ipsum!\n\nLorem Ipsum dolor..."
},
{
"__typename": "ComponentContentButton",
"id": "1",
"linkUrl": "how-it-works"
},
{
"__typename": "ComponentContentButton",
"id": "3",
"linkUrl": "about"
},
{
"__typename": "ComponentContentText",
"id": "2",
"body": "## Lorem Ipsum dolor"
},
{
"__typename": "ComponentContentText",
"id": "3",
"body": "Lorem Ipsum dolor..."
},
{
"__typename": "ComponentContentButton",
"id": "2",
"linkUrl": "contact"
}
]
}
}
}
and this is my component in react:
import React from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
//import ReactMarkdown from 'react-markdown';
const Page = () => (
<Query
query={gql`
{
page(id: 1) {
slug
id
title
body {
__typename
... on ComponentContentText {
id
body
}
__typename
... on ComponentContentButton {
id
linkUrl
}
}
}
}
`}
>
{({ loading, error, data }) => {
if (loading) return <p className='loading'>loading</p>;
if (error) return <p className='error'>error: {JSON.stringify(error)}</p>;
//return <ReactMarkdown source={data} />;
}}
</Query>
);
export default Page;
As I am quite new on this, I would like to know and understand how to return this data.
Thanks!

You can read about fragments in Apollo GraphQL here. For a newbie with Apollo GraphQL, I recommend you using useQuery rather than Query component which will not be supported in Apollo 3.0 here. You can read more in my answer here.

Related

#React Import an image in a component

i'm trying to define my "Logo" as an image.
i tried to create a logo.js component, define it as an image and call it here, but i have absolutely no clue on how to solve that.
Here is my banner component,
import React from 'react'
import Banner from 'react-banner'
import 'react-banner/dist/style.css'
import Logo from '../Logo'
export default props => {
return (
<Banner
Logo = "My logo"
url={ window.location.pathname }
items={[
{ "content": "Quoi de neuf au tieks ?", "url": "/wassup" },
{ "content": "Les poucaves du jour", "url": "/lpdc", "children": [
{ "content": "Océane", "url": "/children/oceane" },
{ "content": "Mehdi", "url": "/children/mehdi" },
{ "content": "Yikes", "url": "/children/zemmeled" }
]}
]} />
)
}
if I understand well your question is: I can you pass a logo to the Banner Component ?
A quick look to the react-banner documentation and you can find :
"logo (node)
The image, text, or whatever else you may want to display in the left section of the banner.
url (string)"
https://skipjack.github.io/react-banner/customization
So I believe you can do as follow:
import React from 'react'
import Banner from 'react-banner'
import 'react-banner/dist/style.css'
import Logo from '../Logo'
export default props => {
return (
<Banner
Logo = "My logo"
logo=<Logo />
url={ window.location.pathname }
items={[
{ "content": "Quoi de neuf au tieks ?", "url": "/wassup" },
{ "content": "Les poucaves du jour", "url": "/lpdc", "children": [
{ "content": "Océane", "url": "/children/oceane" },
{ "content": "Mehdi", "url": "/children/mehdi" },
{ "content": "Yikes", "url": "/children/zemmeled" }
]}
]} />
)
}
As you can see on the code above, I pass the Logo component into the props logo of the BannerComponent from the librairy react-banner.

React reading nested JSON data fails when loads or refresh

I am developing a screen to show data based on search results. Below is the JSON output.
When I click search result below component is called. In the result screen i need id, name and table1, table2 data.
Table1 and Table2 are nested outputs and these will be displayed in React Table or Data Grid (next step)
Issue: Unable to render StudyData.table1.name
Options Tried
1. UseEffect()
UseEffect() able to read StudyData.studyname but not StudyData.table1.name
Assigned to a variable
Assigned to a state
2. Render
tried using map for subdocuments
Finding: It fails only first time load and refresh. I tried to comment -> save->load -> remove the comments and save. Then works (as component is loaded). I am missing something during the first time load or refresh. Please help
[
{
"id": "DD3",
"studydate": "DDD",
"studydescription": "DD3 Description",
"studyname": "DD3",
"table1": [
{
"no": "1",
"name": "DD3 Name",
"date": "Krishna",
"description\r": "1111\r"
},
{
"no": "2",
"name": "DD3 Nam2",
"date": "Test2",
"description\r": "2222\r"
},
{
"no": "3",
"name": "DD3 Name3",
"date": "Test3",
"description\r": "3333"
}
],
"table2": [
{
"No": "2",
"Study Field1": "21",
"Study Field2": "22",
"Study Field3\r": "23"
}
],
"table3": [
{
"No": "3",
"Study Field5": "T31",
"Study Field6": "T32",
"Study Field7": "T33",
"Study Field 8\r": "T34"
}
],
"_rid": "QeNcANZFTTIKAAAAAAAAAA==",
"_self": "dbs/QeNcAA==/colls/QeNcANZFTTI=/docs/QeNcANZFTTIKAAAAAAAAAA==/",
"_etag": "\"33002e92-0000-0200-0000-5fa6fe320000\"",
"_attachments": "attachments/",
"_ts": 1604779570
}
]
COMPONENT CODE
import React, { useEffect } from "react";
import api from "./UploadStudyFilesapi";
import { DataGrid } from "#material-ui/data-grid";
export default function StudyDisplay(props) {
let myData = props.Study;
const [StudyData, setStudyData] = React.useState([]);
const [Table1Rows, setTable1Rows] = React.useState([]);
useEffect(() => {
// Update the document title using the browser API
api.getStudy(myData.studyname).then((json) => setStudyData(json));
console.log(StudyData.studyname); //works
//console.log(StudyData.table1.no) //doesn't work
let myTable = StudyData.table1;
console.log(myTable);
setTable1Rows(StudyData.table1);
console.log(Table1Rows);
}, [myData]);
return (
<div>
{StudyData.length === 0 ? (
<h1>Loading...</h1>
) : (
StudyData.map((item) => (
<div key={item.studyname}>
{item.studyname}
{/* DOESNT WORK first brose or refresh*/}
{item.table1.map((key2) => (
<div>
{console.log(key2.name)}
{key2.name}
{/* Want to pass Key2 to DataGrid or React-Table */}
{/* <DataGrid rows={key2} columns={{field1:"No"}} /> */}
</div>
))}
</div>
))
)}
</div>
);
}
Thanks for your support. I added conditional rendering based on search selection in parent component. That resolved all the issues

How to output datocms graphql query with an array inside multiple posts

I am using the DatoCMS portfolio gatsby cloud autogenerated template. I have been able to successfully change some models in DataCMS that reflect in my graphQL playground, I then can adjust my code to reflect those changes. Now this model is for posts so there is multiples of them, now the part I am getting stuck on is, I have added a modular component to each post, which creates an array in the graphQL returned data, and I can't for the life of me figure out how to map thorough it when I am already mapping through all the posts that it is contained in.
Here is my code, and I am trying to output the data from details.task
import React from 'react'
import { Link, graphql } from 'gatsby'
import Masonry from 'react-masonry-component'
import Img from 'gatsby-image'
import Layout from "../components/layout"
const IndexPage = ({ data }) => (
<Layout>
{/* <Masonry className="showcase"> */}
{data.allDatoCmsPricing.edges.map(({ node: pricing }) => (
<div key={pricing.id} className="">
<figure className="card">
{/* <Link to={`/works/${work.slug}`} className="card__image">
<Img fluid={work.coverImage.fluid} />
</Link> */}
<h6 className="card__title">
<Link to={`/works/${pricing.slug}`}>{pricing.title}</Link>
</h6>
<div className="card__description">
<p>{pricing.excerpt}</p>
</div>
{{pricing.details.task}}//Tasks go here, I know this wont work
</figure>
</div>
))}
{/* </Masonry> */}
</Layout>
)
export default IndexPage
export const query = graphql`
query IndexQuery {
allDatoCmsPricing(sort: { fields: [position], order: ASC }) {
edges {
node {
id
title
slug
excerpt
details{
task
}
coverImage {
fluid(maxWidth: 450, imgixParams: { fm: "jpg", auto: "compress" }) {
...GatsbyDatoCmsSizes
}
}
}
}
}
}
`
and here is the resulting data from a playground query
{
"data": {
"allDatoCmsPricing": {
"edges": [
{
"node": {
"id": "DatoCmsPricing-1913799-en",
"title": "Silver",
"slug": "flyer-1",
"excerpt": "European minnow priapumfish mosshead warbonnet shrimpfish bigscale. Cutlassfish porbeagle shark ricefish walking catfish glassfish Black swallower.",
"details": [
{
"task": "Client Consultation"
},
{
"task": "S.M.A.R.T Goal Setting"
},
{
"task": "Fitness Assessment"
},
{
"task": "Client Centered Exercises"
},
{
"task": "1-2 Sessions per week"
}
]
}
},
{
"node": {
"id": "DatoCmsPricing-1913807-en",
"title": "Packaging 1",
"slug": "packaging-1",
"excerpt": "Efficiently unleash cross-media information without cross-media value. Quickly maximize timely deliverables for real-time schemas. Dramatically maintain clicks-and-mortar.",
"details": []
}
},
{
"node": {
"id": "DatoCmsPricing-1913806-en",
"title": "Stationery 1",
"slug": "stationery-1",
"excerpt": "User experience deployment MVP ecosystem direct mailing. Creative iteration early adopters research & development partnership buyer investor innovator success scrum project validation graphical user interface termsheet mass market.",
"details": []
}
}
]
}
}
}
With help from another source the following is what I was looking for to output the tasks
<ul className="details-list">{pricing.details.map(detailEntry => {
return <li key={detailEntry.id}><span>{detailEntry.task}</span></li>
})}</ul>

Why is refetchQueries needed?

I am following a tutorial on GraphQL, in the video the author does not use refetchQueries for a deleteMutation and all works well with UI updates and mutation. But here in the project sandbox code is updated and refetchQuery is now used for this operatio on Job component -> line 20 -> deleteJob(): codeSandBox.
I have this similar problem in my app that does not update the UI automatically without refetchQueries done everywhere. Shouldn't Apollo be applying automatically the cache of Apollo via apollo-cache-inmemory, perform mutation and update UI in this kind of mutation if I understand it right.
Example out of the box with apollo-boost:
export default gql`
mutation deleteItem($id: uuid!) {
delete_item(where: {id:{_eq: $id }}){
returning {
id
}
}
}`;
const onDeleteItem = (id) => {
deleteItem({
variables: { id },
});
};
Any suggestions or experiences on this?
The answer is relatively simple: There is no universal way in GraphQL to tell a client that an entity was deleted. Let's first compare this to an update mutations. Imagine we are updating one of the jobs that we already have in our cache. First the cache (simplified, not actually quite how it looks inside of Apollo):
{
"Query": {
"jobs": ["Job:1", "Job:2"],
},
"Job:1": {
"__typename": "Job",
"id": 1,
"company": "Big Corp",
"title": "Sales Specialist"
},
"Job:2": {
"__typename": "Job",
"id": 2,
"company": "Big Corp",
"title": "GraphQL Expert"
}
}
If Apollo now gets an answer from an update mutation that looks like the following:
{
"data": {
"updateJob": {
"__typename": "Job",
"id": 2,
"company": "Big Corp",
"title": "GraphQL Unicorn"
}
}
}
It can use the dataIdFromObject function to understand that the object belongs to the cache key "Job:2" in our normalised cache. Apollo can assume that this version is newer than the old one and merge the keys with preference of the newer result. Our cache now looks like this:
{
"Query": {
"jobs": ["Job:1", "Job:2"],
},
"Job:1": { ... },
"Job:2": {
"__typename": "Job",
"id": 2,
"company": "Big Corp",
"title": "GraphQL Unicorn" // updated!
}
}
Then the "jobs" query will automatically update with the new job because it is just referencing the job and is not storing the entity itself. Great! But now compare the result from the delete function:
{
"data": {
"deleteJob": {
"returning": {
"id": 2,
}
}
}
}
The result of this query could be anything. Apollo cannot know that you have just deleted a job with a certain id. Maybe if GraphQL had something in the specification like a magical "__isDeleted" and we would get something like:
{
"data": {
"deleteJob": {
"__typename": "Job",
"__isDeleted": true,
"id": 2,
}
}
}
}
We could give our cache implementation the hint that entities with __isDeleted: true should be removed from all referencing queries. But unfortunately this does not exists. This is not to bad though, we can either use refetchQuery to trigger a refetch of the other query or we can manually update the other query:
const deleteJob = useMutation(DELETE_JOB, {
update(store, response) {
const data = store.readQuery({ query: GET_JOBS });
data.jobs = data.jobs.filter(job => job.id !== response.deleteJob.returning.id);
store.writeQuery({ query: GET_JOBS, data });
}
});

Right way to parse Redux state props - using super json objects

Following my trip with React and Redux, I'm facing a problem, simple in appearance, but hard to solve : I'm setting my Redux state with a very big JSON object. Those datas are retrieved from an async call. When I'm setting it, I created an entry in my reducer with
let initialState = {
pages: []
}
Then, I'm putting different pages, with their params and datas into this pagesarray. So far so good, the state is well updated.
BUT, my different app pages use only parts of it as you can imagine. For instance, I have a page named Gallery which might need my state to look like this :
"pages": [
{
"component":"Gallery",
"key": "gallery",
"title": "Galerie photos",
"url": "/galerie-photos",
"sections": [
{
"component": "Gallery",
"params": {
"images": [
{
"id": 1,
"src": "http://lorempicsum.com/futurama/627/300/1",
"alt": "alternate text"
},
{
"id": 2,
"src": "http://lorempicsum.com/futurama/500/400/2",
"alt": "alternate text"
},
{
"id": 3,
"src": "http://lorempicsum.com/futurama/400/320/3",
"alt": "alternate text"
},
{
"id": 4,
"src": "http://lorempicsum.com/futurama/800/500/4",
"alt": "alternate text"
},
{
"id": 5,
"src": "http://lorempicsum.com/futurama/320/300/5",
"alt": "alternate text"
},
{
"id": 6,
"src": "http://lorempicsum.com/futurama/420/360/6",
"alt": "alternate text"
}
]
}
}
]
}
]
In my GalleryContainer, I'm requesting only the images property as this is the only concern of my Gallery component. I'm able to retrieve this data with no problem. Testing with the following code returns the desired images array :
But as far as I tested this, the images are not retrieved by my Gallerycomponent : when console logging on the component side, I got undefined, and on the container side, no problem.
I tested something different : I set a galleryImages property via the reducer, and set it with the gallery images directly. This worked.
The questions are : Why doesn't it work in the first case, and do in the second ? Do I have to work only with datas that are set as state properties only ? Can't I have a "super" json set in my state to work with directly ?
Thanks to light me up on this one :)
// Component
import React from 'react'
const Gallery = ({images}) => (
<div>
{images.map((image, key) =>
<div key={key} className="image-element-class" style={card}>
<img src={image.src} alt={image.alt} style={imageStyle}/>
</div>
)}
</div>
)
const card = {
width: 'calc((100% / 3) - (15px / 1.5))',
marginBottom: '15px',
boxSizing: 'border-box'
}
const imageStyle = {
width: '100%'
}
export default Gallery
// Container
import { connect } from 'react-redux'
import Gallery from '../../components/pages/Gallery'
const getImages = state => {
return {
images: state.data.pages.filter(page => page.component === 'Gallery' &&(
page.sections.filter(section => section.component === 'Gallery' &&(
section.params.images
))
)),
}
}
const mapStateToProps = state => {
return getImages(state)
}
const GalleryContainer = connect(
mapStateToProps,
{}
)(Gallery)
export default GalleryContainer
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>

Resources