Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Reg铆strate

Comienza en:

3D
4H
9M
14S
Curso de Introducci贸n a React.js

Curso de Introducci贸n a React.js

Juan David Castro Gallego

Juan David Castro Gallego

Reto: loading skeletons

20/23

Lectura

Muchas aplicaciones se quedan en blanco mientras cargan su contenido. Otras muestran alg煤n mensaje de 鈥渃argando鈥 y luego s铆 renderizan todo el contenido de la aplicaci贸n.

...

Reg铆strate o inicia sesi贸n para leer el resto del contenido.

Aportes 47

Preguntas 5

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

Loading and Empty state screens:
.

.
Error State Screen:
.

Opte por los cl谩sicos punticos de carga

Hola 馃榾

Para que nos muestre varios componentes TodosLoading podemos hacer esto

{loading && new Array(5).fill(1).map((a, i) => <TodosLoading key={i} />)}

resultado

{loading && 
            new Array(4).fill().map((item, index)=>(
              <LoadingTodo key={index} />
))}
const LoadingTodo = () => {
    return (
    <li className="TodoItem-loading">
        <div className="LoaderBalls">
            <span className="LoaderBalls__item"></span>
            <span className="LoaderBalls__item"></span>
            <span className="LoaderBalls__item"></span>
        </div>
    </li>
    )
}
  .TodoItem-loading {
      background-color: #FAFAFA;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      margin-top: 24px;
      box-shadow: 0px 5px 50px rgba(32, 35, 41, 0.15);
      color: #333;
      min-height: 4.5rem;
  }
  
  .LoaderBalls {
    width: 90px;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .LoaderBalls__item {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #bec3c5;
  }

  .LoaderBalls__item:nth-child(2) {
    animation: opacitychange 1s ease-in-out infinite;
  }

  .LoaderBalls__item:nth-child(3) {
    animation: opacitychange 1s ease-in-out 0.33s infinite;
  }

  .LoaderBalls__item:nth-child(1) {
    animation: opacitychange 1s ease-in-out 0.66s infinite
  }
  
  @keyframes opacitychange {
    0%, 100% {
      opacity: 0;
  }
  60% {
      opacity: 1;
  }
  }

yo solo lo puse una animaci贸n de carga

Loading state:

Ideal state:

Error state

Empty state:

All completed state:

TodoForm

Loading code:

//TodoLoading.js
import React from "react"
import ContentLoader from "react-content-loader"
import './TodoLoad.css'

const TodoLoad = (props) => (
  <ContentLoader 
    speed={2}
    width={280}
    height={40}
    viewBox="0 0 280 40"
    backgroundColor="#3d087b"
    foregroundColor="#f43bea"
    {...props}
  >
    <rect x="48" y="8" rx="3" ry="3" width="88" height="6" /> 
    <rect x="48" y="26" rx="3" ry="3" width="52" height="6" /> 
    <circle cx="20" cy="20" r="20" />
  </ContentLoader>
)

export { TodoLoad }

//AppUI.js:
...
{loading && (
  <>
    <TodoLoad/>
    <TodoLoad/>
    <TodoLoad/>
  </>
)}

Error code:

//TodoError.js
import React from "react";
import './TodoError.css'

const TodoError = () => {
  return (
    <div className="TodoError">
      <i className="fa fa-grav" aria-hidden="true"></i>
      <p>Vaya, parece que encontramos un problema, danos un poco de tiempo y vuelve a intenar en unos par de minutos...</p>
    </div>
  )
}

export { TodoError }

AppUI.js
...
{error && (
  <Modal>
    <TodoError />
  </Modal>
)}

Empty code:

//EmptyTodos.js
import React from "react";
import './EmptyTodos.css'

const EmptyTodos = () => {
  return (
    <div className="EmptyTodos">
      <div className="EmptyTodos-icons">
        <i className="fa fa-sun-o" aria-hidden="true"></i>
        <i className="fa fa-smile-o" aria-hidden="true"></i>
      </div>
      <p>隆Crea tu primer TODO!</p>
    </div>
  )
}

export { EmptyTodos }

//AppUI.js
...
{(!loading && !searchedTodos.length) && (
  <EmptyTodos />
)} 

All completed state:

//AllCompleted.js
import React from 'react'
import './AllCompleted.css'

const AllCompleted = () => {
  return (
    <div className="AllCompleted">
      <i className="fa fa-coffee" aria-hidden="true"></i>
      <p>Has completado todos tus TODOs!</p>
      <p>Es hora de un merecido descanzo</p>
    </div>
  )
}

export { AllCompleted }

//AppUI.js
...
{(allCompleted && !!searchedTodos.length) && (
  <Modal>
    <AllCompleted />
  </Modal>
)}

La variable AllCompleted es un estado que se actualiza al momento de cargar los todos en el custom hook de useLocalStorage, y tambi茅n se actualiza cada vez que los todos sufren un cambio.
Este es mi custom hook con las mejoras de la variable allCompleted y con el useEffect modificado para que solo se ejecute cuando se carga inicialmente

//useLocalStorage.js
import React from "react";

function useLocalStorage(itemName, initialValue) {
  const [error, setError] = React.useState(false)
  const [loading, setLoading] = React.useState(true)
  const [item, setItem] = React.useState(initialValue)
  const [allCompleted, setAllCompleted] = React.useState(false)

  const checkAllCompleted = (todos) => {
    if (Array.isArray(todos)) {
      const completedTodos = todos.filter(todo => !!todo.completed).length
      const totalTodos = todos.length  
      if (totalTodos > 0 && (completedTodos === totalTodos)) {
        setAllCompleted(true)
      } else {
        setAllCompleted(false)
      }
    }
  }

  React.useEffect(() => {
    setTimeout(() => {
      
      try {
        const localStorageItem = localStorage.getItem(itemName)
        let parsedItems;
        
        if (!localStorageItem) {
          localStorage.setItem(itemName, JSON.stringify(initialValue))
          parsedItems = initialValue
        } else {
          parsedItems = JSON.parse(localStorageItem)
        }

        setItem(parsedItems)
        checkAllCompleted(parsedItems)
        setLoading(false)

      } catch (error) {
        setError(error)
      }
    }, 1500);
  },[initialValue, itemName])

  const saveItem = (newItem) => {
    try {
      localStorage.setItem(itemName, JSON.stringify(newItem))
      setItem(newItem)
      checkAllCompleted(newItem)
    } catch (error) {
      setError(error)
    }
  }

  return {
    item,
    saveItem,
    loading,
    error,
    allCompleted
  }
}

export { useLocalStorage }

Otras mejoras que hice fueron la implementaci贸n del hook useRef para que cuando la app cargue el foco est茅 en el input de b煤squeda. Lo mismo cuando se carga el componente de TodoForm para que se ubique autom谩ticamente en el textarea

TodoSearch

//TodoSearch/index.js
...
  const refInput = React.useRef()
  React.useEffect(() => {
    refInput.current.focus()
  },[])

  return (
    <section className="TodoSearch">
      <input 
        ref={refInput}
        value={searchValue}
        onChange={onSearchValueChange}
        placeholder="Cebolla" 
      />
      <i className="fa fa-search" aria-hidden="true"></i>
    </section>
  )
...

TodoForm

En TodoForm tambi茅n puse una validaci贸n para un m谩ximo de 40 caracteres por Todo

  const textareaRef = React.useRef()

  React.useEffect(() => {
    textareaRef.current.focus()
  }, [])
  
  const onChange = (event) => {
    setNewTodoValue((prevState) => {
      let eventValue = ''
      if (event.target.value.length > 40) {
        eventValue = prevState
      } else {
        eventValue = event.target.value
      }
      return eventValue
    })
  }
  const onCancel = () => {
    setOpenModal(false)
  }
  const onSubmit = (event) => {
    event.preventDefault()
    addTodo(newTodoValue)
    setOpenModal(false)
  }
  return (
    <form 
      className="TodoForm"
      onSubmit={onSubmit}
    >
      <label>Escribe tu nuevo <span>TODO</span></label>
      <textarea 
        ref={textareaRef}
        value={newTodoValue}
        onChange={onChange}
        placeholder="Cortar la cebolla para el almuerzo"
      />
      <p>{newTodoValue.length}/40</p>
       ...
      </form>
  )
...

Transparent Scroll

En el TodoList puse una validaci贸n para que el alto no supere cierto m谩ximo y as铆 poder hacer scroll sin que el Bot贸n se desplace demasiado y salga del viewport. Y ya que iba a aparecer un scroll le puse estilos para que luzca m谩s est茅tico:

.TodoList {
  position: relative;
  max-height: 450px;
  margin: 15px 0;
  padding: 5px 5px;
  background: var(--background05);
  border-radius: 10px;
  overflow-y: scroll;
}

.TodoList-ul {
  display: flex;
  flex-direction: column;
  width: 280px;
}

/* * ============================== */
/* * ========== WEBKIT ============ */
/* * ============================== */

::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 25px;
}

