Mapped nested component doesn't work peopetly (React Js) - javascript

I have a list of items mapped to be displayed on my "allItems" page. Now every Item has a button that fires up a modal with the specs of the item, but this modal display the same value for all the item (the last one in the array).
I tried to pass the id in toggle func but it doesn't work.
Anyone knows how can I display the same data in the Card and the Modal?
Here's my code:
state = {
modal: false,
}
toggle = () => {
this.setState({
modal: !this.state.modal
})
}
render(){
return(
{rooms.map(({ _id, name, descr, prezzo }) => (
<>
<Card key={_id} className="rooms-card-template">
<CardImg />
<CardBody>
<CardTitle>{name}</CardTitle>
<CardText>{descr}</CardText>
<CardSubtitle>{prezzo}$/notte</CardSubtitle>
<Button onClick={this.toggle}>Apri Annuncio</Button>
<Modal isOpen={this.state.modal} toggle={this.toggle}>
<ModalHeader >{name}</ModalHeader>
<ModalBody>
{descr}
<h5 style={{ paddingTop: "10px"}}>Riepilogo prenotazione</h5>
<Form>
<FormGroup>
<Label>Struttura:</Label>
<Input value={name}/>
</FormGroup>
<FormGroup>
<Label>Ospiti:</Label>
<Input type="select"name="ospiti" id="ospiti">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
</Input>
</FormGroup>
<FormGroup>
<Label>Check in</Label>
<Input type="date" name="checkin-date" id="checkin-date" placeholder="Check in" />
</FormGroup>
<FormGroup className="rooms-checkout">
<Label>Check out</Label>
<Input type="date" name="checkout-date" id="checkout-date" placeholder="Check out" />
</FormGroup>
{ isAuth ? userAuth : userUnauth }
</Form>
</ModalBody>
</Modal>
</CardBody>
</Card>
</>
))}
)
}

Issue
You've a single boolean modal state that all the modals cue from. When this.state.modal is true then a modal is rendered and opened for each element being mapped.
Solution
Instead of storing a boolean value for whether or not a modal should be open you should store an id when you want a specific modal to open.
state = {
modal: null // <-- null, non-id state value
};
toggle = (id) => () => { // <-- curried function handler
this.setState((prevState) => ({
modal: prevState.modal === id ? null : id // <-- set new id or toggle off
}));
};
render() {
return (
<>
{rooms.map(({ _id, name, descr, prezzo }) => (
<Card key={_id} className="rooms-card-template">
...
<CardBody>
...
<Button
onClick={this.toggle(_id)} // <-- pass id
>
Apri Annuncio
</Button>
<Modal
isOpen={this.state.modal === _id} // <-- open if id is match
toggle={this.toggle(_id)} // <-- pass id
>
...
</Modal>
</CardBody>
</Card>
))}
</>
);
}

Related

Multiple Modal Opening when Updating a Row using React

