Estoy tratando de encontrar la forma correcta de definir algunos componentes que podrían usarse de manera genérica:
<Parent>
<Child value="1">
<Child value="2">
</Parent>
Hay una lógica pasando por la representación entre los padres y los niños de los componentes supuesto, se puede imaginar <select>
y <option>
como ejemplo de esta lógica.
Esta es una implementación ficticia para el propósito de la pregunta:
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 pregunta es cada vez que se utiliza {this.props.children}
para definir un componente contenedor, ¿cómo se transmite alguna propiedad a todos sus hijos?
Puede usar React.Children
para iterar sobre los hijos y luego clonar cada elemento con nuevos accesorios (fusionados superficialmente) usando React.cloneElement
. Por ejemplo:
const Child = ({ doSomething, value }) => (
<button onClick={() => doSomething(value)}>Click Me</button>
);
class Parent extends React.Component{
doSomething = value => {
console.log("doSomething called by child with value:", value);
}
render() {
const childrenWithProps = React.Children.map(this.props.children, child => {
// checking isValidElement is the safe way and avoids a typescript error too
if (React.isValidElement(child)) {
return React.cloneElement(child, { doSomething: this.doSomething });
}
return child;
});
return <div>{childrenWithProps}</div>;
}
}
function App() {
return (
<Parent>
<Child value={1} />
<Child value={2} />
</Parent>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id="container"></div>
Alternativamente, puede pasar accesorios a los niños con accesorios de renderizado . En este enfoque, los hijos (que pueden ser children
o cualquier otro nombre de accesorio) es una función que puede aceptar cualquier argumento que desee pasar y devuelve los hijos:
const Child = ({ doSomething, value }) => (
<button onClick={() => doSomething(value)}>Click Me</button>
);
class Parent extends React.Component{
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>{this.props.children(this.doSomething)}</div>
}
};
function App(){
return (
<Parent>
{doSomething => (
<React.Fragment>
<Child doSomething={doSomething} value={1} />
<Child doSomething={doSomething} value={2} />
</React.Fragment>
)}
</Parent>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id="container"></div>
En lugar de <React.Fragment>
o simplemente <>
, también puede devolver una matriz si lo prefiere.
Para una forma un poco más limpia de hacerlo, intente:
<div>
{React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>
Editar: para usar con varios niños individuales (el niño en sí mismo debe ser un componente) puede hacerlo. Probado en 16.8.6
<div>
{React.cloneElement(props.children[0], { loggedIn: true, testingTwo: true })}
{React.cloneElement(props.children[1], { loggedIn: true, testProp: false })}
</div>
Prueba esto
<div>{React.cloneElement(this.props.children, {...this.props})}</div>
Me funcionó usando react-15.1.
Ver todas las otras respuestas
El contexto está diseñado para compartir datos que pueden considerarse "globales" para un árbol de componentes de React, como el usuario autenticado actual, el tema o el idioma preferido. 1
Descargo de responsabilidad: esta es una respuesta actualizada, la anterior utilizó la API de contexto anterior
Se basa en el principio consumidor / proveedor. Primero, crea tu contexto
const { Provider, Consumer } = React.createContext(defaultValue);
Entonces usa via
<Provider value={/* some value */}>
{children} /* potential consumers */
<Provider />
y
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
Todos los Consumidores que son descendientes de un Proveedor volverán a renderizar cada vez que cambie la propuesta de valor del Proveedor. La propagación del Proveedor a sus Consumidores descendientes no está sujeta al método shouldComponentUpdate, por lo que el Consumidor se actualiza incluso cuando un componente ancestro abandona la actualización. 1
Ejemplo completo, semi-pseudocódigo.
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>
);
}
}
Con la actualización a Reaccionar 16.6 ahora puede usar React.createContext y 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 brilla donde el caso React.cloneElement no pudo manejar componentes anidados
class SomeComponent extends React.Component {
render() {
return (
<Parent>
<Child value={1} />
<SomeOtherComp><Child value={2} /></SomeOtherComp>
</Parent>
);
}
}
Puede usarlo React.cloneElement
, es mejor saber cómo funciona antes de comenzar a usarlo en su aplicación. Se presenta en React v0.13
, siga leyendo para obtener más información, así que algo junto con este trabajo para usted:
<div>{React.cloneElement(this.props.children, {...this.props})}</div>
Así que trae las líneas de la documentación de React para que entiendas cómo funciona todo y cómo puedes usarlas:
En React v0.13 RC2 presentaremos una nueva API, similar a React.addons.cloneWithProps, con esta firma:
React.cloneElement(element, props, ...children);
A diferencia de cloneWithProps, esta nueva función no tiene ningún comportamiento mágico incorporado para fusionar style y className por la misma razón que no tenemos esa característica de transferPropsTo. Nadie está seguro de cuál es exactamente la lista completa de cosas mágicas, lo que hace que sea difícil razonar sobre el código y difícil de reutilizar cuando el estilo tiene una firma diferente (por ejemplo, en el próximo React Native).
React.cloneElement es casi equivalente a:
<element.type {...element.props} {...props}>{children}</element.type>
Sin embargo, a diferencia de JSX y cloneWithProps, también conserva las referencias. Esto significa que si tiene un hijo con un árbitro, no se lo robará accidentalmente a su antepasado. Obtendrá la misma referencia adjunta a su nuevo elemento.
Un patrón común es mapear a sus hijos y agregar un nuevo accesorio. Se informaron muchos problemas sobre la pérdida de la referencia de cloneWithProps, lo que hace que sea más difícil razonar sobre su código. Ahora, seguir el mismo patrón con cloneElement funcionará como se esperaba. Por ejemplo:
var newChildren = React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, { foo: true })
});
Nota: React.cloneElement (child, {ref: 'newRef'}) anula la referencia, por lo que todavía no es posible que dos padres tengan una referencia para el mismo hijo, a menos que use callback-refs.
Esta fue una característica crítica para ingresar a React 0.13 ya que los accesorios ahora son inmutables. La ruta de actualización suele ser clonar el elemento, pero al hacerlo, puede perder la referencia. Por lo tanto, necesitábamos una mejor ruta de actualización aquí. Cuando estábamos actualizando los sitios de llamadas en Facebook, nos dimos cuenta de que necesitábamos este método. Recibimos los mismos comentarios de la comunidad. Por lo tanto, decidimos hacer otro RC antes del lanzamiento final para asegurarnos de que lo incluimos.
Planeamos eventualmente desaprobar React.addons.cloneWithProps. No lo estamos haciendo todavía, pero esta es una buena oportunidad para comenzar a pensar en sus propios usos y considerar usar React.cloneElement en su lugar. Nos aseguraremos de enviar una versión con avisos de obsolescencia antes de eliminarla, por lo que no es necesaria ninguna acción inmediata.
más aquí ...
La mejor manera, que le permite realizar una transferencia de propiedad, es children
como un patrón de función
https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9
Fragmento de código: https://stackblitz.com/edit/react-fcmubc
Ejemplo:
const Parent = ({ children }) => {
const somePropsHere = {
style: {
color: "red"
}
// any other props here...
}
return children(somePropsHere)
}
const ChildComponent = props => <h1 {...props}>Hello world!</h1>
const App = () => {
return (
<Parent>
{props => (
<ChildComponent {...props}>
Bla-bla-bla
</ChildComponent>
)}
</Parent>
)
}
Ninguna de las respuestas aborda el problema de tener hijos que NO sean componentes de React, como cadenas de texto. Una solución alternativa podría ser algo como esto:
// 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>
}
Necesitaba corregir la respuesta aceptada anterior para que funcione usando eso en lugar de este puntero. Esto dentro del alcance de la función de mapa no tenía definida la función doSomething .
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>
}})
Actualización: esta corrección es para ECMAScript 5, en ES6 no hay necesidad en var that = this
Manera más limpia considerando uno o más niños
<div>
{ React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
Si tiene varios hijos a los que desea pasar accesorios , puede hacerlo de esta manera, usando React.Children.map:
render() {
let updatedChildren = React.Children.map(this.props.children,
(child) => {
return React.cloneElement(child, { newProp: newProp });
});
return (
<div>
{ updatedChildren }
</div>
);
}
Si su componente tiene solo un hijo, no hay necesidad de mapeo, puede simplemente cloneElement de inmediato:
render() {
return (
<div>
{
React.cloneElement(this.props.children, {
newProp: newProp
})
}
</div>
);
}
Ya no necesitas {this.props.children}
. Ahora usted puede envolver su componente secundario usando render
en Route
y pasar sus puntales como de costumbre:
<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>
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)}/>
);
y 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('...')
);
vea el ejemplo aquí: https://plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p=preview
Quizás también pueda encontrar útil esta función, aunque muchas personas lo han considerado como un anti-patrón, aún se puede usar si sabe lo que está haciendo y diseña bien su solución.
Según la documentación de cloneElement()
React.cloneElement(
element,
[props],
[...children]
)
Clone y devuelva un nuevo elemento React usando element como punto de partida. El elemento resultante tendrá los accesorios del elemento original con los nuevos accesorios fusionados superficialmente. Los niños nuevos reemplazarán a los niños existentes. Se conservarán la clave y la referencia del elemento original.
React.cloneElement()
es casi equivalente a:<element.type {...element.props} {...props}>{children}</element.type>
Sin embargo, también conserva las refs. Esto significa que si tiene un hijo con un árbitro, no se lo robará accidentalmente a su antepasado. Obtendrá la misma referencia adjunta a su nuevo elemento.
Entonces cloneElement es lo que usaría para proporcionar accesorios personalizados a los niños. Sin embargo, puede haber varios hijos en el componente y necesitaría recorrerlo. Lo que sugieren otras respuestas es que las mapees usando React.Children.map
. Sin embargo, a React.Children.map
diferencia de los React.cloneElement
cambios, las claves del elemento agregan y extra .$
como prefijo. Consulte esta pregunta para obtener más detalles: React.cloneElement dentro de React.Children.map está haciendo que las claves de los elementos cambien
Si desea evitarlo, debe optar por la forEach
función como
render() {
const newElements = [];
React.Children.forEach(this.props.children,
child => newElements.push(
React.cloneElement(
child,
{...this.props, ...customProps}
)
)
)
return (
<div>{newElements}</div>
)
}
Creo que un accesorio de renderizado es la forma adecuada de manejar este escenario.
Dejas que el padre proporcione los accesorios necesarios que se utilizan en el componente hijo, refactorizando el código del padre para que se vea en algo como esto:
const Parent = ({children}) => {
const doSomething(value) => {}
return children({ doSomething })
}
Luego, en el componente secundario, puede acceder a la función proporcionada por el padre de esta manera:
class Child extends React {
onClick() => { this.props.doSomething }
render() {
return (<div onClick={this.onClick}></div>);
}
}
Ahora la estructura original se verá así:
<Parent>
{(doSomething) =>
(<Fragment>
<Child value="1" doSomething={doSomething}>
<Child value="2" doSomething={doSomething}>
<Fragment />
)}
</Parent>
const Parent = (props) => {
const attributeToAddOrReplace= "Some Value"
const childrenWithAdjustedProps = React.Children.map(props.children, child =>
React.cloneElement(child, { attributeToAddOrReplace})
);
return <div>{childrenWithAdjustedProps }</div>
}
El contexto le permite pasar un accesorio a un componente secundario profundo sin pasarlo explícitamente como un accesorio a través de los componentes intermedios.
El contexto tiene sus inconvenientes:
Usando un contexto componible
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>;
};
Además de la respuesta de @and_rest, así es como clono a los niños y agrego una clase.
<div className="parent">
{React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>
Al usar componentes funcionales, a menudo obtendrá el TypeError: Cannot add property myNewProp, object is not extensible
error al intentar establecer nuevas propiedades en props.children
. Hay una solución para esto clonando los accesorios y luego clonando al niño con los nuevos accesorios.
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>
)
}
La forma más hábil de hacer esto:
{React.cloneElement(this.props.children, this.props)}
Para cualquiera que tenga un solo elemento hijo, esto debería hacerlo.
{React.isValidElement(this.props.children)
? React.cloneElement(this.props.children, {
...prop_you_want_to_pass
})
: null}
Me inspiré en todas las respuestas anteriores y esto es lo que he hecho. Estoy pasando algunos accesorios como algunos datos y algunos componentes.
import React from "react";
const Parent = ({ children }) => {
const { setCheckoutData } = actions.shop;
const { Input, FieldError } = libraries.theme.components.forms;
const onSubmit = (data) => {
setCheckoutData(data);
};
const childrenWithProps = React.Children.map(
children,
(child) =>
React.cloneElement(child, {
Input: Input,
FieldError: FieldError,
onSubmit: onSubmit,
})
);
return <>{childrenWithProps}</>;
};
¿Es esto lo que necesitabas?
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>
}
})
Alguna razón por la que React.children no estaba funcionando para mí. Esto es lo que funcionó para mí.
Solo quería agregar una clase al niño. similar a cambiar un accesorio
var newChildren = this.props.children.map((child) => {
const className = "MenuTooltip-item " + child.props.className;
return React.cloneElement(child, { className });
});
return <div>{newChildren}</div>;
El truco aquí es el React.cloneElement . Puedes pasar cualquier utilería de manera similar
Render props es el enfoque más preciso para este problema. En lugar de pasar el componente hijo al componente principal como accesorios secundarios, deje que el padre represente el componente secundario manualmente. Render tiene accesorios incorporados en react, que toma el parámetro de función. En esta función, puede permitir que el componente principal represente lo que desee con parámetros personalizados. Básicamente, hace lo mismo que los accesorios para niños, pero es más personalizable.
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>;
}
}
Llegué a esta publicación mientras investigaba para una necesidad similar, pero sentí que la solución de clonación que es tan popular es demasiado cruda y me aleja de la funcionalidad.
Encontré un artículo en react Documents Componentes de orden superior
Aquí está mi muestra:
import React from 'react';
const withForm = (ViewComponent) => {
return (props) => {
const myParam = "Custom param";
return (
<>
<div style={{border:"2px solid black", margin:"10px"}}>
<div>this is poc form</div>
<div>
<ViewComponent myParam={myParam} {...props}></ViewComponent>
</div>
</div>
</>
)
}
}
export default withForm;
const pocQuickView = (props) => {
return (
<div style={{border:"1px solid grey"}}>
<div>this is poc quick view and it is meant to show when mouse hovers over a link</div>
</div>
)
}
export default withForm(pocQuickView);
Para mí, encontré una solución flexible al implementar el patrón de Componentes de orden superior.
Por supuesto, depende de la funcionalidad, pero es bueno que si alguien más está buscando un requisito similar, es mucho mejor que depender de un código de reacción de nivel sin procesar como la clonación.
Otro patrón que utilizo activamente es el patrón de contenedor. lea sobre esto, hay muchos artículos por ahí.
La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.
Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.
Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.
Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.
Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?
Las mujeres del Reino Unido acuden a las urnas para las elecciones especiales de hoy en un aniversario particularmente apropiado. Han pasado 104 años desde que Emily Wilding Davison murió por la causa del sufragio femenino, pisoteada hasta la muerte en medio de una carrera de caballos de alto perfil, frente al Rey y la Reina.
Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.
Hemos pirateado algunas ciudades industriales en esta columna, como Los Ángeles y Las Vegas. Ahora es el momento de una ciudad militar-industrial-compleja.
Un minorista está enlatando su sección de tallas grandes. Pero no están tomando la categoría solo en línea o descontinuándola por completo.
El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.
Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon
El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.
El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".
Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.
Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.