Material ui Dialog onClick deletes only last index - javascript

So I've created a get request, which shows all the exercises and the options to edit (which will redirect to another url) and delete. I wanted to use material ui's Dialog, so there will be a confirmation before deleting. The problem is that, say I have 3 exercises and want to delete exercise with index 1, when click the on delete button it appears the last index in the element i.e the last exercise, although I want to delete the second. How can I fix that?
And also can I move Dialog in another file for more clear coding?
Exercises.js
const Exercises = () => {
const { categories, deletedCategories, error, isLoading, exercises, open, handleClickOpen, handleClose, deleteExercise } = useFormExercises();
const [showCategories, setShowCategories] = useState(false);
if (isLoading) {
return <div>Loading...</div>
}
if (error) {
return <div>There was an error: {error}</div>
}
return (
<Grid container className='content' >
<Card className='exerciseCard'>
<h1>Exercises</h1>
<FormControlLabel sx={{ width:'500px'}}
label="Show Categories"
control={
<Checkbox
sx={{ml:2}}
checked={showCategories}
onChange={(event) => setShowCategories(event.target.checked)}
/>
}
/>
<Button sx={{pr:6}} variant='fab' href={('/exercises/add')}>
<Add />
</Button>
<Divider />
<Grid className="exerciseContent" >
{exercises.map(exercise => (
<Card>
<tr key={exercise.id}>
<List>
<ListItem
key={exercise.id}
secondaryAction={
<IconButton onClick={handleClickOpen}>
<Delete />
</IconButton>
}>
<Button
sx={{mr:4}}
href={(`/exercises/edit/${exercise.id}`)} >
<Edit />
</Button>
<ListItemText sx={{justifyContent:'center', width:'400px'}}
primary={exercise.exercise_name}
secondary={showCategories ? exercise["exerciseCategories.category_name"] : null}
/>
</ListItem>
<Dialog
open={open}
onClose={handleClose}
style={{ borderColor: 'red' }}
>
<Box sx={{ borderTop: 3, color: 'red' }}>
<DialogTitle sx={{ color: 'black', backgroundColor: 'gainsboro', pl: 11 }}>
Delete Exercise
</DialogTitle>
<DialogContent>
<DialogContentText color='black' >
<Warning fontSize='large' color='error' id='warning' />
Are you sure you want to delete the exercise: {exercise.exercise_name} ?
</DialogContentText>
</DialogContent>
<DialogActions >
<Button variant='contained' onClick={() => deleteExercise(exercise.id)} autoFocus>
Yes
</Button>
<Button variant='outlined' onClick={handleClose}>No</Button>
</DialogActions>
</Box>
</Dialog>
</List>
</tr>
</Card>
))}
</Grid>
</Card>
</Grid>
)
}
export default Exercises
My delete function
useFormExercises.js
...
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
}
const deleteExercise = async (id) => {
try {
await fetch(`${BASE_URL}/exercises/${id}`, {
method: "DELETE",
}).then(response => {
setExercises(exercises.filter(exercise => exercise.id !== id))
return response.json()
})
} catch (error) {
console.log(error)
}
}
And if another thing seems of, please share your opinion!
Thanks in advance!

Related

Problems using a Collapse MaterialUI componente inside an iterableble component

