reactjs pop up opened and closed while re-rendering the parent component - reactjs

I am trying to simulate the Trello board, with reactjs and signalR (.netCore).
I created the board with list of cards and each card item has card details which is a pop up.
that card details pop up opens when the user clicks on the card title. I am using signalR (Websocket) to make multiusers interact within the same board by listening to an event which re-render the whole board when any user change a card.
The issue happens when :
User A is Opening the pop up modal for task 1 and user B is dragging task 2 from todo card list to the in progress card list for example.
when that action happened the pop up close and open again as the whole trello board was rerendered based on the event that each user is listening to.
How can I fix that opening closing issue with the pop up an keep it open as it's ??
and if the logic is wrong please feel free to inform me!
note: I am using react-beautiful-dnd package for the drag and drop feature.
Trello Board
<DragDropContext
onDragEnd={(result) => {
ondragend(result);
}}
>
<div className="App">
<div>
{
sprintLoading ?
sprints.map((card, index) => (
// our sprints
<TrelloList
sprintType={card.sprintType}
index={index}
listId={card.sprintId}
sprintSchemaId={card.sprintSchemaId}
key={card.sprintId}
title={card.sprintName}
cards={card.tasks}
/>
))
: null
}
</DragDropContext >
);
}
Trello List
useEffect(async () => {
}, [tasks, socketState, trelloRerender]);
return (
<Droppable droppableId={String(listId)} >
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={styles.container}>
<h4>{title}</h4>
{cards ? cards.map((card, index) =>
(
// our tasks
<TrelloCard
sprintType={sprintType}
listId={listId}
index={index}
cardId={card.taskId}
key={card.taskId}
text={card.taskDescription}
projectId={card.projectId}
userId={card.userId}
sprintId={card.sprintId}
sprintSchemaId={card.sprintSchemaId}
title={card.taskName}
schemaId={card.sprintSchemaId}
groupId={card.groupId}
taskStatus={card.taskStatus}
userName={card.user ? card.user.userName : ''}
taskCreatedDate={card.taskCreatedDate}
taskDueDate={card.taskDueDate}
comments={card.comments}
/>
)) : null}
{provided.placeholder}
</div>
)}
</Droppable>
)
};
Trello card
const { showTaskDetails, setShowTaskDetails } = useContext(ShowTaskDetailsContext);
useEffect(async () => {
await axios.get(`${BACKEND_URL}/tasks/${cardId}`).then(res => {
console.log(res.data.comments);
setCurrentTaskComments(res.data.comments)
console.log(currentTaskComments);
});
}, [rerenderForComments]);
useEffect(async () => {
const sprintId = location.pathname.split('/')[2];
setRerenderforCards(false);
setSprintId(sprintId);
setIsRolledback(false);
await fetchObject(`${BACKEND_URL}/sprintschemas/${sprintSchemaId}`)
.then(res => {
if (res.sprintTaskRollback !== null) {
setIsRolledback(true);
}
})
.catch(err => console.log(err));
}, []);
const handleShowTaskDetails = () => setShowTaskDetails(true);
const handleCloseTaskDetails = () => setShowTaskDetails(false);
return (
<Draggable draggableId={String(cardId)} index={index} key={cardId} >
{provided => (
// as provided.innerdiv need to refer to an action DOM node and card is a material-ui node
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps} >
<Card style={styles.cardContainer} >
{/* testing generic hooks */}
<CardContent>
<Typography component={'div'}
gutterBottom>
<span
// href={`/loboard/${sprintId}`}
style={{ display: "block", fontWeight: "bold" }}
onClick={handleShowTaskDetails}
>
{title}
</span>
<TrelloCardDetails
showTaskDetails={showTaskDetails}
handleCloseTaskDetails={handleCloseTaskDetails}
title={title}
description={text}
comments={currentTaskComments}
assignto={userName}
cardId={cardId}
handleDropDownchanged={handleDropDownchanged}
handleMarkCompleted={handleMarkCompleted}
markCompletedBtnTxt={markCompletedBtnTxt}
markBtnStyle={markBtnStyle}
userId={userId}
setComments={setCurrentTaskComments}
rerenderForComments={rerenderForComments}
setRerenderForcommented={setRerenderForcommented}
/>
{/* onchange == move task to it's new list of cards */}
<Form.Group className="mt-2" controlId="exampleForm.ControlSelect2">
<Form.Row>
<Form.Label column="sm" sm={4}>Move to:</Form.Label>
<Col>
{/* adding default value to reset */}
<Form.Control size="sm" as="select"
onChange={handleDropDownchanged}
defaultValue={'select'}
>
<option disabled value="select">select status</option>
<option value="backlog" name='setBacklog'>backlog</option>
<option value="todo" name='setTodo'>todo</option>
<option value="inprogress" name='setInProgress'>inprogress</option>
<option value="done" name='setInProgress'>done</option>
</Form.Control>
</Col>
</Form.Row>
</Form.Group>
<Form.Group>
<Form.Row>
<Form.Label column="sm" sm={4}>assigned to:</Form.Label>
<Col>
<Form.Control
type="text"
disabled
value={userName} />
</Col>
</Form.Row>
</Form.Group>
<Row>
<Button className={markBtnStyle}
onClick=
{() =>
handleMarkCompleted(cardId)
}
style={{ display: "block" }}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" className="bi bi-check" viewBox="0 0 16 16">
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z" />
</svg>
{markCompletedBtnTxt}
</Button>
</Row>
{/* <Row>
<Col> */}
<span className="text-sm float-right mt-2">
<small title="created at" className="text-muted d-block ">
<BsCalendar /> {taskCreatedDate.split('T')[0]}
</small>
</span>
</Typography>
</CardContent>
</Card>
</div>
)}
</Draggable>
)
Card details component
export const TrelloCardDetails = ({
showTaskDetails,
description,
comments,
title,
handleCloseTaskDetails
, assignto, cardId,
handleDropDownchanged,
handleMarkCompleted,
markBtnStyle,
markCompletedBtnTxt,
userId,
setComments, rerenderForComments,
setRerenderForcommented
}) => {
useEffect(() => {
console.log("card details")
}, [comments]);
return (
<div>
<Modal
show={showTaskDetails}
onHide={handleCloseTaskDetails}>
<Modal.Header closeButton>
<Modal.Title>{title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Group className="mt-2" controlId="exampleForm.ControlSelect2">
<Form.Row>
<Form.Label column="sm" sm={4}>Move to:</Form.Label>
<Col>
{/* adding default value to reset */}
<Form.Control size="sm" as="select"
onChange={handleDropDownchanged}
defaultValue={'select'}
>
<option disabled value="select">select status</option>
<option value="backlog" name='setBacklog'>backlog</option>
<option value="todo" name='setTodo'>todo</option>
<option value="inprogress" name='setInProgress'>inprogress</option>
<option value="done" name='setInProgress'>done</option>
</Form.Control>
</Col>
</Form.Row>
</Form.Group>
<Form.Group>
<Form.Row>
<Form.Label column="sm" sm={4}>assigned to:</Form.Label>
<Col>
<Form.Control
type="text"
disabled
value={assignto} />
</Col>
</Form.Row>
</Form.Group>
<Form.Group>
<Form.Row>
<Form.Label column="sm" sm={4}>Description: </Form.Label>
<Col>
<Form.Control style={{ resize: 'none' }} as="textarea" value={description} disabled />
</Col>
</Form.Row>
</Form.Group>
<Row>
<Button
className={markBtnStyle}
onClick=
{() =>
handleMarkCompleted(cardId)
}
style={{ display: "block" }}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" className="bi bi-check" viewBox="0 0 16 16">
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z" />
</svg>
{markCompletedBtnTxt}
</Button>
</Row>
{/* <div style={{marginTop:"20px", backgroundColor: "#e9ecef", opacity: "1", border: "1px solid #ced4da", borderRadius: "5px" }}> */}
<CommentList rerenderForComments={rerenderForComments} setRerenderForcommented={setRerenderForcommented} assignto={assignto} comments={comments} setComments={setComments} taskId={cardId} userId={userId} />
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleCloseTaskDetails}>
Close
</Button>
<Button variant="primary" onClick={handleCloseTaskDetails}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</div >
);
}

