ReactJS: Data not binding from continuous response - javascript

While integrating the aws ivs streaming channel with quiz related metadata, at that time getting the console.log of the metadata records and while passing those records into another component it is not handling any how.
A playground that i have created into codesandobx
PlayerComponent
function PlayerComponent(options) {
useEffect(() => {
const script = document.createElement("script");
script.src = "https://player.live-video.net/1.0.0/amazon-ivs-player.min.js";
script.async = true;
document.body.appendChild(script);
script.onload = (IVSPlayer) => {
if (IVSPlayer.isPlayerSupported) {
const player = IVSPlayer.create();
player.attachHTMLVideoElement(document.getElementById("video-player"));
player.load(
"https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.xhP3ExfcX8ON.m3u8"
);
player.play();
player.addEventListener(
IVSPlayer.PlayerEventType.TEXT_METADATA_CUE,
(cue) => {
const metadataText = cue.text;
setMetaData(metadataText);
console.log("PlayerEvent - METADATA: ", metadataText);
}
);
}
};
return () => {
document.body.removeChild(script);
};
}, []);
return (
<div ref={divEl}>
<video id="video-player" ref={videoEl} autoPlay controls></video>
{metaData !== undefined ? <QuizComponent metadata={metaData} /> : ""}
</div>
);
}
On QuizComponent would like to render those metadata
export default function QuizComponent(props) {
const questionData = props;
return (
<React.Fragment>
<h2>{questionData.metadata.question}</h2>
</React.Fragment>
);
}
But any how not able to render the data into component.
Ref example of what I am going to implement.
https://codepen.io/amazon-ivs/pen/XWmjEKN?editors=0011

I found the problem. Basically you are referring IVSPlayer as if it was the argument of the arrow function you passed to script onload, while the argument instead is an event (the onload event).
Solution: const {IVSPlayer} = window;. Infact docs say
Once amazon-ivs-player.min.js is loaded, it adds an IVSPlayer variable to the global context.
Docs also explain how to setup with NPM which you may be interested in.
I updated my playground here.
I also suggest you to edit the version of the player as the last one is 1.2.0.

Related

What does this getFirebaseRoot error occur on InterstitialAd?