::-webkit-scrollbar {
	width: 10px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 25px;
}

::-webkit-scrollbar:hover {
	background: rgba(255, 255, 255, 0.2);
  box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.2);
}

::-webkit-scrollbar:hover:active {
  background: rgba(255, 255, 255, 0.25);
}

Hola devs !! Puede que haya un error en el Modal en el aspecto de s铆 al momento de agregar todos, nosotros no escribamos la tarea y le demos al bot贸n de agregar , se agrega una tarea vac铆a, para evitar esto coloque el atributo required en textarea y as铆 no se va una tarea vac铆a , haciendo que el usuario no ignore llenar este campo!!

Qu茅 tal?

Pantalla de carga

La hice con la herramienta que nos dejaron en la lectura, bastante sencillo hacerlo con esa herramienta la verdad

Usuario nuevo / o sin TO-DOs

Por si hay un error ya saben donde ir

Vamos avanzando, est谩 muy bueno el proyecto para llevar varios conceptos

Aporte en acci贸n:


.
.

Pantalla de empty todos


.
.

Pantalla de error todos


.
.

Pantalla de loading skeletons

Loading State:

EmptyToDos:

TheProjectWithTodos:

Usando React Content Loader, aunque debo decir que no me siento c贸modo usando medidas absolutas. En un futuro ver茅 c贸mo hacerlo con medidas relativas.

