No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

React.Children y React.cloneElement

11/19
Recursos

Aportes 42

Preguntas 11

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

11.-React.Children y React.cloneElement


Para poder pasar propiedades especiales a los componentes hijos de nuestros componentes contenedores cuando hacemos composición.


Cuando enviamos más de un componente o elemento hijo al que use CloneElement, la app deja de funcionar y suelta un error. CloneElement necesita recibir un elemento de react, cuando children es más de un componente entonces tenemos un array, para esto existe React.Children que nos ayuda a que CloneElement entienda sin importar cuantos elementos vienen en el props.children.

function TodoHeader({ children, loading }) {
  //No importa si viene un elemento, o dos o null siempre nos devuelve un array

  return (
    <header>
      {React.Children.toArray(children).map((child) =>
        React.cloneElement(child, { loading: loading })
      )}
    </header> //Por cada child vamos a llamar a clone element.
  ); //Crear elemento a partir de otro (elemento, objeto con las props que queramos que tenga)
}

No son las herramientas más populares pero pueden ser muy útiles cuando queremos compartir una o ciertas props a los componentes hijos de un componente contenedor.

Imagino un componente que se renderiza en diferentes partes pero en alguna de ellas toma algunas propiedades diferentes ya sea estilos o textos. se podría en ciertas partes usar React.cloneElemet para no tener que modificar otros componentes padres del aplicativo.

🗄️ React.Children y React.cloneElement

Apuntes

React.cloneElement

  • Con esta característica de React podemos crear elementos de Nodos React
  • Cabe aclarar que esta funciona con un unico nodo, en caso de aplicarla en un conjunto de los mismos podemos ayudarnos de React.Children

React.Children

  • Nos permite manipular la prop children entre uno de sus usos podemos volver un conjunto de nodos react a un array

Muy buena explicación! Solo un apunte, actualmente cloneElement está desaconsejado por React y en su documentación te muestra diferentes alternativas: https://beta.reactjs.org/reference/react/cloneElement
Un saludo!

Me pareció sencillo trabajar así también jeje, no se qué opinen:

import React from "react";

function TodoHeader({ children, loading }) {
  return (
    <header>
      {
        [...children].map((child, index) => (
          React.cloneElement(child, { loading, key: index })
        ))
      }
    </header>
  );
}

export {
  TodoHeader
};

 {React.Children.toArray(children).map((child) => React.cloneElement(child, { loading }))}

Pónganle un:

.TodoSearch__container {
   transition: 1s;
}

Y verán que bonito

Recomiendo mucho el link que adjunto Juan de Medium! Buenísimo, me lo dejó super claro

React.Children tiene varias utilidades para trabajar con props.children. Para profundizar en que hacen les dejo lo siguiente:
React.Children

React.Children.map( children, function() )

React.Children.forEach( children, function() )

React.Children.count( children )

React.Children.only( children )

React.Children.toArray( children )

Con respecto a React.cloneElement( children, { props } ) me sirve para cuando tengo varios hijos dentro de un componente y casualmente todos ellos usan las mismas props que el padre además de otras propias. Por tanto para evitar escribirle las mismas props a cada hijo, las clonamos del padre y con métodos de array las repartimos entre los hijos.
React.cloneElement

🤯 despacio despacio cerebrito.
Wow, esto sí que me voló la cabeza.

Los “React Children” se usan principalmente para dar estructura y organizar el contenido dentro de un componente React. Al utilizar componentes en React, se puede dividir el contenido en diferentes partes y organizarlo de manera más clara y sencilla. Además, esto permite reutilizar código de manera más eficiente, ya que puedes crear componentes genéricos que pueden ser utilizados en diferentes partes de tu aplicación.

React.cloneElement es una función de React que se utiliza para clonar un elemento y pasarle nuevas propiedades. Esto puede ser útil en casos donde quieras crear una copia de un componente existente y modificar algunas de sus propiedades, pero sin tener que crear un nuevo componente desde cero. Por ejemplo, si tienes un componente que muestra un botón y quieres cambiar el color del botón en una parte de tu aplicación, puedes utilizar React.cloneElement para clonar el componente del botón y pasarle una nueva propiedad de color.

