I'm trying to add inline scripting to a React component:
I have my custom hook:
import { useEffect } from 'react';
const useScript = url => {
useEffect(() => {
const script = document.createElement('script');
script.src = url;
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
}
}, [url]);
};
export default useScript;
Which I use like so:
import useScript from 'hooks/useScript';
const MyComponent = () => {
useScript('path/to/my.js');
}
Script added to the document after SPA navigation event but it doesn't add events to my buttons without reloading the page.
I had a problem inside one of the added scripts. I changed the way of adding script. I exported the function inside js, then I imported this function inside my component and then I used useEffect.
import { MyFunction } from './myscript.js';
const MyComponent = () => {
useEffect(() => {
MyFunction();
}, []);
return(
......
)
Related
I got somthing like this on React?
$(document).on("click", function (e) { ... }
Or is mandatory to use onClick on all components?
It is not a good practice and You should try and avoid jQuery in ReactJS, But:
You can have that, but in order to do that you need to add your code in the right life cycle hook
for example, you can do this
import React from 'react';
import $ from 'jquery';
useEffect(() => {
function onClickHanlder (e) { ... }
$(document).on("click", onClickHanlder)
// to prevent leak memory unsubscribe from event on unmount
return () => {
$(document).off("click")
}
},[]);
In a class component you can add your code in componentDidMount
import React from 'react';
import { useEffect } from 'react';
const App = () => {
useEffect(() => {
initCustomScripts();
}, [])
const initCustomScripts = () => {
document.getElementById('custom-button').addEventListener('click', function(){
//code here
alert('custom-button clicked');
})
}
return (
<div>
<button id="custom-button">Custom button</button>
</div>
);
}
export default App;
in my react-native app App.js I setup a listener for firebase notifications, I also setup navigation in order to navigate and get current name inside notif handler functions, however when my app mounts I get this error:
TypeError: navigationRef.isReady is not a function. (In 'navigationRef.isReady()', 'navigationRef.isReady' is undefined)
getCurrentRoute
In my App.js file
import { navigationRef, isReadyRef, getCurrentRoute, navigate } from '#env/RootNavigation.js';
useEffect(() =>
{
console.log('APP STARTED');
registerListener();
}, []);
const registerListener = () =>
{
messaging().onMessage((message) => {
handleForegroundNotification(message);
});
};
const handleForegroundNotification = (message) =>
{
let routeName = getCurrentRoute();
Alert.alert(routeName);
};
<NavigationContainer ref={navigationRef} onReady={ () => { isReadyRef.current = true; } }>
<ROOTSTACK1></ROOTSTACK1>
</NavigationContainer>
In my RootNavigation file:
import * as React from 'react';
export const isReadyRef = React.createRef();
export const navigationRef = React.createRef();
export function navigate(name, params) {
if (isReadyRef.current && navigationRef.current) {
// Perform navigation if the app has mounted
navigationRef.current.navigate(name, params);
} else {
// You can decide what to do if the app hasn't mounted
// You can ignore this, or add these actions to a queue you can call later
}
};
export function getCurrentRoute(){
if (navigationRef.isReady()) {
const route=navigationRef.getCurrentRoute();
console.log(route);
// sample output {key:"Home-k2PN5aWMZSKq-6TnLUQNE",name:"Home"}
return route.name;
}
};
Documentation states to use createNavigationContainerRef vs React.createRef.
So it would be
export const navigationRef = createNavigationContainerRef();
from https://reactnavigation.org/docs/navigating-without-navigation-prop
Newbie to react here.
TLDR: I have a helper function called createNotification which when called inserts a <ToastNotification /> component into a container element using render(). If I use createPortal() nothing is appended. If I use render, the component is only added once despite multiple triggers.
Can anyone help me figure out whats happening please?
Thank you
helpers.js
import { ToastNotification } from "carbon-components-react";
import { render, createPortal } from "react-dom";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return render(<ToastNotification />, container); //works but only once, not on multiple triggers
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
the function above is called from other components as needed:
login.js
import { createNotification } from "../../helpers";
const Login = () => {
const validateLogin = async (event) => {
createNotification();
// validation logic
performLogin();
};
const performLogin = async () => {
//axios call here
};
// main component content
return (
<>
<!-- validateLogin() called on form submit -->
</>
);
};
export default Login;
app.js
//imports
function App() {
return (
<div>
<div className="App"></div>
</div>
);
}
export default App;
Thank you
Solved this myself by adding the createPortal() within the render().
If anyone can provide an explanation, it would be much appreciated.
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container);
return render(createPortal(<ToastNotification />, container), document.createElement("div"));
};
createNotification aren't mounted in component in app Virtual Dom... when you use render(createPortal) then you just create spearted app.
import { createNotification } from "../../helpers";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
const Login = () => {
const [validate, setValidate] = useState(false);
const validateLogin = async (event) => {
if('some logic')
return setValidte(true)
setVAlidte(false)
};
useEffect(() => {
if(!valite)
return;
//axios heare
}, [validate])
// main component content
return (
<>
{!validate && <CreateNotfication/>}
<!-- validateLogin() called on form submit -->
</>
);
};
I am trying to add a Facebook comments plugin (check it here) to my vue app, the problem is that div is created in DOM but it sometimes shows, sometimes not(width: 0, height 0)
Note: I am calling XFBML.parse function, my host is added to fb app
This is my current code:
<template>
<div
ref="commentContainer"
class="fb-comments"
:data-href="onUrl()"
:data-width="cwidth"
:data-numposts="numposts"
></div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, watch } from "vue";
import router from "../../router";
export default defineComponent({
props: {
cwidth: {
type: String,
default: "100%",
},
numposts: {
type: String,
default: "2",
},
},
setup({ cwidth, numposts }) {
const commentContainer = ref(null);
const init = () => {
if (
window.FB &&
!commentContainer.value.hasAttribute("fb-xfbml-state")
) {
setTimeout(() => {
window.FB.XFBML.parse(commentContainer.value.parentElement);
}, 2000);
}
};
onMounted(() => {
setTimeout(() => {
init();
}, 1500);
});
const onUrl = () => {
return document.location.origin + document.location.pathname;
};
watch(
() => router.currentRoute.value,
() => {
init();
}
);
return { cwidth, numposts, commentContainer, onUrl };
},
});
</script>
Instead of doing setTimeout try using nextTick and not passing any params to the parse function.
E.g. in the mounted function
this.$nextTick(() => {
window.FB.XFBML.parse()
})
Are you waiting 1.5s before running init() for a reason?
The above works using Vue2, for Vue3 example see below:
import { createApp, nextTick } from 'vue'
const app = createApp({
setup() {
const init = async () => {
await nextTick()
window.FB.XFBML.parse()
}
}
})
https://v3.vuejs.org/api/global-api.html#nexttick
Also, make sure you have added the SDK script and provided fb-root div to your index.html. It would not work on mine unless I added these just before the closing </body> tag.
I also had to add the the nextTick code to the route watcher to force the window to parse FB again when a new page is navigated to. I'm unsure of the Vue 3 version but I'm sure you can figure it out from this example:
watch: {
$route (to, from) {
if (to.fullPath !== from.fullPath) {
this.$nextTick(() => {
window.FB.XFBML.parse()
})
}
}
}
I am trying to use the gmail API with React.js.
I keep getting the error 'gapi is not defined'. I believe my client.js file in the HTML is loading after my mail.js file runs?
How can I get around this?
Index.html
...
<script src="https://apis.google.com/js/client.js"></script>
Index.js
import './Mail.js';
Mail.js
import { createAction, handleActions } from 'redux-actions'
const CLIENT_ID = '1.apps.googleusercontent.com'
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
export const SET_GMAIL_CREDENTIALS = 'SET_GMAIL_CREDENTIALS'
export const CHANGE_LOADING = 'CHANGE_LOADING'
export const SET_GMAIL_LABELS = 'SET_GMAIL_LABELS'
export const SELECT_GMAIL_LABEL = 'SELECT_GMAIL_LABEL'
export const SET_GMAIL_EMAILS = 'SET_GMAIL_EMAILS'
let defaultState = {
profile: {
emailAddress: ''
},
loading: true,
labels: [],
currentLabel: null,
emails: []
}
export const connect = () => {
return (dispatch, getState) => {
dispatch(turnLoadingOn())
gmailAuth(false, populateCredentials(dispatch), clearCredentials(dispatch))
}
}...
I think you're right. The way I'm handling these situations is by loading the external JS file from React and using it in a promise.
So your flow should be something like this:
React app loads
React app injects your file in the HTML
Do your thing in step 2's callback or .then()
Create a helper function. Put it in a folder like helpers/load-script. Below you have all the code you should have in that file:
export default function loadScript(url, cb) {
var scr = document.createElement('script');
scr.type = 'text/javascript';
if (scr.readyState) { // IE
scr.onreadystatechange = function() {
if (scr.readyState ==`loaded' || scr.readyState ==='complete') {
scr.onreadystatechange = null;
cb();
}
};
} else { // Others
scr.onload = cb;
}
script.src = url;
document.getElementsByTagName('head')[0].appendChild(scr);
}
Next, import that function inside the component you want to use it into:
import React from 'react';
import loadScript from 'helpers/load-script';
class testComponent extends React.Component {
componentDidMount() {
loadScript('https://apis.google.com/js/client.js', () => {
// do mail api stuff here
});
}
render() {
return (
<div>hi there</div>
);
}
}
export default testComponent;
I had the same problem with GrowSurf Javascript Web API, The external script loaded after the render function, and its function was being undefined in componentDidMount().
This is a logic I used to get the GrowSurf function not to be undefined. It can also help anyone who wants to use a function of any external JS introduced in index.html.
You can use DOMSubtreeModified in componentDidMount() to check every time the DOM is modified, once the external JS is loaded(found) run your function there, and then stop the looping of DOMSubtreeModified.
componentDidMount() {
let functionDefined = false;
window.addEventListener('DOMSubtreeModified', function () {
if(!functionDefined) {
if (window.growsurf) {
console.log('a function is defined', window.growsurf.getReferrerId());
functionDefined = true;
}
}
}, false);
}
For your case, you can simply do this.
componentDidMount() {
let functionDefined = false;
window.addEventListener('DOMSubtreeModified', function () {
if(!functionDefined) {
if (window.gapi) {
// do mail api stuff here
functionDefined = true;
}
}
}, false);
}