import React from 'react'
import ContentLoader from 'react-content-loader'

export function TodosLoading(props) {
    return (
        <ContentLoader
            speed={2}
            width={272}
            height={102}
            viewBox="0 0 272 104"
            backgroundColor="#d9d9d9"
            foregroundColor="#ecebeb"
            {...props}
        >
            {/* L铆nea Izquierda */}
            <rect x="0" y="16" rx="0" ry="0" width="2" height="102" />
            {/* L铆nea derecha */}
            <rect x="270" y="16" rx="0" ry="0" width="2" height="102" />
            {/* L铆nea superior */}
            <rect x="0" y="16" rx="0" ry="0" width="272" height="2" />
            {/* L铆nea inferior */}
            <rect x="0" y="102" rx="0" ry="0" width="272" height="2" />
            {/* Check */}
            <rect x="24" y="48" rx="0" ry="0" width="24" height="24" />
            {/* Texto */}
            <rect x="72" y="28" rx="0" ry="0" width="176" height="24" />
            {/* Texto */}
            <rect x="72" y="68" rx="0" ry="0" width="176" height="24" />
        </ContentLoader>
    )
}

Tiene una peque帽a animaci贸n que no supe como cargarla

decid铆 crear algo mas expresivo
EmptyTodos

TodosErrot

TodoLoading

Mi Todo List

La verdad no hice muchos cambios al css :C


Aca pongo como va quedando al mio, todav铆a estoy definiendo que colores y cosas adicionales deber铆a tener el proyecto. 馃槂

Bueno amigos, yo no me quise quebrar la cabeza demasiado, porque apenas estoy aprendiendo el concepto, sin embargo, use Loading Sleletons sin mayor problema, solamente genere un Hook personalizado para que se mostrara la animaci贸n de la carga, y en logar de la <p> que se us贸 en clase, coloque el hook en el loading.
Ac谩 les comparto mi evidencia:

/* Declaraci贸n del Hook para el Loading Skeleton */
import { Carga } from '../Carga';
import { Vacio } from '../Vacio';
import { Error } from '../Error';
{/* Sustituyendo el <p> por el Loading Skeleton */}
{loading && <MyLoader />}
import React from "react"
import ContentLoader from "react-content-loader"

function Carga({props}){
    
    return (
        <ContentLoader 
            rtl
            speed={1}
            width={500}
            height={160}
            viewBox="0 0 500 160"
            backgroundColor="#e8e8e8"
            foregroundColor="#d2ff3a"
            {...props}
        >
            <rect x="8" y="16" rx="4" ry="4" width="15" height="25" />
            <rect x="30" y="55" rx="4" ry="4" width="457" height="23" />
            <rect x="8" y="94" rx="4" ry="4" width="15" height="25" />
        </ContentLoader>
    );

}

export {Carga};

隆OJO!: Necesitan usar npm install react-content-loader para que les ejecute correctamente.
Despues de leer la solucion si me atrevi a hacer lo mismo.

import React from "react"
import img from "../images/registro.png";
import './Vacio.css';

function Vacio(){
    
    return (
        <figure className="vacio">
            <figcaption>隆Crea tu primer TODO!</figcaption>
            <img className="img" src={img} alt="隆Crea tu primer TODO!" />
        </figure>
    );

}

export {Vacio};
import React from "react"
import img from "../images/error.png";
import './Error.css';

