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

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>

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'))

Linking href in Material UI Drawer Component in dynamic website

I'm trying to use Material UI's drawer component (https://material-ui.com/components/drawers/) to set up a navigation bar. As I'm using this code for the project, I'm not sure exactly how to link the href properly so the user can click on the home menu button and routes to a different page. I have used history.push in each function for different pages I have created, but not sure how to link it up.
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { Drawer, Button, List, Divider, ListItem, ListItemIcon, ListItemText, AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import { Link } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import clsx from 'clsx';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
import HomeIcon from '#material-ui/icons/Home';
import LockOpenIcon from '#material-ui/icons/LockOpen'; // log in
import ExitToAppIcon from '#material-ui/icons/ExitToApp'; // sign out
import AccountCircleIcon from '#material-ui/icons/AccountCircle'; //user profile?
import GroupIcon from '#material-ui/icons/Group'; // team
import AccountBoxIcon from '#material-ui/icons/AccountBox';
import wecycle from '../images/wecycle_logo.PNG';
const useStyles = makeStyles((theme) => ({
main: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
/*height: "100vh",*/
fontFamily: "Roboto",
},
navbar: {
backgroundColor: "white",
},
navbarContent: {
width: "100%",
margin: "0 auto",
},
navbarTitle: {
fontSize: "2rem",
flexGrow: '2',
},
icon: {
color: 'black',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "2.5rem",
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
wecycleLogo: {
width: "115px",
height: "50px",
}
}));
/**
* The code line 63 to 147 is composed of Material UI Drawer Component code.
* Please refer to https://material-ui.com/components/drawers/ for 'Temporary Drawer'
*
* #returns header
*/
export default function Header() {
const classes = useStyles();
const {currentUser, logout } = useAuth();
const [error, setError] = useState("");
const history = useHistory();
function aboutUsRedirect() {
history.push("/aboutUs");
}
function landingRedirect() {
history.push("/");
}
function loginRedirect() {
history.push("/login");
}
function signupRedirect() {
history.push("/signup");
}
async function handleLogout() {
setError('');
try {
await logout();
history.pushState("/");
} catch {
setError("Failed to log out");
}
}
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Home', 'Log In', 'Sign Up'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <HomeIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Profile', 'About'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <AccountBoxIcon /> : <GroupIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
return (
<div className={classes.main} id="header">
<AppBar className={classes.navbar}>
<Toolbar className={classes.navbarContent}>
<h1 className={classes.navbarTitle}>
<img src = {wecycle} className={classes.wecycleLogo} />
</h1>
<IconButton>
{['right'].map((anchor) => (
<React.Fragment key={anchor}>
<IconButton onClick={toggleDrawer(anchor, true)}>
<SortIcon className={classes.icon} />
</IconButton>
<Drawer anchor={anchor} open={state[anchor]} onClose={toggleDrawer(anchor, false)}>
{list(anchor)}
</Drawer>
</React.Fragment>
))}
</IconButton>
</Toolbar>
</AppBar>
</div>
);
}
You can attach the callbacks to the ListItem component.
<ListItem button onClick={clickHandler}>
<ListItemIcon>
<IconComponent />
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
You can optimize your item mapping by moving more data into the array being mapped.
const menuItems = [
{
text: 'Home',
icon: HomeIcon,
onClick: () => history.push("/"),
},
{
text: 'Log In',
icon: MailIcon,
onClick: () => history.push("/login"),
},
{
text: 'Sign Up',
icon: HomeIcon,
onClick: () => history.push("/signup"),
},
];
...
<List>
{menuItems.map(({ text, icon: Icon, onClick}, index) => (
<ListItem button key={text} onClick={onClick}>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>

How to make Material UI Temporary Drawer button as icon instead of text?

I'm using Material UI to create a navigation bar with Temporary Drawer. When the user clicks on the hamburger menu icon, I want the menu to fade-in to the screen and slide from the right.
Basically, all the functionality works, except only the button named 'RIGHT' works instead of the hamburger icon I have created beside it..
I have tried removing 'right' and replace it with the icon, but when I do that the menu comes out from left-top...
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { Link as Scroll } from 'react-scroll'
import clsx from 'clsx';
import Drawer from '#material-ui/core/Drawer';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: "100vh",
fontFamily: "Roboto",
},
appbar: {
background: "none",
},
appbarWrapper: {
width: "80%",
margin: "0 auto"
},
appbarTitle: {
fontSize: "2rem",
flexGrow: '1',
},
icon: {
color: '#fff',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "4.5rem",
},
goDown: {
color: '#5AFF3D',
fontSize: '4rem',
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
}));
export default function Header() {
const classes = useStyles();
const [checked, setChecked] = useState(false);
useEffect(() => {
setChecked(true);
}, [])
// NEW
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
/*
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
*/
return (
<div className={classes.root} id="header">
<AppBar className={classes.appbar} elevation={0}>
<Toolbar className={classes.appbarWrapper}>
<h1 className={classes.appbarTitle}>
We<span className={classes.colorText}>cycle</span>
</h1>
<IconButton>
<SortIcon className={classes.icon} /*onClick={handleClick} aria-control="fade-menu" aria-haspopup="true"*/ />
{['right'].map((anchor) => (
<React.Fragment key={anchor}>
<Button onClick={toggleDrawer(anchor, true)}>{anchor}</Button>
<Drawer anchor={anchor} open={state[anchor]} onClose={toggleDrawer(anchor, false)}>
{list(anchor)}
</Drawer>
</React.Fragment>
))}
</IconButton>
{/* <Menu
id="fade-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu> */}
</Toolbar>
</AppBar>
<Collapse
in={checked} {...(checked ? { timeout: 1000 } : {})}
collapsedHeight={50}
>
<div className={classes.container}>
<h1 className={classes.title}>
Meet the <br /> <span className={classes.colorText}>Team </span>
</h1>
<Scroll to="place-to-visit" smooth={true}>
<IconButton>
<ExpandMoreIcon className={classes.goDown} />
</IconButton>
</Scroll>
</div>
</Collapse>
</div>
);
}
I've made your code work for what you need and left some comments inside. I don't know what you ideally want to make, but don't copy all the code from the MUI example which you might not fully understand.
const [anchorEl, setAnchorEl] = useState(false);
// const open = Boolean(anchorEl);
const handleDrawerOpen = () => {
setAnchorEl(true);
};
const handleDrawerClose = () => {
setAnchorEl(false);
};
Above is used to control the state of Drawer.
Following is the code that you can just copy.
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { Link as Scroll } from 'react-scroll'
import clsx from 'clsx';
import Drawer from '#material-ui/core/Drawer';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: "100vh",
fontFamily: "Roboto",
},
appbar: {
background: "none",
},
appbarWrapper: {
width: "80%",
margin: "0 auto"
},
appbarTitle: {
fontSize: "2rem",
flexGrow: '1',
},
icon: {
color: '#fff',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "4.5rem",
},
goDown: {
color: '#5AFF3D',
fontSize: '4rem',
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
}));
export default function Header() {
const classes = useStyles();
const [checked, setChecked] = useState(false);
useEffect(() => {
setChecked(true);
}, [])
// NEW
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
const [anchorEl, setAnchorEl] = useState(false);
// const open = Boolean(anchorEl);
const handleDrawerOpen = () => {
setAnchorEl(true);
};
const handleDrawerClose = () => {
setAnchorEl(false);
};
return (
<div className={classes.root} id="header">
<AppBar className={classes.appbar} elevation={0}>
<Toolbar className={classes.appbarWrapper}>
<h1 className={classes.appbarTitle}>
We<span className={classes.colorText}>cycle</span>
</h1>
{/* You Don't need the map here unless you want many button to toggle one Drawer,
like the example on https://material-ui.com/components/drawers/.
and the point here for your question is to move the onClick to IconButton, and you may
not want the button inside IconButton, since it is a button already,
the inside button would make it a little ugly */}
<IconButton onClick={handleDrawerOpen} onClose={handleDrawerClose}>
<SortIcon className={classes.icon} /*onClick={handleClick} aria-control="fade-menu" aria-haspopup="true"*/ />
RIGHT{/* delete the text here if you don't need */}
</IconButton>
<Drawer anchor='right' open={anchorEl} onClose={handleDrawerClose}>
{list('right')}
</Drawer>
{/* <Menu
id="fade-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu> */}
</Toolbar>
</AppBar>
<Collapse
in={checked} {...(checked ? { timeout: 1000 } : {})}
collapsedHeight={50}
>
<div className={classes.container}>
<h1 className={classes.title}>
Meet the <br /> <span className={classes.colorText}>Team </span>
</h1>
<Scroll to="place-to-visit" smooth={true}>
<IconButton>
<ExpandMoreIcon className={classes.goDown} />
</IconButton>
</Scroll>
</div>
</Collapse>
</div>
);
}
Recomandation: since you have import React, { useEffect, useState } from 'react', you may not use React.useState() but use useState() directly.

