Comment passer des accessoires à {this.props.children}

889

J'essaie de trouver la bonne façon de définir certains composants qui pourraient être utilisés de manière générique:

<Parent>
  <Child value="1">
  <Child value="2">
</Parent>

Il y a une logique en cours pour le rendu entre les composants parents et enfants bien sûr, vous pouvez l'imaginer <select>et <option>comme exemple de cette logique.

Il s'agit d'une implémentation factice aux fins de la question:

var Parent = React.createClass({
  doSomething: function(value) {
  },
  render: function() {
    return (<div>{this.props.children}</div>);
  }
});

var Child = React.createClass({
  onClick: function() {
    this.props.doSomething(this.props.value); // doSomething is undefined
  },
  render: function() {
    return (<div onClick={this.onClick}></div>);
  }
});

La question est chaque fois que vous utilisez {this.props.children}pour définir un composant wrapper, comment transmettez-vous une propriété à tous ses enfants?

plus-
la source

Réponses:

955

Clonage d'enfants avec de nouveaux accessoires

Vous pouvez utiliser React.Children pour parcourir les enfants, puis cloner chaque élément avec de nouveaux accessoires (peu profonds fusionnés) à l'aide de React.cloneElement, par exemple:

import React, { Children, isValidElement, cloneElement } from 'react';

const Child = ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    const childrenWithProps = Children.map(children, child => {
      // Checking isValidElement is the safe way and avoids a TS error too.
      if (isValidElement(child)) {
        return cloneElement(child, { doSomething })
      }

      return child;
    });

    return <div>{childrenWithProps}</div>
  }
};

ReactDOM.render(
  <Parent>
    <Child value="1" />
    <Child value="2" />
  </Parent>,
  document.getElementById('container')
);

Violon: https://jsfiddle.net/2q294y43/2/

Appel des enfants en fonction