function Error(){
    
    return (
        <figure className="error">
            <figcaption>Desesp茅rate, hubo un error...</figcaption>
            <img className="img" src={img} alt="Desesp茅rate, hubo un error..." />
        </figure>
    );

}

export {Error};

Espero y les sirva.

Estado de carga:

Ya todo cargado:

Hola! As铆 va quedando mi app:

  • Loading

    (los puntitos est谩n animados)
  • Empty
  • Error

Aqu铆 mi propuesta de LOADING鈥

Aqu铆 dejo mis pantallas, no son la gran cosa pero es trabajo honesto 馃槂

Esta pantalla es una animaci贸n van apareciendo y desapareciendo, pero no supe como poner un gif aqu铆

El resto si son im谩genes est谩ticas

Este ha sido mi resultado

repositorio 馃憠 https://github.com/Misael-GC/intreoduccion-react

Aqui mi reto:

Loading Page:

Error Page:

Empty Page:

Utilice algo diferente, aqu铆 por si quieren aprender a c贸mo hacerlo: https://css-tricks.com/single-element-loaders-the-dots/

Carga

Sin TODO鈥檚

TODOS!

Despues de hacer el reto de esta clase me encontre con el peque帽o problema de que cuando buscamos los TODOs el componente de <EmptyTodos /> se activa porque la expresion si es valida:

  • EmptyTodo activo:

Entonces para arreglar esto cambie el evaluador de los todos para que no use searchedTodos que va a cambiar con el estado sino totalTodos, asi cuando hayan TODOs y estemos buscando no va saltar el componente de empty.

  • EmptyTodo no activo:

  • Codigo:

/* Tambien puse condicionales extras para que no se interfieran entre si las expresiones*/
				{loading && <TodosLoading />}
				{!loading && error && <TodosError />}
				{!error && !loading && totalTodos < 1 && <EmptyTodos />}

Ustedes tambien encontraron este problema probando sus componentes?
Y si es asi como lo solucionaron?

En este enlace encuentran varios estilos de loaders solo con CSS y HTML. Un soluci贸n r谩pida, f谩cil y que no requiere instalaciones.

<TodoContainer>
          {/* {value.error && <p>Desesperate, hubo un error...</p>}
          {value.loading && <p>Estamos cargando, no desesperes...</p>}
          {(!value.loading && !value.filtradoTodos.length) && <p>Crea tu primer TODO</p>} */}
          {/* {value.filtradoTodos.map(todo => (
          <TodoItem 
          key={todo.id} 
          tarea={todo.tarea} 
          fecha={todo.fecha} 
          todoEstado={todo.estado}
          completeTodo={()=> completeTodo(todo.tarea)}
          deleteTodo={()=> deleteTodo(todo.tarea)}
          editTodo={()=> editTodo(todo.tarea)}
          />))} */}
          {(value.loading)? <Loader />
            :
            value.todos.map(todo => <TodoItem 
              key={todo.id} 
              todo={todo}
              tarea={todo.tarea} 
              fecha={todo.fecha} 
              todoEstado={todo.estado}
              completeTodo={value.completeTodo}
              deleteTodo={value.deleteTodo}
            />)
          }
        </TodoContainer>

Reto:
.

Para poner tres barras solo copie 3 veces la etiqueta!

     <TodoList>
            {error && <TodosError error={error} />}
            {loading && <TodosLoading />}
            {loading && <TodosLoading />}
            {loading && <TodosLoading />}
            {(!loading && !searchedTodos.length) && <EmptyTodos />}
</TodoList>

Loading Screen:

Aca estan mis estilos:

Hola a todos este es mi aporte y lo hice de 2 maneras

  1. por eseleton desde Create React Content Loader pero no fue de mi preferencia

  2. lo hice de forma personalizada que es algo muy secillo aqui se los dejo:

Eskeleton personalizado:

CODIGO REACT

import React from "react";
//import ContentLoader from "react-content-loader";
import "bootstrap";
import "../styles/TodosLoading.css"

function TodosLoading(props) {
  return (
    <div className="container">
      <div className="background">
        <ul className="listado">
          <li className="item">Loading...</li>
          <li className="item">Loading...</li>
          <li className="item">Loading...</li>
          <li className="item">Loading...</li>
          <li className="item">Loading...</li>
        </ul>
      </div>
    </div>
  );
}

export { TodosLoading };

CODIGO CSS

