No tienes acceso a esta clase

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

Poniendo en práctica las render props

9/19
Recursos

Aportes 28

Preguntas 22

Ordenar por:

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

Codigo de TodoList en App/Index.js

      <TodoList
        error={error}
        loading={loading}
        searchedTodos={searchedTodos}
        onError={() => <TodosError />}
        onLoading={() => <TodosLoading />}
        onEmptyTodos={() => <EmptyTodos />}
        render={todo => (
          <TodoItem
            key={todo.text}
            text={todo.text}
            completed={todo.completed}
            onComplete={() => completeTodo(todo.text)}
            onDelete={() => deleteTodo(todo.text)}
          />
        )}
      />

codigo de TodoList en TodoList/index.js

function TodoList(props) {
  return (
    <section className="TodoList-container">
      {props.error && props.onError()}
      {props.loading && props.onLoading()}
      {(!props.loading && !props.searchedTodos.length) && props.onEmptyTodos()}

      <ul>
        {props.searchedTodos.map(props.render)}
      </ul>
    </section>
  );
}

La explicacion es muy clara y se entiende todo, pero Siento que esta clase esta mal enfocada, todo lo que era composicion de componentes se perdio en esta clase, se pasaron props al componente sumandole las render props, quedo un componente mas complejo, esto es realmente util cuando el componente que recibe las render props puede manejar su propio estado

Creo q perdimos las composición de componentes volvemos a útilizar props😅😅😅😅😅😅

En mi humilde opinión esto es añadirle otro nivel de complejidad a la aplicación y/o al componente (además que utilizar este recurso puede sacrificar un poco le legibilidad del código) por lo que hay que saber muy bien cuándo usarlo…
.
No me parece óptimo que todo lo que se vaya a renderizar adentro de un componente se pase por render props. En dónde sí veo que puede ser necesario usarlo es cuando el children del componente **no nos es suficiente ** y por lo tanto necesitemos de hacer render de un componente en un lugar aparte de donde estarán los children… Espero explicarme bien 😅😁

Desde mi punto de vista, dentro de TodoList el siguiente código no es válido (o está mal usado):

<ul>
          {props.children}
</ul>

.
¿Por qué? Porque no estamos definiendo a TodoList como una etiqueta que contenga hijos dentro de sí. Por esta razón, dentro de <ul> no debemos emplear props.children, sino el resultado de la ejecución de props.render, que sí son los elementos de nuestra lista.
.
Sería así:

{<ul> { props.searchedTodos.map( props.render ) } </ul>}

TodoList

function TodoList(props) {
    return (
        <section className="TodoList-container">
            {props.error && props.onError()}
            {props.loading && props.onLoading()}

            {(!props.loading && !props.searchedTodos.length) && props.onEmptyTodos()}

            {props.searchedTodos.map(props.render)}

            <ul>
                {props.children}
            </ul>
        </section>
    )
}

index.js

<TodoList
        error={error}
        loading={loading}
        searchedTodos={searchedTodos}
        onError={() => <TodosError />}
        onLoading={() => <TodosLoading />}
        onEmptyTodos={() => <EmptyTodos />}
        render={todo => (
          <TodoItem
            key={todo.text}
            text={todo.text}
            completed={todo.completed}
            onComplete={() => completeTodo(todo.text)} //Marcamos el todo como completado
            onDelete={() => deleteTodo(todo.text)} //Eliminamos en todo
            />
        )}
      />

Recomiendo leer la documentación, los ejemplos son bastante buenos aquí:

https://es.reactjs.org/docs/render-props.html

En este caso es mejor el operador ternario ya que si ocurre un error este no va a mostrar los todos sino que simplemente renderizara el componente error

<section className="container">
  <ul className="todo-list">
    {error
      ? onError()
      : loading
      ? onLoading()
      : !searchedTodos.length
      ? onEmptyTodos()
      : searchedTodos.map(render)}
  </ul>
</section>

El simbolito de pregunta se llama signo de interrogación.

Para quienes no entendieron el shortcut del profe en la linea 12 del componente TodoList, lo que esta haciendo es una funcionalidad propia del método map(). Este tiene la capacidad de mandar a llamar una función (Esto se conoce como un callback) por cada elemento que se encuentre en un array, props.render trae consigo una render function. Al liberarse nos trae un componente TodoItem provando que haga esto por cada elemento que nos viende dentro searchedTodos.
Por esto es que se recomienda saber un poquito más JavaScript antes de pasarse directamente a cualquier framework. Les dejo la documentación de MDN que nos habla al respecto sobre esta característica.

Me gusto mucho esta clase, ahora tienen mucho sentido las cosas

Código de la clase en TypeScript
(Difiere un poco en organizacion con la del profe, pero lo importante es el tipado)

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>
        <TodoCounter
          completedTodos={completedTodos}
          totalTodos={totalTodos}
        />
        <TodoSearch
          searchValue={searchValue}
          setSearchValue={setSearchValue}
        />
      </TodoHeader>
      <TodoList
        loading={loading}
        error={error}
        onLoading={()=>(
          <>
            <TodosLoading />
            <TodosLoading />
            <TodosLoading />
          </>
        )}
        onError={()=>(
          <TodosError />
        )}
        onEmpty={()=><EmptyTodos />}
        searchedTodos={searchedTodos}
        render={(todo) => (
          <TodoItem
            key={todo.text}
            text={todo.text}
            completed={todo.completed}
            onComplete={() => { completeTodo(todo.text) }}
            onDelete={() => { deleteTodo(todo.text) }}
          />
        )}
      />
      <TodoButton setOpenModal={setOpenModal} />

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



