I wrote the following component to embed a Pinterest widget in my React app:
import React, { Component } from 'react'
class PinterestWidget extends Component {
componentWillMount() {
const script = document.createElement('script')
script.async = true
script.type = 'text/javascript'
script['data-pin-build'] = 'doBuild'
script.src = '//assets.pinterest.com/js/pinit.js'
document.body.appendChild(script)
}
render() {
const { url } = this.props
return (
<a data-pin-do="embedPin" data-pin-build="doBuild" href={url}>
{url}
</a>
)
}
}
export default PinterestWidget
It works fine when the component mount the first time.
But when I navigate to another route and go back to this page, it doesn't render the widget anymore.
I tried to place the script in componentDidUpdate, but no success.
PS: I searched for a component but the only one I found is react-pinterest which is not maintained for 2 years — and doesn't work in my app.
Can anybody help me?
I found myself in the exact same position today. After wrestling with forks of that same react-pinterest npm package I found a solution in this issue https://github.com/pinterest/widgets/issues/13.
It looks like you already got close by adding the data-pin-build="doBuild" data attribute but you aren't calling doBuild() when the user returns to the component after hitting a different route. Adding the data attribute instructs pinit.js to add doBuild() to the global scope but doesn't automatically call the function when you remount the component.
Try replacing your componentWillMount function with something like this:
componentDidMount() {
if (!window.doBuild) {
this.preloadWidgetScript();
} else {
window.doBuild();
}
}
preloadWidgetScript = () => {
const script = document.createElement('script');
script.async = true;
script.dataset.pinBuild = 'doBuild';
script.src = '//assets.pinterest.com/js/pinit.js';
document.body.appendChild(script);
}
One other thing to note is the change from componentWillMount to componentDidMount, this is necessary to ensure the embed link is rendered before running doBuild().
In my case, using the answer of #n33kos it works with useEffect and so far every time (without checking whether it has appended the script or not). Here the <a> link to pinterest exist in a markdown file that is injected as html
useEffect(() => {
const script = document.createElement('script')
script.async = true
script.type = 'text/javascript'
script.dataset.pinBuild = 'doBuild'
script.src = '//assets.pinterest.com/js/pinit.js'
document.body.appendChild(script)
if (window.doBuild) window.doBuild()
}, []) // only run once
Related
I've implemented google analytics into a react app. The problem is that it just shows the page title upon every page view. What's really strange is that when I disable the code to update upon page change, it still registers each url change as a new pageview in Google Analytics (With the same page title as always). This is navigating to pages using the react-router-dom, not hard refreshed, but the pageviews are still showing in GA!
Here's the code I'm using
import { useLocation } from "react-router-dom";
import { useEffect, useState, createContext } from "react";
export const GoogleAnalyticsContext = createContext(null);
function GoogleAnalyticsProvider({children}) {
const [gaInitialized, setGaInitialized] = useState(false);
const [gaTrackingCode, setGaTrackingCode] = useState();
const location = useLocation();
useEffect(() => {
//Fetch tracking code
setGaTrackingCode('[my tracking code]')
}, [])
useEffect(() => {
if (gaTrackingCode) {
console.log("Updating script");
const script1 = document.createElement('script');
script1.async = true;
script1.src = `https://www.googletagmanager.com/gtag/js?id=${gaTrackingCode}`
document.body.appendChild(script1);
const script2 = document.createElement('script');
script2.async = true;
script2.text = `
window.dataLayer = window.dataLayer || [];
function gtag(){window.dataLayer.push(arguments);}
window.gtag = gtag;
gtag('js', new Date());
gtag('config', '${gaTrackingCode}', {
cookie_flags: 'SameSite=None;Secure'
});
`
document.body.appendChild(script2);
setGaInitialized(true);
}
}, [gaTrackingCode])
// useEffect(() => {
// if (gaInitialized) {
// window.gtag('config', gaTrackingCode, {
// page_title: location.pathname + location.search,
// page_page: location.pathname + location.search
// })
// }
// }, [gaInitialized, location])
const store = {
gaInitialized
}
return <GoogleAnalyticsContext.Provider value={store}>{children}</GoogleAnalyticsContext.Provider>
}
export default GoogleAnalyticsProvider
Notice the commented out part. It's still registering new pageviews even with that commented out.
I'd wonder if Google has implemented some new feature, if it weren't for the fact that I've got the exact same setup running in a different environment, and this doesn't happen, and the pageviews work correctly (with the commented out code uncommented)
Edit: I've checked with console.log, and the initialization script is only being called once.
Edit2: I'm using a workaround. I can send events successfully, so I'm sending all the pageviews as events.
So I am currently working on a website in react, and I am trying to change the favicon dynamically.
I had found a snippet of code which I tried to adapt in order to make it work but clearly I'm still missing something. My website does not break but there are no changes.
Here is my code:
import Logo from './Images/logo.png';
function App() {
//need help
useEffect((url) =>{
let link = document.queryCommandValue("link[rel~={Logo}]");
if(!link){
link = document.createElement('link');
link.rel = 'icon';
document.getElementsByTagName('head')[0].appendChild(link);
}
link.href = url;
},[]);
return (
<div className="App">
You can do it using React's useEffect hook and some JavaScript. Usually, you would define your useEffect in your page components, so when your page is rendered, the effect triggers which updates the favicon.
const Page1 = (props) => {
// effect to update favicon
useEffect(() => {
let link = document.querySelector("link[rel~='icon']");
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.getElementsByTagName('head')[0].appendChild(link);
}
link.href = 'https://stackoverflow.com/favicon.ico';
}, []);
return (
<div>Page1</div>
);
}
Here's more on how to change favicon dynamically with just JavaScript
I am using a hubspot form in my next.js site - I want to render the form on page load as you can see in the code.
The undesired effect is that the form renders twice, I want to build in a system so it only renders on page load and thats it. How do I achieve this?
I tried to follow this to solve it but did not work https://www.youtube.com/watch?v=KzH6YxW0zW4.
Code below
useEffect(() => {
let isCancelled = false;
const script = document.createElement('script');
script.src = "//js-eu1.hsforms.net/forms/v2.js";
document.body.appendChild(script)
if (!isCancelled) {
script.addEventListener('load', () => {
if (window.hbspt){
hubspotForm();
}
})
}
return () => {
isCancelled = true
};
}, [])
Removing strict mode is something that you should not prefer. I believe if you do this:
useEffect(() => {
const script = document.createElement('script');
script.src = "//js-eu1.hsforms.net/forms/v2.js";
document.body.appendChild(script)
const listener=() => {
if (window.hbspt){
hubspotForm();
}
}
script.addEventListener('load', listener)
return () => {
script.removeEventListener('load',listener)
};
}, [])
it should work fine. I prepared a detailed explanation of the topic (why useEffect is running twice and how to handle it.) here if you might want to have a look. Removing Strict mode is an easy out way but you should not choose it.
Remove strict mode from your index.js, that should do the trick.
check the version of react and react-dom in the package.json file
if you're using the lastest version of react v18. Because of the react strict mode and concurrrent rendering feature it executes the useEffect() hook to run twice even the if you set an empty dependency array.
you can disable react strictmode from next.config.js file
it should help to fix your problem
You can do something like this:
useEffect(() => {
// check whether the script is present already
if (document.getElementById('your-script-id')) return;
let isCancelled = false;
const script = document.createElement('script');
// add an id here
script.id = "your-script-id";
// the rest of your code
}
Also, check that you're not re-mounting your component, the fact that your script is triggered twice suggests that somewhere outside your component is re-mounted on load, which might cause performance issues.
I need to use this script < script type="text/javascript" src="http://ezwp.tv/Scripts/webService.js" >
I need to use it like this var x = new instance_From_Script("")
but in react it is used differently.
I have done this,
componentDidMount() {
const script = document.createElement("script");
script.src = "/static/libs/your_script.js";
script.async = true;
script.onload = () => this.scriptLoaded();
document.body.appendChild(script);
}
but I do not know how to use it outside of the JSX in the render () return ()
what I would really like to do is set an instance of the script in my states so
this.state = {
script: new script_instance()
With using that snippet, you can access any object in that external JS file in global object (window in browsers) after component mounted.
consider:
importing external js file using componentDidMount method:
componentDidMount() {
const script = document.createElement("script");
script.src = "/url_to_jquery/jquery.min.js";
script.async = true;
script.onload = () => this.scriptLoaded();
document.body.appendChild(script);
}
Now you can access jquery selectors like this in your declared method:
yourMethodName(){
window.$("#sample").click(function(){
alert("Text: Button Clicked");
});
}
I'm using React, Meteor and React Router for a large app.
When I navigate back in the browser history and even when I go forward the data provided by createContainer seems to disappear. So far I tried everything, but this problem persists. Do any of you know why this happens? How to solve it? Any help would be greatly appreciated.
Everything is fine if I force a full page refresh.
One of my components' exports:
export default createContainer( ( params ) => {
const subs = {}
subs.image = Meteor.subscribe('contents.images');
subs.provider = Meteor.subscribe('contentProviders.images');
const data = {}
data.image = CloudinaryImageContent.findOne({ _id: params.match.params.id });
data.provider = CloudinaryProvider.findOne();
return { subs, data }
}, ImageContentEditPage);
In my wrapped component I'm just running a componentWillReceiveProps
componentWillReceiveProps = ({data}) => {
this.setState({provider: data.provider, image: data.image});
}
In this specific scenario I ended up using componentDidMount with some basic validation for data availability.
componentDidMount = () => {
const { data } = this.props;
this.setState({provider: data.provider || {}, image: data.image || {}});
}
That did the job.