the drag and drop feature doesnt work like it supposed to - reactjs

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 */

Related

How to stop modal from closing when clicking on a select option?

I've made a custom filter for MUI's datagrid, the filter has two select's which allow you to filter by the column and filter type. The selects are quite big and endup outside the modal, when clicking on an option the whole modal closes, how can I prevent this from happening?
I've used this tutorial - Detect click outside React component to detect clicks outside the filter.
The code below shows the filter and I've also made an codesandbox example here - https://codesandbox.io/s/awesome-panka-g92vhn?file=/src/DataGridCustomFilter.js:0-6708
any help would be appreciated
import React, { useState, useEffect, useRef } from "react";
import {
Button,
Stack,
FormControl,
InputLabel,
Select,
MenuItem,
Paper,
Grid,
IconButton,
TextField,
ClickAwayListener
} from "#material-ui/core";
import FilterListIcon from "#mui/icons-material/FilterList";
import AddIcon from "#mui/icons-material/Add";
import CloseIcon from "#mui/icons-material/Close";
import { useForm, useFieldArray, Controller } from "react-hook-form";
import { columns } from "./columns";
const filterTypes = {
string: ["contains", "equals", "starts with", "ends with", "is any of"],
int: ["contains", "equals", "less than", "greater than"]
};
function FilterRow({
len,
setOpen,
field,
control,
columns,
index,
handleRemoveFilter
}) {
return (
<Grid container spacing={0}>
<Grid
item
md={1}
style={{
display: "flex",
alignSelf: "flex-end",
alignItems: "center"
}}
>
<IconButton
size="small"
onClick={() => {
if (len === 1) {
setOpen(false);
} else {
console.log(index, "---");
handleRemoveFilter(index);
}
}}
>
<CloseIcon style={{ fontSize: "20px" }} />
</IconButton>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.column`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Column</InputLabel>
<Select
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<MenuItem value={a.headerName}>{a.headerName}</MenuItem>
);
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={3}>
<Controller
name={`filterForm.${index}.filter`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Filter</InputLabel>
<Select
value={value}
onChange={onChange}
label="Filter"
defaultValue=""
>
{filterTypes.string.map((a) => {
return <MenuItem value={a}>{a}</MenuItem>;
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.value`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl>
<TextField
onChange={onChange}
value={value}
label="Value"
variant="standard"
/>
</FormControl>
)}
/>
{/* )} */}
</Grid>
</Grid>
);
}
function DataGridCustomFilter() {
const { control, handleSubmit } = useForm();
const { fields, append, remove } = useFieldArray({
control,
name: "filterForm"
});
const [open, setOpen] = useState(false);
const onSubmit = (data) => {};
useEffect(() => {
if (fields.length === 0) {
append({
column: "ID",
filter: filterTypes.string[0],
value: ""
});
}
}, [fields]);
const [clickedOutside, setClickedOutside] = useState(false);
const myRef = useRef();
const handleClickOutside = (e) => {
if (myRef.current && !myRef.current.contains(e.target)) {
setClickedOutside(true);
setOpen(!open);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
});
return (
<>
<Button
startIcon={<FilterListIcon />}
size="small"
onClick={() => {
setOpen(!open);
}}
// disabled={isDisabled}
>
FILTERS
</Button>
{open ? (
<div ref={myRef}>
<Paper
style={{
width: 550,
padding: 10,
zIndex: 1300,
position: "absolute",
inset: "0px auto auto 0px",
margin: 0,
display: "block"
// transform: "translate3d(160.556px, 252.222px, 0px)",
}}
variant="elevation"
elevation={5}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={0.5}>
<div style={{ maxHeight: 210, overflow: "scroll" }}>
{fields.map((field, index) => {
return (
<div style={{ paddingBottom: 5 }}>
<FilterRow
len={fields.length}
control={control}
setOpen={setOpen}
field={field}
columns={columns}
handleRemoveFilter={() => remove(index)}
{...{ control, index, field }}
// handleClickAway={handleClickAway}
/>
</div>
);
})}
</div>
<div style={{ marginTop: 10, paddingLeft: 40 }}>
<Stack direction="row" spacing={1}>
<Button size="small" startIcon={<AddIcon />}>
ADD FILTER
</Button>
<Button size="small" type="submit">
{fields.length > 1 ? "APPLY FILTERS" : "APPLY FILTER"}
</Button>
</Stack>
</div>
</Stack>
</form>
</Paper>
</div>
) : null}
</>
);
}
export default DataGridCustomFilter;
So far I've tried MUI's ClickAwayListener and the example above, both seem to give the same result
DataGrid component uses NativeSelect. I have checked your codesandbox and tried replacing Select to NativeSelect and MenuItem to Option. filter is working properly. below is sample code for update.
...
<NativeSelect
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<option value={a.headerName}>{a.headerName}</option >
);
})}
</NativeSelect>
...

