I want to archive an onClick event on an iframe that I use to show different invoices pdf.
For that I use a filelist:
const [filelist, setFilelist] = useState([])
{filelist?.map((element, index) => (
<div key={index} id={index + "_pdf"}
onMouseOver={(e) => handleOnMouseOver(e)}
onMouseOut={(e) => handleOnMouseOut(e)}
>
{<iframe title="pdf_viewer" src={`${element.file_path_fe}`} width="100%" height="400em" />}
</div>
))}
Now I've found this snippet:
https://codesandbox.io/p/sandbox/react-detect-mouse-click-over-iframe-708ys?file=%2Fsrc%2FApp.js%3A21%2C4
where clicks are detected via the blur effect.
Since I use functional components, I have rewritten it this way:
useEffect(() => {
...
// iframe clickable:
// Focus the page
window.focus();
// Add listener to check when page is not focussed
// (i.e. iframe is clicked into)
window.addEventListener("blur", onblur);
return () => {
// Anything in here is fired on component unmount.
// so clean up the window eventlistener
window.removeEventListener("blur", onblur);
}
// remove onblur missing dependency warning
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
and the functions:
const onblur = (e) => {
if (state) {
//window.focus();
console.log(e);
}
};
const handleOnMouseOver = (e) => {
console.log("mouse in");
setState({ iframeMouseOver: true });
};
const handleOnMouseOut = (e) => {
console.log("mouse out");
window.focus(); // Make sure to set focus back to page
setState({ iframeMouseOver: false });
};
It works, but it's not exactly how I want it to work. So my questions would be:
Edit: I had an idea on how to track the current iframe, I totally forgot that I have the mouseOver events. I added a variable:
const [pdfFrame, setPdfFrame] = useState([])
and set the variable in the mouseOver events:
const handleOnMouseOver = (e) => {
setPdfFrame([e.target])
setState({ iframeMouseOver: true });
};
const handleOnMouseOut = (e) => {
window.focus(); // Make sure to set focus back to page
setPdfFrame([])
setState({ iframeMouseOver: false });
};
but for some reason, the blur function always shows an empty variable, even though it's rerendered with the correct value:
console.log("Rendering with: ", pdfFrame);
const onblur = () => {
if (state) {
window.focus();
console.log(pdfFrame);
}
};
I think this would be the correct approach, but I miss something here.
Edit2: Thanks to Lanre I got it to work, here is the full component (without the irrelevant stuff):
import "./Matching.css"
import { useState, useEffect, React, useCallback } from 'react';
const Matching = () => {
const [filelist, setFilelist] = useState([])
const [state, setState] = useState([{ iframeMouseOver: false }])
const [pdfFrame, setPdfFrame] = useState(null)
const mandant = 2;
useEffect(() => {
/* here set the filelist, but only once on loading thus the []
Create an file folder in public folder and add the pdf with this name(or any other name, and adjust the json)
example:
{
"file_path": "./view/public/files/2/8dc9e7a05ad84f0bd675624416f7c308.pdf",
"file_path_fe": "./files/2/8dc9e7a05ad84f0bd675624416f7c308.pdf",
"hash": "8dc9e7a05ad84f0bd675624416f7c308",
"id": 1,
"mandant": "2",
"name": "Satzung.pdf",
"org_name": "Satzung.pdf",
"size": 126286,
"type": "application/pdf"
}
*/
axios.post("/select", {
"table": "files",
"columns": ["*"],
"where": `where mandant = ${mandant}`
}).then(res => setFilelist(res.data))
}, [])
//var markedRows = []
const onblur = useCallback(() => {
if (state.iframeMouseOver) {
setTimeout(() => {
window.focus();
}, 0);
}
}, [pdfFrame, state.iframeMouseOver]);
// componentDidMount equivalent
useEffect(() => {
// iframe clickable:
// Focus the page
window.focus();
// Add listener to check when page is not focussed
// (i.e. iframe is clicked into)
window.addEventListener("blur", onblur);
//console.log(pdfFrame);
return () => {
// Anything in here is fired on component unmount.
// so clean up the window eventlistener
window.removeEventListener("blur", onblur);
}
}, [onblur]);
const handleOnMouseOver = (e) => {
setPdfFrame(e.currentTarget.querySelector('iframe'))
setState({ iframeMouseOver: true });
};
const handleOnMouseOut = (e) => {
setPdfFrame(null)
setState({ iframeMouseOver: false });
window.focus(); // Make sure to set focus back to page
};
return (
<div className="matching-wrapper">
<div className="row">
<div className="col-3">
{filelist?.map((element, index) => (
<div key={index} id={index + "_pdf"}
onMouseOver={(e) => handleOnMouseOver(e)}
onMouseOut={(e) => handleOnMouseOut(e)}
>
{<iframe title="pdf_viewer" src={`${element.file_path_fe}`} width="100%" height="400em" />}
</div>
))}
</div>
<div className="col-2">
{/* other stuff not relevant to the question*/}
</div>
<div className="col-7">
{/* other stuff not relevant to the question*/}
</div>
</div>
</div >
);
};
export default Matching;
So I can suggest a couple of fixes that you could try. Looking through your code, I think you could make some changes like:
const onblur = () => {
if (state.iframeMouseOver) {
console.log("Iframe was clicked");
setTimeout(() => {
window.focus();
}, 0);
}
};
const [pdfFrame, setPdfFrame] = useState(null);
const handleOnMouseOver = (e) => {
setPdfFrame(e.currentTarget.querySelector('iframe'));
setState({ iframeMouseOver: true });
};
const handleOnMouseOut = (e) => {
setPdfFrame(null);
setState({ iframeMouseOver: false });
window.focus();
};
onblur
always has access to the current state and props too.const onblur = useCallback(() => {
if (state.iframeMouseOver) {
console.log("Iframe clicked:", pdfFrame);
setTimeout(() => {
window.focus();
}, 0);
}
}, [pdfFrame, state.iframeMouseOver]);
useEffect(() => {
window.addEventListener("blur", onblur);
return () => {
window.removeEventListener("blur", onblur);
};
}, [onblur]);
Let me know if this helps. Cheers!
Update: @user3793935 has updated the question with the working solution.