React Encountered two children with the same key - reactjs

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.

Related

How to add page number to the URL

Could someone please tell me how can I add page number to my url. The component is as follows:
/** NPM Packages */
import React, { Component } from "react";
import { connect } from "react-redux";
import { Spinner, Pagination } from "react-bootstrap";
//import styles from "./App.module.css";
/** Custom Packages */
import List from "../List";
//import fetchCategories from "../../../actions/configuration/category/fetchCategories";
import deleteCategory from "../../../actions/configuration/category/deleteCategory";
import API from "../../../../app/pages/utils/api";
class Category extends Component {
constructor(props) {
super(props);
this.state = {
mesg: "",
mesgType: "",
isLoading: true,
total: null,
per_page: null,
current_page: 1,
pdata: []
};
this.fetchCategoriesAPI = this.fetchCategoriesAPI.bind(this);
}
fetchCategoriesAPI = async pno => {
await API.get("categories?offset=" + (pno.index+1))
.then(res => this.setState({ pdata: res.data }))
.then(() => this.props.passToRedux(this.state.pdata))
.catch(err => console.log(err));
};
componentDidMount = async () => {
const { state } = this.props.location;
if (state && state.mesg) {
this.setState({
mesg: this.props.location.state.mesg,
mesgType: this.props.location.state.mesgType
});
const stateCopy = { ...state };
delete stateCopy.mesg;
this.props.history.replace({ state: stateCopy });
}
this.closeMesg();
await this.fetchCategoriesAPI(1);
this.setState({ isLoading: false });
};
onDelete = async id => {
this.props.removeCategory(id);
await deleteCategory(id).then(data =>
this.setState({ mesg: data.msg, mesgType: "success" })
);
this.closeMesg();
};
closeMesg = () =>
setTimeout(
function() {
this.setState({ mesg: "", mesgType: "" });
}.bind(this),
10000
);
/** Rendering the Template */
render() {
let activePage = this.state.pdata.currPage;
let items = [];
let totalPages = Math.ceil(this.state.pdata.totalCount / 10);
for (let number = 1; number <= totalPages; number++) {
items.push(
<Pagination.Item key={number} active={number == activePage}>
{number}
</Pagination.Item>
);
}
const paginationBasic = (
<div>
<Pagination>
{items.map((item,index)=>{
return <p key={index} onClick={() => this.fetchCategoriesAPI({index})}>{item}</p>
})}
</Pagination>
<br />
</div>
);
const { mesg, mesgType, isLoading } = this.state;
return (
<>
{mesg ? (
<div
className={"alert alert-" + mesgType + " text-white mb-3"}
role="alert"
>
{mesg}
</div>
) : (
""
)}
{isLoading ? (
<div className="container-fluid">
<h4
className="panel-body"
style={{ "text-align": "center", margin: "auto" }}
>
Loading
<Spinner animation="border" role="status" />
</h4>
</div>
) : (
<div>
<List
listData={this.props.categories}
listName="category"
_handleDelete={this.onDelete.bind(this)}
/>
{paginationBasic}
</div>
)}
</>
);
}
}
const matchStatestoProps = state => {
return { categories: state.categories };
};
const dispatchStatestoProps = dispatch => {
return {
passToRedux: pload =>
dispatch({ type: "FETCH_CATEGORIES", payload: pload }),
removeCategory: id => dispatch({ type: "DELETE_CATEGORY", payload: id })
};
};
export default connect(matchStatestoProps, dispatchStatestoProps)(Category);
the route is as follows:
<Route exact path="/categories/:page?" component={Category} />
So basically I want the page number to be displayed in the URL. Also if I change the page number, the data should load the corresponding page. Please help me
Could someone please help me out?
In a class component:
Your router will pass match in as a prop. When your component mounts, get this.props.match.params.page and load the data accordingly:
class MyComponent extends React.Component {
componentDidMount () {
// get the 'page' param out of the router props.
// default to 0 if not specified.
const { page = 0 } = this.props.match.params;
// it comes in as a string, parse to int
const p = parseInt(page, 10);
// do whatever you need to do (load data, etc.)
}
}
In a function component:
In a function component, you can get the page param via react-router's useParams hook:
import { useParams } from 'react-router-dom';
function MyComponent () {
const { page } = useParams(); // get the 'page' router param
const p = parseInt(page, 10); // comes in as a string, convert to int
// do whatever you need to do with it
}
If you need prev/next navigation you can deduce those page numbers from the current page.
I made this quick example that demonstrates how to access and use the route's url parameters via react router's useParams hook and how to do it via the match prop with a class component.
You can get page number from props like this:
const matchStatestoProps = (state, ownProps) => {
return { id: ownProps.match.params.id; categories: state.categories };
};
In your routes:
<Route path="/page/:id" component={Page} />

