Each child in a list should have a unique "key" prop Error [duplicate] - javascript

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>
)
};

Related

Commerce JS, generateToken returning "Material-UI: A component is changing the controlled value state of Select to be uncontrolled."

This is where i generate the token
import React, { useState, useEffect } from 'react';
import { Paper, Stepper, Step, StepLabel, Typography, CircularProgress, Divider, Button } from '#material-ui/core';
import { commerce } from '../../../lib/commerce';
import useStyles from './styles';
import AddressForm from '../AddressForm';
import PaymentForm from '../PaymentForm';
const steps = ['Shipping address', 'Payment details'];
const Checkout = ({ cart }) => {
const [activeStep, setActiveStep] = useState(0);
const [checkoutToken, setCheckoutToken] = useState(null);
const classes = useStyles();
useEffect(() => {
if (cart.id) {
const generateToken = async () => {
try {
const token = await commerce.checkout.generateToken(cart.id, { type: 'cart' });
setCheckoutToken(token)
} catch (error){
console.log(error);
}
};
generateToken();
}
}, [cart]);
const Confirmation = () => (
<div>
Confirmation
</div>
)
const Form = () => activeStep === 0
? <AddressForm checkoutToken={checkoutToken} />
: <PaymentForm />
return (
<>
<div className={classes.toolbar} />
<main className={classes.layout} >
<Paper className={classes.paper}>
<Typography variant='h4' align='center'>Checkout</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((step) => (
<Step key={step}>
<StepLabel>{step}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? <Confirmation /> : checkoutToken && <Form />}
</Paper>
</main>
</>
)
}
export default Checkout
Here is my App.js
import React, { useState, useEffect, Fragment } from 'react'
import { commerce } from './lib/commerce';
import { Products, Navbar, Cart, Checkout } from './components';
import { BrowserRouter as Router, Routes, Route} from 'react-router-dom';
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
}
const fetchCart = async () => {
setCart(await commerce.cart.retrieve())
}
const handleAddToCart = async ( productId, quantity) =>{
const { cart } = await commerce.cart.add(productId, quantity);
setCart(cart);
}
const handleUpdateCartQty = async (productId, quantity) => {
const { cart } = await commerce.cart.update(productId, { quantity });
setCart(cart);
}
const handleRemoveFromCart = async (productId) => {
const { cart } = await commerce.cart.remove(productId);
setCart(cart);
}
const handleEmptyCart = async () => {
const { cart } = await commerce.cart.empty();
setCart(cart);
}
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
return (
<Router>
<div>
<Navbar totalItems={cart.total_items} />
<Routes>
<Route exact path='/' element={<Products products={products} onAddToCart={handleAddToCart} />} />
<Route exact path='/cart' element={<Cart cart={cart} handleUpdateCartQty={handleUpdateCartQty} handleAddToCart={handleAddToCart} handleRemoveFromCart={handleRemoveFromCart} handleEmptyCart={handleEmptyCart} />} />
<Route exact path='/checkout' element={ <Checkout cart={cart} />} />
</Routes>
</div>
</Router>
)
}
export default App;
And here is my cart.jsx incase their is anything relevant there
import React from 'react'
import { Container, Typography, Button, Grid} from '#material-ui/core';
import { Link } from 'react-router-dom';
import useStyles from './styles';
import CartItem from './CartItem/CartItem';
const Cart = ({ cart, handleUpdateCartQty, handleRemoveFromCart, handleEmptyCart }) => {
const classes = useStyles();
const EmptyCart = () => (
<Typography variant='subtitle1'>
You have no items in your shopping cart.
<Link to='/' className={classes.link}>Add Items!</Link>
</Typography>
);
const FilledCart = () => (
<>
<Grid container spacing={3}>
{ cart.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<CartItem item={item} onUpdateCartQty={handleUpdateCartQty} onRemoveFromCart={handleRemoveFromCart} />
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant='h4'>
Subtotal: {cart.subtotal.formatted_with_symbol}
<div>
<Button className={classes.emptyButton} size='large' type='button' variant='contained' color='secondary' onClick={handleEmptyCart}>
Empty Cart
</Button>
<Button component={Link} to='/checkout' className={classes.checkoutButton} size='large' type='button' variant='contained' color='primary'>
Checkout
</Button>
</div>
</Typography>
</div>
</>
);
// Wait for cart to load items
if(!cart.line_items){
return '...loading';
}
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} varaint='h3' gutterBottom >Your Shopping Cart</Typography>
{ !cart.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container>
)
}
export default Cart
[error messages][1]
[1]: https://i.stack.imgur.com/vlard.png
Warning: Expected onSubmit listener to be a function, instead got a
value of string type. form
FormProvider#http://localhost:3000/static/js/bundle.js:76722:7
AddressForm#http://localhost:3000/static/js/bundle.js:1096:7 Form div
Paper#http://localhost:3000/static/js/bundle.js:12332:17
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 main
Checkout#http://localhost:3000/static/js/bundle.js:1332:7
Routes#http://localhost:3000/static/js/bundle.js:67209:7 div
Router#http://localhost:3000/static/js/bundle.js:67146:7
BrowserRouter#http://localhost:3000/static/js/bundle.js:65952:7
App#http://localhost:3000/static/js/bundle.js:347:82
Warning: A component is changing a controlled input to be
uncontrolled. This is likely caused by the value changing from a
defined to undefined, which should not happen. Decide between using a
controlled or uncontrolled input element for the lifetime of the
component. More info: https://reactjs.org/link/controlled-components
input SelectInput#http://localhost:3000/static/js/bundle.js:13482:19
div InputBase#http://localhost:3000/static/js/bundle.js:8257:25
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25
Input#http://localhost:3000/static/js/bundle.js:9146:26
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25
Select#http://localhost:3000/static/js/bundle.js:13182:26
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 div
Grid#http://localhost:3000/static/js/bundle.js:7352:29
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 div
Grid#http://localhost:3000/static/js/bundle.js:7352:29
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 form
FormProvider#http://localhost:3000/static/js/bundle.js:76722:7
AddressForm#http://localhost:3000/static/js/bundle.js:1096:7 Form div
Paper#http://localhost:3000/static/js/bundle.js:12332:17
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 main
Checkout#http://localhost:3000/static/js/bundle.js:1332:7
Routes#http://localhost:3000/static/js/bundle.js:67209:7

