How to stop useEffect from loading my modal multiple times? - javascript

I have a function based React view which has a modal that loads based on the presence of an item in the local storage:
function myView() {
...
function HelpModal() {
const [show, setShow] = useState(false);
useEffect(()=>{
let pop_status = localStorage.getItem('this-page-modal');
if(!pop_status){
setShow(true);
}
},[])
if(!setShow) return null;
const handleClose = () => {
localStorage.setItem('this-page-modal', '1');
setShow(false);
};
const handleShow = () => setShow(true);
return (
<>
<Button variant="primary" onClick={handleShow}>
Button text
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Body>
<p>Hey! This is a modal.</p>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
}
return (
<div>
<Container style={{marginTop: "1em"}}>
<Row>
<Col><h1>My View</h1></Col>
<Col><span style={{float: "right"}}> <HelpModal /></span></Col>
</Row>
</Container>
</div>
);
}
The problem: When I load the page, the modal flickers and loads 2 or 3 times.
How do I make it load only once? Any suggestions will be appreciated.

For the general case, you can use useLayoutEffect to make the update occur quicker, near instantaneously, instead of a bit after a repaint with useEffect.
Also, the if(!setShow) return null; line is completely superfluous because the useState setter function will always be a function, which is truthy.
useLayoutEffect(() => {
const pop_status = localStorage.getItem('this-page-modal');
if (!pop_status) {
setShow(true);
}
}, []);
But since the function inside the effect is completely synchronous, it'd make more sense to use the storage value as the initial value for the show state, instead of calling the state setter; you an remove the effect entirely.
const [show, setShow] = useState(!localStorage.getItem('this-page-modal'));
const handleClose = () => {
localStorage.setItem('this-page-modal', '1');
setShow(false);
};
const handleShow = () => setShow(true);
return (
// ...

Your setup could be causing re-renders based upon code not represented here so it's difficult to diagnose the exact issue. One thing to try might be to put a console.log statement in various function scopes to see how many times it's being re-rendered on load. Aside from that, (and concurring with previous answer) I think you might be able to simplify your code down a bit and get the same result by having the conditional render be straight from localStorage. Maybe something like:
function HelpModal() {
const [show, setShow] = useState(localStorage.getItem("this-page-modal"));
const handleClose = () => {
setShow(false);
localStorage.setItem("this-page-modal", false);
};
const handleShow = () => {
setShow(true);
localStorage.setItem("this-page-modal", true);
};
return (
<>
<Button variant="primary" onClick={handleShow}>
Button text
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Body>
<p>Hey! This is a modal.</p>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
}
function myView() {
return (
<>
<HelpModal />
</>
);
}

Related

How to create a Modal in react triggered without button

I want to create a model in react material UI library which shows the info on the landing page as the user logs in.
I have tried triggering with timeout but im unable to close the modal once it appears.
const[post,setPost] = useState('')
const [open, setOpen] = useState(false);
const handleClose = () => {
setOpen(false);
};
useEffect(() =>{
setTimeout(()=>{
setNotesPopup(true);
},3000);
},[]);
const Popup=((props) =>{
return(props.trigger)
})
return (
<>
<NavigationBar/>
<SydBackdrop openBackdrop={openBackdrop} />
<Container maxWidth="xl" className="mb-2">
<Card className={classes.card}>
<CardHeader title="Customer Search Parameters" className={classes.cardHeader}/>
<CardContent>
<Dialog trigger={notesPopup} setTrigger={setNotesPopup} aria-labelledby="customized-dialog-title" open={open}>
<DialogTitle id="customized-dialog-title"> UI changes</DialogTitle>
<DialogContent dividers><Typography gutterBottom><Markdown>{post}</Markdown></Typography></DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">OK</Button>
</DialogActions>
</Dialog>
</>
)
}
Consider using a global state that is available to your entire app like Redux or React's context api.
Put a property that has modalStatus as false. When the user logs in, update that value in your global state to true and when the user closes the modal, it sets it to false

Rendering Modal Popup

My goal is to launch a modal pop-up during the event a button is clicked. I've been having issues making the Axios API call to the function that contains the modal. The process of the API call has been listed below:
First: This is the button where the Axios post request is made (addToCart) (works fine)
<button className="button is-primary" onClick={addToCart}>
ADD TO CART
</button>
Second: This is the Axios post request that makes a call to the modal pop-up (Example()) (works fine)
const addToCart = () => {
Axios.post(
//standard backend API call is made here
)
.then((res) => {
console.log("post called")
Example(); //I want to launch my modal when this state is reached
})
.catch((err) => {
if (err.response && err.response.status === 401) {
setIsLoggedIn(false);
}
});
Third: This is the modal that I'd like to popup but have been having issues rendering the function (Issue)
https://react-bootstrap.github.io/components/modal/
function Example() {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Button variant="primary" onClick={handleShow}>
Launch demo modal
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</>
);
}
render(<Example />);
I receive the following error:
Line 38:3: 'render' is not defined no-undef
How can I properly render my modal pop-up?
Instead of having a show state in your Example component, receive this data in a prop. In addition, you'll also want to pass some callbacks to the component to be able to react to certain events (e.g. clicking "Close" or "Save Changes").
I'd do the following:
const Example = ({ show, onClose, onSave }) => (
<Modal show={show} onHide={onClose}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={onClose}>
Close
</Button>
<Button variant="primary" onClick={onSave}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
)
This way you could always render the Example component in some parent component and show it conditionally, like so:
const SomeParentComponent = () => {
const [showExample, setShowExample] = useState(false)
const addToCart = () => {
Axios.post("http://example.com/api/add-to-cart")
.then((res) => {
console.log("post called")
setShowExample(true)
})
.catch((err) => {
if (err.response && err.response.status === 401) {
setIsLoggedIn(false);
}
})
}
return (
<>
<button className="button is-primary" onClick={addToCart}>
ADD TO CART
</button>
<Example
show={showExample}
onClose={() => setShowExample(false)}
onSave={() => setShowExample(false)}
/>
</>
)
}

React hook: How to call Modal component from another component in react

I am trying to create a modal that I can reuse/call from multiple components. I want the modal to display in app.js but the button call is on another component.
Once I am able to implement one, I can but button on other components and call same modal instead of having to create the same modal for each component
<div className="App">
<HeroSlider />
<HowItWorks />
<Modal />
<Footer />
</div>
The modal button is on this component(HeroSlider). Once its click it will call the modal component and display it in app.js
import React, { useState } from "react";
import Header from './Header'
function HeroSlider(props) {
const [show, setShow] = useState(false);
const modalShow = () => setShow(true);
const openIsAccount = () => {
}
return (
<div className="jumbotron" id="jumbotron2" >
<button type="button" className="btn btn-primary" id="shoutBtn" onClick={modalShow}><span>Get Started</span>
</button>
</div>
);
}
export default HeroSlider;
Here is the Modal.js
const IsAccountOpen = (props) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Modal show={props.show} onHide={handleClose} backdrop="static" keyboard={false}>
<Modal.Header closeButton>
<Modal.Title>Modal title</Modal.Title>
</Modal.Header>
<Modal.Body>
I will not close
</Modal.Body>
</Modal>
</>
);
};
export default IsAccountOpen
You need to add a function in the app.js to handle the click on the button, so the function will change the state show to true, and you will pass the state to your modal, like this:
App.js
[showState, setShowState] = useState(false)
buttonClickedHandler = () => {
setShowState((showState) => showState = !showState )
}
<div className="App">
<HeroSlider buttonClicked={buttonClickedHandler} />
<HowItWorks />
<Modal show={showState} buttonClicked={buttonClickedHandler} />
<Footer />
</div>
HeroSlider.js
import React, { useState } from "react";
import Header from './Header'
function HeroSlider(props) {
const [show, setShow] = useState(false);
const modalShow = () => setShow(true);
const openIsAccount = () => {
}
return (
<div className="jumbotron" id="jumbotron2" >
<button type="button" className="btn btn-primary" id="shoutBtn" onClick={props.buttonClicked}><span>Get Started</span>
</button>
</div>
);
}
export default HeroSlider;
IsAccountOpen.js
const IsAccountOpen = (props) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Modal show={props.show} onHide={props.buttonClicked} backdrop="static" keyboard={false}>
<Modal.Header closeButton>
<Modal.Title>Modal title</Modal.Title>
</Modal.Header>
<Modal.Body>
I will not close
</Modal.Body>
</Modal>
</>
);
};
export default IsAccountOpen
I think the best solution is to use redux, because you will need to access and update the state from different components, you shouldn't use context API because the state changes frequently and you will hit the performance.
You'll need to extract the state to App level, so that this can be shared to components. You can do this with useState, or using context & custom hooks if you want to make it very clean. But do it with useState on first.
HeroSlider should receive modalShow function in it's props.
Modal.js should receive the show state as props.

