React: Comment remplacer des parties d'une chaîne par un composant ?

Créé le 12 mars 2015  ·  40Commentaires  ·  Source: facebook/react

J'ai ce code qui ne fonctionne visiblement pas :

    _.each(matches, (match) ->
      string = string.replace(match, ->
        <span className="match" key={i++}>{match}</span>
      )
    )

Parce que cela se traduirait par une chaîne mélangée avec des objets. Mauvais je sais. Mais comment ajouter des composants React dans une chaîne ? Tout ce que je veux, c'est mettre en évidence des parties d'une chaîne avec un composant de réaction. Une affaire difficile à résoudre, je suppose.

Commentaire le plus utile

Vous pouvez utiliser la fonction split avec une expression régulière, puis remplacer les parties capturées, comme ceci :

var parts = "I am a cow; cows say moo. MOOOOO.".split(/(\bmoo+\b)/gi);
for (var i = 1; i < parts.length; i += 2) {
  parts[i] = <span className="match" key={i}>{parts[i]}</span>;
}
return <div>{parts}</div>;

Faites-moi savoir si ce n'est pas clair.

Tous les 40 commentaires

quelque chose comme ça devrait fonctionner :

const escapeRE = new RegExp(/([.*+?^=!:$(){}|[\]\/\\])/g)
const safeRE = (string) => {
  return string.replace(escapeRE, "\\$1")
}

class Hightlight extends Component {
  static propTypes = {
    match : PropTypes.string,
    string : PropTypes.string,
  }

  render() {
    return (
      <span
        dangerouslySetInnerHTML={{
          __html : string.replace(safeRE(this.props.match), "<strong className\"match\">$1</strong>")
        }} />
    )
  }
}

Oui, j'ai pensé à abuser de dangerouslySetInnerHTML mais je n'aime pas cette idée. Cette méthode est qualifiée de dangereuse pour de bonnes raisons.

N'y a-t-il aucun moyen d'apprendre à React à analyser jsx dans une chaîne? Avoir React analyser iE

var example = "this one word <span className="match" key={i++}>hello</span> is highlighted"

et renvoyer ceci dans une méthode de rendu ?

@binarykitchen Vous pouvez construire des éléments React de manière dynamique et c'est ce que vous devez faire ici, vous ne pouvez tout simplement pas le faire avec un remplacement de chaîne uniquement.

@syranide Oui, je le sais déjà. Mon propos est le suivant : ne serait-ce pas une fonctionnalité intéressante de JSX/React ? Analysez une chaîne et transformez toutes les balises à l'intérieur en composants React enfants.

Vous pouvez utiliser la fonction split avec une expression régulière, puis remplacer les parties capturées, comme ceci :

var parts = "I am a cow; cows say moo. MOOOOO.".split(/(\bmoo+\b)/gi);
for (var i = 1; i < parts.length; i += 2) {
  parts[i] = <span className="match" key={i}>{parts[i]}</span>;
}
return <div>{parts}</div>;

Faites-moi savoir si ce n'est pas clair.

L'analyse de JSX au moment de l'exécution est sujette aux erreurs et lente, et peut facilement avoir des implications de sécurité - elle ne serait pas intrinsèquement plus sûre que ne l'est dangereusement SetInnerHTML.

cela peut être un peu un problème avec le type de boîtier

- mlb

Le 12 mars 2015, à 21h37, Ben Alpert [email protected] a écrit :

Vous pouvez utiliser la fonction split avec une expression régulière, puis remplacer les parties capturées, comme ceci :

var parts = "Je suis une vache; les vaches disent moo. MOOOOO.".split(/(\bmoo+\b)/gi);
pour (var i = 1; i < parties.longueur; i += 2) {
parties[i] = ;}retourner

{les pièces}
;
Faites-moi savoir si ce n'est pas clair.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub.

