React render array of images in array - reactjs

end app for woocommerce store, but i have problem rendering the first image of array in
when i console.log(images.src) i see the list of urls of the images, but in img src= it return : TypeError: Cannot read property 'src' of undefined
I will be very thankful to help me correctly map the images.
here is my code:
class App extends React.Component {
constructor(props) {
super(props);
this.getPosts = this.getPosts.bind(this);
this.state = {
posts : [],
images: []
};
}
getPosts = async () => {
let res = await api.get("products", {
per_page: 20,
})
let { data } = await res;
this.setState({ posts: data });
}
componentDidMount = async () => {
await this.getPosts();
};
render() {
const { posts } = this.state;
const { images } = this.state
return(
<div>
<Head>
<title>Онлайн магазин KIKI.BG</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<React.Fragment >
{posts.map((posts, index ) => {
{
posts.images.map((images, subindex) =>
console.log(images.src),
<img src={images[0].src} />
)}
return (
<div>
<h1>{posts.name}</h1>
<h2>{posts.price}</h2>
</div>
)})}
</React.Fragment>
</div>
)
}
}
export default App;

{posts.map((posts, index ) => {
{
posts.images.src.map((image, subindex) =>
<img src={image.src} />
)}
return (
<div>
<h1>{posts.name}</h1>
<h2>{posts.price}</h2>
</div>
)
})}

well, console.log(images.src) i see the list of urls of the images doesn't make any sense.. images is array. So images[0] should be image with data with property src on it?. Btw a lot of stuff in this code is just wrong.
Don't rebind getPosts already bound getPosts function in constructor (via class property) (getPosts). BTW you dont need to bind is here at all, its not called as a callback.
Its weird, that you call await res after api.get() ... shouldn't be it just await api.get()? Another await is usually used on fetch, when you do something like await response.json().
There is no need for async/await in componentDidMount
If getPosts will throw it will mess up your component, its better to handle error in catch and call props.onError(error) for example
You don't have any key attributes on element in map, thats wrong. You should put some unique id there (url fe? if not same, or id) for proper component re-render.
You have some weird brackets issue in your maps...
You shouldn't use more than one h1 one the page :-)
images.src should be string, not array...
Why is there subindex and index when u are not using it?
Why you store images when they are not filled anywhere? Are they in the response of get? Thats maybe why u get an TypeError !
I would add loading and no data message...
That would be my code:
import { Component, Fragment } from 'react';
class App extends Component {
static defaultProps = {
onError: console.error;
};
state = {
posts: [],
images: [],
loading: false,
};
// This could be done with hooks much better tho...
async componentDidMount () {
this.setState({ loading: true });
try {
await this._fetchData();
}
catch (error) {
this.props.onError(error); // Or something rendered in state.error?
}
finally {
this.setState({ loading: false });
}
}
render () {
const { images, posts, loading } = this.state;
if (!images.length) {
return <div>No data.</div>;
}
if (loading) {
return <div>Loading</div>;
}
const postBoxes = posts.map((post, index) => {
const image = images[index];
// Because you don't know, if that specific image is there... if this are your data..
const imageElement = image ?
<img src={image.src} alt="dont know" /> :
null;
const { name, price } = post;
// If name is unique, otherwise some id.
return (
<Fragment key={name} >
{imageElement}
<h2>{name}</h2>
<h3>{price}</h3>
</Fragment>
);
});
return (
<div>
<Head>
<title>Онлайн магазин KIKI.BG</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Fragment>
{postBoxes}
</Fragment>
</div>
);
}
async _fetchData () {
const { data } = await api.get('products', { per_page: 20 });
const { posts, images } = data;
this.setState({ posts, images });
}
}
export default App;

if console.log(images.src) -> gives list of images.
Then,
<img src={images.src[0]}/> -> should do the trick.
may be, Add a null check to be certain.
images.src[0] && <img src={images.src[0]}/>

Related

React Encountered two children with the same key