Dispatch a Redux action from a modal in React

I'm a begginer in React and I'm trying to find a way to dispatch a redux action from a modal.
I have a list of products and a button 'add to bag' under each image. When we click on the button 'add to bag', I want a modal to appear that ask for a confirmation. The action need to be dispatched when the user click on the confirm button inside de modal window.
The action need to grab the item object.
All is working fine ...but I'm not able to pass the item into the action when I want to launch the action from the modal.
So, my problem is not the Redux side but the modal part ( I use React-bootstrap for the modal).
I have this error message : 'item' is not defined
I'm not sure I understand exactly why this does'nt work and I failed to find a solution. I tried so many things but it's just not working.
Is there a simple way to add/pass the item data into the modal easily ?
Help would be very appreciated ! :)
Thanks a lot !!!
Here are part of my files :
Product.js
import { Modal } from "react-bootstrap";
function Products(props) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<div>
<CardGroup>
{_.map(props.data, (item) => (
<Col>
...Some code here
<div>
<div>
{item.name.map((item) => (
<ul key={item}>{item}</ul>
))}
</div>
<div>
<button onClick={() => {handleShow()}}>
Add to bag
</button>
</div>
</div>
</Col>
))}
</CardGroup>
<Modal show={show} onHide={handleClose}>
<Modal.Body>Please confirm you want to add this product</Modal.Body>
<Modal.Footer>
<button
onClick={props.addProductToBasket(item)}
>
Confirm
</button>
<button onClick={handleClose}>Cancel</button>
</Modal.Footer>
</Modal>
</div>
);
}
const mapStateToProps = (state) => {
return { code here ...};
};
const mapDispatchToProps = (dispatch) => {
return {
addProductToBasket: (id) => dispatch(addProductToBasket(id)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Products);
Here is the part of my Product.js file im my Store/actions folder
export const addProductToBasket = (id) => {
return {
type: ADD_PRODUCTD_TO_BASKET,
payload: id,
};
};
Since you only show the modal if an item was clicked, store the item or null instead of Boolean in the state, and open and close the modal accordingly:
function Products(props) {
const [selectedItem, setShowItem] = useState(null); // item for open model or null for closed
const handleClose = () => setShowItem(null);
const handleShow = item => setShowItem(item);
return (
<div>
<CardGroup>
{_.map(props.data, (item) => (
<Col>
...Some code here
<div>
<div>
{item.name.map((item) => (
<ul key={item}>{item}</ul>
))}
</div>
<div>
<button onClick={() => handleShow(item)}>
Add to bag
</button>
</div>
</div>
</Col>
))}
</CardGroup>
<Modal show={selectedItem !== null} onHide={handleClose}>
<Modal.Body>Please confirm you want to add this product</Modal.Body>
<Modal.Footer>
<button
onClick={() => props.addProductToBasket(selectedItem)}
>
Confirm
</button>
<button onClick={handleClose}>Cancel</button>
</Modal.Footer>
</Modal>
</div>
);
}
Not related, but it will make your life easier - use the object form of mapDispatchToProps that will save you the need to wrap with dispatch manually:
const mapDispatchToProps = {
addProductToBasket
};

React (Nextjs): render table row to boostrap modal onclick

I have the following Nextjs page using React bootstrap modal and a MUI datatables component:
const Start = () => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const options = {
onTableInit: rowData,
onRowClick: rowData => Testing(rowData),
}
return (
<Layout>
<Modal show={show} onHide={handleClose}>
<Modal.Header>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>
Modal content
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
<MUIDataTable
title={"Datatable with modal onRowClick"}
data={data}
columns={columns}
options={options}
responsive="scrollFullHeight"
/>
</Layout>
)
}
export default Start;
The bootsrap modal works fine and opens onRowClick, I am also able to console log the rowData in a function when clicking a certain row:
function Testing(rowData) {
console.log(rowData)
);
I would like to achieve passing this rowData to the bootstrap modal accordingly when clicked on certain row.
I know I should be using the mapping, since it's an JSON object
{rowData.map(item => {
return <li key={item}>{item}</li>
}
)}
But how to integrate this into my current system? I get the error on rowData is not a function.

Categories

Resources