I got infinity loop after creating one modal for all buttons - javascript

I have three buttons with different content and one modal, which I want to use after clicking on the button, and change the content. But after reloading my page, I got infinity loop in function, where I set true on view Modal. Can you help me, where is the issue?
First I created a state for my data and then I have functions, where I set state. And after a click on the button, I pass individual data (header, body) to showModal function with these data. But it is not working, I got infinity loop in showModal and I don´t know why.
let header = null;
let body = null;
const [modal, setModal] = useState({
modalHeader: '',
modalBody: {},
modalStatus: false
});
const showModal = (header, body) => {
setModal({
modalHeader: header,
modalBody: body,
modalStatus: true
});
};
const hideModal = () => {
setModal({
modalHeader: '',
modalBody: null,
modalStatus: false
});
};
return(
<div>
<Modal
show={modal.modalStatus}
onCancel={hideModal}
header={modal.modalHeader}
footerClass="modalAction"
footer={
<React.Fragment>
<Button inverse onClick={hideModal}>
BACK
</Button>
</React.Fragment>
}>
<div className="backgroud-graph">
<h2 className="center">{modal.modalHeader}</h2>
<Pie data={modal.modalBody} />
</div>
</Modal>
<Button onClick={showModal(header="Cards" , body={dataYellowCardsCoop} )}>
Porovnanie kariet
</Button>
<Button onClick={showModal(header="Tackles" , body= {dataTacklesCoop})}>
Porovnanie obrannych zakrokov
</Button>
<Button onClick={showModal(header="Goals" , body= {dataGoalsCoop})}>
Porovnanie golov
</Button>
</div>
);

