Agregar etiqueta de script a React / JSX

266

Tengo un problema relativamente sencillo al tratar de agregar secuencias de comandos en línea a un componente React. Lo que tengo hasta ahora:

'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>
        );
    }
};

También he intentado:

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

Ninguno de los enfoques parece ejecutar el script deseado. Supongo que es algo simple que me estoy perdiendo. ¿Alguien puede ayudar?

PD: Ignora el foobar, tengo una identificación real realmente en uso que no tenía ganas de compartir.

ArrayKnight
fuente
55
¿Existe una motivación específica para cargar esto a través de React en lugar de incluirlo en el HTML de su página base? Incluso si esto funcionara, significaría que volvería a insertar un script cada vez que se montara el componente.
loganfsmyth
¿Es ese el caso? Supuse que la diferencia de DOM haría que ese no fuera el caso, pero admito que dependería de la implementación de DocumentTitle.
loganfsmyth
8
Corregir @loganfsmyth, React no volverá a cargar el script en el renderizado si el siguiente estado también tiene el script.
Max

Respuestas:

405

Editar: las cosas cambian rápido y esto está desactualizado - ver actualización


¿Desea buscar y ejecutar el script una y otra vez, cada vez que se representa este componente, o solo una vez cuando este componente se monta en el DOM?

Quizás intente algo como esto:

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

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

    document.body.appendChild(script);
}

Sin embargo, esto solo es realmente útil si el script que desea cargar no está disponible como módulo / paquete. Primero, siempre:

  • Busque el paquete en npm
  • Descargue e instale el paquete en mi proyecto ( npm install typekit)
  • importel paquete donde lo necesito ( import Typekit from 'typekit';)

Esta es probablemente la forma en que instaló los paquetes reacty react-document-titlede su ejemplo, y hay un paquete Typekit disponible en npm .


Actualizar:

Ahora que tenemos ganchos, un mejor enfoque podría ser usar useEffectasí:

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);
  }
}, []);

Lo que lo convierte en un gran candidato para un gancho personalizado (por ejemplo:) 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;

Que se puede usar así:

import useScript from 'hooks/useScript';

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

  // rest of your component
}
Alex McMillan
fuente
Gracias. Estaba pensando en esto mal. Seguí adelante con esta implementación. Simplemente agregue el try{Typekit.load({ async: true });}catch(e){}después delappendChild
ArrayKnight
2
Decidí que la implementación "avanzada" de TypeKit era más adecuada para este enfoque.
ArrayKnight
10
Esto funciona: cargar el script, pero ¿cómo puedo obtener acceso al código en el script? Por ejemplo, me gustaría llamar a una función que vive dentro del script, pero no puedo invocarla dentro del componente donde se carga el script.
zero_cool
2
Cuando el script se agrega a la página, se ejecutará normalmente. Por ejemplo, si usó este método para descargar jQuery de un CDN, luego de que la componentDidMountfunción haya descargado y agregado el script a la página, tendrá los objetos jQueryy $disponibles a nivel mundial (es decir, activado window).
Alex McMillan
1
Tuve un problema similar al usar un script de autenticación y resultó que sería mejor incluirlo en el archivo html de la raíz una capa por encima de su App.js. En caso de que alguien encuentre esto útil. Como @loganfsmith mencionó ...
devssh
57

Además de las respuestas anteriores, puede hacer esto:

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)} />;
  }
}

El div está vinculado thisy el script se inyecta en él.

La demostración se puede encontrar en codesandbox.io

sidonaldson
fuente
55
Esta instancia no funcionó para mí, pero document.body.appendChild lo hizo a partir de la respuesta de Alex McMillan.
Qué sería genial
66
Probablemente no se unió this.instancea la referencia dentro de su método de renderizado. He agregado un enlace de demostración para mostrar que funciona
sidonaldson
1
@ShubhamKushwah ¿Debe estar haciendo renderizado del lado del servidor?
ArrayKnight
@ArrayKnight sí, descubrí más tarde que en el servidor estos objetos no existen: document, window. Así que prefiero usar el globalpaquete npm
Shubham Kushwah
¿cuál es la necesidad de s.async = true, no puedo encontrar ninguna referencia al respecto, a saber su propósito, ¿puedes explicarlo
Sasha Romanov
50

Mi forma favorita es usar React Helmet: es un componente que permite una fácil manipulación del cabezal del documento de una manera que probablemente ya esté acostumbrado.

p.ej

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
fuente
55
Esta es, de lejos, la mejor solución.
paqash
44
Desafortunadamente, no está funcionando ... Ver codesandbox.io/s/l9qmrwxqzq
Darkowic
2
@Darkowic, conseguí que tu código funcione agregando async="true"a la <script>etiqueta que agregó jQuery al código.
Soma Mbadiwe 01 de
@SomaMbadiwe ¿por qué funciona async=truey falla sin él?
Webwoman
3
Intenté esto, no funciona para mí. No recomendaría usar react-helmet por la única razón de que inyecta propiedades adicionales en el script que no se pueden eliminar. Esto realmente rompe ciertos scripts, y los encargados
Philberg
16

Si necesita tener <script>bloque en SSR (representación del lado del servidor), un enfoque con componentDidMountno funcionará.

Puedes usar react-safe biblioteca en su lugar. El código en React será:

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
fuente
12

La respuesta que proporcionó Alex Mcmillan me ayudó más, pero no funcionó para una etiqueta de script más compleja.