I created a project using React and Bootstrap Table. Opening of modal is functional but unfortunately all the data in the table also opens their own modal.
So example I have 5 data in the table, when updating a row, the other four (4) modal also rendered and display.
The id of the data from Firestore is accessible to the child component (Edit_Modal.tsx) is there a way to add an id for the modal to make a reference to the data?
Manuscript.jsx
<tbody>
{thesisData.map((doc, index) => {
return (
<tr key={doc.id}>
<td>{index + 1}</td>
<td>{doc.title}</td>
<td>
{doc.members[0]}, {doc?.members[1]}
</td>
<td>{doc.adviser}</td>
<td>{doc.course}</td>
<td>{doc.pages}</td>
<td className="m-1 text-center">
<Button
className="mb-1"
variant="secondary"
onClick={(e) => openUpdateModal(doc.id, e)}
>
<IconContext.Provider value={{ color: "#fff" }}>
<div>
<BsFillPencilFill />
</div>
</IconContext.Provider>
{showModalEdit && <Edit_Modal modalToggle={doc.id} />}
</Button>
<Button
className="mb-1"
variant="danger"
onClick={(e) => deleteHandler(doc.id)}
>
<IconContext.Provider value={{ color: "#fff" }}>
<div>
<BsFillTrashFill />
</div>
</IconContext.Provider>
</Button>
</td>
</tr>
);
})}
</tbody>
openUpdateModal() function
const openUpdateModal = (id) => {
setThesisId(id);
setShowModalEdit(true);
};
Edit_Modal.tsx
const Edit_Modal = ({ modalToggle }) => {
//Use States for Modal
const [show, setShow] = useState(true);
const handleClose = () => {
setShow(!show);
};
return ReactDom.createPortal(
<div>
<Modal
show={show}
keyboard={false}
onHide={handleClose}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
Update Thesis Details
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form
noValidate
validated={validated}
id="addFormId"
onSubmit={handleUpdateForm}
>
<Row className="mb-3">
<Form.Group as={Col} controlId="formGridTitle">
<Form.Label>Title</Form.Label>
<Form.Control
type="text"
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter title"
value={title}
required
/>
<Form.Control.Feedback type="invalid">
Please enter a title.
</Form.Control.Feedback>
</Form.Group>
<Form.Group as={Col} controlId="formGridAdviser">
<Form.Label>Adviser</Form.Label>
<Form.Control
type="text"
onChange={(e) => setAdviser(e.target.value)}
placeholder="Name of Adviser"
value={adviser}
required
/>
<Form.Control.Feedback type="invalid">
Please enter an Adviser.
</Form.Control.Feedback>
</Form.Group>
</Row>
<Form.Group className="mb-3" controlId="formGridAbstract">
<Form.Label>Abstract</Form.Label>
<Form.Control
as="textarea"
onChange={(e) => setAbstract(e.target.value)}
rows={3}
placeholder="Enter Abstract Details"
value={abstract}
required
/>
<Form.Control.Feedback type="invalid">
Please enter Abstract Details.
</Form.Control.Feedback>
</Form.Group>
<Button type="Submit">Add Content</Button>
</Form>
</Modal.Body>
</Modal>
</div>,
document.getElementById("modal-root")
);
};
export default Edit_Modal;
As #adhinarayan said you can use the useState of setThesisId to get the specific ID of your document.
Example useState:
const [thesisId, setThesisId] = useState("");
You can use the openUpdateModal function to get the Modal and the thesis ID
const openUpdateModal = (id) => {
setThesisId(id); //useState for ID
setShowModalEdit(true); //useState for Modal
};
Finally, make sure that the Modal Component was placed outside of the map function to avoid looping of modals. You can set the thesisId to customized props of Edit_Modal
...</tbody>
</Table>
{showModalEdit && <Edit_Modal modalToggle={thesisId} />}
</Col>...

React-Hook Form with useFieldArray: TextField value does not show up in the console. How can I fix it?

I wanted to enter the word Product but when I submit it, it does not show up in the console.
What shows up in the console:
As you can see here, the word Product does not appear in the console. Any idea on how I can solve this?
This is the codesandbox link: https://codesandbox.io/s/react-hook-form-usefieldarray-nested-arrays-forked-vjwbp?file=/src/index.js
this is the fieldArray.js where the input fields for products is
import React from "react";
import { useFieldArray } from "react-hook-form";
import NestedArray from "./nestedFieldArray";
import { TextField } from "#mui/material";
let renderCount = 0;
export default function Fields({ control, register, setValue, getValues }) {
const { fields, append, remove, prepends } = useFieldArray({
control,
name: "test"
});
renderCount++;
return (
<>
<ul>
{fields.map((item, index) => {
return (
<li key={item.id}>
{/* <select
ref={register()}
name={`test[${index}].name`}
defaultValue={item.name}
>
<option value="">Select</option>
<option value="10">ItemA</option>
<option value="20">ItemB</option>
</select> */}
{/* {index + 1} to show the qty */}
<TextField
name={`test[${index}].name`}
refer={register()}
defaultValue={item.name}
/>
<button type="button" onClick={() => remove(index)}>
Delete
</button>
<NestedArray nestIndex={index} {...{ control, register }} />
</li>
);
})}
</ul>
<section>
<button
type="button"
onClick={() => {
append({ name: "append" });
}}
>
Add product
</button>
</section>
<span className="counter">Render Count: {renderCount}</span>
</>
);
}
you can change it try way:
fieldArray.js
<TextField
name={`test[${index}].nestedArray[${index}].product`}
inputRef={register({ required: true })}
defaultValue={item.name}
/>;

Make Whole Div Clickable in React

