react fires events for all items and the item selected - reactjs

I'm trying to fire the onClick event for the item that i clicked on. Not for all items.
<Button onClick = {this.writeComment} varient="outlined" component="span" color="primary">
{!this.state.isComment ? "Write Comment": "Close"}
</Button>
{this.state.isComment ? <Comment onSubmit={this.commentSubmit} commentBody={this.state.comment_body} commentChange={this.handleCommentChange}/> : null}
writeComment function
writeComment = (e) => {
e.preventDefault();
this.setState({
isComment:!this.state.isComment
})
}
this should render a comment form for that item only, not for all items. This is within a map loop.
{this.state.images.length > 0 ? (
this.state.images.map( (img, i) => (
<Grid item sm={12} md={12} key={i} style={{ margin: '30px 0px'}}>
<Paper style={{padding:'20px 20px'}}>
{/* // empty image_title */}
<Typography style={{ padding: '30px 5px', letterSpacing:'8px', textTransform:'uppercase'}} variant="h4" align="center">{img.image_title}</Typography>
<Divider style={{ width: '150px', margin:'10px auto', backgroundColor:'#000000'}} variant="middle" />
<Image image_url={img.img_url} />
<Typography variant="h6" align="center">{img.user.username}</Typography>
<Typography variant="h6" align="center">{moment(img.created_at).calendar()}</Typography>
<Button onClick = {this.writeComment} varient="outlined" component="span" color="primary">
{!this.state.isComment ? "Write Comment": "Close"}
</Button>
{this.state.isComment ? <Comment onSubmit={this.commentSubmit} commentBody={this.state.comment_body} commentChange={this.handleCommentChange}/> : null}
<Button onClick={() => this.deleteImg(img.id)} variant="outlined" component="span" color="primary">
Delete
</Button>
</Paper>
</Grid>
))
) : (
<div>
<Grid item md={8}>
<Typography>No Images yet</Typography>
</Grid>
</div>
)}
</Grid>
{/* Images */}
</Grid>
Full code
import React, { Component } from "react";
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
import ImageUploader from 'react-images-upload';
import Divider from '#material-ui/core/Divider';
import Axios from '../Axios';
import Image from './Image';
import moment from 'moment';
import Comment from './Comment';
class Dashboard extends Component{
constructor(props){
super(props);
this.state = {
image_url: 'http://www.conservewildlifenj.org/images/artmax_1001.jpg',
images: [],
description:'',
upload:false,
isComment:false,
comment_body:''
}
}
handleUpload = file => {
const data = new FormData()
const image = file[0]
// console.log(this.state.description)
// data.append('ourImage', this.state.description)
data.append('ourImage',image, this.state.description )
Axios.post('/images/upload', data).then((response) => {
const newImage = {...response.data}
console.log(newImage);
//update component-state
this.setState({
description:'', // resets title after upload
images: [
{
id: newImage[0].id,
user:{
username: newImage[0].user.username
},
image_title: newImage[0].image_title,
img_url: newImage[0].img_url,
created_at: new Date().toLocaleString().replace(',', ''),
updated_at: new Date().toLocaleString().replace(',', '')
},
...this.state.images
]
})
});
}
handleChange = (e) => {
// e.preventDefault();
this.setState({
[e.target.name]: e.target.value
})
// console.log(this.state.description)
}
handleCommentChange = (e) => {
this.setState({
comment_body: e.target.value
})
}
componentWillMount(){
Axios.get('/images/uploads').then( (response) => {
// let img;
// let imgTitle;
Object.keys(response.data).forEach( (key) => {
console.log(response.data[key]);
this.setState({
images:[ ...this.state.images, response.data[key]]
})
console.log(this.state.images);
});
})
}
componentDidUpdate(prevProps, prevState) {
if (this.state.images.length !== prevState.images.length) {
console.log(this.state.images);
}
// debugger;
}
onUploadClick = (e) => {
e.preventDefault();
this.setState({
upload: !this.state.upload
})
}
deleteImg = (id) => {
Axios.post(`/images/delete/${id}`).then( () => {
this.setState({
images: [ ...this.state.images.filter(img => img.id !== id)]
})
})
}
writeComment = (e) => {
e.preventDefault();
this.setState({
isComment:!this.state.isComment
})
}
commentSubmit = (e) => {
e.preventDefault();
console.log(this.state.comment_body);
// Axios.post('/images/newComment', this.state.comment_body).then( (response )=> {
// const newComment = { ...response.data};
// console.log(newComment);
// this.setState({
// comment_body: ''
// })
// })
}
render(){
const uploader = (
<ImageUploader
withIcon={true}
withPreview={true}
onChange={this.handleUpload}
singleImage={true}
buttonText='Upload an image'
imgExtension={['.jpg', '.gif', '.png', '.gif']}
maxFileSize={5242880}
/>
)
return(
<div>
<Grid container justify="center" spacing={16}>
<Grid item sm={8} md={6} style={{ margin: '40px 0px', padding: '0px 30px'}}>
<Typography align="center" variant="h6">
Welcome to the Dashboard
</Typography>
<Button onClick={this.onUploadClick} variant="outlined" component="span" color="primary">
{/* toggle between Upload or Close
Will be upload by default, else if upload is clicked, close will show.
*/}
{!this.state.upload ? "Upload": "Close"}
</Button>
<br></br>
<br></br>
{this.state.upload ? (
<div>
<TextField
id="outlined-name"
label="Image Title"
name="description"
type="text"
required={true}
fullWidth
style={{ borderRadius: '0px'}}
className=""
value={this.state.description}
onChange={this.handleChange}
margin="normal"
/>
<br></br>
<br></br>
{/* so here what we are saying, if this text field is FILLED show the uploader component
else hide it.
*/}
{this.state.description ? uploader : null}
</div>
):(
null
)}
{this.state.images.length > 0 ? (
this.state.images.map( (img, i) => (
<Grid item sm={12} md={12} key={i} style={{ margin: '30px 0px'}}>
<Paper style={{padding:'20px 20px'}}>
{/* // empty image_title */}
<Typography style={{ padding: '30px 5px', letterSpacing:'8px', textTransform:'uppercase'}} variant="h4" align="center">{img.image_title}</Typography>
<Divider style={{ width: '150px', margin:'10px auto', backgroundColor:'#000000'}} variant="middle" />
<Image image_url={img.img_url} />
<Typography variant="h6" align="center">{img.user.username}</Typography>
<Typography variant="h6" align="center">{moment(img.created_at).calendar()}</Typography>
<Button onClick = {this.writeComment} varient="outlined" component="span" color="primary">
{!this.state.isComment ? "Write Comment": "Close"}
</Button>
{this.state.isComment ? <Comment onSubmit={this.commentSubmit} commentBody={this.state.comment_body} commentChange={this.handleCommentChange}/> : null}
<Button onClick={() => this.deleteImg(img.id)} variant="outlined" component="span" color="primary">
Delete
</Button>
</Paper>
</Grid>
))
) : (
<div>
<Grid item md={8}>
<Typography>No Images yet</Typography>
</Grid>
</div>
)}
</Grid>
{/* Images */}
</Grid>
</div>
)
}
}
export default Dashboard;