Vous pouvez également transmettre des accessoires aux enfants avec des accessoires de rendu . Dans cette approche, les enfants (qui peuvent être childrenou tout autre nom d'accessoire) est une fonction qui peut accepter tous les arguments que vous souhaitez transmettre et renvoie les enfants:

const Child = ({ doSomething, value }) => (
  <div onClick={() =>  doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    // Note that children is called as a function and we can pass args to it
    return <div>{children(doSomething)}</div>
  }
};

ReactDOM.render(
  <Parent>
    {doSomething => (
      <React.Fragment>
        <Child doSomething={doSomething} value="1" />
        <Child doSomething={doSomething} value="2" />
      </React.Fragment>
    )}
  </Parent>,
  document.getElementById('container')
);

Au lieu de <React.Fragment>ou simplement, <>vous pouvez également retourner un tableau si vous préférez.

Violon: https://jsfiddle.net/ferahl/y5pcua68/7/

Dominique
la source
7
Ça ne marche pas pour moi. cela n'est pas défini dans React.cloneElement ()
Patrick
12
Cette réponse ne fonctionne pas, le valuepassé doSomethingest perdu.
Dave
3
@DominicTobias Arg, désolé, j'ai basculé console.log pour alerter et oublié de concaténer les deux paramètres en une seule chaîne.
Dave
1
Cette réponse a été super utile, mais j'ai rencontré un problème qui n'est pas mentionné ici et je me demandais si c'était quelque chose de nouveau qui avait changé ou si c'était quelque chose d'étrange de ma part. Lorsque j'ai cloné mon élément enfant, son enfant a été défini sur l'ancien élément, jusqu'à ce que j'ajoute this.props.children.props.children au troisième argument de cloneElement.
aphenine
7
Que faire si l'enfant est chargé via une route (v4) qui est chargée à partir d'une page de route distincte?
blamb
394

Pour une façon légèrement plus propre de le faire, essayez:

<div>
    {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>

Modifier: Pour utiliser avec plusieurs enfants individuels (l'enfant doit lui-même être un composant), vous pouvez le faire. Testé en 16.8.6

<div>
    {React.cloneElement(props.children[0], { loggedIn: true, testingTwo: true })}
    {React.cloneElement(props.children[1], { loggedIn: true, testProp: false })}
</div>
Andres F Garcia
la source
10
J'utilisais la réponse la mieux notée, mais celle-ci est beaucoup plus simple! Cette solution est également ce qu'ils utilisent sur la page d'exemples de react-router.
captDaylight
10
Quelqu'un pourrait-il expliquer comment cela fonctionne (ou ce qu'il fait réellement)? En lisant les documents , je ne pouvais pas voir comment cela descendrait dans les enfants et ajouterait cet accessoire à chaque enfant - est-ce que c'est ce qu'il est censé faire? Si c'est le cas, comment savons-nous que c'est ce qu'il fera? Il n'est pas du tout évident qu'il soit même valide de passer une structure de données opaque ( this.props.children) à cloneElement... qui attend un ... élément.
GreenAsJade
51
Exactement, cela ne semble pas fonctionner avec plus d'un enfant.
Danita
17
Vous pouvez donc écrire du code qui fonctionne pendant que quelqu'un passe un seul enfant dans un composant, mais lorsqu'il en ajoute un autre, il se bloque ... cela ne sonne pas très bien à première vue? Il semblerait que ce soit un piège pour l'OP, qui a posé des questions précises sur la transmission d'accessoires à tous les enfants.
GreenAsJade
10
@GreenAsJade va bien tant que votre composant attend un seul enfant. Vous pouvez définir via vos composants propTypes qu'il attend un seul enfant. React.Children.onlyLa fonction renvoie le seul enfant ou lève une exception s'il y en a plusieurs (cela n'existerait pas s'il n'y avait pas de cas d'utilisation).
cchamberlain
80

Essaye ça

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

Cela a fonctionné pour moi en utilisant react-15.1.

7puns
la source
3
Est-il possible de revenir React.cloneElement()directement sans l'entourer de <div>balises? Parce que si l'enfant est un <span>(ou quelque chose d'autre) et que nous voulons conserver son type d'élément tag?
adrianmc
1
Si c'est un enfant, vous pouvez laisser de côté l'emballage et cette solution ne fonctionne que pour un enfant, alors oui.
ThaJay
1
Travaille pour moi. Sans entourer <div>, c'est ok.
Crash Override
4
Si vous devez explicitement imposer que vous ne recevez qu'un seul enfant, vous pouvez le faire, React.cloneElement(React.Children.only(this.props.children), {...this.props})ce qui générera une erreur si plusieurs enfants lui sont transmis. Ensuite, vous n'avez pas besoin d'envelopper dans un div.
itsananderson
1
Cette réponse peut produire une valeur d'objet cyclique TypeError :. À moins que vous ne vouliez que l'un des accessoires de l'enfant soit lui-même, utilisez let {children, ...acyclicalProps} = this.propspuis React.cloneElement(React.Children.only(children), acyclicalProps).
Parabolord
69

Passez des accessoires pour diriger les enfants.

Voir toutes les autres réponses

Passer des données globales partagées à travers l'arborescence des composants via le contexte

Le contexte est conçu pour partager des données qui peuvent être considérées comme «globales» pour une arborescence de composants React, tels que l'utilisateur authentifié actuel, le thème ou la langue préférée. 1

Avertissement: il s'agit d'une réponse mise à jour, la précédente utilisait l'ancienne API de contexte

Il est basé sur le principe Consommateur / Fournir. Créez d'abord votre contexte

const { Provider, Consumer } = React.createContext(defaultValue);

Utilisez ensuite via

<Provider value={/* some value */}>
  {children} /* potential consumers */
<Provider />

et

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

Tous les consommateurs qui sont des descendants d'un fournisseur seront rendus à chaque fois que la valeur de l'accessoire du fournisseur change. La propagation du fournisseur à ses consommateurs descendants n'est pas soumise à la méthode shouldComponentUpdate, de sorte que le consommateur est mis à jour même lorsqu'un composant ancêtre sort de la mise à jour. 1

Exemple complet, semi-pseudo-code.

import React from 'react';

const { Provider, Consumer } = React.createContext({ color: 'white' });

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: { color: 'black' },
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

class Toolbar extends React.Component {
  render() {
    return ( 
      <div>
        <p> Consumer can be arbitrary levels deep </p>
        <Consumer> 
          {value => <p> The toolbar will be in color {value.color} </p>}
        </Consumer>
      </div>
    );
  }
}

1 https://facebook.github.io/react/docs/context.html

Lyubomir
la source
6
Contrairement à la réponse acceptée, cela fonctionnera correctement même si d'autres éléments sont inclus sous Parent. C'est certainement la meilleure réponse.
Zaptree
6
Props! = Context
Petr Peller
vous ne pouvez pas dépendre de modifications se propageant dans le contexte. Utilisez des accessoires quand il est possible qu'ils changent.
ThaJay
1
Peut-être que je ne comprends pas, mais n'est-il pas faux de dire «le contexte rend les accessoires disponibles»? Lorsque j'ai utilisé le contexte pour la dernière fois, c'était une chose distincte (c'est-à-dire this.context) - il n'a pas fusionné comme par magie le contexte avec les accessoires. Vous avez dû définir et utiliser intentionnellement le contexte, ce qui est tout autre chose.
Josh
Vous comprenez parfaitement, c'était incorrect. J'ai édité ma réponse.
Lyubomir
48

Passer les accessoires aux enfants imbriqués

Avec la mise à jour React 16.6 vous pouvez maintenant utiliser React.createContext et contextType .

import * as React from 'react';

// React.createContext accepts a defaultValue as the first param
const MyContext = React.createContext(); 

class Parent extends React.Component {
  doSomething = (value) => {
    // Do something here with value
  };

  render() {
    return (
       <MyContext.Provider value={{ doSomething: this.doSomething }}>
         {this.props.children}
       </MyContext.Provider>
    );
  }
}

class Child extends React.Component {
  static contextType = MyContext;

  onClick = () => {
    this.context.doSomething(this.props.value);
  };      

  render() {
    return (
      <div onClick={this.onClick}>{this.props.value}</div>
    );
  }
}


// Example of using Parent and Child

import * as React from 'react';

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <Child value={2} />
      </Parent>
    );
  }
}

