How to access child ref from parent without fowardRef - reactjs

I have a ref which works on the parent component, but i need this ref on the child component.
const divRef = React.useRef<any>();
However props.ref is showing undefined on the commentListContainer. What should i pass on the commentListContainer ?
I saw that forwardRef could be used in a situation like this, but im unsure how this fowardRef would work using hooks in a typescript manner.
PostItemContainer.tsx
import React, { Fragment, useState, useRef } from "react";
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
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 { toast, ToastContainer } from "react-toastify";
import OurLink from "../../../common/OurLink";
import CommentForm from "../comment/CommentForm";
import CommentList from "../commentList/CommentList";
import OurModal from "../../../common/OurModal";
import "react-toastify/dist/ReactToastify.css";
function PostItemContainer(props: any) {
const [openModal, setOpenModal] = useState(false);
const [openForm, setOpenForm] = useState(false);
const [comment_body, setCommentBody] = useState("");
const [gifUrl, setGifUrl] = useState("");
const divRef = React.useRef<any>();
const writeComment = () => {
// this is the same as this.setState({ openForm: !this.state.open })
setOpenForm(!openForm);
};
const commentChange = (comment) => {
setGifUrl("");
setCommentBody(comment);
};
const selectGif = (e) => {
setGifUrl(e.images.downsized_large.url);
setCommentBody("");
// you wont be able to add text comment with a gif, it will look weird :(
};
const handleClickOpen = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const commentSubmit = (e: any, id: number) => {
e.preventDefault();
const formData = {
comment_body,
id,
gifUrl,
};
props.postComment(formData);
setCommentBody("");
setOpenForm(false);
console.log(divRef);
window.scrollTo(0, divRef.current.offsetTop);
};
const { post, currentUser, getNotifications } = props;
return (
<Fragment>
{getNotifications && <ToastContainer autoClose={1000} position={toast.POSITION.BOTTOM_RIGHT} />}
<Grid item={true} sm={12} md={12} style={{ margin: "20px 0px" }}>
<Paper style={{ padding: "20px" }}>
<Typography variant="h5" align="left">
<OurLink
style={{ fontSize: "16px" }}
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, 50)}</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">
<OurLink
to={{
pathname: `/profile/${post.author.username}`,
state: { post },
}}
title={post.author.username}
/>
</Typography>
<Typography align="right">Likes: {post.likeCounts}</Typography>
<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, post.userId)}>
<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">
{Object.entries(currentUser).length === 0 ? (
<Fragment>
<span onClick={handleClickOpen}>
<FavoriteBorderIcon style={{ color: "red", cursor: "pointer" }} />
</span>
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
{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>
)}
</Fragment>
)}
</Typography>
</Grid>
</Grid>
<Typography variant="h6" align="left">
{moment(post.createdAt).calendar()}
</Typography>
<Grid item={true} sm={12} lg={12} style={{ paddingTop: "40px" }}>
{Object.entries(currentUser).length === 0 ? (
<Fragment>
<Button onClick={handleClickOpen} variant="outlined" component="span" color="primary">
{"Write A Comment"}
</Button>
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
<Button onClick={writeComment} variant="outlined" component="span" color="primary">
{openForm ? "Close" : "Write A Comment"}
</Button>
</Fragment>
)}
{openForm ? (
<CommentForm
commentChange={(e: any) => commentChange(e.target.value)}
comment_body={comment_body}
onSubmit={(e) => commentSubmit(e, post.id)}
gifUrl={selectGif}
isGif={gifUrl}
/>
) : null}
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
<CommentList ref={divRef} user={currentUser} deleteComment={props.deleteComment} userId={post.userId} postId={post.id} comments={post.Comments} {...props} />
{/* 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>
)}
// <div ref={divRef}></div> works here
</Grid>
</Paper>
</Grid>
</Fragment>
);
}
export default PostItemContainer;
commentListContainer.tsx
import React from "react";
import List from "#material-ui/core/List";
import Typography from "#material-ui/core/Typography";
import CommentItem from "./../commentItem/CommentItem";
import moment from "moment";
import CommentAuthorData from "../commentAuthorData/commentAuthorData";
const ourStyle = {
margin: "15px",
};
const CommentListContainer = (props) => {
const { comment, openModal, handleClickOpen, handleCloseModal, isBold } = props;
return (
<List style={{ paddingBottom: "20px" }}>
<CommentAuthorData {...props} comment={comment} openModal={openModal} handleClickOpen={handleClickOpen} handleCloseModal={handleCloseModal} isBold={isBold} />
{/* want to call ref here but it returns undefined */}
<div ref={props.ref} style={ourStyle}>
<CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">
{moment(comment.createdAt).calendar()}
</Typography>
</div>
</List>
);
};
export default CommentListContainer;

