Ajout d'une balise de script à React / JSX

266

J'ai un problème relativement simple d'essayer d'ajouter des scripts en ligne à un composant React. Ce que j'ai jusqu'à présent:

'use strict';

import '../../styles/pages/people.scss';

import React, { Component } from 'react';
import DocumentTitle from 'react-document-title';

import { prefix } from '../../core/util';

export default class extends Component {
    render() {
        return (
            <DocumentTitle title="People">
                <article className={[prefix('people'), prefix('people', 'index')].join(' ')}>
                    <h1 className="tk-brandon-grotesque">People</h1>

                    <script src="https://use.typekit.net/foobar.js"></script>
                    <script dangerouslySetInnerHTML={{__html: 'try{Typekit.load({ async: true });}catch(e){}'}}></script>
                </article>
            </DocumentTitle>
        );
    }
};

J'ai aussi essayé:

<script src="https://use.typekit.net/foobar.js"></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>

Aucune des deux approches ne semble exécuter le script souhaité. Je suppose que c'est une chose simple qui me manque. Quelqu'un peut-il m'aider?

PS: Ignorez le foobar, j'ai un vrai identifiant réellement utilisé que je n'ai pas envie de partager.

ArrayKnight
la source
5
Existe-t-il une motivation spécifique pour le charger via React au lieu de l'inclure dans le code HTML de votre page de base? Même si cela fonctionnait, cela signifierait que vous réinsérez un script à chaque fois que le composant est monté.
loganfsmyth
Est-ce le cas? J'ai supposé que la différence de DOM ferait que ce ne serait pas le cas, mais j'admets que cela dépendrait de la mise en œuvre de DocumentTitle.
loganfsmyth
8
Corrigez @loganfsmyth, React ne rechargera pas le script lors du nouveau rendu si l'état suivant contient également le script.
Max

Réponses:

405

Edit: Les choses changent rapidement et ce n'est plus à jour - voir la mise à jour


Voulez-vous récupérer et exécuter le script encore et encore, à chaque fois que ce composant est rendu, ou une seule fois lorsque ce composant est monté dans le DOM?

Essayez peut-être quelque chose comme ceci:

componentDidMount () {
    const script = document.createElement("script");

    script.src = "https://use.typekit.net/foobar.js";
    script.async = true;

    document.body.appendChild(script);
}

Cependant, cela n'est vraiment utile que si le script que vous souhaitez charger n'est pas disponible en tant que module / package. Premièrement, je voudrais toujours:

  • Recherchez le package sur npm
  • Téléchargez et installez le package dans mon projet (npm install typekit )
  • importle package où j'en ai besoin ( import Typekit from 'typekit';)

C'est probablement la façon dont vous avez installé les packages reactet à react-document-titlepartir de votre exemple, et un package Typekit est disponible sur npm .


Mettre à jour:

Maintenant que nous avons des crochets, une meilleure approche pourrait être d'utiliser useEffectcomme ceci:

useEffect(() => {
  const script = document.createElement('script');

  script.src = "https://use.typekit.net/foobar.js";
  script.async = true;

  document.body.appendChild(script);

  return () => {
    document.body.removeChild(script);
  }
}, []);

Ce qui en fait un excellent candidat pour un crochet personnalisé (par exemple:) hooks/useScript.js:

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;

Qui peut être utilisé comme ceci:

import useScript from 'hooks/useScript';

const MyComponent = props => {
  useScript('https://use.typekit.net/foobar.js');

  // rest of your component
}
Alex McMillan
la source
Je vous remercie. Je pensais à ce mal. J'ai poursuivi cette mise en œuvre. Ajout de l' try{Typekit.load({ async: true });}catch(e){}after theappendChild
ArrayKnight
2
J'ai décidé que l'implémentation "avancée" de TypeKit était plus adaptée à cette approche.
ArrayKnight
10
Cela fonctionne - pour charger le script, mais comment puis-je avoir accès au code dans le script. Par exemple, je voudrais appeler une fonction qui vit à l'intérieur du script, mais je ne peux pas l'invoquer à l'intérieur du composant où le script est chargé.
zero_cool
2
Lorsque le script est ajouté à la page, il sera exécuté normalement. Par exemple, si vous avez utilisé cette méthode pour télécharger jQuery à partir d'un CDN, puis après que la componentDidMountfonction a téléchargé et ajouté le script à la page, vous aurez les objets jQueryet $disponibles globalement (c'est-à-dire: on window).
Alex McMillan
1
J'ai eu un problème similaire en utilisant un script d'authentification et il s'est avéré qu'il serait préférable de l'inclure dans le fichier html de la racine, une couche au-dessus de votre App.js. Au cas où quelqu'un trouverait cela utile. Comme @loganfsmith l'a mentionné ...
devssh
57