How to access child ref from parent without fowardRef

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.

Is there a way to force a React component to rerender a child?

I have a React component <ProductPrices>:
render() {
if (this.props.currentProduct.id !== 'new' && !this.props.currentProductPrices) {
this.props.fetchProductPrices(this.props.currentProduct.id);
}
return (
<div>
<div style={{backgroundColor: 'gray', paddingLeft: '10px', paddingTop: '10px', paddingBottom: '5px'}}>
<h1>Precios</h1>
</div>
<div>
{this.props.currentProductPrices && this.props.currentProductPrices.map((price, index) => {
console.log({detail: price});
return (
<ProductPricesEntry key={price.id} price={price} index={index} />
)
})}
</div>
</div>
)
}
As you can see, <ProductPrices> contains a number of subcomponents <ProductPricesEntry>, which amount is dynamic depending on Redux state variable currentProductPrices:
render() {
console.log({entry: this.props.price});
return (
<div style={{display: 'flex', background: 'white', backgroundColor: 'lightgray', marginTop: '2px', paddingLeft: '10px', paddingTop: '10px', paddingBottom: '5px'}}>
<div className='col-10'>
<div fullWidth>
<h3>{this.props.price.prices_table.name}</h3>
</div>
<div fullWidth style={{display: 'flex'}}>
{this.props.price.current_price?
<div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.props.price.current_price)}</strong></div>:
<div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
}
{this.props.price.due_date?
<div style={{textAlign: 'center'}} className='col-4'><strong>{this.props.price.due_date}</strong></div>:
<div style={{textAlign: 'center', color: 'orange'}} className='col-4'>Ninguno</div>
}
{this.props.price.next_price?
<div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.props.price.next_price)}</strong></div>:
<div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
}
</div>
<div fullWidth style={{display: 'flex'}}>
<div style={{textAlign: 'right'}} className='col-4'>Actual</div>
<div style={{textAlign: 'center'}} className='col-4'>Vigencia</div>
<div style={{textAlign: 'right'}} className='col-4'>Próximo</div>
</div>
</div>
<div className='col-1'>
<IconButton color="primary" aria-label={''.concat('update-price-', this.props.price.id)}>
<i className="zmdi zmdi-edit zmdi-hc-fw" onClick={this.handleUpdateClick} />
</IconButton>
<Dialog fullWidth open={this.state.updateDialogOpen} arialabelledby={''.concat('update-price-', this.props.price.id)}>
<DialogTitle id={"".concat("update-price-", this.props.price.id)}>Actualizar Precio</DialogTitle>
<DialogContentText>
<div style={{paddingLeft: '25px'}}><h2>{this.props.currentProductData.name}</h2></div>
<div style={{paddingLeft: '25px'}}><h2>{this.props.price.prices_table.name}</h2></div>
</DialogContentText>
<DialogContent>
<FormControl fullWidth>
<InputLabel htmlFor="newPrice">Precio</InputLabel>
<Input
type="number"
id="newPrice"
name="newPrice"
value={this.state.newPrice}
onChange={this.handleChange}
startAdornment={<InputAdornment position="start">$</InputAdornment>}
/>
</FormControl>
<div fullWidth><TextField fullWidth label="Fecha" type="date" name="newDate" value={this.state.newDate} onChange={this.handleChange} /></div>
</DialogContent>
<DialogActions>
<Button onClick={this.handleDialogAcceptClick} name="accept" color="primary">
Aceptar
</Button>
<Button onClick={this.handleDialogCancelClick} name="cancel" color="secondary">
Cancelar
</Button>
</DialogActions>
</Dialog>
</div>
</div>
)
}
}
I have put console.log() statements right before calling <ProductPricesEntry> from <ProductPrices>, and inside <ProductPricesEntry> when rendering, and I can see that both console.log() statements are reached the first time, but the one inside <ProductPricesEntry> is not reached if this.props.currentProductPrices changes:
This is the value of this.props.currentPrices that is different, and I can see the change on Redux Tools:
The problem is that console.log() statement inside <ProductPricesEntry> is never reached, which means that it does not rerenders, in despite that the Redux state value that changed is sent to the component as a props, and displayed inside.
I guess I am doing something wrong, but I can't find it.
EDIT
This is the reducer that changes the state that must cause the rerendering:
case UPDATE_PRODUCT_PRICE_SUCCESS: {
if (state.currentPricesTable) {
let currentPricesTableProducts = [...state.currentPricesTableProducts];
let updatedProductIndex = currentPricesTableProducts.findIndex(product => product.id === action.payload.productPrice.id)
currentPricesTableProducts[updatedProductIndex]['next_price'] = action.payload.productPrice.next_price;
currentPricesTableProducts[updatedProductIndex]['due_date'] = action.payload.productPrice.start_date;
return {
...state,
currentPricesTableProducts: [...currentPricesTableProducts],
alert: {type: ALERT_SUCCESS, message: "El precio se actualizó existosamente."},
showMessage: true,
}
} else if (state.currentProduct) {
let currentProductPrices = [...state.currentProductPrices];
let updatedProductPriceIndex = currentProductPrices.findIndex(productPrice => productPrice.prices_table_product === action.payload.productPrice.prices_table_product)
currentProductPrices[updatedProductPriceIndex].next_price = action.payload.productPrice.next_price;
currentProductPrices[updatedProductPriceIndex].due_date = action.payload.productPrice.start_date;
return {
...state,
currentProductPrices: [...currentProductPrices],
alert: {type: ALERT_SUCCESS, message: "El precio se actualizó existosamente."},
showMessage: true,
}
} else {
return {
...state
}
}
}
As you can see, I replace the state variable currentProductPrices whit a brand new array.
I added a console.log() just before returning from the reducer, and I can see that the data is right. I can see the change:
I could never make it work as it was, but I found a wait around the problem, and I am sharing it in case it was useful for anyone.
I basically connected <ProductPricesEntry> to Redux store instead of sending the data as props from the parent.
class ProductPricesEntry extends React.Component {
constructor(props) {
super(props);
this.state = {
updateDialogOpen: false,
newPrice: null,
newDate: null,
price: null,
}
this.price = null;
this.handleUpdateClick = this.handleUpdateClick.bind(this);
this.handleDialogAcceptClick = this.handleDialogAcceptClick.bind(this);
this.handleDialogCancelClick = this.handleDialogCancelClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleUpdateClick = () => {
this.setState({updateDialogOpen: true});
}
handleDialogAcceptClick = () => {
this.props.updateProductPrice(
this.price.prices_table_product,
this.price.prices_table.id,
this.props.currentProductData.id,
this.state.newPrice, this.state.newDate
);
let price = {...this.state.price};
price.due_date = this.state.newDate;
price.next_price = this.state.newPrice;
this.setState({newPrice: null, newDate: null, updateDialogOpen: false, price: price});
}
handleDialogCancelClick () {
this.setState({newPrice: null, newDate: null, updateDialogOpen: null});
}
handleChange (event) {
this.setState({[event.target.name]: event.target.value})
}
render() {
this.price = this.props.currentProductPrices[this.props.index]
return (
<div key={this.props.index} style={{display: 'flex', background: 'white', backgroundColor: 'lightgray', marginTop: '2px', paddingLeft: '10px', paddingTop: '10px', paddingBottom: '5px'}}>
<div className='col-10'>
<div fullWidth>
<h3>{this.price.prices_table.name}</h3>
</div>
<div fullWidth style={{display: 'flex'}}>
{this.price.current_price?
<div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.price.current_price)}</strong></div>:
<div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
}
{this.price.due_date?
<div style={{textAlign: 'center'}} className='col-4'><strong>{this.price.due_date}</strong></div>:
<div style={{textAlign: 'center', color: 'orange'}} className='col-4'>Ninguno</div>
}
{this.price.next_price?
<div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.price.next_price)}</strong></div>:
<div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
}
</div>
<div fullWidth style={{display: 'flex'}}>
<div style={{textAlign: 'right'}} className='col-4'>Actual</div>
<div style={{textAlign: 'center'}} className='col-4'>Vigencia</div>
<div style={{textAlign: 'right'}} className='col-4'>Próximo</div>
</div>
</div>
<div className='col-1'>
<IconButton color="primary" aria-label={''.concat('update-price-', this.price.id)}>
<i className="zmdi zmdi-edit zmdi-hc-fw" onClick={this.handleUpdateClick} />
</IconButton>
<Dialog fullWidth open={this.state.updateDialogOpen} arialabelledby={''.concat('update-price-', this.price.id)}>
<DialogTitle id={"".concat("update-price-", this.price.id)}>Actualizar Precio</DialogTitle>
<DialogContentText>
<div style={{paddingLeft: '25px'}}><h2>{this.props.currentProductData.name}</h2></div>
<div style={{paddingLeft: '25px'}}><h2>{this.price.prices_table.name}</h2></div>
</DialogContentText>
<DialogContent>
<FormControl fullWidth>
<InputLabel htmlFor="newPrice">Precio</InputLabel>
<Input
type="number"
id="newPrice"
name="newPrice"
value={this.state.newPrice}
onChange={this.handleChange}
startAdornment={<InputAdornment position="start">$</InputAdornment>}
/>
</FormControl>
<div fullWidth><TextField fullWidth label="Fecha" type="date" name="newDate" value={this.state.newDate} onChange={this.handleChange} /></div>
</DialogContent>
<DialogActions>
<Button onClick={this.handleDialogAcceptClick} name="accept" color="primary">
Aceptar
</Button>
<Button onClick={this.handleDialogCancelClick} name="cancel" color="secondary">
Cancelar
</Button>
</DialogActions>
</Dialog>
</div>
</div>
)
}
}
const mapStateToProps = ({inventory}) => {
const {
currentProduct,
currentProductData,
currentProductPrices,
} = inventory
return {
currentProduct,
currentProductData,
currentProductPrices,
}
}
export default connect(mapStateToProps, {updateProductPrice}) (ProductPricesEntry);
Now it works as intended.

