0

When the page loads I use componentDidMount() to document.createElement("script"); in the layout index.js of a ReactJS and GatsbyJS project as

componentDidMount () {
  const tripadvisorLeft = document.createElement("script");
  tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
  tripadvisorLeft.async = true;
  document.body.appendChild(tripadvisorLeft);
}

This then requests the data to be displayed and works fine. However, when I <link to=... another page using gatsby-link (imagine the same issue applies for react-router), componentDidMount() has already run so it won't fetch the data again.

How can I ensure that this script is mounted after each path change, or better by a specific path?

Shishir Anshuman
  • 1,115
  • 7
  • 23
Darren
  • 2,176
  • 9
  • 42
  • 98
  • Just to be sure, do you want the body of `componentDidMount` (i.e. fetching the script) to happen on each route change? – Phillip Apr 18 '18 at 07:21
  • 1
    React Router offers hooks like `onChange` and `onUpdate` which you can use to do something like what you are trying to achieve. [See this related stack overflow question](https://stackoverflow.com/questions/39351573/react-router-onchange-hook?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa) – Chirag Ravindra Apr 18 '18 at 07:23
  • Ideally only on certain route paths, for example the `root` and `/reviews/` but at this stage everything would be a great starting point. Also not the entire functions of `componentDidMount` only the ` const tripadvisorLeft` – Darren Apr 18 '18 at 07:24
  • Thank you @ChiragRavindra. I am just looking this up for `gatsby-link` – Darren Apr 18 '18 at 07:30
  • Which version of `react-router` are you using? – Liam May 04 '18 at 01:18

4 Answers4

1

You can use navigateTo from the gatsby-link package to roll your own Link component that executes your custom logic before navigating.

Link.jsx

import React from "react";
import { navigateTo } from "gatsby-link";

const RESOURCE_PATH = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
const refetchAndNavigate = (path) => () => {
  // refetch
  const tripadvisorLeft = document.createElement("script");
  tripadvisorLeft.src = RESOURCE_PATH;
  tripadvisorLeft.async = true;
  document.body.appendChild(tripadvisorLeft);

  // finally navigate
  navigateTo(path);
}

const Link = ({ to, ...propsToPass }) => (
  <div {...propsToPass} onClick={refetchAndNavigate(to)}/>
);

export default Link;

I have not tested this code but idea will work.

Liau Jian Jie
  • 7,071
  • 2
  • 15
  • 16
  • Thank you for your question. I have had a play with your idea and think it's on track to what I need but some issues are blocking me. This would be remounting the script throughout the site? I have multiple scripts that require mounting as per their component, some with data being used via GraphQl to build the URL. This would not be possible in your example. Wondering if we could start a live chat and I could share a link to the source? – Darren Apr 30 '18 at 01:04
  • @Darren It sounds like you need access to props pass down into components in order to build the API in the first place. If that's the case you'll need to perform your fetching logic in `componentDidUpdate`. Also, have you looked into [React portals](https://reactjs.org/docs/portals.html)? – Liau Jian Jie Apr 30 '18 at 06:24
  • Thank you for your reply. Yes, I though about Portals but this will not help. I have never been so frustrated in looking for a solution. If you have a moment to check out the source https://github.com/discovr-bookings/mountainWhispers if that helps you see how I can implement your suggestion. You can also see deployed version here that shows the issue gracious-allen-d5eb54.netlify.com/varenna/reviews. Cheers. – Darren Apr 30 '18 at 06:32
0

You can add in the componentDidUpdate lifecycle method which is called whenever the component is updated:

componentDidUpdate(prevProps) {
  // check if path has changed
  if (prevProps.pathname !== this.props.pathname) { 
    // call something to fetch data
  }
}

You can add more conditions to match only certain paths.

Check your gastby-link library to see how you can get the current pathname to pass in as props.

You can also try passing pathname={window.location.pathname} from a parent component that gets re-rendered when path changes.

Roy Wang
  • 11,112
  • 2
  • 21
  • 42
  • Thank you. Did not work for me sorry. When loading the `script` via `componentDidUpdate` I do not get the scripts mounted, conditions added or not. Any ideas? – Darren Apr 30 '18 at 01:20
  • Can you try removing then re-inserting the `tripadvisorLeft` element in `componentDidUpdate`? Perhaps try `this.forceUpdate()` after the script runs too (make sure you add the path change check otherwise it'll go into infinite recursive rendering). Also check that `componentDidUpdate` is indeed getting called. – Roy Wang Apr 30 '18 at 04:42
  • Thank you for your reply. I have tried in many ways but cannot get working - now back to using `componentDidMount`. Here is the source https://github.com/discovr-bookings/mountainWhispers/blob/master/src/components/properties/TripAdvisor.js if that helps you see how I can implement your suggestion. You can also see deployed version here that shows the issue https://gracious-allen-d5eb54.netlify.com/varenna/reviews/. – Darren Apr 30 '18 at 04:48
  • Did you try my suggestion above (re-inserting the script element)? Did `componentDidUpdate` get called? Also try `tripadvisorLeft.onload = () => this.forceUpdate()`. Add logging to check if `this.forceUpdate()` is being called etc. – Roy Wang Apr 30 '18 at 04:51
  • Yes, tried but the scripts aren't getting called. Will try `forceUpdate` but sure this has been tried. – Darren Apr 30 '18 at 04:57
  • No, that isn't doing it either. `forceUpdate` logged in the console but I am now thinking the issue is not with the `script` but with the entire rendering of the component. When the script is called it alters the DOM but the when the component is re-rendered the DOM has changed and so the content is missing. I need to re-render the entire component, including the `script`. – Darren Apr 30 '18 at 05:15
0

Could you use a HOC to accomplish what you want. I just threw this together, it will need to be modified.

Create a component something like,

const withTracking = (WrappedComponent) => {
    return class withTracking extends React.Component {
        constructor(props) {
            super(props);

            this.trackit = this.trackIt.bind(this);
        }

        trackIt() {
            {
                const tripadvisorLeft = document.createElement("script");
                tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
                tripadvisorLeft.async = true;
                document.body.appendChild(tripadvisorLeft);
            }
        }

        render() {
            return (
                <div>
                    <WrappedComponent {...this.props} trackIt={this.trackIt} />
                </div>
            );
        }
    }
};

Then in react router wrap it like,

<Route exact path="/pet-of-the-week" component={withTracking(TEST)}/>

And in the component use componentDidMount,

const TEST = (WrappedComponent) => {
    return class ClickLogger extends React.Component {
        constructor(props) {
            super(props);
        }

        componentDidMount() {
            this.trackIt();
        }

        render() {
            return (
                <div>
                    <h1>something</h1>
                </div>
            );
        }
    }
};
0

componentDidMount runs the first time (and only the first time!) the component is mounted in the DOM.

componentDidUpdate runs every time the component receives new props, but not for the initial render.

In order to operate on the DOM for the first render and for subsequent updates, you'll need to register your <script> handling logic in both componentDidMount and componentDidUpdate, i.e.

class YourComponent extends React.Component {
  componentDidMount () {
    const tripadvisorLeft = document.createElement("script");
    tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
    tripadvisorLeft.async = true;
    document.body.appendChild(tripadvisorLeft);

    // Keep track of the script tag
    this.scriptTag = tripadvisorLeft
  }

  componentDidUpdate (prevProps) {
    // Figure out if the path changed
    if (this.props.path !== prevProps.path) {
      // Remove the old script tag
      document.body.removeChild(this.scriptTag)

      // Add it back
      const tripadvisorLeft = document.createElement("script");
      tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
      tripadvisorLeft.async = true;
      document.body.appendChild(tripadvisorLeft);

      this.scriptTag = tripadvisorLeft
    } 
  }
}

You can refactor this to move the <script> creation logic into a helper function, but I've inlined it here to make it clear what's going on.

bgran
  • 867
  • 6
  • 12
  • Thank you @bgran. I like that the scripts is being removed and replaced. It works well to the question I asked. – Darren May 05 '18 at 00:59
  • My only query is I have multiple posts using this component but each replacing the `locationID` in the url query. So when using this script is being used in each post it does not replace the script with the new `locationID` unless `Link to=` the path and then out and back to it again. Would this mean that `if (this.props.path !== prevProps.path) {` is not being recognised? – Darren May 05 '18 at 01:05
  • It's hard to say exactly what's going on without seeing your code - if you can create a new branch with the code that's not working, I can provide some more specific feedback. It's possible that it's not the `path` prop you should be comparing. It might be called `pathname`. If you set a breakpoint in `componentDidUpdate`, you can compare the props when you click a `` and see exactly what changed. – bgran May 05 '18 at 09:03
  • Thank you @bgran. I have posted a question relating to this additional trouble here https://stackoverflow.com/questions/50206298/pull-graphql-data-into-gatsby-browser-js-or-better-solution-please. Here you can see full code. Thank you again. – Darren May 07 '18 at 02:50