onClick ={() => this.writeComment(image.id)} //id of your item
writeComment = (id) => {
this.setState({
isComment: this.state.isComment ? '' : id // check if you state is filled to toggle on/off comment
})
}
{this.state.isComment === image.id ? <Comment onSubmit={this.commentSubmit} commentBody={this.state.comment_body} commentChange={this.handleCommentChange}/> : null}

Related

I want to click anywhere on the box to navigate me to the post detail page except the buttons. It's buttons should it's own job

import {
Box,
Flex,
Text,
Input,
Image,
useColorModeValue,
useClipboard,
Divider,
IconButton,
HStack,
Menu,
MenuButton,
MenuList,
Portal,
MenuItem,
useToast,
useOutsideClick,
useDisclosure,
} from "#chakra-ui/react";
import {
BsThreeDots,
BsDot,
BsBookmark,
BsFlag,
BsCheckCircle,
} from "react-icons/bs";
import { BsHeart } from "react-icons/bs";
import { AiFillHeart, AiFillMessage } from "react-icons/ai";
import { FaShareSquare } from "react-icons/fa";
import parse from "html-react-parser";
import copyLink from "#/images/copy-link.svg";
import { axiosInstance } from "#/axiosConfig";
import { COLORS } from "../../constants/COLORS";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { useDispatch, useSelector } from "react-redux";
import { Link, useNavigate } from "react-router-dom";
import { addNotice, deleteMyPostSlice } from "../../redux/slices/userSlice";
import { FiEdit } from "react-icons/fi";
import { HiOutlineTrash } from "react-icons/hi";
import {
addComment,
deletePost,
publishPost,
unPublishPost,
} from "../../redux/asyncActions/postAction";
import CustomImage from "../small/CustomImage";
import {
deletePostSlice,
removeBookmarkSlice,
} from "../../redux/slices/postSlice";
import {
EmailShareButton,
FacebookShareButton,
TwitterShareButton,
LinkedinShareButton,
FacebookIcon,
TwitterIcon,
LinkedinIcon,
EmailIcon,
} from "react-share";
import AddPicker from "../small/AddPicker";
import PostTag from "./PostTag";
import LikedPeople from "../small/LikedPeopleModal";
import { useRef } from "react";
import ReportModal from "../common/ReportModal";
dayjs.extend(relativeTime);
var domain = window.location.host;
const NewsFeedPost = ({ data }) => {
const [liked, setLiked] = useState(data?.has_liked);
const [hasFav, setHasFav] = useState(data?.has_favorited);
const [likedCount, setLikedCount] = useState(data?.likers_count);
const [commentInput, setCommentInput] = useState("");
const { isOpen, onOpen, onClose } = useDisclosure();
const userId = useSelector((state) => state.userReducer?.userInfo?.id);
const { hasCopied, onCopy } = useClipboard(
`${domain}/post-detail/${data?.id}`
);
const toast = useToast();
const bg = useColorModeValue("white", COLORS.darkGray);
const borderColor = useColorModeValue(
COLORS.lightGrayBorder,
COLORS.darkGrayBorder
);
const navigate = useNavigate();
const ref = useRef();
const [isModalOpen, setIsModalOpen] = useState(false);
useOutsideClick({
ref: ref,
handler: () => setIsModalOpen(false),
});
const dispatch = useDispatch();
const likeThisPost = async (post_id) => {
try {
const r = await axiosInstance.post(`post/like/${post_id}`);
setLikedCount(r.data.likers_count);
setLiked((prev) => !prev);
} catch (err) {}
};
const bookmarkThisPost = async (post_id) => {
try {
const r = await axiosInstance.post(`post/favorite/${post_id}`);
setHasFav(r.data.favorited);
if (r.data.favorited) {
dispatch(addNotice("This post has been bookmarked"));
} else {
dispatch(addNotice("Bookmark has been removed"));
}
dispatch(removeBookmarkSlice(post_id));
} catch (err) {}
};
const deleteThisPost = (id) => {
dispatch(deletePost(id));
dispatch(deleteMyPostSlice(id));
};
const addMyComment = (e, id) => {
e.preventDefault();
let data = {
post_id: id,
content: commentInput,
};
dispatch(addComment(data));
navigate(`/post-detail/${id}`, { replace: true });
};
const unPublishPosts = (id) => {
dispatch(unPublishPost(id));
dispatch(deletePostSlice(id));
};
const publishPosts = (id) => {
dispatch(publishPost(id));
dispatch(deleteMyPostSlice(id));
};
return (
<Box
bg={bg}
mt="1.5rem"
p="1.5rem"
pt="0.8rem"
borderWidth={1}
borderColor={borderColor}
style={{ borderRadius: "10px" }}
onClick={() => navigate(`/post-detail/${data?.id}`)}
>
<Flex mb="1.5rem" align="center" direction="row" justify="space-between">
<Link to={`/post-detail/${data?.id}`}>
<Text fontWeight="400">{data?.title}</Text>
</Link>
<Menu>
<MenuButton>
<BsThreeDots />
</MenuButton>
<Portal>
<MenuList w="100px" p="3" bg={bg}>
{data?.user.id === userId ? (
<Flex direction={"column"} ml="1rem">
<Link to={`/post-edit/${data?.id}`}>
<Flex className="menuitems">
<FiEdit />
<Text ml="4" fontSize="0.8rem">
Edit
</Text>
</Flex>
</Link>
<Flex
className="menuitems"
onClick={() => deleteThisPost(data?.id)}
>
<HiOutlineTrash />
<Text ml="4" fontSize="0.8rem">
Delete
</Text>
</Flex>
{!data.is_published ? (
<Flex
className="menuitems"
onClick={() => publishPosts(data?.id)}
>
<BsCheckCircle />
<Text ml="4" fontSize="0.8rem">
Publish
</Text>
</Flex>
) : (
<Flex
className="menuitems"
onClick={() => unPublishPosts(data?.id)}
>
<FiEdit />
<Text ml="4" fontSize="0.8rem">
Unpublish
</Text>
</Flex>
)}
<Flex
className="menuitems"
onClick={() => bookmarkThisPost(data?.id)}
>
<BsBookmark color={hasFav ? "lightgreen" : ""} />
<Text ml="4" fontSize="0.8rem">
{hasFav ? "Remove Bookmark" : "Bookmark"}
</Text>
</Flex>
</Flex>
) : (
<Flex direction={"column"} ml="1rem">
<Flex
className="menuitems"
onClick={() => bookmarkThisPost(data?.id)}
>
<BsBookmark color={hasFav ? "lightgreen" : ""} />
<Text ml="4" fontSize="0.8rem">
{hasFav ? "Remove Bookmark" : " Bookmark"}
</Text>
</Flex>
<Flex className="menuitems" onClick={onOpen}>
<BsFlag />
<Text ml="4" fontSize="0.8rem">
Report
</Text>
<ReportModal
modalIsOpen={isOpen}
closeModal={onClose}
type="post"
id={data?.id}
/>
</Flex>
</Flex>
)}
</MenuList>
</Portal>
</Menu>
</Flex>
<Flex direction="row" align="center" justify="space-between">
<Box>
<Flex direction="row" align="center">
<Link to={`/user-profile/${data?.user?.id}`}>
<CustomImage
size="50px"
char={data.user?.firstname.charAt(0)}
imageUrl={data?.user?.photo}
alt={`${data.user?.firstname}'s Avatar`}
role={data?.user?.role}
/>
</Link>
<Flex ml="1rem" direction="column">
<Link to={`/user-profile/${data?.user?.id}`}>
<Text
fontSize={["12px", "13px", "13px", "13px"]}
mb="2px"
fontWeight="500"
>
{data?.user?.firstname} {data?.user?.lastname}
</Text>
</Link>
<Flex direction="row" align="center">
<Text
style={{ color: "#ABAAAF" }}
fontSize={["9px", "9px", "9px", "12px"]}
>
{dayjs(data.created_at).fromNow()}
</Text>
<BsDot
color="#ABAAAF"
fontSize={"20px"}
// fontSize={["14px","14px","15px","24px"]}
ml="4px"
/>
<Flex fontSize="10px" ml="4px">
in{" "}
<Text
ml="1"
cursor="pointer"
color="#FF8053"
onClick={() =>
navigate(`/?category_id=${data?.category?.id}`)
}
>
{data?.category?.name}
</Text>
</Flex>
</Flex>
</Flex>
</Flex>
</Box>
{data.tags && <PostTag tags={data?.tags} />}
</Flex>
<Box className="parseContent" mt="1.5rem" fontSize="12px">
<Link to={`/post-detail/${data?.id}`}>{parse(data.content_html)}</Link>
</Box>
<Divider mt="1rem" mb="0.5rem" />
<Flex
direction="row"
justify={{ base: "start", sm: "space-between" }}
mt="1rem"
align="center"
>
<Box w={{ base: "50%", sm: "50%", md: "70%", lg: "70%" }}>
<Box pos="relative">
<form
onSubmit={(e) => addMyComment(e, data?.id)}
style={{ position: "relative" }}
>
<Input
value={commentInput}
onChange={(e) => setCommentInput(e.target.value)}
fontSize={["9px", "10px", "12px", "12px"]}
placeholder="Add Response..."
// zIndex={0}
/>
<AddPicker setInput={setCommentInput} />
</form>
</Box>
</Box>
<Box width={{ base: "45%", sm: "45%", lg: "40%" }} pl="1.5rem">
<Flex direction="row" justify="space-around" position="relative">
<Flex direction="row" align="center">
<IconButton
onClick={() => likeThisPost(data.id)}
_hover={{ background: "transparent" }}
_active={{ background: "transparent" }}
_focus={{ boxShadow: "none" }}
bg="transparent"
color="#C5D0E6"
children={
liked ? (
<AiFillHeart fontSize={"1.7rem"} color={COLORS.hasLiked} />
) : (
<BsHeart fontSize={"1.4rem"} />
)
}
/>
<Text
color="#ABAAAF"
fontSize="14px"
onClick={() => setIsModalOpen(true)}
cursor="pointer"
>
{likedCount}
</Text>
{isModalOpen && (
<Box ref={ref}>
<LikedPeople />
</Box>
)}
</Flex>
<Link to={`/post-detail/${data?.id}`}>
<DataIconCount icon="AiFillMessage" count={data.comments_count} />
</Link>
<Menu>
<MenuButton
bg="transparent"
color="#C5D0E6"
_hover={{ background: "transparent", color: "#7B6CB4" }}
_active={{ background: "transparent" }}
_focus={{ boxShadow: "none" }}
className="newsfeed-iconbtn"
as={IconButton}
aria-label="Options"
icon={<FaShareSquare fontSize={"1.5rem"} />}
/>
<MenuList bg={bg}>
<MenuItem fontSize="12px">
<HStack align="center" gap="4px">
<FacebookShareButton
url={`${domain}/post-detail/${data?.id}`}
>
<Flex>
<FacebookIcon size={22} />
<Text ml="3">Facebook</Text>{" "}
</Flex>
</FacebookShareButton>
</HStack>
</MenuItem>
<MenuItem fontSize="12px">
<TwitterShareButton url={`${domain}/post-detail/${data?.id}`}>
<Flex>
<TwitterIcon size={22} />
<Text ml="3">Twitter</Text>{" "}
</Flex>
</TwitterShareButton>
</MenuItem>
<MenuItem fontSize="12px">
<LinkedinShareButton
url={`${domain}/post-detail/${data?.id}`}
>
<Flex>
<LinkedinIcon size={22} />
<Text ml="3">LinkedIn</Text>{" "}
</Flex>
</LinkedinShareButton>
</MenuItem>
<MenuItem fontSize="12px">
<EmailShareButton url={`${domain}/post-detail/${data?.id}`}>
<Flex>
<EmailIcon size={22} />
<Text ml="3">Mail</Text>
</Flex>
</EmailShareButton>
</MenuItem>
<MenuItem fontSize="12px">
<HStack
onClick={() => {
onCopy();
toast({
description: "Copied Post Link",
status: "success",
position: "top-right",
});
}}
align="center"
gap="4px"
>
<Image src={copyLink} />
<Text>Copy Link</Text>
</HStack>
</MenuItem>
</MenuList>
</Menu>
</Flex>
</Box>
</Flex>
</Box>
);
};
I
I have a post where there are lots of button. Each button has it's own task. Whenever I click on any white space or anywhere except this buttons, I should get navigated to post details page. So I have assigned navigate function on the onclick of the parent container that is "Box". The issue is that since all children are wrapped in box so clicking on those buttons also navigate . I want to navigate only on white spaces. I wrote a clumsy way of using e.stopPropagation() so buttons click prevent navigate() but still few buttons are navigating. Is there any way of navigating only when anywhere except buttons?
In the click event listener, check if the event current target is not a button.
if(!evt.currentTarget.matches('button'))