According to the docs: https://reactjs.org/docs/forwarding-refs.html you should use forwardRef for this:
// I used any for props, feel free to replace with your Props interface
const CommentListContainer: React.ForwardRefRenderFunction <HTMLDivElement, any> = (props, ref) => {
const { comment, openModal, handleClickOpen, handleCloseModal, isBold } = props;
return (
<List style={{ paddingBottom: "20px" }}>
<CommentAuthorData {...props} comment={comment} openModal={openModal} handleClickOpen={handleClickOpen} handleCloseModal={handleCloseModal} isBold={isBold} />
{/* here you pass your ref */}
<div ref={ref} style={ourStyle}>
<CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">
{moment(comment.createdAt).calendar()}
</Typography>
</div>
</List>
);
};
// you use forwardRef here
export default React.forwardRef(CommentListContainer);
Here is the relevant TS definition for this: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L564
Your parent should remain unchanged.

Related

the drag and drop feature doesnt work like it supposed to

I havent used react this deeply before and i am onfused on what to be done i am not able to figure out why the drag and drop feature is not working as it should please help , i was doing a follow along tutorial and this is exactly what he did and was able to drag and drop the slides and i have checked the entire code and wasnt able to fighure out what was it that i was doing wrong please help
import React, { useState, useEffect } from "react";
import CropOriginalIcon from "#material-ui/icons/CropOriginal";
import Select from "#material-ui/core/Select";
import Switch from "#material-ui/core/Switch";
import CheckBoxIcon from "#material-ui/icons/CheckBox";
import ShortTextIcon from "#material-ui/icons/ShortText";
import SubjectIcon from "#material-ui/icons/Subject";
import MoreVertIcon from "#material-ui/icons/MoreVert";
import { BsTrash } from "react-icons/bs";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import DragIndicatorIcon from "#material-ui/icons/DragIndicator";
import { Icon, IconButton, MenuItem, Typography } from "#material-ui/core";
import FilterNoneIcon from "#material-ui/icons/FilterNone";
import AddCircleOutlineIcon from "#material-ui/icons/AddCircleOutline";
import OndemandVideoIcon from "#material-ui/icons/OndemandVideo";
import TextFieldsIcon from "#material-ui/icons/TextFields";
import { BsFileText } from "react-icons/bs";
import Accordion from "#material-ui/core/Accordion";
import AccordionSummary from "#material-ui/core/AccordionSummary";
import AccordionDetails from "#material-ui/core/AccordionDetails";
import Button from "#material-ui/core/Button";
import { FcRightUp } from "react-icons/fc";
import Closelcon from "#material-ui/icons/Close";
import Radio from "#material-ui/core/Radio";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import "./Question_form.css";
function Question_form() {
const [questions, setQuestions] = useState([
{
questionText: "Which is the capital city of Karnataka??",
questionType: "radio",
options: [
{ optionText: "Bengaluru" },
{ optionText: "Mandya" },
{ optionText: "Hubli" },
{ optionText: "Mandya" },
],
open: true,
required: false,
},
]);
function changeQuestion(text, i) {
var newQuestion = [...questions];
newQuestion[i].questionText = text;
setQuestions(newQuestion);
console.log(newQuestion);
}
function changeOptionValue(text, i, j) {
var optionsQuestion = [...questions];
optionsQuestion[i].options[j].optionText = text;
setQuestions(optionsQuestion);
console.log(optionsQuestion);
}
function addQuestionType(i, type) {
let qs = [...questions];
console.log(type);
qs[i].questionType = type;
setQuestions(qs);
}
function removeOption(i, j) {
var RemoveOptionQuestion = [...questions];
if (RemoveOptionQuestion[i].options.length > 1) {
RemoveOptionQuestion[i].options.splice(j, 1);
setQuestions(RemoveOptionQuestion);
console.log(i + "_" + j);
}
}
function addOption(i) {
var optionsOfQuestion = [...questions];
if (optionsOfQuestion[i].options.length < 5) {
optionsOfQuestion[i].options.push({
optionText: "option " + (optionsOfQuestion[i].options.length + 1),
});
} else {
console.log("Max 5 options");
}
setQuestions(optionsOfQuestion);
}
function copyQuestion(i) {
let qs = [...questions];
var newQuestion = qs[i];
setQuestions([...questions, newQuestion]);
}
function deleteQuestion(i) {
let qs = [...questions];
if (questions.length > 1) {
qs.splice(i, 1);
}
setQuestions(qs);
}
function requiredQuestion(i) {
var reqQuestion = [...questions];
reqQuestion[i].required = !reqQuestion[i].required;
console.log(reqQuestion[i].required + " " + i);
setQuestions(reqQuestion);
}
function addMoreQuestionField() {
setQuestions([
...questions,
{
questionText: "Question",
questionType: "radio",
options: [{ optionText: "Option 1" }],
open: true,
required: false,
},
]);
}
function onDragEnd(result) {
if (!result.destination) {
return;
}
var itemgg = [...questions];
const itemF = reorder(
itemgg,
result.source.index,
result.destination.index
);
setQuestions(itemF);
}
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
function questionsUI() {
return questions.map((ques, i) => (
<Draggable key={i} draggableId={i + "id"} index={i}>
{(provided,snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div>
<div style={{ marginBottom: "0px" }}>
<div style={{ marginBottom: "0px", width: "100%" }}>
<DragIndicatorIcon
style={{
transform: "rotate(-90deg)",
color: "#DAE0E2",
position: "relative",
left: "300px",
}}
fontSize="small"
/>
</div>
<div>
<Accordion
className={questions[i].open ? "add border" : ""}
expanded={questions[i].open}
>
<AccordionSummary
aria-controls="panel1a-content"
id="panel1a-header"
elevation={1}
style={{ width: "100%" }}
>
{/* {questions[i].open ? (
<div className="saved_questions">
<Typography
style={{
fontSize: "15px",
fontWeight: "400",
letterSpacing: ".1px",
lineHeight: "24px",
paddingBottom: "8px"
}}
>
{i + 1}.{questions[i].questionText}
</Typography>
{ques.options.map((op, j) => (
<div key={j}>
<div style={{ display: "flex" ,}}>
<FormControlLabel
style={{ marginLeft: "5px", marginBottom: "5px" }}
disabled
control={
<input
type={ques.questionType}
color="primary"
style={{ marginRight: "3px" }}
required={ques.type}
/>
}
label={
<Typography
style={{
fontFamily: "Roboto,Arial,sans-serif",
fontSize: "13px",
fontWeight: "400",
letterSpacing: ".2px",
lineHeight: "20px",
color: "#202124",
}}
>
{ques.options[j].optionText}
</Typography>
}
/>
</div>
</div>
))}
</div>
) :
""
} */}
</AccordionSummary>
<div className="question_boxes">
<AccordionDetails className="add_question">
<div className="add_question_top">
<input
type="text "
className="question"
placeholder="Question"
value={ques.questionText}
onChange={(e) => {
changeQuestion(e.target.value, i);
}}
></input>
<CropOriginalIcon style={{ color: "#5f6368" }} />
<Select
className="select"
style={{ color: "#5f6368", fontSize: "13px" }}
>
<MenuItem
id="text"
value="Text"
onClick={() => {
addQuestionType(i, "text");
}}
>
<SubjectIcon style={{ marginRight: "10px" }} />
Paragraph
</MenuItem>
<MenuItem
id="checkbox"
value="Checkbox"
onClick={() => {
addQuestionType(i, "checkbox");
}}
>
<CheckBoxIcon
style={{
marginRight: "10px",
color: "#70757a",
}}
checked
/>
CheckBoxes
</MenuItem>
<MenuItem
id="radio"
value="Radio"
onClick={() => {
addQuestionType(i, "radio");
}}
>
<Radio
style={{
marginRight: "10px",
color: "#70757a",
}}
checked
/>
Multiple Choice
</MenuItem>
</Select>
</div>
{ques.options.map((op, j) => (
<div className="add_question_body" key={j}>
{ques.questionType !== "text" ? (
<input
type={ques.questionType}
style={{ marginRight: "10px" }}
/>
) : (
<ShortTextIcon style={{ marginRight: "10px" }} />
)}
<div>
<input
type="text"
className="text_input"
placeholder="option"
value={ques.options[j].optionText}
onChange={(e) => {
changeOptionValue(e.target.value, i, j);
}}
></input>
</div>
<CropOriginalIcon style={{ color: "#5f6368" }} />
<IconButton aria-label="delete">
<Closelcon
onClick={() => {
removeOption(i, j);
}}
/>
</IconButton>
</div>
))}
{ques.options.length < 5 ? (
<div className="add_question_body">
<FormControlLabel
disabled
control={
ques.questionText !== "text" ? (
<input
type={ques.questionType}
color="primary"
inputProps={{
"aria-label": "secondary checkbox",
}}
style={{
marginLeft: "10px",
marginRight: "10px",
}}
disabled
/>
) : (
<ShortTextIcon
style={{ marginRight: "10px" }}
/>
)
}
label={
<div>
<input
type="text"
className="text_input "
style={{ fontSize: "13px", width: "60px" }}
placeholder="Add other"
></input>
<Button
size="small"
onClick={() => {
addOption(i);
}}
style={{
textTransform: "none",
color: "#4285f4",
fontSize: "13px",
fontWeight: "600",
}}
>
Add options
</Button>
</div>
}
/>
</div>
) : (
""
)}
<div className="add_footer">
<div className="add_question_bottom_left">
<Button
size="small"
style={{
textTransform: "none",
color: "4285f4",
fontSize: "13px",
fontWeight: "600",
}}
>
<FcRightUp
style={{
border: "2px solid #4285f4",
padding: "2px",
marginRight: "8px",
}}
/>
Answer Key
</Button>
</div>
<div className="add_question_bottom">
<IconButton
aria-label="Copy"
onClick={() => {
copyQuestion(i);
}}
>
<FilterNoneIcon />
</IconButton>
<IconButton
aria-label="delete"
onClick={() => {
deleteQuestion(i);
}}
>
<BsTrash />
</IconButton>
<span
style={{ color: "#5f6368", fontSize: "13px" }}
>
required
</span>
<Switch
name="checkedA"
color="primary"
onClick={() => {
requiredQuestion(i);
}}
value={ques.type}
/>
<IconButton>
<MoreVertIcon />
</IconButton>
</div>
</div>
</AccordionDetails>
<div className="question_edit">
<AddCircleOutlineIcon
onClick={addMoreQuestionField}
className="edit"
/>
<OndemandVideoIcon className="edit" />
<CropOriginalIcon className="edit" />
<TextFieldsIcon className="edit" />
</div>
</div>
</Accordion>
</div>
</div>
</div>
</div>
)}
</Draggable>
));
}
return (
<div>
<div className="question_form">
<br></br>
<div className="section">
<div className="question_title_section">
<div className="question_form_top">
<input
type="text"
className="question_form_top_name"
style={{ color: "black" }}
placeholder="Untitled Document"
></input>
<input
type="text"
className="question_form_top_desc"
placeholder="Form description"
></input>
</div>
</div>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
className="droppable"
{...provided.droppableProps}
ref={provided.innerRef}
>
{questionsUI()}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
</div>
)
}
export default Question_form;
/*here i am not able to drag and drop the element please help */

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