no se ustedes, pero me parece innecesario complicarme la vida xd

podria ser para evitar el DRY, pero no lo se

Está bueno conocer este tipo de herramientas que no son tan utilizadas, pero existen y uno puede toparse con ellas en un proyecto laboral.

Para los que estén usando Typescript y sufran un poco con los tipos les comparto lo que hice.

  • Children en el componente de Header lo difiní como un array de ReactElement (Es mejor usar ReactElement ya que es más específico que ReactNode).
  • A los componentes del searchbar y del título les puse como nuleable la propiedad de “loading”
interface TodoHeaderType {
    children : ReactElement[],
    loading : boolean,
}

function TodoHeader({children, loading} : TodoHeaderType) {
    return (
        <header>
            {
                React.Children
                    .map(children, child => React.cloneElement(child, {loading}))
            }
        </header>
    );
}

export default TodoHeader;

Para los que quieran una animación de carga que va acorde al proyecto a mi parecer les dejo un fade en css:

.TodoCounter--loading {
  animation: fade 1s infinite;
}

/* infinite fade animation */
@keyframes fade {
  0% {
    opacity: 0.5;
  }
  50% {
    opacity: 0.3;
  }
  100% {
    opacity: 0.5;
  }
}

React.Children y React.cloneElement , me parecieron bastante utiles para pasar props entre varios hijos y contenedores, con eso no tener que estar pasando todos los props a cada hijo directamente

Me funcionó sin el paso extra de React.Children.toArray(children)

TodoCounters desde el curso de introducción ya habia puesto estos condicionales para evitar que saliera y el resto era css normal:

import React from 'react';
import './TodoCounter.scss';

function TodoCounter({loading, completedTodos, totalTodos}) {

  if (!loading) {
    if (totalTodos === 0) {
      return (
        <h2>
          Escribe tu primera tarea
        </h2>
      );
    } else if (totalTodos === completedTodos) {
      return (
        <h2 className='textCounter'>Felicidades, completaste todas las tareas!!!</h2>
      );
    } else {
      return (
        <h2>
          Has completado {completedTodos} de {totalTodos} tareas
        </h2>
      );
    }
  } else {
     return;
 }
}

  export { TodoCounter };

Me sirvió la idea para el TodoSearch …👍

Creo que puede ser util para manipular una gran cantidad de componentes children y es necesario agregarles una propiedad a todos o incluso definiendo a que componente darle la propiedad.

Esta herramienta acaba de reemplazar formalmente al hook useContext 🔁

En mi app, le estaba enviando el estado “darkMode” a CADA componente para asignar estilos oscuros… esta funcionalidad me arregla la vida!

Esto es lo que tenia tiempo queriendo saber como se hacia, pense no era posible algo asi, excelente clase.

Un uso interesante puede ser si se necesita que todos los componentes dentro de un container tengan cierto color o propiedad visual, y que si están fuera de ese container no tengan ese color. O incluso poder definir ese color dependiendo del container. Se aplica el clone element a todos los hijos del container y se le añade la propiedad deseada.

Me voló la cabeza el uso de estás herramientas!

<h2 className="TodoCounter">{!loading && `Has completado ${completedTodos} de  ${totalTodos}`} TODOs</h2>

¿Qué es lo que me encanta de estos cursos? que JuanDC nos enseña muchas formas de hacer lo mismo y eso es genial porque tienes varias opciones para solucionar un problema, gracias JuanDC!!!

BEM (Block Element Modifier) es un estandar para crear nombres de clases que sigue la estructura:

block__element--modifier

Por lo que la clase para el texto en TodoCounter podría quedar algo así:

.TodoCounter__text--disabled {
  opacity: 25%;
}

siempre que intentaba poner children en un componente visual studio me importaba Children. Ahora por fin se para que es.

Yo lo había pensado algo así, para que lo mostrara según había Todos o no