J'ai également rencontré ce problème en essayant de mettre en évidence des parties d'une chaîne par programme (c'est-à-dire remplacer par des balises <span> ). J'ai fini par créer un module basé sur la solution de @spicyj ci-dessus. Pour toute personne intéressée : iansinnott/react-string-replace

@iansinnott Merci pour ce partage. J'ai commencé à l'utiliser, puis j'ai réalisé que j'allais avoir besoin de l'API complète String.prototype.replace (nécessité d'accéder aux groupes de correspondance individuels pour une expression rationnelle dans la fonction de remplacement). J'ai réalisé que le changement serait plus qu'un simple patch (et nécessiterait un changement d'API) alors j'ai créé un nouveau dépôt : https://github.com/oztune/string-replace-to-array

Si react-string-replace ne correspond pas à vos besoins :

https://github.com/EfogDev/react-process-string

Ce qui suit a fonctionné pour moi comme moyen de mettre en surbrillance le(s) mot(s) clé(s) dans une chaîne de texte, sans
paramètre HTML dangereusement :

/**
 * Find and highlight relevant keywords within a block of text
 * <strong i="7">@param</strong>  {string} label - The text to parse
 * <strong i="8">@param</strong>  {string} value - The search keyword to highlight
 * <strong i="9">@return</strong> {object} A JSX object containing an array of alternating strings and JSX
 */
const formatLabel = (label, value) => {
  if (!value) {
    return label;
  }
  return (<span>
    { label.split(value)
      .reduce((prev, current, i) => {
        if (!i) {
          return [current];
        }
        return prev.concat(<b key={value + current}>{ value }</b>, current);
      }, [])
    }
  </span>);
};

formatLabel('Lorem ipsum dolor sit amet', 'dolor');
// <span>Lorem ipsum <b>dolor</b> sit amet</span>

J'avais besoin de quelque chose comme ça pour les mentions correspondant à un modèle afin qu'il puisse envelopper plusieurs valeurs et apporter quelques modifications à la solution @richardwestenra . Peut-être que cela sera utile à quelqu'un.

/**
 * Find and highlight mention given a matching pattern within a block of text
 * <strong i="7">@param</strong> {string} text - The text to parse
 * <strong i="8">@param</strong> {array} values - Values to highlight
 * <strong i="9">@param</strong> {RegExp} regex - The search pattern to highlight 
 * <strong i="10">@return</strong> {object} A JSX object containing an array of alternating strings and JSX
 */
const formatMentionText = (text, values, regex) => { 
    if (!values.length)
        return text;

    return (<div>
        {text.split(regex)
            .reduce((prev, current, i) => {
                if (!i)
                    return [current];

                return prev.concat(
                    values.includes(current)  ?
                        <mark key={i + current}>
                            {current}
                        </mark>
                        : current
                );
            }, [])}
    </div>);
};

const text = 'Lorem ipsum dolor sit amet [[Jonh Doe]] and [[Jane Doe]]';
const values = ['Jonh Doe', 'Jane Doe'];
const reg = new RegExp(/\[\[(.*?)\]\]/); // Match text inside two square brackets

formatMentionText(text, values, reg);
// Lorem ipsum dolor sit amet <mark>Jonh Doe</mark> and <mark>Jane Doe</mark>

J'ai eu un problème similaire et je l'ai résolu avec des portails de réaction, pour trouver facilement le nœud, j'ai remplacé la chaîne par un div et rendu mon composant de réaction à l'aide de createPortal dans ce div.

@rmtngh Je suis curieux de savoir pourquoi vous auriez besoin de faire cela. Il semble que cela soit exagéré. Est-ce que vous entrelacez du code non réactif ?

En ce qui concerne les solutions à ce problème, j'aimerais recommander string-replace-to-array (je suis l'auteur). Il a été combattu et éprouvé dans plusieurs environnements. Il est également assez petit (la bibliothèque entière est ce fichier https://github.com/oztune/string-replace-to-array/blob/master/string-replace-to-array.js).

Voici un exemple d'utilisation avec regex :

import replace from 'string-replace-to-array'

// The API is designed to match the native 'String.replace',
// except it can handle non-string replacements.
replace(
  'Hello Hermione Granger...',
  /(Hermione) (Granger)/g,
  function (fullName, firstName, lastName, offset, string) {
    return <Person firstName={ firstName } lastName={ lastName } key={ offset } />
  }
)

// output: ['Hello ', <Person firstName="Hermione" lastName="Granger" key={ 0 } />, ...]

@oztune Merci pour votre réponse, dans ce cas, j'ai une chaîne de balisage html provenant d'un éditeur wysiwyg d'un CMS et j'aimerais placer des composants de réaction à certains endroits à l'intérieur de ce balisage.
Pensez-vous qu'il serait préférable d'utiliser une bibliothèque au lieu d'utiliser des portails ? Je pense que ce que fait le portail est de simplement rendre le composant à l'intérieur d'un autre nœud au lieu du nœud le plus proche, ce qui ne serait pas plus cher que de rendre un composant normal et la seule étape supplémentaire consiste à remplacer la chaîne qui peut être effectuée avec le remplacement natif de js.
Est-ce que je manque quelque chose ? Y aura-t-il des avantages à utiliser la bibliothèque à la place ?

@rmtngh Je dirais que cela dépend de ce que vous essayez de faire. Si vous essayez simplement de saupoudrer des composants React dans différentes zones de votre arbre DOM et que l'arbre n'est pas déjà rendu avec React, les portails sont la voie à suivre. La méthode que j'ai mentionnée est la plus utile lorsque les pièces que vous essayez de remplacer sont déjà rendues à l'intérieur d'un composant React.

Voici le code qui renvoie un tableau de nœuds en fonction du texte et du motif :

const highlightPattern = (text, pattern) => {
  const splitText = text.split(pattern);

  if (splitText.length <= 1) {
    return text;
  }

  const matches = text.match(pattern);

  return splitText.reduce((arr, element, index) => (matches[index] ? [
    ...arr,
    element,
    <mark>
      {matches[index]}
    </mark>,
  ] : [...arr, element]), []);
};

Merci @wojtekmaj
Ma version pour les groupes en pattern :

const replacePatternToComponent = (text, pattern, Component) => {
  const splitText = text.split(pattern);
  const matches = text.match(pattern);

  if (splitText.length <= 1) {
    return text;
  }

  return splitText.reduce((arr, element) => {
      if (!element) return arr;

      if(matches.includes(element)) {
        return [...arr, Component];
      }

      return [...arr, element];
    },
    []
  );
};

const string = 'Foo [first] Bar [second]';
const pattern = /(\[first\])|(\[second\])/g;

replacePatternToComponent(string, pattern, <Component />);

Approche à l'aide d'un tapuscrit :

const formatString = (
  str: string,
  formatingFunction?: (value: number | string) => React.ReactNode,
  ...values: Array<number | string>
) => {
  const templateSplit = new RegExp(/{(\d)}/g);
  const isNumber = new RegExp(/^\d+$/);
  const splitedText = str.split(templateSplit);
  return splitedText.map(sentence => {
    if (isNumber.test(sentence)) {
      const value = values[Number(sentence)];
      return Boolean(formatingFunction) ? formatingFunction(value) : value;
    }
    return sentence;
  });
};

Exemple d'utilisation :

const str = '{0} test {1} test';
formatString(str, value => <b>{value}</b>, value1, value2);

Résultat:
<b>value1</b> test <b>value2</b> test

@rmtngh pourriez-vous fournir un exemple de la façon dont vous avez réalisé l'enrichissement du balisage via createportal ? Je cherche à faire la même chose.

@Dashue L'idée est de créer des cibles pour vos composants de réaction à l'intérieur de votre balisage, en fonction de la chaîne HTML et du contrôle que vous en avez,
Si vous pouvez le modifier côté serveur, vous souhaiterez peut-être donner un identifiant à vos éléments. Quelque chose comme :
<div id="component_target_1"></div>
Et à l'intérieur de réagir, recherchez \id="(component_target_\d)"\g avec regexp, stockez les identifiants (state.targets) et affichez vos composants :

let mappedComponents
if(this.state.targets && this.state.targets.length){
            mappedComponents = this.state.targets.map((id,index)=><MyComponent id={id} key={id} />)
        }

Voici un exemple de "MyComponent":

import React from 'react'
import ReactDOM from 'react-dom'

export default class MyComponent extends React.Component {
  constructor(props) {
    super(props)
  }
  getWrapper(){
    return document.getElementById(this.props.id)
  }
  render() {
    if(!this.getWrapper()) return null

    const content = <div>
        Hello there! I'm rendered with react.
      </div>

      return ReactDOM.createPortal(
        content,
        this.getWrapper(),
      );
  }
}

Si vous n'avez pas beaucoup de contrôle sur la chaîne HTML, vous pouvez toujours utiliser la même approche, vous devrez peut-être rechercher certains éléments et injecter un élément cible dans la chaîne.

Super fil les gars!

J'ai créé un simple stylo React Text Highlighter qui implémente certaines des idées de ce fil (avec un peu de magie supplémentaire...), j'espère que les futurs visiteurs pourront le trouver utile.

Voici une solution utilisant map ,
forking @sophiebits solution ici:

const reg = new RegExp(/(\bmoo+\b)/, 'gi');
const parts = text.split(reg);
return <div>{parts.map(part => (part.match(reg) ? <b>{part}</b> : part))}</div>;

Belle solution @rehat101

Voici ce qui a le mieux fonctionné dans mon cas : diviser par le caractère espace et rechercher des mots spécifiques.

C'est un peu brouillon dans le HTML mais qu'importe

💗 RegExp et la carte rendent cela tellement propre. ??

image

const Speech = ({ speech, speeches, setSpeeches, i, text }) => {

const highlightRegExp = new RegExp(
    /you|can|do|it|puedes|hacerlo|pingüinos|son|bellos/,
    "gi"
  );
  const delineator = " ";
  const parts = text.split(delineator);

  return (
        <div>
          {parts.map(part =>
            part.match(highlightRegExp) ? <b>{part + " "}</b> : part + " "
          )}
        </div>
  );
};

Belle @ rehat101

Si react-string-replace ne correspond pas à vos besoins :

https://github.com/EfogDev/react-process-string

Brillant! Je le trouve extrêmement utile car la plupart du temps, vous avez réellement besoin d'accéder à la chaîne d'origine

J'ai remanié un peu la solution de
js const highlightQueryText = (text: string, filterValue: string) => { const reg = new RegExp(`(${filterValue})`, 'gi'); const textParts = text.split(reg); return ( <span style={{ fontWeight: 600 }}> {textParts.map(part => (part.match(reg) ? <span style={{ fontWeight: 'normal' }}>{part}</span> : part))} </span> ); };

J'ai donc trouvé une belle combinaison d'implémentation de DOMPurify pour nettoyer les chaînes html avec html-react-parser pour y trouver un balisage ciblé et le convertir en composants JSX - j'utilise Rebass avec des accessoires passés comme exemple. deux packages vous aideront à éviter les frais généraux liés au lancement de votre propre analyseur, je ne recommanderais tout simplement pas ce package d'analyseur sans assainissement. Pour ceux qui luttent encore avec cela, évaluez peut-être ce codepen pour ce qu'il vaut : https://codesandbox.io/s/eager-wiles-5hpff. Les commentaires sur les améliorations sont également très appréciés.

Salut Jesse,
Cela semble être un très bon combo pour prendre du texte et le transformer en
Réagissez les composants! Ce serait certainement bien pour remplacer des pièces d'un
chaîne avec un composant.

Merci d'avoir partagé.

Pourrait également être utile dans un site JAMstack pour transformer Markdown en composants.

Meilleures salutations,
Derek

Derek R. Austin, PT, DPT, MS, BCTMB, LMT, CSCS

Rejoignez-moi sur LinkedIn : https://www.linkedin.com/in/derek-austin/

Le mercredi 26 février 2020 à 16 h 35, Jesse Lewis [email protected] a écrit :

j'ai donc trouvé une belle combinaison d'implémentation de DOMPurify pour assainir le html
des chaînes avec html-react-parser pour y trouver le balisage ciblé et le convertir
aux composants JSX - j'utilise Rebass avec des accessoires passés à titre d'exemple.
est sorti assez bien, les deux packages vous aideront à éviter les frais généraux
de lancer votre propre analyseur, je ne recommanderais tout simplement pas ce package d'analyseur
sans désinfection. Pour ceux qui luttent avec cela, peut-être encore une portée
ce codepen pour ce qu'il vaut :
https://codesandbox.io/s/eager-wiles-5hpff. Commentaires sur les améliorations
très apprécié aussi.

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/facebook/react/issues/3386?email_source=notifications&email_token=AMV42QTV4FDXZIFXCGB3NZDRE3OATA5CNFSM4A5VRBI2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXH165ZKTDN5WW2YYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXH165Z59TDN5WW2
ou se désinscrire
https://github.com/notifications/unsubscribe-auth/AMV42QXARIOKOYSJ4C5IFQLRE3OATANCNFSM4A5VRBIQ
.

Vous pouvez utiliser la fonction split avec une expression régulière, puis remplacer les parties capturées, comme ceci :

var parts = "I am a cow; cows say moo. MOOOOO.".split(/(\bmoo+\b)/gi);
for (var i = 1; i < parts.length; i += 2) {
  parts[i] = <span className="match" key={i}>{parts[i]}</span>;
}
return <div>{parts}</div>;

Faites-moi savoir si ce n'est pas clair.

Cela fonctionne très bien. Un moyen de le faire fonctionner dans Angular? J'ai posté une question à ce sujet, en utilisant votre code:
https://stackoverflow.com/questions/60889171/wrap-certain-words-with-a-component-in-a-contenteditable-div-in-angular

Bonjour les amis, j'ai besoin de votre aide s'il vous plait, j'utilise React Native et ce que je dois faire c'est à partir d'un texte que j'extrait de la DB pour appliquer un format (couleur de police, lien) pour faire des @mentions , lors de la recherche si dans le le texte trouve 1 seule correspondance rend le remplacement tout bon, mais ! S'il y a plusieurs @mentions dans le texte, cela me renvoie une erreur.

/////Exemple de texte : _hey qu'est-ce qui s'est passé @-id:1- tout va bien ??
//// listusers son tableau, exemple: [idusuario: "1", usuario: "@luigbren", format: "@-id:1-".....]

const PatternToComponent = (text, usuarios,) => {
    let mentionsRegex = new RegExp(/@-(id:[0-9]+)-/, 'gim');
    let matches = text.match(mentionsRegex);
    if (matches && matches.length) {
        matches = matches.map(function (match, idx) {
            let usrTofind = matches[idx]
            //////////////////////////////////////////////////////////////////
            const mentFormat = listusers.filter(function (item) { 
                const itemData = item.format;
                return itemData.indexOf(usrTofind) > -1;
            });
            if (mentFormat.length > 0) {
                let idusuario = mentFormat[0].idusuario
                let replc = mentFormat[0].usuario

                console.log(usrTofind) //// here find @-id:1-
                console.log(replc)   //// here is <strong i="12">@luigbren</strong> for replace

                ////////// now here replace part of the string, @-id:1- with a <Text> component 
                ///////// with <strong i="13">@luigbren</strong> and the link, this is repeated for every <strong i="14">@mention</strong> found

                parts = text.split(usrTofind);
                for (var i = 1; i < parts.length; i += 2) {
                  parts[i] = <Text key={i} style={{ color: '#00F' }} onPress={() => { alert('but this is'); }}>{replc}</Text>;
                }
                return text = parts;
                /////////////////////////////////////////////////////////////////////
            } else {
                return text
            }
        });
    } else {
        return text
    }
    return text
};

de cette façon, le code ne fonctionne bien pour moi que lorsque dans le texte il n'y a qu'une seule mention, exemple '_hey what s'est passé @-id:1- tout va bien ??_' , mais lorsque vous placez plus d'une mention, cela me donne une erreur , exemple : '_hey qu'est-ce qui s'est passé @-id:1- tout va bien ?? @-id:2- @-id:3-_' ... Erreur : TypeError : text.split n'est pas une fonction et si au lieu de placer _parts = text.split(usrTofind);_ je place _parts = text.toString ().split(usrTofind);_ cela me donne une erreur [Object Object]

Vous pouvez utiliser html-react-parser pour analyser la chaîne, puis la remplacer par le nom du nœud avec des composants jsx. Ce type de remplacement vous permettra d'utiliser des éléments de réaction dynamiquement en remplaçant des chaînes.

      parse(body, {
        replace: domNode => {
          if (domNode.name === 'select') { // or
            return React.createElement(
              Select, // your react component
              { },
              domToReact(domNode.children)
            );
          }
        }

Quelqu'un me dit comment remplacer react-router-dom dans la chaîne ?

Exemple:

var text = "Are You okay <strong i="9">@sara</strong> ?";

var href= <Link to={{
  pathname: '/user/sara',
}}> <strong i="10">@sara</strong> </Link>;

var replace = text.replace("@sara", href);

//output : Are You okey [Object Object] ?

Je ne sais vraiment pas qui est [Object Object] ? Je dis "Tu es d'accord @sara", mais ce code a appelé quelqu'un d'autre !

J'ai presque la même question dans react-native , j'apprécie si quelqu'un peut m'aider

ma question

Rien n'a fonctionné pour moi sauf ce package https://www.npmjs.com/package/regexify-string

J'utilise cette fonction :

export const replaceWithHtml = (
  text: string,
  textToReplace: string,
  replaceValue: any,
): any[] => {
  const delimiter = '|||||'
  return text && text.includes(textToReplace)
    ? text
        .replace(textToReplace, `${delimiter}${textToReplace}${delimiter}`)
        .split(delimiter)
        .map((i) => {
          if (i === textToReplace) {
            return replaceValue
          } else {
            return i || null
          }
        })
    : text
}

Comme ça:

const text = 'This is an [icon]'
replaceWithHtml(
      text,
      '[icon]',
      <i className="icon-cogs" />,
)

Je voulais juste un outil d'interpolation simple, pour remplacer certains mots-clés par certains éléments.

Pour réduire le nombre d'éléments dans ma sortie, j'utilise un React.Fragment .

const interpolate = (text, values) => {
  const pattern = /([$0-9]+)/g
  const matches = text.match(pattern)
  const parts = text.split(pattern)

  if (!matches) {
    return text
  }

  return parts.map((part, index) => (
    <Fragment key={part + index}>{matches.includes(part) ? values[part] : part}</Fragment>
  ))
}

// ...

<p>
  {interpolate('$1 with $2 is fun!', {
    $1: <em>Playing</em>,
    $2: <strong>JavaScript</strong>,
  })}
</p>

// <p><em>Playing</em> with <strong>JavaScript</strong> is fun!</p>

Merci @sophiebits pour l'idée sur split() 🙏

Basé sur la solution de @pedrodurek , voici quelque chose pour mes besoins spécifiques (traductions avec des clés lisibles au lieu de simplement des chiffres):

export const insertComponentsIntoText = (
    str: string,
    replacements: {
        [key: string]: React.ReactNode
    }
) => {
    const splitRegex = new RegExp(/\[\[(\w*)\]\]/g);
    const parts = str.split(splitRegex);
    return parts.map(part => {
        if (replacements.hasOwnProperty(part)) {
            return replacements[part];
        }
        return part;
    });
};

Exemple:

insertComponentsIntoText(`"Please accept our [[privacyPolicyLink]] and [[termsLink].", {
    "privacyPolicyLink": <a key="privacyPolicyLink" href="/privacy">Privacy Policy</a>,
    "termsLink": <a key="termsLink" href="/terms">Terms and Conditions</a>
})

...devient...

Please accept our <a key="privacyPolicyLink" href="/privacy">Privacy Policy</a> and <a key="termsLink" href="/terms">Terms and Conditions</a>.

(Les clés sont nécessaires car elles deviennent en fait un tableau et réagissent n'aime pas les éléments de tableau sans clés.)

Rien n'a fonctionné pour moi sauf ce package https://www.npmjs.com/package/regexify-string

Moi aussi. Je les ai tous essayés, seul ce package fonctionne. Merci!

Cette page vous a été utile?
0 / 5 - 0 notes