I've implemented infinite scroll in React app, however, I get Warning: Encountered two children with the same key <...>. Keys should be unique so that components maintain their identity across updates. <...>.
If I replace key={movie.id} by key={i}, infinite scroll stops working.
This is the code of my component:
import{ useContext, useRef, useEffect } from "react";
import { Card, Grid, CardActionArea, CardMedia } from '#material-ui/core';
import FavoriteBorderIcon from '#material-ui/icons/FavoriteBorder';
import { MoviesContext } from "../../services/context";
import { Movie } from "../../services/movies.service";
import '../../App.scss';
import './Catalog.scss';
import noImage from '../../images/no-image-available.png';
import loadingSpinner from '../../images/loading-spinner.gif';
import { NavLink } from 'react-router-dom';
import useIntersectionObserver from '../../services/useIntersectionObserver';
import { fetchMovies } from "../../services/movies.service";
const posterBaseUrl = "https://image.tmdb.org/t/p/w300";
const CatalogCards = () => {
const { movies, updateMovies, searchedMovie, moviesPage, setMoviesPage, setSelectedMovie, setIsMoviePageFirstTimeOpened } = useContext(MoviesContext);
const loadingRef = useRef<HTMLDivElement | null>(null);
const entry = useIntersectionObserver(loadingRef, {})
const isVisible = !!entry?.isIntersecting;
const SetSelectedMovieId = (id: number) => {
setIsMoviePageFirstTimeOpened(true);
setSelectedMovie(id);
}
useEffect (
() => {
if ( isVisible ) {
setMoviesPage(moviesPage+1);
fetchMovies(String(moviesPage))
.then(nextPage => {
updateMovies((movies: Movie[]) => movies.concat(nextPage));
})
.catch(() => updateMovies([]))
}
},
[isVisible]
);
return (
<div >
<Grid container spacing={1} className="container-content">
{
movies.length > 0
?
movies.map((movie, i) => (
<Grid item key={movie.id}>
<NavLink to={'/movie/' + movie.id}>
<Card className="card-list" onClick={() => SetSelectedMovieId(movie.id)} >
<CardActionArea>
<CardMedia
component="img"
alt={"Poster of " + movie.title}
image={movie.poster_path ? posterBaseUrl + movie.poster_path : noImage}
title={movie.title}
/>
</CardActionArea>
</Card>
</NavLink>
</Grid>
))
:
searchedMovie ?
<div className="">Try a different phrase...</div>
:
<CardMedia
component="img"
image={loadingSpinner}
className="loading-spinner"
/>
}
</Grid>
<div ref={loadingRef}>...</div>
</div>
);
}
export default CatalogCards;
fetchMovies() method fetches info about movies from an API.
useIntersectionObserver is a custom hook that helps to check if ref={loadingRef} appears in the screen and more movies should be fetched.
How can I solve it?
Thanks!
Related
This question already has answers here:
Understanding unique keys for array children in React.js
(27 answers)
Closed 4 months ago.
Currently I am working on a project using React, Next.js and Ant-Design.
While working on my project, I got an error because I didn't use a unique key like this:
Below is the detailed error log.
Warning: Each child in a list should have a unique "key" prop.
Check the render method of `ForwardRef`. It was passed a child from BoardCard. See https://reactjs.org/link/warning-keys for more information.
at eval (webpack-internal:///./node_modules/antd/es/popover/index.js:30:31)
at eval (webpack-internal:///./node_modules/antd/es/card/Card.js:58:62)
at article
at BoardCard (webpack-internal:///./components/MyPage/BoardCard.js:34:19)
at li
at InternalItem (webpack-internal:///./node_modules/antd/es/list/Item.js:65:31)
at ul
at div
at div
at Spin (webpack-internal:///./node_modules/antd/es/spin/index.js:94:90)
at SpinFC (webpack-internal:///./node_modules/antd/es/spin/index.js:222:34)
at div
at List (webpack-internal:///./node_modules/antd/es/list/index.js:58:26)
at section
at BoardList (webpack-internal:///./components/MyPage/BoardList.js:22:78)
at section
at MyBoard
at section
at MyInfo
at header
at O (webpack-internal:///./node_modules/styled-components/dist/styled-components.browser.esm.js:31:19811)
at AppLayout (webpack-internal:///./components/AppLayout/index.js:28:23)
at profile
at App (webpack-internal:///./pages/_app.js:28:24)
at Provider (webpack-internal:///./node_modules/react-redux/es/components/Provider.js:13:3)
at Wrapper (webpack-internal:///./node_modules/next-redux-wrapper/es6/index.js:184:35)
at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:20746)
at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:23395)
at Container (webpack-internal:///./node_modules/next/dist/client/index.js:241:5)
at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:833:24)
at Root (webpack-internal:///./node_modules/next/dist/client/index.js:986:26)
As a result of checking the error log, it was confirmed that the error occurred in the Boardlist and BoardCard components.
So I checked the key of the relevant component, but I am using a unique key.
import React from 'react';
import { useSelector } from 'react-redux';
import { List } from 'antd';
import BoardCard from './BoardCard';
const BoardList = () => {
const { boardPosts } = useSelector((state) => state.user);
return (
<section>
<List
itemLayout="vertical"
bordered
size="large"
pagination={{
onChange: (page) => console.log(page), pageSize: 3,
}}
dataSource={boardPosts}
renderItem={(item) => (
<List.Item>
<BoardCard post={item} key={item.id}/>
</List.Item>
)}
/>
</section>
)
};
export default BoardList;
import React, { useState, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Card, Modal, Button, Popover, Row, Col, message } from 'antd';
import PropTypes from 'prop-types';
import PostModal from '../Modal/PostModal';
import PostDeleteModal from '../HomePost/PostDeleteModal';
import {
likePostRequestAction, unLikePostRequestAction,
moveToCommentRequestAction, returnToCommentRequestAction
} from '../../reducers/post';
import {
BoardCardHeader, BoardCardBody, MoreIcon, ColWrapper, CardTextWrapper,
CardTitle, CardText, CardContent, HeaderBtn, TwoToneHeartBtnIcon,
HeartBtnIcon, CommentBtnIcon, CardImageWrapper, ImageWrapper
} from './styles';
const BoardCard = ({ post }) => {
const dispatch = useDispatch();
const [visible, setVisible] = useState(false);
const id = useSelector((state) => state.user.me.id);
const liked = post.Likers.find((v) => v.id === id);
const onLikePost = useCallback(() => {
dispatch(likePostRequestAction(post.id));
}, []);
const unLikePost = useCallback(() => {
dispatch(unLikePostRequestAction(post.id));
}, []);
const showPostModal = useCallback(() => {
setVisible(true);
}, []);
const boardCardCommentBtn = useCallback(() => {
setVisible(true);
dispatch(moveToCommentRequestAction());
}, []);
const boardmodalOkBtn = useCallback(() => {
setVisible(false);
dispatch(returnToCommentRequestAction());
}, []);
const boardmodalCancelBtn = useCallback(() => {
setVisible(false);
dispatch(returnToCommentRequestAction());
}, []);
return (
<article>
<Card
headStyle={BoardCardHeader}
bodyStyle={BoardCardBody}
hoverable
extra={[
<Popover
trigger="click"
content={
<>
<Button>Modify</Button>
<PostDeleteModal post={post} />
</>
}
>
<MoreIcon key="ellipsis"/>
</Popover>
]}
>
<Row>
<ColWrapper xs={24} md={16}>
<CardTextWrapper>
<CardText
title={<CardTitle>{post.title}</CardTitle>}
description={post.desc}
onClick={showPostModal}
/>
<CardContent onClick={showPostModal}>{post.recipes}</CardContent>
</CardTextWrapper>
<div>
{
liked
? <HeaderBtn type='text' icon={<TwoToneHeartBtnIcon twoToneColor="#eb2f96" onClick={unLikePost} />}>{post.Likers.length}</HeaderBtn>
: <HeaderBtn type='text' icon={<HeartBtnIcon />} onClick={onLikePost}>{post.Likers.length}</HeaderBtn>
}
<HeaderBtn type='text' icon={<CommentBtnIcon />} onClick={boardCardCommentBtn} >{post.Comments.length}</HeaderBtn>
</div>
</ColWrapper>
<Col xs={24} md={8}>
<CardImageWrapper>
<ImageWrapper
alt="board image"
src={`http://localhost:3065/${post.Images[0]?.src}`}
onClick={showPostModal}
/>
</CardImageWrapper>
</Col>
</Row>
</Card>
<Modal
centered
visible={visible}
onOk={boardmodalOkBtn}
onCancel={boardmodalCancelBtn}
width={1000}
>
<PostModal post={post} />
</Modal>
</article>
);
};
BoardCard.propTypes = {
post: PropTypes.shape({
id: PropTypes.number,
User: PropTypes.object,
title: PropTypes.string,
desc: PropTypes.string,
content: PropTypes.arrayOf(PropTypes.object),
Images: PropTypes.arrayOf(PropTypes.object),
tag: PropTypes.string,
Comments: PropTypes.arrayOf(PropTypes.object),
})
};
export default BoardCard;
I tried to solve the problem, but couldn't find a way.
So, I would like to know why the above problem occurs and how to solve it.
You must put the key on the first child
const BoardList = () => {
const { boardPosts } = useSelector((state) => state.user);
return (
<section>
<List
itemLayout="vertical"
bordered
size="large"
pagination={{
onChange: (page) => console.log(page), pageSize: 3,
}}
dataSource={boardPosts}
renderItem={(item) => (
<List.Item key={item.id}>
<BoardCard post={item}/>
</List.Item>
)}
/>
</section>
)
};
I have a component that takes an object and passes it to a new screen upon navigating to the screen. However, when I go to the next screen the object passed is undefined. What am I doing wrong here? I have done the exact same thing with another component and it works perfectly fine, but on this component, it isn't passing the parameter properly. Is there something else I need to configure in the navigator?
GoalCard.JS
import * as React from 'react';
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import { useNavigation } from "#react-navigation/core";
import { Card, Chip, Divider, Paragraph, Text, Title } from 'react-native-paper';
const GoalCard = ({ item }) => {
const navigation = useNavigation();
const goals = JSON.parse(item.Goals);
const tasks = JSON.parse(item.GoalTasks);
const [goalsData, setGoalsData] = React.useState(
{
GoalName: item.GoalName,
GoalID: item.GoalID,
GoalDescription: item.GoalDescription,
GoalComplete: item.GoalComplete,
GoalTasks: tasks,
Goals: goals,
UserID: item.UserID,
createdAt: item.createdAt,
updatedAt: item.updatedAt
}
);
return(
<Card className="card">
<Card.Content>
<Title>Goal Set: {item.GoalName}</Title>
<Divider/>
<Paragraph>
<Chip
onPress={() => {
navigation.navigate(
'Goals', {
goalsData: goalsData
});
}}
>
Edit
</Chip>
<Text onPress={() => console.log(goalsData)}>Log</Text>
<Text>Description: {item.GoalDescription}</Text>
<Divider/>
{Object.entries(goals).map(obj => (
<Text key={uuidv4()}>{obj[1].goal}{" "}</Text>
))}
</Paragraph>
</Card.Content>
</Card>
);
}
export default GoalCard;
GoalScreen.js
Pressing "Log" as seen in this file returns undefined
import React from "react";
import { ScrollView, View } from "react-native";
import { Text } from "react-native-paper";
import { MainStyles } from "../../styles/Styles";
const GoalScreen = ({ route }) => {
const { goalData } = route.params;
return (
<ScrollView>
<View style={MainStyles.col}>
<Text onPress={() => console.log(goalData)}>Log</Text>
</View>
</ScrollView>
);
};
export default GoalScreen;
There is a typo ... You are setting
goalsData: goalsData
But you are trying to read as below
const { goalData } = route.params;
try
const { goalsData } = route.params;
I want that the Input field acts as a search bar and shows me the typed cryptocurrencies but because of the initial render of the useEffect, the value of the cryptos is being set to undefined and because of this no crypto is being shown on the page. Please suggest any alternate way to implement this functionality and also How can I stop the useEffect to get render at the starting .
import { Card, Row, Col, Input, Typography } from 'antd'
import React from 'react'
import { Link } from 'react-router-dom'
import { useGetCryptosQuery } from '../services/CryptoApi'
import { useState, useEffect } from 'react'
import millify from 'millify'
import { filter } from 'htmlparser2/node_modules/domutils'
const Cryptocurrencies = ( props ) => {
const count = props.simplified ? 10 : 100;
const { data: cryptoList, isFetching } = useGetCryptosQuery( count )
const [cryptos, setCryptos] = useState( cryptoList?.data?.coins )
const [searchTerm, setSearchTerm] = useState( '' )
useEffect( () => {
const filteredData = cryptoList?.data?.coins.filter( ( coin ) => { coin.name.toLowerCase().includes( searchTerm.toLowerCase() ) } )
setCryptos( filteredData )
}, [searchTerm, cryptoList] )
console.log( cryptos )
if ( isFetching ) {
return "loading...";
}
return (
<div>
{
<>
<div>
<Input placeholder="Search Cryptocurrency" onChange={( e ) => setSearchTerm( e.target.value )} className="" />
</div>
<Row gutter={[32, 32]} className="crypto-card-container">
{cryptos?.map( ( currency ) => {
return (
<Col xs={24} sm={12} lg={6} className="crypto-card" key={currency.id}>
<Link to={`/ crypto / ${currency.id} `}>
<Card
title={`${currency.rank}.${currency.name} `}
extra={<img className="crypto-image" src={currency.iconUrl} />}
hoverable
>
<p>Price : ${millify( currency.price )}</p>
<p>Market Cap : {millify( currency.marketCap )}</p>
<p>Daily Change : {millify( currency.change )}%</p>
</Card>
</Link>
</Col>
)
} )}
</Row>
</>
}
</div>
)
}
export default Cryptocurrencies
Simply can add a conditional if statement to solve the issue.
Let's take a look at the useEffect, As you describe at the component did mount, the searchTerm is still an empty string. By adding a simple if statement to check the seatchTerm value, filteredData and setCryptos methods only works when searchTerm has a string with a minimum of one letter.
useEffect( () => {
if(searchTerm.length > 0) {
const filteredData = cryptoList?.data?.coins.filter((coin) => { coin.name.toLowerCase().includes(searchTerm.toLowerCase())})
setCryptos(filteredData)
}
}, [searchTerm, cryptoList] )
I am new with React, I am trying to do the cart of the app that i am doing and I am having problems sending the items to the cart avoiding to have them duplicate or adding another extra item in the cart. These is the CartProvider code:
import { useState } from "react";
import CartContext from "./CartContext";
export const CartProvider = ({ children }) => {
const [list, setList] = useState();
const addCart = (varietals, quantity) => {
if (isInCart(varietals.item.id ) === -1){
setList(varietals)
}else{
list[isInCart].quantity += quantity
}
};
const isInCart = (id) => {
return list.findIndex(varietals => varietals.id === id)
};
return(
<>
<CartContext.Provider value={{list, addCart}}>
{children}
</CartContext.Provider>
</>);
};
This is the ItemDetail code which i use to display the Item:
import { useContext, useState } from "react";
import { Button, Container } from "react-bootstrap";
import { Link } from "react-router-dom";
import CartContext from "../../Context/CartContext";
import Item from "../Item/Item";
import ItemCountComponent from "../ItemCount";
const ItemDetail = ({ varietals }) => {
const [checkout, setCheckout] = useState(false);
const { list, addCart } = useContext(CartContext);
const onAdd = (count) => {
console.log("Selected ", count);
setCheckout(true);
addCart({Item: varietals, Quantity: count});
};
console.log(list)
return (
<>
<br />
<Container className="py-5 px-5 shadow">
<Item varietals={varietals} />
<div className="ml-4 mr-3">
<div className="font-italic mb-4 text-center text-muted">{varietals.description}</div>
{checkout ? <Link to='/Cart'><Button variant="info">Checkout</Button></Link> : <ItemCountComponent className="d-flex justify-content-center" onAdd={onAdd} stock={5} initial={1} />}
</div>
</Container>
</>
)
};
export default ItemDetail;
This is the Item.jsx:
import { Card, Container } from "react-bootstrap";
import "./Style.scss";
const Item = ({ varietals }) => {
return (
<>
<Container className="px-1">
<Card className="clemmy">
<Card.Img variant="top" src={varietals.pictureUrl} className="shadow"/>
<Card.Body>
<Card.Title className="d-flex justify-content-center text-muted">
{varietals.title}
</Card.Title>
<Card.Subtitle className="d-flex justify-content-center text-muted">
${varietals.price}
</Card.Subtitle>
</Card.Body>
</Card>
</Container>
</>
)
};
export default Item;
This is the ItemListContainer.jsx:
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import aimara from "../../aimara";
import ItemList from "../../Components/ItemList/ItemList";
const ItemListContainer = () => {
const [varietals, setVarietals] = useState([])
const { categoryId } = useParams();
useEffect(() => {
const myPromise = new Promise((resolve, reject) => {
if (categoryId) {
const products = aimara.filter((producto) => {
return producto.category.toString() === categoryId;
});
resolve(products);
} else resolve(aimara);
});
myPromise.then((result) => setVarietals(result));
}, [categoryId]);
return <ItemList varietals={varietals} />
};
export default ItemListContainer;
The error that I am seeing is "TypeError: Cannot read property 'id' of undefined"
If there is someone that could help me would be much appreciate it. Also, in case you need some extra file let me know and I update the post as quickly as I can. Cheers
I'm trying to use the useContext hook in my react app to manage the cart state but I don't know why am I getting this error again and again that the function that I'm trying to destructure is undefined.
I'm new to using context API please help me with the same. I saw other answers related to context API and tried everything like exporting the CartContext as default and then importing it without destructoring but nothing worked. I'm neither able to import the functions nor the data.
Here is my CartContext.js file
import React, { useState, createContext, useReducer } from "react";
import CartReducer from "./CartReducer";
//Initialized Context
export const CartContext = createContext();
export const CartProvider = ({ children }) => {
const [products, setProducts] = useState([]);
const [state, dispatch] = useReducer(CartReducer, products);
function addProductWithQuantity(product) {
dispatch({
type: "ADD_PRODUCT",
payload: {
product,
quantity: 1,
},
});
}
const deleteProductWithId = (id) => {
dispatch({
type: "DELETE_PRODUCT",
payload: id,
});
};
return (
<CartContext.Provider
value={{
state,
addProductWithQuantity,
deleteProductWithId,
}}
>
{children}
</CartContext.Provider>
);
};
And here is the file where I'm using the useContext.
import React, { useContext } from "react";
import {
Card,
Typography,
Tooltip,
IconButton,
Button,
} from "#material-ui/core";
import { Link } from "react-router-dom";
import styles from "./Product.module.css";
import amazon from "../../icons/amazon-brands.png";
import flipkart from "../../icons/flipkart.png";
import { SnackbarProvider, useSnackbar } from "notistack";
import classNames from "classnames";
import { CartContext } from "../../Context/CartContext";
const Product = ({ product }) => {
return (
<SnackbarProvider maxSnack={3} preventDuplicate>
<ProductCard product={product} />
</SnackbarProvider>
);
};
export default Product;
const ProductCard = ({ product }) => {
const {
id,
imageUrls,
storeName,
category,
price,
amazonLink,
flipkartLink,
title,
} = product;
const iconColor = "var(--primaryColor)";
const { addProductWithQuantity } = useContext(CartContext); //Throws an error always
console.log(addProductWithQuantity);
const gotoURL = (location) => {
let a = document.createElement("a");
a.target = "_blank";
a.href = location;
a.click();
};
const handleClickVariant = (variant, title, id) => () => {
enqueueSnackbar(`Successfully added ${title} to cart`, {
variant,
});
onClickHeart(id);
// saveToLocal();
addProductWithQuantity(product);
};
const saveToLocal = () => {
let oldCart = localStorage.getItem("cart");
let newCart = oldCart ? JSON.parse(oldCart) : [];
newCart.push(product);
localStorage.setItem("cart", JSON.stringify(newCart));
};
const onClickHeart = (id) => {
document
.getElementById(id)
.style.setProperty("color", iconColor, "important");
};
const { enqueueSnackbar } = useSnackbar();
return (
<Card key={id} className={styles.card}>
<Link to={`/products/${id.trim()}`} className={styles.noDecoration}>
<div className={styles.thumbnail}>
<img src={imageUrls[0]} alt={title} />
</div>
</Link>
<div className={styles.name}>
<Typography className={styles.category} variant="subtitle2">
{category}
</Typography>
<Link to={`/products/${id.trim()}`} className={styles.noDecoration}>
<Typography className={styles.company} variant="subtitle1">
{title}
</Typography>
</Link>
</div>
<div className={styles.priceLike}>
<h4>₹{price}</h4>
<Tooltip title="Add to favourites" placement="top">
<IconButton
onClick={handleClickVariant("success", title, id)}
className={styles.likeIcon}
>
<i id={id} className={classNames("fas fa-cart-plus")} />
</IconButton>
</Tooltip>
</div>
<div className={styles.buttons}>
<Button
variant="contained"
color="primary"
className={styles.amazonBtn}
onClick={() => gotoURL(amazonLink)}
>
<img src={amazon} alt="amazon-link" /> amazon.in
</Button>
<Button
variant="contained"
color="primary"
className={styles.flipkartBtn}
onClick={() => gotoURL(flipkartLink)}
>
<img src={flipkart} alt="flipkart-link" />
</Button>
</div>
</Card>
);
};
}
The problem was I forgot to wrap my routes with CartContext Provider.
Here is what I did,
import CartProvider from '../CartContext';
return(
<CartProvider>
... Routes
<CartProvider>
)