How to trigger API inside React Modal only when Modal is opened? - javascript

I am working on a REACT based web-app POC in my org. There is table of issues and for each of these issues, I have to provide a button in the table which when a user clicks on - it will open up a modal, fetch data for that issue via an API call and then broadcast the data in that modal.
The problem:
Let's say I have 300 issues listed in that table, hence there are 300 clickable buttons for opening modals and calling API. Now the problem is that, whenever that table loads, it calls APIs for all 300 issues at once, but I want each API to only be called when an user clicks on the respective button!
Here is the code for Modal component which I have managed so far:
import React, { FunctionComponent, useState, useEffect } from 'react'; // importing FunctionComponent
import { Modal, Button } from 'react-bootstrap';
type IssueReportProps = {
issueInfo: any
}
const IssueReport: FunctionComponent<IssueReportProps> = ({ issueInfo }) => {
const issueNumber: string = issueInfo.number;
const [show, setShow] = useState(false);
const [diagnosisInfo, setdiagnosisInfo] = useState({});
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
useEffect(() => {
async function fetchData() {
const res = await fetch("http://api-call/?issuenumber=".concat(issueNumber));
res.json().then(res => setdiagnosisInfo(res));
}
fetchData();
}, [issueNumber]);
console.log(diagnosisInfo);
return (
<>
<Button variant="outline-primary" onClick={handleShow} size="sm">
Diagnosis
</Button>
<Modal show={show} onHide={handleClose} backdrop="static" keyboard={false}>
<Modal.Body>
<p>
Issue Info: {JSON.stringify(diagnosisInfo)}
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default IssueReport;
The console.log(diagnosisInfo); confirms my suspicions that once the issue is loaded, APIs for all issues are called. How to prevent this?
Please let me know if I need to provide more details.
EDIT1: Accepted Solution-
Here is the change I made to the code post #Dykotomee's solution:
// useEffect:
useEffect(() => {
async function fetchData() {
const res = await fetch("http://api-call/?issuenumber=".concat(issueNumber));
res.json().then(res => setdiagnosisInfo(res));
}
// fetchData();
if (show){
fetchData();
}
}, [issueNumber, show]);

useEffect is called every time the component renders. Therefore, when the table loads with 300 components, the API is fetched 300 times!
You only want to fetch if the modal is showing. Try wrapping your call to fetchData in a conditional:
if (show) {
fetchData();
}
It's not ideal, considering the modal will likely show prior to the fetch being completed, but you can adjust your code or add more states to compensate.

Related

Axios and getRequest from external component

I'm new to React.js and I'm actually trying to create a page structured in the following way:
-A form to insert [version,date,image,content] and post a JSON via POST request (with Axios)
-A display part in which I GET the same data and display on screen by clicking on a button
actually I'm able to do this by introducing the Get and Post logic in the used component.
In order to use Components and have a clear code, i would to have a separate component to call inside the various components to make a GET or POST request.
By using hooks I'm not able to do this. The actual code is:
This is UserGet.js
import axios from "axios";
import {useState} from "react";
const baseURL = "http://localhost:8088/";
const userURL ="changelog/version.txt";
function UserGet(props) {
const [get, setGet] = useState(null);
axios.get(baseURL+userURL).then((response) => {
setGet(response.data);
});
return [get, setGet] ;
}
export default UserGet;
while in the component I want to use as data displayer:
const DisplayInfo =(props) => {
const [items, setItems] = useState([]);
const onFinish = (() => {
setItems(UserGet());
})
const DisplayData = items.map(
(info)=>{
return(
<div className='changelogDisplay' key={info.version}>
<button className='version' >v {info.version} </button>
<div className='datelog' > {info.data} </div>
<div className='dataHeader' > {info.title} </div>
<div className='whatsnew' > {info.text} </div>
<div className='imageLog' alt='Changelog pic' >{info.img} </div>
</div>
)
});
return(
<div>
<Form onFinish={onFinish}>
<Form.Item>
<Button type="primary" htmlType="submit" name='Refresh'> Refresh list</Button>
<div className='displayData'>
{DisplayData}
</div>
</Form.Item>
</Form>
</div>
)
}
export default DisplayInfo;
Actually, if I use
const response = UserGet();
I'm able to get data and insert into items for further process. I want to get the data using Onfinish or Onclick properties but I'm not able due to Hooks that cannot stay inside a function. How can I get a solution?
I tried different ways, and I don't want to use Redux at the moment in order to improve my knowledge on this.
Hooks are not so simple to me
Currently you're setting the items state to the [get, setGet] value that returns from the UserGet hook.
Here you return the data back from the request.
async function UserGet(props) {
const response = await axios.get(baseURL + userURL);
return response.data;
}
Then in the onFinish
const onFinish = (() => {
const newItems = UserGet();
setItems(newItems);
// or
// setItems(UserGet());
});
I hope this helps you with your project!

State messiness trying to open a dialog, yet allowing the dialog to close itself

I'm prototyping a new version of our application in React 18. I'm somewhat new to React and have stumbled upon a scenario that has a few different problems.
We need to open a modal/dialog when a user performs an action. They will click a button to edit data, that opens a dialog window with a form. When they close the dialog, the form data is passed back to the component which opened it.
In our old app, it would be something like const user = new UserModal(123)
I'm using BlueprintJS's Dialog component for this, but this issue is applicable to any library.
I'm writing a wrapper because all of our modals will have similar functionality so the props to the Dialog component will never change outside of whether it's open or not.
Here's a super basic example of this "wrapper" component:
export const Modal = ({ isOpen }: ModalProps) => {
const [isOpen2, setIsOpen] = useState(isOpen);
const handleClose = useCallback(() => {
setIsOpen(false);
}, []);
return (
<Dialog isOpen={isOpen2}>
<p>this is a dialog</p>
<Button onClick={handleClose} text="close" />
</Dialog>
);
}
Using this in the parent would look like this:
const Demo = () => {
const [isOpen, setIsOpen] = useState(false);
// some code calls setIsOpen(true) when we need to open the modal
return <Modal isOpen={isOpen} />;
}
This presents multiple problems:
A parent controller can trigger this dialog to open, but never close (interacting with the app is prevented while a modal is open)
The modal can close itself via an X or "Cancel" button
This leads to two useState invocations - one in the parent controller and one inside the modal. This doesn't work right by itself, because once the state is set in the controller, it can't update when the prop changes with more code
The parent controller would need to know when it closes so it can update it's own state value.
I really dislike having to put <Modal> elements in the parent jsx, I liked the new UserModal code but that might be a fact of life
Overall, this feels like a very wrong approach. How can I design this to be more "proper" and yet work the way I need?
you can pass your method from parent to child and call there and also you can use 1 state for manage modal status.
export const Modal = ({ isOpen, handleClose, closeCallBack }: ModalProps) => {
const handleCloseChild = () =>{
closeCallBack()
handleClose()
}
return (
<Dialog isOpen={isOpen}>
<p>this is a dialog</p>
<Button onClick={handleCloseChild} text="close" />
</Dialog>
);
}
and parent something like this
const Demo = () => {
const [isOpen, setIsOpen] = useState(false);
// some code calls setIsOpen(true) when we need to open the modal
const handleClose = () =>{
setIsOpen(false)
}
return <Modal isOpen={isOpen} handleClose={handleClose} closeCallBack={() => // do what you want on close modal or you just do this in side modal or even in handelClose function } />;
}

Render a react component in a new window

I am developing a Certificate Management System where after all the processes have been done, the user may print a certificate.
I am struggling to implement such that upon clicking the print button, a new tab will open containing the ready to print HTML certificate so that the user will only CTRL + P to have the certificate printed.
How do i render my react certificate component in a new window? Such that i would only pass the props which are the data to be put into the certificate e.g., name, date etc.. like <Certificate name={john} />
I have tried implementing the npm react-new-window but it does not work with
<Button onclick={() => {
<NewWindow>
<CertificateComponent>
</NewWindow>
}}
>
PRINT BUTTON
</Button>
I have looked into react portals but most use cases are for Modals, which is where my "PRINT" button is rendered.
Sorry for the bad english/explanation. Thank you!
New Solution based on CreatePortal
import React, { useEffect, useCallback, useMemo, useState } from "react";
import { render, createPortal } from "react-dom";
const App = () => {
const [isOpen, setOpenState] = useState(false);
const open = useCallback(() => setOpenState(true));
const close = useCallback(() => setOpenState(false));
return (
<div>
<h1>Portals in React</h1>
<button onClick={open}>Open</button>
<button onClick={close}>Close</button>
{isOpen && (
<NewWindow close={close}>
Example <button onClick={close}>Close</button>
</NewWindow>
)}
</div>
);
};
const NewWindow = ({ children, close }) => {
const newWindow = useMemo(() =>
window.open(
"about:blank",
"newWin",
`width=400,height=300,left=${window.screen.availWidth / 2 -
200},top=${window.screen.availHeight / 2 - 150}`
)
);
newWindow.onbeforeunload = () => {
close();
};
useEffect(() => () => newWindow.close());
return createPortal(children, newWindow.document.body);
};
render(<App />, document.getElementById("root"));
There can be multiple approaches for this.
Approach 1:
Create a new route and map the certificateComponent to it, make sure it doesn't have any authentication or any dependency to it.
Store the required data for certificateComponent either in session storage or local storage.
When the user clicks on print button, redirect the user to this new route using window.open("http://localhost:port/newroute").
In certificateComponent read the values stored in session/local storage and map it accordingly.
Approach 2:
Make the certificate component as an overlay which occupies the entire screen which shows up when the user click on print button.
If any UI elements need to be hidden, you can do something as shown below:
printFn = function() {
// show the certificate component
// hide the ui elements not required
// window.print()
}
This worked for me
const myWindow: any = window.open('','', 'height: 500;width:500');
ReactDOM.render(<Yourcomponent prop={propValue} /> , myWindow.document.body);
myWindow.document.close();
myWindow.focus();
myWindow.print();
myWindow.close();

Prevent modal from closing after re-render in react

Inside a Component, I have a Modal from which the user can do an update to some data through an API and then change the state of that main Component. Because there is a change in state, everything will re-render.
I would like to keep the modal open, so that I can show a success message.
The code would be something like this.
const Main = () => {
const [state, setState()] = useState();
return (
<Component state={state}>
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
</Component>
)
}
When user clicks on modal's button, the state changes, and Component is re-rendered. Modal is rendered too, as it's inside.
I have thought of two possible solutions:
Move the modal outside of the component. This is a problem, as my actual code is not as simple as the example I posted. In my code, the modal opens on the click of a button B, which is deep inside Component. So, if I move the modal out from Component, I would have to pass the status and the action to change status (e.g. [open, setOpen]) through several components until button B (prop drilling).
Another solution: On the action onButtonClick I just do the API update, and use a new state updated = true; then, onModalClose, only if updated is true, I run setState so Component is rendered just after the modal is closed. But this solution seems a hack to me. There must be a better way.
Is there any better solution?
Something is obviously going very wrong here, the Modal should not close. As a workaround you could do something like this:
const Main = () => {
const [state, setState()] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
), [])
return (
<Component state={state}>{modal}</Component>
)
}
Your Modal is re-rendering because your function passed as onButtonClick is redefined at every render.
So you have 2 options here:
1/ Keep your Modal inside your Component and use useMemo
import { useMemo } from 'react'
const Main = () => {
const [state, setState] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => (
updateThroughApi().then(() => setState())
)}
/>
), [])
return (
<Component state={state}>
{modal}
</Component>
)
}
Or 2/ Move your Modal outside your component and use combination of memo and useCallback
import { memo, useCallback } from 'react'
const Main = () => {
const [state, setState] = useState();
const onButtonClick = useCallback(() => updateThroughApi().then(() => setState()), []);
return (
<Component state={state}>
<Modal onButtonClick={onButtonClick} />
</Component>
)
}
const Modal = memo(({onButtonClick}) => {
})
So in this case, at every render, memo will compare if all Modal props are === from previous render, which is now the case, because memoization of onButtonClick with useCallback, and so your Modal component will not re-render
https://reactjs.org/docs/hooks-reference.html#usememo