Im having troubles to expand and contract a Collapse Component from MaterialUI since Im mapping and array and iterating the same component, when i press the collapse button, all components expands/contracts at the same time ( I suppose that Im not providing an identifier to point where the collapse function should be used),Im currently Using an State to control the collapse action:
const [expanded, setExpanded] = useState(false);
This is the return where I iterate the component using map on RecetasAll object,
return (
<React.Fragment key={RecetasAll.id}>
<Card className="searchItem" sx={{ maxWidth: 345 }}>
<CardHeader
action={<IconButton aria-label="settings"></IconButton>}
title={RecetasAll.titulo}
/>
<h4
className="Dieta"
style={{
backgroundColor: color(RecetasAll.Tiporeceta.tipoReceta),
}}
>
{RecetasAll.Tiporeceta.tipoReceta}
</h4>
<span className="Calorias">{RecetasAll.informacionNutricional}</span>
<CardMedia
component="img"
height="194"
image={RecetasAll.imagen}
alt="Paella dish"
/>
<CardContent>
{RecetasAll.Productos.map((Productos) => {
return (
<React.Fragment key={Productos.id}>
<Typography variant="body2" color="text.secondary">
{Productos.producto}
</Typography>
</React.Fragment>
);
})}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
<ExpandMore
expand={expanded}
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent id={RecetasAll.id}>
<Typography paragraph>Preparacion:</Typography>
<Typography paragraph>{RecetasAll.pasos}</Typography>
<Button
href="#contained-buttons"
variant="contained"
onClick={handleSearch}
>
Ver mas
</Button>
</CardContent>
</Collapse>
</Card>
</React.Fragment>
);
});
return <>{itemRecetas}</>;
}
Im triying to set an id property to the CardContent since its the child of the Collapse component
id={RecetasAll.id}
this is the function Im using to expand or collapse but I dont know how to get the id properly to compare its value with expanded state:
const handleExpandClick = (e) => {
let clickedItemId = e.currentTarget.id;
if (expanded === clickedItemId) {
setExpanded(!expanded);
} else {
setExpanded(clickedItemId);
}
};
You could refactor every card into a new component and that way you can have a state to open/close the individual card. When iterating you can pass in the RecetasAll.
const MyCard = ({ RecetasAll }) => {
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpanded = () => {
setIsExpanded(prevIsExpanded => !prevIsExpanded);
};
return (
...
<ExpandMore
expand={isExpanded}
onClick={toggleExpanded}
aria-expanded={isExpanded}
>
...
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
...
);
};
If you don't want to use a new component you could store all the ids of the expanded cards in a state. Based on if the id is in the array the card will be expanded or collapsed.
const [expandedIds, setExpandedIds] = useState([]);
const toggleExpanded = (id) => {
setExpandedIds((prevExpandedIds) => {
// if id is already in array remove
if (prevExpandedIds.includes(id))
return prevExpandedIds.filter((i) => i !== id);
// else add to array
return [...prevExpandedIds, id];
});
};
return (
...
<ExpandMore
expand={expandedIds.includes(RecetasAll.id)}
onClick={() => toggleExpanded(RecetasAll.id)}
aria-expanded={expandedIds.includes(RecetasAll.id)}
>
...
<Collapse in={expandedIds.includes(RecetasAll.id)} timeout="auto" unmountOnExit>
...
)

I can't delete a post from my database using a button. React-Graphql

