Loading and Empty state screens:
.
.
Error State Screen:
.
Primeros pasos con React
Cómo aprender React.js
Cuándo usar React.js
Cambios en React 18: ReactDOM.createRoot
Instalación con Create React App
Fundamentos de React: maquetación
JSX: componentes vs. elementos (y props vs. atributos)
Componentes de TODO Machine
CSS en React
Fundamentos de React: interacción
Manejo de eventos
Manejo del estado
Contando y buscando TODOs
Completando y eliminando TODOs
Fundamentos de React: escalabilidad
Organización de archivos y carpetas
Persistencia de datos con Local Storage
Custom Hook para Local Storage
Manejo de efectos
React Context: estado compartido
useContext
Modales y formularios
Portales: teletransportación de componentes
Formulario para crear TODOs
Quiz: Modales y formularios
Retos
Reto: loading skeletons
Reto: icon component
Próximos pasos
Deploy con GitHub Pages
Toma el Curso de React.js: Patrones de Render y Composición
Muchas aplicaciones se quedan en blanco mientras cargan su contenido. Otras muestran algún mensaje de “cargando” 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
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
//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/>
</>
)}
//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>
)}
//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 />
)}
//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/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>
)
...
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>
)
...
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?
La hice con la herramienta que nos dejaron en la lectura, bastante sencillo hacerlo con esa herramienta la verdad
Vamos avanzando, está muy bueno el proyecto para llevar varios conceptos
.
.
.
.
.
.
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:
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:
Utilice algo diferente, aquí por si quieren aprender a cómo hacerlo: https://css-tricks.com/single-element-loaders-the-dots/
Carga
Sin TODO’s
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:
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
por eseleton desde Create React Content Loader pero no fue de mi preferencia
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:
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 };
Dejo mi versión.
https://react-todo-app-lucaspintos909.vercel.app/
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;
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?
o inicia sesión.