My react component return statement fails to render but console.log shows exactly what I need

I am new to react and working on a react video player. I'm having issue implementing the comment section.
This is my videoplayer component itself.
export default function VidPlayer() {
// useStates
const [state, setState] = useState({
playing: true,
});
const [comments, setComments] = useState([]);
const [comment, setComment] = useState("");
const { playing } = state;
const playerRef = useRef(null);
// declaring functions for video player buttons
const handlePlayPause = () => {
setState({ ...state, playing: !state.playing });
};
const handleRewind = () => {
playerRef.current.seekTo(playerRef.current.getCurrentTime() - 5);
};
const handleFoward = () => {
playerRef.current.seekTo(playerRef.current.getCurrentTime() + 5);
};
const handleStop = () => {
playerRef.current.seekTo(0);
setState({ playing: !state.playing });
};
// declaring functions for comment section
const addComments = () => {
if (comment) {
setComments({...comments, comment});
setComment("");
console.log("Hello", comments);
}
};
const handleComment = (e) => {
setComment(e.target.value);
};
return (
<div>
<AppBar style={{ background: "#e6880e" }} position="static">
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Favour's Video Player
</Typography>
</Toolbar>
</AppBar>
{/* container for the videoplayer, buttons and comment section */}
<div className="container">
<>
{/* videoplayer */}
<div className="reactPlayer one">
<ReactPlayer
ref={playerRef}
url="https://www.youtube.com/watch?v=1_ATK0BLc8U&t=3s"
playing={playing}
controls
/>
</div>
{/* buttons */}
<div className="btn-stack two">
<Stack spacing={2} direction="row">
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handlePlayPause}
>
Play
</Button>
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handleRewind}
>
Rewind{" "}
</Button>
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handleFoward}
>
Forward{" "}
</Button>
<Button
style={{ background: "#e6880e" }}
size="small"
variant="contained"
onClick={handleStop}
>
Stop
</Button>
</Stack>
</div>
{/* comment section */}
<div className="comment three">
<Comment userComs={comments} />
<TextField
placeholder="add comment"
size="small"
variant="outlined"
onChange={handleComment}
value={comment}
/>
<Button
style={{ background: "#e6880e", marginLeft: '1em' }}
onClick={addComments}
variant="contained"
size="small"
>
Send
</Button>
</div>
</>
</div>
</div>
);
}
It takes in this comments component towards the end.
export default function commentList(props) {
console.log("Hello brian", props.userComs);
const { userComs } = props;
if (Object.keys(userComs).length > 0) {
console.log(userComs);
// userComs.forEach((element) => {
// console.log("Im in", userComs);
Object.values(userComs).forEach(val => {
// console.log("Im in", userComs);
// console.log(val);
return (
<div>
<List
sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}
>
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
secondary={
<React.Fragment>
<Typography
sx={{ display: "inline" }}
component="span"
variant="body2"
color="text.primary"
>
Ali Connors
</Typography>
{val}
</React.Fragment>
}
/>
</ListItem>
<Divider variant="inset" component="li" />
</List>
</div>
);
});
} else {
return <div></div>;
}
}
This is the Front-End enter image description here
Unfortunately, when I enter a comment and click send, the screen goes blank and console throws a "nothing was returned from render" error. Can someone help me check what is wrong and how I can fix this please?
As the error says, the component isn't returning anything.
Object.values(userComs).forEach(val => {
should be
return Object.values(userComs).map(val => {
because forEach doesn't return anything and the JSX returned in each iteration will not be used anywhere, but map returns a new array that React can use.
BTW make sure to give a key prop to each div that is returned from the callback.
<div key={val}> // assuming `val` is unique

Material UI Avatar Image Upload

I managed it to make an Avatar chooser, but I don't know how to save the picture in Firebase or how to even save it as a Profile picture.
This is how it looks like:
This comes out if I click on the button:
I can choose a pic, but it just not saving it anywhere and also not displaying the picture in the avatar.
My code:
function Profile(props, navigation) {
const classes = useStyles();
const user = useUser(props.route.params.uid);
const [time, setTime] = useState("7:30");
const [timing, setTiming] = useState([]);
const [timeAfternoon, setTimeAfternoon] = useState("7:30");
const [timingAfternoon, setTimingAfternoon] = useState([]);
const [sickDaysStart, setSickDaysStart] = useState(Date.now());
const [sickDaysEnd, setSickDaysEnd] = useState(Date.now());
const [sickDaysConfirm, setSickDaysConfirm] = useState([]);
const [donwloadURL, setDownloadURL] = useState([]);
const onLogout = () => {
firebase.auth().signOut();
};
function handelSickDaysStart(e) {
setSickDaysStart(e.target.value);
}
function handelSickDaysEnd(e) {
setSickDaysEnd(e.target.value);
}
function handleTime(e) {
setTime(e.target.value);
}
function handleTimeAfternoon(e) {
setTimeAfternoon(e.target.value);
}
function delayMorning() {
db.collection("delayInTheMorning")
.doc()
.set({
time,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTiming([...timing, { time }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function delayAfternoon() {
db.collection("delayInTheAfternoon")
.doc()
.set({
timeAfternoon,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTimingAfternoon([...timingAfternoon, { timeAfternoon }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function sickDaysStartEnd() {
db.collection("DaysofSickness")
.doc()
.set({
sickDaysStart,
sickDaysEnd,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setSickDaysConfirm([
...sickDaysConfirm,
{ sickDaysStart, sickDaysEnd },
]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function isCurrentUserProfile() {
if (props.route.params.uid === firebase.auth().currentUser.uid) {
return true;
} else {
return false;
}
}
async function handleFileInputChange(e) {
const files = e.target.files;
const file = files[0];
const storage = firebase.storage();
const usersImageRef = storage
.ref()
.child(`users/${user.uid}/profilepicture.jpg`);
const snap = await usersImageRef.put(file);
const donwloadURL = await snap.ref.getDownloadURL();
setDownloadURL(donwloadURL);
await firebase.auth().currentUser.updateProfile({ photoURL: donwloadURL });
}
if (user === null) {
return <div className={classes.root} />;
}
return (
<ScrollView style={styles.root}>
<Container className={classes.div}>
<input
accept="image/*"
className={classes.input}
id="contained-button-file"
multiple
type="file"
onChange={handleFileInputChange}
/>
<label>
<IconButton>
<Avatar
src="../assets/ana.png"
style={{
margin: "10px",
width: "60px",
height: "60px",
}}
/>
</IconButton>
</label>
<Typography className={classes.text}> {user.name} </Typography>
<Typography className={classes.text}> {user.email} </Typography>
{isCurrentUserProfile() ? (
<Button
className={classes.btn}
size="large"
variant="outlined"
onClick={() => onLogout()}
>
Logout
</Button>
) : null}
</Container>
<Card className={classes.div}>
{/* //Verspätung */}
<CardContent className={classes.cardBackGround}>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung{" "}
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTime(value);
}}
/>
<Button className={classes.cardBtn} onClick={() => delayMorning()}>
Absenden
</Button>
</Container>
</CardContent>
{/* //Krankenmledungen */}
<CardContent className={classes.cardBackGround}>
<Typography variant="h5" className={classes.cardTyp}>
Krankenmledungen
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="date"
label="Von"
type="date"
defaultValue="2021-09-14"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysStart(value);
}}
/>
<TextField
id="date"
label="bis"
type="date"
defaultValue="2021-09-20"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysEnd(value);
}}
/>
</Container>
<Button
className={classes.cardBtnKM}
onClick={() => sickDaysStartEnd()}
>
Absenden
</Button>
</CardContent>
{/* //Verspätung Abolung*/}
<CardContent className={classes.cardBackGround}>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung Abholung
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTimeAfternoon(value);
}}
/>
<Button
className={classes.cardBtn}
onClick={() => delayAfternoon()}
>
Absenden
</Button>
</Container>
</CardContent>
</Card>
{/* <List> */}
{/* Verspätungs Liste */}
{timing.map((item) => {
return (
<List className={classes.lists}>
<ListItem className={classes.list1}>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung in der Früh ${item.time}`}
/>
</ListItem>
</List>
);
})}
{/* Krankmeldung */}
{timingAfternoon.map((item) => {
return (
<List className={classes.lists}>
<ListItem className={classes.list}>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung bei der Abholung ${item.timeAfternoon}`}
/>
</ListItem>
</List>
);
})}
{/* Verspätungs Nachmittag */}
{sickDaysConfirm.map((item) => {
return (
<List className={classes.lists}>
<ListItem className={classes.list}>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Krankmeldung von ${item.sickDaysStart} bis ${item.sickDaysEnd}`}
/>
</ListItem>
</List>
);
})}
</ScrollView>
);
}
There is also a imagePicker from Expo, I was thinking of trying that but I'm just not sure.
If you want to change the profile picture of a user you can use this code:
import { getAuth, updateProfile } from "firebase/auth";
const auth = getAuth();
updateProfile(auth.currentUser, {
displayName: "User Name", photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(() => {
// Profile updated!
// ...
}).catch((error) => {
// An error occurred
// ...
});
You can find more info about it here.
To get the picture URL you would need to upload it to Firebase Storage and get the downloadURL:
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
const storage = getStorage();
const storageRef = ref(storage, 'some-child');
// 'file' comes from the Blob or File API
uploadBytes(storageRef, file).then((snapshot) => {
console.log('Uploaded a blob or file!');
getDownloadURL(snapshot.ref).then((downloadURL) => {
console.log('File available at', downloadURL);
});
});
I would also change the input to not have multiple files to select. If you would share more of your code I can integrate those code snippets in your code in case you have problems with it.
UPDATE:
Here is the whole example. I see you use the old SDK so the example is with it:
function Profile(props, navigation) {
const classes = useStyles();
const user = useUser(props.route.params.uid);
const delay = DayandTime();
const [time, setTime] = useState("7:30");
const [timing, setTiming] = useState([]);
const [downloadurl, setDU] = useState("/images/example.jpg");
const [timeAfternoon, setTimeAfternoon] = useState("7:30");
const [timingAfternoon, setTimingAfternoon] = useState([]);
const [sickDaysStart, setSickDaysStart] = useState(Date.now());
const [sickDaysEnd, setSickDaysEnd] = useState(Date.now());
const [sickDaysConfirm, setSickDaysConfirm] = useState([]);
const onLogout = () => {
firebase.auth().signOut();
};
function handelSickDaysStart(e) {
setSickDaysStart(e.target.value);
}
function handelSickDaysEnd(e) {
setSickDaysEnd(e.target.value);
}
function handleTime(e) {
setTime(e.target.value);
}
function handleTimeAfternoon(e) {
setTimeAfternoon(e.target.value);
}
function delayMorning() {
db.collection("delayInTheMorning")
.doc()
.set({
time,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTiming([...timing, { time }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function delayAfternoon() {
db.collection("delayInTheAfternoon")
.doc()
.set({
timeAfternoon,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTimingAfternoon([...timingAfternoon, { timeAfternoon }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function sickDaysStartEnd() {
db.collection("DaysofSickness")
.doc()
.set({
sickDaysStart,
sickDaysEnd,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setSickDaysConfirm([
...sickDaysConfirm,
{ sickDaysStart, sickDaysEnd },
]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function isCurrentUserProfile() {
if (props.route.params.uid === firebase.auth().currentUser.uid) {
return true;
} else {
return false;
}
}
if (user === null) {
return <div className={classes.root} />;
}
async function handleFileInputChange(e) {
const files = e.target.files;
const file = files[0];
const storage = firebase.storage();
const usersImageRef = storage
.ref()
.child(`users/${user.uid}/profilepicture.jpg`);
const snap = await usersImageRef.put(file);
const downloadURL = await snap.ref.getDownloadURL();
setDU(downloadURL);
await firebase.auth().updateProfile({ photoURL: downloadURL });
}
return (
<ScrollView style={styles.root}>
<Container className={classes.div}>
<input
accept="image/*"
className={classes.input}
id="contained-button-file"
multiple
type="file"
onChange={handleFileInputChange}
/>
<label htmlFor="contained-button-file">
<IconButton>
<Avatar
src="/images/example.jpg"
style={{
margin: "10px",
width: "60px",
height: "60px",
}}
/>
</IconButton>
</label>
<Typography className={classes.text}> {user.name} </Typography>
<Typography className={classes.text}> {user.email} </Typography>
{isCurrentUserProfile() ? (
<Button
className={classes.btn}
size="large"
variant="outlined"
onClick={() => onLogout()}
>
Logout
</Button>
) : null}
</Container>
<Card className={classes.div}>
{/* //Verspätung */}
<CardContent>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung{" "}
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTime(value);
}}
/>
<Button className={classes.cardBtn} onClick={() => delayMorning()}>
Absenden
</Button>
</Container>
</CardContent>
{/* //Krankenmledungen */}
<CardContent className={classes.cardKrankmeldung}>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Krankenmledungen{" "}
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="date"
label="Von"
type="date"
defaultValue="2021-09-14"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysStart(value);
}}
/>
<TextField
id="date"
label="bis"
type="date"
defaultValue="2021-09-20"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysEnd(value);
}}
/>
</Container>
<Button
className={classes.cardBtnKM}
onClick={() => sickDaysStartEnd()}
>
Absenden
</Button>
</CardContent>
{/* //Verspätung Abolung*/}
<CardContent>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung Abholung
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTimeAfternoon(value);
}}
/>
<Button
className={classes.cardBtn}
onClick={() => delayAfternoon()}
>
Absenden
</Button>
</Container>
</CardContent>
</Card>
<List>
{/* Verspätungs Liste */}
<ListItem>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung in der Früh ${delay}`}
/>
</ListItem>
{/* Verspätungs Krankmeldung */}
<ListItem>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Krankmeldung ${delay}`}
/>
</ListItem>
{/* Verspätungs Nachmittag */}
<ListItem>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung Nachmittag ${delay}`}
/>
</ListItem>
</List>
</ScrollView>
);
}
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
posts: store.userState.posts,
following: store.userState.following,
});
export default connect(mapStateToProps, null)(Profile);
const useStyles = makeStyles({
root: {
backgroundColor: "white",
},
div: {
marginTop: 20,
marginLeft: 15,
marginRight: 15,
backgroundColor: "white",
},
avatar: {
marginBottom: 10,
},
btn: {
marginTop: 10,
width: 250,
marginBottom: 30,
},
text: {
fontSize: 25,
marginTop: 10,
},
cardTyp: {
textAlign: "left",
paddingLeft: 13,
},
cardBtn: {
marginTop: 20,
marginLeft: 30,
},
cardBtnKM: {
marginTop: 20,
marginLeft: 10,
},
cardContainer: {
display: "flex",
},
});
const styles = StyleSheet.create({
root: {
backgroundColor: "white",
},
});

React + Material-UI: list should have a unique "key" prop

I get following error / warning during rendering:
Warning: Each child in a list should have a unique "key" prop.
Check the render method of `App`. See .. for more information.
in ListItemCustom (at App.js:137)
in App (created by WithStyles(App))
in WithStyles(App) (at src/index.js:7)
What to do? Do I need to add a uniq key to my ListItem material-ui component?
App.js:
import React, { Component } from "react";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import FacebookLogin from "react-facebook-login";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import ArrowForwardIos from "#material-ui/icons/ArrowForwardIos";
import ArrowBackIos from "#material-ui/icons/ArrowBackIos";
import axios from "axios";
import ListItemCustom from "./components/ListItemCustom";
import ListSubheader from "#material-ui/core/ListSubheader";
import Switch from "#material-ui/core/Switch";
import TextField from "#material-ui/core/TextField";
import Box from "#material-ui/core/Box";
import IconButton from "#material-ui/core/IconButton";
// import this
import { withStyles } from "#material-ui/core/styles";
// make this
const styles = theme => ({
root: {
flexGrow: 1
},
menuButton: {
marginRight: theme.spacing(2)
},
title: {
flexGrow: 1
},
listSubHeaderRoot: {
backgroundColor: "#E5E5E5",
color: "#252525",
lineHeight: "22px"
}
});
class App extends Component {
state = {
accessToken: "",
isLoggedIn: false,
userID: "",
name: "",
email: "",
picture: "",
selectedEvent: undefined,
buyOrRelease: "buy",
pages: []
};
responseFacebook = response => {
this.setState({
accessToken: response.accessToken,
isLoggedIn: true,
userID: response.userID,
name: response.name,
email: response.email,
picture: response.picture.data.url
});
let accessToken = response.accessToken;
axios
.get(
"https://graph.facebook.com/v5.0/me/accounts?fields=id,name&access_token=" +
response.accessToken
)
.then(async pagesResponse => {
let promisesArray = pagesResponse.data.data.map(async page => {
console.log("page " + page.id + " " + page.name);
return axios
.get(
"https://graph.facebook.com/v5.0/" +
page.id +
"/events?fields=id,name&access_token=" +
accessToken
)
.catch(e => e);
});
const responses = await Promise.all(promisesArray);
var pages = [];
responses.forEach((response, i) => {
const page = pagesResponse.data.data[i];
pages.push({
id: page.id,
name: page.name,
events: response.data.data
});
});
this.setState({
pages: pages
});
});
};
handleClick = event =>
this.setState({
anchorEl: event.currentTarget
});
handleClose = () => {
this.setState({ anchorEl: undefined });
};
handleCloseAndLogOut = () => {
this.setState({ anchorEl: undefined });
this.setState({ isLoggedIn: undefined });
this.setState({ userID: undefined });
this.setState({ name: undefined });
this.setState({ email: undefined });
this.setState({ picture: undefined });
};
switchToRelease = () => {
this.setState({ buyOrRelease: "release" });
};
switchToBuy = () => {
this.setState({ buyOrRelease: "buy" });
};
componentDidMount() {
document.title = "Tiket.hu";
}
handleSort = event => {
this.setState({ selectedEvent: event });
};
navigateBack = () => {
this.setState({ selectedEvent: undefined });
};
render() {
let fbOrMenuContent;
let listContent;
let buyOrReleaseMenuItem;
if (this.state.isLoggedIn) {
let eventsList;
if (this.state.buyOrRelease === "buy") {
} else {
eventsList = this.state.pages.map(page => {
let eventsList2 = page.events.map(event => (
<ListItemCustom key={event.id} value={event} onHeaderClick={this.handleSort} />
));
return (
<div>
<ListSubheader className={this.props.classes.listSubHeaderRoot} key={page.id}>{page.name}</ListSubheader>
{eventsList2}
</div>
);
});
}
listContent = (
<div>
<List component="nav" aria-label="main mailbox folders">
{eventsList}
</List>
</div>
);
if (this.state.selectedEvent) {
listContent = (
<div>
<List component="nav" aria-label="main mailbox folders">
<ListItem button onClick={this.navigateBack}>
<IconButton edge="start" aria-label="delete">
<ArrowBackIos />
</IconButton>
<Box textAlign="left" style={{ width: 150 }}>
Back
</Box>
<ListItemText
secondaryTypographyProps={{ align: "center" }}
primary={this.state.selectedEvent.name}
/>
</ListItem>
<ListItem button>
<Box textAlign="left" style={{ width: 150 }}>
Select auditorium
</Box>
<ListItemText
secondaryTypographyProps={{ align: "right" }}
secondary="UP Újpesti Rendezvénytér"
/>
<IconButton edge="end" aria-label="delete">
<ArrowForwardIos />
</IconButton>
</ListItem>
<ListItem button>
<Box textAlign="left" style={{ width: 150 }}>
Release purpose
</Box>
<ListItemText
secondaryTypographyProps={{ align: "right" }}
secondary="Normal selling"
/>
<IconButton edge="end" aria-label="delete">
<ArrowForwardIos />
</IconButton>
</ListItem>
<ListItem>
<ListItemText primary="Start selling" />
<Switch edge="end" />
</ListItem>
<ListItem>
<ListItemText primary="Notify if different price would increase revenue" />
<Switch edge="end" />
</ListItem>
<ListSubheader className={this.props.classes.listSubHeaderRoot}>
Sector
</ListSubheader>
<ListItem button>
<Box textAlign="left" style={{ width: 150 }}>
Select sector
</Box>
<ListItemText
secondaryTypographyProps={{ align: "right" }}
secondary="A"
/>
<IconButton edge="end" aria-label="delete">
<ArrowForwardIos />
</IconButton>
</ListItem>
<ListItem button>
<Box textAlign="left" style={{ width: 500 }}>
Marketing resource configuration & result
</Box>
<ListItemText
secondaryTypographyProps={{ align: "right" }}
secondary=""
/>
<IconButton edge="end" aria-label="delete">
<ArrowForwardIos />
</IconButton>
</ListItem>
<ListItem>
<ListItemText primary="Price in sector" />
<TextField InputLabelProps={{ shrink: true }} />
</ListItem>
</List>
</div>
);
}
if (this.state.buyOrRelease === "buy") {
buyOrReleaseMenuItem = (
<Menu
id="simple-menu"
anchorEl={this.state.anchorEl}
keepMounted
open={Boolean(this.state.anchorEl)}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleCloseAndLogOut}>Log out</MenuItem>
<MenuItem onClick={this.switchToRelease}>
Switch Release mode
</MenuItem>
<MenuItem onClick={this.handleClose}>My tickets</MenuItem>
</Menu>
);
} else {
buyOrReleaseMenuItem = (
<Menu
id="simple-menu"
anchorEl={this.state.anchorEl}
keepMounted
open={Boolean(this.state.anchorEl)}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleCloseAndLogOut}>Log out</MenuItem>
<MenuItem onClick={this.switchToBuy}>Switch Buy mode</MenuItem>
</Menu>
);
}
fbOrMenuContent = (
<div>
<Button
aria-controls="simple-menu"
aria-haspopup="true"
onClick={this.handleClick}
>
{this.state.name}
</Button>
{buyOrReleaseMenuItem}
</div>
);
} else {
let fbAppId;
if (
window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1"
)
fbAppId = "402670860613108";
else fbAppId = "2526636684068727";
fbOrMenuContent = (
<FacebookLogin
appId={fbAppId}
autoLoad={true}
fields="name,email,picture"
scope="public_profile,pages_show_list"
onClick={this.componentClicked}
callback={this.responseFacebook}
/>
);
}
return (
<div className="App">
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={this.props.classes.title}>
Tiket.hu
</Typography>
<Button color="inherit">Search</Button>
<Button color="inherit">Basket</Button>
{fbOrMenuContent}
</Toolbar>
</AppBar>
{listContent}
</div>
);
}
}
export default withStyles(styles)(App);
ListItemCustom.js:
import React, { Component } from "react";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import ArrowForwardIos from "#material-ui/icons/ArrowForwardIos";
export default class ListItemCustom extends Component {
eventSelected = () => {
this.props.onHeaderClick(this.props.value);
};
render() {
return (
<ListItem button key={this.props.value.id} onClick={this.eventSelected}>
<ListItemText primary={this.props.value.name}/>
<ListItemIcon>
<ArrowForwardIos />
</ListItemIcon>
</ListItem>
);
}
}
You should add an unique prop to your components inside .map that is inside your render
eventsList = this.state.pages.map(page => {
let eventsList2 = page.events.map((event, i) => (
// unique key prop
<ListItemCustom key={i} value={event} onHeaderClick={this.handleSort} />
));
return (
<div key={page.name}> // unique key prop
<ListSubheader>{page.name}</ListSubheader>
{eventsList2}
</div>
);
});
Please notice that using i (the index) isn't good, you should have an unique property like an id.
Your problem is in the following loop
eventsList = this.state.pages.map(page => {
let eventsList2 = page.events.map(event => (
<ListItemCustom value={event} onHeaderClick={this.handleSort} />
));
return (
<div>
<ListSubheader>{page.name}</ListSubheader>
{eventsList2}
</div>
);
})
Each list item should have an unique key (among siblings), so you need to provide keys for the inner and outer loop, like this
eventsList = this.state.pages.map((page,index) => {
let eventsList2 = page.events.map((event,i) => (
<ListItemCustom key={i} value={event} onHeaderClick={this.handleSort} />
));
return (
<div key={index}>
<ListSubheader>{page.name}</ListSubheader>
{eventsList2}
</div>
);
});
In this example I'm using the index as key, but you should avoid that
You are right, you will need to supply each ListItem with a unique key, such as an id. You may use the index from Array.map(), but it is generally not recommended.
As stated on the official React documentation,
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity:
eventsList = this.state.pages.map((page) => {
let eventsList2 = page.events.map((event) => (
<ListItemCustom value={event} onHeaderClick={this.handleSort} />
));
return (
<div>
<ListSubheader key={page.id}>{page.name}</ListSubheader>
{eventsList2}
</div>
)
});
Enclose the ListItem component inside <React.Fragment> and apply key property to the <React.Fragment> component as follows:
<React.Fragment key={`some-unique-id`}>
<ListItem >
...
</ListItem>
</React.Fragment>

how to prevent duplicate onChange values within map loop

I'm trying to prevent onChange values from duplicating within each item.
for example
onChange method for comments
handleCommentChange = (e) => {
this.setState({
comment_body: e.target.value
})
}
I'm assuming i would have to loop through keys or something within the onChange method. I'm not sure how i would go about doing this.
something like
Pseudo Attempt
Object.keys(this.state.items).forEach(key){
this.setState({
comment_body: e.target.value[key]
})
}
Or is there a better way ? not sure.
map iteration code
{this.state.images.length > 0 ? (
this.state.images.map( (img, i) => (
<Grid item sm={12} md={12} key={img.id} style={{ margin: '30px 0px'}}>
<Paper style={{padding:'20px 20px'}}>
{/* // empty image_title */}
<Typography style={{ padding: '30px 5px', letterSpacing:'8px', textTransform:'uppercase'}} variant="h4" align="center">{img.image_title}</Typography>
<Divider style={{ width: '150px', margin:'10px auto', backgroundColor:'#000000'}} variant="middle" />
<Image image_url={img.img_url} />
<Typography variant="h6" align="center">{img.user.username}</Typography>
<Typography variant="h6" align="center">{moment(img.created_at).calendar()}</Typography>
<Button onClick ={() => this.writeComment(img.id)} variant="outlined" component="span" color="primary">
{this.state.isComment === img.id ? "Close" : "Write A Comment"}
</Button>
{/* here were prevent comments being selected for all items in the array, renders the comment form you clicked on. */}
{this.state.isComment === img.id ?
<Comment onSubmit={this.commentSubmit}
commentBody={this.state.comment_body }
commentChange={this.handleCommentChange}/>
: null}
{/* hide delete button when user enters comment */}
{!this.state.isComment ? <Button style={{margin: '0px 20px'}} onClick={() => this.deleteImg(img.id)} variant="outlined" component="span" color="primary">
Delete
</Button> : null}
</Paper>
</Grid>
))
) : (
<div>
<Grid item md={8}>
<Typography>No Images yet</Typography>
</Grid>
</div>
)}
The problem is that all your comments refer to the same value in your component-state. I wanted to touch upon this in your earlier question.
What you should be doing is separating your Grid markup into its own component like this:
ImageContainer.js
import React, { Component } from "react";
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
import Divider from '#material-ui/core/Divider';
import Image from './Image';
import moment from 'moment';
import Comment from './Comment';
class ImageContainer extends React.Component{
state = {
isComment: false,
comment_body: ""
}
handleCommentChange = (e) => {
this.setState({
comment_body: e.target.value
})
}
writeComment = (id) => {
this.setState({
isComment: this.state.isComment ? '' : id // check if you state is filled to toggle on/off comment
})
}
commentSubmit = (e) => {
e.preventDefault();
console.log(this.state.comment_body);
// Axios.post('/images/newComment', this.state.comment_body).then( (response )=> {
// const newComment = { ...response.data};
// console.log(newComment);
// this.setState({
// comment_body: ''
// })
// })
}
render(){
const { img } = this.props
return(
<Grid item sm={12} md={12} key={img.id} style={{ margin: '30px 0px'}}>
<Paper style={{padding:'20px 20px'}}>
{/* // empty image_title */}
<Typography style={{ padding: '30px 5px', letterSpacing:'8px', textTransform:'uppercase'}} variant="h4" align="center">{img.image_title}</Typography>
<Divider style={{ width: '150px', margin:'10px auto', backgroundColor:'#000000'}} variant="middle" />
<Image image_url={img.img_url} />
<Typography variant="h6" align="center">{img.user.username}</Typography>
<Typography variant="h6" align="center">{moment(img.created_at).calendar()}</Typography>
<Button onClick ={() => this.writeComment(img.id)} variant="outlined" component="span" color="primary">
{this.state.isComment === img.id ? "Close" : "Write A Comment"}
</Button>
{/* here were prevent comments being selected for all items in the array, renders the comment form you clicked on. */}
{this.state.isComment === img.id ?
<Comment onSubmit={this.commentSubmit}
commentBody={this.state.comment_body }
commentChange={this.handleCommentChange}/>
: null}
{/* hide delete button when user enters comment */}
{!this.state.isComment ? <Button style={{margin: '0px 20px'}} onClick={() => this.deleteImg(img.id)} variant="outlined" component="span" color="primary">
Delete
</Button> : null}
</Paper>
</Grid>
)
}
}
export default ImageContainer
Now each ImageContainer has its own state-values to keep track of and update. So there is no duplicates.
Then in your Dashboard component, simply import ImageContainer.
import ImageContainer from "./ImageContainer"
Now for each image in our .map(), we will create a unique instance of that component, and pass the image as a prop to ImageContainer.
{this.state.images.length > 0 ? (
this.state.images.map( (img, i) => (
<ImageContainer img={img}/>
))
) : (
<div>
<Grid item md={8}>
<Typography>No Images yet</Typography>
</Grid>
</div>
)}
Also remember to hook up your event-handlers to your Comment component. At the minimum it needs to use the props you passed in.
Comment.js
import React from "react";
const Comment = props => {
return (
<form onSubmit={props.onSubmit}>
<input
onChange={props.commentChange}
value={props.commentBody}
/>
</form>
);
};
export default Comment;
Here's a sandbox too, I dont have your Comment component code so it wont have the same styling: https://codesandbox.io/s/young-pine-zmpvr

Resources