No tienes acceso a esta clase

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

Resolviendo los retos de StorageEventListener

16/19
Recursos

Aportes 36

Preguntas 4

Ordenar por:

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

Con mi solución CSS, busqué crear una Modal para atrapar la absoluta atención del usuario, con un fondo “casi rojo” para dar a entender que hay una alerta y un botón que refleja la intención de la misma, además mientras la alerta está abierta, el usuario no puede interactuar con el resto de la app. 😄

Codigo usado por el profesor:

function ChangeAlert({ show, toggleShow}) {
    if(show){
        return (
            <div className="ChangeAlert-bg">
                <div className="ChangeAlert-container">
                <p>Parece que cambiaste tus TODOs en otra pestaña o ventana del navegador.</p>
                <p>¿Quieres sincronizar tus TODOs?</p>
                <button
                    className="TodoForm-button TodoForm-button--add"
                    onClick={toggleShow}
                >
                    Yes!
                </button>
                </div>
            </div>
        );
    } else{
        return null;
    }
}

codigo del css:

.ChangeAlert-bg {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: #1e1e1f50;
    z-index: 2;
}

.ChangeAlert-container {
    position: fixed;
    bottom: -10px;
    left: -10px;
    right: -10px;
    padding: 34px;
    background-color: #1e1e1f;
    color: #f2f4f5;
    z-index: 2;
    text-align: center;
    font-size: 20px;
}

.ChangeAlert-container button {
    margin-top: 16px;
    margin-bottom: 16px;
}

Muestro cómo quedó mi modal para alertar a los ususarios. Lo que hice fue crear un estado de ModalType para que permitiera desplegar en el mismo modal usando las mismos estados para modal de antes 👇

Es poco pero es trabajo honesto

En lo personal no me gusta que de repente desaparezcan los TODOs, Yo los hice casi inutilizables agregándoles un Pseudo elemento con css y una opacidad del 25% para dar a entender que no se pueden usar hasta que se carguen los cambios.

Algo que si no me gusto es la notificación que ti agregaste, me parece que no va concorde al diseño de la aplicación, usar el negro de fondo para el div me parece que hace demasiado contraste, en lo personal prefiero usar el mismo color de fondo de la app.

Por un momento pensé que diría “Cocinar los gatos” jaja. Menos mal que fueron lentejas.

Yo no lo hice como un modal, me acordé de como lo hace twitter y me animé a hacerlo así:

Yo hice un modal:

En App() le pase setOpenModal

<ChangeAlertWithStorageListener
        synchronize={synchronizeTodos}
        setOpenModal={setOpenModal}
/>

El Wrapper en la HOC así:

<WrapperComponent
            {...props}
            show={storageChange}
            toggleShow={toggleShow}
/>

Y el componente ChangeAlert recibió setOpenModal en props:

const ChangeAlert = ({ show, toggleShow, setOpenModal }) => {
    const handleClick = () => {
        toggleShow()
        setOpenModal(false)
    }
    if (show) {
        return (
            <>
                <Modal>
                    <ChangeAlertForm onClick={handleClick} />
                </Modal>
            </>
        )
    }
    return null
}

No me gusta lo de preguntarle al usuario si quiere refrescar o no, me gusta mas lo responsivo que se siente la app cuando se actualiza automáticamente por lo que me ideé una solución. Use el useStorageListener como custom Hook en useLocalStorage y asigne el valor de storageChange al useEffect en useLocalStorage como segundo paramentro, de esta forma, cada que haya un cambio en el estado storageChange se va a ejecutar, todo esto me ahorro el ChangeAlert y pasar estados a los demás componentes,
y al inicio del useEffect en useLocalStorage se pone setLoading(true) y quedaria funcionando de está manera

