Tengo una página que muestra una lista de países proporcionada por una biblioteca externa. Cuando hago clic en un país, me muestra todas las ciudades en él.
Cada objeto de país tiene un método para obtener la lista de ciudades que contiene, proporcionado por la biblioteca.
Tengo un CountryComponent como este
import { City, Country } from "an-external-library"; export const CountryComponent: FC<Props> = ({ country: Country }) => { const [cities, setCities] = useState<City[]>([]); useEffect(() => { country .getCities() .then(setCities) .catch(console.error); }, [country]); // ... } Quiero obtener cities cada vez que se monta el componente y también si cambia el country .
El problema es que si sigo cambiando el país rápidamente, llamará a getCities() cada vez que haga clic en un país de la lista. Como no hay forma de saber en qué orden llegarán las respuestas a esas llamadas, al final las cities serán la respuesta que llegó en último lugar, sea la que sea.
Esto significa que puedo terminar con el país: Francia; ciudades: Nueva York, Los Ángeles, etc...
Soluciones que he probado
CountryComponent y volver a montar si cambia country .En el componente principal, tengo
const handleChangeCountry = (c: Country) => { setCountry(undefined); setTimeout(() => setCountry(c), 10); } render ( <> <CountryList onCountryClick={handleChangeCountry}/> {country ? <CountryComponent country={country}/> : <Spinner /> }) </> ) Esta es una solución tan fea que solo escribir esto me da escalofríos.
Pero funcionó. Solo que no pude hacer que la interfaz de usuario se viera limpia cuando CountryComponent desapareció durante 10 ms y volvió a aparecer; y quiero una mejor solución.
Soluciones que no he probado
Como sugirió debouncing , la eliminación de rebotes podría ser una solución.
Mientras buscaba soluciones antes de publicar, encontré esta respuesta , pero no la probé porque creo que haría que la lista de ciudades tardara un poco en responder a los clics en los países, y eso tampoco es lo ideal.
Idealmente, me gustaría ignorar todas las devoluciones de llamada de getCities() excepto la última, pero no sé cómo lograría esto.
Este es un problema típico: obtiene respuestas asincrónicas desordenadas, lo que potencialmente sobrescribe el estado más reciente (y el único relevante).
Una solución muy fácil es usar el gancho useAsync react-use , que incluye varios mecanismos útiles para este problema típico, el interesante aquí es que descarta las respuestas de todas las solicitudes anteriores, manteniendo solo la respuesta a la solicitud más reciente.
Gancho de reacción que resuelve una función asíncrona o una función que devuelve una promesa;
import { useAsync } from "react-use"; const CountryComponent = ({ country: Country }) => { // With react-use's useAsync, // no need for separate state and useEffect const cities = useAsync( () => country.getCities(), [country] ).value; // returned object { loading: boolean, value: T, error } // ... }