I have checkbox and its working well. However I wanted the whole div to be clickable also not just the checkbox alone.
Pls check here CLICK HERE
<form onSubmit={handleSubmit}>
{products.map((product) => (
<div key={product} style={{ cursor: "pointer" }}>
{product}
<Checkbox
name="products"
value={product}
checked={values.products.includes(product)}
onChange={({ target }) => {
let updatedProducts;
if (values.products.includes(product)) {
updatedProducts = values.products.filter(
(product) => product !== target.value
);
} else {
updatedProducts = [...values.products, target.value];
}
setFieldValue("products", updatedProducts);
}}
/>
</div>
))}
<button type="submit">Submit</button>
</form>
Wrap the checkbox in label element. This will make the text, i.e. "label" clickable as part of the input. Since label elements are display: inline you will need to also override the display CSS to maintain it as a block level element.
<form onSubmit={handleSubmit}>
{products.map((product) => (
<label key={product} style={{ cursor: "pointer", display: "block" }}>
{product}
<Checkbox
name="products"
value={product}
checked={values.products.includes(product)}
onChange={({ target }) => {
let updatedProducts;
if (values.products.includes(product)) {
updatedProducts = values.products.filter(
(product) => product !== target.value
);
} else {
updatedProducts = [...values.products, target.value];
}
setFieldValue("products", updatedProducts);
}}
/>
</label>
))}
<button type="submit">Submit</button>
</form>
If you want to have your product name clickable and change checkbox state, use a label with an htmlFor
You would need to make a few changes to your app like so:
<label for={product}>{product}</label> <!-- ADD LABEL TAG AND FOR HERE! <!--
<Checkbox
name="products"
id={product} // <-- HAVE YOUR ID SAME WITH THE FOR ABOVE
value={product}
checked={values.products.includes(product)}
onChange={({ target }) => {
let updatedProducts;
if (values.products.includes(product)) {
updatedProducts = values.products.filter(
(product) => product !== target.value
);
} else {
updatedProducts = [...values.products, target.value];
}
setFieldValue("products", updatedProducts);
}}
/>;
And inside Input component:
<Input
type="checkbox"
id={id} // <-- CHANGE ID TO USE ACTUAL ID ABOVE
name={name}
value={value}
checked={checked}
onChange={onChange}
/>;

ReactJS and Material UI CheckBox State Change

Im using Material UI Checkbox to create a form that takes input and then in theory adds the new values or updates the values into a state object. This form is used for both editing a holiday or creating a new holiday.
I'm struggling with finding why my state isnt being updated upon clicking on a checkbox or typing in an input box. The checkbox wont change to checked/unchecked and the input wont remvoe the value when i use backspace to remove characters or enter new characters.
Dialog Box:
HolidayData:
{
"id": 1,
"ID": 1,
"HolidayName": "New Year's Day",
"HolidayDate": "05/20/2020",
"Branch": null,
"Hours": null,
"Web": true,
"Phone": true,
"CoOp": false,
"Active": true,
"Submitted": null,
"SubmittedBy": null,
"Published": "05/20/2020",
"PublishedBy": "John.Doe"
}
DialogBox Code:
const HolidayDialog = (props) => {
const [noticeModal, setNoticeModal] = React.useState(false);
const [selectedDate, setSelectedDate] = React.useState(new Date());
const [holidayData, setHolidayData] = React.useState(props.data);
useEffect(() => {
setHolidayData(props.data);
setNoticeModal(props.open)
});
const handleDateChange = (date) => {
setSelectedDate(date);
};
const handleClose = () => {
setNoticeModal(false);
};
const handleChange = (e) => {
const { name, checked } = e.target;
setHolidayData((prevState) => ({ ...prevState, [name]: checked }));
};
const updateValues = (e) => {
const { name, value } = e.target;
setHolidayData((prevState) => ({ ...prevState, [name]: value }));
};
return (
<Dialog
open={noticeModal}
TransitionComponent={Transition}
keepMounted
onClose={handleClose}
aria-labelledby="notice-modal-slide-title"
aria-describedby="notice-modal-slide-description"
>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
{holidayData.HolidayName ? holidayData.HolidayName : 'Create New Holiday'}
</DialogTitle>
<DialogContent dividers>
<form noValidate autoComplete="off">
<div className="row">
<div className="col">
<TextField required name="HolidayName" id="outlined-basic" label="Holiday Name" variant="outlined" onChange={updateValues} value={holidayData.HolidayName ? holidayData.HolidayName : ''}/>
</div>
<div className="col">
<TextField id="outlined-basic" label="Branch" variant="outlined" onChange={updateValues} value={holidayData.Branch ? holidayData.Branch : 'ALL'}/>
</div>
</div>
<div className="row mt-3">
<div className="col">
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
disableToolbar
variant="inline"
format="MM/dd/yyyy"
margin="normal"
id="date-picker-inline"
label="Date picker inline"
value={selectedDate}
onChange={handleDateChange}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</MuiPickersUtilsProvider>
</div>
<div className="col">
<TextField id="outlined-basic" label="Hours" variant="outlined" onChange={updateValues} value={holidayData.Hours ? holidayData.Hours : 'Closed'}/>
</div>
</div>
<div className="row mt-3">
<div className="col d-flex flex-column">
<FormControlLabel
control={
<Checkbox
checked={holidayData.Web ? holidayData.Web : false}
onChange={handleChange}
name="Web"
color="primary"
/>
}
label="Show on Web?"
/>
<FormControlLabel
control={
<Checkbox
checked={holidayData.CoOp ? holidayData.CoOp : false}
onChange={handleChange}
name="CoOp"
color="primary"
/>
}
label="CoOp Holiday?"
/>
</div>
<div className="col d-flex flex-column">
<FormControlLabel
control={
<Checkbox
checked={holidayData.Phone ? holidayData.Phone : false}
onChange={handleChange}
name="Phone"
color="primary"
/>
}
label="Use in IVR?"
/>
<FormControlLabel
control={
<Checkbox
checked={holidayData.Active ? holidayData.Active : false}
onChange={handleChange}
disabled
name="active"
color="primary"
/>
}
label="Active"
/>
</div>
</div>
</form>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="default">
Cancel
</Button>
<Button autoFocus onClick={handleClose} color="primary">
Create Holiday
</Button>
</DialogActions>
</Dialog>
)
}
When i check a box or try to edit an input the handleChange and updateValues are firing. I believe it may be a syntax issue, but i cant seem to find anything. When console.log'ing event.target.name i get the correct name: Web for example
EDIT: The issue appears to be with the value and checked being equal to {holidayData.Phone ? holidayData.Phone : false} or the sorts. However if i bring the values down to {holidayData.Phone} itll start to throw errors:
A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa).
It'll now allow me to check a checkbox but only once and throws this error over and over again and im not sure why or how to correct this?
Most probably the useEffect is causing the issue. You're resetting the data each time the component gets updated. You don't need that if you're already doing that with useState.
Check my example here with only the textbox: https://codesandbox.io/s/fancy-shape-14r63?file=/src/App.js
If you uncomment the useEffect, it stops working.

