Úvod
Správa stavu načítání reakce může být trochu otravná, musíme ji před načítáním nastavit na isLoading a po dokončení ji nastavit zpět na false. Potom jej také musíme nastavit na tlačítko, abychom mohli zobrazit stav načítání nebo dát nějaký text jako indikátor.
Takto to vypadá s běžným vzorem načítání:
const [pokemon, setPokemon] = React.useState<Array<Pokemon>>();
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const getData = () => {
setIsLoading(true);
axios
.get<PokemonList>('https://pokeapi.co/api/v2/pokemon?limit=20')
.then((res) => {
setPokemon(res.data.results);
})
.finally(() => setIsLoading(false));
};
return <button disabled={isLoading}>{isLoading ? 'loading' : 'fetch'}</button>;
je to nepříjemné a ještě jsme se ani nezabývali chybovým stavem.
Co bychom měli spravovat v procesu načítání?
Když načítáme data, musíme udělat nějaké věci, aby bylo čekání snesitelnější. Zde je několik věcí, které můžeme udělat:
Indikátor načítání
Uživatelé potřebují vědět, kdy je jejich aplikace ve stavu načítání. To je důležité, aby nečekali tupě a získali myšlení, že by měli chvíli počkat.
Indikátor načítání může být číselník, normální text, některé animace nebo toast.
Indikátor úspěchu
Musíme uživateli sdělit, zda načítání proběhlo úspěšně, aby mohl pokračovat ve své práci.
Indikátor chyby
Když se načítání dat pokazí, musíme o tom uživatele informovat.
Akce blokování
Běžným příkladem je, že když odesíláme formulář, nechceme, aby uživatel odeslal dvakrát. Můžeme to udělat deaktivací tlačítka, když probíhá stav načítání.
Dalším příkladem je zablokování tlačítka modálního uzavření při načítání, aby jej uživatel omylem nezavřel.
Snadný způsob
Zjistil jsem, že tento vzor je nejvíce bezproblémový a můžeme použít vlastní háčky k zachycení stavu načítání.
Zde je to, co budeme stavět:
Popis videa:
- klikne se na tlačítko getData a poté se zobrazí načítání toastu.
- Při načítání je tlačítko deaktivováno a zobrazuje načítání číselník
- Po 2 sekundách se načítání toastu změní na chybný toast
- znovu se klikne na tlačítko getData a poté se zobrazí načítání toastu
- Po 2 sekundách se načítání toastu změní na úspěšný toast a poté se všechna data načtou správně.
ps:kurzor čekání je v nahrávce nějak divný.
S tímto vzorem snadno pokryjeme všechny 4 věci .
- Stav načítání získáme pomocí toastu
- Můžeme zobrazit indikátor chyby a zobrazit chybovou zprávu z rozhraní API
- Můžeme zobrazit indikátor úspěchu
- Všechna tlačítka jsou deaktivována.
Používáme React Hot Toast pro indikátor načítání, úspěchu a chyb. Všechny jsou spravovány pouze pomocí 1 funkce obalu, jako je tato:
toast.promise(
axios
.get<PokemonList>('https://pokeapi.co/api/v2/pokemon?limit=20')
.then((res) => {
setPokemon(res.data.results);
}),
{
loading: 'Loading...',
success: 'Data fetched successfully',
error: (err: any) =>
err?.response?.data?.msg ?? 'Something is wrong, please try again',
}
);
Konfigurace
Nejprve musíme nainstalovat reagovat-hot-toast
yarn add react-hot-toast
Pro demo používám Next.js, ale konfigurace pro CRA je v podstatě stejná. Přidejte to do _app.tsx
import { AppProps } from 'next/app';
import '@/styles/globals.css';
import DismissableToast from '@/components/DismissableToast';
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<DismissableToast />
<Component {...pageProps} />
</>
);
}
export default MyApp;
Přidal jsem zavřít tlačítko, protože ho ve výchozím nastavení nemá, můžete chytit DismissableToast
kód z mé knihovny.
Použití
Řekněme, že chceme načíst data při připojení z externího API pomocí Axios.
Jen potřebujeme zabalit volání axios funkcí toast.
React.useEffect(() => {
toast.promise(
axios
.get<PokemonList>('https://pokeapi.co/api/v2/pokemon?limit=20')
.then((res) => {
setPokemon(res.data.results);
}),
{
loading: 'Loading...',
success: 'Data fetched successfully',
error: (err: any) =>
err?.response?.data?.msg ?? 'Something is wrong, please try again',
}
);
}, []);
A je to! Toast zobrazí stav při načítání a kdy je úspěšný nebo chybný.
Další opětovné použití
Můžete jej vytvořit ještě více, když deklarujete defaultToastMessage
a v případě potřeby jej přepište.
export const defaultToastMessage = {
loading: 'Loading...',
success: 'Data fetched successfully',
// you can type this with axios error
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: (err: any) =>
err?.response?.data?.msg ?? 'Something is wrong, please try again',
};
toast.promise(axios, {
...defaultToastMessage,
loading: 'Override loading',
});
Přístup ke stavu načítání
Můžeme to udělat s toastovým API, které jsem zabalil do vlastního háčku.
import { useToasterStore } from 'react-hot-toast';
/**
* Hook to get information whether something is loading
* @returns true if there is a loading toast
* @example const isLoading = useLoadingToast();
*/
export default function useLoadingToast(): boolean {
const { toasts } = useToasterStore();
const isLoading = toasts.some((toast) => toast.type === 'loading');
return isLoading;
}
A můžeme to použít právě takto
const isLoading = useLoadingToast();
<button disabled={isLoading}></button>;
S isLoading
stavu, zbytek je veškerá vaše kreativita, můžete ukázat nějakou kostru, změnit text načítání, dát číselníky načítání, cokoliv chcete.
Gotcha:2 Axios Calls
Pokud máte 2 volání axios, můžete zřetězit další volání axios a přidat další then
získat hodnotu.
toast.promise(
axios
.post('/user/login', data)
.then((res) => {
const { jwt: token } = res.data.data;
tempToken = token;
localStorage.setItem('token', token);
// chaining axios in 1 promise
return axios.get('/user/get-user-info');
})
.then((user) => {
const role = user.data.data.user_role;
dispatch('LOGIN', { ...user.data.data, token: tempToken });
history.replace('/');
}),
{
...defaultToastMessage,
}
);
Integrace SWR
Použití SWR k načtení dat je ještě úžasnější, protože stav načítání potřebujeme ukázat pouze při prvním načtení. Zde je ukázka:
Popis videa:
- Při první návštěvě se zobrazí načítaný toast a poté se změní v úspěšný toast.
- Při druhé návštěvě nedochází k načítání toastu a data jsou předem vyplněna mezipamětí.
Toto je syntaxe SWR:
const { data, error } = useSWR<PokemonList>(
'https://pokeapi.co/api/v2/pokemon?limit=20'
);
Můžeme použít jiný vlastní háček ✨
Udělal jsem tento háček, abychom mohli zabalit useSWR
stejně jako toast.promise
funkce.
useWithToast pro SWR
import * as React from 'react';
import toast from 'react-hot-toast';
import { SWRResponse } from 'swr';
import { defaultToastMessage } from '@/lib/helper';
import useLoadingToast from '@/hooks/useLoadingToast';
type OptionType = {
runCondition?: boolean;
loading?: string;
success?: string;
error?: string;
};
export default function useWithToast<T, E>(
swr: SWRResponse<T, E>,
{ runCondition = true, ...customMessages }: OptionType = {}
) {
const { data, error } = swr;
const toastStatus = React.useRef<string>(data ? 'done' : 'idle');
const toastMessage = {
...defaultToastMessage,
...customMessages,
};
React.useEffect(() => {
if (!runCondition) return;
// if toastStatus is done,
// then it is not the first render or the data is already cached
if (toastStatus.current === 'done') return;
if (error) {
toast.error(toastMessage.error, { id: toastStatus.current });
toastStatus.current = 'done';
} else if (data) {
toast.success(toastMessage.success, { id: toastStatus.current });
toastStatus.current = 'done';
} else {
toastStatus.current = toast.loading(toastMessage.loading);
}
return () => {
toast.dismiss(toastStatus.current);
};
}, [
data,
error,
runCondition,
toastMessage.error,
toastMessage.loading,
toastMessage.success,
]);
return { ...swr, isLoading: useLoadingToast() };
}
Navíc jsem do return přidal isLoading, takže nemusíme volat useLoadingToast
už háčky
Použití
const { data: pokemonData, isLoading } = useWithToast(
useSWR<PokemonList>('https://pokeapi.co/api/v2/pokemon?limit=20')
);
Skvělé, vypadá to dobře a čistě.
Zprávy toastů můžete stále přepsat takto
const { data: pokemonData, isLoading } = useWithToast(
useSWR<PokemonList>('https://pokeapi.co/api/v2/pokemon?limit=20'),
{
loading: 'Override Loading',
}
);
Závěr
Doufám, že to rozšíří vaši sbírku vzorů.
Můžete se podívat na zdrojový kód ukázky na githubu, ale mějte na paměti, že existuje další příslib zpoždění načítání.