I am making a test news app with infinite scroll react but when I scroll for more news I got the same news that I have seen above and get the error in console like:
react-dom.development.js:67 Warning: Encountered two children with the
same key,
https://www.nytimes.com/2022/04/01/movies/oscars-will-smith-slap.html.
Keys should be unique so that components maintain their identity
across updates. Non-unique keys may cause children to be duplicated
and/or omitted — the behavior is unsupported and could change in a
future version.
at div
at div
at div
at div
at InfiniteScroll (http://localhost:3000/static/js/bundle.js:35314:24)
at New (http://localhost:3000/static/js/bundle.js:590:5)
at Routes (http://localhost:3000/static/js/bundle.js:37042:5)
at Router (http://localhost:3000/static/js/bundle.js:36975:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:36451:5)
at div
at App (http://localhost:3000/static/js/bundle.js:36:5)
The place where I am using the keys is app
import NewsIItem from './NewsIItem'
import InfiniteScroll from 'react-infinite-scroll-component';
import ScrollLoader from './ScrollLoader';
export class New extends Component {
static defaultProps = {
category: "general"
}
// static propTypes = {
// category : PropTypes.string
// }
constructor() {
super();
this.state = {
articles: [],
loading: false,
page: 1,
totalResults: 0
}
}
async componentDidMount() {
// console.log("Inside the cdm");
let myUrl = `https://newsapi.org/v2/top-headlines?country=us&category=${this.props.category}&apiKey={private}&page=1&pageSize=${this.props.pageSize}`
let data = await fetch(myUrl)
let parsedData = await data.json()
console.log(parsedData);
this.setState({ articles: parsedData.articles, totalResults: parsedData.totalResults })
}
fetchData = async () => {
this.setState({
page: this.state.page + 1
})
let myUrl = `https://newsapi.org/v2/top-headlines?country=us&category=${this.props.category}&api= {private}&page=1&pageSize=${this.props.pageSize}`
this.setState({ loading: true })
let data = await fetch(myUrl)
let parsedData = await data.json()
console.log(parsedData);
this.setState({ articles: this.state.articles.concat(parsedData.articles), totalResults: parsedData.totalResults, loading: false })
}
render() {
return (
<>
<h2 className="headlines text-center">Newsers Most updated ~ Headlines</h2>
<InfiniteScroll
dataLength={this.state.articles.length} //This is important field to render the next data
next={this.fetchData}
hasMore={this.state.articles.length !== this.state.totalResults}
loader={<ScrollLoader />}
>
<div className="container">
<div className="row my-3">
{this.state.articles.map((element) => {
return <div className="col-md-4" key={element.url}>
<NewsIItem title={element.title} description={element.description} imageUrl={!element.urlToImage ? "https://i.pinimg.com/originals/d1/a6/2a/d1a62a6d8969170025f279115470e34b.jpg" : element.urlToImage} newsId={element.url} />
</div>
})}
</div>
</div>
</InfiniteScroll>
</>
)
}
}
export default New
Just update this line of code:
{this.state.articles.map((element) => return <div className="col-md-4" key=
{element.url}>
To this
{this.state.articles.map((element,index)=> {
return <div className="col-md-4" key={index}>
Yo have this line twice:
<Route exact path="/sports" element={<New key= "sports" pageSize={this.pageSize} category="sports" />} />
Remove one or change the key values.

How to fetch data using link in Nextjs

My data is stored in Mongo Atlas. I can fetch the data (posts) but when I click on the post, it doesn't provide me with the data of that ID. I have created a pages/posts page. I am only able to view the ID of the post due to the code below which is in postlist.tsx and is only passing the id to the next page via its url. I want to be able to view the title and body of the post associated with that particular ID as well:
This is my blogs.tsx
interface PostState{
posts: Post[];
}
export default class PostList extends React.Component <{},PostState> {
state = {
posts: []
};
componentDidMount = () => {
this.getBlogPost();
};
getBlogPost = () => {
axios
.get('/api')
.then(({data}) => {
const reverseData = new Array;
for (let datetime = data.length - 1; datetime >= 0; datetime--) {
reverseData.push(data[datetime]);
}
this.setState({posts: reverseData})
})
.catch((error) => {
alert('Error: there was an error processing your request')
})
}
displayBlogPost = (posts : Post[]) => {
const currentDateTime = new Date();
if (!posts.length)
return null;
return posts.map((post, index) => (
<div key={index}>
<Card>
<Title>
<Link href={`/post?id=${post._id}`} as= .
{`/post/${post._id}`}></Title>
{/* <p>{post.date}</p>
<p>{post.name}</p> */}
<FullName>{`${post.name} |
${dateToString(currentDateTime)}`}</FullName>
<Line />
<Question >{post.body}</Question>
</Card>
</div>
));
}
render() {
return (
<div>
<Container>
<Headers/>
<div className="blog">
{this.displayBlogPost(this.state.posts)}
</div>
</Container>
</div>
);
}
}
First you should create the Post route
pages/post/index.js
import React from 'react'
class Post extends React.Component {
static async getInitialProps({ query }) {
return { query };
}
render() {
return <div>id: {this.props.query.id}</div>
}
}
export default Post
Then create pages/post/[id].js
import Post from './index'
export default Post
Now when you open localhost:port/post/22 or localhost:port/post?id=22 it should show you the number 22.
To link to a post from another page you can make the following link:
<Link href={`/post?id=${id}`} as={`/post/${id}`}>
<a>{title}</a>
</Link>
If that still doesn't work then please show us the code (on github).
If it works then now you can pass an id to post page so you only need to fetch the post. You can do that in getInitialProps
class Post extends React.Component {
static async getInitialProps({query}) {
const post = await axios.get(url, { // make sure the url is full url, no relative urls
params: {
id: query.id
}
})
return { post: post.data }
}
render() {
return (
<pre>
post:{' '}
{JSON.stringify(this.props.post, undefined, 2)}
</pre>
)
}
}

How render components after fetching data

I have a container called "Recetas" (Receipes) which has inside a component called "Showcase". The idea is that the user can create a request in "Recetas" and when the data is fetched, the container will re-render and "Showcase" will update with the new data.
I'm saving the request in the state of "Recetas" and passing the data to "Showcase" with props.
The problem is that the render is being execute before I receive the new data. So I'm always showing "old" data. Is there any way I can put on hold the render until I've received the data? Or how can I solve it?
class Recetas extends Component {
state = {
loading: false,
data: [],
maxResult: 12,
minResult: 0,
query: 'burger',
appId: 'xxxxxx',
appKey: 'xxxxx'
}
componentDidMount() {
this.fetchData();
}
async fetchData() {
this.setState({loading: true});
console.log('fetching ...');
try {
const request = `https://api.edamam.com/search?q=${this.state.query}&app_id=${this.state.appId}&app_key=${this.state.appKey}&from=${this.state.minResult}&to=${this.state.maxResult}`;
console.log('request: ', request);
const result = await axios(request);
this.setState({ data: result.data.hits, loading: false });
} catch (error) {
console.log(error);
}
}
queryHandler = value => {
this.setState({
query: value
});
this.fetchData();
}
render() {
return (
<div className={classes.Recetas}>
{console.log('actualState: ', this.state)}
<SearchInput
query={this.state.query}
queryHandler={(value) => this.queryHandler(value)}/>
<Showcase
data={this.state.data}
loading={this.state.loading}/>
</div>
);
}
Showcase component
const showcase = props => {
const spinner = <Spinner />;
const recetas = props.data.map((elem, index) => {
return <Receta key={index} title={elem.recipe.label} url={elem.recipe.image} />
});
console.log('[Showcase] props.data: ', props.data);
return (
<div className={classes.Showcase}>
{props.loading ? spinner : recetas}
</div>
);
}
In case the Function Component not watch the props change. You should move the spinner to the container component.
Recetas.js
render() {
...
const {loading, data} = this.state
return (
...
<div className={classes.Recetas}>
{loading ? < Spinner /> : <Showcase data={data} />}
</div>
);
}
//////////////
ShowCase.js
const showcase = props => (
<div className={classes.Showcase}>
{
props.data.map(({recipe: {label, imgage}}, index) => <Receta key={index} title={label} url={image} />)
}
</div>
)
And using Destructuring_assignment for shorter code.
Finally I managed to solve it adding a setTimeout function which execute the fetch 500 ms after updating the state in 'queryhandler' method.

how to iterate through json data from api in react.js

I just started learning React and am trying to loop through an array of JSON data. However, I am getting some syntax errors. I'm trying to use the array.map function, but it's not working properly, and I'm not exactly sure how to implement it to make it display each element in the JSON array instead of just one. Any help is greatly appreciated - thanks!
import React, { Component } from 'react';
import axios from "axios";
import './App.css';
import UserForm from "./components/UserForm.js";
class App extends Component {
state = {
name: "",
stars: "",
icon: "",
trails: [], isLoaded: false
}
getUser = (e) => {
e.preventDefault();
const address = e.target.elements.address.value;
if (address) {
axios.get(`https://www.hikingproject.com/data/get-trails?lat=40.0274&lon=-105.2519&maxDistance=10&key=200279581-dd891420fa2c470dbb683b34e017062a`)
.then((res) => {
console.log(res);
const trailList = res.data.trails.map((trail) => {
console.log(trail.name)
console.log(trail.stars)
return <div> <p>{trail.name}</p> </div>
})
this.setState({ trails: trailList, isLoaded: true });
const name = res.data.trails.name;
const stars = res.data.trails.stars;
const icon = res.data.trails.imgMedium;
this.setState({ name });
this.setState({ stars });
this.setState({ icon });
})
}
else return;
}
render() {
return (
<div>
<div className="App">
<header className="App-header">
<h1 className="App-title">HTTP Calls in React</h1>
</header>
<UserForm getUser={this.getUser} />
<div className="newmessage">
{this.state.trails.map((obj) => {
return(
<div>
<p>{obj.name}</p> >
<p> {obj.stars}</p>
</div>
);
}}
</div>
</div>
</div>
</div>
);
}
};
export default App;
A good start would be to fetch your data in the componentDidMount either with fetch or axios. Never used axios, so I am going to answer the question with fetch
Leave the constructor as it is. Then write a componentDidMount like so:
componentDidMount() {
fetch('https://www.hikingproject.com/data/get-trails?lat=40.0274&lon=-105.2519&maxDistance=10&key=200279581-dd891420fa2c470dbb683b34e017062a')
.then(res => res.json())
.then(data => this.setState({ trails: data.trails }))
.catch(e => console.log(e))
}
then in a sub-render method, such as renderData, write the following code:
renderData() {
if (!this.state.trails) {
return null;
}
return this.state.trails.map(trail => <p>{trail.name}</p>);
}
Then call {this.renderData()} in your render
render() {
return (
<div>{this.renderData()}</div>
)
}
This code has been tested on my local environment and it was working as it should.

Trying to manipulate a div with reactjs on async data

I try to animate a div with reactjs using async data via redux and it's not clear to me when can I have a reference to the virtual dom on state loaded.
In my case I have a div with id header where I would like to push down the container when data was populated.
If I try in componentDidMount than I get Cannot read property 'style' of undefined because componentDidMount still having a reference to an on load container
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
sliderLength: null
}
}
componentDidMount() {
this.props.actions.getSlides()
if(this.header) {
setTimeout(function() {
this.header.style.bottom = -(this.header.clientHeight - 40) + 'px';
}, 2000);
}
//header.style.bottom = -pushBottom+'px';
}
componentWillReceiveProps(nextProps) {
let {loaded} = nextProps
if(loaded === true ) {
this.animateHeader()
}
}
animateHeader() {
}
componentWillMount() {
const {slides} = this.props;
this.setState({
sliderLength: slides.length,
slides: slides
});
}
render() {
const {slides, post, loaded} = this.props;
if(loaded ===true ) {
let sliderTeaser = _.map(slides, function (slide) {
if(slide.status === 'publish') {
return <Link key={slide.id} to={'portfolio/' + slide.slug}><img key={slide.id} className="Img__Teaser" src={slide.featured_image_url.full} /></Link>
}
});
let about = _.map(post, function (data) {
return data.content.rendered;
})
return (
<div className="homePage">
<Slider columns={1} autoplay={true} post={post} slides={slides} />
<div id="header" ref={ (header) => this.header = header}>
<div className="title">Title</div>
<div className="text-content">
<div dangerouslySetInnerHTML={createMarkup(about)}/>
</div>
<div className="sliderTeaser">
{sliderTeaser}
</div>
<div className="columns">
<div className="column"></div>
<div className="column"></div>
<div className="column"></div>
</div>
</div>
<div id="bgHover"></div>
</div>
);
} else {
return <div>...Loading</div>
}
}
}
function mapStateToProps(state) {
return {
slides: state.slides,
post: state.post,
loaded: state.loaded
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(slidesActions, dispatch)
};
}
function createMarkup(markup) {
return {__html: markup};
}
export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
How do I deal in this case with states?
Between I found a solution but not sure if is the right workaround
componentDidUpdate() {
if(this.header) {
setTimeout(function() {
this.header.style.bottom = -(this.header.clientHeight - 35) + 'px';
}, 2000);
}
}
In general, try to avoid using ref as much as possible. This is particularly difficult if you're new to React but with some training, you'll find yourself not needing it.
The problem with modifying the styles like you're doing is that when the component will render again your changes will be overwritten.
I would create a new state property, say state.isHeaderOpen. In your render method you will render the header differently depending on the value of this header e.g.:
render () {
const {isHeaderOpen} = this.state
return (
<header style={{bottom: isHeaderOpen ? 0 : 'calc(100% - 40px)'}}>
)
}
Here I'm using calc with percentage values to get the full height of the header.
Next, in your componentDidMount simply update the state:
componentDidMount () {
setTimeout(() => this.setState({isHeaderOpen: false}), 2000);
}
In this way, the component will render again but with the updated style.
Another way is to check if the data has been loaded instead of creating a new state value. For example, say you're loading a list of users, in render you would write const isHeaderOpen = this.state.users != null.
If you are trying to animate a div why are you trying to access it by this.header just use the javaScript's plain old document.getElementById('header') and then you can play around with the div.

Resources