En plus des réponses ci-dessus, vous pouvez faire ceci:

import React from 'react';

export default class Test extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    const s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.innerHTML = "document.write('This is output by document.write()!')";
    this.instance.appendChild(s);
  }

  render() {
    return <div ref={el => (this.instance = el)} />;
  }
}

Le div est lié à this et le script y est injecté.

La démo peut être trouvée sur codesandbox.io

sidonaldson
la source
5
this.instance n'a pas fonctionné pour moi, mais document.body.appendChild l'a fait d'après la réponse d'Alex McMillan.
Que serait cool le
6
Vous ne vous êtes probablement pas lié this.instanceà la référence dans votre méthode de rendu. J'ai ajouté un lien de démonstration pour montrer qu'il fonctionne
sidonaldson
1
@ShubhamKushwah Vous devez faire le rendu côté serveur?
ArrayKnight
@ArrayKnight oui j'ai découvert plus tard que sur le serveur ces objets n'existent pas: document, window. Je préfère donc utiliser le globalpackage npm
Shubham Kushwah
quel est le besoin de s.async = true, je ne trouve aucune référence à ce sujet, pour savoir son but, pouvez-vous l'expliquer
sasha romanov
50

Ma façon préférée est d'utiliser React Helmet - c'est un composant qui permet une manipulation facile de la tête du document d'une manière que vous avez probablement déjà utilisée.

par exemple

import React from "react";
import {Helmet} from "react-helmet";

class Application extends React.Component {
  render () {
    return (
        <div className="application">
            <Helmet>
                <script src="https://use.typekit.net/foobar.js"></script>
                <script>try{Typekit.load({ async: true });}catch(e){}</script>
            </Helmet>
            ...
        </div>
    );
  }
};

https://github.com/nfl/react-helmet

AC Patrice
la source
5
C'est de loin la meilleure solution.
paqash
4
Malheureusement, cela ne fonctionne pas ... Voir codesandbox.io/s/l9qmrwxqzq
Darkowic
2
@Darkowic, j'ai fait fonctionner votre code en l'ajoutant async="true"à la <script>balise qui a ajouté jQuery au code.
Soma Mbadiwe
@SomaMbadiwe pourquoi il fonctionne avec async=trueet échoue sans lui?
Webwoman
3
J'ai essayé ça, ça ne marche pas pour moi. Je ne recommanderais pas d'utiliser react-casque pour la seule raison qu'il injecte des propriétés supplémentaires dans le script qui ne peuvent pas être supprimées. Cela brise en fait certains scripts, et les responsables ne l'ont pas corrigé depuis des années, et refusent de github.com/nfl/react-helmet/issues/79
Philberg
16

Si vous devez avoir un <script>bloc en SSR (rendu côté serveur), une approche avec componentDidMountne fonctionnera pas.

Vous pouvez utiliser la react-safebibliothèque à la place. Le code dans React sera:

import Safe from "react-safe"

// in render 
<Safe.script src="https://use.typekit.net/foobar.js"></Safe.script>
<Safe.script>{
  `try{Typekit.load({ async: true });}catch(e){}`
}
</Safe.script>
scabbiaza
la source
12

La réponse d' Alex Mcmillan m'a le plus aidé mais n'a pas vraiment fonctionné pour une balise de script plus complexe.

J'ai légèrement modifié sa réponse pour trouver une solution pour une longue balise avec diverses fonctions qui définissait en outre déjà "src".

