Hello dear Stack Overflow, I just started a Gatsby website but I'm having issues looping through an array passed to a component.
What I'm trying to do:
I have a Gatsby page called blog.js, in this page I have been showing blog titles retrived via GraphQL. Using a loop directly in the blog.js page I can see all the titles.
My loop inside of blog.js looks like this
<div>
<h1>Blogg data</h1>
{data.posts.edges.map (({ node }) => (
<p>{node.title}</p>
))}
</div>
It retrieves data from the following GraphQL query
export const query = graphql`
query BlogPageQuery {
posts: allSanityPost(
limit: 12
sort: { fields: [publishedAt], order: DESC }
) {
edges {
node {
id
publishedAt
mainImage {
asset {
_id
}
alt
}
title
_rawExcerpt
slug {
current
}
}
}
}
}
`
Instead of creating the blog posts previews in blog.js I instead want to use a component to do this. I've created a component called BlogPostPreviewGrid and call it like this from blog.js
<BlogPostPreviewGrid blogPosts={data}/>
My BlogPostPreviewGrid component currently looks like this
const BlogPostPreviewGrid = blogPosts => {
return (
<div>
<p>Here be component data</p>
{console.log(blogPosts)}
{blogPosts.posts.edges.map (({ node }) => (
<p>{node.title}</p>
))}
</div>
)
}
export default BlogPostPreviewGrid
What's not working:
I cannot loop through the data retrieved by the component, when running the loop I get a console error massage stating blogPostPreviewGrid.js:13 Uncaught TypeError: Cannot read property 'edges' of undefined
What have I tried:
My first response was to console.log blogPosts, console.log shows the array object, I've attached a the array from Chromes console log
blogPostPreviewGrid.js:13 {blogPosts: {…}}blogPosts: posts: edges: Array(2)0: node: {id: "54fe241a-c7d4-50d2-be51-4403304ddc86", publishedAt: "2020-01-05T23:00:00.000Z", mainImage: {…}, title: "Testpost2", _rawExcerpt: Array(1), …}__proto__: Object1: {node: {…}}length:
I've also written a conditional statement so that the component only tries to render the data if something exists in blogPosts and tried tweak the loop. I ended up with doing a git reset --hard so the conditional rendering is not present right now.
Thank you for all replies!
Values passed to children components in react are passed in one big object known as props: https://reactjs.org/docs/components-and-props.html.
So you need to either destructure props in your function call:
const BlogPostPreviewGrid = ({blogPosts}) => {
// do stuff
console.log(blogPosts)
}
Or use props object
const BlogPostPreviewGrid = props => {
// do stuff using blogPosts
console.log(props.blogPosts);
}
If your component looked something like this <Foo prop1={prop1} prop2={prop2} /> then you would access it like so:
const Foo = props => {
console.log(props.prop1);
console.log(props.prop2);
}
or like so:
const Foo = ({ prop1, prop2 }) => {
console.log(prop1);
console.log(prop2);
}
This should work:
BlogPostPreviewGrid.jsx
const BlogPostPreviewGrid = ({blogPosts}) => {
return (
<div>
{blogPosts.posts.edges.map (({ node }) => (
<p>{node.title}</p>
))}
</div>
)
}
export default BlogPostPreviewGrid
Related
I'm modifying some code to use React Query rather than useEffect - see new code using React Query below:
import axios from 'axios';
import { useQuery } from '#tanstack/react-query'
function MembersList() {
const { data } = useQuery(["members"], () => {
return axios.get('http://localhost:3001/members').then((res) => res.data)
})
return (
<div className="List">
{data?.map((value, key) => {
return (
<div className="member">
<div key={member_id}> {value.forename} {value.surname}</div>
</div>
);
})}
</div>
);
}
export default MembersList;
I'm getting an error that 'member_id' is not defined - arising from the row where I try and add 'member_id' as a key (see below).
Error Message
'Member_id' is the first field in the array, see below JSON from Insomnia:
JSON showing the 'member_id field'
The error is clearly telling me to define 'member_id' but I'm not sure how or where specifically to do that.
If I remove the 'key={member_id}' then the code compiles and runs, but throws a warning that "Each child in a list should have a unique "key" prop.".
I've reviwed many similar issues on Stack Exchange and React docs, but still can't see why my code isn't working.
The thing you are getting back from the request is an object. An object can be thought of like a dictionary, you look up a word and it has a definition attached to it. member_id is just one of the words you can look up in this object. Right now, you don't specify what member_id is so javascript "thinks" it should be a variable that you defined, similar to data above. However, what you really want is the value of member_id that is present in the object. Therefore you should change it to value.member_id where value is one of the objects in your data list.
A visual way of thinking about it is like this
data = [{...}, {...}, ...];
value = data[0]; // this is what map is doing except for 0...N-1 where N is the length of your list
value;
> {...}
value.member_id;
> 1
Therefore, change your code to this:
import axios from 'axios';
import { useQuery } from '#tanstack/react-query'
function MembersList() {
const { data } = useQuery(["members"], () => {
return axios.get('http://localhost:3001/members').then((res) => res.data)
})
return (
<div className="List">
{data?.map((value, key) => {
return (
<div className="member" key={value.member_id}> // <<<
<div> {value.forename} {value.surname}</div>
</div>
);
})}
</div>
);
}
export default MembersList;
import React, {useState, useEffect} from "react";
import './App.css';
function App() {
const [ data, setData ] = useState([]);
useEffect( ()=> {
loadData();
//getData();
}, []);
const loadData = async () => {
await fetch("https://randomuser.me/api")
.then(response => response.json())
.then(receiveddata => setData(receiveddata));
}
console.log(data);
return (
<div className="App">
<p> Fetch/ Async/ Await</p>
{data.map(user => (
<div key={user.id}>{user.name}, {user.email}</div>
))}
</div>
);
}
export default App;
I'm getting this error on the console,
Uncaught TypeError: data.map is not a function
You are getting the error because data is not an Array. The response that you are getting back is an object that has two properties results and info. Since your state is an array, set the state to receiveddata.results instead.
Firsteval, your console.log(data) will return nothing, because the state will not be updated when your code will run the console.log() ; so don't expect to get debug info with that.
Next, your response is an object, not an array.
You can do this way : setData(receiveddata.results) then you can map() your data.
In your render you should also check if map is not empty like this:
{data && data.map(user => (
<div key={user.id}>{user.name}, {user.email}</div>
))}
You need to check the structure of data comping from your API. Your array is inside "results" property. And "title" is an object as well. You can find a working example of your code over here CodeSandbox. Check the console you will better understand the structure of your response
You're getting this error because you are treating data as an array while it contains an object, which is what the API returns in this case.
Consider the schema of the data you're getting back from the API which is something similar to below. You can see that results is the key for an array within an object:
{
results: [
{
...
email: string;
id: {
name: string;
value: string;
}
gender: string;
name: {
title: string;
first: string;
last: string;
}
...
}
}
So, to access the first, last names and email properties, you need to call the map() method on the results array within the object which was assigned to data as shown below.
Make sure that data.results is assigned before trying to access it with data.results && data.results.map(..):
{
data.results && data.results.map(user => (
<div key={user.id.value}>
{user.name.first}, {user.name.last}, {user.email}
</div>))
}
I'm working on a site where I have a gallery and custom build lightbox. Currently, I'm querying my data with a page query, however, I also use them in other components to display the right images and changing states. It is easier for me to store states in Context API as my data flow both-ways (I need global state) and to avoid props drilling as well.
I've setup my context.provider in gatsby-ssr.js and gatsby-browser.js like this:
const React = require("react");
const { PhotosContextProvider } = require("./src/contexts/photosContext");
exports.wrapRootElement = ({ element }) => {
return <PhotosContextProvider>{element}</PhotosContextProvider>;
};
I've followed official gatsby documentation for wrapping my root component into context provider.
Gallery.js here I fetch my data and set them into global state:
import { usePhotosContext } from "../contexts/photosContext";
const Test = ({ data }) => {
const { contextData, setContextData } = usePhotosContext();
useEffect(() => {
setContextData(data);
}, [data]);
return (
<div>
<h1>hey from test site</h1>
{contextData.allStrapiCategory.allCategories.map((item) => (
<p>{item.name}</p>
))}
<OtherNestedComponents />
</div>
);
};
export const getData = graphql`
query TestQuery {
allStrapiCategory(sort: { fields: name }) {
allCategories: nodes {
name
}
}
}
`;
export default Test;
NOTE: This is just a test query for simplicity
I've double-checked if I get the data and for typos, and everything works, but the problem occurs when I try to render them out. I get type error undefined. I think it's because it takes a moment to setState so on my first render the contextData array is empty, and after the state is set then the component could render.
Do you have any idea how to work around this or am I missing something? Should I use a different type of query? I'm querying all photos so I don't need to set any variables.
EDIT: I've found a solution for this kinda, basically I check if the data exits and I render my component conditionally.
return testData.length === 0 ? (
<div className="hidden">
<h2>Hey from test</h2>
<p>Nothing to render</p>
</div>
) : (
<div>
<h2>Hey from test</h2>
{testData.allStrapiCategory.allCategories.map((item) => (
<p>{item.name}</p>
))}
</div>
);
However, I find this hacky, and kinda repetitive as I'd have to use this in every component that I use that data at. So I'm still looking for other solutions.
Passing this [page queried] data to root provider doesn't make a sense [neither in gatsby nor in apollo] - data duplication, not required in all pages/etc.
... this data is fetched at build time then no need to check length/loading/etc
... you can render provider in page component to pass data to child components using context (without props drilling).
I'm trying to pass props from my Parent component to child component. Here are the important snippets:
Snippet 1: This is the object that contains the prop (integer).
const cardProps = {
cardProps0: 0,
Snippet 2: This is the Card component within the parent component that carries the prop to child component
return (
<MyCardLink source={cardProps.cardProps0} />
Snippet 3: This is the child component (MyCardLink)
useEffect((props) => {
axios
.get(
'http://newsapi.org/v2/everything?q=economy&apiKey=XXXXXXXXXXXXXXXX'
)
.then((res) => {
console.log(res);
setNews(res.data.articles[props.source]);
})
.catch((err) => {
console.log(err);
});
}, []);
The goal is that [prop.source] contains a number value from a list of an array served by an API. If I just place a number value in the child component (MyCardLink) in place of [props.source] on the setNews function then it renders the component no problem.
My problem is when I pass the prop from parent component to child component and use [prop.source], nothing renders and all I get from the console log is:
Cannot read property 'source' of undefined.
Am I doing something wrong?
Instead of passing props into your useEffect, you need to add into your MyCardLink component's parameters as:
const MyCardLink = (props) => {
// your component's defintion
}
Additionally you can destructure as the following:
const MyCardLink = (props) => {
const { source } = props
// ... rest
}
Then simply you can use in your useEffect without props as:
useEffect(() => {
axios.get(
'http://newsapi.org/v2/everything?q=economy&apiKey=XXXXXXXXXXXXXXXX'
)
.then((res) => {
console.log(res);
setNews(res.data.articles[source]);
})
.catch((err) => {
console.log(err);
}
);
}, []);
Based on your other question from the comment section what I would do is:
Change the initial value of the state from "" to null as const [news, setNews] = useState(null).
Also I would use && for null check and render <Card /> component only if it has value as news && <Card className={classes.root}> in your return.
The reason behind this is your API response is arriving asynchronously.
use use props in component below:
const MyCardLink =(props)=>{
...
...
...
}
export default MyCardLink;
I'm working on a react/redux project that uses axios to get data, but some of the data is not accessible using dot notation for some reason.
For example, this pulls in the data just fine:
console.log(this.props.apts.data)
while this returns TypeError: Cannot read property 'apartments' of undefined:
console.log(this.props.apts.data.apartments)
I know for a fact there is data present. This happens in other parts of the file as well, not just this one particular array of objects.
Here's one of the JSON files I'm working with: https://api.myjson.com/bins/x2ad4
This is my action creator:
export const fetchPosts = () => {
return async dispatch => {
const response = await jsonPlaceholder.get('https://api.myjson.com/bins/x2ad4');
dispatch({ type: 'FETCH_POSTS', payload: response })
}
}
If I change the payload to response.data.apartments, I can then get access to the apartments array in my component like so:
class PostList extends Component {
componentDidMount() {
this.props.fetchPosts();
}
formatPricing(price) {
if(this.props.apts[0].pricing.currency === 'EUR') {
return `${price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")} €`;
} else {
return `£${price}`
}
}
renderList() {
return this.props.apts.map(apartment => {
return (
<Grid key={apartment._id} item lg={4} md={6} xs={12}>
<div>
<img style={{objectFit: 'cover'}} src={apartment.images.photos[0].path} alt="" width='100%' height="150px"/>
<div className="containeraps">
<div className='aptprice'>{this.formatPricing(apartment.pricing.price)}</div>
<div className="monthutil">
<div>Per Month</div>
<div>Utilities incl.</div>
</div>
</div>
<div className="movein">
from 29.24.2019 - {parseInt(apartment.details.squareMeters)} m² - {apartment.bedroomCount} bedroom
</div>
</div>
</Grid>
)
})
}
render() {
return (
<div className='aptlist'>
<Grid container spacing={24}>
{this.renderList()}
</Grid>
</div>
);
}
}
const mapStateToProps = (state) => {
return { apts: state.PostsReducer }
}
export default connect(mapStateToProps,
{fetchPosts}
)(PostList);
But if I just use the response as the payload, then I can't access the apartments array because 'return this.props.apts.data.apartments.map(apartment =>' gives me an TypeError: Cannot read property 'apartments' of undefined.
Unfortunately, I need data from just the response as well, so I don't have a workaround.
This is my reducer:
export default (state = [], action) => {
switch(action.type) {
case 'FETCH_POSTS':
return action.payload;
default:
return state;
}
}
I initially thought it had something to do with not loading the data, but since some of the data is present it must all be accessible. That's what's stumping me right now, why is only some of the data accessible while some isn't.
I've switched around how I'm referencing the data using dot notation in various different files to change how I pull it in, but so far there's only 1 way to get access to the apartments array. The crazy thing is that I can console.log it even when it doesn't work, and I see the data in the console.
Update: I can't update anything that is more than 2 calls deep using dot notation. So props.apts.queryParams returns info, but using dot notation to get anything inside of queryParams doesn't work either.
And for the apartments array, I cannot view any unique indexes of that array. So props.apts.apartments display the array of data, while props.apts.apartments[0] doesn't display any data at all. I tried using spread operators to fix this but I wasn't able to find a solution.
I found a workaround, it's not elegant but it gets the job done. I created several reducers and dispatched different levels of the json based on the data I needed.
dispatch({ type: 'APARTMENT_LIST', payload: response.data.apartments })
dispatch({ type: 'APARTMENT_TOTAL', payload: response.data})
dispatch({ type: 'CITY_NAME', payload: response.data.queryParams})