Related

Reacht 3 different cards next to each other

I am new in React and have a problem with my cards here. I want to show 3 cards next to each other but I get 3 same cards. In bootstrap react I Found this loop to show cards next to each other but because I am looping my cars in const cards I get now 3 same cards next to each other.
I have tried using React bootstrap cards but none of them worked.
If anyone knows how I can fix my issue it would be a great help!
Here is my code:
const cards = hasLoaded ? (
<>
{cars.results.length ? (
<InfiniteScroll
children={cars.results.map((car) => (
<Car2 key={car.id} {...car} setCars={setCars} />
))}
dataLength={cars.results.length}
loader={<Asset spinner />}
hasMore={!!cars.next}
next={() => fetchMoreData(cars, setCars)}
/>
) : (
<Container className={appStyles.Content}>
<Asset src={NoResults} message={message} />
</Container>
)}
</>
) : (
<Container className={appStyles.Content}>
<Asset spinner />
</Container>
);
return (
<Container>
<Row className="h-100">
<Col className="py-2 p-0 p-lg-2" lg={8}>
<PopularProfiles mobile />
<i className={`fas fa-search ${styles.SearchIcon}`} />
<Form
className={styles.SearchBar}
onSubmit={(event) => event.preventDefault()}
>
<Form.Control
value={query}
onChange={(event) => setQuery(event.target.value)}
type="text"
className="mr-sm-2"
placeholder="Search posts"
/>
</Form>
</Col>
</Row>
<Row>
{Array.from({ length: 3 }).map((_, idx) => (
<Col md={3}>{cards}</Col>
))}
<Col md={3} className="d-none d-lg-block p-0 p-lg-2">
<PopularProfiles />
</Col>
</Row>
</Container>
);

