How to swap MUI icon on IconButton when hovering - reactjs

I have these tabs that have a close button on them, if the content in them has edits then the close icon turns to a circle, similar to visual code.
{tabs.map((tab, index) => {
const child = (
<StyledTab
label={
<span>
{tab.label + ':' + tab.hasEdit}
<IconButton size="small" component="span" onClick={() => closeClickHandler(tab.value)}>
{tab.hasEdit ? (
<CircleIcon style={{ fontSize: "12px" }} />
) : (
<CloseIcon style={{ fontSize: "18px" }} />
)}
</IconButton>
</span>
}
value={tab.value}
key={index}
/>
);
return (
<DraggableTab
label={
<span>
{tab.label}
<IconButton size="small" component="span" onClick={() => {
closeClickHandler(tab.value);
}}>
{tab.hasEdit ? (
<CircleIcon style={{ fontSize: "12px" }} />
) : (
<CloseIcon style={{ fontSize: "18px" }} />
)}
</IconButton>
</span>
}
value={tab.value}
index={index}
key={index}
child={child}
/>
);
})}
What I'm having trouble with is getting the icon to change from a circle to a close icon while hovering over the button.
Could someone give me a hand on a good way to implement this please?!

You could do this by adding a state for the items. Then add a onMouseEnter and onMouseLeave events on the IconButton. When hovering we can add the index to the array and finally remove when we're leaving. To determine if a icon needs to change we can check if the index in in the hoveringItems.
const [hoveringItems, setHoveringItems] = useState([]);
function handleHover(index, isLeaving) {
setHoveringItems((prevItems) => {
if (isLeaving) return prevItems.filter((item) => item !== index);
return [...prevItems, index];
});
}
return (
<IconButton
size="small"
component="span"
onClick={() => {
closeClickHandler(tab.value);
}}
onMouseEnter={() => handleHover(index, false)}
onMouseLeave={() => handleHover(index, true)}
>
{tab.hasEdit || hoveringItems.includes(index) ? (
<CircleIcon style={{ fontSize: "12px" }} />
) : (
<CloseIcon style={{ fontSize: "18px" }} />
)}
</IconButton>
);

Related

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

Create Stepper, that handle next and back as onClick-event

I want to create a Stepper, where you can handleNext and handleBack in the StepLabel as onClickevent.
I dont have a clue, how to do it.
here is the sandbox: https://codesandbox.io/s/stepper-forked-2q1wd
export default function IndexPage() {
const [activeStep, setActiveStep] = useState(0);
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
function getStepsContent(stepIndex) {
switch (stepIndex) {
case 0:
return <h1>adsf</h1>;
case 1:
return <h1>adsf</h1>;
case 2:
return <h1>adsf</h1>;
}
}
const steps = getSteps();
return (
<>
<div>
<Stepper
alternativeLabel
activeStep={activeStep}
connector={<ColorlibConnector />}
>
{steps.map((label) => (
<Step key={label}>
<StepLabel
onClick={handleNext}
StepIconComponent={ColorlibStepIcon}
>
{label}
</StepLabel>
</Step>
))}
</Stepper>
</div>
<br />
<div>
{getStepsContent(activeStep)}
{activeStep === steps.length ? (
"Bewerbung abgesendet"
) : (
<div style={{ display: "flex", justifyContent: "center" }}>
<div>
<Button
variant="outlined"
color="primary"
onClick={handleBack}
disabled={activeStep === 0}
>
{activeStep > 0 ? "Zurück" : null}
</Button>
<Button
style={{ color: "white", margin: 30 }}
variant="contained"
color="primary"
onClick={handleNext}
>
{activeStep === steps.length - 1 ? "Absenden" : "Weiter"}
</Button>
</div>
</div>
)}
</div>
</>
);
}
I just added the handleNext function as a onClick, but I want also to go back with clicking on the stepper.
You can use the index to set the steps. replace your onClick on your StepLabel with this
{steps.map((label, index) => (
<Step key={label}>
<StepLabel
onClick={() => setActiveStep(index)}
StepIconComponent={ColorlibStepIcon}
>
{label}
</StepLabel>
</Step>
))}
Stepper StepLabel Fix