Creating a react-native app and trying to ad ads through admob and firebase, but getting an error I couldn't find much on.
Relevant parts of my App.js:
...
import {
InterstitialAd,
TestIds,
AdEventType,
} from '#react-native-firebase/admob';
...
showAds = () => {
let interstitial = InterstitialAd.createForAdRequest(TestIds.INTERSTITIAL);
let interstitialListener = interstitial.onAdEvent(type => {
if (type === AdEventType.LOADED) {
interstitial.show();
}
});
interstitial.load();
return () => {
interstitialListener = null;
};
};
onEvent = e => {
if (e.type === 'game-over') {
this.setState({
running: false,
});
this.showAds();
};
UPDATE:
Following this guide instead but getting another error.
Yeah sorry to tell you but #react-native-firebase/admob is now deprecated The last supported version was 11.5.0. I wasted time with this too because this old website (https://rnfb-docs.netlify.app) says it exist. When really you should use this one (https://rnfirebase.io). What I did was used (https://www.applovin.com/) for ads it was really easy to setup.

ReactJS - Adding (Mollie) script to React page

I'm trying to import a script called Mollie (used for payments), but I'm not sure how to do it in React.
Normally in Javascript you would do something like this:
<html>
<head>
<title>My Checkout</title>
</head>
<body>
<script src="https://js.mollie.com/v1/mollie.js"></script>
</body>
</html>
I've tried this (according to other Stackoverflow posts)
useEffect(() => {
const script = document.createElement("script");
script.src = "https://js.mollie.com/v1/mollie.js";
script.addEventListener("load", () => setScriptLoaded(true));
document.body.appendChild(script);
}, []);
const mollie = Mollie(); // Mollie is not defined
But then Mollie is undefined. Can anyone point in the right direction on how to import Mollie in React?
I'm following this guide (but it's for standard Javascript)
You can easily install this package from npmjs.com where you can find necessary documentations and examples to get started. Installation:
npm i #mollie/api-client
the point here is that the effect is being invoked after the react component mounted and rendered to the user.
The next line where you are trying to call Mollie in fact running earlier when component is being constructed but not rendered yet.
There are multiple options what you can do about it:
Import script in the index.html file as you do for standard non-React solution. There should be a "public" folder containing this file in case of create-react-app usage or other place in case of custom project setup. The HTML should exist in any form even in case it's being generated on the server side dynamically. A mollie instance can later be created in the component or globally.
Use multiple effects in the React component: one to load Mollie and another one to use it when loaded:
// MollieExample.jsx
const MollieExample = () => {
const [mollie, setMollie] = useState();
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://js.mollie.com/v1/mollie.js';
script.addEventListener("load", () => {
setMollie(window.Mollie(/* Mollie arguments */);
});
document.body.appendChild(script);
}, []);
useEffect(() => {
if (mollie) {
console.log('Mollie exists here');
console.log(typeof mollie);
}
} , [mollie]);
return <p>typeof mollie: {typeof mollie}</p>;
};
Use dynamic script loading in case it is required with globally shared Mollie instance via custom hook:
// useMollie.js
let molliePromise;
const useMollie = (effect, deps) => {
const [mollie, setMollie] = useState();
const mollieCb = useCallback((mollie) => effect(mollie), deps);
useEffect(() => {
if (!molliePromise) {
molliePromise = new Promise((resolve) => {
const script = document.createElement("script");
script.src = "https://js.mollie.com/v1/mollie.js";
script.addEventListener("load", () => {
resolve(window.Mollie(/* Mollie arguments */);
});
document.body.appendChild(script);
});
}
molliePromise.then((mollie) => setMollie(mollie));
}, []);
useEffect(() => {
if (mollie) {
mollieCb(mollie);
}
}, [mollie, mollieCb]);
};
// MollieConsumer.jsx
const MollieConsumer = () => {
useMollie((mollie) => {
console.log('Mollie exists here');
console.log(typeof mollie);
}, [/* useMollie effect callback dependencies array */]);
return (
<p>Mollie consumer</p>
);
};
// App.jsx
function App() {
/* Both consumers use the same instance of Mollie */
return (
<div>
<MollieConsumer/>
<MollieConsumer/>
</div>
);
}
I assume you will end up with using some middle option. For instance, with importing script in the index.html (or any other sort of the HTML page you have containing the React application host element) and global hook.

How to prevent multiplication of ipcRenderer listenters?

I have a button in my react application that needs to do something 1 time every time it's clicked. Currently, when clicked, the listener seems to multiply.
Here is brief example of the code that I'm using:
// In App.js
const App = () => {
const buttonHandler = () => {
api.myApi.rendererToMain();
api.myApi.mainToRenderer();
};
return (
<div>
<Button cb={buttonHandler} />
</div>
);
};
// In Button.js
const Button = (props) => {
return (
<button type="button" onClick={ (e) => { props.cb() }}>Send Ping</button>
)
};
// In main.js
ipcMain.on('async', (e, msg) => {
console.log(msg); // print ping
e.reply('async_response', 'pong');
});
// In preload.js
contextBridge.exposeInMainWorld('api', {
myApi: {
rendererToMain: () => {
ipcRenderer.send('async', 'ping');
},
mainToRenderer: () => {
ipcRenderer.on('async_response', (e, msg) => {
console.log(msg);
});
},
},
});
When I run the electron app, I have a terminal for the Main process open, and devTools open for output from the renderer. Currently, when the button is pressed 3 times, the result looks like:
// In the browserWindow devTools console
pong
(2)pong
(5)pong
// In the Main process console
ping
ping
ping
The desired output is only different for the renderer console:
pong
pong
pong
My attempted solution
My first attempt at solving this on my own, after some stackoverflow research, was to try and remove the ipcRenderer listener for the "async_response" channel. All attempts to do this resulted in no output in the renderer console, and all 3 expected pings in the Main process console.
For example:
// Change made in preload.js
contextBridge.exposeInMainWorld('api', {
myApi: {
rendererToMain: () => {
ipcRenderer.send('async', 'ping');
},
mainToRenderer: () => {
ipcRenderer.on('async_response', (e, msg) => {
console.log(msg); // With below change, this does nothing.
});
// Attempt to remove the listener now?
// Seems to remove the listener before any output logged.
ipcRenderer.removeAllListeners('async_response');
},
},
});
If anyone could help me understand where and how to stop the listeners from multiplying, I would be eternally grateful. Thank you in advance.
After some sleep, I realized that my search was too narrow. I found that the ipcRenderer.on() method was being subscribed to an additional time each time the button was pressed, as per the concepts in this post: Node .on method firing too many times
Understanding this, I made the following change to my code, such that the call to the ipcRenderer.on() method only occurred once:
// In App.js
const App = () => {
const buttonHandler = () => {
api.myApi.rendererToMain();
// api.myApi.mainToRenderer(); <- Removed this call to the ipcRenderer.on()
};
return (
<div>
<Button cb={buttonHandler} />
</div>
);
};
// In Button.js
const Button = (props) => {
const callbackHandler = () => {
props.cb();
};
// Subscribe to listener on component construction
api.myApi.mainToRenderer();
return (
<button type="button" onClick={ (e) => { callbackHandler() }}>Send Ping</button>
)
};
These changes result in exactly the expected behavior. After three clicks of the button, I see in the renderer console:
(3)pong
Going to leave this unanswered for a time. I'm definitely open to comments about my fix and implementation all around.

How can React useScript hook handles multiple calls

I'm using the React useScript hook (from useHooks website). It allows to easily load external scripts and cache them once loaded.
It works fine however I found an edge-case causing me some issues..
The problem is with the caching of scripts.
If I have a component loaded 2 times in a page using useScript as below:
const ScriptDemo = src => {
const [loaded, error] = useScript("https://hzl7l.codesandbox.io/test-external-script.js");
return (
<div>
<div>
Script loaded: <b>{loaded.toString()}</b>
</div>
<br />
{loaded && !error && (
<div>
Script function call response: <b>{TEST_SCRIPT.start()}</b>
</div>
)}
</div>
);
};
function App() {
return (
<div>
<ScriptDemo />
<ScriptDemo />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can see and reproduce here: https://codesandbox.io/s/usescript-hzl7l
If my App only have one ScriptDemo it's fine, however having two or more would make it fails.
Indeed the flow will be:
ScriptDemo -> is script cached ? no -> add script to cache -> fetch it -> render
ScriptDemo2 -> is script cached ? yes -> render (but it's not finish loading
yet ..)
One way to fix it is to change the useScript hook to only cache the script after a successful onScriptLoad callback.
The issue with this approach is that the external script will be called twice.
See here: https://codesandbox.io/s/usescript-0yior
ScriptDemo -> is script cached ? no -> fetch it -> add script to cache -> render
ScriptDemo -> is script cached ? no -> fetch it -> add script to cache -> render
I thought about caching the script src AND a loading boolean but then it implies setting up a timeout handling and it gets very complex in my opinion.
So, what is the best way to update the hook in order to load the external script only once but ensuring it's correctly loaded?
In useScript module, we will need to keep track of status of loading the script.
So instead of cachedScripts being simple array of strings, we now need to keep an object representing the status of loading.
This modified implementation of useScript will address the issue:
import { useState, useEffect } from 'react';
let cachedScripts = {};
export function useScript(src) {
// Keeping track of script loaded and error state
const [state, setState] = useState({
loaded: false,
error: false
});
useEffect(
() => {
const onScriptLoad = () => {
cachedScripts[src].loaded = true;
setState({
loaded: true,
error: false
});
};
const onScriptError = () => {
// Remove it from cache, so that it can be re-attempted if someone tries to load it again
delete cachedScripts[src];
setState({
loaded: true,
error: true
});
};
let scriptLoader = cachedScripts[src];
if(scriptLoader) { // Loading was attempted earlier
if(scriptLoader.loaded) { // Script was successfully loaded
setState({
loaded: true,
error: false
});
} else { //Script is still loading
let script = scriptLoader.script;
script.addEventListener('load', onScriptLoad);
script.addEventListener('error', onScriptError);
return () => {
script.removeEventListener('load', onScriptLoad);
script.removeEventListener('error', onScriptError);
};
}
} else {
// Create script
let script = document.createElement('script');
script.src = src;
script.async = true;
// Script event listener callbacks for load and error
script.addEventListener('load', onScriptLoad);
script.addEventListener('error', onScriptError);
// Add script to document body
document.body.appendChild(script);
cachedScripts[src] = {loaded:false, script};
// Remove event listeners on cleanup
return () => {
script.removeEventListener('load', onScriptLoad);
script.removeEventListener('error', onScriptError);
};
}
},
[src] // Only re-run effect if script src changes
);
return [state.loaded, state.error];
}
Edit:
Went to GitHub page of useHooks to suggest this improvement and found some one has already posted similar fix:
https://gist.github.com/gragland/929e42759c0051ff596bc961fb13cd93#gistcomment-2975113

Receiving message from injected JavaScript in a React Native WebView

In my React Native App, I'm using a WebView to display a google ad (AdSense) by using the "injectedJavascript" prop. The problem is I can't know the height of the ad in advance. So I give it a random height at first and when its style is updated, I plan to set its height correctly.
I assume I have to get the height in the injected JS code, and then use the "window.postMessage()" method to send it to the WebView through the "onMessage" prop.
MutationObserver combined with promises seem very appropriate for this case. For now, I'd like to just receive the message from the webview. So this is my code right now but no message is sent :
export default class App extends Component {
constructor(props) {
super(props);
}
_onMessage(e) {
console.warn(e.nativeEvent.data);
}
render() {
const jsCode = `
window._googCsa('ads', pageOptions, adblock1);
function waitForAdSense(id) {
var config = {
attributes: true,
attributeFilter: ['style'],
};
return new Promise((resolve, reject) => {
var adSenseElement = document.body.getElementById(id);
if (adSenseElement.style) {
resolve(adSenseElement.style.height);
return;
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'style') {
observer.disconnect();
resolve(adSenseElement);
return;
}
});
});
observer.observe(adSenseElement, config);
});
}
waitForAdSense('afscontainer1').then(height => {
window.postMessage(height, '*');
});
`;
return (
<ScrollView>
<WebView
key={'AdSense'}
ref={'webview2'}
style={{ height: 300 }}
source={{
uri: isAndroid
? 'file:///android_asset/widget/adSense.html'
: './widget/index.html',
}}
javaScriptEnabled={true}
mixedContentMode="compatibility"
injectedJavaScript={jsCode}
scrollEnabled={false}
domStorageEnabled={true}
onMessage={this._onMessage}
scalesPageToFit
startInLoadingState={true}
automaticallyAdjustContentInsets={false}
/>
;
</ScrollView>
);
}
}
Though, I can make it work with this code but setTimeout is not the best solution :
window._googCsa('ads', pageOptions, adblock1);
var adSenseContainer = document.getElementById("afscontainer1");
setTimeout(function(){ window.postMessage(adSenseContainer.style.height, '*'); },1000);
Do you have any ideas ? I think my function waitForAdSense() might be bugged somehow. Thanks in advance !
The best solution would be to use AdMob instead of AdSense on a React Native mobile application. It is somewhat pointless to mess around with these issues as the AdSense javascript wasn't made with this use case in mind. Here is a library made to easily integrate AdMob within your app.
I managed to make it work with this changes in the injected code :
window._googCsa('ads', pageOptions, adblock1);
function waitForAdSense() {
var config = {
attributes: true,
attributeFilter: ["style"]
};
var adSenseContainer = document.getElementById("afscontainer1");
return new Promise((resolve, reject) => {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.attributeName === 'style' && adSenseContainer.style.height) {
var height = adSenseContainer.style.height;
resolve(height);
observer.disconnect();
return;
};
});
});
observer.observe(adSenseContainer, config);
});
};
waitForAdSense().then(height => {
window.postMessage(height, '*');
});
Thanks for the advices !

Categories

Resources