I use react in the project. The modal closes when the user refreshes, but I want to modify it so that it doesn't close. I found 'beforeunload' event, but I want to prevent 'alert' from appearing.
And when I refresh, I want to keep the data I received from the API and maintain the model.
I want to make the modal close only when I press the close button, what methods can I use?
This is not a real code. Just a example...
function Modal() {
const [open, setOpen] = useState(false)
const handleOpen = () => {
setOpen(!open);
}
return (
<Modal>
<button onClick={handleOpen}>
modal
</button>
(some data from API)
</Modal>
)
}
Read localStorage value first, if it exists use it, and then everytime you set a new value update it in local storage as well.
Remember localstorage only saves values as a string; and when it doesn't exist it will come back as a null.
function Modal() {
const [open, setOpen] = useState(JSON.parse(localStorage.getItem("open")) || false)
const handleOpen = () => {
setOpen((prevState) => {
localStorage.setItem("open", !prevState);
return !prevState;
});
}
return (
<Modal>
<button onClick={handleOpen}>
modal
</button>
</Modal>
)
}
Related
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.
I've been creating an app with NextJS, my problem came when I tried to generate new content from an API when I click on a button. I get the server call succesfully but it gets updated only when I reload the page, not when I click on the button as I want.
Here's the code:
import React, {useState} from 'react'
import { FaCog } from 'react-icons/fa'
export async function getServerSideProps() {
const res = await fetch(`https://animechan.vercel.app/api/random`)
const data = await res.json()
return {
props: {
quote: data.quote,
anime: data.anime,
character: data.character
}
}
}
const Button = () => {
const [clicked, setClicked] = useState(false)
const handleClicked = (props) => {
return (
<div>
<p style={{fontWeight: 'bold'}}>{props.anime}</p>
<p>{props.quote}</p>
<p style={{fontWeight: 'bold'}}>-{props.character}</p>
</div>
)
setClicked(!clicked)
}
return (
<div className="main_button">
<button onClick={handleClicked}>{clicked ? 'Generate again...' : 'Generate content '} <FaCog className={clicked ? 'main_button-spinner' : null}/> </button>
</div>
)
}
export default Button
I want that each time I click on the button, the content gets updated and I receive new content from the API. As I explained above, this is working fine on the API call, but the content gets updated just by reloading the page and not as I need it to work. Any idea?
You're misunderstanding the role of getServerSideProps. Take a look at the Next.js documentation: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
getServerSideProps only runs on server-side and never runs on the browser
If you want your React application to change in response to hitting this API, then you need to submit a request to the API from within the React code (in response to the button click), save the results of the API to state, and then display the results in the frontend.
Psuedo code follows:
const [data, setData] = useState(null);
// ...
async function handleClicked() {
const apiResponse = await fetch("...");
setData(apiResponse); // Or whatever properties from the response you care about
}
// ...
<button onClick={handleClicked}>
{clicked ? 'Generate again...' : 'Generate content '}
<FaCog className={clicked ? 'main_button-spinner' : null}/>
</button>
Tried to search for a similar question but couldn't find any, apologies if I missed one. I am using React Material-UI to create a simple web application. I have an XGrid which lists a number of products and various statistics over a user-selected date range. Each row has a button that the user can click. The idea is that, upon clicking the button, the application calls an API to get relevant information about the product in that row. I would like the info returned from the API to be displayed on a Material-UI modal.
I am getting stuck on how to pass the information from the API to the Modal component and display it. I have a simple example in the code below that is close to what I want to accomplish.
import "./styles.css";
import React from "react";
import { Button, Modal, Typography, Box } from "#material-ui/core";
export default function App() {
const [open, setOpen] = React.useState(false);
const handleClose = () => setOpen(false);
return (
<div className="App">
<Button
variant="outlined"
color="primary"
onClick={() => {
setOpen(true);
(async () => {
let response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
let apiCall = await response.json();
let grouped_data = JSON.parse(apiCall.data);
})();
}}
>
Click Here
</Button>
<Modal open={open} onClose={handleClose}>
<Box>
<Typography>Random Text</Typography>
</Box>
</Modal>
</div>
);
}
In this example, I isolated the code into one single App.js on codesandbox. However, in practice I would have the Button and Modal combined into a separate component file with the API URL passed as a prop to the component. I also used JSONPlaceholder instead of my actual API. Currently, the button displays a modal which says "Random Text". However, I would like it to display the information retrieved using the fetch call.
Also, the table will have thousands of rows so I would like to avoid calling the API until the user actually clicks the button. That way I don't overload the server with thousands of separate API calls every time someone loads the page. I appreciate any help anyone could provide.
You would be better to create a variable to hold the information retrieved from the api, and a common function which would be called from the button click. Something like this:
import "./styles.css";
import React from "react";
import { Button, Modal, Typography, Box } from "#material-ui/core";
export default function App() {
const [open, setOpen] = React.useState(false);
const [data, setData] = React.useState(null);
const handleClose = () => {
setOpen(false);
setData(null);
};
const getData = async (someID) => {
try {
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${someID}`
);
let apiCall = await response.json();
let grouped_data = JSON.parse(apiCall.data);
setOpen(true);
setData(grouped_data);
} catch (error) {
//Handle errors
}
};
return (
<div className="App">
<Button
variant="outlined"
color="primary"
onClick={() => {
getData(someID);
}}
>
Click Here
</Button>
<Modal open={open} onClose={handleClose}>
<Box>
<Typography>{data ? data.some_property : ''}</Typography>
</Box>
</Modal>
</div>
);
}
I have an item and when I click on more info button a modal window with description appears but there is no endpoint like /modal in current route cause it's not external page I redirect to.
So in my modal window I have make a bid button and I can set a price in case I'm logged in.
otherwise Modal window with login form should appear with a request to log in.
This modal is on my navBar that is fixed whether I'm on current page or another one.
So how to pass this Modal Log in using function from another component ?
Here is my Modal with item description:
const ModalDetails = (props) => {
console.log(props, ' for modal')
const [loggedIn, setLoggedIn] = useState(false)
const checkUser = () => {
if (!loggedIn) {
/// how to pass that path to log in modal??
}
}
return (
{ item.typeSale == 'auction' && <button className='btn-item auction-btn-bet'
onClick={checkUser}>Make a bet</button>}
)
}
Log in modal in my App.js
const App = () => {
...
<Nav.Link className=' nav-item link-nav button-nav' onClick={handleShow}>
<img className='img-small' src={person} />
Log in
</Nav.Link>
<LoginForm show={show} handleShow={handleShow} handleClose={handleClose} />
}
I have everything as a separate component
I don't think it is possible. I think you can use Redux or Context to store the modal's open state.
I have a react modal, which inside a body contains a button, on clicking this button it opens new page eg payment page but the modal still open, now I want on the click it should open a new page and close the modal.
Here is my modal
function ModalA() {
const history = useHistory();
const dispatch = useDispatch();
const handleCloseModal = () => {
dispatch(actions.modalsActions.closeModal({
id: "ModalA",
}))
}
return (
<Modal id="ModalA">
<button onClick={}>Upgrade Now</button>
</Modal>
)
}
export default ModalA
Now when I click the Upgrade now button it open a new page but it does not close the modal.
Is there any good solution for this problem?
You just have to call both functions inside the onClick. The below code should work
<button onClick={() =>{
handleCloseModal()
history.push('/account/payments')
}}>Upgrade Now</button>