Render a react component in a new window - javascript

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();

Related

How to render a new popup every time I clicked Grid?

The problem is...
The first popup renders fine.
But when I try to render the second popup, it's not working.
A new popup is not invoked, the previous popup is refreshed.
I want to call a new popup when I clicked a cell in the grid.
my code is like this
const Main = () => {
const [isPopupOpen, setIsPopupOpen] = useState(false);
return (
<>
... other components (including grid)
{ isPopupOpen && <Popup />}
</>
)
};
when Grid is Clicked, 'isPopupOpen' is updated to true.
I use 'react-new-window' library, and this library use 'window.open()' ((https://github.com/rmariuzzo/react-new-window)
so I set different window names to call several popups.
but I can't solve the problem.
I try to set a state object that has a boolean value.
const [popupObj, setPopupObj] = useState({});
when the grid is clicked, popupObj updates like
{'cellA': true, 'cellD': true}
and a return statement is like
{popupObj[cellName] && <Popup /> }
but the result was the same.
what should I do to solve this problem?
I wrote an example for you. Hope it helps.
use popupIds state to store the popups that you want to open
use Set to toggle the popupIds in the addPopup click handler
import * as React from "react";
export default function App() {
const [popupIds, setPopupIds] = React.useState([]);
const addPopup = (popupId) => {
const set = new Set(popupIds);
if (set.has(popupId)) {
set.delete(popupId);
} else {
set.add(popupId);
}
setPopupIds(Array.from(set));
};
return (
<div className="App">
{["hello", "react"].map((popupId) => (
<div onClick={() => addPopup(popupId)}>{popupId}</div>
))}
{popupIds.map((popupId) => (
<Popup title={getPopupTitle(popupId)} />
))}
</div>
);
}
const getPopupTitle = (popupId) => `title for ${popupId}`;
const Popup = ({ title }) => <div>{title}</div>;
Here is a codesandbox that you can play with directly.
You need to add your popup in an array, so you can render many popup as you want, then you need to define in How much time you will remove a added popup from array or add a close button
Extra: you can configure in global state to access in all your application to your popups and you will have a component like this: https://www.npmjs.com/package/notistack

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 } />;
}

how to create page url preview in react js?

I have a react page builder, I am displaying user-created pages using dangerous HTML now I want when the user clicks a button preview to open this page on a new window.
Here is my component to display user pages
import React, { useState, useEffect} from "react";
import api from "../utils/api";
function MyPages() {
const [MyPagesList, setMyPagesList] = useState();
const getMyPages = async () => {
try {
const response = await api.get("/pages");
setMyPagesList(response.data.data);
} catch (error) {}
};
useEffect(() => {
getMyPages();
}, []);
return (
<div className="all-mypages">
<div className="all-mypages__cards">
{MyPagesList && MyPagesList.map(function (data, id) {
return (
<div className="mypages-card" key={id}>
<a className="mypages-card__link">
<div className="mypages-card__content"
dangerouslySetInnerHTML={{__html: data.attributes.html}}></div>
</a>
<button className="preview " onClick={()=>history.push(`/mypages/preview/${data.attributes.html}`)>Preview</button>
</div>
);
})}
</div>
</div>
);
}
export default MyPages;
When user clicks preview button here is what I get
My solution is not working and I am out of ideas, what do I need to do to solve this issue?
The result is correct, because you are appending the html to /mypages/preview/. If you want the data.attributes.html ( which is the user html content ) to be displayed on a new page then:
Create another React component which looks for this html content from localStorage redux
When clicked on the preview button open a new page with url in react router pointing to this component
This component on load will get the localStorage html content and pass it to its dangerouslySetInnerHtml and display it.

Check changes before routing in React / Next js

I am having a Next JS app where there are very simple two pages.
-> Home page
import Header from "../components/header";
const handleForm = () => {
console.log("trigger");
};
export default () => (
<>
<Header />
<h1>Home</h1>
<form onSubmit={handleForm}>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<button type="submit"> Login </button>
</form>
</>
);
-> About page
import Header from "../components/header";
export default () => (
<>
<Header />
<h1>About us</h1>
</>
);
Requirement:
-> Home page has a login form
-> If user started typing in any of the fields then without submitting the form, if he tries to move to About us page then a warning needs to be displayed something similar like beforeunload_event.
I am not sure how we can handle it in react as I am new to it.. Kindly please help me to handle a alert if user trying to navigate to other url while editing the form fields..
From my understanding, you can achieve your goal by listen the event routeChangeStart as then throws exception in case of rejecting to move the target url.
I forked above codesandbox and created a simple demo based on your idea which doesn't allow to switch page in case of username having value (form is dirty).
Here is the general idea:
import router from "next/router";
export default () => {
// Assume this value holds the status of your form
const [dirty, setDirty] = React.useState();
// We need to ref to it then we can access to it properly in callback properly
const ref = React.useRef(dirty);
ref.current = dirty;
React.useEffect(() => {
// We listen to this event to determine whether to redirect or not
router.events.on("routeChangeStart", handleRouteChange);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, []);
const handleRouteChange = (url) => {
console.log("App is changing to: ", url, ref.current);
// In this case we don't allow to go target path like this
// we can show modal to tell user here as well
if (ref.current) {
throw Error("stop redirect since form is dirty");
}
};
return (
// ...
)
}
The link codesandbox is here https://codesandbox.io/s/react-spring-nextjs-routes-forked-sq7uj

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

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.

Categories

Resources