Les muestro como quedo mi mensaje de actualización.
![](

Sigo sin poder ver nada en el navegador, he visto todo el curso así. 😥😥
.

Reutilicé el div modal para solo integrar un nuevo div con la alerta de refresh. En lo personal pasé el synchronizedItem hasta TodoList y utilicé ese estado para no mostrar con css la lista, pero creo que la solución de solo ocupar los estados loading y error son más óptimas.

Esta fue mi solución CSS ![](

lo que yo hice para poder eliminar a la hora de cargar fue hacer una funcion que llame deleteRenderItems a la cual le pase un array vacio ( [ ] ) a la funcion setItems quedando de la siguiente manera.

import React from "react";

function useLocalStorage(itemName, initialValue) {
  const [item, setItems] = React.useState(initialValue);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(false);
  const [sincronizedItem, setSincronizedItem] = React.useState(true);

  React.useEffect(() => {
    setTimeout(() => {
      try {
        const localStorageItem = localStorage.getItem(itemName);
        let parsedItem;

        if (!localStorageItem) {
          localStorage.setItem(itemName, JSON.stringify(initialValue));
          parsedItem = initialValue;
        } else {
          parsedItem = JSON.parse(localStorageItem);
        }
        setItems(parsedItem);
        setLoading(false);
        setSincronizedItem(true);
      } catch (error) {
        setError(error);
      }
    }, 500);
  }, [sincronizedItem]);

  const saveItem = (newItem) => {
    try {
      const stringifiedItem = JSON.stringify(newItem);
      localStorage.setItem(itemName, stringifiedItem);
      setItems(newItem);
    } catch (error) {
      setError(error);
    }
  };
  const sincronizeItem = () => {
    setLoading(true);
    setSincronizedItem(false);
  };
  const deleteRenderItems = () => {
    setItems([]);
  };
  return {
    deleteRenderItems,
    item,
    saveItem,
    loading,
    error,
    sincronizeItem,
  };
}

export { useLocalStorage };

y utilice lo pase por render props a nuestro HOC quedando asi .

import React from "react";

function withStorageListener(WrappedComponent) {
  return function WrappedComponentWithStorageListener(props) {
    const [storageChange, setStorageChange] = React.useState(false);
    window.addEventListener("storage", (change) => {
      if (change.key === "TODOS_V1") {
        console.log("Hubo cambios en TODOS_V1");
        setStorageChange(true);
      }
    });
    const toggleShow = () => {
      props.sincronize();
      setStorageChange(false);
      props.deleteRenderItems();
    };

    return <WrappedComponent show={storageChange} toggleShow={toggleShow} />;
  };
}

export { withStorageListener };

Para el reto de css use el mismo modal de la clase pero modifique el boton para que se viera como uno de recarga:

Para ellos use el caracter “clockwise open circle arrow” en su codigo hex:

HTML:

<button className="SyncTodosButton" onClick={toggleShow}>
	&#x21bb;
</button>

Y le añadi una pequeña animacion para que diera el efecto de que debia ser pulsado, y un efecto de hover.

CSS:

.SyncTodosButton {
    background-color: #61DAFA;
    box-shadow: 0px 5px 25px rgba(97, 218, 250, 0.5);
    border: none;
    border-radius: 50%;
    cursor: pointer;
    font-size: 50px;
    font-weight: bold;
    color: #FAFAFA;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 64px;
    width: 64px;
    transform: rotate(0);
    transition: .5s ease;
    animation: 2s infinite changeAnimation;
}

.SyncTodosButton:hover {
    transform: rotate(360deg);
    animation: none;
}

@keyframes changeAnimation {
    25% {
        box-shadow: 0px 5px 25px 5px rgba(97, 218, 250, 0.5);
        background-color: #59c9e7;
    }
    37.5% {
        box-shadow: 0px 5px 25px 10px rgba(97, 218, 250, 0.5);
        background-color: #44b7d6;
    }
    50% {
        box-shadow: 0px 5px 25px 15px rgba(97, 218, 250, 0.5);
        background-color: #44b7d6;
    }
    62.5% {
        box-shadow: 0px 5px 25px 10px rgba(97, 218, 250, 0.5);
        background-color: #44b7d6;
    }
    75% {
        box-shadow: 0px 5px 25px 5px rgba(97, 218, 250, 0.5);
        background-color: #59c9e7;

    }
    100% {
        box-shadow: 0px 5px 25px rgba(97, 218, 250, 0.5);
        background-color: #61DAFA;

    }
}

Ta potente React.
Page
Code

yo hice una pantalla bien intrusiva como con el modal.
Mientras la gente no entre desde inspeccionar y lo borre, no hay problema
![](

Antes de esta clase intenté quitar los To Do’s para que no se vieran durante la carga pero lo hice diferente.
En el useLocalStorage.js > función para sincronizar que le puse “sync” en vez de “syncronize” como le puso el profe

const sync = () => {
	setItem([]);
	setLoading(true);
	setSincronize(false);
}

O sea, aparte del setLoading y setSincronize, reutilicé el setItem que devolvía el array parseado y le puse que durante la carga esté vacío.
.
Hay alguna desventaja respecto a la propuesta del profe?

Hasta ahora llevo el proyecto así: https://jocad7.github.io/to-do-project/

No me enfoque tanto en el css.

Creo que todos tuvimos la misma idea jajaja Cool!

Qué tal si hacemos que los Todos se actualicen automáticamente, sin necesidad de ningún botón? Me parece más User Friendly, a todos nos gusta la automatización.

Muy buenos estos patrones de diseño de React!

Agregue el Z-Index para no acceder al boton de add Todos

Solo realize esto y ya se quitan los otros TODOs

un pequeño aporte: En react 18 ya no es necesario el boton de actualizar lo hace automatico

Mi solución para ocultar la lista fue agregar en el hock de useStorageTodo dentro de la función sincroniced agregué setTodo([ ])
Mi solución: ![](https://static.platzi.com/media/user_upload/image-210608bc-6355-49b4-8653-6fb1b392e344.jpg)
De mi parte, me fui sobre el componente index.js/TodoList. Puse una lógica para que detectara si el componente seguía cargando, pusiera un ReactComponent vacío, y si había cargado ya, desplegara la lista, pero ¿qué es más eficiente? Este es mi código: ```js import React from 'react'; import './TodoList.css' function TodoList(props) { const renderFunc = props.children || props.render; console.log(`loading ${props.loading}`) return ( <> { props.loading ? ( <> ) : ( <section className='TodoList-container'> {props.error && props.onError()} {props.loading && props.onLoading()} {(!props.loading && !props?.totalTodos) && props.onEmptyTodos()} {(!!props.totalTodos && !props?.searchedTodos.length) && props.onEmptySearchResults(props.searchText)} {/* {props.searchedTodos.map(todo => (props.render(todo)))} */} {/* {props.searchedTodos.map(props.render)} */} {/* {props.searchedTodos.map(props.children)} */} {props.searchedTodos.map(renderFunc)}
    {props.children}
</section> ) } ); } export { TodoList }; ```

Resolviendo los retos de StorageEventListener

.
Para el primer reto, lo primero que se hizo fue modificar la estructura del componente ChangeAlert con más contenido, clases y estilos. Lo más destacable de los estilos css es el z-index: 2; que impide que mientras se muestre la alerta se puedan modificar o agregar TODOs.
.

// ChangeAlert/index.js
import React from 'react';
import { withStorageListener } from './withStorageListener';
import './ChangeAlert.css';

function ChangeAlert({ show, toggleShow }) {
  if (show) {
    return (
      <div className="ChangeAlert-bg">
        <div className="ChangeAlert-container">
          <p>Parece que cambiaste tus TODOs en otra pestaña o ventana del navegador.</p>
          <p>¿Quieres sincronizar tus TODOs?</p>
          <button
            className="TodoForm-button TodoForm-button--add"
            onClick={toggleShow}
          >
            Yes!
          </button>
        </div>
      </div>
    );
  } else {
    return null;
  }
}

const ChangeAlertWithStorageListener = withStorageListener(ChangeAlert);

export { ChangeAlertWithStorageListener };
.ChangeAlert-bg {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: #1e1e1f50;
  z-index: 2;
}

.ChangeAlert-container {
  position: fixed;
  bottom: -10px;
  left: -10px;
  right: -10px;
  padding: 34px;
  background-color: #1e1e1f;
  color: #f2f4f5;
  z-index: 2;
  text-align: center;
  font-size: 20px;
}

.ChangeAlert-container button {
  margin-top: 16px;
  margin-bottom: 16px;
}

.
Finalmente para resolver el segundo reto, antes en el componente TodoList no teníamos una validación adecuada para renderizar los TODOs, simplemente era {props.searchedTodos.map(renderFunc)} por lo que ahora validamos que si no hay estado de carga, ni estado de error entonces renderizamos los TODOs.
.

function TodoList(props) {
  const renderFunc = props.children || props.render;
  
  return (
    <section className="TodoList-container">
			...
      {(!props.loading && !props.error) && props.searchedTodos.map(renderFunc)}
    </section>
  );
}
La parte de no mostrar la lista mientras se recarga la resolví volviendo a poner a "item" en su "initialValue", en este caso \[], al ejecutar la función "synchronizeItem". De esta forma si no hay nada... entonces no hay nada que mostrar. ![](https://static.platzi.com/media/user_upload/image-6932732f-2001-4d7f-8708-9b10a751e398.jpg)
   {!props.loading && !props.error && props.searchedTodos.map(renderThis)}      

Codigo TypeScript de la clase:

import { FC } from 'react'
import { withStorageListener } from "./withStorageListener"
import './ChangeAlert.css'

interface Props {
    show: boolean,
    toggleShow: ()=>void,
    sincronizeTodos:()=>void
}

const ChangeAlert:FC<Props> = ({
    show,
    toggleShow,
    sincronizeTodos
})=> {

    if(show){
        return(
            <div className='ChangeAlert-bg'>
                <div className='ChangeAlert-container'>
                    <p>Parece que cambiaste tus TODOs en otra pestaña o ventana del navegador.</p>
                    <p>¿Quieres sincronizar tus TODOs?</p>
                    <button
                        className="TodoForm-button TodoForm-button--add"
                        onClick={()=>{
                            toggleShow();
                            sincronizeTodos();
                        }}
                    >
                        Yes!
                    </button>

                </div>
            </div>
        )
    } else {
        return null
    }
}

const ChangeAlertWithStorageListener = withStorageListener(ChangeAlert);

export { ChangeAlertWithStorageListener }
.ChangeAlert-bg {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: #1e1e1f50;
    z-index: 2;
}

.ChangeAlert-container {
    position: fixed;
    bottom: -10px;
    left: -10px;
    right: -10px;
    padding: 34px;
    background-color: #1e1e1f;
    color: #f2f4f5;
    z-index: 2;
    text-align: center;
    font-size: 20px;
}

.ChangeAlert-container button {
    margin-top: 16px;
    margin-bottom: 16px;
}

import { FC, useEffect, useState } from "react"

function withStorageListener<T>(
    WrappedComponent: FC<T>
) {
    return function WrappedComponentWithStorageListener(props:Omit<T, "show" | "toggleShow">){
        
        const [storageChange, setStorageChange] = useState(false);

        useEffect(()=>{

            const functionHandler = (change: StorageEvent)=>{
                if(change.key=== "TODOS_V1"){
                    console.log('Hubo cambios en TODOS_V1');
                    setStorageChange(true)
                }

            }

            window.addEventListener('storage', functionHandler)

            return ()=>{
                window.removeEventListener('storage', functionHandler)
            }

        }, [])

        const toggleShow = ()=>{
            setStorageChange(false)
        }

        return (
            <WrappedComponent 
                {...(props as T)}
                show={storageChange}
                toggleShow={toggleShow}          
            />
        )
    }   
}

export { withStorageListener }

En mi caso decidí que al haber un cambio de una vez actualice los ToDos:

useEffect(() => {
		initialize();

		const onChange = (change: StorageEvent) => {
			console.log({ change });
			if (change.key === "todos") {
				const newTodos = JSON.parse(change.newValue || "[]");
				dispatch({ type: "UPDATE_TODOS", payload: newTodos });
			}
		};

		window.addEventListener("storage", onChange);

		return () => {
			window.removeEventListener("storage", onChange);
		};
	}, []);

Así quedo mi advertencia con un “modal” en toda la pantalla visible del navegador 😄