No tienes acceso a esta clase

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

Curso de React.js

Curso de React.js

Juan David Castro Gallego

Juan David Castro Gallego

Buscando TODOs

9/34
Recursos

Aportes 32

Preguntas 2

Ordenar por:

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

La diferencia entre los métodos toLowerCase() y toLocaleLowerCase() en JavaScript está relacionada con la forma en que se procesan los caracteres en mayúsculas y acentuados en diferentes idiomas.

toLowerCase() convierte una cadena de texto en minúsculas, utilizando las reglas de conversión que se aplican a los caracteres ASCII (caracteres en inglés y otros idiomas europeos que no tienen acentos)

Por otro lado, toLocaleLowerCase() también convierte una cadena de texto en minúsculas, pero utiliza las reglas de conversión específicas del idioma y la ubicación (localización) en la que se está ejecutando el código. Esto significa que, en función de la localización, algunos caracteres con acentos o diacríticos (como la letra “á” en español) pueden ser convertidos a su equivalente en minúsculas, mientras que otros caracteres pueden permanecer sin cambios.

👉🏻 Encontré otro 🚧error🚧 para el filtro.
.
Si buscas una palabra con tílde como “Canción” no la encontrarás.

Para eso implementé una linea de código que hace que las tildes las quita de las vocales, así podrás buscar “cancion” sin tilde y te aparecerá.

  const searchedTodos = Todos.filter(
    (Todo) => {
      
      // función texto sin tildes
      const noTildes = (text) => {
        return text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
      };

      // Normalizando texto sin tildes y a Lower Case
      const TodoTextLC = noTildes(todo.text.toLowerCase());
      const searchTextLC = noTildes(searchValue.toLowerCase());

      //renderizar con filtro
      return TodoTextLC.includes(searchTextLC);
    }
  );

Para los que de pronto tuvieron también dificultad en entender porque si searchValue es vacío, porque devuelve todos los valores del array cuando se filtra?

const searchedTodo= todos.filter((todo) => todo.text.toLowerCase().includes(searchValue.toLowerCase()));

Lo primero estamos aplicando el método includes de strings, es decir:

todo.text.toLowerCase() // string
todo.text.toLowerCase().includes(searchValue.toLowerCase())) // booleano

Si aplicamos un includes cuyo valor es vacio el va devolver un TRUE, por ejemplo:

const letra = "S"
const vacio = ""
const nombre = "Sergio"
const nombreCompleto = "Sergio Aguilar"
const numero = “3”

console.log(letra.includes("")) //True
console.log(vacio.includes("")) //True
console.log(nombre.includes("")) //True
console.log(nombreCompleto.includes("")) //True
console.log(numero.includes("")) //True

Como resultado cada elemento(todo) recorrido va ser True y por ende el filter aplicado va devolver cada elemento del array.

En un ambito profesional (dependiendo cada caso de uso), para un campo de buscar, me gusta normalizar ambas strings, ignorando mayusuclas, ignorando acentos, quitando espacios, en cualquier posicion de la string.

Mi método:

const normalizeString = (string) => {
    string = string || "";
    string = string.toLowerCase();
    // remover acentos
    string = string.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
    string = string.trim();
    return string;
  };

y lo uso asi:

const filteredTodos = todos.filter((todo) => {
    let { text: normalizedTodo } = todo;
    normalizedTodo = normalizeString(normalizedTodo);
    let normalizedSearch = normalizeString(searchValue);

    return normalizedTodo.includes(normalizedSearch);
  });

Espero le sirva a alguien 😃

Clase 9 - Buscando TODO’s

Para esta clase utilizaremos estados derivados, por lo tanto definimos una constante:

const searchedTodos = todos.filter(
  (todo) => {
    return todo.text.include(searchValue)
  }
)

Aqui lo que hacemos es buscar dentro de los textos de los todos el valor de searchValue creando un nuevo array con las coincidencias.

Luego debemos cambiar el comportamiento de del componente TodoList para que trabaje con el nuevo array, cambiando el defaultTodos con searchedTodos:

<TodoList>
  {searchedTodos.map(todo => (
    <TodoItem
      key={todo.text}
      text={todo.text}
      completed={todo.completed}
    />
  ))}
</TodoList>

Esto ya funciona, pero tiene un detalle no reconoce mayusculas o minusculas de acuerdo como busquemos, asi que lo que haremos es optimizar la busqueda para que del lado de la logica comvierta todo el texto en minusculas:

const searchedTodos = todos.filter(
  (todo) => {
    return todo.text.toLowerCase().include(searchValue.toLowerCase())
  }
)