For the handler, you need to pass a function, not call it, as you do here:
onClick={showModal(header="Cards" , body={dataYellowCardsCoop} )}
In your case, when the component is rendered the showModal function is called.
Additionally, you pass arguments incorrectly (you don't need an equal sign and their names).
<Button onClick={() => showModal("Cards", {dataYellowCardsCoop})}>
Porovnanie kariet
</Button>

Related

Why does the state switch while the function is running?

The site has a button for deleting records (DeleteForeverIcon in the code). When you click on this button, a window opens (made according to the documentation using Dialog Mui).
When you click on the "Confirm" button, the handleDeleteItem() function is launched, which deletes the entry. But I can’t understand why the window closes while this function is running, because I don’t switch the state anywhere
Looking for a solution on my own, I added console.log() to my code (below is the same code, only with console.log()) and came up with the following conclusion: when I run the handleDeleteItem() function, the open state switches to false and so the window closes. But why does it switch to false?
export function DeleteButton({ item }) {
const { urlParams } = useContext(PageContext)
const { firestore } = useContext(AppContext)
const [open, setOpen] = useState(false);
console.log(open, "window close") // console.log here
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleDeleteItem = async () => {
console.log("start") // console.log here
await deleteItem()
console.log("finish") // console.log here
}
return (
<ButtonGroup>
<DeleteForeverIcon onClick={handleClickOpen}/>
<Dialog
open={open}
onClose={handleClose}>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleDeleteItem}>Confirm</Button>
</DialogActions>
</Dialog>
</ButtonGroup >
)
}
That is, summing up the question, I would like the window to close only after the deleteItem () function has completed, and not during its execution
Based on some further clarification in the comments, it seems as though your issue is to do with the fact that calling deleteItem(...) causes your state to update in your parent components (due to an onSnapshot firebase listener). Your parent components are responsible for rendering the children components. When your state updates, the item/row that you deleted won't be in the new state value, and so the component that was rendering your Dialog previously won't be rendered (because it has been deleted).
Here is a minified example of your issue:
const { useState } = React;
const List = () => {
const [items, setItems] = useState(["a", "b", "c", "d", "e"]);
const handleDelete = (charToDel) => {
setItems(items => items.filter(char => char !== charToDel));
}
return <ul>
{items.map(char =>
<li key={char}>{char} - <DeleteButton value={char} onDelete={handleDelete}/></li>
)}
</ul>
}
const DeleteButton = ({value, onDelete}) => {
const [open, setOpen] = useState(false);
return <React.Fragment>
<button onClick={() => setOpen(true)}>×</button>
<dialog open={open}>
<p>Delete {value}?</p>
<button onClick={() => onDelete(value)}>Confirm</button>
</dialog>
</React.Fragment>
}
ReactDOM.createRoot(document.body).render(<List />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
So since you're not rendering the component that renders the Dialog once you remove the item, you won't be rendering the <Dialog> anymore and so it disappears.
One way to fix this is to lift the <Dialog> component up to a component that doesn't get removed when you remove an item from your state. By the looks of things, the closest parent component that has this property is DevicesTable. In there you can render your dialog and keep track of a selectedItem to determine which item that should be deleted, which you can set based on the item you press (see code comments below):
// DevicesTable component
const [selectedItem, setSelectedItem] = useState();
const handleClose = () => {
setSelectedItem(null);
}
const handleDeleteItem = () => { // this doesn't need to be `async` if not using `await`
deleteItem(selectedItem, firestore, urlParams); // this doesn't need to be `await`ed if you don't have any code following it
}
return (
<>
{/* vvvvvv -- move dialog here -- vvvvvv */}
<Dialog open={!!selectedItem} onClose={handleClose}>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleDeleteItem}>Confirm</Button>
</DialogActions>
</Dialog>
{/* ^^^^^^ -- move dialog here -- ^^^^^^ */}
<TableContainer className="TableContainerGridStyle">
<Table className="TableStyle">
<DevicesTableHeader />
<TableBody className="TableBodyStyle">
{devices.map((device) =>
<DevicesTableCell
device={device}
onDeleteButtonPress={() => setSelectedItem(device)} /* <--- set the selected item */
key={device.description.id}
/>
)}
</TableBody>
</Table>
</TableContainer>
</>
);
For brevity, I've removed the open state and instead used the presence of the selectedItem to determine if the modal should be open or not, but you can of course add that back in if you wish and set both the selectedItem and the open state when opening and closing the modal.
Within DevicesTableCell, you would then grab the onDeleteButtonPress prop, and then pass it to DeleteButton like so:
// v-- grab the function
function DevicesTableCell({ device, onDeleteButtonPress }) {
...
<DeleteButton item={device} onDeleteButtonPress={onDeleteButtonPress}/> {/* pass it to the componennt */}
...
}
Within DeleteButton you should then invoke the onDeleteButtonPress function:
<DeleteForeverIcon onClick={onDeleteButtonPress} />
If you don't like the idea of passing callbacks down through multiple components like this, you can avoid that by using useReducer with a context, as described here.

Call a function in React from child component

