web-dev-qa-db-fra.com

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

J'essaie de trouver le bon moyen 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 existe bien entendu une logique de rendu entre les composants parent et enfants, vous pouvez donc imaginer <select> et <option> comme exemple de cette logique.

Ceci est une implémentation factice pour les besoins 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 que chaque fois que vous utilisez {this.props.children} pour définir un composant wrapper, comment transmettez-vous une propriété à tous ses enfants?

682
plus-

Vous pouvez utiliser React.Children pour parcourir les enfants, puis cloner chaque élément avec de nouveaux accessoires (fusion peu profonde) à l'aide de React.cloneElement e.g:

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

class Parent extends React.PureComponent {
  doSomething = (value) => {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    const { children } = this.props;

    const childrenWithProps = React.Children.map(children, child =>
      React.cloneElement(child, { doSomething: this.doSomething })
    );

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

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

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

756
Dominic

Pour une façon un peu plus propre de le faire, essayez:

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

Remarque : cela ne fonctionnera que s'il y a un seul enfant et qu'il s'agit d'un élément React valide.

318
Andres F Garcia

Essaye ça

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

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

64
7puns

Pass accessoires pour diriger les enfants.

Voir toutes les autres réponses

Transmettez des données globales partagées dans l’arborescence des composants via context

Le contexte est conçu pour partager des données pouvant ê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/fourniture. Tout d'abord, créez votre contexte 

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

Puis utiliser 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 les descendants d’un fournisseur reviendront à l’affichage chaque fois que l’accessoire de valeur du fournisseur sera modifié. La propagation du fournisseur à ses descendants Consumers n'est pas soumise à la méthode shouldComponentUpdate. Le consommateur est donc mis à jour même lorsqu'un composant ancêtre quitte la mise à jour. 

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

49
Lyubomir

Avec la mise à jour de 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() {
    <Parent>
      <Child value={1} />
      <Child value={2} />
    </Parent>
  }
}

React.createContext indique où React.cloneElement case ne peut pas gérer les composants imbriqués

class SomeComponent extends React.Component {

  render() {
    <Parent>
      <Child value={1} />
      <SomeOtherComp><Child value={2} /></SomeOtherComp>
    </Parent>
  }
}
30
Kenneth Truong

Vous pouvez utiliser React.cloneElement, il est préférable de connaître son fonctionnement avant de commencer à l'utiliser dans votre application. Il a été introduit dans React v0.13, lisez la suite pour plus d'informations, pour que quelque chose de parallèle fonctionne pour vous:

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

Alors, apportez les lignes de la documentation React pour que vous compreniez comment tout 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 magie comportement intégré pour la fusion de style et className pour la même raison nous n'avons pas cette fonctionnalité de transferPropsTo. Personne ne sait quoi exactement la liste complète des choses magiques sont, ce qui la rend difficile à raisonner sur le code et difficile à réutiliser quand 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. Ce signifie que si vous avez un enfant avec une référence, vous ne serez pas accidentellement le voler à votre ancêtre. Vous obtiendrez la même référence attachée à votre nouvel élément.

Un schéma courant consiste à cartographier vos enfants et à ajouter un nouvel accessoire . De nombreux problèmes ont été signalés concernant la perte de l’arbitre, ce qui rend plus difficile de raisonner sur votre code. Maintenant, suivez la même chose pattern 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 (child, {ref: 'newRef'}) NE remplace PAS le fichier ref, il n’est donc toujours pas possible que deux parents aient une référence au même enfant, sauf si vous utilisez callback-refs.

C'était une caractéristique essentielle pour entrer dans React 0.13 puisque les accessoires sont maintenant immuable. Le chemin de mise à niveau consiste souvent à cloner l'élément, mais par Vous risqueriez de perdre l'arbitre. Par conséquent, nous avions besoin d’une mise à niveau plus agréable chemin ici. Alors que nous améliorions les sites d’appel sur Facebook, nous avons réalisé que nous avions besoin de cette méthode. Nous avons eu les mêmes retours de la part de la communauté . Par conséquent, nous avons décidé de créer un autre RC avant la publication finale de assurez-vous que nous obtenons cela dans.

Nous prévoyons de déprécier éventuellement React.addons.cloneWithProps. N'étaient pas le faire encore, mais c’est une bonne occasion de commencer à réfléchir à vos propres utilisations et envisagez plutôt d’utiliser React.cloneElement. Bien être Assurez-vous d'expédier une version avec des avis de dépréciation avant que nous ayons réellement retirez-le pour qu'aucune action immédiate ne soit nécessaire.

plus ici ...

18
Alireza

Vous n'avez plus besoin de {this.props.children}. Vous pouvez maintenant envelopper votre composant enfant en utilisant render dans Route et transmettre 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>
5
yeasayer

Plus propre compte tenu d'un ou plusieurs enfants

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
4
and_rest

La meilleure façon de procéder à un transfert de propriété est children comme 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)}
    </>
}
4
Nick Ovchinnikov