Trigger usePosition() custom hook onClick element

I'm facing some troubles when I try to trigger usePosition hook in a onClick event.
What I want to achieve is to delay the geolocation permission prompt triggered by the browser until the user clicks some element.
What I've tried so far is a bunch of variations of the following code, but without success:
const IndexPage = () => {
const [geolocation, setGeolocation] = useState({});
const [isGeolocationActive, triggerGeolocation] = useState(false);
let currentPosition = usePosition();
function handleGeolocation() {
if (isGeolocationActive) {
setGeolocation(currentPosition)
console.log('geolocation', geolocation)
} else triggerGeolocation(true);
}
return (
<Layout>
<Wrapper type={`list`} classNames={`wrapper pet__cards cards__list`}>
<Card>
<Image/>
<div onClick={() => handleGeolocation()}>Click me to share my location</div>
I've tried to set a useState hook (initially set to false) which should control and handle the usePosition hook if is set to true but the browser still asking for geolocation permission as soon as I land on the page.
Edit: I've also tried to put the usePosition hook in another component and call it onClick event. However, in this case, I face some hooks rules error such as:
"Invalid hook call. Hooks can only be called inside of the body of a
function component. This could happen for one of the following
reasons..."
Finally I've solved the issue using usePosition hook in another component, something like this:
import React from "react";
import {usePosition} from "use-position";
const Geolocation = () => {
let currentPosition = usePosition();
return (
<div>
<p>{currentPosition.latitude}</p>
</div>
)
};
export default Geolocation;
While in my main component I've used a useState hook which controls the rendering like this:
const IndexPage = () => {
const [geolocation, setGeolocation] = useState('');
function handleGeolocation() {
setGeolocation(<Geolocation/>);
}
return (
<Layout>
<Card>
<Image/>
<div onClick={() => handleGeolocation()}>Click me to share my location</div>
{geolocation}
...

Categories

Resources