<TodoHeader>
				<Title>Todo tasks</Title>
				{totalTodos > 0 && 
						<TodoSearch 
							search={searchValue} 
							setSearch={setSearch} />
				}
			</TodoHeader>

jejjejejej ese error del minuto 12:05 se solucionaba dandole exactamente el index del children


  { React.cloneElement(children[0], {loading}) }
  { React.cloneElement(children[1], {loading}) }

Inquietud

Buenas tardes a todos, tengo una inquietud porque en este curso no se esta dejando Archivos de la clase?, es muy ganador encontrar los archivos para un mejor entendimiento. O en su caso un repositorio de git de cada clase. Espero una repuesta agradezco si es del profesor @JUAN DAVID CASTRO

Una vez use React.Children y React.cloneElement para crear un componente de formulario. En el que pasandole ciertas propiedades terminada pasandole los valores a todos sus hijos (inputs)

Me parece que está súper práctico para agregar soporte de idiomas! Dejo un ejemplo de cómo lo distribuyo en mi header, aunque no pude usar Children 😅 👇

Ejemplo: https://juliantoro91.github.io/TAPP-react-todo-app/

En App.js

    <TodoHeader
        loading={loading}
        languageSupport={languageSupport}
        languageShifter={
          <LanguageShifter
            saveLanguage={saveLanguage} />}
        todoCounter={
          <TodoCounter
            tasksState={tasksState}
          />}
      />

En el TodoCounter del header

<div className="header-todo-counter">{ react.cloneElement(todoCounter, { loading, languageSupport }) }</div>

Repositorio Github: https://github.com/juliantoro91/TAPP-react-todo-app

Me parece muy útil para hacer animación con clases de CSS, simplifica mucho la lógica que hay que implementar .-.