Get All TextField values from loop in Next.js when I press Submit button

first of all look at these below screenshots:
There are two tasks which I want to achieve:
There are two questions shown on the page using the array map method, by default I'm showing only one question, and when I press the next part button the second question will appear with the same question and a TextField (multiline). Now I've implemented a word counter in TextField but when I type something in 1st question the counter works properly. But when I go to the next question the here counter shows the previous question's word counter value, I want them to separately work for both questions.
When I click on the next part and again when I click on the previous part then the values from TextField are removed automatically. I want the values there if I navigate to the previous and next part questions. Also, I want to get both TextField values for a form submission when I press the Submit Test button.
Below are my codes for this page. I'm using Next.js and MUI
import { Grid, Typography, Box, NoSsr, TextField } from '#mui/material';
import PersonIcon from '#mui/icons-material/Person';
import Timer from '../../../components/timer';
import Button from '#mui/material/Button';
import { useState } from 'react';
import ArrowForwardIosIcon from '#mui/icons-material/ArrowForwardIos';
import { Scrollbars } from 'react-custom-scrollbars';
import AppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import ArrowBackIosIcon from '#mui/icons-material/ArrowBackIos';
import axios from '../../../lib/axios';
import { decode } from 'html-entities';
import { blueGrey } from '#mui/material/colors';
export default function Writing({ questions }) {
const [show, setShow] = useState(false);
const [show1, setShow1] = useState(true);
const [showQuestionCounter, setShowQuestionCounter] = useState(0);
const [wordsCount, setWordsCount] = useState(0);
return (
<>
<Box sx={{ flexGrow: 1 }}>
<AppBar position="fixed" style={{ background: blueGrey[900] }}>
<Toolbar>
<Grid container spacing={2} alignItems="center">
<Grid item xs={4} display="flex" alignItems="center">
<PersonIcon
sx={{ background: '#f2f2f2', borderRadius: '50px' }}
/>
<Typography variant="h6" color="#f2f2f2" ml={1}>
xxxxx xxxxx-1234
</Typography>
</Grid>
<Grid item xs={4} container justifyContent="center">
<Timer timeValue={2400} />
</Grid>
<Grid item xs={4} container justifyContent={'right'}>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Settings
</Button>
<Button
variant="contained"
style={{
background: 'white',
color: 'black',
margin: '0px 10px',
}}
size="small">
Hide
</Button>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Help
</Button>
</Grid>
</Grid>
</Toolbar>
</AppBar>
</Box>
<Box
sx={{
background: blueGrey[50],
height: '100%',
width: '100%',
position: 'absolute',
}}
pt={{ xs: 13, sm: 11, md: 10, lg: 11, xl: 11 }}>
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Box
key={question.id}
px={3}
sx={{ background: '#f2f2f2', pb: 4 }}
position={{
xs: 'sticky',
sm: 'sticky',
lg: 'initial',
md: 'initial',
xl: 'initial',
}}>
<Box
style={{ background: '#f7fcff', borderRadius: '4px' }}
py={1}
px={2}>
<Box>
<Typography variant="h6" component="h6" ml={1}>
Part {question.id}
</Typography>
<Typography variant="subtitle2" component="div" ml={1} mt={1}>
<NoSsr>
<div
dangerouslySetInnerHTML={{
__html: decode(question.questions[0].question, {
level: 'html5',
}),
}}></div>
</NoSsr>
</Typography>
</Box>
</Box>
<Box
style={{
background: '#f7fcff',
borderRadius: '4px',
marginBottom: '75px',
}}
pt={1}
px={3}
mt={{ xs: 2, sm: 2, md: 2, lg: 0, xl: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} lg={6} md={6} xl={6}>
<Box
py={{ lg: 1, md: 1, xl: 1 }}
style={{ height: '50vh' }}>
<Scrollbars universal>
<Typography
variant="body1"
component="div"
style={{ textAlign: 'justify' }}
mr={2}>
<NoSsr>
<div
dangerouslySetInnerHTML={{
__html: decode(question.question_text, {
level: 'html5',
}),
}}></div>
</NoSsr>
</Typography>
</Scrollbars>
</Box>
</Grid>
<Grid
item
xs={12}
sm={12}
lg={6}
md={6}
xl={6}
mt={{ md: 4, lg: 4, xl: 4 }}>
<TextField
id={`${question.id}`}
label="Type your answer here"
multiline
name={`answer_${question.id}`}
rows={12}
variant="outlined"
fullWidth
helperText={`Words Count: ${wordsCount}`}
onChange={(e) => {
setWordsCount(
e.target.value.trim().split(/\s+/).length
);
}}
/>
</Grid>
</Grid>
</Box>
</Box>
) : null
)}
<Box sx={{ position: 'fixed', width: '100%', left: 0, bottom: 0 }}>
<Grid
container
style={{ background: blueGrey[300], display: 'flex' }}
py={2}
px={3}>
<Grid
item
xs={3}
sm={3}
lg={6}
md={6}
xl={6}
container
justifyContent={'start'}>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Save Draft
</Button>
</Grid>
<Grid
item
xs={9}
sm={9}
lg={6}
md={6}
xl={6}
container
justifyContent={'end'}>
<Button
variant="contained"
size="small"
style={{
background: 'white',
color: 'black',
visibility: show1 ? 'visible' : 'hidden',
}}
endIcon={<ArrowForwardIosIcon />}
onClick={() => {
setShow((prev) => !prev);
setShowQuestionCounter(showQuestionCounter + 1);
setShow1((s) => !s);
}}>
Next Part
</Button>
{show && (
<>
<Box>
<Button
variant="contained"
style={{
background: 'white',
color: 'black',
margin: '0 10px',
visibility: show ? 'visible' : 'hidden',
}}
startIcon={<ArrowBackIosIcon />}
size="small"
onClick={() => {
setShow1((s) => !s);
setShowQuestionCounter(showQuestionCounter - 1);
setShow((prev) => !prev);
}}>
previous Part
</Button>
<Button variant="contained" color="success">
Submit Test
</Button>
</Box>
</>
)}
</Grid>
</Grid>
</Box>
</Box>
</>
);
}
export async function getServerSideProps(context) {
const { id } = context.query;
const token = context.req.cookies.token;
if (!token) {
context.res.writeHead(302, {
Location: '/',
});
context.res.end();
}
const res = await axios.get(`test/${id}/questions`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (res.data.success) {
return {
props: {
questions: res.data.data.questions,
},
};
}
}
The wordsCount state is shared between both questions, which means that when you go to the next question the state remains unchanged and shows the wordsCount from the first question. To solve it, each question needs to have it's own state which you can do by creating a Question component and mapping over it:
export default function Question({ question }) {
const [wordsCount, setWordsCount] = useState(0)
return (
<Box
// ...
>
{/* ... */}
<TextField
// ...
helperText={`${wordsCount} words`}
onChange={(e) => {
setWordsCount(e.target.value.trim().split(/\s+/).length)
}}
/>
{/* ... */}
</Box>
)
}
Then map over it:
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Question key={question.id} question={question} />
) : null
)}
Currently, the value of TextField gets reset you the component is unmounted (i.e. when you go to the next question). You need to make the TextField component a controlled component, meaning that you store the value of the field in useState. And if you need to submit the value of TextField later, then you probably need to store the values in the parent component:
export default function Writing({ questions }) {
// ...
const [answers, setAnswers] = useState([])
function handleChange(id, answer) {
// copy the current answers
let newAnswers = [...answers]
// find the index of the id of the answer in the current answers
const index = newAnswers.findIndex((item) => item.id === id)
// if the answer does exist, replace the previous answer with the new one, else add the new answer
if (index) {
newAnswers[index] = { id, answer }
setAnswers(newAnswers)
} else {
setAnswers([...answers, { id, answer }])
}
}
return (
<>
{/* ... */}
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Question
key={question.id}
question={question}
value={answers.find((item) => item.id === question.id)?.answer || ''}
handleChange={handleChange}
/>
) : null
)}
</>
}
In the Question component, add the handler.
export default function Question({ question, value, handleInputChange }) {
const [wordsCount, setWordsCount] = useState(0)
return (
<Box>
{/* ... */}
<TextField
helperText={`${wordsCount} words`}
value={value}
onChange={(e) => {
handleInputChange(question.id, e.target.value)
setWordsCount(e.target.value.trim().split(/\s+/).length)
}}
/>
{/* ... */}
</Box>
)
}
In the parent component (Writing) you should be able to use the values for form submission.