*{
   
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    color: grey;
 }
 
 .container{
 padding: 10px;
 margin: 1%;
 border: 0px solid grey;
 text-align: left;
    
 }
 
 .listado{
 
  margin: 0;
  padding: 0 0 36px 0;
  list-style: none;
 
 }
 
 
 .item{
  border: 0px solid grey;
  padding: 20px;
  margin-top: 24px;
  box-shadow: 0px 10px 60px  rgba(32, 35, 41, 0.15);
  border-radius: 5px;
     
 }
 .background {
     animation-duration: 5s;
     animation-fill-mode: forwards;
     animation-iteration-count: infinite;
     animation-name: preloadAnimation;
     animation-timing-function: linear;
     background: #f6f7f8;
     background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);
     display: absolute;
         min-height: 150px;
 }
 
 @keyframes preloadAnimation{
     0%{
         background-position: -769px 0
     }
     100%{
         background-position: 769px 0
     }
 }

load{
  width: 100%;
  justify-content: center;
  display: flex;
 align-content: center;
  height: 53vh!important;

}
.load span{
  margin-left: 10px;
  color:#8bc6ec;
  font-size: 10px;
 
}
#spn1{
   animation-name: example;
  animation-duration: 4s ;
  animation-iteration-count: infinite
}

#spn2{
  animation-name: example;
 animation-duration: 4s ;
 animation-iteration-count: infinite;
 animation-delay: 1s;
}

#spn3{
  animation-name: example;
 animation-duration: 4s ;
 animation-delay: 2s;
 animation-iteration-count: infinite
}

#spn4{
  animation-name: example;
 animation-duration: 4s ;
 animation-delay: 3s;
 animation-iteration-count: infinite
}
@keyframes example {
    0%   {color:rgb(255, 255, 255);  font-size: 10px;}
    50% {color:rgb(25, 153, 192); font-size: 20px}; 
    100% {color:rgb(255, 255, 255); font-size: 20px}; 
  
}

Cre茅 una app muy simple en React desde 0 (la primera que hago) y le a帽ad铆 Skeleton. Les dejo el resultado:
https://jorgemacper.github.io/random-user/

Aqu铆 les dejo el repositorio del proyecto:
https://github.com/jorgemacper/random-user

Me ayud贸 mucho esta serie de tutoriales donde se explica muy bien como usar skeletons con React:
https://www.youtube.com/watch?v=cg_tmJBisp8&list=PL4cUxeGkcC9i6bZhMuAzQpC6YgLmB4k4-

Mi dise帽o con Skeleton

Ac谩 les dejo otro ejemplo de como crear un skeleton:

https://platzi.com/comentario/2570313/

Este es el resultado del reto. Saludos !

import React from "react";
import ContentLoader from "react-content-loader";
import "./LoadingSkeleton.css";

function LoadingSkeleton(props) {
  return (
    <li className="item">
      <span
        className={`${
          props.completed ? "fas fa-undo todo-undo" : "check fas fa-check"
        }`}
        onClick={props.onComplete}
      ></span>
      <p className="loading_description">
        <ContentLoader className="content_loader"
        speed={1}
        width={130}
        height={20}
        viewBox="0 0 130 20"
        backgroundColor="#f3f3f3"
        foregroundColor="#ecebeb"
        {...props}
      >
        <circle cx="20" cy="10" r="10" />
        <circle cx="50" cy="10" r="10" />
        <circle cx="80" cy="10" r="10" />
      </ContentLoader>
      </p>
      <span
        className={"delete-todo fas fa-trash"}
        onClick={props.onDelete}
      ></span>
    </li>
  );
}

export { LoadingSkeleton };

Aqu铆 mi resultado hasta ahora:

Muy buen ejercicio

import React, { useContext} from 'react';
import { Box, Stack, Text, Skeleton } from '@chakra-ui/react'
import { TodoContext } from '../Context';
import { TodoItem, ModalTodo } from '../../components'

const TodoList = () => {
    const { searchTodos: todos, error, loading } = useContext(TodoContext);
    return (
            <Box
                rounded={'lg'}
                bg={'todo.1000'}
                backdropFilter={'blur(10px)'}
                boxShadow={'lg'}
                p={8}
                mt={10}
				mb={20}>
                <Stack spacing={4}>
                    {error && <Text>Error: {error}</Text>}
                    {loading && <Stack>
                        <Skeleton height='20px' />
                        <Skeleton height='20px' />
                        <Skeleton height='20px' />
                        <Skeleton height='20px' />
                        </Stack>
                    }
                    {(!todos.length && !loading) &&  <Text align={'center'}>No hay Todos</Text>}
                    <ModalTodo />
                    {
                        todos.map((todo, index) => {
                            return (
                                <TodoItem
                                    key={index} 
                                    todo={todo}
                                />
                            )
                        })
                    }
                </Stack>
            </Box>          
    )
}

export default TodoList;