Inside a component, there is a mapping for creating multiple elements. Each element can be deleted if a button is clicked:
const ParentComponent = () => {
...
const [fields, setFields] = useState([{value: 'a', editable: false},
[{value: 'b', editable: true},
[{value: 'c', editable: false}]);
const handleRemoveField = (id) => {
const values = [...fields];
values.splice(id, 1);
setFields(values);
}
...
return (
...
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
</div>
);
})}
...
);
The above code is working fine. The problem comes up when the delete part must be done from a modal component instead of directly clicking the button.
So I created a new Modal component:
const DeleteModal = ({ isDeleteModalOpen, closeDeleteModal }) => {
return (
<Modal isOpen={isDeleteModalOpen}>
<ModalTitle handleClose={closeDeleteModal} />
<button onClick={deleteElement}> OK </button>
<button onClick={closeDeleteModal}> cancel </button>
</Modal>
);
};
And inside ParentComponent, DeleteModal was imported. When handleRemoveField is called, instead of directly deleting the element it is opening the modal.
What I don't know how to do is to delete de element when OK button from modal is clicked (deleteElement function from modal should do that).
Modified ParentComponent:
const ParentComponent = () => {
...
const [fields, setFields] = useState([{value: 'a', editable: false},
[{value: 'b', editable: true},
[{value: 'c', editable: false}]);
const handleRemoveField = (id) => { // modified the method to call modal
openDeleteModal(id);
};
// added for open/close modal
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
const openDeleteModal = () => {
setDeleteModalOpen(true);
};
const closeDeleteModal = () => setDeleteModalOpen(false);
...
return (
...
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
// imported modal here:
<DeleteModal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal} />
</div>
);
})}
...
);
The cancel button is working, it closes the modal. The problem is with OK button that must delete the element.
How can that be done?
You can add a new prop to your Modal components, maybe something along the lines of onDelete. Than you can pass a method which deletes your element to the Modal like so:
<Modal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal}
onDelete={() => deleteElement(id)}
/>
Inside your Modal component you can call your onDelete prop to call deleteElement(id):
const DeleteModal = ({ isDeleteModalOpen, closeDeleteModal, onDelete }) => {
return (
<Modal isOpen={isDeleteModalOpen}>
<ModalTitle handleClose={closeDeleteModal} />
<button onClick={onDelete}> OK </button>
<button onClick={closeDeleteModal}> cancel </button>
</Modal>
);
};
So long story short: You can pass down a method with your child component to call it from there.
You could save the ID that is going to be deleted before open modal, and use it to decide if modal is visible or not, as well.
const [idToDelete, setIdToDelete] = useState(null);
const handleRemoveField = (id) => {
setIdToDelete(id);
};
...
<DeleteModal isDeleteModalOpen={idToDelete !== null} closeDeleteModal={closeDeleteModal} />
you use an variable to save which item you are deleting
const [itemDel,setItemDel] = React.useState(null);
then, when click edit to show modal, you set that item to itemDel ( in handleRemoveField function)
something like
return (
<>
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
</div>
);
})}
{
!!itemDel&& <DeleteModal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal} />
}
</>
);
and, when you done, or want to hide modal, just call setItemDel(null)

cannot read property 'function name' of undefined React js

im working with the modal from https://react-bootstrap.netlify.app/components/modal/ and basically i've managed to display a modal from a button that i click. However inside the modal there's another button that when i click should perform a task i've defined in a function already. Now when i click this button in the modal i get the error cannot read property 'confirm_booking' of undefined Here is my code.
constructor(props){
super(props)
this.state={
setModalShow_available_room: false,
modalShow_available_room: false
}
this.confirm_booking = this.confirm_booking.bind(this)
}
render (){
function Available_room_modal(props) {
return (
<Modal
{...props}
size="sm"
aria-labelledby="contained-modal-title-vcenter"
centered>
<Modal.Body>
<Button block onClick={() => { this.confirm_booking() }} >Confirm</Button>
</Modal.Body>
</Modal>
);
}
return(
<div>
<Button block onClick={() => { this.open_modal() }} >Show modal</Button>
<Available_room_modal
show={this.state.modalShow_available_room}
onHide={() => {
this.setState({ setModalShow_available_room: false })
this.setState({ modalShow_available_room: false })
}} />
</div>
)
}
/**then for my functions **/
/**this opens the modal **/
open_modal() {
this.setState({ setModalShow_available_room: true })
this.setState({ modalShow_available_room: true })
}
/**this is the function assigned to the button inside the modal which throws an error when i click it**/
confirm_booking() {
this.setState({ setModalShow_available_room: false })
this.setState({ modalShow_available_room: false })
}
you are clearly not understanding how react works. Please try to see the react documentation first.
I will try to show you some of your errors:
You can't declare a function inside render method. Render method is just to return JSX code. You could declare a function in the class, return jsx from there and call it from render, that is valid.
<Button block onClick={() => { this.confirm_booking() }} >Confirm</Button>
Here, you are calling this.confirm_booking EVERY time your component is being rendered. You should change it to this:
<Button block onClick={this.confirm_booking}> Confirm </Button>

How Can i display a Modal onSubmit Form?