Je devais corriger la réponse acceptée ci-dessus pour que cela fonctionne avec that au lieu de this pointeur. Cela dans le cadre de la fonction de carte n'a pas eu quelque chose 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'est pas nécessaire dans var que = this

4
olenak

Si vous avez plusieurs enfants auxquels vous souhaitez transmettre les accessoires , vous pouvez le faire de cette façon, en utilisant le fichier 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 a seulement un enfant, il n'y a pas besoin de mappage, vous pouvez simplement clonerElement tout de suite:

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

Aucune des réponses n'aborde le problème des enfants qui sontPASdes 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>

}
2
poynting

Vous pouvez également trouver cette fonctionnalité utile, même si de nombreuses personnes l’ont considérée comme un anti-motif, elle peut toujours être utilisée si vous savez ce que vous faites et concevez bien votre solution.

Fonction en tant que composants enfants

2

Selon la documentation de cloneElement()

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

Cloner et renvoyer un nouvel élément React en utilisant element comme élément de départ point. L’élément résultant aura les accessoires de l’élément original avec les nouveaux accessoires fusionnés peu profondement. Les nouveaux enfants remplaceront enfants existants. La clé et la référence de l'élément d'origine seront conservé.

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 avez un enfant avec un ref dessus, 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 le parcourir en boucle. Ce que d’autres réponses suggèrent, c’est que vous puissiez les utiliser en utilisant React.Children.map Cependant, React.Children.map, à la différence de React.cloneElement, modifie les clés de l'élément ajouté et le .$ supplémentaire comme préfixe. Vérifiez cette question pour plus de détails: React.cloneElement à l'intérieur de React.Children.map provoque le changement des clés d'élément

Si vous souhaitez l’éviter, optez plutôt pour la fonction forEach comme

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

}
2
Shubham Khatri

Je pense qu'un accessoire de rendu est le moyen approprié de gérer ce scénario

Vous laissez le parent fournir les accessoires nécessaires utilisés dans le composant enfant, en restructurant le code parent pour qu'il ressemble à 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 la manière suivante:

class Child extends React {

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

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

}

Maintenant, la structure fianl va ressembler à ceci:

<Parent>
  {(doSomething) =>
   (<Fragment>
     <Child value="1" doSomething={doSomething}>
     <Child value="2" doSomething={doSomething}>
    <Fragment />
   )}
</Parent>
1
Ze Rubeus

Pour quiconque a 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}
1
user10884362

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 exemple ici: https://plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p=preview

1
Maksim Kostromin

Suite à @and_rest answer, 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>
1
sidonaldson

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 transmettre un accessoire à un composant enfant profond sans le transmettre explicitement comme accessoire à travers les composants situés entre.

Le contexte comporte des inconvénients:

  1. Les données ne circulent pas comme d'habitude - 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.

tilisation d'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>;
};

1
Ben Carp

Le moyen le plus simple de faire cela:

    {React.cloneElement(this.props.children, this.props)}
0
nitte93user3232918

Render props est l'approche la plus précise à ce problème. Au lieu de transmettre le composant enfant au composant parent sous forme d'accessoires enfants, laissez parent rendre le composant enfant manuellement. Render est un accessoire intégré dans react, qui prend le paramètre de fonction. Dans cette fonction, vous pouvez laisser le composant parent restituer 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

0
omeralper

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>
  }
})
0
amit_183

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

Je voulais juste ajouter une classe à l'enfant. semblable à 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>;

Le truc ici est le React.cloneElement . Vous pouvez passer n'importe quel accessoire de la même manière

0
aWebDeveloper