AutoComplete: get the id for the user that i was choosen from users list, post request with status 400

I have this project and as it is clear in the postman, I have a request and through the request I must send the invoiceId to the invoice and the user ID must be sent in addition to a message, but the real problem is that I did not know how to get the user ID, the meaning is that when I printed “invoiceID” , "UserID "and "Message", the invoiceID and the message have a value, but UserID is worthless without value "undefined", and the reason is that I have list of the Users, and I must choose one user and then I want to pass this userID for the user that i was choosen and pass it in the "assignToUser" function, and i don't know how to do that.
And in the network I have these errors:
and in Network i have this errors:
enter image description here
how can i solve my problem?
invoiceSlice.js:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
import FuseUtils from "#fuse/utils";
import { getInvoices } from "./invoicesSlice";
export const assignToUser = createAsyncThunk(
"invoicesApp/invoice/assignToUser",
async ({ invoiceId, userId, message }, { dispatch }) => {
console.log("invoiceId, userId, message", invoiceId, userId, message);
const response = await axios
.post(`/invoices/flow/${invoiceId}/approve`, { userId, message })
.catch((error) => {
console.log("error response: ", error);
});
const data = await response.data.data;
console.log("approve invoices: ", data);
dispatch(getInvoices());
return data;
}
);
const invoiceSlice = createSlice({
name: "invoicesApp/invoice",
initialState: null,
reducers: {
resetInvoice: () => null,
newInvoice: {
reducer: (state, action) => action.payload,
prepare: (event) => ({
payload: {
invoice: "",
netAmount: 0,
taxNumber: 0,
grossAmount: 0,
dueDate: "",
issueDate: "",
},
}),
},
},
extraReducers: {
[assignToUser.fulfilled]: (state, action) => action.payload,
},
});
export const { newInvoice, resetInvoice } = invoiceSlice.actions;
export default invoiceSlice.reducer;
approveUser.js:
import { Fragment, useState } from "react";
import { ButtonGroup } from "#material-ui/core";
import React from "react";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
import DialogTitle from "#material-ui/core/DialogTitle";
import useMediaQuery from "#material-ui/core/useMediaQuery";
import { useTheme } from "#material-ui/core/styles";
import { useSnackbar } from "notistack";
import Slide from "#material-ui/core/Slide";
import {
rejectInvoice,
approveInvoice,
assignToUser,
} from "../../store/invoiceSlice";
import { useDispatch, useSelector } from "react-redux";
import TextField from "#mui/material/TextField";
import FlagIcon from "#mui/icons-material/Flag";
import { makeStyles } from "#material-ui/core/styles";
import Autocomplete from "#mui/material/Autocomplete";
import { getUsers } from "../../store/invoiceSlice";
import { useEffect } from "react";
const useStyles = makeStyles((theme) => ({
paper: { padding: "3rem", maxWidth: "990px", minWidth: "300px" },
textStyle: {
paddingLeft: "2rem",
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
font: {
fontSize: "5rem",
},
}));
const GroupButttonApproveStatus = (id) => {
const [dialogOpen, setDialogOpen] = useState(false);
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
const [assignToUserDialog, setAssignToUserDialog] = useState(false);
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const dispatch = useDispatch();
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
const AssignToUserFullScreen = useMediaQuery(theme.breakpoints.down("md"));
const classes = useStyles();
const [users, setUsers] = useState([]);
const [message, setMessage] = useState("");
useEffect(() => {
getUsers().then((response) => {
setUsers(response);
});
}, []);
// confirm
console.log("users: ", users);
const handleAssignToUserDialogOpen = () => {
setAssignToUserDialog(true);
};
const handleAssignToUserDialogClose = () => setAssignToUserDialog(false);
// end assign to user
const handleConfirmDialogClose = () => setConfirmDialogOpen(false);
const handleClickConfirmDialogOpen = () => {
setConfirmDialogOpen(true);
};
//end confirm
const handleDialogClose = () => setDialogOpen(false);
const handleClickOpen = () => {
setDialogOpen(true);
};
const handleClose = () => {
setDialogOpen(false);
};
const rejectInvoiceHandleClick = () => {
enqueueSnackbar(
"Invoice rejected successfully",
{ variant: "error" },
{
anchorOrigin: {
vertical: "top",
horizontal: "right",
},
},
{ TransitionComponent: Slide }
);
};
const approveInvoiceHandleClick = () => {
enqueueSnackbar(
"Invoice approved successfully",
{ variant: "success" },
{
anchorOrigin: {
vertical: "top",
horizontal: "right",
},
},
{ TransitionComponent: Slide }
);
};
return (
<Fragment>
<ButtonGroup size="large">
<Button
onClick={(ev) => {
handleClickConfirmDialogOpen();
}}
>
Approve
</Button>
<Button
onClick={(ev) => {
handleClickOpen();
}}
>
Reject
</Button>
<Button
onClick={(ev) => {
handleAssignToUserDialogOpen();
}}
>
Assign to User to approve
</Button>
</ButtonGroup>
{/* reject Dialog */}
<Dialog
classes={{ paper: classes.paper }}
maxWidth="sm"
fullScreen={fullScreen}
open={dialogOpen}
onClose={handleDialogClose}
>
<DialogTitle style={{ fontWeight: "bold" }}>Reject Invoice</DialogTitle>
<DialogContent>
<div
style={{
backgroundColor: "#F8F9FA",
borderRadius: 10,
padding: "3rem",
}}
>
<DialogContentText>
<FlagIcon
style={{ fontSize: 40, color: "#dc3c24", paddingRight: "1rem" }}
/>
Do you really want to reject this invoice ?
</DialogContentText>
<DialogContentText>
<FlagIcon
style={{ fontSize: 40, color: "#F8F9FA", paddingRight: "1rem" }}
/>
Keep in mind that once the invoice is rejected you won’t be able
to proceed with it.
</DialogContentText>
</div>
</DialogContent>
<DialogActions>
<div style={{ paddingRight: "1rem" }}>
<Button
onClick={handleClose}
style={{ color: "#dc3c24", fontWeight: 500 }}
autoFocus
>
Cancel
</Button>
<Button
onClick={(ev) => {
dispatch(rejectInvoice(id?.id));
rejectInvoiceHandleClick(ev);
handleClose();
}}
style={{ color: "#212529", fontWeight: 500 }}
color="primary"
autoFocus
>
Reject Invoice
</Button>
</div>
</DialogActions>
</Dialog>
{/* End reject Dialog */}
{/* Confirm Dialog */}
<Dialog
classes={{ paper: classes.paper }}
maxWidth="sm"
fullScreen={fullScreen}
open={confirmDialogOpen}
onClose={handleConfirmDialogClose}
>
<DialogTitle style={{ fontWeight: "bold" }}>
Approve Invoice
</DialogTitle>
<DialogContent>
<div
style={{
backgroundColor: "#F8F9FA",
borderRadius: 10,
padding: "3rem",
}}
>
<DialogContentText>Almost ready for payment !</DialogContentText>
<DialogContentText>
By confirming you mark this invoice ready for approval.
</DialogContentText>
</div>
</DialogContent>
<DialogActions>
<div style={{ paddingRight: "1rem" }}>
<Button
onClick={handleConfirmDialogClose}
style={{ color: "#dc3c24", fontWeight: 500 }}
autoFocus
>
Cancel
</Button>
<Button
onClick={(ev) => {
dispatch(approveInvoice(id?.id));
approveInvoiceHandleClick(ev);
handleConfirmDialogClose();
}}
style={{ color: "#212529", fontWeight: 500 }}
color="primary"
autoFocus
>
yes, Confirm
</Button>
</div>
</DialogActions>
</Dialog>
{/* End Confirm Dialog */}
{/* assign to user dialog */}
<Dialog
classes={{ paper: classes.paper }}
maxWidth="sm"
fullScreen={AssignToUserFullScreen}
open={assignToUserDialog}
onClose={handleAssignToUserDialogClose}
>
<DialogTitle style={{ fontWeight: "bold", fontSize: "3rem" }}>
Request approval
</DialogTitle>
<div
style={{
backgroundColor: "#F8F9FA",
borderRadius: 10,
padding: "2rem",
paddingLeft: "2rem",
}}
>
<DialogContentText style={{ fontWeight: 600 }}>
{" "}
<FlagIcon
style={{ fontSize: 40, color: "#aacc00", paddingRight: "1rem" }}
/>
Send an invoice approval request to a team member.
</DialogContentText>
<DialogContentText style={{ paddingLeft: 10 }}>
The assigned member will receive a notification asking them to
approve this invoice. Once they accept, payment is on the way!
</DialogContentText>
</div>
<DialogTitle>Assign a member to approve</DialogTitle>
<DialogContent>
<Autocomplete
id="combo-box-demo"
// value={users || ""}
options={users || []}
getOptionLabel={(option) => option.name || ""}
sx={{ width: 860 }}
renderInput={(params) => (
<TextField
{...params}
placeholder="Search Member"
fullWidth
InputProps={{ ...params.InputProps, style: { fontSize: 17 } }}
InputLabelProps={{ style: { fontSize: 17 } }}
/>
)}
/>
</DialogContent>
<DialogContent style={{ marginTop: "15rem" }}>
<form className={classes.root} noValidate autoComplete="off">
<TextField
value={message}
onChange={(e) => setMessage(e.target.value)}
id="outlined-basic"
variant="outlined"
placeholder="Add a message"
fullWidth
size="medium"
InputProps={{ style: { fontSize: 17 } }}
InputLabelProps={{ style: { fontSize: 17 } }}
/>
</form>
</DialogContent>
<DialogActions>
<div style={{ paddingRight: "1rem" }}>
<Button
onClick={handleAssignToUserDialogClose}
style={{ color: "#dc3c24", fontWeight: 500 }}
autoFocus
>
Cancel
</Button>
<Button
onClick={(ev) => {
dispatch(assignToUser(id?.id, users.id, message));
approveInvoiceHandleClick(ev);
handleAssignToUserDialogClose();
}}
style={{ color: "#212529", fontWeight: 500 }}
color="primary"
autoFocus
>
Assign to approve
</Button>
</div>
</DialogActions>
</Dialog>
</Fragment>
);
};
export default GroupButttonApproveStatus;
invoiceDetails.js:
import React from "react";
import { getInvoice } from "../../store/invoiceSlice";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Grid from "#material-ui/core/Grid";
import TextField from "#material-ui/core/TextField";
import moment from "moment";
import InputAdornment from "#material-ui/core/InputAdornment";
import TodayIcon from "#material-ui/icons/Today";
import { makeStyles } from "#material-ui/core/styles";
import RejectDialog from "./rejectDialog";
import GroupButton from "./groupButttonReviewStatus";
import GroupButttonReviewStatus from "./groupButttonReviewStatus";
import GroupButttonApproveStatus from "./groupButtonApproveStatus";
import GroupButttonPaymentStatus from "./groupButtonPaymentStatus";
import useMediaQuery from "#material-ui/core/useMediaQuery";
import { useTheme } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
},
},
input: {
display: "none",
},
button: {
margin: theme.spacing(1),
// padding: theme.spacing(4),
},
}));
const InvoiceDetails = () => {
const classes = useStyles();
const theme = useTheme();
const routeParams = useParams();
const [invoice, setInvoice] = useState([]);
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const breakpoint = useMediaQuery(theme.breakpoints.down("sm"));
// const defaultLayoutPluginInstance = defaultLayoutPlugin();
useEffect(() => {
getInvoice(routeParams).then((response) => {
setInvoice(response);
});
}, []);
const handleClick = () => {
console.info(`You clicked ${options[selectedIndex]}`);
};
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
console.log("invoice url: ", invoice?.file?.url);
console.log("invoice tara : ", invoice);
const statusGropButton = (status, id) => {
switch (status) {
case "review_pending":
return <GroupButttonReviewStatus id={id} />;
case "approval_pending":
return <GroupButttonApproveStatus id={id} />;
case "payment_pending":
return <GroupButttonPaymentStatus id={id} />;
case "rejected":
return <GroupButton id={id} />;
default:
return;
}
};
return (
<>
<Grid container>
<Grid item xs={7} sm={7}>
{/* pdf viewer */}
<object
// data={invoice?.file?.url}
data="https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea%20Brochure.pdf"
type="application/pdf"
width="100%"
height="100%"
>
<p>
Alternative text - include a link{" "}
<a href="https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea%20Brochure.pdf">
to the PDF!
</a>
</p>
</object>
</Grid>
<Grid item xs={5} sm={5} style={{ padding: "4rem" }}>
<Grid item>
<h1 style={{ fontWeight: "bold" }}>Invoice Details</h1>
</Grid>
<Grid item style={{ marginTop: "3rem", marginBottom: "2rem" }}>
<Grid item style={{ marginBottom: 10 }}>
<h3>From</h3>
</Grid>
<Grid item>
<h3>{invoice?.submittedBy?.name || ""}</h3>
</Grid>
<Grid item>
<h3>{invoice?.submittedBy?.email || ""}</h3>
</Grid>
</Grid>
<Grid item>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Invoice ID</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.id || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Issue Date</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={
moment(moment.utc(invoice.issueDate).toDate())
.local()
.format("YYYY-MM-DD HH:mm:ss") || ""
}
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<TodayIcon />
</InputAdornment>
),
}}
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Due Date</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={
moment(moment.utc(invoice.dueDate).toDate())
.local()
.format("YYYY-MM-DD HH:mm:ss") || ""
}
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<TodayIcon />
</InputAdornment>
),
}}
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Net Amount</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.netAmount || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Tax Number</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.taxNumber || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Gross Amount</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
// label="Size"
id="outlined-size-normal"
value={invoice.grossAmount || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid
container
direction="row"
justifyContent="center"
alignItems="center"
style={{ marginTop: "3rem" }}
>
<Grid item>{statusGropButton(invoice.status, invoice?.id)}</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</>
);
};
export default InvoiceDetails;
and this code:
useEffect(() => {
getUsers().then((response) => {
setUsers(response);
});
}, []);
return:
Based on your code I observed this.
You are already passing invoice?.id in statusGropButton(invoice.status, invoice?.id) method.
You are again expecting id from the input as id?.id in assignToUser(id?.id, users.id, message). Why?
Just assuming, because of that the invoiceId is going as undefined. Try by passing just id instead of id?.id while calling the api functions.
You are opening a Dialogue by using assignToUserDialog flag. While submitting the form, you are doing dispatch(assignToUser(id?.id, users.id, message)); in which users is an array. That is the reason you are getting undefined. I would suggest you maintain the specific user information for which the dialogue is opened and send that specifc user.id to the API call.
invoiceId undefined when send request ,you could check assignToUser

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

Resources