React.createContext brille là où le cas React.cloneElement n'a pas pu gérer les composants imbriqués

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <SomeOtherComp><Child value={2} /></SomeOtherComp>
      </Parent>
    );
  }
}
Kenneth Truong
la source
3
Pouvez-vous expliquer pourquoi => les fonctions sont une mauvaise pratique? La fonction => aide à lier les gestionnaires d'événements pour obtenir le thiscontexte
Kenneth Truong
@KennethTruong car chaque fois qu'il est rendu, il crée une fonction.
itdoesntwork
9
@itdoesntwork ce n'est pas vrai. Il crée uniquement une nouvelle fonction lorsque la classe est créée. Ce n'est pas créé pendant la fonction de rendu ..
Kenneth Truong
@KennethTruong reactjs.org/docs/faq-functions.html#arrow-function-in-render Je pensais que vous parliez de la fonction flèche dans le rendu.
itdoesntwork
24

Vous pouvez utiliser React.cloneElement, il est préférable de savoir comment cela fonctionne avant de commencer à l'utiliser dans votre application. Il est introduit dans React v0.13, lisez la suite pour plus d'informations, donc quelque chose avec ce travail pour vous:

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

Apportez donc les lignes de la documentation React pour que vous compreniez comment tout cela fonctionne et comment vous pouvez les utiliser:

Dans React v0.13 RC2, nous allons introduire une nouvelle API, similaire à React.addons.cloneWithProps, avec cette signature:

React.cloneElement(element, props, ...children);

Contrairement à cloneWithProps, cette nouvelle fonction n'a pas de comportement intégré magique pour la fusion de style et className pour la même raison que nous n'avons pas cette fonctionnalité de transferPropsTo. Personne ne sait exactement quelle est la liste complète des choses magiques, ce qui rend difficile de raisonner sur le code et difficile à réutiliser lorsque le style a une signature différente (par exemple dans le prochain React Native).

React.cloneElement est presque équivalent à:

<element.type {...element.props} {...props}>{children}</element.type>

Cependant, contrairement à JSX et cloneWithProps, il conserve également les références. Cela signifie que si vous obtenez un enfant avec une référence, vous ne le volerez pas accidentellement à votre ancêtre. Vous obtiendrez la même référence attachée à votre nouvel élément.

Un modèle courant consiste à cartographier vos enfants et à ajouter un nouvel accessoire. De nombreux problèmes ont été signalés à propos de la perte de la référence par cloneWithProps, ce qui rend plus difficile de raisonner sur votre code. Maintenant, suivre le même modèle avec cloneElement fonctionnera comme prévu. Par exemple:

var newChildren = React.Children.map(this.props.children, function(child) {
  return React.cloneElement(child, { foo: true })
});

Remarque: React.cloneElement (enfant, {ref: 'newRef'}) REMPLACE la référence de sorte qu'il n'est toujours pas possible pour deux parents d'avoir une référence pour le même enfant, sauf si vous utilisez des références de rappel.

C'était une fonctionnalité essentielle pour entrer dans React 0.13 car les accessoires sont désormais immuables. Le chemin de mise à niveau consiste souvent à cloner l'élément, mais ce faisant, vous risquez de perdre la référence. Par conséquent, nous avions besoin d'un chemin de mise à niveau plus agréable ici. En améliorant les sites d'appels sur Facebook, nous avons réalisé que nous avions besoin de cette méthode. Nous avons reçu les mêmes commentaires de la communauté. Par conséquent, nous avons décidé de faire un autre RC avant la version finale pour nous assurer que nous obtenons cela.

Nous prévoyons de déprécier éventuellement React.addons.cloneWithProps. Nous ne le faisons pas encore, mais c'est une bonne occasion de commencer à réfléchir à vos propres utilisations et d'envisager d'utiliser React.cloneElement à la place. Nous veillerons à expédier une version avec des avis de dépréciation avant de la supprimer afin qu'aucune action immédiate ne soit nécessaire.

plus ici ...

Alireza
la source
18

La meilleure façon, qui vous permet de faire un transfert de propriété, c'est childrencomme une fonction

Exemple:

export const GrantParent = () => {
  return (
    <Parent>
      {props => (
        <ChildComponent {...props}>
          Bla-bla-bla
        </ChildComponent>
      )}
    </Parent>
  )
}