jsx: ReactJS if condition

How to use if condition in jsx: ReactJS? I just want that if the
if user == "author" or "supervisor":
<IconButton
aria-label="delete"
onClick={() => props.pressHandler(props.id)}
>
<DeleteIcon style={{ color: 'red' }} />
</IconButton>
else
no delete button
Just put them in braces,
{ ["author", "supervisor"].includes(user) &&
<IconButton
aria-label="delete"
onClick={() => props.pressHandler(props.id)}
>
<DeleteIcon style={{ color: 'red' }} />
</IconButton> || null }
Reference: https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator
To elaborate on what Stark wrote:
You can use the js operator as such:
{(
(user === "author" || user === "supervisor") &&
<IconButton
aria-label="delete"
onClick={() => props.pressHandler(props.id)}
>
<DeleteIcon style={{ color: 'red' }} />
</IconButton>
) || undefined
}
Same above with ternary operator and React Fragment:
{
(user === "author" || user === "supervisor") ?
<IconButton
aria-label="delete"
onClick={() => props.pressHandler(props.id)}
>
<DeleteIcon style={{ color: 'red' }} />
</IconButton> : <></>
}
In false case, Undefined or React.Fragment wont be rendered.
{
["author", "supervisor"].includes(user) ? (
<IconButton
aria-label="delete"
onClick={() => props.pressHandler(props.id)}
>
<DeleteIcon style={{ color: "red" }} />
</IconButton>
) : null;
}
When you use "&&" operator sometimes you can see "false" text on your app. null will be great option to display nothing or use can put something differrent than delete button for regular user as null.

react calls function for each item in array

