Por favor, un curso intermedio y uno avanzado con este mismo profe. Es muy simple y claro a la vez.
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
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Para crear las funcionalidades de completar y eliminar TODOs podemos crear una función que reciba el id o texto de nuestro TODO, para después editarlo o eliminarlo.
Creamos la función completeTodo
, que recibirá el texto de nuestro TODO, ubicamos el TODO en nuestro arreglo, cambiamos el valor de la propiedad completed
de nuestro TODO y muy importante actualizar nuestro estado, para que React pueda re-renderizar nuestros TODOs con los nuevos datos.
const completeTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
setTodos(newTodos);
};
Podemos hacer algo parecido a la función de completar, pero ahora, en lugar de cambiar si está completada o no, solamente la eliminaremos de nuestros TODOs con el método splice
, y también regresaremos un nuevo arreglo con los TODOs actualizados.
const deleteTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
setTodos(newTodos);
};
Una vez tenemos creada la lógica para completar y eliminar TODOs, podemos pasar esas funciones a nuestros TodoItem.
import React from 'react';
import { TodoCounter } from './TodoCounter';
import { TodoSearch } from './TodoSearch';
import { TodoList } from './TodoList';
import { TodoItem } from './TodoItem';
import { CreateTodoButton } from './CreateTodoButton';
// import './App.css';
const defaultTodos = [
{ text: 'Cortar cebolla', completed: true },
{ text: 'Tomar el cursso de intro a React', completed: false },
{ text: 'Llorar con la llorona', completed: true },
{ text: 'LALALALAA', completed: false },
];
function App() {
const [todos, setTodos] = React.useState(defaultTodos);
const [searchValue, setSearchValue] = React.useState('');
const completedTodos = todos.filter(todo => !!todo.completed).length;
const totalTodos = todos.length;
let searchedTodos = [];
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
const completeTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
setTodos(newTodos);
};
const deleteTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
setTodos(newTodos);
};
return (
{searchedTodos.map(todo => (
completeTodo(todo.text)}
onDelete={() => deleteTodo(todo.text)}
/>
))}
);
}
export default App;
Para que nuestra aplicación funcione también tenemos que recibir las props en nuestros ítems y usarlas.
import React from 'react';
import './TodoItem.css';
function TodoItem(props) {
return (
<li className="TodoItem">
<span
className={`Icon Icon-check ${props.completed && 'Icon-check--active'}`}
onClick={props.onComplete}
>
√
span>
<p className={`TodoItem-p ${props.completed && 'TodoItem-p--complete'}`}>
{props.text}
p>
<span
className="Icon Icon-delete"
onClick={props.onDelete}
>
X
span>
li>
);
}
export { TodoItem };
Contribución creada por: Brandon Argel.
Aportes 98
Preguntas 38
Por favor, un curso intermedio y uno avanzado con este mismo profe. Es muy simple y claro a la vez.
Yo me tome el atrevimiento y cambie el método completeTodo()
por toggleCompleteTodo()
para permitir que destilde de completado algún TODO que le marco completado sin querer. De esta forma sigue funcionando todo y me parece más genial para el usuario por si se confunde.
.
La solución me quedo asi:
const toggleCompleteTodos = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = !newTodos[todoIndex].completed;
setTodos(newTodos);
}
Hasta ahora, excelente curso BatiProfe 😃
Otra forma de borrar los todos
function deleteTodo(text){
const newTodos = todos.filter(todo=>todo.text !== text)
setTodos(newTodos)
}
La mejor frase que puedo escuchar de Juan es decir: "Entonces, recapitulemos…"
Todos los profesores deberían hacer esto para poder juntar toda la información en nuestro cerebro de forma más fácil
Para cambiar el icono de la tarea es muy sencillo:
Mandamos por props una propiedad donde mandemos por valor todo.complete (es el mapeo del nuevo array que se creo la clase pasada de toda la información de los todos)
Y en el componente TodoItem traer ese prop y hacer un if ternario como en la siguiente imagen y listo! Espero haber ayudado 😃
.
Porfa que el curso de avanzado lo dicte JUAN DAVID los otros profesores son buenos pero hablan como si uno ya supierta todo en cambio Juan te lleva de la mano y sin darte cuenta estás entiendiendolo todo!
me tome el atrevimiento de hacer una verificacion. Cuando un todo no este completado no se pueda eliminar!
necesitamos mas cursos como este
Hola, trate de optimizar el código y permitir al usuario cambiar entre completo o no. Quedó así:
const findIndex = (text) => {
return todos.findIndex(todo => todo.text === text)
}
const toggleTodo = (text) => {
const newTodos = [...todos]
newTodos[findIndex(text)].completed = !newTodos[findIndex(text)].completed
setTodos(newTodos)
}
const deleteTodo = (text) => {
const newTodos = [...todos]
newTodos.splice(findIndex(text), 1)
setTodos(newTodos)
}
Por favor, un curso intermedio y uno avanzado con este mismo profe. Es muy simple y claro a la vez.
Apoyo la porpuesta de @Marcelo Diaz. De verdad muy buen profe
Yo originalmente ya había usado un índice en la TodoList:
<TodoList>
{searchedTodos.map((todo, index) => (
<TodoItem
text={todo.text}
key={index}
completed={todo.completed}
onComplete={() => completeTodo(index)}
onDelete={() => deleteTodo(index)}
/>
))}
</TodoList>
Por lo que en vez de buscar por texto (ya con esto podemos tener 2 o más elementos con mismo texto) podemos buscar por índice:
const completeTodo = (index) => {
const newTodos = [...todos]
newTodos[index].completed = !newTodos[index].completed
setTodos(newTodos)
}
const deleteTodo = (index) => {
const newTodos = [...todos]
newTodos.splice(index, 1)
setTodos(newTodos)
}
Les dejo una muestra de los avances que tengo hasta el momento 🥳
Les comparto mi progreso con un diseño con una paleta de colores similar a la de Platzi 😄
.
Les comparto otra alternativa de completeTodo y deleteTodo…
En este caso todo sucede dentro del setState para ahorrar memoria en la aplicación.
Cuando manejas una app o una página que actualiza listas muy grandes te permitirá ahorrar memoria al no tener que crear nuevas variables con la lista a modificar.
/**
* TOGGLE TODO COMPLETED/UNCOMPLETED
*
* @param {text} text
*/
const toggleCompleteTodo = (text) => {
//* Update the state of todos with setTodos
setTodos(
//* Filter the todos array
todos.map(todo => {
//* If the text of the todo matches the text of the todo that was clicked
if (todo.text === text) {
return {
//* ... Include the rest of the todo array
...todo,
//* And toggle the completed property
completed: !todo.completed,
};
}
//* Return the rest of the todos array unchanged
return todo;
})
);
};
/**
* DELETE TODO
*
* @param {text} text
*/
const deleteTodo = (text) => {
//* Update the state of todos with setTodos
setTodos(todos.filter(
//* Filter the todos array to remove the todo that was clicked
todo => todo.text !== text
));
};
Preferi implementar un toggleTodo que un todoComplete por si hacemos click por error.
newTodos[todoIdx].completed = !newTodos[todoIdx].completed;
Yo le pase los todos y el setTodo al item y desde ahi resolvi la logica de la siguiente forma, pudiendo marcar y desmarcar el todo por si lo marcaste por error y aun no lo habias completado, yo mismo hice los svg para no utilizar iconos y/o imagenes
function TodoItem({text, completed, todos, setTodos}) {
const onComplete = () => {
let actualizarTodos = []
if(completed) {
actualizarTodos = todos.map(item => {
if(text === item.text) {
item.completed = false;
}
return item;
})
} else {
actualizarTodos = todos.map(item => {
if(text === item.text) {
item.completed = true;
}
return item;
})
}
setTodos(actualizarTodos);
}
const onDelete = () => {
setTodos(todos.filter(item => item.text !== text))
}
return (
<li className="TodoItem">
<svg
className={`iconCheck ${completed && 'iconCheck--active'}`}
height="40"
width="40"
onClick={onComplete}
>
<path d="M 25 13 A 10 10 0 1 0 30 20 m 0 0 z M 15 20 l 5 5 L 33 10 " />
</svg>
<p className={`TodoItemText ${completed && 'TodoItemText--completed'}`}>{text}</p>
<svg
className={`iconDump ${completed && 'iconDump--active'}`}
height="40"
width="40"
onClick={onDelete}
>
<path d="M 8 10 L 32 10 m 0 0 z M15 10 l 0 -3 l 2 -2 l 6 0 l 2 2 l 0 3 z" />
<path d="M10 15 l 20 0 l -3 20 l -14 0 l -3 -20" />
<path d="M15 18 l 1 14 z M 20 18 l 0 14 z M 25 18 l -1 14" />
</svg>
</li>
)
}
export { TodoItem };
Algo útil, sería permitirle al usuario poder quitar el to-do como completado. Para hacer esto, basta con colocar
newTodos[todoIndex].completed = ! newTodos[todoIndex].completed
El cual servirá de toggler, donde si se hace click y está marcado como completado, lo desmarcará y quedará sin completar.
con un IF en la funcion completeTodos de App.js, puedes volver a destachar un TODO tachado(algo básico, pero por si lo necesitan:
const completeTodos = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
if(todos[todoIndex].completed === false){
todos[todoIndex].completed = true;
} else{
todos[todoIndex].completed = false;
}
setTodos(newTodos);
};
Otra alternativa de resolver el mismo problema:
const completeTodos = text => {
setTodos(prev => prev.map(todo => todo.text === text
? {...todo, completed: true}
: todo
))
}
const deleteTodos = text => {
setTodos(prev => prev.filter(todo => todo.text !== text))
}
Completando TODOS
La funcion recibe el texto, lo ubica en el array y su propiedad completed cambia el valor, actualizamos el estado para que React re-renderize los TODOS con los nuevos datos.
Eliminando TODOs
Ahora en lugar de cambiar el estado del TODO eliminaremos usando el metodo splice y regresaremos un nuevo array actualizado con los TODOs.
Para que sirva la app tenemos que recibir las props en nuestros items y usarlas
Es mas fácil así, solo le pasas el índice y ya ¯_(ツ)_/¯:
(task, i) => <TodoItem ... onCheck={() => updateItemCheck(i)} />
Para la función de deleteTodo yo no la hice con splice, la hice con filter:
const deleteTodo(text) => {
const removeTodo = todos.filter((todo) => todo.text !== text);
setTodos(leftTodos);
}
Para quienes no les funcione, cambie el nombre de la propiedad onComplete por complete y onDelete por delete (cambiarlo por otro, pero con on lo probre varias veces y no me funciono) , me funciono de esta manera.
<TodoList>
{searchTodos.map(todo => (
<TodoItem
key={todo.text}
text={todo.text}
completed={todo.completed}
complete={() => completeTodo(todo.text)}
delete={() => deleteTodo(todo.text)}
/>
))}
Hice lo mismo que el compañero lucas y de verdad que le agrega mucha funcionalidad a la app por si nos llegamos a equivocar y marcamos una tarea como completada cuando aún no está completa🥴
Aquí dejo el código:
En App.js
En TodoItem.Js
Con esto, marcamos y desmarcamos los todo.
newTodos[todoIndex] = {
text: newTodos[todoIndex].text,
completed: !newTodos[todoIndex].completed
}
Para que al momento de darle click en el chulo de completarTodo, se podria poner la negacion de el valor del todo por si quisiera indicar nuevamente que no se ha completado:
newTodos[indexTodo].completed = !newTodos[indexTodo].completed;
Me pareció interesante poder hacer que el ícono de check funcione como un toggle (un botón que se activa y desactiva) por lo que agregúe un condicional dentro de nuestra función:
const toggleCompleteTodos = (text) =>
{
const todoIndex = todos.findIndex(task=> task.text == text);
const newTodos = [...todos];
if(newTodos[todoIndex].completed == false) // Si nuestro valor es falso, lo cambiamos a true
{
newTodos[todoIndex].completed = true;
}
else // caso contrario, va false :)
{
newTodos[todoIndex].completed = false;
}
setTodos(newTodos);
}
Never stop learning ♥
Una cosa interesante que me paso antes de ver la resolución del video fue la de clonar el vector todos, no se si sera algo trivial pero cuento lo que me paso por si alguno le sirve.
//en mi caso la llame así y recibe la posicion del vector
const finishTodo = (id) => {
todos[id].completed = true;
let aux = todos;
setTodos(aux);
};
si hacia esta asignación
let aux = todos;
no se realizaba el cambio de estado en la pagina de forma automática
en cambio al hacerlo como el profesor si funcionaba instantáneamente.
let aux = [...todos];
Investigando un poco, creo que esto se debe a la forma en que js copia los arrays. De la anterior forma se copiaba una referencia y de la segunda se hacia una copia real del array.
Dejo una guia en donde lo explican de mejor manera
https://www.kuworking.com/javascript-como-copiar-arrays
//permite completar y descompletar un todo
const completeTodos = (text) => {
setTodos(
todos.map((todo) =>
todo.text === text ? { ...todo, completed: !todo.completed } : todo
)
);
};
//eliminando todo
const deleteTodos = (text) => {
setTodos(todos.filter((todo) => todo.text !== text));
};
Podemos modificar la función completeTodos para que podamos volver a marcarlo como incompleto, en caso de que nos equivoquemos o que al fin resulte que no completamos efectivamente ese ToDo jeje
const toggleCompleteTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text == text);
const newTodos = [...todos];
newTodos[todoIndex].completed = !newTodos[todoIndex].completed
setTodos(newTodos)
}
También podemos simplificar la función deleteTodo con el método filter, que retorna un nuevo array:
const deleteTodo = (text) => {
setTodos(
todos.filter(todo => todo.text != text)
)
}
profe si que explicas bien.
Con lo visto en esta clase y los aported de otros compañeros le reduje a mi solucion como 8 lineas de codigo :'v
Agregue un id para cada funcion (toggle y delete), para evitar posibles errores , y use el metodo splice de Array para manejar esos casos .
Hola, yo hice el completeTodos de otra manera (incluyendo el toggle), ¿Qué tal les parece (bien o mal)?
const completeTodos = (text) => {
let todoFound = todos.find( todo => {
return todo.text === text;
});
todoFound.completed = !todoFound.completed;
//crear una nueva lista de ToDos para que aplique el cambio
const newTodoList = [...todos];
setTodos(newTodoList);
}
Si se usa:
<const todoIndex = todos.findIndex(todo => todo.text === text)>
Va a afectar la aplicacion, ya que si, pusieramos dos o mas todos con el mismo texto, lo que pasaria es que solamente podria completar uno de esos dos o mas todos, por la tanto para evitar errores seria mejor usar:
<const todoIndex = todos.findIndex(todo => todo.id === text)>
Ya que como el id es un identificador unico de cada todo, entonces no afectaria si hay mas de un todo con el mismo texto
Completando y eliminando TODOs
Eliminar TodoS
const deleteTodo = (key) => {
const newTodos = todoS.filter(todo => todo.text != key);
setTodo(newTodos);
};
En mi caso he querido realizar un setTimeOut para que, despues de pasado el tiempo de la tarea completada, se eliminara. AQUI DEJO EL CODIGO
const [todos,setTodo]=React.useState(defaulttodos);
const [search, setSearch] = React.useState('');
//Para contar la cantidad de tareas realizadas
const completedTodos = todos.filter(todos => !!todos.completed ).length;
//Contar la cantidad de las tareas
const totalTodos = todos.length;
const completeTodos = (text)=>{
const todoIndex = todos.findIndex(todo =>todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex] = {
text: todos[todoIndex].text,
completed: true,
};
setTodo(newTodos);
// alert(`Has completado la tarea ${todos[todoIndex].text}`)
setTimeout(deleteTodos,5000)
}
const deleteTodos=(text)=>{
const todoIndex = todos.findIndex(todo=>todo.completed===true)
if(todoIndex===false){
console.log('La tarea aun no esta lista')
}else{
const newTodos=[...todos];
newTodos.splice(todoIndex,1)
setTodo(newTodos)
alert('La tarea completada sera eliminada')
}
}```
he aqui mi codigo, he tratado de hacer la inteligencia un poco diferente a la del profe(muy poco ) pero mellvo lo bailado por completo de la funcion delete no sin agradecer una busque en google de como eliminar elementos de un array, en fin he aca mi codigo…
const completeTodo = (identificador) =>{
let index = todos.findIndex(todo => todo.text === identificador)
let newArray = [...todos]
newArray[index].completed=true
setTodos(newArray)
}
const deleteTodo = (identificador) =>{
let deleteElement = todos.filter(todo => todo.text !== identificador)
setTodos(deleteElement)
}
le puse a mi codigo una alerta antes de eliminarlo para evitar errores este es el codigo:
le agregé una propiedad delete al array todos y dependiendo de esta propiedad se renderiza el cuadro de confirmación
const deleteTodos = (text)=>{
const newTodos = [...todos];
const todoIndex = todos.findIndex(todo => todo.text == text);
newTodos[todoIndex].deleted = true;
setTodos(newTodos)
}
const confirmDelete = (text) =>{
const todoIndex = todos.findIndex(todo => todo.text == text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
setTodos(newTodos)
}
const cancelDelete = (text) =>{
const newTodos = [...todos];
const todoIndex = todos.findIndex(todo => todo.text == text);
newTodos[todoIndex].deleted = false;
setTodos(newTodos)
}
<TodoList>
{todoSearched.map(todo =>(
<TodoItem
key ={todo.text}
text = {todo.text}
completed = {todo.completed}
confDelete = {todo.deleted}
onComplete = {()=> completeTodos(todo.text)}
onDelete= {()=> deleteTodos(todo.text)}
onConfirmDelete={()=> confirmDelete(todo.text)}
onCancel = {()=>cancelDelete(todo.text)}
/>
))}
</TodoList>
// este es el componente todoItem
function TodoItem(props){
return(
<li className="item-list">
<span
className={`check ${props.completed && 'check-on'}`}
onClick={props.onComplete}
></span>
<p className={`item-p ${props.completed && 'item-p-completed'}`}>{props.text}</p>
<span
className="delete"
onClick={props.onDelete}
></span>
<div className={`confirmation ${props.confDelete && 'confDelete'}`}>
<h3 className="title">¿Estas seguro de eliminar este ToDo?</h3>
<span
className="confButton"
onClick={props.onCancel}>cancelar</span>
<span
className="confButton yes"
onClick={props.onConfirmDelete}
>confirmar</span>
</div>
</li>
)
}
En caso de querer una función más dinámica y que valide si la tarea esta completada pase a por hacer y viceversa.
const completeTodos(text) = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text)
let is_completed = newTodos[todoIndex].completed;
const newTodos = [...todos];
# Aqui viene el cambio
newTodos[todoIndex].completed = is_completed ? false: true;
}
Me parece mas fácil eliminar los todos de esta forma:
function tareaEliminada(item){
const tareasEliminadas = listaTareas.filter(
tarea => tarea.nombre !== item.nombre)
setListaTareas(tareasEliminadas)
}
Podemos utilizar nuestro operador ternario para cambiar el estado de nuestras ToDos
_si te equivocas y realizas una tarea que no has realizado puedas volver a desmarcar _
const completeTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text)
const newTodos = [...todos]
newTodos[todoIndex].completed ? newTodos[todoIndex].completed = false : newTodos[todoIndex].completed = true
setTodos(newTodos)
}
mi lista de tareas
Yo me emociono igual que JuanDC cuando me funcionan el código jeje 😁😊
Me encanta que el profe mantenga la “lógica de negocio” alejada de la vista de cada uno de los componentes.
Encontró la manera perfecta de explicarnos y en el proceso no enseñarnos malas prácticas.
Amo este curso ❤️
Otra forma para el método de borrar los TODOs
const deleteTodo = (text) => {
const newTodos = todos.filter(todo => todo.text !== text)
setTodos(newTodos)
}
me alegra que hice el deletetodo por cuenta propia antes de que juan lo hiciera
Todos los pequeños detalles que agregué:
Mi forma mas sencilla de marcar o desmarcar una tarea fue esta:
const completeTodos = (text) => {
const clickedTodo = todos.find( todo => todo.text === text)
clickedTodo.completed = !clickedTodo.completed
setTodos([
...todos
])
}
y funciona!
Siendo sincero, estaba codeando y este código me salió sin querer y ni sabía por qué funcionaba.
Después de un rato analizando, vi que funciona porque la variable clickedTodo solo es un pointer al objeto original en memoria, así que editar una propiedad del clickedTodo cambiará el objeto original también. Entonces, sin necesidad de crear un array de todos nuevo, envié el mismo array todos pero ahora editado, listo para re-renderizar.
Yo solo le puse un condicional que si el todo ya esta en true que lo pase a false y sino que lo pase a true,
tmb en el delete use filter porque asi lo pense y cuado vi como lo hizo el profe bueno lo deje asi me parece que filter es mas corto y facil de leer
const completeTodo =(id)=>{
const todoIndex = todos.findIndex(todo=> todo.id === id)
const newTodos =[...todos];
newTodos[todoIndex].complete? newTodos[todoIndex].complete=false : newTodos[todoIndex].complete=true;
setTodos(newTodos)
}
const deleteTodo =(id)=>{
let newTodos =[...todos];
newTodos=newTodos.filter(todo =>todo.id !== id)
setTodos(newTodos)
}
Alternativas para los métodos de completar y borrar:
- Completado: Podemos definir el método toggleCompleted que nos permite marcar y desmarcar como completado, para esto utilice el método map, aunque se podría seguir utilizando el del profe.
const toggleCompleted = (text) => {
setTasks(tasks.map((task) => {
if (task.text === text) return { ...task, completed: !task.completed };
return task;
}))
};
- Borrar tarea: Podemos utilizar filter para retornar a nuestro array de todos todas las tareas que NO cumplan con la que queremos borrar, y retornando nada si encontramos la que queremos borrar, para de ese modo quitarla del array de todos.
const deleteTask = (text) => {
setTasks(tasks.filter((task) => {
if (task.text !== text) {
return task;
}
return;
}))
}
Es hasta este punto que voy entendiendo lo importante que es comprender los arrays en Js en el manejo de REACT
Para los que no lo han completado aquí esta el curso:
Hola, les comparto mis funciones para completar y eliminar ToDos:
const completeToDo = (text) => {
const updatedTodos = todos.map(todo => {
return {
text: todo.text,
completed: todo.completed || todo.text === text
}
});
setTodos(updatedTodos);
}
const deleteToDo = (text) => {
const updatedTodos = todos.filter(todo => todo.text !== text);
setTodos(updatedTodos);
}
Por si a alguien le sirve, la diferencia entre == y ===, es:
== compara dos datos aunque su tipo sea diferente. Si los tipos de datos son diferentes entonces convierte uno de los valores al otro tipo y compara si son iguales para devolverte true.
=== Revisa que tanto los datos, así como su tipo, sean iguales. Si los tipos de datos son diferentes entonces devolverá false sin ninguna conversión.
Yo borre el Item con ayuda de .Filter envés de .Splice y ademas utilize .Find para no permitirle que se borre sin antes haber completado la tarea. Y todo con la ayuda de MDN!
const deleteTodo = (text) => {
const todoSelect = todos.find((todo) => todo.text == text);
console.log(todoSelect.completed);
if (!todoSelect.completed) {
alert("Debes completarlo primero!!");
} else {
const deleteTodo = todos.filter((todo) => todo.text !== todoSelect.text);
setTodos(deleteTodo);
}
};
La emocion de que ande todo de primera no se va nunca jaja
Por aquí les dejo un pequeño aporte para reducir un poco nuestro código…
agrege una funcion de los mas sencilla para poner el item de "todo check"despues de un click en verdadero, con dos click denuevo en falso.
import React from "react";
import './TodoItem.css';
function TodoItem(props){
return(
<li className="TodoItem">
<span className= {`Icon Icon-check ${props.completed && 'Icon-check--active'}`}
onClick={props.onComplete} onDoubleClick={props.noComplete}
>
✔
</span>
<p className={`TodoItem-p ${props.completed && 'TodoItem-p--complete'}`}
>
{props.text}
</p>
<span className="Icon Icon-delete" onClick={props.onDelete}
>
<ion-icon name="trash-outline"></ion-icon>
</span>
</li>
);
}
export { TodoItem };
codigo de app
import React from 'react';
import { TodoCounter } from './TodoCounter';
import { TodoSearch } from './TodoSearch';
import { TodoList } from './TodoList';
import { TodoItem } from './TodoItem';
import { CreateTodoButton } from './CreateTodoButton';
// import './App.css';
const defaultTodos = [
{ text: 'Cortar cebolla', completed: true },
{ text: 'Tomar el cursso de intro a React', completed: false },
{ text: 'Llorar con la llorona', completed: true },
{ text: 'LALALALAA', completed: false },
];
function App() {
const [todos, setTodos] = React.useState(defaultTodos);
const [searchValue, setSearchValue] = React.useState('');
const completedTodos = todos.filter(todo => !!todo.completed).length;
const totalTodos = todos.length;
let searchedTodos = [];
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
const completeTodos = (text)=> {
const todoIndex = todos.findIndex(todo =>todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
setTodos(newTodos);
};
const descompleteTodos = (text)=> {
const todoIndex = todos.findIndex(todo =>todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = false;
setTodos(newTodos);
};
const deleteTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
setTodos(newTodos);
};
return (
<React.Fragment>
<TodoCounter
total={totalTodos}
completed={completedTodos}
/>
<TodoSearch
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
<TodoList>
{searchedTodos.map(todo => (
<TodoItem
key={todo.text}
text={todo.text}
completed={todo.completed}
onComplete={() => completeTodos(todo.text)}
noComplete={() => descompleteTodos(todo.text)}
onDelete={() => deleteTodo(todo.text)}
/>
))}
</TodoList>
<CreateTodoButton />
</React.Fragment>
);
}
export default App;
el problema es que no se si el ondbclick es mala practica de esa manera por que pensaba hacerlo con if pero asi es mas sencillo.
He notado que al usar solo todos cuando queremos marcar una tarea completada y sin usar el array de **newTodos **para **setTodos(todos) ** el componente del Item de React no se re-renderiza. Solo cuando hago un cambio en el codigo.
const todoIndex = todos.findIndex(todo => todo.text === text);
todos[todoIndex].completed = true;
setTodos(todos);
Interesante. Eso me hace pensar un poco en como React renderiza los componentes, todavia no tengo la certeza pero seguro que lo veremos en el curso. 😄
completando y eliminando todos
aprendiendo a elegir y modificar cambios
¡Hola! Les comparte mi función para borrar cosas, usa filter en lugar de lo que David usó, funciona igual pero es ligeramente más cortito 😄
const deleteTodo = (text) => {
const newTodos = todos.filter(todo => todo.text !== text);
setTodos(newTodos);
};
Mi practica
import React from 'react';
import {v4 as uuidv4} from 'uuid';
import {TodoCounter, TodoSearch, TodoList, TodoItem, CreateTodoButton} from "./components";
// import logo from './logo.svg';
//import './App.css';
const defaultTodos = [
{id: uuidv4(), text: 'Cortar cebolla', completed: false},
{id: uuidv4(), text: 'Tomar el curso', completed: true},
{id: uuidv4(), text: 'Ir a dormir', completed: false},
];
function App() {
const [todos, setTodos] = React.useState(defaultTodos);
const [searchValue, setSearchValue] = React.useState('');
const completedTodos = todos.filter(todo => !!todo.completed).length;
const totalTodos = todos.length;
let searchedTodos = [];
if (searchValue.length >= 1) {
searchedTodos = todos.filter(todo => {
let todoText = todo.text.toLowerCase();
let searchText = searchValue.toLowerCase()
return todoText.includes(searchText);
});
} else {
searchedTodos = todos;
}
const toggleCompleteTodos = (id) => {
const todoIndex = todos.findIndex(todo => todo.id == id);
const newTodos = [...todos];
(newTodos[todoIndex].completed === false)
? newTodos[todoIndex].completed = true
: newTodos[todoIndex].completed = false;
setTodos(newTodos);
};
const deleteTodo = (id) => {
const todoIndex = todos.findIndex(todo => todo.id == id);
const newTodos = [...todos];
console.log(newTodos);
newTodos.splice(todoIndex,1);
console.log(newTodos);
setTodos(newTodos);
};
return (
<React.Fragment>
<TodoCounter
completed={completedTodos}
total={totalTodos}
/>
<TodoSearch
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
<TodoList>
{searchedTodos.map(todo => (
<TodoItem
key={todo.id}
text={todo.text}
completed={todo.completed}
onToggleComplete={() => toggleCompleteTodos(todo.id)}
onDelete={()=>deleteTodo(todo.id)}
/>
))}
</TodoList>
<CreateTodoButton/>
</React.Fragment>
);
}
export default App;
import React from 'react';
import './TodoItem.css';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CloseIcon from '@mui/icons-material/Close';
function TodoItem(props) {
const onToggleComplete = props.onToggleComplete;
const onDelete = props.onDelete;
return (
<li className="TodoItem">
<span
className={`Icon Icon-check ${props.completed && 'Icon-check--active'}`}
onClick={onToggleComplete}
>
{
props.completed
? <CheckBoxIcon/>
: <CheckBoxOutlineBlankIcon/>
}
</span>
<p className={`TodoItem-p ${props.completed && 'TodoItem-p--complete'}`}>{props.text}</p>
<span
className="Icon Icon-delete"
onClick={onDelete}
><CloseIcon/></span>
</li>
)
}
export {TodoItem};
esta es mi solucion para cambiar el eliminar todos
const deleteTodo = (text) => {
const updatedTodos = todos.filter( todo => todo.concept !== concept)
settodos(updatedTodos)
}
Muy buenas estas clases, con estas explicaciones, me gustó lo del pan tajado jajaja.
Otra forma de hacerlo… es pasarle el index desde .map
searchTasks.map( (task, index) => (
<TodoItem key={task.id} task={task}
changeCompleteTask={() => changeCompleteTask(index)}
delTask={() => delTask(index)} />
))
no me sorprenderia si mi profe a los 30 años es el nuevo elon musk
Para poder marcar y desmarcar un Todo (a veces hacemos missclick o nos distraemos y marcamos el que no era) solo es poner una condicional que verifiqué si tiene estado “false” lo cambie a “true” o viceversa. 😀
const completeTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
//La condicional es la siguiente:
newTodos[todoIndex].completed === false ? newTodos[todoIndex].completed = true : newTodos[todoIndex].completed = false;
setTodos(newTodos);
}
Me gusta escribir las variables globales en mayúsculas, para no confundirme e identificarlas rapidamente.
Intenté crear estados individuales por cada todo en sus atributos ‘completed’ y ‘deleted’. Pero para hacerlo debía crear un nuevo componente dentro del componente que ya tenía, dado que no dejaba crear los Hooks. El objetivo era ejecutar la actualización desde el mismo componente ‘List Item’. Observé que no podía actualizar los estados originales, supongo por lo que estos estados realmente no estaban dentro de App, sino de cada ‘Item’, y sí, se guardaban los valores de los estados (encapsulamiento? 🤨), pero no quedaban registrados en los Todos originales. Probé copiar la lista de Todos y modificar la nueva lista, para luego usar ‘set’, pero me generó un error por muchos Renders. En conclusión, no es buena idea JAJAJA 🤣
Dejo el código para que se hagan un idea del enredo! 👇
function LoadTodosListItem (todo) {
const [completed, setCompleted] = React.useState(todo.completed);
const [deleted, setDeleted] = React.useState(todo.deleted);
return (
<TodoItem key={todo.text} text={todo.text} completed={completed} setCompleted={setCompleted} deleted={deleted} setDeleted={setDeleted} color={todo.color} todos={todos} setTodos={setTodos} />
)
}
Y…
<TodoList>
{searchedTodos.map(todo => LoadTodosListItem(todo))}
</TodoList>
Que clase tan genial
no me salio T.T
pero estoy feliz
Otra forma de hacer el toggle:
const toggleTodo = (text) => {
const todoIndex = todos.findIndex((todo) => todo.text === text);
setTodos((prev) => {
prev[todoIndex].completed = !prev[todoIndex].completed;
return [...prev];
});
};
La función setTodos
del react recibe por parámetro un valor prev
interno, ese valor es un estado previo de todos
, por lo que se puede trabajar sobre ese mismo valor sin necesidad de crear una nueva variable.
Sí, es un poco más complejo, pero es otra forma (?)
Otra forma de hacer deleteTodo
const deleteTodo = (text) => {
setTodos((prevTodo) =>
prevTodo.filter((todo) => !todo.text.includes(text))
);
};
setTodo
toma prevTodo
como parámetro propio que retorna el estado anterior de todos
. Se pasa un arrow function y se filtra para que retorne los todo
que no incluyan el texto.
Así es como me queda usando las librerías de SweetAlert2 y para ello debes ejecutar en la terminal o CMD el comando:
npm install --save sweetalert2 sweetalert2-react-content
y después para el tema oscuro de las alertas:
npm install @sweetalert2/theme-dark
Por último en los componentes de “.js” debes importarlos, en App.js importas:
import ‘@sweetalert2/theme-dark’;
En los componentes donde vallas a mostrar la alerta importas:
import Swal from “sweetalert2”;
En estos ejemplos, estas son una parte de mi código de las alertas:
const completeTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
Swal.fire({
title: "¿Marcar Todo?",
background: "#000",
showCancelButton: true,
confirmButtonColor: "#31b904",
cancelButtonColor: "#d30404",
cancelButtonText: "NO",
confirmButtonText: "SÍ",
footer: text
}).then((result) => {
if (!!result.value) {
newTodos[todoIndex].completed = true;
setTodos(newTodos);
}
})
};
const deleteTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
Swal.fire({
icon: 'question',
title: "¿Eliminar Todo?",
background: "#000",
confirmButtonColor: "#d30404",
showCancelButton: true,
cancelButtonColor: '#31b904',
cancelButtonText: "NO",
confirmButtonText: "SÍ",
footer: text
}).then((result) => {
if (!!result.value) {
newTodos.splice(todoIndex, 1);
setTodos(newTodos);
}
})
};
Este curso es buenisimo!!! D:
Hasta este momento , un excelente curso de React, no puedo esperar para segui con el proximo!!!
Con este código extra la función completeTodo también habilita el desmarcar como hechos los ‘todos’
const completeTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text)
const newTodos = […todos]
if(!newTodos[todoIndex].completed) {
newTodos[todoIndex].completed = true
} else {
newTodos[todoIndex].completed = false
}
setTodos(newTodos)
}
Juan contagia la felicidad cuando corre bien la aplicación jajajaja
A mi me gusta más enviar en props el objeto completo, en ocasiones te ahorra enviar múltiples propiedades. En este caso como trabajamos con todas las propiedades del todo creo que conviene mucho.
Ejemplo:
<TodoItem key={todo.text} todo={todo}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
Para completar el todo se simplifica la función:
const completeTodo = (todo) => {
let index = todos.indexOf(todo);
let newTodos = [...todos];
newTodos[index].completed=true;
setTodos(newTodos);
}
Y en el componente trabajas con el “objeto” que te interesa:
<span className={`Icon Icon-check ${props.todo.completed && 'Icon-check--active'}`}
onClick={onComplete}
>
Mi solucion
const [completed, setCompleted] = useState(props.completed);
const handleComplete = (event) => {
event.target.checked ? setCompleted(true) : setCompleted(false);
};
Tambien se puede hacer una forma mas simplificada en deleteTodo:
const deleteTodo = (text) => {
const newTodos = todos.filter(todo => todo.text !== text);
setTodos(newTodos);
}
De esta forma no buscamos el indice ni nada. Simplemente creamos un nuevo array con la funcion filter, con los todos que tengan un texto diferente al que le enviamos por parametro.
Esta forma de utilizar el codigo, es util cuando estamos haciendo una App de TODOs con POO
todos[todoIndex] = {
text: todos[todoIndex].text,
completed:true,
};
Recien termine el curso de POO con Anncode y mi mente visualiza todo en modo “Objetos”
Que bueno por el profesor que se tomo la molestia de explicar muy detalladamente la funcion completeTodos.
Excelente el filtro
este es mi codigo! en mi caso tuve que buscarlo con un for porque las otras funciones me daban errores o no era lo esperado.
import './TodoItem.scss';
import checked from './assets/checked.svg';
import deleted from './assets/delete.svg';
function TodoItem(props){
const checkTodo =()=>{
if(props.completed === false){
// busca el todo con el text correspondiente
for (let i = 0; i < props.todos.length; i++) {
let todo = props.todos[i];
if(todo.text === props.text){
todo.completed = true;
}
}
// crea un nuevo arraya con el todo seleccionado con completed como true
let newTodos = props.todos;
// setea mediante una props el nuevo array
props.setTodos([...newTodos])
}else{
}
}
const deleteTodo =()=>{
let newTodos = props.todos.filter(todo => todo.text !== props.text);
props.setTodos(newTodos)
}
return(
<li className={`TodoItem ${props.completed && 'TodoItem-completed'}`} >
<p>{props.text}</p>
<div className='TodoItem__buttons'>
<button onClick={checkTodo}>
<img src={checked} alt="check task" title='check task'/>
</button>
<button onClick={deleteTodo}>
<img src={deleted} alt="delete task" title='delete task'/>
</button>
</div>
</li>
);
}
export { TodoItem };
Para este punto, podría crear un proyecto sencillo para la cual, podría aplicar todos estos conocimientos creando un proyecto desde cero.
Ya les contare mis avances.
Creo que sería bueno, dentro de la función completeTodo, colocar un condicional para que en caso de que newTodos[todoIndex].completed sea false, se cambie a true como ya hizo el profesor, pero que si es true, cambie a false y así el símbolo check vuelva al color negro y el “todo” ya no aparezca tachado, esto removiendo las clases que le hicieron tomar dichos estilos.
¡Lo logré, conseguí crear mi botón para eliminar TODOS!
https://github.com/luiznaiper/react-platzi-proyect-1.git
Lo que hice fue filtrar los TODOS que tuvieran la propiedad completada como falso y actualicé el estado:
const deleteCompletedTodos = ()=> {
const filterTodos = todos.filter(todo => todo.completed === false)
console.log(filterTodos)
setTodos(filterTodos)
}
<ClearCompletedButton
total = { totalTodos }
completed={ completedTodos }
deleteCompletedTodos={ ()=> deleteCompletedTodos()}
/>
Me costó muchísimo pero se logró
Pueda que haya sido por alguna razón que desconozco pero también se podía sacar en “index” a la hora de iterar con el método “map”.
<TodoList>
{searchedTodos.map((todo, index) => (
<TodoItem
key={todo.id}
text={todo.text}
completed={todo.completed}
completar={() => completeTask(index)}
remover={() => removeTask(index)} />
))}
</TodoList>
Metodos:
const completeTask = (index) => {
const newTodos = [...todos];
newTodos[index].completed = true;
setTodos(newTodos);
}
const removeTask = (index) => {
const newTodos = [...todos];
newTodos.splice(index,1);
setTodos(newTodos);
}
const completeTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
updateTodo(newTodos);
};
const deleteTodo = (text) => {
const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
updateTodo(newTodos);
};
Yo hice diferente en borrado (usando filter), les dejo mi función espero les sirva.
const deleteTodo = ( text ) => {
const newTodos = todos.filter( todo => todo.text !== text );
setTodos(newTodos);
};
Así los eliminé todos y con menos líneas de código, no se si será correcto
const deleteTodo = (text) => {
const todoFilter = todos.filter(todo => todo.text !== text)
setTodos(todoFilter)
}
Saludos Platzinautas.
Lo que hice fue, las variables completeTodos y totalTodos los lleve abajo, despues de la funcion if/else, y cambie los contadores de todos por searchedTodos
Eureka… funciona… super, muchas gracias…
Siento cosquillitas…
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?
o inicia sesión.