How do i render multiple props in a single component and then pass those props to a child component in React

I was trying to render multiple props in a single component both the props are from different apis which are working together and the problem is i want to map the props in a single component to display a list of posts. someone said me do this by creating a variable (array) in my component. Then, spreading the properties (props) into the variable e.g myVariable.push(...posts, ...externalPosts). But i can't seem to figure out how do i achieve the results the Component renders another child component called to which i want to pass on the props.
App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Posts } from './components';
const App = () => {
const [ posts, setPosts ] = useState([]);
const [ postsExternal, setPostsExternal ] = useState([]);
const fetchPostsAll = () => {
axios.get(`http://localhost:2000/posts`).then(({ data }) => {
let externalPosts = [];
setPosts(data);
console.log(data);
data.map(({ external_id }) => {
axios
.get(`http://localhost:2000/posts${external_id}`)
.then(({ data }) => {
console.log(data);
externalPosts.push(data);
});
setPostsExternal(externalPosts);
});
});
}
useEffect(() => {
fetchPostsAll();
}, []);
return (
<div>
<Navbar/>
<Posts posts={posts} postsExternal={postsExternal} />
</div>
);
};
export default App;
Posts.js
import React from 'react';
import Post from './Post/Post';
const Posts = ({ posts, postsExternal }) => {
return (
<main>
<Container fluid>
<Row className="p-2">
{ posts.map((post) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post post={post} postExternal={postsExternal}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
Post.js
import React from 'react';
const Post = ({ post, postExternal }) => {
return (
<Figure>
<span>{post.title}</span>
<span>{postExternal.rating}</span>
</Figure>
)
}
export default Post;
The problem is with the Posts.js file while mapping i want to map both the props and pass those single item props to Post.js
Do:
import React from 'react';
import Post from './Post/Post';
const Posts = ({ posts, postsExternal }) => {
return (
<main>
<Container fluid>
<Row className="p-2">
{ posts.map((post, index) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post post={post} postExternal={postsExternal[index]}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
Edited:
import React from 'react';
import Post from './Post/Post';
const Posts = ({ posts, postsExternal }) => {
return (
<main>
<Container fluid>
<Row className="p-2">
{posts.length !== 0 && postsExternal.length !== 0 && posts.map((post, index) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post post={post} postExternal={postsExternal[index]}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
Make sure your data is loaded.
fetchPostsAll function:
const fetchPostsAll = () => {
axios.get(`http://localhost:2000/posts`)
.then(({ data }) => {
console.log(data);
const externalPosts = data.map(({ external_id }) => {
return axios
.get(`http://localhost:2000/posts${external_id}`)
.then(({ data }) => {
return data;
});
})
Promise
.all(externalPosts)
.then(externalPosts => {
setPosts(data);
setExternalPosts(externalPosts);
});
})
;
}
You just don't desctructure the props and pass it as is.
import React from 'react';
import Post from './Post/Post';
const Posts = (props) => {
const { posts } = props;
return (
<main>
<Container fluid>
<Row className="p-2">
{ posts.map((post) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post {...props} post={post}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
And you can do this in your App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Posts } from './components';
const App = () => {
const [ posts, setPosts ] = useState([]);
const [ postsExternal, setPostsExternal ] = useState([]);
const fetchPostsAll = () => {
axios.get(`http://localhost:2000/posts`).then(({ data }) => {
let externalPosts = [];
setPosts(data);
console.log(data);
data.map(({ external_id }) => {
axios
.get(`http://localhost:2000/posts${external_id}`)
.then(({ data }) => {
console.log(data);
externalPosts.push(data);
});
setPostsExternal(externalPosts);
});
});
}
useEffect(() => {
fetchPostsAll();
}, []);
const props = { posts, postsExternal };
return (
<div>
<Navbar/>
<Posts {...props} />
</div>
);
};
export default App;

"Warning: Encountered two children with the same key" for infinite scroll

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!

Add items to Cart using React Context

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

Passing props to modal passes every object

I'm not 100% sure what's going on here. I've got a display component that displays a bunch of cards, using a map based on my database - On the card is an edit button that pops a modal up, passing props over to the edit form.. Here's kinda how it looks:
import React, { useState } from 'react'
import { useQuery, useMutation } from '#apollo/client'
import { GET_ALL_PROJECTS, REMOVE_PROJECT } from '../helpers/queries'
import { makeStyles } from '#material-ui/core/styles'
import DeleteIcon from '#material-ui/icons/Delete'
import EditIcon from '#material-ui/icons/Edit'
import AddForm from './AddForm'
import EditForm from './EditForm'
import AlertMessage from '../Alerts/AlertMessage'
import { Grid, Typography, Card, CardActionArea, CardActions, CardContent, CardMedia, Button, Modal, Backdrop, Fade } from '#material-ui/core'
const useStyles = makeStyles((theme) => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
paper: {
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
const DisplayProjects = () => {
const styles = useStyles()
const [deleteItem] = useMutation(REMOVE_PROJECT)
const { loading, error, data } = useQuery(GET_ALL_PROJECTS)
const [status, setStatusBase] = useState('')
const [resultMessage, setResultMessage] = useState('')
const [addOpen, setAddOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const onDelete = (id, e) => {
e.preventDefault()
deleteItem({
variables: { id },
refetchQueries: [{ query: GET_ALL_PROJECTS }]
}).then(
res => handleSuccess(res),
err => handleError(err)
)
}
// Handles Result of the Delete Operation
const handleSuccess = (res) => {
console.log(res.data.deleteProject.proj_name)
// console.log('success!');
setResultMessage(res.data.deleteProject.proj_name)
setStatusBase({
msg: `Successfully Deleted ${resultMessage}`,
key: Math.random()
})
}
const handleError = (err) => {
console.log('error')
}
//Handles the Modal for Add Project
const handleAddOpen = () => {
setAddOpen(true);
};
const handleAddClose = () => {
setAddOpen(false);
};
//Handles the Modal for Edit Project
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
if (loading) return '...Loading'
if (error) return `Error: ${error.message}`
return (
<div>
<div style={{ marginTop: 20, padding: 30 }}>
<Grid container spacing={8} justify='center' alignItems='center'>
{data.projects.map(p => {
return (
<Grid item key={p._id}>
<Card >
<CardActionArea>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CardMedia
style={{ width: 400, height: 100, paddingTop: 10, }}
component='img'
alt='Project Image'
height='140'
image={require('../../images/html-css-javascript-lg.jpg')}
/>
</div>
<CardContent >
<Typography gutterBottom variant='h5' component="h2">
{p.proj_name}
</Typography>
<Typography component='p'>
{p.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button>
<DeleteIcon onClick={e => onDelete(p._id, e)} />
</Button>
<Button onClick={handleEditOpen}>
<Modal
open={editOpen}
onClose={handleEditClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={editOpen}>
<div className={styles.paper}>
<EditForm
id={p._id}
close={handleEditClose}
name={p.proj_name}
desc={p.description}
gh={p.gh_link}
live={p.live_link}
img={p.image_url}
/>
</div>
</Fade>
</Modal>
<EditIcon />
</Button>
</CardActions>
</Card>
{ status ? <AlertMessage key={status.key} message={status.msg} /> : null}
</Grid>
)
}
)}
</Grid>
<Button type='button' onClick={handleAddOpen}>Add Project</Button>
<Modal
open={addOpen}
onClose={handleAddClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={addOpen}>
<div className={styles.paper}>
<AddForm close={handleAddClose} />
</div>
</Fade>
</Modal>
</div>
</div >
)
}
export default DisplayProjects
And here's the form. I've destructured out the props into variables and placed them into a state object called details, so they can be overwritten and submitted to the database..
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import { useMutation, useQuery } from '#apollo/client'
import { EDIT_PROJECT, GET_ALL_PROJECTS, GET_PROJECT_BY_ID} from '../helpers/queries'
const AddForm = (props) => {
const params = useParams()
const id = params.toString()
// console.log(id);
const [editProjectItem] = useMutation(EDIT_PROJECT)
const {loading, data, error} = useQuery(GET_PROJECT_BY_ID, {
variables: {
id
},
})
const [details, setDetails] = useState({})
if (loading) return '...Loading';
if (error) return <p>ERROR: {error.message}</p>;
if (!data) return <p>Not found</p>;
setDetails(data.projectById)
console.log(data.projectById)
const submitForm = e => {
e.preventDefault()
try {
editProjectItem({
variables: { id, proj_name, description, gh_link, live_link, image_url},
refetchQueries: [{query: GET_ALL_PROJECTS}]
})
}
catch (err) {
console.log('You Goofed')
}
// setDetails({
// proj_name: '',
// description: '',
// gh_link: '',
// live_link: '',
// image_url: ''
// })
props.close()
}
const changeDetails = (e) => {
setDetails({
...details,
[e.target.name]: e.target.value
})
}
const {_id, proj_name, description, gh_link, live_link, image_url} = details
return (
<div key = {_id}>
<h2>Edit {proj_name}</h2>
<form onSubmit = {submitForm} >
<label>
Project Name:
<input
name = 'proj_name'
value = {proj_name}
onChange = {changeDetails}
/>
</label>
<label>Description</label>
<input
name = 'description'
value = {description}
onChange = {changeDetails}
/>
<label>GitHub Link</label>
<input
name = 'gh_link'
value = {gh_link}
onChange = {changeDetails}
/>
<label>Live Link</label>
<input
name = 'live_link'
value = {live_link}
onChange = {changeDetails}
/>
<label>Preview Image</label>
<input
name = 'image_url'
value = {image_url}
onChange = {changeDetails}
/>
<button type = 'submit'>Submit</button>
</form>
</div>
)
}
export default AddForm
The problem I'm running into, is that when I access the modal, the props are sent from literally EVERY Object, instead of the one, and displays the data for the last record instead of the one I want to edit You can see what happens here (I logged props.id in order to test) https://imgur.com/a/pcEKl89
What did I miss? (Disclaimer: I am still a student, and learning the craft.. be gentle on my code please)
EDIT: I just realized that I didn't indicate that this is the final form of the EditForm component. I haven't added the logic in to make the updates yet, I just wanted to get the data showing properly first.
EDIT2: I made some changes to how the ID is passed over, I was already using React-Router, so I went ahead and made a route to /edit/:id and then using useParams(), I got the ID that way. It seems to be working, however now I'm getting a Too many re-renders message. Updated the AddForm code above to reflect the changes..
I figured out the re-render issue.. it was as simple as dropping the setDetails function into a useEffect Hook:
useEffect(()=> {
if(data){
setDetails(data.projectById)
}
},[data])

Categories

Resources