No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Completando el StorageEventListener

15/19
Recursos

Aportes 41

Preguntas 14

Ordenar por:

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

o inicia sesi贸n.

Les doy la bienvenida a una nueva edici贸n de鈥
馃寛 Encuentra el error del profesor 馃寛
Estrellita a la primera persona que lo resuelva. 馃槈

.

馃憠 驴Cu谩l es el error con el c贸digo de esta clase?

  • Pista #1: est谩 relacionado a rendimiento
  • Pista #2: est谩 relacionado a los efectos

.

Pero 驴Juan, cu谩les efectos?
Exacto, 驴cu谩les efectos? Ese es el problema鈥

.

Respuestas aqu铆 en los comentarios 馃憞

Aporte respecto al event listener "storage" (addEventListener(鈥榮torage鈥)):

No funcionar谩 en la misma p谩gina que realiza los cambios; en realidad, es una forma de que otras p谩ginas del dominio usen el almacenamiento para sincronizar los cambios que se realicen. Las p谩ginas de otros dominios no pueden acceder a los mismos objetos de almacenamiento.

Esto lo saqu茅 de MDN
![](

Para que no aparezca los todos mientras est谩n sincroniz谩ndose

{!props.loading && props.searchedTodos.map(renderFunc)}

Estuve lidiando mucho rato con un concepto que entend铆 mal. Ya que no me aparec铆a el mensaje de cambio, pero era porque solo estaba usando una ventana. El evento se dispara cuando se modifica el local storage en el contexto de otro documento. Les dejo enlace por si les pasa igual. En resumen, tienen que tener dos pesta帽as abiertas
M谩s informaci贸n

Se que este no es un curso de ingl茅s, pero en vista de que nadie lo comento y que esta es una plataforma de aprendizaje:
Sincronizar --> Synchronize (verbo)
Sincronizado --> Synchronized (adjetivo)
Sincronizaci贸n --> Sync (sustantivo)

Para recargar la p谩gina en onClick llame 煤nicamente window.location.reload() y al recargar la ya no hac铆a falta toggleShow porque por defecto se volv铆a a false.

<button  onClick={() => {window.location.reload();}}>

Se crea css para mostrar el boton de refresh y se pasa a una ventana modal

Con respecto a como se soluciono el hecho que no se mostrara el listado y solo el skeleton loading queda asi mi solucion

import "./todoList.css";
const TodoList = ({
  children,
  error,
  loading,
  showEmptyTodos,
  showRender,
  showEmptySearchResults,
  onError,
  onLoading,
  onEmptyTodos,
  onEmptySearchResults,
  render
}) => {
  return (
    <section className="todolist-container">
      {error && onError()}
      {loading && onLoading()}
      {showEmptyTodos && onEmptyTodos()}
      {showEmptySearchResults && onEmptySearchResults()}
      {showRender && render()}
      <ul>{children}</ul>
    </section>
  );
};

export default TodoList;

Repo

Project TODOs

Utilice window.location.reload() y me ahorre todo esto jajaja

El reto es bastante sencillo鈥 bueno para m铆 qu茅 tengo experiencia animando cosas con css y js. El 煤nico problema est谩 en que firefox no permite hacer un drop image en este contendor 馃槮 no lo podr谩n ver.

Lo 煤nico que hice es agregar condiciones que involucren clases para ciertos elementos que ser谩n afectados con los estados de la p谩gina

Como pueden ver aqu铆, solo agregu茅 como dependencia del elemento 茅l prop.loading

Aqu铆 se ve el uso que le doy, si la p谩gina est谩 cargando, agrego una opacidad en los items y posicione un pseudo-elemento para evitar el uso de los items

<li className={`TodoItem ${props.loading? 'TodoItem-Loading' : ''}`}>

Sin necesidad de modificar el c贸digo que tenemos y sin cambios bruscos al momento de carga la nueva informaci贸n

Quiz谩s cuando termine el curso deje el repositorio para que lo vean

Yo lo que hice para el reto de al final fue pasarle al TodoItem la propiedad loading

<TodoItem
                key={todo.text}
                text={todo.text}
                loading={loading}
                completed={todo.completed}
                onComplete={() => completeTodo(todo.text)}
                onDelete={() => deleteTodo(todo.text)}
/>

despu茅s en TodoItem checo si loading es true y si es true le agrego la clase TodoItem鈥搇oading

<li className={`TodoItem ${!!props.loading && 'TodoItem--loading'}` }>
      <CompleteIcon 
        completed={props.completed}
        onComplete={props.onComplete}
      />
      <p 
      className={`TodoItem-p ${props.completed && 'TodoItem-p--complete'}`}>
        {props.text}
      </p>
      <DeleteIcon
      onDelete={props.onDelete}
      />
    </li>

lo que hace esa clase es hacerle un display none a los items

 .TodoItem--loading{
    display: none;
  }

y tada!! ya no aparecen los todosmientras se sincronizan los todos

Soluci贸n a los retos:
Para que al cargar no mostrar los TODOs:

import './TodoList.css'

function TodoList(props) {
    return (
        <section className="TodoList-container">
            {props.error && props.onError()}
            {props.loading && props.onLoading()}
            {(!props.loading && !props.totalTodos) && props.onEmpty()}
            {(Boolean(props.totalTodos) && !props.searchedTodos.length) && props.onEmptySearchResults(props.searchText)}

            <ul>
                {!props.loading && props.searchedTodos.map(props.render || props.children)}
            </ul>
        </section>
    )
}

export { TodoList }

En cuanto a no dejar modificar hasta que se actualice:

Agregaria en useLocalStorage:

const [canChange, setCanChange ] = React.useState(true);

Y luego lo acomodo a los diferentes componentes:

function CreateTodoButton({setOpenModal,canChange}) {
    const onClickButton = () =>{
        setOpenModal(prevState=>!prevState);
    }

    return (
        <button 
        className="CreateTodoButton"
        onClick={onClickButton}
        disabled={!canChange}
        >+</button>
    )
}

export { CreateTodoButton }

Si les queda dudas me lo piden y agrego los detalles en los comentarios de este comentario jejeje

Para que no aparezcan mientras se actualiza, prefer铆 afectar de una vez la funci贸n Render, en mi Render Props:

	render={todo => (
            (sincronizedTodos) ?
              <TodoItem 
                key={todo.text} 
                text={todo.text} 
                completed={todo.completed} 
                color={todo.color} 
                todoComplete={() => todoComplete(todo.text)} 
                todoDelete={() => todoDelete(todo.text)}
              />
              : null
          )
import React from 'react';
import { withStorageListener } from './withStorageListener';
// Se importa el HOC



//Componente
function Changealert({show,toggleShow,setItem}){

    if(show){  //Si show es verdadero
        return (

            <div>
                 <p>Hubo Cambios</p>
                 <button onClick={()=>{
                     
                     toggleShow(false);

                     const localStorageItem = localStorage.getItem('TODOS_V1');
                     const parsedItem = JSON.parse(localStorageItem);         
                     setItem(parsedItem)
                     
                     }}>Volver a cargar la informaci贸n</button>
                
            </div>

        );

    }else{
            
        return null;

    }

       

}

const ChangeAlertWithStorageListener=withStorageListener(Changealert);
//Se crea una variable , y envuelve en el HOC componente Changealert


export {ChangeAlertWithStorageListener};
//Se exporta este vendria siendo el componente de verdad

No se que tan malooo sea hacer esto, pero toda la parte de la sincronizaci贸n la veo m谩s sencilla de hacerla asi simplemente:

function useLocalStorage(itemName, defaultValue = [], withSync = true) {

    const [sync, setSync] = React.useState(false)
    const [loading, setLoading] = React.useState(true);
    const [items, setItems] = React.useState(defaultValue);
    const [error, setError] = React.useState(false);

    if (withSync) {
        window.addEventListener("storage", () => {
            setSync(sync + 1);
        });
    }

Si esto genera problemas de optimizaci贸n o se considera una mala practica, pues creo que ahi esta el detalle, pero si no para mi queda de lujo 馃槀

para recargar la informacion simplemente llame a location.reload(); en el onClick

despu茅s de ver la soluci贸n del profe, pienso que es mejor si simplemente ponemos al useEffect a dispararse cuando haya un cambio en la variable Item, as铆 las paginas se sincronizan sin que les tengamos que decir nada al usuario

Sobre la api window storage event, aqu铆 la documentaci贸n de MDN:

addEventListener('storage', (event) => { });
onstorage = (event) => { };

Para el cleanup del Listener:

    useEffect(() => {
        window.addEventListener('storage', onStorageChange)
        return () => window.removeEventListener('storage', onStorageChange)
    }, [])

Aunque qued茅 con la duda de si la dependencia del useEffect deber铆a ser [] o [onStorageChange].

Adicionalmente, supongo que en caso de usar la api con window.onstorage se puede hacer cleanup con algo como:

return () => { window.onstorage = null };

Excelente clase馃檶馃檶

Como creamos la funci贸n toggleShow, ya necesitamos pasarle el argumento false a la funci贸n toggleShow en el onClick:

<div>
        <p>Hubo cambios</p>
        <button onClick={toggleShow}>
          Volver a cargar la informaci贸n
        </button>
</div>

Estuve como 2 horas sufriendo porque no se estaba renderizando el 鈥淗ubo cambios鈥 馃槪馃槶 y ya estaba en modo:

Bueno, andaba en esas, y ya estaba escribiendo mi pregunta bien detallada para los compa帽eros de Platzi y mientras lo hacia pens茅 en probar algo en mi c贸digo y funcion贸!!! ya me aparece incluso el bot贸n de recargar 馃コ馃槑

Me sent铆 como si estuviese hablando al patito de hule pero virtual xD

En resumen, creo que la raz贸n por la que me estaba dando errores en el c贸digo y no me aparec铆a el 鈥渉ubo cambios鈥 es porque cre茅 mi proyecto con Vite, por ende, los archivos dentro de la carpeta ChangeAlert los tuve que nombrar con 鈥.jsx鈥 y eso, a su vez, me oblig贸 a cambiar algunos nombres de las funciones dentro de estos archivos鈥 Luego de renombrarlas comenz贸 a funcionar todo bien 馃槂

Mi c贸digo en GitHub: TodoApp

Momento fuera de contexto: 4:51
Jaja

Yo hice esto:

                <button onClick={()=>{
                    toggleShow(false)
                    window.location.reload(true)
                    }}>
                    Actualizar
                </button>

Pero no creo que sea la soluci贸n m谩s elegante jaja

Para ocultar los todos, realize esto, me funcion贸, pero no s茅 si era lo esperado jaja

Lo Mejore. Pero鈥 no supe resolverlo desde el App.js

function ChangeAlert({show, toggleShow}){
    if(show){
       return (
        <Modal>
            <div className={'ChangeAlert shadow'}>
            <h2 className={'textShadow'}>Hubieron cambios</h2>
            <button className={'shadow '}
            onClick={()=>toggleShow(false)}
            >Volver a cargar Todos</button>
        </div>
        </Modal>
       );
    }

MI soluci贸n al reto y no se si afecte el tema de performance

En useLocalStorage exporte el estado de syncronizedItem,
para manejarlo en useTodos y con eso validar para que realice el render de los TodoItem

todo => syncronizedTodo ? 
					<TodoItem
						key={todo.id}
						completed={todo.completed}
						task={todo.task}
						onClickCompleteTodo={() => onClickCompleteTodo(todo.id)}
						onClickDelete={() => onClickDelete(todo.id)}
						onClickEdit={() => onClickEdit(todo.id)}
					/>
					:
					<TodoLoading/>
			}

Aprovechando el componente Modal que ya teniamos; lo us茅 en el componente ChangeAlert (Aparte de los estilos de este componente)
As铆 que cuando el usuario quiera darle 鈥淒esea actualizar el componente鈥 No recarge la p谩gina.
No se si de pronto me confundo pero tengo entendido que React e no necesita actualizar la p谩gina para ver las actualizaciones m谩s recientes.

const ChangeAlert = ({ show, toggleShow }) => {
	if (show) {
		return (
			<Modal kind={true}>
				<div className={style.altertContainer}>
					<h2>There is changes in localStorage</h2>
					<button 
						onClick={() => toggleShow(false)}
						className={buttonStyle.standardButton}
					>
						Reload State
					</button>
				</div>
			</Modal>
		);
	}

	return null;
};

Adicional tambi茅n con la propiedad Kind en Modal, valide que si esta era true agregara a la clase absolute un

z-index:2 

para 鈥渋nutilizar鈥 el b贸ton de agregar un Todo.

export const Modal = ({ children, kind  }) => {
	
	const className = kind ? `${style.ModalBackground} ${style.absolute}` : style.ModalBackground;
	
	return ReactDOM.createPortal(
		<div className={className}>{children}</div>,
		/* llamamos a nuestro nuevo nodo */
		document.getElementById('modal')
	);
};

Y as铆 quedo:

As铆 es como resolv铆 los retos agregue una validaci贸n para no iterar los elementos al menos que no hubiera un error y no se encontrar谩 cargando la App. Puse un bot贸n para darle al usuario la posibilidad de no recargar la p谩gina.

antes de ver la solucion de eprofe lo que hice fue que el pedido de el contenido de local storage fuese una funcion tal que asi y cada que haya u cambio en storage invocar a update() y el cartel lo oculto en 3s para que no sea molesto y tambien se cierra con el botton

const update= ()=>{
        try{...
        }catch(err){...
        }
    }
React.useEffect(()=>{
    setTimeout(()=>{
        update();
        },1500);
    }, []);

Para mostrar los cambios al hacer click en el boton, lo que hice fue -> <button
onClick={() => window.location.reload(false)}
>
Volver a cargar la informacion
</button>
</div>

Mi soluci贸n a que no salgan los todos mientras est谩 en loading fue a帽adir una condici贸n extra antes de renderizar los todos, esa condici贸n es !props.loading, para que si el valor de loading es true no se renderice nada y cuando sea false ya pueda renderizar 馃槃

Buenas, al desaf铆o del profe Juan Dav铆d lo resolv铆 de dos formas, una f谩cil y r谩pida pero que es muy muy b谩sica y otra m谩s pensada.
La primera simplemente agregue un window.location.reload() en la funci贸n del bot贸n.
La segunda llev茅 el estado storageChange al hook useLocationStorage para que el useEffect corra cada vez que ese estado se actualice.
Sigo con el video!

yo simplemente lo que hice fue algo sensillo coloque dentro del button una etiqueta a con href="." y esto automaticamente me recarga la pagina

function ChangeAlert({show, toggleShow}) {
  if (show) {
    return (
      <div>
        <p>Hubo Cambios</p>
        <button
          onClick={() => toggleShow(false)}
        >
        <a href=".">Volver a cargar</a>
        </button>
      </div>
    );
  } else {
    return null;
  }
}

Lo que yo hice para solucionar el reto fue exportar la variable 鈥渟incronizedItem鈥 del hook useLocalStorage, la importe en el hook useTodos y la volvi a exportar en este, luego la importe en el componente App y se la env铆e como props a mi componente TodoList, en el componente TodoList solo valide lo siguiente.

  {
        props.sincronizing && props.searchedTodos.map( todo => renderFunc(todo))
      }

esta fue mi solucion al reto #2

import "../TodoList/TodoList.css"

function TodoList({ error, onError, loading, onLoading, onEmptyTodos, searchedTodos, render, totalTodos, onEmptySearchResults, searchText, children }) {

    const renderFunc = children || render

    // Agrega una condici贸n para no mostrar el componente cuando est谩 cargando
    if (loading) {
        return (
            <section className="todolist-container">
                {loading && onLoading()}
            </section>
        );
    }

    return (
        <section className="todolist-container">
            {error && onError()}
            {(!totalTodos && !searchedTodos.length) && onEmptyTodos()}
            {(!!totalTodos && !searchedTodos.length) && onEmptySearchResults(searchText)}
            {searchedTodos.map(renderFunc)}
            <ul className="">
                {children}
            </ul>
        </section>
    );
}

export { TodoList };

Gracias, todavia no se como lo voy a aplicar, pero si he tenido la duda de como trabajar con sesiones concurrentes de varios usuarios usando la misma spreadsheet. Y se que este ejemplo de LocalStorage es un comienzo.

Mmm me gust贸 esta clase, pero creo que se sobrecomplic贸 algo que en realidad es muy simple.
.
Entiendo que es con fines educativos, pero resulta un poco mareante.
.
En lugar de usar HOCs, tranquilamente podr铆amos mostrar el alert con:

{ sincronized && <AlertWithStorageListener /> }

Esto funciona, pero me queda la duda, es esto correcto y la complejizaci贸n con HOCs que se hace en la clase es meramente con fines educativos, o tiene raz贸n de ser el hecho de haber hecho este HOC?

woow! antes me hacia esta pregunta, cargar cierta info en otros proyectos.

Codigo en TypeScript de la clase (con soluci贸n del reto del profesor):

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 }
import { Dispatch, SetStateAction, FC } from 'react'
import { withStorageListener } from "./withStorageListener"

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

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

    if(show){
        return(
            <div>
                <p>Hubo cambios</p>
                <button onClick={()=>{
                    toggleShow(false);
                    sincronizeTodos();
                }}
                >
                    Volver a cargar la informaci贸n
                </button>
            </div>
        )
    } else {
        return null
    }
}

const ChangeAlertWithStorageListener = withStorageListener(ChangeAlert);

export { ChangeAlertWithStorageListener }
import { useEffect, useState } from "react";

const useLocalStorege = <T,>(itemName:string, initialValue:T) => {

    const [item, setItem] = useState<T>(initialValue);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(false);
    const [synchronizedItem , setSynchronizedItem] = useState(true);

    useEffect(() => {
        setTimeout(() => {
            try {
                const localStorageTodos = localStorage.getItem(itemName);
                let parsedItem:T;
                if(!localStorageTodos){
                    localStorage.setItem(itemName, JSON.stringify(initialValue))
                    parsedItem = initialValue
                } else {
                    parsedItem = JSON.parse(localStorageTodos) as T;
                }
                saveItem(parsedItem);
                setLoading(false);
                setSynchronizedItem(true);    
            } catch (err) {
                setError(true);
            }
        }, 2000);
    }, [synchronizedItem])
    
    const saveItem = (newTodos:T) => {
        setItem(newTodos);
        localStorage.setItem(itemName, JSON.stringify(newTodos))
    }

    const sincronizeItem = () => {
        setLoading(true);
        setSynchronizedItem(false);
        setItem([] as T);
    }

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

}

export { useLocalStorege }

import { Dispatch, SetStateAction, useState } from "react";
import { useLocalStorege } from "./useLocalStorage";

export interface ITodo {
  text:string,
  completed:boolean
}

interface GlobalState {
  searchedTodos: ITodo[]
  completedTodos: number,
  totalTodos: number,
  searchValue: string,
  setSearchValue: Dispatch<SetStateAction<string>>
  addTodo: (v:string)=>void
  completeTodo: (v:string)=>void,
  deleteTodo: (v:string)=>void,
  loading: boolean,
  error: boolean,
  openModal: boolean,
  setOpenModal: Dispatch<SetStateAction<boolean>>
  sincronizeTodos: ()=>void
}

const useTodos = (): GlobalState => {

    const {
      item: todos,
      saveItem: saveTodos,
      loading,
      error,
      sincronizeItem:  sincronizeTodos,
    } = useLocalStorege<ITodo[]>('TODOS_V1', []);
    
    const completedTodos:number = todos.filter(todo=>todo.completed).length;
    const totalTodos:number = todos.length;
    const [searchValue, setSearchValue] = useState<string>('');
    const [openModal, setOpenModal] = useState(false);
  
    const searchedTodos = todos.filter(todo=> todo.text.toLowerCase().includes(searchValue.toLowerCase()));

    const addTodo = (text:string) => {
      const newTodos = [...todos]
      newTodos.push({
        text,
        completed: false
      })
      saveTodos(newTodos)
    }
    
    const completeTodo = (text: string) =>{
      const newTodos = todos.map(todo=>{
        if(todo.text === text) todo.completed = !todo.completed;
        return todo;
      })
      saveTodos(newTodos);
    }
  
    const deleteTodo = (text: string) => {
      const newTodos = todos.filter(todo=>todo.text !== text);
      saveTodos(newTodos);
    }

    return {
      searchedTodos,
      completedTodos,
      totalTodos,
      searchValue,
      setSearchValue,
      addTodo,
      completeTodo,
      deleteTodo,
      loading,
      error,
      openModal,
      setOpenModal,
      sincronizeTodos,
    }
}

export { useTodos }

aca me quede

Mi manera de solucionar el problema de comunicar el componente ChangeAlert con el resto de la app:

  1. En nuestro hook useTodos. Cree una funcion 鈥淩efresh todos鈥
    const refreshTodos = () => {
        const myNewTodo = JSON.parse(localStorage.getItem('TODOS_V1'))

        setTodos(myNewTodo)
    }
  1. Mande la funcion 鈥淩efresh Todos鈥 como props de nuestro componente en nuestro App.js
<ChangeAlertWithListener refreshTodos={refreshTodos}/>
  1. Desglosamos las props en nuestro app components:
      <WrappedComponent 
        showAlert={showAlert} 
        setShowAlert={setShowAlert}
        {...props} 
      />

Y voila. con esas cosas ya puedo refrescar al momento de colocar aceptar. sin la necesidad de recargar la pagina

Bueno, ya pas贸 un a帽o desde el curso, pero para el reto, que no aparezcan los todos, lo solucion茅 as铆:
{!props.loading && props.searchedTodos.map(renderFunc)}

He tenido problemas con el sincronizeTodos por inercia de mi parte he colocado sincronizedTodos en mi caso me hubiera gustado que la palabra fuera otra mas diferencial alguna sugerencia ?