Ahora optimizamos el codigo:

const searchedTodos = todos.filter(
  (todo) => {
    const todoText = todo.text.toLowerCase();
    const searchText = searchValue.toLowerCase();
    return todoText.includes(searchText);
  }
)

saludos.
yo decidí usar match con indicador/bandera i, para que se ignoren las diferencias entre mayúsculas y minúsculas.

  const searchedTodos = Lista.filter(dato => {
    const Reg = new RegExp(Buscar, 'i');
    if (dato.text.match(Reg) != null)
    {
      return dato;
    }
  });

Mi solución al reto: Los métodos de los arrays (Map, Find, Filter, etc) tienen un parámetro llamado index, ese index permite conocer la posición del elemento que se accede en el array, teniendo en cuenta lo anterior creamos 3 hooks de estado y enviamos el set del componente padre al hijo (App.js > TodoItem.js).

  const [todoIndex, setTodoIndex] = useState(null);
  const [removeTodo, setRemoveTodo] = useState(null);
  const [changeTodoCompleted, setChangeTodoCompleted] = useState(null);
<>
          {searchedTodos.map((todo, index) => (
            <TodoItem
              key={index}
              index={index}
              taskText={todo.text}
              completed={todo.completed}
              setRemoveTodo={setRemoveTodo}
              setChangeTodoCompleted={setChangeTodoCompleted}
              setTodoIndex={setTodoIndex}
            />
          ))}
</>

En el componente TodoItem al momento de hacer click en uno de los iconos, asignamos el valor del index a nuestro setTodoIndex y setRemoveTodo (ósea almacenamos el número del índice) y con setChangeTodoCompleted lo que hacemos es guardar un valor boolean que permita cambiar la tarea de completado a no completado.

  return (
    <li className="todoItemList">
      <span
        onClick={() => {
          setTodoIndex(index);
          if (!completed) {
            setChangeTodoCompleted(true);
          } else setChangeTodoCompleted(false);
        }}
      >
        <CheckIcon completed={completed} />
      </span>
      <p
        className={`TodoItem-p ${completed && "TodoItem-p--complete"}`}
      >{`${taskText}`}</p>
      <span className="removeItem" onClick={() => setRemoveTodo(index)}>
        <RemoveIcon />
      </span>
    </li>
  );

En App.js hacemos un find a nuestros todos y validamos que el index coincida con el todoIndex para luego cambiar el estado del todo.completed en true o false, así mismo, asignamos null al valor de la variable con el fin de “reiniciar” el estado y evitar cambiar un item erróneo. Para el eliminar sucede algo similar solo que en vez de hacer un find hacemos un splice entre la posición del index y la cantidad de elementos a eliminar.

<>
          {
            // Change the todo state
            todoIndex !== null
              ? todos.find((todo, index) => {
                  if (index === todoIndex) {
                    setTodoIndex(null);
                    todo.completed = changeTodoCompleted;
                    return todo;
                  }
                })
              : null
          }
          {
            // Delete todo
            removeTodo !== null
              ? todos.splice(removeTodo, 1) && setRemoveTodo(null)
              : null
          }
</>

Hola! tengo una pregunta, porque el filter me devuelve todos los elementos del array si no hay ningun valor en searchValue? no deberia quedar vacio?

Para mi app, yo no utilice un buscador, pero si agrege categorias que filtran los todos. Tambien agrege la opcion para eliminar los todos que solo has completado.
Un Gif de la app se puede ver en link de abajo. Intente ponerlo como visible desde el aporte, pero por alguna razon no lo logre. El link es de drive.
Muestra de la app

Hola, buenas 👋. Les comparto mi solución al reto de la clase anterior de tratar de hacer coincidir las búsquedas.

Mi solución es muy básica y simplemente es si la búsqueda es exactamente igual a algún TODO. Pero bueno, es mi solución xd.

const TODOs = todos.map(todos => todos.text);

  const resultado = TODOs.find(elemento => elemento == searchValue);

  console.log(resultado);

Ambas formas son validas, en el primer caso el return esta implícito

Yo hice un ligero cambio a la app, decidi que las coincidencias entre el buscador y los todos se remalcaran con un font-weight: bold.

Esto lo logre agregando el siguiente codigo a todoItem:

// ignore this line i'm just importing icons
import { AiOutlineCheck, AiOutlineDelete } from 'react-icons/ai';
import "./TodoItem.css"

