目标:创建一个可重用的useFecth自定义钩子+防抖动组件,以使API在每次击键后都不会被调用,而是在用户完成输入后才被调用 example in codesandbox
我所拥有的:
问题: 如果值是狗的品种,但键入的错误或提供的值(狗的品种)不存在,则会引发我正在useFetch挂钩中处理的错误。到目前为止,很好的问题是,如果我要传递存在的另一个值(狗品种),它仍然会抛出“不存在”错误,而该错误不应该发生。感觉就像卡在了那里,而且没有重新渲染。
import useFetch from "./useFetch";
import IsLoadingComponent from "./isLoadingComponent";
export default function App() {
const [value, setValue] = useState("");
const [dogBreed, setDogBreed] = useState("");
//custom fetch hook
//will be trigger after debounce function is done executing
const url = `https://dog.ceo/api/breed/${dogBreed}/images/random`;
const { data: randomImage, isLoading, hasError, errorMessage } = useFetch(
url,
dogBreed
);
//debounce the value after user is done typing not after every keystroke
const debouncedSave = useCallback(
debounce((nextDogBreed) => setDogBreed(nextDogBreed), 500),
[] // will be created only once initially
);
//this will trigger the debounce function
const handleChange = (e) => {
const { value: nextDogBreed } = e.target;
setValue(nextDogBreed);
debouncedSave(nextDogBreed);
};
// console.log(dogBreed);
// console.log(randomImage);
// console.log(hasError);
// console.log(errorMessage);
return (
<div className="App">
<input placeholder="breed" onChange={handleChange} />
<br />
{isLoading ? (
<IsLoadingComponent />
) : hasError ? (
<h1>{errorMessage.message}</h1>
) : (
<img
src={randomImage}
alt="dogImage"
style={{ width: "200px", height: "200px", marginTop: "2rem" }}
/>
)}
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
useFetch自定义挂钩
import { useState, useEffect } from "react";
const useFetch = (url, dogBreed) => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
useEffect(() => {
if (dogBreed) {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(url);
const result = await response.json();
if (response.ok) {
setData(result.message);
} else {
setHasError(true);
setErrorMessage(result);
}
} catch (err) {
setHasError(true);
setErrorMessage(err.message);
} finally {
setIsLoading(false);
}
};
fetchData();
} else {
setData("https://images.dog.ceo/breeds/african/n02116738_10215.jpg ");
}
}, [url, dogBreed]);
return { data, isLoading, hasError, errorMessage };
};
export default useFetch;
感谢您的帮助
答案 0 :(得分:1)
具有缓存和去抖动功能的自定义提取。
const debounce = (fn: any, time: any) => {
let timer: any;
return (...args: any) => {
if(timer) clearTimeout(timer)
timer = setTimeout(()=> {
timer = null
fn(args)
}, time)
}
}
const useFetch = (num: string) => {
const [data, setData] = useState({})
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const cache = useRef<Map<string, {}>>(new Map())
useEffect(() => {
async function call() {
if(cache.current.has(num)){
const d = cache.current.get(num) as {}
setData(d)
}else{
try{
setLoading(true)
const r = await axios(`https://jsonplaceholder.typicode.com/posts/${num}`)
setData(r.data)
cache.current.set(num, r.data)
}catch(err){
setError('Request failed')
setData({})
}finally{
setLoading(false)
}
}
}
if(num.length){
call()
}else{
setData({})
setLoading(false)
}
},[num])
return [data, error, loading]
}
const Component = () => {
const [value, setValue] = useState('')
const [resp, setData] = useState({})
const [debouncedTerm, setDebouncedTerm] = useState('')
const [data, error, loading] = useFetch(debouncedTerm);
const cb = useCallback(debounce((v: string) => setApiCallVal(v), 1000),[])
const changeHandler = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
cb(e.target.value)
}
if(loading) return <p>Loading...</p>
return (
<section className="container">
<section className="header-wrapper">
<h1>Fetch + Debounce</h1>
<input onChange={changeHandler} value={value} />
<p>{JSON.stringify(data)}</p>
{error}
</section>
</section>
)
}