Antd dynamic form - how can I change form elements based on current values?

I have a dynamic Antd form where I can enter menu elements. The user can add, remove and reorder menu elements. The dynamic fields work fine, but I have a radio button for each dynamic element indicating that the corresponding menu element is an external URL or an internal page. I need to display different input elements if the user chooses URL or internal page. I tried the method listed in the Antd documentation, but it is for static fields and it doesn't seem to be working for dynamic elements.
My code so far is:
<Form.List name={"menuTree"}>
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Row id={"menu-row-" + index} key={index}>
<Col span={8}>
<Form.Item label="Title" name={[field.name, "title"]} key={"title" + index + Math.random()}>
<Input />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label="Type" name={[field.name, "type"]}>
<Radio.Group>
<Radio value={"link"}>URL</Radio>
<Radio value={"page"}>Page</Radio>
</Radio.Group>
</Form.Item>
</Col>
<Col span={9}>
<Form.Item
// label="URL"
// name={[field.name, "url"]}
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.type !== currentValues.type}>
{({ getFieldValue }) => {
return getFieldValue("type") === "link" ? (
<Form.Item name={[field.name, "url"]} label="URL">
<Input />
</Form.Item>
) : (
<Form.Item name={[field.name, "page"]} label="Page">
<Input />
</Form.Item>
)
}}
<Input />
</Form.Item>
</Col>
<Col span={2}>
<Form.Item>
<Button htmlType="button" onClick={() => moveUp(index)}>
<CaretUpOutlined />
</Button>
<Button htmlType="button" onClick={() => moveDown(index)}>
<CaretDownOutlined />
</Button>
</Form.Item>
</Col>
<Col span={1}>
<Button htmlType="button" onClick={() => removeMenuItem(index)}>
<MinusOutlined />
</Button>
</Col>
</Row>
))}
<Button type="dashed" onClick={() => addMenuItem()} block icon={<PlusOutlined />}>
Add menu item
</Button>
</>
)}
</Form.List>
How can I achieve to change the input on a per line basis whenever the user changes the value of the radio button?
You are trying to get type value with wrong name path. getFieldValue expects the complete path when you want to get any value.
You form list name is menuTree. Since it's a list, your namepath will look like this:
getFieldValue(['menuTree', field.name, 'type'])
<Form>
<Form.List name={'menuTree'}>
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Row id={'menu-row-' + index} key={index}>
<Col span={8}>
<Form.Item label='Title' name={[field.name, 'title']} key={'title' + index + Math.random()}>
<Input />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label='Type' name={[field.name, 'type']}>
<Radio.Group>
<Radio value={'link'}>URL</Radio>
<Radio value={'page'}>Page</Radio>
</Radio.Group>
</Form.Item>
</Col>
<Col span={9}>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues?.menuTree?.[field.name]?.type !== currentValues?.menuTree?.[field.name]?.type
}
>
{({ getFieldValue }) => {
return (
<>
{getFieldValue(['menuTree', field.name, 'type']) === 'link' ? (
<Form.Item name={[field.name, 'url']} label='URL'>
<Input />
</Form.Item>
) : (
<Form.Item name={[field.name, 'page']} label='Page'>
<Input />
</Form.Item>
)}
</>
);
}}
</Form.Item>
</Col>
<Col span={2}>
<Form.Item>
<Button htmlType='button' onClick={() => {}}>
<CaretUpOutlined />
</Button>
<Button htmlType='button' onClick={() => {}}>
<CaretDownOutlined />
</Button>
</Form.Item>
</Col>
<Col span={1}>
<Button htmlType='button' onClick={() => remove(field.name)}>
<MinusOutlined />
</Button>
</Col>
</Row>
))}
<Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
Add menu item
</Button>
</>
)}
</Form.List>
</Form>