how to create a file tree explorer/view using react js?

I have an react js application whose landing page(index page) allows the user to create a new folder or new view.
As seen in the screenshot the folders and views are displayed in the tile view format.I would like to change the view to a tree structure similar to the file explorer seen on ide such as visual studio code or eclipse.
My render function for the landing page-
render() {
const formList = localStorage.getItem("FormList") !== null ? JSON.parse(localStorage.getItem("FormList")) : [];
const { openmodal, newfolder, foldername, folderList, formView, alertMessage, alert } = this.state;
const folderid = this.props.history.location.pathname;
let fid = folderid.replace('/folder/', '');
return (
<div className="bacgrounImage">
<AlertFunction type={alert} msg={alertMessage} />
<Container fluid>
<Row>
<Col lg="12"
className="spilt folderheader"
>
<div style={{ paddingTop: '7px', paddingLeft: '3%' }}>
<NavLink to={fid === '/' ? "/formcreate" : '/formcreate/' + fid} >
<button className='butt' onClick={() => {
localStorage.setItem('viewId', null);
}} style={{ float: 'right', width: '14%', height: '43px', marginLeft: '10px' }} onClick={e => this.layoutset(e, null, null)}>
<h6 style={{ fontWeight: '500' }}>Create New View</h6>
</button>
</NavLink>
{newfolder === null &&
<button className='butt' type='button' onClick={this.folderName} style={{ width: '15%%', float: 'right' }} >
<img src={require('./image/newFolder.svg')} />
<span style={{ fontWeight: '500' }}>Create New Folder</span></button>
}
</div>
<div>
<h4>File List</h4>
</div>
</Col>
<Col lg='12' className='newfolder folderList'>
<div style={{ display: newfolder === 2 ? 'block' : 'none', padding: '8px 8px 16px 1px' }}>
<span style={{ padding: '6px 18px', cursor: 'pointer' }} onClick={e => this.backToForm(e)}><i className="fa fa-arrow-left" aria-hidden="true" ></i></span>
</div>
{(folderList !== undefined && folderList.length > 0) &&
folderList.map((val, i) => {
return <div key={i + "create"} className='list' id={val.id} onDoubleClick={() => this.folderClick(val.id, 1)}>
<img src={require('./image/folder.svg')} style={{ width: '48px' }} />
<span>{val.name}</span>
</div>
}
)
}
{(formView !== undefined && formView.length > 0) &&
formView.map((val, i) =>
<div key={i + "create"} className='list' id={val.id} onDoubleClick={() => this.folderClick(val.id, 2)}>
<img src={require('./image/file.svg')} style={{ width: '48px' }} />
<span>{val.name}</span>
</div>)
}
{
(folderList !== undefined && folderList.length === 0 && formView !== undefined && formView.length === 0) &&
<div className='noDataFound'>No data found </div>
}
{newfolder === 1 &&
<div className='list' style={{ backgroundColor: '#d3daff' }}>
<img src={require('./image/folder.svg')} style={{ width: '48px' }} />
<input value={foldername} onChange={e => this.setState({ foldername: e.target.value })} onKeyPress={this.createnewFolder} type='text' style={{ width: '72%', marginLeft: '2px', height: '27px' }} onFocus={this.handleFocus} />
</div>
}
</Col>
<div lg="12">
<Formlisting formList={formList} openmodal={openmodal} toggle={this.toggle} />
</div>
</Row>
</Container>
</div>
);
}
How do i modify the render function to change the view from the tile view to tree structure view as seen in the screenshot.Plz help?