React render array of images in array

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]}/>

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 fix recursively updating state?

I am bulding an app using newsapi. i am facing two issue on my state. i fetch data using api and assign it to my state. and use it in my view.
Issue no 1
My view gets rendered before my app receives the data.
Issue no 2
When I try to update my state after a new fetch. it recursively updates the set of data again and again.
import React, {Component} from 'react';
import NewsComponent from './NewsComponent/NewsComponent'
class News extends Component {
state = {
displayStatus: false,
newsItems: []
};
toogleDisplayHandler = () => {
if(this.state.displayStatus===true){
this.setState({displayStatus:false})
}
else{
this.setState({displayStatus:true})
}
}
render(){
const NewsAPI = require('newsapi');
const newsapi = new NewsAPI('d6da863f882e4a1a89c5152bd3692fb6');
//console.log(this.props.keyword);
newsapi.v2.topHeadlines({
sources: 'bbc-news,abc-news',
q: this.props.keyword
}).then(response => {
//console.log(response)
response.articles.map(article => {
//console.log(article);
return(
//console.log(this.state.newsItems)
this.setState({
newsItems: [...this.state.newsItems, article],
})
//this.state.newsItems.push(article)
)
});
});
let Article = null;
Article = (
<div>
{
this.state.newsItems.map((news, index) => {
return (
<NewsComponent key={index}
title={news.title}
url={news.url}
description={news.description}
author={news.author}
publish={news.publishedAt}
image={news.urlToImage}
/>
)
})
}
</div>
)
return (
<div className="App">
{Article}
<button onClick={this.toogleDisplayHandler}>
{this.state.displayStatus === true ? "Hide Article" : "Display Articles"}
</button>
</div>
)
}
}
export default News;
Please help me to resolve this issue.
You should never setState in render as that would cause an infinite loop. Do it in componentDidMount or the constructor.
I would also recommend not using map for simply iterating over a list. Array.map is a function that is useful for returning an array that is constructed by iterating over another array. If you want to run some code for each element of an array use Array.forEach instead.
Like this:
import React, { Component } from "react";
import NewsComponent from "./NewsComponent/NewsComponent";
class News extends Component {
state = {
displayStatus: false,
newsItems: []
};
toogleDisplayHandler = () => {
if (this.state.displayStatus === true) {
this.setState({ displayStatus: false });
} else {
this.setState({ displayStatus: true });
}
};
componentDidMount = () => {
const NewsAPI = require("newsapi");
const newsapi = new NewsAPI("d6da863f882e4a1a89c5152bd3692fb6");
newsapi.v2
.topHeadlines({
sources: "bbc-news,abc-news",
q: this.props.keyword
})
.then(response => {
response.articles.forEach(article => {
this.setState({
newsItems: [...this.state.newsItems, article]
});
});
});
};
render() {
let Article = null;
Article = (
<div>
{this.state.newsItems.map((news, index) => {
return (
<NewsComponent
key={index}
title={news.title}
url={news.url}
description={news.description}
author={news.author}
publish={news.publishedAt}
image={news.urlToImage}
/>
);
})}
</div>
);
return (
<div className="App">
{Article}
<button onClick={this.toogleDisplayHandler}>
{this.state.displayStatus === true
? "Hide Article"
: "Display Articles"}
</button>
</div>
);
}
}
export default News;
1) You can add a check either your state has the data which you want to show on screen to render the view.
2) Please use ComponentDidMount React life cycle function to fetch data from an external source and update this data in the state. In the Render method, it will keep calling it recursively.

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.

Resources