React-Bootstrap modal only show and delete last mapped element

i'm building eccommerce and in my cart i want to show a modal when user tap on delete icon but my modal always show and delete last element of my cart
how can i resolve this issue
here's my modal component:
const VerticalModal = ({ item, mShow, hide, name }) => {
return (
<Modal
show={mShow}
size="md"
centered
>
<Modal.Header>
<Modal.Title >
<h6>Remove from cart</h6>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p className="text-center">Are you sure you want to remove {name}</p>
</Modal.Body>
<Modal.Footer className="d-flex justify-content-between">
<Button variant="outline-secondary" size="sm" onClick={hide}>
Cancel
</Button>
<Button variant="outline-danger" size="sm" onClick={item}>
Remove
</Button>
</Modal.Footer>
</Modal>
);
};
here's my cart code :
{cart &&
cart.cartItems.length > 0 &&
cart.cartItems.map((item, index) => (
<Card
key={index}
className=" cart-card-magninn cart-card-shadow"
>
<Row className="p-3">
<Col>{item.img}</Col>
<Col>{item.name}</Col>
<Col md={2} xs={2}>
<button
onClick={() => setModalShow(true)}
>
<FontAwesomeIcon icon={faTrashAlt} />
</button>
<VerticalModal
mShow={modalShow}
hide={() => setModalShow(false)}
item={() => removeFromCartHandler(item.prodid)}
name={item.prodname}
/>
</Col>
</Row>
)}
You're creating four modals and whenever, the modal opens, it has the value of the last item. You should move modal out of the map function and store the selected item in a separate state and use that to delete the info.
Initialize the selected item as empty object.
const [selectedItem, setSelectedItem] = useState({})
Then, update the click method on the button.
<button
onClick={() => {
setModalShow(true)
setSelectedItem(item)
}}>
<FontAwesomeIcon icon={faTrashAlt} />
</button>
After that, move the modal code outside of the map function.
{modalShow ?
<VerticalModal
mShow={modalShow}
hide={() => {
setModalShow(false)
setSelectedItem({})
}}
item={() => {
removeFromCartHandler(selectedItem.prodid)
setSelectedItem({})
}
name={selectedItem.prodname}
/>
: null}
Updated code could be something like this
{cart &&
cart.cartItems.length > 0 &&
cart.cartItems.map((item, index) => (
<Card
key={index}
className=" cart-card-magninn cart-card-shadow"
>
<Row className="p-3">
<Col>{item.img}</Col>
<Col>{item.name}</Col>
<Col md={2} xs={2}>
<button
onClick={() => {
setModalShow(true)
setSelectedItem(item)
}}>
<FontAwesomeIcon icon={faTrashAlt} />
</button>
</Col>
</Row>
)}
{modalShow ?
<VerticalModal
mShow={modalShow}
hide={() => {
setModalShow(false)
setSelectedItem({})
}}
item={() => {
removeFromCartHandler(selectedItem.prodid)
setSelectedItem({})
}
name={item.prodname}
/>
: null}

in react-sortable-hoc Input wrapped loses focus when onChange is fired

i am using react-sortable-hoc in Model view,
But my component below, the input field loses focus after typing a character.
Focus on any row of Column 2 with onChange prop, type a 2-digit number (e.g. '20') on it and then lose focus after the 1st digit (e.g. after '2')
Why is this so?
Main Page Component
render() {
const SortableItem = sortableElement(({value,yourIndex}) =>{
return (
<Row xs="12">
<Col xs="1">
<FormGroup>
<DragHandle/>
</FormGroup>
</Col>
<Col xs="4">
<FormGroup>
{
this.state.MenuItems && this.state.MenuItems != null ?
<Select
options={this.state.MenuItems}
value={this.state.MenuItems.filter(option => option.value === value.menugroupsitemsitemid)}
onChange={this.handelItemSelectChange(yourIndex)} />
: <Select value={null} options={emptyVal} />
}
</FormGroup>
</Col>
<Col xs="2">
<FormGroup>
<Input
className="form-control-alternative text-right"
id="itemstepname"
placeholder="Price"
type="text"
name="itemsstepsitemsprice"
value={value.menugroupsitemsprice === undefined ? '0' : value.menugroupsitemsprice}
onChange={(e) => this.handelInputPrice(yourIndex,value.menugroupsitemsitemid,e)}
/>
</FormGroup>
</Col>
<Col xs="3">
<FormGroup>
{
this.state.TaxCode && this.state.TaxCode != null ?
<Select
placeholder="Tax"
options={this.state.TaxCode}
value={this.state.TaxCode.filter(option => option.value === value.menugroupsitemstaxcode)}
onChange={(newValue) => this.handelTaxSelectChange(yourIndex,value.menugroupsitemsitemid,newValue)} />
: <Select value={null} options={emptyVal} />
}
</FormGroup>
</Col>
<Col xs="2">
<FormGroup>
{
value.menugroupsitemsitemid ?
<span className="link-button" onClick={this.getItemSteps(value.menugroupsitemsitemid)}><i className="fa fa-edit mr-2 text-red" aria-hidden="true"></i></span> : null
}
{ this.state.menuGroupItem.length > 1 ?
<span className="link-button" onClick={(e) => this.DecreaseItem(yourIndex,value.menugroupsitemsitemid)}><i className="fa fa-minus-circle mr-2 text-red" aria-hidden="true"></i></span>
: null
}
{
this.state.menuGroupItem.length === yourIndex + 1 ?
<span className="link-button" onClick={this.IncrementItem}><i className="fa fa-plus-circle text-icon" aria-hidden="true"></i></span>
: null
}
</FormGroup>
</Col>
</Row>
)
});
const SortableItmList = sortableContainer(({items}) => {
return (
<Row>
<Col lg="12">
<FormGroup>
<label
className="form-control-label"
htmlFor="input-itemdescription"
>
Items
</label>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} yourIndex={index} />
))}
</FormGroup>
</Col>
</Row>
)
});
return (
<Row>
<Modal
className="modal-dialog-centered"
isOpen={this.state.itemModal}
toggle={() => this.toggleModal("itemModal")}
>
<div className="modal-header">
<h2 className="modal-title" id="modal-title-default">
{this.state.menutypename}
</h2>
</div>
{this.state.itmModelError ? (
<div className="model-alert">
<Alert color="danger" isOpen={this.state.visible}>{this.state.itmModelError}</Alert>
</div>
) : null}
<div className="breadcrumb">
<li> <span className="menu-ol-list">{this.state.groupname}</span> </li>
</div>
<div className="modal-body">
<SortableItmList axis="y" helperClass="sortable-list-tab" lockAxis="y" distance={0} items={this.state.menuGroupItem} onSortEnd={this.onSortEnd.bind(this)} useDragHandle />
</div>
<div className="modal-footer">
<Button className="ml-auto" onClick={() => this.toggleModal("itemModal")} color="primary" type="button">
Go Back
</Button>
</div>
</Modal>
</Row>
)
}