Formik lost fields when trigger a modal confirm

I have a form using Formik library, all are right but when i want to confirm and submit i need to pass first a modal, the problem is when i launch the confirm modal all data i put in the form are eliminate and nothing is send, i noticed when the i launch the modal, the form tag are upload and then lost the data, someone has the same problem ? o are something to prevent the form tag upload when change the state of the modal ?
here is part of my code, i use CSSTransition on the modal, that mount and unmount the component, and i call previous fields on the form using FieldArray
<Formik
enableReinitialize
initialValues={INITIAL_VALUES}
onSubmit={(values) => {
this.props.onSubmitForm(values.rows);
}}
>
{({ values, errors, touched, isValid }) => (
<Form>
<Title />
<div className="wrapper__brick wrapper__brick--to-up">
<FieldArray
name="rows"
render={({ push }) => (
<>
<div className="wrapper__block">
<div className="master-table">
<FieldArray
name="rows"
render={({ remove }) => (
<div className="master-table__container">
{values.rows.length > 0 &&
values.rows.map((rows, index) => (
<div
className="master-table__row"
key={index}
>
<div className="master-table__item">
<Field
className={errors.rows && errors.rows[index] && touched.rows && touched.rows[index] ?
'input input--empty is-danger'
:
'input input--empty'
}
name={`rows.${index}.name`}
placeholder="Name"
type="text"
validate={validateName}
/>
<ErrorMessage
name={`rows.${index}.name`}
component="p"
className="help is-danger"
/>
</div>
<div className="master-table__item">
<div className={rows.default ? 'select select--disabled' : 'select select--empty'}>
<Field
as="select"
disabled={rows.default}
name={`rows.${index}.kind`}
>
{this.allowedKinds.map((row) => (
<option
value={row.internal}
key={row.internal}
>
{row.name}
</option>
))}
</Field>
</div>
</div>
<div className="master-table__item">
<button
className="button button--trigger tooltip is-tooltip-right"
data-tooltip={rows.default ? t('common.deleteNotAvailable') : t('common.delete')}
onClick={() => remove(index)}
type="button"
disabled={rows.default}
>
<span className="icon">
<i className="far fa-trash-alt" />
</span>
</button>
</div>
</div>
))}
</div>
)}
/>
</div>
</div>
<Footer
onAddRow={() => push({
id: UUID.v4(),
name: '',
kind: 'string',
type: 'generic',
required: false,
default: false
})}
validForm={!isValid}
/>
<TransitionWrapper stateToggle={this.state.openModal}>
<ModalDialog
iconModal="fas fa-info-circle"
titleText={t('modals.descriptionContinue')}
onCloseMethod={this.handleShowModal}
onAcceptMethod={this.props.onSubmitForm}
/>
</TransitionWrapper>
</>
)}
/>
</div>
</Form>
)}
</Formik>

Categories