how to unit test component that relies on redux props

I have a component called Comment List, comment list is getting all comments that is apart of this.props.posts Which comes from the dashboard container.
The <CommentList/> component is being called in the <PostList/> component, from there it is passing {post.Comments} as props to
My question is how would i properly test <CommentList/> Component, considering it relies on redux selector, api call etc.
Here is the flow.
1) dashboard container holds the props for this.props.post
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
addContent,
addTitle,
createPostInit,
deleteCommentInit,
initCommentUpdates,
deletePostInit,
dislikePostInit,
getPostsInit,
likePostInit,
notificationInit,
postCommentInit,
} from "../actions/postActions";
import Dashboard from "../components/dashboard/dashboard";
import { getBodyError, getIsNotified, getNotification, getPopPosts, getPosts, getTitleError, getUser, postContent, title } from "./../selectors/selectors";
const mapDispatchToProps = (dispatch: any) => ({
getPostsInit: () => dispatch(getPostsInit()),
initCommentUpdates: () => dispatch(initCommentUpdates()),
notificationInit: () => dispatch(notificationInit()),
likePost: (id: number) => dispatch(likePostInit(id)),
addTitle: (data: string) => dispatch(addTitle(data)),
addContent: (data: string) => dispatch(addContent(data)),
postCommentInit: (commentData: object) => dispatch(postCommentInit(commentData)),
dislikePost: (id: number) => dispatch(dislikePostInit(id)),
deletePostInit: (id: number, userId: number) => dispatch(deletePostInit(id, userId)),
deleteComment: (id: number, postId: number, userId: number) => dispatch(deleteCommentInit(id, postId, userId)),
createPostInit: (postData: object) => dispatch(createPostInit(postData)),
});
const mapStateToProps = createStructuredSelector({
posts: getPosts(),
popPosts: getPopPosts(),
user: getUser(),
isNotified: getIsNotified(),
titleError: getTitleError(),
bodyError: getBodyError(),
title: title(),
postContent: postContent(),
notification: getNotification(),
});
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
this.props.posts gets mapped, and post.Commments gets passed to commentList prop
postList.tsx
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
<CommentList user={currentUser} deleteComment={props.deleteComment} userId={post.userId} postId={post.id} comments={post.Comments} />
{/* if show more hide show more button and show show less comments button */}
</Fragment>
) : (
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px" }}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
CommentList.tsx
import Button from "#material-ui/core/Button";
import Divider from "#material-ui/core/Divider";
import Grid from "#material-ui/core/Grid";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import Typography from "#material-ui/core/Typography";
import OurListItem from "../../common/OurListItem";
import DeleteOutlineOutlinedIcon from "#material-ui/icons/DeleteOutlineOutlined";
import moment from "moment";
import React, { Fragment, useState } from "react";
export default function CommentList(props: any) {
const [showMore, setShowMore] = useState<Number>(3);
const [showLessFlag, setShowLessFlag] = useState<Boolean>(false);
const showComments = (e) => {
e.preventDefault();
setShowMore(12);
setShowLessFlag(true);
};
const showLessComments = (e) => {
e.preventDefault();
setShowMore(3);
setShowLessFlag(false);
};
return (
<Grid>
{props.comments.slice(0, showMore).map((comment, i) => (
<div key={i}>
<List style={{ paddingBottom: "20px" }}>
<OurListItem>
<Typography color="primary" align="left">
{comment.comment_body}
</Typography>
{comment.gifUrl && (
<div style={{ display: "block" }}>
<img width="100%" height="300px" src={`${comment.gifUrl}`} />
</div>
)}
</OurListItem>
{props.user && props.user.user && comment.userId === props.user.user.id ? (
<Typography style={{ display: "inline-block", float: "right" }} align="right">
<span style={{ cursor: "pointer" }} onClick={() => props.deleteComment(comment.id, props.postId, comment.userId)}>
<DeleteOutlineOutlinedIcon style={{ margin: "-5px 0px" }} color="primary" /> <span>Delete</span>
</span>
</Typography>
) : null}
<Typography style={{ padding: "0px 0px" }} variant="caption" align="left">
{comment.author.username}
</Typography>
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">
{moment(comment.createdAt).calendar()}
</Typography>
<Divider variant="fullWidth" component="li" />
</List>
</div>
))}
<Fragment>
{props.comments.length > 3 && showLessFlag === false ? (
<Button onClick={(e) => showComments(e)} variant="outlined" component="span" color="primary">
Show More Comments
</Button>
) : (
<Fragment>
{props.comments.length > 3 && (
<Button onClick={(e) => showLessComments(e)} variant="outlined" component="span" color="primary">
Show Less Comments
</Button>
)}
</Fragment>
)}
</Fragment>
</Grid>
);
}
How would i correctly test this component with redux, considering im not getting it from redux, here is my approach,
CommentList.test.tsx
import React from "react";
import CommentList from "./CommentList";
import Grid from "#material-ui/core/Grid";
import { createShallow } from "#material-ui/core/test-utils";
import toJson from "enzyme-to-json";
const props = {
comments: [
{
userId: 1,
id: 1,
comment_body: "delectus aut autem",
author: {
username: "Bill",
},
},
{
userId: 2,
id: 2,
comment_body: "delectus aut autem",
author: {
username: "Bill",
},
},
{
userId: 3,
id: 3,
comment_body: "delectus aut autem",
author: {
username: "Bill",
},
},
],
};
describe("Should render <CommentList/>", () => {
let wrapper;
let shallow;
beforeEach(() => {
shallow = createShallow();
wrapper = shallow(<CommentList {...props} />);
});
it("should render <CommentList/>", () => {
expect(wrapper.find(Grid)).toHaveLength(1);
});
it("should snap <CommentList/> component", () => {
// expect(toJson(wrapper)).toMatchSnapshot();
});
});
My question is how would i properly test Component, considering it relies on redux selector, api call etc.
I don't see this being true anywhere. Component doesn't rely nor know about Redux, it's only getting props (which you can mock in your tests).
How would i correctly test this component with redux, considering I'm not getting it from redux, here is my approach
You don't - that's testing implementation details. You can test the reducers themselves, but the best way to test this is not to tie them together.
The way I see it, you're trying to test your reducers using this component, not necessarily the component itself as to test the component itself works just fine.
Or to test it all together, you should look at E2E tests.

react fires events for all items and the item selected

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}

Resources