I'm quite stuck on how to execute a function for a specific post.id, and not execute it for all items within the array.
The scenario
upon clicking show more comments, it shows more comments for each item in the array.
For example
and this
How can i make it so that upon show more comments, it shows more comments for that post only.
here is the current code, in which the logic is happening.
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{padding: "10px 0px", margin: "20px 0px"}}>Commments</Typography>
<CommentList showMore={showMore} comments={post.Comments} />
{/* if show more hide show more button and show show less comments button */}
{/* {isPost === post.id ? ( */}
<Fragment>
{post.Comments.length > 3 && showLessFlag === false && (
<Button onClick={ e => showComments(e, post.id)} variant="outlined" component="span" color="primary">
Show More Comments
</Button>
)}
{post.Comments.length > 3 && showLessFlag === true && (
<Button onClick={ e => showLessComments(e)} variant="outlined" component="span" color="primary">
Show Less Comments
</Button>
)}
</Fragment>
{/* ):(
null
)} */}
</Fragment>
):(
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px"}}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
fullCode(postList)
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import Divider from "#material-ui/core/Divider";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Typography from "#material-ui/core/Typography";
import DeleteOutlineOutlinedIcon from "#material-ui/icons/DeleteOutlineOutlined";
import FavoriteIcon from "#material-ui/icons/Favorite";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
import moment from "moment";
import React, { Fragment, useState } from "react";
import OurLink from "../../common/OurLink";
import CommentList from "../../forms/commentList/CommentList";
import CommentForm from "../../forms/comment/CommentForm";
export default function PostList(props: any) {
const [isComment, setIsComment] = useState(false);
const [showMore, setShowMore] = useState(3)
const [showLessFlag, setShowLessFlag] = useState(false);
const [comment_body, setCommentBody] = useState('');
const [isPost, setIsPost] = useState(null);
const writeComment = (id) => {
setIsComment(isComment ? "" : id);
};
const showComments = (e, id) => {
e.preventDefault();
setShowMore(12);
setShowLessFlag(true);
// setIsPost(isPost ? "" : id)
}
const showLessComments = (e) => {
e.preventDefault();
setShowMore(3);
setShowLessFlag(false);
}
const commentSubmit = (e: any, id:number) => {
e.preventDefault();
const formData = {
comment_body,
postId: id
};
if(comment_body.length > 6 ){
if(props.postComment(formData)){
setIsComment(false)
setCommentBody('')
}
}else{
alert("Comment must be at least 6 characters")
}
};
const { posts, currentUser} = props;
console.log(isPost)
return posts.length > 0 ? (
posts.map((post, i) => (
<Fragment key={i}>
<Grid item={true} sm={12} md={12} style={{ margin: "20px 0px" }}>
<Paper style={{ padding: "20px",}}>
<Typography variant="h5" align="left">
<OurLink to={{
pathname: `/post/${post.id}`,
state: { post },
}}
title={post.title}
/>
</Typography>
<Grid item={true} sm={12} md={12} style={{ padding: "30px 0px"}} >
<Typography align="left">{post.postContent.slice(0, 30)}</Typography>
</Grid>
<Avatar
style={{
display: "inline-block",
margin: "-10px -20px",
padding: "0px 30px 0px 20px",
}}
sizes="small"
src={post.author.gravatar}
/>
<Typography display="inline" variant="subtitle1" align="left">
{post.author.username}
</Typography>
<Typography align="right">Likes: {post.likeCounts}</Typography>
{/* <span
style={{ cursor: "pointer" }}
onClick={() => props.likePost(post.id)}
>
{" "}
Like this post
</span>
<div style={{ margin: "20px 0px", cursor: "pointer" }}>
<span onClick={() => props.dislikePost(post.id)}>
Dislike this post
</span>
</div> */}
<Grid container={true} spacing={1} style={{ padding: "20px 0px"}}>
<Grid item={true} sm={10} lg={10} md={10} style={{ padding: "0px 0px"}}>
<Typography align="left">
{currentUser && currentUser.user && post.userId === currentUser.user.id ? (
<span style={{cursor: "pointer"}} onClick={() => props.deletePost(post.id)}>
<DeleteOutlineOutlinedIcon style={{ margin: "-5px 0px"}} color="primary" /> <span>Delete</span>
</span>
) : (
null
)}
</Typography>
</Grid>
<Grid item={true} sm={2} lg={2} style={{ padding: "0px 15px"}}>
<Typography align="right">
{post.likedByMe === true ? (
<span style={{ cursor: "pointer"}} onClick={() => props.dislikePost(post.id)}>
<FavoriteIcon style={{ color: "red" }}/>
</span>
) : (
<span onClick={() => props.likePost(post.id)}>
<FavoriteBorderIcon
style={{ color: "red", cursor: "pointer" }}
/>
</span>
)}
</Typography>
</Grid>
</Grid>
<Typography variant="h6" align="left">
{moment(post.createdAt).calendar()}
</Typography>
<Grid item={true} sm={12} lg={12} style={{ paddingTop: "40px"}}>
<Button onClick={() => writeComment(post.id)} variant="outlined" component="span" color="primary">
{isComment === post.id ? "Close" : "Write A Comment"}
</Button>
{isComment === post.id
? (
<CommentForm
commentChange={e => setCommentBody(e.target.value)}
comment_body={comment_body}
onSubmit={e => commentSubmit(e, post.id)}
/>
)
: null}
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{padding: "10px 0px", margin: "20px 0px"}}>Commments</Typography>
<CommentList showMore={showMore} comments={post.Comments} />
{/* if show more hide show more button and show show less comments button */}
{/* {isPost === post.id ? ( */}
<Fragment>
{post.Comments.length > 3 && showLessFlag === false && (
<Button onClick={ e => showComments(e, post.id)} variant="outlined" component="span" color="primary">
Show More Comments
</Button>
)}
{post.Comments.length > 3 && showLessFlag === true && (
<Button onClick={ e => showLessComments(e)} variant="outlined" component="span" color="primary">
Show Less Comments
</Button>
)}
</Fragment>
{/* ):(
null
)} */}
</Fragment>
):(
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px"}}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
</Grid>
</Paper>
</Grid>
</Fragment>
))
) : (
<div>
<Grid item={true} md={8}>
<Typography>No Posts yet</Typography>
</Grid>
</div>
);
}
CommentList
import Divider from "#material-ui/core/Divider";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Grid from "#material-ui/core/Grid";
import moment from "moment";
import React, { Component } from "react";
const CommentList = (props: any) => {
return(
<div style={{ overflow:"scroll"}}>
{props.comments.slice(0, props.showMore).map((comment, i) => (
<div key={i}>
<List style={{ paddingBottom: "20px"}}>
<ListItem alignItems="center" style={{ padding: "0px"}}>
<Typography color="primary" align="left">
{comment.comment_body}
</Typography>
</ListItem>
<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>
))}
</div>
)
};
export default CommentList;
I pretty much just moved the show/show less logic to the commentlist component, and made this component into to a react hook component, instead of a state less component.
so now we have
Similar issue
how to prevent duplicate onChange values within map loop
CommentList
import Divider from "#material-ui/core/Divider";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Grid from "#material-ui/core/Grid";
import moment from "moment";
import React, { Component, Fragment, useState } from "react";
export default function CommentList(props: any) {
const [showMore, setShowMore] = useState(3)
const [showLessFlag, setShowLessFlag] = useState(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" }}>
<ListItem alignItems="center" style={{ padding: "0px" }}>
<Typography color="primary" align="left">
{comment.comment_body}
</Typography>
</ListItem>
<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>
)
};
postList(after clean up)
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
<CommentList 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>
)}