export const Parent = ({ children }) => {
    const somePropsHere = { //...any }
    <>
        {children(somePropsHere)}
    </>
}
Nick Ovchinnikov
la source
1
Cela me semble beaucoup plus simple (et meilleur pour les performances?) Que la réponse acceptée.
Shikyo
2
Cela nécessite que les enfants soient une fonction et ne fonctionne pas pour les composants profondément imbriqués
illusion numérique
@digitalillusion, je ne comprends pas ce que cela signifie nested components. React n'a pas de motifs imbriqués, seulement des compositions. Oui, les enfants doivent être une fonction, il n'y a aucun conflit, car c'est un enfant JSX valide. Pouvez-vous donner un exemple avec nesting components?
Nick Ovchinnikov
1
Vous avez raison, le cas des enfants profondément imbriqués peut également être traité au <Parent>{props => <Nest><ChildComponent /></Nest>}</Parent>lieu de (ne pas fonctionner), <Parent><Nest>{props => <ChildComponent />}</Nest></Parent>donc je suis d'accord que c'est la meilleure réponse
illusion numérique
Lors de la tentative, je reçois ce qui suit:TypeError: children is not a function
Ryan Prentiss
6

J'avais besoin de corriger la réponse acceptée ci-dessus pour le faire fonctionner en utilisant cela au lieu de ce pointeur. Ce dans le cadre de la fonction de carte n'a pas doSomething fonction définie.

var Parent = React.createClass({
doSomething: function() {
    console.log('doSomething!');
},

render: function() {
    var that = this;
    var childrenWithProps = React.Children.map(this.props.children, function(child) {
        return React.cloneElement(child, { doSomething: that.doSomething });
    });

    return <div>{childrenWithProps}</div>
}})

Mise à jour: ce correctif concerne ECMAScript 5, dans ES6 il n'y a pas besoin de var qui = this

olenak
la source
13
ou utilisez simplementbind()
plus
1
ou utiliser une fonction de flèche qui se lie à la portée lexicale, j'ai mis à jour ma réponse
Dominic
que doSomethingse doSomething: function(obj) { console.log(obj) }this.props.doSomething(obj)"obj"
passerait
4
@ plus- je sais que c'est vieux, mais utiliser bind ici est une idée terrible, bind crée une nouvelle fonction qui lie le contexte à une nouvelle. essentiellement une fonction appelant la applyméthode. l'utilisation bind()de la fonction de rendu créera une nouvelle fonction à chaque appel de la méthode de rendu.
Bamieh
6

Une façon plus propre en considérant un ou plusieurs enfants

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
and_rest
la source
Celui-ci ne fonctionne pas pour moi, il donne une erreur: les enfants ne sont pas définis.
Deelux
@Deelux this.props.children au lieu des enfants
Martin Dawson
cela passe l'enfant comme ses propres enfants this.props. En général, je ne recommanderais que le clonage avec des accessoires spécifiques, pas l'ensemble du shebang.
Andy
Les passes {...this.props}n'ont pas fonctionné pour moi, est-ce que c'est {...child.props}correct?
Felipe Augusto
Pour les composants fonctionnels:React.Children.map(children, child => React.cloneElement(child, props))
vsync
5

Aucune des réponses ne traite du problème d'avoir des enfants qui ne sont PAS des composants React, tels que des chaînes de texte. Une solution de contournement pourrait être quelque chose comme ceci:

// Render method of Parent component
render(){
    let props = {
        setAlert : () => {alert("It works")}
    };
    let childrenWithProps = React.Children.map( this.props.children, function(child) {
        if (React.isValidElement(child)){
            return React.cloneElement(child, props);
        }
          return child;
      });
    return <div>{childrenWithProps}</div>

}
poynting
la source
5

Vous n'en avez plus besoin {this.props.children}. Maintenant , vous pouvez envelopper votre composant enfant à l' aide renderde Routeet passer vos accessoires comme d' habitude:

<BrowserRouter>
  <div>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/posts">Posts</Link></li>
      <li><Link to="/about">About</Link></li>
    </ul>

    <hr/>

    <Route path="/" exact component={Home} />
    <Route path="/posts" render={() => (
      <Posts
        value1={1}
        value2={2}
        data={this.state.data}
      />
    )} />
    <Route path="/about" component={About} />
  </div>
</BrowserRouter>
oui
la source
2
Les accessoires de rendu sont désormais standard dans React ( reactjs.org/docs/render-props.html ) et méritent d'être considérés comme une nouvelle réponse acceptée pour cette question.
Ian Danforth
19
Comment est-ce une réponse à la question?
Maximo Dominguez
4

Parent.jsx:

import React from 'react';

const doSomething = value => {};

const Parent = props => (
  <div>
    {
      !props || !props.children 
        ? <div>Loading... (required at least one child)</div>
        : !props.children.length 
            ? <props.children.type {...props.children.props} doSomething={doSomething} {...props}>{props.children}</props.children.type>
            : props.children.map((child, key) => 
              React.cloneElement(child, {...props, key, doSomething}))
    }
  </div>
);

Child.jsx:

import React from 'react';

/* but better import doSomething right here,
   or use some flux store (for example redux library) */
export default ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}/>
);

et main.jsx:

import React from 'react';
import { render } from 'react-dom';
import Parent from './Parent';
import Child from './Child';

render(
  <Parent>
    <Child/>
    <Child value='1'/>
    <Child value='2'/>
  </Parent>,
  document.getElementById('...')
);

voir l'exemple ici: https://plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p=preview

Maksim Kostromin
la source
4

Peut-être que vous pouvez également trouver cette fonctionnalité utile, bien que de nombreuses personnes aient considéré cela comme un anti-modèle, elle peut toujours être utilisée si vous savez ce que vous faites et concevez bien votre solution.

Fonction en tant que composants enfants

Alexandr Cherednichenko
la source
Ce n'est pas un anti-modèle: youtube.com/watch?v=BcVAq3YFiuc
Pietro Coelho
4

Si vous avez plusieurs enfants auxquels vous souhaitez transmettre des accessoires , vous pouvez le faire de cette façon, en utilisant React.Children.map:

render() {
    let updatedChildren = React.Children.map(this.props.children,
        (child) => {
            return React.cloneElement(child, { newProp: newProp });
        });

    return (
        <div>
            { updatedChildren }
        </div>
    );
}

Si votre composant n'a qu'un seul enfant, il n'est pas nécessaire de mapper, vous pouvez simplement clonerElement immédiatement:

render() {
    return (
        <div>
            {
                React.cloneElement(this.props.children, {
                    newProp: newProp
                })
            }
        </div>
    );
}
Nesha Zoric
la source
3