function TodoItem(props){
  function getHighlightedText(text, highlight){
    // Split on highlight term and include term into parts, ignore case
    const parts = text.split(new RegExp(`(${highlight})`, 'gi'));
    return <span> { parts.map((part, i) => 
        <span key={i} style={part.toLowerCase() === highlight.toLowerCase() ? { fontWeight: 'bold' } : {} }>
            { part }
        </span>)
    } </span>;
  }
    return(
      <li className={`TodoItem ${props.completed && "active"}`} >
        <span className="">
          <AiOutlineCheck className='check'></AiOutlineCheck>
        </span>
        <p>{getHighlightedText(props.text, props.searchValue)}</p>
        <span><AiOutlineDelete className="delete"></AiOutlineDelete></span>
      </li>
    );
  }

export {TodoItem}; 

Para que el codigo funcione necesitamos agregar un prop con el estado searchValue a todoItem, basicamente el codigo envuelve las coincidencias en una etiqueta span que tiene el font-weight:bold en sus estilos.

Reto Solucionado modificaciones: Los defaults todos van a tener un id único Este id al igual que todos y setTodos lo pasamos como parametro para la función , en este caso son 2 delete y change, en el caso de delete filtramos el array todos con todos los que NO coincidan con el id pasado y ese valor se lo pasamos a setTodos en el caso del update lo que hacemos es una transformacion usamos un map que retornara cada id hasta el caso en donde el id coincida con el pasado en el parametro y con este valor le asignamos su inverso, ya luego con la transformación se la pasamos a setTodos ![](https://static.platzi.com/media/user_upload/image-ef14c797-3a1f-4510-b013-2390dedb9bb2.jpg) ![](https://static.platzi.com/media/user_upload/image-6ff0365d-b5bb-40bb-a1ae-88c2bff667e0.jpg)![](https://static.platzi.com/media/user_upload/image-994345f9-5ec1-4f9c-b171-2ed45553832c.jpg) ![](https://static.platzi.com/media/user_upload/image-82482f46-a80d-4335-8d44-f2090c00bce3.jpg)

Con expresiones regulartes RegExp se puede solucionar el problema usando la etiqueta ‘i’ que ignora las mayusculas y las min[unsculas

const searchedTodo = todos.filter((todo) =>
    RegExp(`.*${searchValue}.*`, "i").test(todo.text)
  );

para lograr el reto de mostrar lo que el usuario busca:
primero realice un estado derivado en donde utilice el estado searchValue


const filter=todos.filter(
    (todo)=> todo.text.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase())

posteriormente simplemente utilice el operador ternario para mostrar toda la lista si searchvalue es “” y si contiene algo use filter.map en ves de defaultodos.map

<TodoList>
 {searchValue === '' ? (

   todos.map((todo) => (
     <TodoItem key={todo.text} text={todo.text} completed={todo.completed} />
   ))
 ) : (

   filter.map((todo) => (
     <TodoItem key={todo.text} text={todo.text} completed={todo.completed} />
   ))
 )}
</TodoList>

Cada vez que juan dice pasito a pasito inevitablemente digo suave suavecito 😂😂😂

Les comparto mi solución al reto. No la mires sin antes intentarlo!
Como podemos ver, lo que hice fue mandarle al ToDoItem el index del ToDo en cuestión para saber cual debemos eliminar y para saber a cual cambiarle el atributo ‘completed’. Lo pensé así debido a que si lo hacemos por el nombre se podrían eliminar todos los ToDo’s con el mismo nombre, lo cual suena razonable pero quizá sea mejor darle la libertad al usuario de tener ToDo’s iguales.


App.js:


ToDoItem.js:


Cualquier oportunidad de mejora bienvenida 😀

Uno de los retos completados

// Funcion para marcar los jutsus completados
  const completeTodo = (id) => {
    const todoIndex = todos.findIndex((todo) => todo.id === id);
    const newTodos = [...todos];
    newTodos[todoIndex].completed = true;
    saveTodos(newTodos);
  };

  const  getTodo = (id) => {
    const todoIndex = todos.findIndex((todo) => todo.id === id);
    return todos[todoIndex]

  }

Me parece genial esto de los retos, tenemos que conseguir la manera, probablemente lo hacemos funcionar y luego comparamos a ver si había (de seguro) mejores maneras de hacerlo.

Lo abordé con estas dos funciones. Pasando tanto los todos como la función setTodos a cada TodoItem.

Vamos a ver si así era 👀

Hola a todos, este es mi aporte. Para resolver el requerimiento creé dos funciones que reciben como parámetro el elemento a eliminar o marcar como completada respectivamente, esto en el archivo App.js, y pasé estas funciones como props del TodoItem, en el TodoItem, agregué el onClick y en el cuerpo de la función llamé a las funciones en las props y les pasé el texto del item, que de igual manera paso en las props.

Archivo App.js

const itemDeleteHandler = (value) => {
       const result = todos.filter((item) => {
            return item.text !== value;
       });

       setTodos(result);
  }

  const itemDoneHandler = (value) => {
       const result = todos.map((item)=>{
            return item.text === value ? {text : item.text, completed: true} : item;
       })

       setTodos(result);
  } 
 <TodoList>
        {searchedTodos.map(todo => {
          return <TodoItem 
                  key={todo.text} 
                  text={todo.text}
                  completed={todo.completed}
                  ondelete={itemDeleteHandler}
                  oncomplete={itemDoneHandler}/>
        })}
      </TodoList>

Archivo TodoItem.js

function TodoItem(props){
    return (
          <li>
               <div className="item-container">
                <span onClick={() => {
                        props.oncomplete(props.text);
                }
                }>
                    <BsCheck2 color={props.completed ? "#198754" : "gray"} size={24} strokeWidth={2}/>
                </span>
                <p className={props.completed ? `text-completed` : ``}>{props.text}</p>
                <span onClick={() => { 
                  props.ondelete(props.text);
                }}>
                  <BsFillTrashFill color="#dc3545" size={20} id="btn-delete"/>
                </span>
              </div>
          </li>

    );
 }

Espero se entienda mi explicación. Este es mi respositorio de github:

Pd: Solo puse el código que cambie para que no sea dificil de leer.

Para solucionar el reto propuesto al final de la clase para hacer check, uncheck y delete a los TO DO’s hice para cada boton una funcion que se active en el metodo onClick.

Desde onClick le paso una variable para identificar el boton.

<span className="icons close" onClick={(event)=>{
          const message = event.target.parentElement.childNodes[2].textContent
          console.log('eliminar');
          deleteTodo(message)
        }}>
</span>

Haciendolo de esta manera puedo saber cual texto de cual to do le pertenece a cual boton segun fue clicado. El numero 2 en childNodes es porque en la estructura de mi contenedor padre el elemento [2] es el parrafo que contiene el texto de cada todo.

Luego la funcion vendria siendo la siguiente

  function checkTodo(checkThis){
    const updatedTodos=[]
    props.todos.forEach((todo)=>{
      if(todo.text===checkThis){
        todo.completed=true
        updatedTodos.push(todo)
      }else{
        updatedTodos.push(todo)
      }
    })
    props.setTodos(updatedTodos)
  }

Alli creo un array vacio, y luego evaluo el estado de todos que pase por props, y evaluo para cada todo si su propiedad text es igual a checkThis (que es el mensaje asociado al boton clicado), si es igual quiero que me le cambie la propiedad completed a true y me lo meta al nuevo array y los todo que no cumplan la condicion tambien quiero que me los meta al nuevo array.

Finalmente actualizo el estado con el nuevo Array.

Tengo un boton de uncheck asi que seria exactamente igual pero cambiando la propiedad completed a false y para el boton de delete en el nuevo array metes todos los todos exceptuando el que cumpla la condicion, que seria la misma condicion en la funcion que les mostre

Me encanta que el maestro aparezca en medio del código
## 🦄✨La manera en la que implementé la eliminación de un Todo, es enviando como prop el estado de los Todos a cada TodoItem así: ![](https://static.platzi.com/media/user_upload/image-cc5856c2-0758-4a56-8ec8-c6cdef2cb8cb.jpg) Para luego, en el componente ToDoItem, filtrar a todos los ToDo's, para que en el Array de ToDo's estén todos los ToDo's, excepto aquel que tiene un texto igual al ToDo que queremos eliminar ;) ![](https://static.platzi.com/media/user_upload/image-02c32e7d-dd01-42f3-855a-25b036c6a2cf.jpg)
Yo resolví la búsqueda haciendo uso de expresiones regulares y para ello hago uso de `RegExp` ya que con esto se evita el uso del `.toLowerCase()` . Lo que se hace es lo siguiente: 1. Definir la expresión regular acorde con el `searchValue` agregando el parámetro `'i'` para indicar que sea *case insensitive*. 2. Filtrar el arreglo de tasks (to do's) evaluando la expresión regular con cada `text` del arreglo. 3. Calcular las tareas completadas y las totales, a partir de los to do's filtrados, para también se actualicen cada vez que se hace una búsqueda. Aquí el código: `const regex = new ``RegExp```(searchValue, 'i'); // 'i' flag for case-insensitive search // console.log(`Regex: ${regex}`);`` `const filteredTasks = tasks.filter(``taskObject`` => regex.test(``taskObject```.text)); // console.log(`Filtered tasks: ${JSON.stringify(filteredTasks)}`);`` `const completedTasks = filteredTasks.filter(``task`` => ``task``.completed).length;` `const totaltasks = filteredTasks.length;`

Hello, comparto mi solucion:
Dentro del componente, TodoList, he creado un metodo llamado “deleteItem” el cual recibe “text” que vendria siendo el texto del item a eliminar. Hago unfilter para agregar a un nuevo array los elementos que no coincidan con tal text, y luego actualizo el estado del componente:

function deleteItem(text){

     const estadoDerivado = todos.filter(todo=>todo.text !== text);
      console.log('Deleting: ' + text);
     setTodos(estadoDerivado);
         
   }

En la lección anterior, exploramos cómo transmitir datos entre componentes, específicamente entre componentes padres e hijos. Además, aprendimos a crear estados derivados, los cuales, a partir de un estado inicial, nos permiten realizar cálculos o filtrados para enviar información relevante a otros componentes de la aplicación.

Introducción a la Búsqueda de TODOs

En esta clase, abordamos un nuevo desafío: implementar un sistema de búsqueda en nuestra aplicación de lista de todos. El objetivo es permitir a los usuarios encontrar rápidamente los elementos de la lista que coincidan con su consulta.

Implementación de la Búsqueda de TODOs

Para lograr esto, introdujimos un nuevo estado derivado llamado searchTodos. Este estado se crea mediante el método filter de los Arrays y se basa en el estado searchValue. Cada elemento en la lista original (todos) se evalúa para determinar si su texto incluye el valor de búsqueda, independientemente de las mayúsculas o minúsculas.

const searchedTodos = todos.filter(todo => {
  const todoText = todo.text.toLowerCase();
  const searchText = searchValue.toLowerCase();
  return todoText.includes(searchText);
});

Consideraciones sobre Mayúsculas y Minúsculas

Abordamos un problema común al realizar búsquedas: la distinción entre mayúsculas y minúsculas. Para superar esta limitación, convertimos tanto el texto de cada todo como el valor de búsqueda a minúsculas durante la comparación. Esto garantiza que la búsqueda sea insensible a mayúsculas y minúsculas, proporcionando una experiencia más flexible y amigable para el usuario.

const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();

Optimización del Código

Para mejorar la legibilidad y mantener un código organizado, introdujimos variables intermedias (todoText y searchText) que representan el texto convertido a minúsculas. Esta práctica facilita la comprensión del código y su mantenimiento a largo plazo.

const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);

Desafío: Completar y Eliminar TODOs

Como desafío adicional, se plantea la tarea de implementar la funcionalidad de completar y eliminar todos. Cada todo debe tener la capacidad de ser completado o eliminado de manera independiente. Se alienta a los estudiantes a experimentar con el uso del actualizador de estado setTodos para lograr esta funcionalidad.

Próximos Pasos

  • Experimenta con la implementación de la funcionalidad de completar y eliminar todos.
  • Explora cómo transmitir el actualizador de estado setTodos a los componentes hijos para permitir interacciones específicas de cada todo.
  • Comparte tus experiencias y resultados en los comentarios para fomentar el aprendizaje colaborativo.

En la próxima clase, guiaremos paso a paso la solución a este desafío y exploraremos cómo completar y eliminar todos de manera efectiva. ¡Nos vemos allí!

yo lo logre de esta manera: ```js <TodoList> { defaultTodos.filter(todo => todo.text.toLowerCase().includes(searchValue.toLowerCase())).map(todo => ( <TodoItem key={todo.text} text={todo.text} completed={todo.completed} onComplete={() => alert(todo.text)} onDelete={() => alert('delete')} /> )) } </TodoList> ```
solo yo estoy teniendo problemas con la preproducción de las clases?? se queda colgado el video cada 10 segundos!!!

No voy a crear controversia, pero llevo ya 3 años trabajando con VUE profesionalmente y realmente en developer experience esta en otro nivel de React, un poco más legible el código. Sin embargo puedo apreciar porque React sigue tan posicionado. Personalmente quiero ver que onda con React y aprenderlo, porque no, pero hasta el momento no he visto una feature que me haga decir como, juemadre me cambio.

me hubiera gustado que no todo sea con un objeto defaultTodos y arrays sino interactuar con un api REST de verdad

Traté de aplicar el ereto final sobre borrar toDo pero no sé porque solo se queda el elemento que borré y en consola claramente se ve que solo quedan los otros toDos 😛

Sorry profe le fallé 😦

Ala… algo en mi mente… :S
genial!!!