export default App

import { FC, ReactNode } from "react";
import { ITodo } from "../App/useTodos";
import './TodoList.css'

interface Props {
    loading: boolean,
    error: boolean,
    searchedTodos: ITodo[],
    onError: ()=>ReactNode,
    onLoading: ()=>ReactNode,
    onEmpty:()=>ReactNode,
    render: (value:ITodo)=>ReactNode,
}

const TodoList:FC<Props> = ({
    loading,
    error,
    searchedTodos,
    onLoading,
    onEmpty,
    onError,
    render
}) => {

    return (
        <ul className="TodoList">
            {loading && onLoading()}
            {error && onError()}
            {(!loading && searchedTodos.length === 0) && onEmpty()}
            { searchedTodos.map(render) }
        </ul>
    )
}

export { TodoList }

Creo que el map deberia renderizarse dentro el ul por tema de semantica

import '../css/TodoList.css'


function TodoList(props) {
  return (
    <section className="todo-list-container">
      {props.error && props.onError()}
      {props.loading && props.onLoading()}

      {(!props.loading && !props.todosFiltered.length) && props.onEmptyTodos()}

      <ul>
        {props.todosFiltered.map(props.render)}
      </ul>
    </section>
  );
}


export { TodoList };

f1 + backspace + nombredelarchivo … este es un atajo para encontrar rapidamente los archivos,

Poniendo en práctica las render props

Para esto vamos a reescribir nuestro componente TodoList. Vamos a escribir todas las funcionalidades que tenía este componente usando las render props.
.

Vamos a App.js.
.

Aquí haremos la maquetación usando nuestras render props:

	...
		<TodoList 
	    onError={()=> <TodoError />}
	    onLoading={()=> <TodoLoading />}
	    onEmptyTodos={()=> <EmpyTodos />}
	    render={
	      todo => (
	        <TodoItem
	          key={todo.text}
	          text={todo.text}
	          completed={todo.completed}
	          onComplete={() => completeTodo(todo.text)}
	          onDelete={() => deleteTodo(todo.text)}
	        />
	      )
	    }
	  />

TodoList.js:

/* Aquí añadimos una clase y validamos su hay algo en props.error
si lo hay, renderizamos lo que haya en la función onError() */
function TodoList(props) {
  return (
    <section className='TodoList-container'>
      {props.error && props.onError()}
      <ul>
        {props.children}
      </ul>
    </section>
  )
}

Para que esto sea posible iremos a nuestro TodoList y recibiremos el error, en caso de que lo haya para cumplir con la condicional.
.

Esto mismo haremos con loading y con searchedTodos.

	<TodoList 
    error={error}
    loading={loading}
    searchedTodos={searchedTodos}

    ...
  />

Ahora vamos a seguir maquetando nuestro Todolist.js.

function TodoList(props) {
  return (
    <section className='TodoList-container'>
      {/* haremos lo mismo que hicimos con error con el resto de 
      funciones */}
      {props.error && props.onError()}
      {props.loading && props.onLoading()}
      {(!props.loading && !props.searchedTodos.length) && props.onEmptyTodos()}

      <ul>
        {props.searchedTodos.map(props.render)}
      </ul>    
		</section>
  )
}

En este punto tenemos lo que deseamos, de esta forma es como usamos nuestras render props. ¿Muy fácil verdad? :3 .

en mi opinión creo que dejamos <ul> sin uso he realizado lo siguiente espero no estar mal con temas de lógica y buenas practicas

TodoList

{ props.error && props.onError() }
            { props.loading && props.onLoading() }
            { (!props.loading && !props.searchedTodos.length && props.searchValue.length < 1) && props.onEmptyTodos() }
            { (props.searchedTodos.length <= 0 && props.searchValue.length >= 1) && props.onSearchNoFound() }
            <ul>
                { props.searchedTodos.map(todo => props.render(todo)) }
            </ul>

En mi caso aplique el patrón de diseño de las render props de otra manera. Les dejo el código por si a alguien le parece útil.