Selon la documentation de cloneElement()

React.cloneElement(
  element,
  [props],
  [...children]
)

Clonez et renvoyez un nouvel élément React en utilisant l'élément comme point de départ. L'élément résultant aura les accessoires de l'élément d'origine avec les nouveaux accessoires fusionnés de manière superficielle. Les nouveaux enfants remplaceront les enfants existants. la clé et la référence de l'élément d'origine seront conservées.

React.cloneElement() est presque équivalent à:

<element.type {...element.props} {...props}>{children}</element.type>

Cependant, il conserve également les références. Cela signifie que si vous obtenez un enfant avec une référence, vous ne le volerez pas accidentellement à votre ancêtre. Vous obtiendrez la même référence attachée à votre nouvel élément.

Donc, cloneElement est ce que vous utiliseriez pour fournir des accessoires personnalisés aux enfants. Cependant, il peut y avoir plusieurs enfants dans le composant et vous devrez boucler dessus. Les autres réponses suggèrent que vous puissiez les cartographier à l'aide React.Children.map. Cependant, React.Children.mapcontrairement à ce qui React.cloneElementchange, les clés de l'élément sont ajoutées et extra .$comme préfixe. Consultez cette question pour plus de détails: React.cloneElement dans React.Children.map entraîne la modification des clés d'élément

Si vous souhaitez l'éviter, vous devriez plutôt opter pour la forEachfonction comme

render() {
    const newElements = [];
    React.Children.forEach(this.props.children, 
              child => newElements.push(
                 React.cloneElement(
                   child, 
                   {...this.props, ...customProps}
                )
              )
    )
    return (
        <div>{newElements}</div>
    )

}
Shubham Khatri
la source
2

Suite à la réponse @and_rest, voici comment je clone les enfants et ajoute une classe.