Save Position of react-draggable objects in react

I am using react-draggable to drag and drop elements. How should I save the elements positions after dragging.
<div className={classes.section}>
<h5>Create Invoice</h5>
{isEditing ? <Button variant="contained" color="primary">Edit</Button> : <Button variant="contained" color="primary">Update</Button>} </label>
<div id="target" onDrop={this.drop} onDragOver={this.allowDrop}>
<Paper className={classes.paper} >
<form>
<Draggable axis="both" bounds="div" disabled={isEditing}>
<CustomInput select variant="outlined" label="To" helperText="Please select your company"/></Draggable>
<Draggable axis="both" bounds="div" disabled={isEditing}><CustomInput name="invoicenumber" label="Invoice Number"/></Draggable>
<Draggable axis="both" bounds="div" disabled={isEditing}><CustomInput id="Date" label="Issue Date" defaultValue="2019-01-01" type="Date" variant="outlined" InputLabelProps={{shrink:true}}/></Draggable>
<TextField id="Date" defaultValue="2019-01-01" type="Date" variant="outlined" InputLabelProps={{shrink:true}}/>
<h5>Item Id</h5> */}
<Draggable axis="both" bounds='div' disabled={isEditing}><CustomInput type="textarea" variant="outlined" label="Description"/></Draggable>
<Draggable axis="both" bounds='Div' disabled={isEditing}>
<CustomInput type="file" variant="outlined" helperText="Attachments" style={{alignContent:"right"}}/>
</Draggable>
</form>
</Paper>
</div>
This is the function for getting the value. Once you get the value then you'll set it a database or localStorage whatever you want.
const eventLogger = (e, data) => {
localStorage.setItem('defaultPosition', { valueX: data.x, valueY: data.y });
};
This function will be called in Dragable by the event onStop.
<Draggable defaultPosition={{ x: 0, y: 0 }} onStop={eventLogger}>
<h2 style={{ cursor: 'grab' }}>
dragable
</h2>
</Draggable>

Resources