Buenas tardes compañeras y compañeros, les comparto la documentación actualizada para "cloneElement" de React: <https://react.dev/reference/react/cloneElement>
Esta brutal, para aquellos componentes cuyos hijos van a tener si o si X propiedad en mi caso      ```js <ItemsContainer nickName={nickName}> {isTodosExisting && <TodosRenderItems dataTodosFiltered={dataTodosFiltered} todos={todos} setTodos={setTodos} />} {isTodosFinished && <TodosRenderCongratulations nickName={nickName} />} </ItemsContainer> import React from "react"; const ItemsContainer = ({ children, nickName }) => { return (
{React.Children.map(children, child => child && React.cloneElement(child, { nickName }) )}
); }; export { ItemsContainer }; ```
Leyendo la documentación de React, veo que no recomiendan usar el cloneElement(). ![](https://static.platzi.com/media/user_upload/image-a826bbae-ebe4-43fe-b19d-e3bb8581af35.jpg) React recomienda pasar la data con Render prop, Context o crear un Custom Hook.

React.Children y React.cloneElement

.
Para poder pasar propiedades especiales a los componentes hijos de nuestros componentes contenedores cuando hacemos composición, podemos utilizar React.Children y React.cloneElement.
.
Nos damos cuenta que al cargar los TODOs los componentes de TodoCounter y TodoSearch siguen un comportamiento incorrecto, puesto que el primero marca como completado 0 de 0 TODOs y el segundo nos permite buscar TODOs teniendo este que estar deshabilitado hasta que carguen los mismos.
.
Entonces un primer intento de solucionar este problema sería agregar la propiedad Loading e internamente los componentes deberían manejar este estado para cambiar la opacidad de los mismos o deshabilitar el buscador mientras se estén cargando los TODOs.
.

<TodoHeader>
	<TodoCounter ... loading={loading} />
	<TodoSearch ... loading={loading} />
</TodoHeader>
function TodoCounter({ totalTodos, completedTodos, loading }) {
  return (
    <h2
      className={`TodoCounter ${!!loading && "TodoCounter--loading"}`}
    >
      Has completado {completedTodos} de {totalTodos} TODOs
    </h2>
  );
}
function TodoSearch({ searchValue, setSearchValue, loading }) {
  const onSearchValueChange = (event) => {
    console.log(event.target.value);
    setSearchValue(event.target.value);
  };

  return (
    <input
      className="TodoSearch"
      placeholder="Cebolla"
      value={searchValue}
      onChange={onSearchValueChange}
      disabled={loading}
    />
  );
}

.
Lo que se puede notar es que en TodoSearch según el estado de loading deshabilita el input por medio de la propiedad disabled y además se le agrega un pequeño estilo de opacidad para distinguir cuando están cargando los TODOs.
.
Hacemos lo mismo para el TodoCounter cuando estén cargando los TODOs por medio de la clase TodoCounter--loading.
.

.TodoSearch:disabled {
	opacity: 25%;
}
.TodoCounter--loading {
	opacity: 25%;
}

.
Si queremos manejar dinámicamente algunas clases de un elemento de un componente de React podemos hacerlo cambiando las comillas dobles por comillas invertidas dentro de unas llaves.
.
Entonces tendríamos la clase TodoCounter que siempre estará presente por defecto, y además tendremos una pequeña validación que verifica si la propiedad loading es verdadera, si ese fuera el caso se añade por medio del operador lógico && (AND) a la clase TodoCounter--loading.
.
A esto se le llama agregar una clase condicionalmente.
.

<h2 className={`TodoCounter ${!!loading && "TodoCounter--loading"}`}>

.
Como podemos notar no es muy práctico estar pasando la propiedad loading a cada uno de los componentes hijos de TodoHeader e incluso en el futuro podrían ser más.
.
Entonces es aquí donde podemos hacer uso de React.Children y React.cloneElement para evitar ser redundantes a la hora de pasar propiedades comunes a los componentes hijos de un componente contenedor.
.
Primero añadimos la propiedad loading al componente contenedor TodoHeader y lo quitamos de las propiedades de los componentes hijos. Pero además necesitamos comentar por un momento el componente TodoSearch por fines educativos y más adelante lo vamos a descomentar cuando implementemos la funcionalidad completa.
.

<TodoHeader loading={loading} >
	<TodoCounter
		totalTodos={totalTodos}
		completedTodos={completedTodos}
	/>
	{/* <TodoSearch
		searchValue={searchValue}
		setSearchValue={setSearchValue}
	/> */}
</TodoHeader>

.
Internamente en TodoHeader utilizamos React.cloneElement que lo que hace es clonar o crear un elemento a partir de otro, pasándole como argumento a los hijos que tengamos del componente contenedor, en este caso sería la propiedad children.
.
Además podemos enviar como segundo argumento un objeto con las propiedades que queramos que tenga nuestro clon, en este caso debemos enviar la propiedad loading.
.

function TodoHeader({ children, loading }) {
  return (
    <header>
      {
        React.cloneElement(children, { loading })
      }
    </header>
  );
}

.
Hecho esto la aplicación funcionará correctamente pero solo si tenemos un componente hijo.
.

<TodoHeader loading={loading} >
	<TodoCounter
		totalTodos={totalTodos}
		completedTodos={completedTodos}
	/>
	<TodoSearch
		searchValue={searchValue}
		setSearchValue={setSearchValue}
	/>
</TodoHeader>

.
Si descomentamos el componente TodoSearch se rompe la aplicación, puesto que tendríamos más de un componente hijo y por defecto React.cloneElement puede manejar solo un componente hijo dentro de children.
.
Para ello utilizaremos React.Children el cual nos ayudará a utilizar React.cloneElement en el caso que el componente contenedor tenga a más de un componente hijo.
.

function TodoHeader({ children, loading }) {
  return (
    <header>
      {
        React.Children
          .toArray(children)
          .map(child => React.cloneElement(child, { loading }))
      }
    </header>
  );
}

.
Entonces lo que se hizo fue utilizar React.Children que junto a la función toArray(children) lo que hace es tomar todos los componentes o elementos que están en la propiedad children en un array que podemos manipular.
.
Esta implementación nos permite tener a TodoCounter y TodoSearch dentro de un array, e incluso no importa si el componente contenedor no tiene componentes hijos puesto que nos daría un array vacío.
.
Finalmente, ya que tenemos el array podemos iterar el mismo por cada uno de sus elementos por medio de map y llamar a React.cloneElement y clonar cada uno de esos componentes hijos.
.
Gracias a esta implementación los componentes hijos recibirán automáticamente la propiedad loading y la van a manejar internamente como se lo requiera.
.
También hay otras aplicaciones de React.Children como ser:
.

  • React.Children.map(): Similar al método map de arrays, permite mapear sobre los hijos y aplicar una función a cada uno.
React.Children.map(children, child => {
  // Operaciones con cada hijo
});
  • React.Children.forEach(): Similar a map, pero no devuelve un array.
React.Children.forEach(children, child => {
  // Operaciones con cada hijo
});
  • React.Children.count(): Devuelve el número total de hijos.
const numberOfChildren = React.Children.count(children);
  • React.Children.only(): Asegura que solo hay un hijo y devuelve ese hijo. Si hay más de uno o ninguno, arrojará un error.
const onlyChild = React.Children.only(children);

.
Además, un ejemplo común de la aplicación de React.cloneElement podría ser el siguiente.
.

function OriginalComponent(props) {
  return (
    <div style={{ color: props.color }}>
      {props.children}
    </div>
  );
}
function App() {
  const originalElement = <OriginalComponent color="blue">Hello, World!</OriginalComponent>;

  const clonedElement = React.cloneElement(originalElement, { color: 'red' });

  return (
    <div>
      {originalElement}
      {clonedElement}
    </div>
  );
}

.
En este ejemplo, OriginalComponent es un componente simple que toma un color y muestra ese color en su estilo. Luego, en el componente App, se crea un elemento original con el color azul y el texto “Hello, World!”. Luego, se clona ese elemento utilizando React.cloneElement y se cambia el color a rojo. Ambos elementos se renderizan en el componente App, y verás dos bloques de texto con diferentes colores.

Código de la App de la clase en TypeScript

import { FC } from 'react'

import { TodoButton } from "../TodoButton"
import { TodoCounter } from "../TodoCounter"
import { TodoItem } from "../TodoItem"
import { TodoList } from "../TodoList"
import { TodoSearch } from "../TodoSearch"
import { TodosError } from "../TodoError"
import { EmptyTodos } from "../EmptyTodo"
import { TodosLoading } from "../TodoLoading"
import { Modal } from "../Modal"
import { TodoForm } from "../TodoForm"
import { TodoHeader } from "../TodoHeader"
import { useTodos } from './useTodos'

const App: FC = () => {

  const {
    loading,
    error,
    searchedTodos,
    addTodo,
    completeTodo,
    deleteTodo,
    openModal,
    setOpenModal,
    completedTodos,
    totalTodos,
    searchValue,
    setSearchValue,
  } = useTodos();

  return (
    <>
      <TodoHeader
        loading={loading}
      >
        <TodoCounter
          completedTodos={completedTodos}
          totalTodos={totalTodos}
          // loading={loading}
        />
        <TodoSearch
          searchValue={searchValue}
          setSearchValue={setSearchValue}
          // loading={loading}
        />
      </TodoHeader>
      <TodoList
        loading={loading}
        error={error}
        onLoading={()=>(
          <>
            <TodosLoading />
            <TodosLoading />
            <TodosLoading />
          </>
        )}
        onError={()=>(
          <TodosError />
        )}
        onEmpty={()=><EmptyTodos />}
        searchedTodos={searchedTodos}
        searchText={searchValue}
        totalTodos={totalTodos}
        onEmptySearchResults={
          (searchText)=>(<p>No hay resultado para {searchText}</p>)
        }
        // render={(todo) => (
        //   <TodoItem
        //     key={todo.text}
        //     text={todo.text}
        //     completed={todo.completed}
        //     onComplete={() => { completeTodo(todo.text) }}
        //     onDelete={() => { deleteTodo(todo.text) }}
        //   />
        // )}
      >
        {
          (todo) => (
            <TodoItem
              key={todo.text}
              text={todo.text}
              completed={todo.completed}
              onComplete={() => { completeTodo(todo.text) }}
              onDelete={() => { deleteTodo(todo.text) }}
            />
          )
        }
      </TodoList>
      <TodoButton setOpenModal={setOpenModal} />

      {
        openModal && (
          <Modal>
            <TodoForm
              addTodo={addTodo}
              setOpenModal={setOpenModal}
            />
          </Modal>
        )
      }
    </>
  )
}



export default App

import { FC, ReactNode, Children, cloneElement, ReactElement } from "react";

interface Props {
    loading:boolean,
    children: ReactNode
}

const TodoHeader:FC<Props> = ({
    loading,
    children
}) => {

    return (
        <header>
            { Children.toArray(children).map((child)=>cloneElement(child as ReactElement, { loading } )) }
        </header>
    )
}

export { TodoHeader }
import { FC, Dispatch, SetStateAction } from "react";
import './TodoSearch.css';

interface Props {
    loading?:boolean,
    searchValue: string,
    setSearchValue: Dispatch<SetStateAction<string>>
}

const TodoSearch:FC<Props> = ({
    loading,
    searchValue,
    setSearchValue
}) => {

    return (
       <input 
        type="text" 
        className="TodoSearch" 
        placeholder="Ingresar nombre de tarea buscada" 
        value={searchValue}
        onChange={(event)=>{
            setSearchValue(event.target.value)
        }}
        disabled={loading}
    />
    )
}

export { TodoSearch }
.TodoSearch {
    background: #F9FBFC;
    border-radius: 2px;
    border: 2px solid #202329;
    margin: 0 24px;
    height: 64px;
    width: calc(100% - 62px);
    font-size: 24px;
    text-align: center;
    font-family: 'Montserrat';
    font-weight: 400;
    color: #1E1E1F;
    box-shadow: 0px 5px 50px rgba(32, 35, 41, 0.25);
}

.TodoSearch:disabled {
    opacity: 25%;
}

.TodoSearch::placeholder {
    color: #A5A5A5;
    font-family: 'Montserrat';
    font-weight: 400;
}

.TodoSearch:focus {
    outline-color: #61DAFA;
}

import { FC } from "react";
import './TodoCounter.css'

interface Props {
    loading?:boolean,
    completedTodos: number,
    totalTodos: number,
}

const TodoCounter:FC<Props> = ({
    loading,
    completedTodos,
    totalTodos
}) => {
 
    return (
        <h1 className={`TodoCounter ${loading && "TodoCounter--loading"}`}>Has completado <span>{completedTodos}</span>  de <span>{totalTodos}</span></h1>
    )
}

export { TodoCounter }
.TodoCounter {
    font-size: 24px;
    text-align: center;
    margin: 0;
    padding: 48px;
    font-weight: normal;
}

.TodoCounter--loading{
    opacity: 25%;
}

.TodoCounter span {
    font-weight: bold;
}

Resumen

  • React.cloneElement sirve para clonar un componente y agregarle propiedades.
  • React.Children.toArray al enviarle la prop children siempre nos devuelve un arreglo con nuestros elementos o componentes de children.

Pasar propiedades de un padre a todos sus hijos
.
La logica es que pasas la prop solo al padre y luego a todos los hijos, para eso usas React.cloneElement y React.Children
.

  • cloneElement recibe como primer parametro un elemento de react y lo clona, de forma que puedes a ese clon agregarle las propiedades
  • Children es una utilidad que nos permitirá trabajar con los hijos del componente. En este caso necesitamos retornar cada hijo como un elemento de react para que cloneElement le pueda agregar la prop que queremos
    .
{React.Children.map( children, child => React.cloneElement(child, {loading}))}

.
Esa linea de codigo…
.

  • Itera los hijos con el metodo map de React.Children
  • Funciona un poquito diferente, porque el primer argumento es el conjunto de hijos a iterar (podrias no hacerlo en todos los hijos, puedes antes de pasar por esta logica, filtrar los hijos a los que quieras aplicar esto)
  • El segundo argumento es una funcion que retorna cada hijo como un elemento react para que React.cloneElement reciba el hijo, lo clone, y le agregue la prop