<div className="parent">
    {React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>
sidonaldson
la source
2

Je pense qu'un accessoire de rendu est la façon appropriée de gérer ce scénario

Vous laissez le parent fournir les accessoires nécessaires utilisés dans le composant enfant, en refactorisant le code parent pour ressembler à quelque chose comme ceci:

const Parent = ({children}) => {
  const doSomething(value) => {}

  return children({ doSomething })
}

Ensuite, dans le composant enfant, vous pouvez accéder à la fonction fournie par le parent de cette façon:

class Child extends React {

  onClick() => { this.props.doSomething }

  render() { 
    return (<div onClick={this.onClick}></div>);
  }

}

Maintenant, la structure fianl ressemblera à ceci:

<Parent>
  {(doSomething) =>
   (<Fragment>
     <Child value="1" doSomething={doSomething}>
     <Child value="2" doSomething={doSomething}>
    <Fragment />
   )}
</Parent>
ZEE
la source
que faire si le parent est juste un wrapper pour un {children} qui est une zone de texte dans un autre composant de classe?
Adebayo
2

Méthode 1 - Cloner des enfants

const Parent = (props) => {
   const attributeToAddOrReplace= "Some Value"
   const childrenWithAdjustedProps = React.Children.map(props.children, child =>
      React.cloneElement(child, { attributeToAddOrReplace})
   );

   return <div>{childrenWithAdjustedProps }</div>
}

Méthode 2 - utiliser un contexte composable

Le contexte vous permet de passer un accessoire à un composant enfant profond sans le faire passer explicitement comme accessoire à travers les composants entre les deux.

Le contexte présente des inconvénients:

  1. Les données ne circulent pas de manière régulière - via des accessoires.
  2. L'utilisation du contexte crée un contrat entre le consommateur et le fournisseur. Il peut être plus difficile de comprendre et de reproduire les exigences requises pour réutiliser un composant.

Utiliser un contexte composable

export const Context = createContext<any>(null);

export const ComposableContext = ({ children, ...otherProps }:{children:ReactNode, [x:string]:any}) => {
    const context = useContext(Context)
    return(
      <Context.Provider {...context} value={{...context, ...otherProps}}>{children}</Context.Provider>
    );
}

function App() {
  return (
      <Provider1>
            <Provider2> 
                <Displayer />
            </Provider2>
      </Provider1>
  );
}

const Provider1 =({children}:{children:ReactNode}) => (
    <ComposableContext greeting="Hello">{children}</ComposableContext>
)

const Provider2 =({children}:{children:ReactNode}) => (
    <ComposableContext name="world">{children}</ComposableContext>
)

const Displayer = () => {
  const context = useContext(Context);
  return <div>{context.greeting}, {context.name}</div>;
};
Ben Carp
la source
Un peu tard, mais pourriez-vous expliquer la notation en {children}:{children:ReactNode}?
camille
@camille, c'est une chose Typescript. En le regardant maintenant, je répondrais simplement avec Javascript, et même si j'écrivais Typescript, je le ferais différemment. Pourrait le modifier à l'avenir.
Ben Carp
1
@camille, fondamentalement, cela signifie que la valeur qui a la clé "children"est de typeReactNode
Ben Carp
1

La façon la plus simple de le faire:

    {React.cloneElement(this.props.children, this.props)}
nitte93user3232918
la source
5
Cela ne copie-t-il pas this.props.children dans this.props.children de l'enfant? et en fait copier l'enfant en lui-même?
Arshabh Agarwal
1

Pour tous ceux qui ont un seul élément enfant, cela devrait le faire.

{React.isValidElement(this.props.children)
                  ? React.cloneElement(this.props.children, {
                      ...prop_you_want_to_pass
                    })
                  : null}
user10884362
la source
0

Est-ce ce dont vous aviez besoin?

var Parent = React.createClass({
  doSomething: function(value) {
  }
  render: function() {
    return  <div>
              <Child doSome={this.doSomething} />
            </div>
  }
})

var Child = React.createClass({
  onClick:function() {
    this.props.doSome(value); // doSomething is undefined
  },  
  render: function() {
    return  <div onClick={this.onClick}></div>
  }
})
amit_183
la source
4
Non, je ne veux pas contraindre le contenu de mon wrapper à un contenu spécifique.
plus
0

Une raison pour laquelle React.children ne travaillait pas pour moi. C'est ce qui a fonctionné pour moi.

Je voulais juste ajouter une classe à l'enfant. similaire à changer un accessoire

 var newChildren = this.props.children.map((child) => {
 const className = "MenuTooltip-item " + child.props.className;
    return React.cloneElement(child, { className });
 });

 return <div>{newChildren}</div>;

L'astuce ici est le React.cloneElement . Vous pouvez passer n'importe quel accessoire de manière similaire

aWebDeveloper
la source
0

Les accessoires de rendu constituent l'approche la plus précise de ce problème. Au lieu de passer le composant enfant au composant parent comme accessoires enfants, laissez le parent rendre le composant enfant manuellement. Le rendu est des accessoires intégrés dans react, qui prend le paramètre de fonction. Dans cette fonction, vous pouvez laisser le composant parent rendre ce que vous voulez avec des paramètres personnalisés. Fondamentalement, il fait la même chose que les accessoires pour enfants, mais il est plus personnalisable.

class Child extends React.Component {
  render() {
    return <div className="Child">
      Child
      <p onClick={this.props.doSomething}>Click me</p>
           {this.props.a}
    </div>;
  }
}

class Parent extends React.Component {
  doSomething(){
   alert("Parent talks"); 
  }

  render() {
    return <div className="Parent">
      Parent
      {this.props.render({
        anythingToPassChildren:1, 
        doSomething: this.doSomething})}
    </div>;
  }
}

class Application extends React.Component {
  render() {
    return <div>
      <Parent render={
          props => <Child {...props} />
        }/>
    </div>;
  }
}

Exemple chez codepen

omeralper
la source
0

Lorsque vous utilisez des composants fonctionnels, vous obtenez souvent l' TypeError: Cannot add property myNewProp, object is not extensibleerreur lorsque vous essayez de définir de nouvelles propriétés props.children. Il y a un moyen de contourner cela en clonant les accessoires puis en clonant l'enfant lui-même avec les nouveaux accessoires.

const MyParentComponent = (props) => {
  return (
    <div className='whatever'>
      {props.children.map((child) => {
        const newProps = { ...child.props }
        // set new props here on newProps
        newProps.myNewProp = 'something'
        const preparedChild = { ...child, props: newProps }
        return preparedChild
      })}
    </div>
  )
}
un maître
la source