Material ui anchor without hooks

I have a menu component that pops open in a table. When I copy the material ui example into the cell in the table it works perfectly.
https://codesandbox.io/s/gitl9
The material ui example uses hooks and I want to change it to a class component and use redux.
When I made the change the pop-up menu does not align beside the the button you press anymore.
The anchorEl attribute is responsible for passing the location of the button that has been called.
I added these attributes allow me to move the menu pop but it does not align with button that you click to open the menu.
const options = ["View", "Edit", "Delete"];
const ITEM_HEIGHT = 48;
class ActionsOptionMenu extends Component {
state = { anchorEl: null };
render() {
return (
<div>
<IconButton
aria-label='more'
aria-controls='long-menu'
aria-haspopup='true'
>
<MoreVertIcon />
</IconButton>
<Menu
getContentAnchorEl={null}
anchorOrigin={{
height: "54px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: `0 ${padding} 0 ${padding}`,
margin: "0 auto 7px auto"
}}
transformOrigin={{ vertical: "top", horizontal: "right" }}
id='long-menu'
anchorEl={anchorEl}
keepMounted
open={true}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: 120
}
}}
>
{options.map(option => (
<MenuItem key={option}>{option}</MenuItem>
))}
</Menu>
</div>
);
}
}
I solved the issue doing it this way.
render() {
const { open } = this.state;
return (
<div>
<IconButton
aria-label='more'
aria-controls='long-menu'
aria-haspopup='true'
buttonRef={node => {
this.anchorEl = node;
}}
onClick={event => this.handleClick(event)}
>
<MoreVertIcon />
</IconButton>
<Popper
open={open}
anchorEl={this.anchorEl}
transition
disablePortal
style={{ zIndex: 100 }}
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
id='menu-list-grow'
style={{
zIndex: 1001,
transformOrigin:
placement === "bottom" ? "center top" : "center bottom"
}}
>
<Paper>
<ClickAwayListener
onClickAway={event => this.handleClose(event)}
>
<MenuList>
<MenuItem onClick={event => this.handleClose(event)}>
Profile
</MenuItem>
<MenuItem onClick={event => this.handleClose(event)}>
My account
</MenuItem>
<MenuItem onClick={event => this.handleClose(event)}>
Logout
</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
);
}

Resources