(Pour mon cas d'utilisation, le script devait vivre dans la tête, ce qui se reflète également ici):

  componentWillMount () {
      const script = document.createElement("script");

      const scriptText = document.createTextNode("complex script with functions i.e. everything that would go inside the script tags");

      script.appendChild(scriptText);
      document.head.appendChild(script);
  }
jake2620
la source
7
Je ne comprends pas pourquoi vous utiliseriez React du tout si vous jetez simplement JS en ligne sur la page ...?
Alex McMillan
2
vous devez ajouter document.head.removeChild(script);votre code, ou vous créerez un nombre infini de balises de script à votre html tant que l'utilisateur visite cet itinéraire de page
sasha romanov
7

J'ai créé un composant React pour ce cas spécifique: https://github.com/coreyleelarson/react-typekit

Il vous suffit de passer votre ID de kit Typekit comme accessoire et vous êtes prêt à partir.

import React from 'react';
import Typekit from 'react-typekit';

const HtmlLayout = () => (
  <html>
    <body>
      <h1>My Example React Component</h1>
      <Typekit kitId="abc123" />
    </body>
  </html>
);

export default HtmlLayout;
Corey Larson
la source
3

Il existe une solution de contournement très agréable à utiliser Range.createContextualFragment.

/**
 * Like React's dangerouslySetInnerHTML, but also with JS evaluation.
 * Usage:
 *   <div ref={setDangerousHtml.bind(null, html)}/>
 */
function setDangerousHtml(html, el) {
    if(el === null) return;
    const range = document.createRange();
    range.selectNodeContents(el);
    range.deleteContents();
    el.appendChild(range.createContextualFragment(html));
}

Cela fonctionne pour du HTML arbitraire et conserve également des informations de contexte telles que document.currentScript.

Maximilian Hils
la source
Pourriez-vous collaborer comment devrait-il fonctionner s'il vous plaît, avec un exemple d'utilisation? Pour moi ça ne marche pas avec le passage du script et du corps par exemple ..
Alex Efimov
3

Vous pouvez utiliser npm postscribepour charger le script dans le composant React

postscribe('#mydiv', '<script src="https://use.typekit.net/foobar.js"></script>')
Ashh
la source
1
Résout mon problème
RPichioli
1

Vous pouvez également utiliser le casque React

import React from "react";
import {Helmet} from "react-helmet";

class Application extends React.Component {
  render () {
    return (
        <div className="application">
            <Helmet>
                <meta charSet="utf-8" />
                <title>My Title</title>
                <link rel="canonical" href="http://example.com/example" />
                <script src="/path/to/resource.js" type="text/javascript" />
            </Helmet>
            ...
        </div>
    );
  }
};

Le casque prend des balises HTML simples et génère des balises HTML simples. C'est très simple et React est adapté aux débutants.

Muhammad Usama Rabani
la source
0

pour plusieurs scripts, utilisez ceci

var loadScript = function(src) {
  var tag = document.createElement('script');
  tag.async = false;
  tag.src = src;
  document.getElementsByTagName('body').appendChild(tag);
}
loadScript('//cdnjs.com/some/library.js')
loadScript('//cdnjs.com/some/other/library.js')
ben
la source
0
componentDidMount() {
  const head = document.querySelector("head");
  const script = document.createElement("script");
  script.setAttribute(
    "src",
    "https://assets.calendly.com/assets/external/widget.js"
  );
  head.appendChild(script);
}
Marlcg58
la source
0

Vous pouvez trouver la meilleure réponse sur le lien suivant:

https://cleverbeagle.com/blog/articles/tutorial-how-to-load-third-party-scripts-dynamically-in-javascript

const loadDynamicScript = (callback) => {
const existingScript = document.getElementById('scriptId');

if (!existingScript) {
    const script = document.createElement('script');
    script.src = 'url'; // URL for the third-party library being loaded.
    script.id = 'libraryName'; // e.g., googleMaps or stripe
    document.body.appendChild(script);

    script.onload = () => {
      if (callback) callback();
    };
  }

  if (existingScript && callback) callback();
};
Prajesh Mishrikotkar
la source
0

Selon la solution d' Alex McMillan , j'ai l'adaptation suivante.
Mon propre environnement: React 16.8+, next v9 +

// ajouter un composant personnalisé nommé Script
// hooks / Script.js

import { useEffect } from 'react'

const useScript = (url, async) => {
  useEffect(() => {
    const script = document.createElement('script')

    script.src = url
    script.async = (typeof async === 'undefined' ? true : async )

    document.body.appendChild(script)

    return () => {
      document.body.removeChild(script)
    }
  }, [url])
}

export default function Script({ src, async=true}) {

  useScript(src, async)

  return null  // Return null is necessary for the moment.
}

// Utilisez le compoennt personnalisé, importez-le simplement et remplacez l'ancienne <script>balise minuscule par la balise camel personnalisée <Script>suffirait.
// index.js

import Script from "../hooks/Script";

<Fragment>
  {/* Google Map */}
  <div ref={el => this.el = el} className="gmap"></div>

  {/* Old html script */}
  {/*<script type="text/javascript" src="http://maps.google.com/maps/api/js"></script>*/}

  {/* new custom Script component */}
  <Script src='http://maps.google.com/maps/api/js' async={false} />
</Fragment>
souiller
la source
Il y a une mise en garde pour ce composant: ce composant Script ne peut garantir que l'ordre de ses propres frères et sœurs. Si vous utilisez ce composant plusieurs fois dans plusieurs composants de la même page, les blocs de scripts peuvent être hors service. La raison en est que tous les scripts sont insérés par document.body.appendChild par programme, au lieu de manière déclarative. Eh bien, le casque déplace toutes les balises de script dans la balise head, ce qui n'est pas ce que nous voulons.
sully
Hey @sully, mon problème ici est d'avoir le script ajouté au DOM séparément, la meilleure solution que j'ai vue jusqu'à présent est pendant le démontage de composant, en supprimant l'élément enfant (c'est-à-dire le <script>) du DOM, puis il est ajouté à nouveau lorsque le composant est monté sur le DOM (j'utilise react-router-dom et un seul composant a besoin de ce script de tous mes composants)
Eazy
0

Un peu tard pour la fête mais j'ai décidé d'en créer une après avoir regardé les réponses de @Alex Macmillan et c'était en passant deux paramètres supplémentaires; la position dans laquelle placer les scripts tels que or et la configuration de l'async sur true / false, la voici:

import { useEffect } from 'react';

const useScript = (url, position, async) => {
  useEffect(() => {
    const placement = document.querySelector(position);
    const script = document.createElement('script');

    script.src = url;
    script.async = typeof async === 'undefined' ? true : async;

    placement.appendChild(script);

    return () => {
      placement.removeChild(script);
    };
  }, [url]);
};

export default useScript;

La façon de l'appeler est exactement la même que celle indiquée dans la réponse acceptée de ce post, mais avec deux (supplémentaires) paramètres supplémentaires:

// First string is your URL
// Second string can be head or body
// Third parameter is true or false.
useScript("string", "string", bool);
Kirasiris
la source
0

Pour importer des fichiers JS dans le projet React, utilisez cette commange

  1. Déplacez le fichier JS vers le projet.
  2. importer les js dans la page en utilisant la commande suivante

C'est simple et facile.

import  '../assets/js/jquery.min';
import  '../assets/js/popper.min';
import  '../assets/js/bootstrap.min';

Dans mon cas, je voudrais importer ces fichiers JS dans mon projet React.

Bathri Nathan
la source
-3

La solution dépend du scénario. Comme dans mon cas, j'ai dû charger une intégration calendaire à l'intérieur d'un composant React.

Calendly recherche un div et lit à partir de son data-urlattribut et charge un iframe à l'intérieur dudit div.

Tout va bien lorsque vous chargez la page pour la première fois: d'abord, div avec data-urlest rendu. Ensuite, un script calendaire est ajouté au corps. Le navigateur le télécharge et l'évalue et nous rentrons tous chez nous heureux.

Le problème survient lorsque vous vous éloignez puis revenez dans la page. Cette fois, le script est toujours dans le corps et le navigateur ne le télécharge pas et ne le réévalue pas.

Réparer:

  1. Sur componentWillUnmountrechercher et supprimer l'élément de script. Ensuite, lors du remontage, répétez les étapes ci-dessus.
  2. Entrez $.getScript. Il s'agit d'une aide jquery astucieuse qui prend un URI de script et un rappel de réussite. Une fois le script chargé, il l'évalue et déclenche votre rappel de réussite. Tout ce que j'ai à faire, c'est dans mon componentDidMount $.getScript(url). Ma renderméthode a déjà le div calendaire. Et ça marche bien.
Rohan Bagchi
la source
2
Ajouter jQuery pour ce faire est une mauvaise idée, et votre cas vous est très spécifique. En réalité, il n'y a rien de mal à ajouter une fois le script Calendly car je suis sûr que l'API a un appel de re-détection. Supprimer et ajouter un script à plusieurs reprises n'est pas correct.
sidonaldson
@sidonaldson jQuery n'est pas une mauvaise pratique si vous devez maintenir un projet, son architecture composée de différents cadres (et bibliothèques) ne doit pas simplement réagir, sinon nous devons utiliser des js natifs pour atteindre les composants
AlexNikonov