Button/dropdown button disabled by default without specifying disabled

I am trying to add a dropdown button in my react project and its rendering as disabled by default. why is this happening ?
The first dropdown is working fine, i called the same dropdown in the navbar after this one and it renders as disabled. I tried other things as well, like adding a button also would not work for me.
The navbar is diplayed when i get a response from the backend and a different component is rendered (ResultTable)
import React from "react";
import ResultTable from "./ResultTable";
...
import DropdownButton from "react-bootstrap/DropdownButton";
import Dropdown from "react-bootstrap/Dropdown";
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
//more state values
threshold: 0.75
};
this.thresholdChange = this.thresholdChange.bind(this);
}
thresholdChange(input) {
this.setState({
threshold: input
});
}
toProperCase = function (txt) {
return txt.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
};
render() {
const { responseData } = this.state;
return (
<div className="container-flex container-without-scroll wrapper"
style={{
backgroundImage: `url(${bg})`,
width: "100%",
height: "100vh",
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
overFlow:'hidden'
}}
>
<div
className={`container-fluid `}>
{this.state.displayTable ? (
<Navbar
style={{
position: "fixed",
left: "50%",
top: "95%",
transform: "translate(-50%, -90%)",
backgroundColor: 'black'
}}
sticky="bottom"
>
<br />
<Navbar.Collapse className="justify-content-end">
<Button
variant="primary"
disabled={
this.props.initialTransaction &&
this.props.initialTransaction.version == 0 &&
this.props.initialTransaction
? true
: false
}
size="sm"
style={{ color: "#FFF" }}
onClick={this.undoAction}
>
<span className=" fa fa-undo "></span>
Undo
</Button>
<Button
variant="primary"
size="sm"
style={{ color: "#FFF" }}
disabled={
(this.props.initialTransaction &&
this.props.initialTransaction.version) <
(this.props.currentVersion &&
this.props.currentVersion.version)
? false
: true
}
onClick={this.redoAction}
>
<span className=" fa fa-repeat "></span>
Redo
</Button>
<Button
variant="success"
size="sm"
style={{ color: "#FFF" }}
disabled={
this.props.initialTransaction &&
this.props.initialTransaction.version == 0
? true
: false
}
onClick={() =>
this.exportExcel(this.props.initialTransaction)
}
>
<span className=" fa fa-download "></span>
Export
</Button>
<DropdownButton
size="md"
title={this.state.threshold}
>
{this.state.thresholdValues.map(eachValue => {
return (
<Dropdown.Item
key = {Math.random()}
onClick={() => this.thresholdChange(eachValue)}
as="button"
>
{eachValue}
</Dropdown.Item>
);
})}
</DropdownButton>
</Navbar.Collapse>
<br/>
</Navbar>
) : null}
{this.state.displayTable ? null : (
<div
className="col-md-4 col-md-offset-4"
style={{
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
backgroundColor: 'rgba(14, 13, 13, 0.74)'
}}
>
<br />
<div className="row">
<div className="input-group col-md-9">
<div className="input-group-prepend">
<span
style={{ cursor: 'pointer' }}
onClick={this.onFormSubmit}
className="input-group-text"
id="inputGroupFileAddon01"
>
{" "}
Upload{" "}
</span>
</div>
<div className="custom-file">
<input
type="file"
className="custom-file-input"
id="inputGroupFile01"
onChange={this.onChange}
aria-describedby="inputGroupFileAddon01"
/>
<label
className="custom-file-label"
htmlFor="inputGroupFile01"
>
{this.props.active && this.props.active.filename}
</label>
</div>
</div>
<div className="col-md-3">
<DropdownButton
size="md"
id="dropdown-item-button"
title={this.state.threshold}
>
{this.state.thresholdValues.map(eachValue => {
return (
<Dropdown.Item
onClick={() => this.thresholdChange(eachValue)}
as="button"
>
{eachValue}
</Dropdown.Item>
);
})}
</DropdownButton>
</div>
</div>
<br />
</div>
)}
<div >
{this.state.displayTable ? (
<div className = "container-flex" style =
{{overflowY:'scroll', maxHeight:'80vh'}}>
<ResultTable
data={responseData}
afterMerge={params => {
this.afterMerge(params);
}}
/>
</div>
) : null}
</div>
</div>
</div>
);
}
}
// Maps state from store to props
const mapStateToProps = (state, ownProps) => {
return {
};
};
// Maps actions to props
const mapDispatchToProps = dispatch => {
return {
};
};
// Use connect to put them together
export default connect(
mapStateToProps,
mapDispatchToProps
)(MainContainer);

Resources