function App() {

const [todos, manageTodos] = useLocalStorage()  
const [search, setSearch] = useState('')
const [isOpenModal, setOpenModal] = useState(false)

useEffect( () => {
  manageTodos('search')
}, [])

  const uncompletedTodos = todos? todos.filter((todo) => !todo.isCompleted).length : 0
  const totalTodos = todos.length
  const searchedTodos = todos.filter( todo => todo.description.toLowerCase().includes(search.toLowerCase())  )

  const list = (
    <>
      { searchedTodos.map( todo =>          
                <TodoItem key={todo.id} todos={todos} manageTodos={manageTodos} />    
            )}
    </>
  )

  const handleOpenModal = () => {
    setOpenModal(true)
  }

  return (
      <div className="app-container">
        <Header>
          <Counter uncompletedTodos={uncompletedTodos} totalTodos={totalTodos}/>
          <Search search={search} setSearch={setSearch} />
        </Header>
        <TodoList list={list}/>
        <img alt="icono para añadir todos" className="plus-icon" src={plusIcon} onClick={handleOpenModal}/>
  
        { isOpenModal &&
          <Modal setOpenModal={setOpenModal}>
            <NewTodo setOpenModal={setOpenModal} manageTodos={manageTodos} noOfTodos={todos.length}/>
          </Modal>
        }
      </div>
    
  )

}

El componente App contiene el estado de los Todos (que inicializo con un useEffect), desde App renderizo un componente todoList, que renderiza TodoItem, y a su vez este renderiza un TodoItemUI (con la maquetación de cada ToDo).
Entonces, para evitar el drilling cree una variable llamada list, en donde está la iteración sobre los todos. Esto me permite desde el componente App mandarle a TodoItem las props, y finalmente desde TodoList recibo list como prop y la renderizo.

const TodoList = ( {list} ) => {
    return (
        <ul>
            {list}
        </ul>
    )
}

Me encanto este curso

I think they made it too complicated trying to explain how render props work with old code from another course trying to explain how this design pattern works I think the best thing to do is to start with basic examples and then add the necessary complexity in the different use cases that may be needed or the limitation that this pattern may give us trying to explain the pattern and trying to explain what the code that is being modified to apply the pattern does that makes it not easy to understand
Ok, una pregunta seria. Por que dejamos \
    children\<ul;/> abajo de todas las validaciones ? yo lo removi del componente y no afecta en nada al renderizar.
Me gusta más hacer una desestructuración de las props. Así queda mi código. ```js function TodoList(props) { const { loading, onLoading, error, onError, searchedTodos, onEmptyTodos, render, } = props; return (
    {error && onError()} {loading && onLoading()} {!loading && !searchedTodos.length && onEmptyTodos()} {searchedTodos.map(render)}
); } ```

Poniendo en práctica las render props

.
Se tiene el siguiente código donde vamos a aplicar el patrón de las render props al componente TodoList.
.

<TodoList>
	{error&&<TodosError/>}
	{loading&&<TodosLoading/>}
	{(!loading&&!searchedTodos.length)&&<EmptyTodos/>}
	{searchedTodos.map(todo=>(
		<TodoItem
			key={todo.text}
			text={todo.text}
			completed={todo.completed}
			onComplete={()=>completeTodo(todo.text)}
			onDelete={()=>deleteTodo(todo.text)}/>
	))}
</TodoList>

.
Lo que se hizo fue añadir la propiedades de error, loading y searchTodos para que posteriormente nos ayuden a renderizar ciertos componentes dependiendo de su estado. Estos componentes se los llamarán por medio de render props que en este caso vienen siendo onError, onLoading, onEmptyTodos y render.
.

<TodoList
	error={error}
  loading={loading}
  searchedTodos={searchedTodos}
  onError={() => <TodosError />}
  onLoading={() => <TodosLoading />}
  onEmptyTodos={() => <EmptyTodos />}
  render={todo => (
	  <TodoItem
			key={todo.text}
	    text={todo.text}
	    completed={todo.completed}
	    onComplete={() => completeTodo(todo.text)}
	    onDelete={() => deleteTodo(todo.text)}
		/>
	)}
/>

.
Internamente el componente TodoList se verá de la siguiente manera.
.

function TodoList(props) {
  return (
    <section className="TodoList-container">
      {props.error && props.onError()}
      {props.loading && props.onLoading()}
      {(!props.loading && !props.searchedTodos.length) && props.onEmptyTodos()}
      {<ul>props.searchedTodos.map(props.render)</ul>}
    </section>
  );
}

.
Desde props vamos observando el estado del componente y renderizamos el correspondiente componente por medio de render props. Por ejemplo para props.error renderizamos lo que está en la función de la render prop onError, y así sucesivamente.

tip si el archivo es .jsx en lugar de js, podemos usar un motor de completado para react, dependiendo del motor este pede no activarse si el archivo es .js

Muy buena clase, pude entender esto de las Render props 😁

Estoy perdidooo

No quise hacerlo igual que Juan por una sencilla razón, y es que dejaríamos atrás la composición de componentes, yo propuse esta solución

<TodoList
          error={error}
          loading={loading}
          searchedTodos={searchedTodos}
          onError={() => <TodosError/>}
          onLoading={() => (
            new Array(5)
              .fill(1)
              .map((a, i) => <TodosLoading key={i}/>)
          )}
          onEmptyTodos={() => <EmptyTodos/>}
        >
            {searchedTodos.map(todo =>(
                <TodoItem 
                    key={todo.text} 
                    text={todo.text}
                    completed={todo.completed}
                    onComplete={() => toggleTodo(todo.text)}
                    onDelete={() => deleteTodo(todo.text)}
            />))}
        </TodoList>

El cambio es que no cree la propiedad render, me parece en mi opinión que es más legible, espero sus opiniones