useFetch自定义钩子+反跳React

时间:2020-10-06 14:01:17

标签: javascript reactjs react-hooks

目标:创建一个可重用的useFecth自定义钩子+防抖动组件,以使API在每次击键后都不会被调用,而是在用户完成输入后才被调用 example in codesandbox

我所拥有的:

  1. 将触发反跳功能的输入标签
  2. 执行反跳功能后,它将设置一个新值并触发useFetch自定义钩子
  3. 自定义挂钩被执行并进行API调用

问题: 如果值是狗的品种,但键入的错误或提供的值(狗的品种)不存在,则会引发我正在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;

感谢您的帮助

1 个答案:

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