Modifiqué ligeramente su respuesta para encontrar una solución para una etiqueta larga con varias funciones que además ya estaba configurando "src".

(Para mi caso de uso, el script necesitaba vivir en la cabeza, lo que también se refleja aquí):

  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
fuente
77
No entiendo por qué usarías React en absoluto si solo viertes JS en línea en la página ...?
Alex McMillan
2
necesita agregar document.head.removeChild(script);su código, o creará un número infinito de etiquetas de script en su html siempre que el usuario visite esta ruta de la página
sasha romanov
7

Creé un componente React para este caso específico: https://github.com/coreyleelarson/react-typekit

Solo necesita pasar su ID de Kit Typekit como accesorio y ya está listo.

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
fuente
3

Hay una muy buena solución usando 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));
}

Esto funciona para HTML arbitrario y también retiene información de contexto como document.currentScript.

Maximilian Hils
fuente
¿Podría colaborar cómo se espera que funcione, con una muestra de uso? Para mí no funciona con el script y el cuerpo que pasan, por ejemplo ...
Alex Efimov
3

Puede usar npm postscribepara cargar script en el componente react

postscribe('#mydiv', '<script src="https://use.typekit.net/foobar.js"></script>')
Ashh
fuente
1
Resuelve mi problema
RPichioli
1

También puedes usar reaccionar casco

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>
    );
  }
};

Helmet toma etiquetas HTML simples y genera etiquetas HTML simples. Es muy simple, y reacciona amigable para principiantes.

Muhammad Usama Rabani
fuente
0

para múltiples scripts, use esto

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
fuente
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
fuente
0

Puede encontrar la mejor respuesta en el siguiente enlace:

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
fuente
0

Según la solución de Alex McMillan , tengo la siguiente adaptación.
Mi propio entorno: React 16.8+, next v9 +

// agrega un componente personalizado llamado 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.
}

// Use el componente personalizado, simplemente impórtelo y sustituya la antigua <script>etiqueta en minúsculas por la etiqueta personalizada en camello <Script>.
// 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>
manchar
fuente
Hay una advertencia para este componente: este componente de Script solo puede garantizar el orden de sus propios hermanos. Si usa este componente varias veces en varios componentes de la misma página, los bloques de secuencias de comandos pueden estar fuera de servicio. La razón es que todos los scripts se insertan mediante document.body.appendChild mediante programación, en lugar de declarativamente. Bueno, el casco mueve todas las etiquetas de script en la etiqueta de la cabeza, que no es lo que queremos.
sully
Hola @sully, mi problema aquí es que el script se agregue al DOM por separado, la mejor solución que he visto hasta ahora es durante el Desmontaje de componentes, eliminando el elemento hijo (es decir, el <script>) del DOM, y luego está siendo agregado nuevamente cuando el componente está montado en el DOM (estoy usando react-router-dom y solo un componente necesita este script de todos mis componentes)
Eazy
0

Llegué un poco tarde a la fiesta, pero decidí crear la mía propia después de mirar las respuestas de @Alex Macmillan y eso fue al pasar dos parámetros adicionales; la posición en la que colocar los scripts como o y configurar el asíncrono en verdadero / falso, aquí está:

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 forma de llamarlo es exactamente la misma que se muestra en la respuesta aceptada de esta publicación, pero con dos parámetros adicionales (nuevamente):

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

Para importar archivos JS en el proyecto de reacción, use este comando

  1. Mueva el archivo JS al proyecto.
  2. importa el js a la página usando el siguiente comando

Es simple y fácil.

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

En mi caso, me gustaría importar estos archivos JS en mi proyecto de reacción.

Bathri Nathan
fuente
-3

La solución depende del escenario. Como en mi caso, tuve que cargar una incrustación de calor dentro de un componente de reacción.

Calendly busca un div y lee de su data-urlatributo y carga un iframe dentro de dicho div.

Todo está bien cuando carga la página por primera vez: primero, data-urlse representa div con . Luego, el script calendly se agrega al cuerpo. El navegador lo descarga y lo evalúa y todos volvemos felices a casa.

El problema surge cuando navega y luego regresa a la página. Esta vez, el script aún está en el cuerpo y el navegador no lo vuelve a descargar ni a evaluar.

Reparar:

  1. En componentWillUnmountbuscar y eliminar el elemento de script. Luego, en el montaje, repita los pasos anteriores.
  2. Introduzca $.getScript. Es un ingenioso asistente de jquery que toma un URI de script y una devolución de llamada exitosa. Una vez que el script se cargó, lo evalúa y dispara su devolución de llamada exitosa. Todo lo que tengo que hacer es en mi componentDidMount $.getScript(url). Mi rendermétodo ya tiene el div calendly. Y funciona sin problemas.
Rohan Bagchi
fuente
2
Agregar jQuery para hacer esto es una mala idea, además su caso es muy específico para usted. En realidad, no hay nada de malo en agregar el script Calendly una vez, ya que estoy seguro de que la API tiene una llamada de re-detección. Eliminar y agregar una secuencia de comandos una y otra vez no es correcto.
sidonaldson
@sidonaldson jQuery no es una mala práctica si tienes que mantener un proyecto, su arquitectura compuesta de diferentes marcos (y libs) no solo reaccionar, de lo contrario, necesitamos usar js nativos para llegar a los componentes
AlexNikonov