No tienes acceso a esta clase

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

Completando el StorageEventListener

15/19
Recursos

Aportes 47

Preguntas 14

Ordenar por:

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

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(‘storage’)):

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)}

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)

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

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

Cuando uno piensa que ya sabe todo, aparecen clases como esta jajajajajjajaa
RETO COMPLETADO, si quieren checar las demas funcionalidades aqui esta la app <https://ulternae.github.io/ReactTodo/> ![](https://static.platzi.com/media/user_upload/image-9e00abbe-ebb3-4b81-9b5d-f42f122dabcd.jpg)![](https://static.platzi.com/media/user_upload/image-76e3f217-10ab-45a4-bb0a-159ef1559313.jpg)![](https://static.platzi.com/media/user_upload/image-ac204457-6aa4-428c-98c9-9532114559c9.jpg)

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–loading

<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 “Hubo 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 “hubo 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 “Desea 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 “inutilizar” 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 “sincronizedItem” 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 es mi solución para esconder la lista de todos mientras están actualizándose sus valores: . . . . . . . . ```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 }; ```
Esta es mi solución para darle estilos al warining: . . . . . . . . ```js import React from 'react'; import { withStorageListener } from './withStorageListener'; function ChangeAlert({ show, toggleShow }) { if (show) { return (

Hubo cambios.

<button style={{ transform: 'translateY(0.6vh)', width: '24vw', height: '4.5vh', borderRadius: '9px', borderWidth: '0px', fontFamily: 'Montserrat, Arial, Helvetica, sans-serif', }} onClick={() => toggleShow(false)} > Recargar informacion </button>
) } else { return null; } } const ChangeAlertWithStorageListener = withStorageListener(ChangeAlert); export { ChangeAlertWithStorageListener }; ```
Creo que está mal implementado el useEffect() con el \[sincronizedItem] ya que dicha variable debería ser parte del contenido del useEffect() para que realmente cumpla su cometido.

Completando el StorageEventListener

.
Completamos lo que faltaba del HOC que estuvimos desarrollando.
.
Dentro de withStorageListener lo primero que hacemos es escuchar el evento de storage que se activará cada vez que haya cambios en el localStorage. Entonces si la key es igual a TODOS_V1 significa que hubo cambios en nuestros TODOs y es necesario que activemos nuestro estado de storageChange a true mediante nuestro setStorageChange.
.

// ChangeAlert/withStorageListener.js
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);
      }
    });

    ...
  }
}

export { withStorageListener };

.
En el componente ChangeAlert aumentamos que si show es false entonces retornamos null, puesto que React siempre de retornar algo.
.
Vamos a envolver en un div al párrafo y un botón para poder esconder nuestro componente si le hacemos click.
.

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

function ChangeAlert({ show, toggleShow }) {
  if (show) {
    return (
      <div>
        <p>Hubo hubo cambios</p>
        <button
          onClick={toggleShow}
        >
          Volver a cargar la información
        </button>
      </div>
    );
  } else {
    return null;
  }
}

const ChangeAlertWithStorageListener = withStorageListener(ChangeAlert);

export { ChangeAlertWithStorageListener };

.
En nuestro custom hook useLocalStorage creamos un nuevo estado sicronizedItem para saber si estamos sincronizados incialmente en true. Mediante setSincronizedItem podremos cambiar el estado de sincronizedItem.
.
Lo que se propone es pasar a sincronizedItem al array de dependencias del useEffect.
.
Entonces, si los todos han cargado bien significa que estamos sincronizados por lo que dentro del try justo después de setItem y setLoading llamamos al setSincronizedItem con el argumento de true.
.
Finalmente creamos una función sincronizeItem que vamos a poder pasar a los otros componentes, que nos permitirá cambiar el estado de carga en true y decirnos que no estamos sincronizados. Es decir, que estamos en proceso de sincronizar nuestros TODOs.
.
Agregamos sincronizeItem al return, en useTodos lo recibiremos cambiándole el nombre por sincronizeTodos y lo retornaremos también para poder recibirlo en el componente App y pasarlo finalmente a nuestro componente ChangeAlertWithStorageListener.
.

useLocalStorage(itemName,initialValue) {
	const [sincronizedItem,setSincronizedItem] = React.useState(true);
	...

	React.useEffect(() => {
		setTimeout(() => {
			try {
				...
				setItem(parsedItem);
				setLoading(false);
				setSincronizedItem(true);
			} catch(error) {
				setError(error);
			}}, 3000);
		} ,[sincronizedItem]);

	...

	const sincronizeItem = () => {
		setLoading(true);
		setSincronizedItem(false);
	};

	return {
		...
		sincronizeItem,
	};
}

export { useLocalStorage };
function useTodos() {
  const {
    ...
    sincronizeItem: sincronizeTodos,
    ...
  } = useLocalStorage('TODOS_V1', []);

return {
    ...
    sincronizeTodos,
  };
}

export { useTodos };
function App() {
  const {
    ...
    sincronizeTodos,
  } = useTodos();

	return (
    <React.Fragment>
			...
			<ChangeAlertWithStorageListener
        sincronize={sincronizeTodos}
      />
    </React.Fragment>
  );
}

export default App;

.
Finalmente en lugar de pasar setStorageChange en la propiedad toggleShow lo que haremos es crear una función con ese mísmo nombre que llame a sincronizar los TODOs y además vuelva a setear nuestro estado de cambios al localStorage como false.
.

// ChangeAlert/withStorageListener.js
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);
    };

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

export { withStorageListener };

.
Aún queda pendiente:
.

  • Agregar el css a la alerta para que se vea bien y que no deje realizar cambios en la aplicación que no está con la información persistida.
  • Los TODOs siguen apareciendo junto al estado de carga mientras sincronizamos nuestros TODOs.

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 “Refresh todos”
    const refreshTodos = () => {
        const myNewTodo = JSON.parse(localStorage.getItem('TODOS_V1'))

        setTodos(myNewTodo)
    }
  1. Mande la funcion “Refresh 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 ?