Hey guys I'm trying to delete a post from my database but I'm having issues getting this button to work. I moved the whole function to my feed file, I created a different function and still doesn't work. I really appreciate your help.`
…All the imports
function DeletePostBtn ({ postId }){
const navigate = useNavigate;`
const { _id } = useParams();`
const [deletePost] = useMutation(REMOVE_POST, {
variables: {id: _id },
onCompleted: () => navigate('/'),
refetchQueries: [{ query: QUERY_POSTS }],
});
// remove handler
const removeHandler = async (event) => {
event.preventDefault();
try {
await deletePost({ variables: {_id} });
} catch (e) {
console.error(e);
}
};
return (
<Button onClick={() => {
removeHandler();
alert('clicked');
}}>
<Trash/>
</Button>
);
}
export default DeletePostBtn;
and I'm calling it in my Feed page`
const Feed = ({ posts }) => {
return (
<div className="feed container gx-12">
<div className="feedWrapper row">
<AddPost />
{posts &&
posts.map((post) => (
<Card key={post._id}>
<Card.Header>
<Row>
<Col xs={1}>
<div className="">
<Card.Img
src={Bot}
className="rounded-circle"
style={{ width: '50px' }}
/>
</div>
</Col>
<Col>
<Link
to={`/profile/${post.username}`}
style={{ fontWeight: 750 }}
className="text-start"
>
u/{post.username}
</Link>
</Col>
<Col className="text-end">
<Dropdown>
<Dropdown.Toggle
variant="text-dark"
id="dropdown-basic"
size="lg"
bsPrefix
className="dropdown"
>
<ThreeDots />
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item href="#/action-1">
<Stack direction="horizontal">
<DeletePost />
DELETE
</Stack>
</Dropdown.Item>
<Dropdown.Item href="#/action-2">EDIT</Dropdown.Item>
<Dropdown.Item href="#/action-3">PROFILE</Dropdown.Item>
<Dropdown.Item href="#/action-3">
SUBCODEIT
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</Col>
</Row>
</Card.Header>
<Card.Title>{post.enteredTitle}</Card.Title>
<Card.Body>
<Card.Text>{post.enteredText}</Card.Text>
</Card.Body>
<Card.Footer className="cardFooter">
<div>
Comments: {post.commentCount} || Click to{' '}
{post.commentCount ? 'Add' : 'Start'} comments.
</div>
<div>{post.createdAt}</div>
</Card.Footer>
</Card>
))}
;
</div>
</div>
);
};
Change your component to use the postId parameter instead of useParams():
function DeletePostBtn ({ postId }){
const navigate = useNavigate;`
const [deletePost] = useMutation(REMOVE_POST, {
variables: {id: postId },
onCompleted: () => navigate('/'),
refetchQueries: [{ query: QUERY_POSTS }],
});
…
}
Then pass the postId to the component in in your .map():
<DeletePost postId={post._id}/>

Calling onClick inside map function

I have a custom Input Box and when I type inside a custom input component It'll re-render the typed input inside.
import {
Badge,
Box,
CloseButton,
Grid,
GridItem,
Input,
Text
} from "#chakra-ui/react";
import React, { useEffect, useState } from "react";
function InputTag(props) {
const [tags, setTags] = useState(props.values);
const removeTag = (index) => {
setTags(tags.filter((_, i) => i !== index));
};
const addTag = (event) => {
if (event.target.value !== "") {
setTags([...tags, event.target.value]);
props.setFieldValue("tags", [...tags, event.target.value]);
event.target.value = "";
}
};
useEffect(() => {
props.show === false && setTags([]);
}, [props.show]);
//update values based on click suggestions
useEffect(() => {
setTags([props.values, props.suggTag]);
}, [props.suggTag, props.values]);
return (
<Box
display={"flex"}
border="1px"
borderColor={"gray.200"}
borderRadius={"md"}
padding={2}
>
<Grid templateColumns="repeat(3, 1fr)" gap={2} overflow="visible">
{tags &&
tags.map((tag, index) => (
<GridItem key={index}>
<Badge
variant="solid"
colorScheme={"purple"}
display={"flex"}
borderRadius="full"
justifyContent="space-between"
alignItems="center"
gap={2}
>
<Text>{tag}</Text>
<CloseButton onClick={() => removeTag(index)} />
</Badge>
</GridItem>
))}
</Grid>
<Input
type="text"
name="tags"
id="tags"
variant={"unstyled"}
placeholder="Add Tag"
_placeholder={{ fontsize: "md" }}
onChange={props.handleChange}
onBlur={props.handleBlur}
onError={props.errors}
onKeyUp={(event) =>
event.key === "Enter" ? addTag(event) && event.preventDefault() : null
}
/>
</Box>
);
}
export default InputTag;
Here, when I hit enter It'll render them inside the custom Input Box
I Inserted a custom array of strings as "ex_Tag" inside Previewer.js so that when I click on the word in array, it'll also get rendered inside custom input as well.
function NewUploader({ isOpen, onClose }) {
const cancelRef = useRef();
const ex_tags = ["Design", "Strategy", "Human Centered Design"];
const [show, Setshow] = useState(true);
const [suggTag, setSuggTag] = useState();
const initialValues = {
files: null,
tags: []
};
const validationSchema = yup.object({
files: yup.mixed().required("File is Required"),
tags: yup.mixed().required("tags required")
});
const onSubmit = (values, actions) => {
const formData = new FormData();
formData.append("files", values.files[0]);
formData.append("tags", values.tags);
for (var pair of formData.entries()) {
console.log(pair[0] + ", " + pair[1]);
}
actions.setSubmitting(false);
actions.resetForm();
Setshow(!show);
onClose();
};
const handlethis = (e) => {
e.preventDefault();
};
//insert suggested word to useState so i can pass it to custom input
const handleClick = (tag) => {
setSuggTag(tag);
};
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
{/* update code on model here */}
<ModalOverlay />
<ModalContent>
<ModalHeader>
<Text fontWeight={"bold"} color="gray.900">
Upload Buddy
</Text>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex direction="column" gap={3}>
<Box>
<Text fontWeight={"normal"} color="gray.700">
This learning contentwill not be summarised. to summarize your
content, use{" "}
<Link color={"purple.400"}>Create Knowledge Nugget</Link> option
instead.
</Text>
</Box>
<Box>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
>
{(formik) => (
<Form
onSubmit={handlethis}
autoComplete="off"
encType="multipart/form-data"
>
<FormLabel htmlFor="file">
<Text
fontSize="sm"
fontWeight="normal"
color="gray.900"
fontFamily={"body"}
>
Upload files
</Text>
</FormLabel>
{/* drag droop sec */}
{formik.isSubmitting ? (
<>
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{formik.values.files &&
formik.values.files.map((file, index) => (
<GridItem key={index}>
<Badge
variant="solid"
borderRadius="xl"
colorScheme={"gray"}
w={file.name.length * 4}
h="8"
display="flex"
justifyContent="center"
alignItems="center"
my={2}
>
<Text fontFamily={"body"}>{file.name}</Text>
<CloseButton colorScheme={"blackAlpha"} />
</Badge>
</GridItem>
))}
</Grid>
<Progress colorScheme={"yellow"} isIndeterminate />
</>
) : (
<>
<Dragdrop setFieldValue={formik.setFieldValue} />
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{formik.values.files &&
formik.values.files.map((file, index) => (
<GridItem key={index}>
<Badge
variant="solid"
borderRadius="xl"
colorScheme={"gray"}
w={file.name.length * 4}
h="8"
display="flex"
justifyContent="space-between"
alignItems="center"
my={2}
>
<Text fontFamily={"body"}>{file.name}</Text>
<CloseButton colorScheme={"blackAlpha"} />
</Badge>
</GridItem>
))}
</Grid>
{formik.errors.files && formik.touched.files && (
<Text fontFamily={"body"} color="red">
{formik.errors.files}
</Text>
)}
</>
)}
<FormErrorMessage>
<ErrorMessage name="file" />
</FormErrorMessage>
<FormLabel htmlFor="tags">
<Text
fontSize="sm"
fontWeight="normal"
color="gray.900"
fontFamily={"body"}
>
Tags
</Text>
</FormLabel>
<InputTag
setFieldValue={formik.setFieldValue}
handleChange={formik.handleChange}
handleBlur={formik.handleBlur.call}
values={formik.values.tags}
show={show}
suggTag={suggTag}
/>
{formik.errors.tags && formik.touched.tags && (
<Text fontFamily={"body"} color="red">
{formik.errors.tags}
</Text>
)}
<FormErrorMessage>
<ErrorMessage name="tags" />
</FormErrorMessage>
<Box
aria-invalid="true"
display={"flex"}
flexDir="row"
gap={2}
my={2}
>
<Text fontFamily={"body"}>Suggested</Text>
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{ex_tags.map(
(tag, index) => (
<GridItem key={index}>
//I inserted on click call here
<Box onClick={handleClick(tag)}>
<Badge
variant={"subtle"}
borderRadius="lg"
colorScheme={"gray"}
_hover={{
cursor: "pointer",
bgColor: "gray.200"
}}
>
<Text fontFamily={"body"}>{tag}</Text>
</Badge>
</Box>
</GridItem>
),
this
)}
</Grid>
</Box>
<Box display={"flex"} justifyContent="center" my={3}>
<Button
type="button"
ref={cancelRef}
colorScheme="yellow"
isLoading={formik.isSubmitting}
onClick={formik.handleSubmit}
>
<Text
fontWeight="bold"
fontSize="18px"
color="gray.900"
fontFamily={"body"}
>
Submit
</Text>
</Button>
</Box>
</Form>
)}
</Formik>
</Box>
</Flex>
</ModalBody>
</ModalContent>
</Modal>
);
}
export default NewUploader;
but It seems when I render them to the screen it will come out as I triggered the onClick even though I didn't.
For now I commented out the useEffect func inside input component
I have uploaded it to code sandbox Bellow.
https://codesandbox.io/s/amazing-heyrovsky-9kr0ex?file=/src/Previewer.js

MATERIAL UI MENU ITEM performs action on page load

I have an onClick event listener on a Menu Item in material UI, MUI.
When I load the page, the onclick calls itself automatically. How can I fix this?
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const handleClick = (event) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const handleDelete = async (postId, token) => {
setLoading(true)
const deleted = await deletePost(postId, token)
if (deleted) {
setLoading(false)
toast.success(`Post deleted successfully`)
history.push('/feed')
handleClose()
} else {
toast.error('Something went wrong')
setLoading(false)
handleClose()
}
}
<IconButton
id='demo-positioned-button'
aria-controls={open ? 'demo-positioned-menu' : undefined}
aria-haspopup='true'
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
>
<MoreVertIcon sx={{ color: '#E4E4E4' }} />
</IconButton>
<Menu
id='demo-positioned-menu'
aria-labelledby='demo-positioned-button'
anchorEl={anchorEl}
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<MenuItem onClick={handleDelete(post._id, auth.token)}>
<ListItemIcon>
<DeleteIcon fontSize='small' />
</ListItemIcon>
<ListItemText>Delete</ListItemText>
</MenuItem>
</Menu>
When I load the page, the delete is called automatially and it repeats continuously, (Infinite loop) I have to reload the page.
I tried with simple window.alert('Hello')
It also alerts infinitely until i reload the page.
It's happening because you are calling the handleDelete directly.
<MenuItem onClick={() => handleDelete(post._id, auth.token)}>
<ListItemIcon>
<DeleteIcon fontSize='small' />
</ListItemIcon>
<ListItemText>Delete</ListItemText>
</MenuItem>
Replace handleDelete(post._id, auth.token) with () => handleDelete(post._id, auth.token)
You have to call the handleDelete in an arrow function:
onClick={() => handleDelete(id)}

How do I get it out that only the user can change the node?

You have to Imagine I have a node inside this node are comments(messages) and now I want the user who made the comment to be able to edit or even delete his own comment directly, but only the user who created it.
I honestly don't know if it works that way, but I hope someone can help me with this.
Edit: I add a deleteHandler.
function Messages({ forum }) {
console.log(forum);
const dispatch = useDispatch();
const classes = useStyles();
const [message, setMessage] = useState("");
const messageList = useSelector((state) => state.messageList);
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
const { messages } = messageList;
const handleClick = async () => {
const finalMessage = `${userInfo.userName}: ${message}`;
await dispatch(createMessageAction(forum._id, finalMessage));
dispatch(listMessage());
setMessage("");
};
const deleteHandler = (id) => {
if (window.confirm("Are you sure? you want to delete")) {
dispatch(deleteMessageAction(id));
}
};
useEffect(() => {
dispatch(listMessage());
}, []);
console.log(messages);
return (
<div>
<div className={classes.messagesOuterContainer}>
<div className={classes.messagesInnerContainer}>
<Typography gutterBottom variant="h6">
Comments
</Typography>
{messages
?.filter((message) => message.forumID === forum._id)
?.map((c) => (
<Typography key={c._id} gutterBottom variant="subtitle1">
<strong>{c.messageText} </strong>
<EditIcon />
<IconButton aria-label="delete">
<DeleteIcon onClick={() => deleteHandler(message._id)}/>
</IconButton>
</Typography>
))}
</div>
{userInfo?.userName && (
<div style={{ width: "70%" }}>
<Typography gutterBottom variant="h6">
Comments
</Typography>
<TextField
fullwidth="false"
rows={4}
variant="outlined"
label="add a Comment"
multiline
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<Button
style={{ marginTop: "10px" }}
fullwidth="false"
disabled={!message}
variant="contained"
color="primary"
onClick={handleClick}
>
Send
</Button>
</div>
)}
</div>
</div>
);
My MessageList state:
>messageList
>messages
>0: {_id:"....", forumID:"..", messageText:"...", user:".."
>1: .....
You must have information about the author of the comment. Assuming each entry in messages has that information, create a conditional that checks to see if the current user's id is the same as the message's author id.
Something along the lines of this.
{messages
?.filter((message) => message.forumID === forum._id)
?.map((c) => (
<Typography key={c._id} gutterBottom variant="subtitle1">
<strong>{c.messageText} </strong>
{message.userId === userLogin.id && ( // modify keys names accordingly
<>
<EditIcon />
<IconButton aria-label="delete">
<DeleteIcon />
</IconButton>
</>
)}
</Typography>
))}

Categories