I have a <Form> ( reduxForm ) Where user submits the values , as soon as the values got submitted It goes to another component showResults and that component Returns the Modal Component , Currently Modal Component is displays on the top of the App component ,
How Can I get the Modal component Popup once user have
submitted(pressed the submit button) the values and then using those
values Modal gets displayed accordingly
Form.jsx
<form onSubmit={handleSubmit}>
{allQuestions}
<div>
<button type="submit" disabled={pristine || submitting}>
Submit
</button> // Once your press this button Modal should PopuP
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
showResults.jsx
<Form
formData={formData}
onSubmit={e => {
this.onSubmit(e);
}}
/>
Modal.jsx
class ShowModal extends React.Component {
state = {
open: false,
};
onOpenModal = () => {
this.setState({ open: true });
};
onCloseModal = () => {
this.setState({ open: false });
};
render() {
const { open } = this.state;
return(
<Modal open={open} onClose={this.onCloseModal}>
<h4>Total : {this.props.total} Out of 10</h4>
</Modal>
</div>)
Either store the model open state outside of the component and use a property (proper way) or ref (the anti pattern way):
<ShowModal ref={(modal) => this.modal = modal} />
and later:
this.modal.setState(show:true)
but the proper property way would be:
<ShowModal open={this.sate.showModal} />
probably since you are using redux you want to store your modal open/close state in redux state as well

React Modal ref Undefined! Can't Add Custom Attribute

I have a simple modal:
renderModalForm() {
return (
<Modal
closeTimeoutMS={150}
show={this.state.isModalOpen}
onHide={this.isModalOpen.bind(this)}
>
<Modal.Body>
<div className="close-button-modal">
<i className="fa fa-times fa-2x pull-right" onClick={this.onButtonClick.bind(this)}></i>
<div className="clearfix"></div>
</div>
<div ref="test" className="testclassname">
</div>
</Modal.Body>
</Modal>
);
}
My sole objective is to inject a custom attribute (which unfortunately cannot start with data- or aria- since it's defined by third party) to the div referenced by ref="test"
When I attempt to inject the custom attribute:
someButtonClicked() {
setTimeout(() => {
this.setState({
isModalOpen: true
})
}, 100);
var element = ReactDOM.findDOMNode(this.refs.test);
element.setAttribute('doku-div', 'form-payment');
}
Here element is always undefined, so setAttribute failed; if I go inspect the element, ref="test" does not exist at the <div> ! Can someone help me as to why this ref is missing inside modal?
The correct way to implement is use Callback hook. Whenever the component will render you will have the ref element. useCallback will also help stop unnecessary renders hence better performance.
const Parent = () => {
const [isVisible, setIsVisible] = useState(false);
return (
<>
<button onClick={() => setIsVisible(!isVisible)}>Show/Hide Popup</button>
<Child isVisible={isVisible} />
</>
);
};
const Child = () => {
const customRef = useCallback((ref) => {
// ref element accessible here
}, []);
return (
<Modal show={isVisible}>
<Modal.Body>
<input ref={customRef} type="text" />
</Modal.Body>
</Modal>
);
};
Try moving your code to ComponentDidMount or ComponentDidUpdate method. Refs shouldn't be undefined there.
You can also use a callback to store a reference to the DOM node:
<Modal.Body>
<div className="close-button-modal">
<i className="fa fa-times fa-2x pull-right" onClick={this.onButtonClick.bind(this)}></i>
<div className="clearfix"></div>
</div>
<div ref="{(testNode) => { this.testNode = testNode; }}" className="testclassname">
</div>
</Modal.Body>
And then use that reference instead of using ReactDOM:
someButtonClicked() {
setTimeout(() => {
this.setState({
isModalOpen: true
})
}, 100);
var element = this.testNode;
this.testNode.setAttribute('doku-div', 'form-payment');
}
useRef hook will not work in modals as the component will mount but the jsx will not render until you make show prop to true. useRef is asynchronous in nature thats why at the time of declaration it sets current to null but after you assign it to any element ref got value of it. But in case of modals the case is different. Here the elements are not registered instantly but after modal show prop is set to true
To solve this make the modal's show prop always to true and make whole component to show/hide dynamically
similar query

Categories