React reading data form the server - reactjs

I am getting an error from strapi backend data when I fetch particular data into the front end react app I get an error when I log the data into the browser console I see my data in the browser console.
query GetPromoProducts {
allStrapiProduct(filter: {promo: {eq: true}}) {
edges {
node {
strapiId
name
variants {
images {
url
}
}
}
}
}
}
`)
const [selectedSlides, setSelectedSlides] = useState(0)
console.log(data);
var slides = [];
data.allStrapiProduct.edges.map(({node}) => {
console.log(node.variants[0].images[0]);
})

Debugging
To debug the server side, use optional chaining and console.log to work your way up to the object's that returning null.
Not sure what the exact code looks like, based on question.
const myQuery = graphql`
query GetPromoProducts {
allStrapiProduct(filter: { promo: { eq: true } }) {
edges {
node {
strapiId
name
variants {
images {
url
}
}
}
}
}
}
`;
const Component = () => {
const [selectedSlides, setSelectedSlides] = useState(0);
console.log(data);
data.allStrapiProduct.edges.map(({ node }) => {
// For this case, use optional chaining to work your way up the tree
console.log(node);
console.log(node?.variants);
console.log(node?.variants?.[0]);
console.log(node?.variants?.[0]?.images);
console.log(node?.variants?.[0]?.images?.[0]);
});
};
To stop right at the area this happens:
data.allStrapiProduct.edges.map(({ node }) => {
if (!node?.variants?.[0]?.images?.length) {
console.log(node);
console.log(node?.variants);
console.log(node?.variants?.[0]);
console.log(node?.variants?.[0]?.images);
console.log(node?.variants?.[0]?.images?.[0]);
throw "Required data missing";
}
});
Resilient React with GraphQL
GraphQL structured data may be typed, but unless the fields are required, you have to handle null.
To make the client-side code more robust, you can use Array.prototype.filter() to skip objects with no images, or - as in this example - make components handle the case where data is missing. It's entirely up to how you want the frontend app to render it.
I haven't tested this code, so take it as "pseudo-JSX" to demonstrate handling empty graphql response fields.
const ProductVariant = ({ variant }) => {
if (!variant) return null;
return (
<div className="variant">
{variant?.images?.map?.((image, idx) => (
<img src={url} key={idx} />
))}
</div>
);
};
const ProductVariants = ({ variants }) => {
if (!variants?.length > 0) return null;
return (
<div className="variant-list">
<div>Variants</div>
<div className="variant-list--items">
{variants?.map?.((variant, idx) => (
<ProductVariant variant={variant} key={idx} />
))}
</div>
</div>
);
};
const Products = () => {
const [selectedSlides, setSelectedSlides] = useState(0);
console.log(data);
const products = data.allStrapiProduct.edges.map(({ node }) => {
// Handle as you deem fit
});
return (
<div id="products">
<p>Here is a list of products</p>
<div className="products">
{products?.map?.((product, idx) => {
return (
<div className="product" key={idx}>
{product?.variants?.length > 1 && (
<ProductVariants variants={variants} />
)}
</div>
);
})}
</div>
</div>
);
};

Related

data.map is not a function react js

I'm new to react and trying to connect firestore for my project.
I followed the example from the Internet and everything works, the data is deleted and written to the database, also when I change the data they change in the database, but I get errors in the console and a white screen.
Uncaught TypeError: data.map is not a function
If you need any more files or code, I will correct my question, please write which ones I need to add
Also, when loading the page, I get the following error in the console:
Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist. at wrappedSendMessageCallback
Here is the code that throws the error:
export default function Saved({ data, setData }) {
function editData(id, newWord, newTranslate, newNote) {
const editedDataList = async (card) => {
if (id === card.id) {
return {
...card,
word: newWord,
translate: newTranslate,
note: newNote,
};
}
let newFields = {
word: newWord,
translate: newTranslate,
note: newNote,
}
await updateDoc(doc(db, "db-name", id), newFields);
console.log(newFields)
return card;
};
setData(editedDataList);
}
const deletePost = async (id) => {
await deleteDoc(doc(db, "db-name", id));
};
const dataList = data.map((card) => (
<SavedData
id={card.id}
key={card.id}
word={card.word}
translate={card.translate}
note={card.note}
editData={editData}
del={deletePost}
/>
));
return (
<div>
<div className="sec-menu"></div>
<div className="saved-inner">
{data.length >= 1 ? (
<div className="saved-list">{dataList}</div>
) : (
<Link className="main-btn" to="/addcard">
Add
</Link>
)}
</div>
</div>
);
}
Here Menu.js code:
function Menu() {
const [data, setData] = useState([]);
useEffect(() => {
const q = query(collection(db, "db-name"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
let wordsArr = [];
querySnapshot.forEach((doc) => {
wordsArr.push({ ...doc.data(), id: doc.id });
});
setData(wordsArr);
});
return () => unsubscribe();
}, []);
return (
<div className="content">
<AuthContextProvider>
<Routes>
<Route
path="saved"
element={<Saved data={data} setData={setData} />}
/>
</Route>
</Routes>
</AuthContextProvider>
</div>
);
}
export default Menu;
On second glance, the issue is where you call setData(editedDataList). You're passing in a function into this method which is in turn updating data to be a function instead of an array. Try changing, editData() to be something like this:
const editData = async (id, newWord, newTranslate, newNote) => {
const editedDataList = await Promise.all(data.map(async (card) => {
let newFields = {
word: newWord,
translate: newTranslate,
note: newNote,
};
if (id === card.id) {
return { ...card, ...newFields };
}
await updateDoc(doc(db, "db-name", id), newFields);
console.log(newFields);
return card;
}));
setData(editedDataList);
};
editedDataList will be an array of the modified cards in the original and setData() should work as expected.
maybe the error occurs because "data" object is not an array.
And check what are you setting on "setData(editedDataList);" instruction

React Apollo Client: Pagination and fetch data on button click

I am new to Apollo Client and want to implement pagination. My code looks like this:
I am using RickandMorty endpoint for this (https://rickandmortyapi.com/graphql)
useCharacters.tsx
import { useQuery, gql } from '#apollo/client';
const GET_ALL_CHARACTERS = gql`
query GetCharacters($page: Int) {
characters(page: $page) {
info {
count
pages
}
results {
id
name
}
}
}
`;
export const useCharacters = (page: number = 1) => {
const { data, loading, error } = useQuery(GET_ALL_CHARACTERS, { variables: { page } });
return { data, loading, error };
};
App.tsx
export const App = () => {
const { data, loading, error } = useCharacters(1);
const nextPage = () => {
const { data, loading, error } = useCharacters(2);
};
return (
<>
{loading ? (
<div> Loading... </div>
) : error ? (
<div>Error</div>
) : (
<>
<CharacterList data={data.characters.results} />
<div onClick={nextPage}> Next </div>
</>
);
};
It is fetching data properly the first time but I want to fetch new data when Next button is clicked on page 2.
I know I can't call useQuery() in a method like this as hooks cannot be called inside a block and also the data, error, and loading won't be accessible outside.
How can I fix this issue? I tried googling it but could not find any help related to this.
This might help other developers who are new to Apollo Client and will save them time.
fetchMore() can be used for pagination with Apollo Client.
useCharacters.tsx
export const useCharacters = (page: number = 1, name: string = '') => {
const { data, loading, error, fetchMore } = useQuery(GET_ALL_CHARACTERS, {
variables: { page, name },
notifyOnNetworkStatusChange: true, // to show loader
});
return { data, loading, error, fetchMore }; // returning fetchMore
};
App.tsx
export const App = () => {
const { data, loading, error, fetchMore } = useCharacters(1);
const nextPage = () => {
/* You can call the returned fetchMore() here and pass the next page number.
updateQuery() simply updates your data to the newly fetched records otherwise return previous records
*/
fetchMore({
variables: {
page: 2,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return fetchMoreResult;
},
});
};
return (
<>
{loading ? (
<div> Loading... </div>
) : error ? (
<div>Error</div>
) : (
<>
<CharacterList data={data.characters.results} />
<div onClick={nextPage}> Next </div>
</>
);
};

TypeError: Cannot read property 'fetchMore' of undefined in Apollo

I'm implementing an infinite scroll with Apollo and React. Everything works fine. When I navigate away from Feed and then back to Feed I'm getting this weird error:
TypeError: Cannot read property 'fetchMore' of undefined
Has anyone else had experience with this issue? I found this but there doesn't seem to yet be any solutions. One of the answers mentions a partial solution "checking the route before executing fetchMore" but I don't know what means.
I'm still in the middle of development so I haven't had a chance to clean this component up yet, but here it is:
import React, { useEffect, useRef } from 'react';
import { useQuery } from '#apollo/client';
import PostUpdateOrShow from '../posts/types/showOrUpdate/PostUpdateOrShow.js'
import Cookies from 'js-cookie';
import Queries from '../../graphql/queries';
import InfiniteScroll from './util/Infinite_Scroll.js';
const { FETCH_USER_FEED, FETCH_TAG_FEED } = Queries;
const Feed = ({
user, tag
}) => {
let fetchMoreDiv = useRef(null);
let cursorId = useRef(null);
useEffect(() => {
var scroll = document.addEventListener('scroll', function(event) {
fetchMoreDiv.current = document.querySelector('#fetchMore')
var el = fetchMoreDiv.current.getBoundingClientRect()
var elTop = el.top
var elBottom = el.bottom
var innerHeight = window.innerHeight
if (elTop >= 0 && elBottom <= innerHeight) {
fetchMore({
query: gqlQuery,
variables: {
query: query,
cursorId: cursorId.current
},
})
}
})
return () => {
document.removeEventListener('scroll', scroll)
}
})
var gqlQuery
var query
if (user) {
gqlQuery = FETCH_USER_FEED
query = user.blogName
} else if (tag) {
gqlQuery = FETCH_TAG_FEED
query = tag.title.slice(1)
} else {
gqlQuery = FETCH_USER_FEED
query = Cookies.get('currentUser')
}
let { loading, error, data, fetchMore } = useQuery(gqlQuery, {
variables: {
query: query,
cursorId: null
},
})
if (loading) return 'Loading...';
if (error) return `Error: ${error}`;
const { fetchUserFeed, fetchTagFeed } = data
cursorId.current = fetchUserFeed ? fetchUserFeed[fetchUserFeed.length - 1]._id :
fetchTagFeed[fetchTagFeed.length - 1]._id
if (tag) {
return(
<div>
<div>
{fetchTagFeed.map((post, i) => {
return (
<div
className='post'
key={post._id}
>
<PostUpdateOrShow
post={post}
/>
</div>
)
})}
</div>
<InfiniteScroll
fetchMoreDiv={fetchMoreDiv}
/>
<div
id='fetchMore'
>
</div>
</div>
)
} else {
return(
<div>
<div>
{fetchUserFeed.map((post, i) => {
return (
<div
className='post'
key={post._id}
>
<PostUpdateOrShow
post={post}
/>
</div>
)
})}
</div>
<InfiniteScroll
fetchMoreDiv={fetchMoreDiv}
/>
<div
id='fetchMore'
>
</div>
</div>
)
}
}
export default Feed;
Apollo client config:
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
fetchLikesRepostsAndComments: {
merge: (existing = [], incoming = []) => {
return incoming
}
},
fetchUserFeed: {
keyArgs: ['query'],
merge: (existing = [], incoming = []) => {
const elements = [...existing, ...incoming].reduce((array, current) => {
return array.map(i => i.__ref).includes(current.__ref) ? array : [...array, current];
}, []);
return elements
},
}
}
}
}
}),
errorLink
})
I believe the issue is that you are using fetchMore within useEffect hook.
Try to rethink your code to avoid this. Using fetchMore outside the hook would work flawlessly.

FetchAPI GraphQL / Gatsby throw error on frontend if null data

I have set up a band site using Gatsby which utilises the Bandsintown API to get events whether they are upcoming or past events.
Displaying past events is fine, but I would like to display a message on the frontend if there are no upcoming events / no events i.e no data comes back from the fetch API call. I am not really sure how to go about this?
Currently I cannot even build the solution and get error in my console:
Cannot query field "allBandsInTownEvent" on type "Query"
Can anybody point me in the right direction?
gatsby-node.js (local plugin)
const fetch = require("node-fetch")
const queryString = require("query-string")
exports.sourceNodes = (
{ actions, createNodeId, createContentDigest },
configOptions
) => {
const { createNode } = actions
delete configOptions.plugins
const processEvent = event => {
const nodeId = createNodeId(`bandsintown-event-${event.id}`)
const nodeContent = JSON.stringify(event)
const nodeData = Object.assign({}, event, {
id: nodeId,
parent: null,
children: [],
internal: {
type: `BandsInTownEvent`,
content: nodeContent,
contentDigest: createContentDigest(event),
},
})
return nodeData
}
const artist = configOptions.artist
const app_id = configOptions.app_id
const apiUrl = `https://rest.bandsintown.com/artists/${artist}/events?app_id=${app_id}&date=upcoming` //upcoming or past
return fetch(apiUrl).then(response =>
response.json().then(data => {
if (Object.entries(data).length === 0) {
return some kind of error? i.e document.querySelector(".no-shows").innerHTML = "No Shows on at the moment"
}
data.forEach(event => {
const nodeData = processEvent(event)
createNode(nodeData)
})
})
)
}
events.js
import React from "react"
import { graphql, StaticQuery } from "gatsby"
import "./index.scss"
export default () => (
<StaticQuery
query={graphql`
query EventsQuery {
allBandsInTownEvent(
limit: 8
sort: { fields: [datetime], order: DESC }
) {
nodes {
id
venue {
city
country
name
}
artist {
id
facebook_page_url
image_url
name
}
datetime(formatString: "ddd, MMM DD YYYY")
url
}
}
}
`}
render={data => (
<div className="Events">
<div className="event-header">Tour Dates</div>
<div className="event-content">
{data.allBandsInTownEvent.nodes.map(node => (
<div className="event-item" key={node.id}>
<div className="event-details">
<div className="event-datetime">{node.datetime}</div>
<div className="event-name">{node.venue.name}</div>
<div className="event-venue">
<div className="city">{node.venue.city}</div>
<div className="region">{node.venue.region}</div>
<div className="country">{node.venue.country}</div>
</div>
</div>
<div className="event-cta">
<a
href={node.url}
rel="noreferrer"
target="_blank"
title={node.title}
>
Tickets
</a>
</div>
</div>
)
)}
</div>
</div>
)}
/>
)

