When i try to manipulate iframe i always get the cors issue permission denied to access property document on cross-origin, same goes when i try to attach event listener.
So is it possible to manipulate DOM elements inside iframe that is coming from different origin?
I'm happy to somehow also take a copy of iframe and just have it in my code as long as it allows me to edit it later, but unsure how?
import { useEffect } from 'react';
const post = () => {
const iframe = document.querySelector('iframe');
const message = { type: 'modifyDOM', text: 'Hello World!' };
// window.postMessage(message);
if (iframe?.contentWindow) {
iframe.contentWindow.postMessage(message, 'http://example.com');
}
};
export const IFrame = () => {
useEffect(() => {
const iframe = document.querySelector('iframe');
const receiveMessage = (e: MessageEvent) => {
if (e.data.type === 'modifyDOM') {
console.log(e.data);
// window.document.body.innerHTML = '<h1>Hello World!</h1>';
if (iframe?.contentWindow) {
iframe.contentWindow.document.body.innerHTML = '<h1>Hello World!</h1>';
}
}
};
window.addEventListener('message', receiveMessage);
return () => {
window.removeEventListener('message', receiveMessage);
};
}, []);
return (
<div>
<button onClick={post}>Post</button>
<iframe src="http://example.com" />
</div>
);
};
Related
I have a react-redux app which runs inside iframe, Iframe with same exact url is getting removed and added back by the parent application but doing so is causing memory footprint and CPU usage growing as we keep doing more number of times (reaches 5 GB after trying around 100 times), I believe it may not be something related to just react but in general as well, any help would be much appreciated.
React App in iframe:
const postMessage = () => {
window.parent.postMessage(JSON.stringify({ type: "TYPEB", message: "some message to parent" }), location.origin);
}
const messageEventHandler = (event) => {
//event handling code
}
const addMessageEventListenerFromParent = () => {
window.addEventListener("message", messageEventHandler);
};
const rootComponent = () =>
useEffect(() => {
// adding event listeners
addMessageEventListenerFromParent();
postMessage();
return () => {
console.log("unmounting the app");
removeMessageEventListenerFromParent();
};
}, []);
}
Parent App (React app):
const Iframe = (props) => {
const iframeRef = useRef(null);
const postMessage = (message) => {
if (iframeEl?.current.contentWindow?.postMessage) {
iframeEl.current.contentWindow.postMessage(message, location.origin);
}
};
useEffect(() => {
postMessage({
type: "TYPEA",
payload: somePayload,
})
}, [props.propA])
const memoizedIframe = useMemo(() => (
<iframe ref= { iframeRef } key = { key } className = "iframetask" src = { sourceUri } />
), [inputs]);
return memoizedIframe;
}
export default memo(Iframe);
This is a sample code of my app (running in iframe), return function (cleanup function) not getting executed when the iframe is removed and added back by parent app, this means the component is not getting unmounted but getting mounted as iframe is re-inserted.
I am trying to turnoff camera and flashlight when the component gets unmount , I am using react hook I have two function startCamera() and stopCamera() , I am calling startcamera when the component gets mount and stop camera when component gets unmount.
But its showing me error when stopCamera is called while unmounting
i have created an another button to test if StopCamera() working and i found its working , but i want to call the function when component is getting unmounted
my code:
CameraScreen.js
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function CameraScreen() {
const videoElement = useRef(null);
const [facingMode, setFacingMode] = useState("environment");
const handleFacingModeToggle = () => {
stopCamera();
facingMode === "environment"
? setFacingMode("user")
: setFacingMode("environment");
};
useEffect(() => {
// const getUserMedia = async () => {
// try {
// const stream = await navigator.mediaDevices.getUserMedia({
// video: true
// });
// videoElement.current.srcObject = stream;
// } catch (err) {
// console.log(err);
// }
// };
// getUserMedia();
startCamera();
return function cleanup() {
stopCamera();
};
}, []);
const stopCamera = () =>
videoElement.current.srcObject &&
videoElement.current.srcObject.getTracks().forEach((t) => t.stop());
function startCamera() {
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
.getUserMedia({
video: { facingMode: facingMode },
width: { ideal: 1280 },
height: { ideal: 720 }
})
.then(function (stream) {
if (videoElement.current) videoElement.current.srcObject = stream;
const track = stream.getVideoTracks()[0];
//Create image capture object and get camera capabilities
const imageCapture = new ImageCapture(track);
const photoCapabilities = imageCapture
.getPhotoCapabilities()
.then(() => {
//todo: check if camera has a torch
//let there be light!
track.applyConstraints({
advanced: [{ torch: true }]
});
});
})
.catch(function (error) {
alert("Please check your device permissions");
console.log("Something went wrong!");
console.log(error);
});
if (videoElement.current)
videoElement.current.onloadeddata = function () {
if (window.NativeDevice)
window.NativeDevice.htmlCameraReadyToRecord(true);
};
}
}
return (
<>
<video
autoPlay={true}
ref={videoElement}
style={{
minHeight: "67.82vh",
maxHeight: "67.82vh",
maxWidth: "100%",
minWidth: "100%"
}}
className="border-3rem bg-[#666]"
></video>
<button onClick={stopCamera}> stopCamera</button>
</>
);
}
App.js
import "./styles.css";
import { useState } from "react";
import CameraScreen from "./cameraScreen";
export default function App() {
const [switchS, setSwitchS] = useState(false);
return (
<div>
<button className="" onClick={() => setSwitchS(!switchS)} value="switch">
switch
</button>
{switchS && <CameraScreen />}
{!switchS && "Blank Screen"}
</div>
);
}
PS: the above code working at :https://5t2to.csb.app/
codesandbox link : https://codesandbox.io/s/practical-fast-5t2to?file=/src/cameraScreen.js
You can use useLayoutEffect hook. It works just before unmounting, like componentWillUnmount.
Here is an example to that
https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/random.js
You can go to https://ikqdn.csb.app/rand in sandbox browser and check the console on clicking to home button.
You can see the difference in working while unmounting of both useEffect and useLayoutEffect
It preserves ref.current, so what you can do is, you can pass ref.current in the function that you are calling just before unmounting, to prevent ref to the dom elment.
It took a bit of debugging/sleuthing to find the issue. So even though you have a ref attached to the video element, when the component is unmounted the ref is still mutated and becomes undefined. The solution is to save a reference to the current videoElement ref value and use this in a cleanup function.
useEffect(() => {
startCamera();
const ref = videoElement.current;
return () => {
ref.srcObject.getTracks().forEach((t) => t.stop());
};
}, []);
Simply add useLayoutEffect to stop camera
useEffect(() => {
// const getUserMedia = async () => {
// try {
// const stream = await navigator.mediaDevices.getUserMedia({
// video: true
// });
// videoElement.current.srcObject = stream;
// } catch (err) {
// console.log(err);
// }
// };
// getUserMedia();
startCamera();
}, []);
useLayoutEffect(()=>()=>{
stopCamera();
},[]);
Just need to change useEffect() to useLayoutEffect()and it will works like a charm.
useLayoutEffect(() => {
const ref = videoElement;
console.log(ref);
startCamera();
return function cleanup() {
stopCamera();
};
}, []);
sanbox link :- https://codesandbox.io/s/naughty-murdock-by5tc?file=/src/cameraScreen.js:415-731
I used to use react-pageflip on Reactjs exactly like the code below and it worked fine. When I copied the same component, I got this error in the nextjs project.
error:
TypeError: _this2.flipBook.getPageFlip is not a function
code:
import HTMLFlipBook from "react-pageflip";
class Book extends Component {
...
onFlip(data) {
this.setState({ page : data});
}
nextButtonClick = () => {
this.flipBook.getPageFlip().flipNext();
};
prevButtonClick = () => {
this.flipBook.getPageFlip().flipPrev();
};
rendr(){
return(
<HTMLFlipBook maxShadowOpacity={1} mobileScrollSupport={true}
className="demo-book"
drawShadow={true}
onFlip={(e) => this.onFlip(e.data)}
changeOrientation="portrait"
ref={(el) => (this.flipBook = el)}
>
<div classname={page1}>
page1
</div>
<div classname={page2}>
page2
</div>
</HTMLFlipBook>
)
}
this.flipBook.getPageFlip().flipNext(); in the block inside the nextButtonClick and prevButtonClick functions, you should use this.flipBook.pageFlip().flipNext() by flipping. It worked for me, I hope it works for you too.
nextButtonClick = () => {
this.flipBook.pageFlip().flipNext()
};
prevButtonClick = () => {
this.flipBook.pageFlip().flipPrev();
};
For function components
This work for me
const bookRef = useRef(null)
const nextButtonClick = () => {
bookRef.current.pageFlip().flipNext()
}
const prevButtonClick = () => {
bookRef.current.pageFlip().flipPrev()
}
<HTMLFlipBook
onFlip={onFlip}
width={616}
height={872}
showCover={true}
ref={bookRef}
>
// ...my render page
</HTMLFlipBook>
I am learning to test React app using jest and Enzyme . I have created a component and using redux to maintain and update the state . The component code is below .
Now i want to write the test to check initial value of prodOverviewAccordion which we are setting as true in context file.
I have tried writing , but getting error . Sharing the test code also . Please help
const ProdOverview = () => {
const {
productState,
setProdOverviewAccordion
} = React.useContext(ProductContext);
const {prodOverviewAccordion } = productState;
const [completeStatusprod, setCompleteStatusprod] = useState(false);
return (
<div onClick={toggleTriggerProd}>
<s-box>
<Collapsible
trigger={
<Accordion
name={ProductConfig.accordionTriggerLabels.prodOverviewLabel}
completeStatusIcon={completeStatusprod ? 'check-circle' : 'alert-triangle'}
completeStatus={completeStatusprod}
/>
}
easing='ease-out'
handleTriggerClick={() => {
if (!prodOverviewAccordion) {
setProdOverviewAccordion(true);
} else {
setProdOverviewAccordion(false);
}
}}
open={prodOverviewAccordion}
data-test='prodOverViewCollapsible'
>
<p>Test</p>
</Collapsible>
</s-box>
</div>
);
};
export default ProdOverview;
const prodsetup = (props = {}) => {
return shallow(<ProdOverview />);
};
describe('Product Overview panel Test', () => {
const mockSetCurrentGuess = jest.fn();
beforeEach(() => {
mockSetCurrentGuess.mockClear();
});
test('should render Collapsible panel', () => {
const wrapper = prodsetup();
const component = findByTestAttr(wrapper, 'prodOverViewCollapsible');
expect(component.length).toBe(1);
});
test('Product Overview Panel should be in open state', () => {
const wrapper = prodsetup();
expect(wrapper.state().prodOverviewAccordion.to.equal(true));
});
});
const [rec, setRec] = useState({});
const [onRec, setOnRec] = useState(true);
useEffect(() => {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
const mediaRecorder = new MediaRecorder(stream)
setRec(mediaRecorder)
})
}, [onRec]);
This is useEffect.
const onRecAudio = () => {
rec.start()
console.log(rec);
console.log("start")
setOnRec(false)
}
This is first click of function. recording start.
const offRecAudio = () => {
rec.stop()
console.log("stop")
setOnRec(true)
}
This is second click of function. recording end.
<button onClick={onRec ? onRecAudio : offRecAudio } />
I don't want the useEffect to run those statements when the component is first rendered, but just click a button to run them. Press once to start recording, press again to end recording. But when I press it again, I see this error.
I've come across the same error but with different patter, I didn't save MediaRecorder in state because I found it's hard to deal with complex objects and Web API interfaces when saving them and restore them from the state, so I used a react ref and two buttons for start and stop, the error was showing when I've forget to unbind the stop event listener, but after unbinding it, it works great.
I'll share my code with you and for the future people who want to record audio using react in a simple way:
import { useRef } from 'react';
declare var MediaRecorder: any;
export function ChatsInputs() {
const stopButtonRef = useRef<HTMLButtonElement>(null);
function startRecording() {
navigator
.mediaDevices
.getUserMedia({ audio: true, video: false })
.then(function (stream) {
const options = { mimeType: 'audio/webm' };
const recordedChunks: any = [];
const mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.addEventListener('dataavailable', function (e: any) {
if (e.data.size > 0) recordedChunks.push(e.data);
});
mediaRecorder.addEventListener('stop', function () {
setBlobUrl(URL.createObjectURL(new Blob(recordedChunks)));
});
if (stopButtonRef && stopButtonRef.current)
stopButtonRef?.current?.addEventListener('click', function onStopClick() {
mediaRecorder.stop();
this.removeEventListener('click', onStopClick)
});
mediaRecorder.start();
});
}
return (
<div>
<button onClick={startRecording}>{'rec'}</button>
<button ref={stopButtonRef}>{'stop'}</button>
<a download="file.wav" href={blobUrl}>{'download audio'}</a>
{
blobUrl ?
<audio id="player" src={blobUrl} controls></audio>
:
null
}
</div>
);
}
Ref Article