array.forEach is not a function

I'm showing array of objects that is received from api call.I'm using react hooks.when I tried to iterate through array of objects it return foreach is not a function.
for this can I use await and async.
function searchArticle() {
const [articleList, setArticleList] = useState([]);
const { articleApiStatus, articleCurrentPage, searcArticle } = state;
useEffect(() => {
setArticleList(state.articleList);
articleListApiCall(articleCurrentPage);
}, [])
const articleListApiCall = (page = 1) => {
const isNewPage = articleCurrentPage !== page;
if (!articleApiStatus || isNewPage) {
getArticleList(page, '')
}
}
const getArticleList = async (page, searchkey) => {
const requestBody = {
page: page - 1, searchkey
}
await onArticleSearchPost(requestBody).then(articleresult => {
setArticleList(articleresult);
}).catch(err => {
console.log(err);
})
}
return (
<React.Fragment>
<div className="article-section">
<div className="search-result">
<Collapse >
{
articleList.forEach(element => {
<Panel header={element.article_title} key={element._id}>
<p>{element.article_body}</p>
</Panel>
})
}
</div>
<div className="pagination-section">
<Pagination defaultCurrent={1} total={50} />
</div>
</div>
</React.Fragment>
)
}
export default searchArticle;
Edit : I'm receiving following data from api call
[
{
"_id":"5d9efbc3b29b2876700adf6f",
"postedBy":"Admin",
"datePosted":"1570700227888",
"__v":0
}
]
First of all, do not use forEach to render JSX, it won't work. Use map instead:
<Collapse>
{articleList.map(element => { // use map
return (
<Panel header={element.article_title} key={element._id}>
<p>{element.article_body}</p>
</Panel>
);
})}
</Collapse>
Second, make sure that state.articleList, which you're setting to state in useEffect, is an actual array.

Resources