【问题标题】:Helper function using hooks inside a functional component使用功能组件内的钩子的辅助函数
【发布时间】:2021-02-17 16:46:34
【问题描述】:

我正在尝试在我的功能组件中使用获取辅助功能,但 React 抱怨:

src\components\Header.js

第 19:30 行:React Hook “useFetch”在函数“handleSearch”中被调用,该函数既不是 React 函数组件也不是自定义 React Hook 函数。 React 组件名称必须以大写字母 react-hooks/rules-of-hooks 开头

我知道功能组件应该以大写字母开头,但是 useFetch 不是组件,它只是一个辅助函数。我究竟做错了什么?我知道我可以通过调用我的函数 UseEffect 而不是 useEffect 来解决这个问题,但我应该吗?

这是我的 helper.js

import { useState, useEffect } from 'react';

export const GH_BASE_URL = 'https://api.github.com/';

export const useFetch = (url, options) => {
    const [response, setResponse] = useState(null);
    const [error, setError] = useState(null);
    const [isLoading, setIsLoading] = useState(false);
    
    useEffect(() => {
        const fetchData = async () => {
            setIsLoading(true);

            try {
                const res = await fetch(url, options);
                const json = await res.json();
                setResponse(json);
                setIsLoading(false);
            } catch (error) {
                setError(error);
            }
        };
        
        if(url) {
            fetchData();
        }
    }, []);
   
    return { response, error, isLoading };
};

还有我的 Header.js 组件

import React, { useState } from 'react';
import { useFetch, GH_BASE_URL } from '../helpers';

const REPO_SEARCH_URL = `${GH_BASE_URL}/search/repositories?q=`;

function Header(props) {
    const [searchValue, setSearchValue] = useState('');

    function handleChange(event) {
        setSearchValue(event.target.value);
    }

    async function handleSearch(event) {
        event.preventDefault();

        const response = useFetch(`${REPO_SEARCH_URL}${searchValue}`);
    }
    
    return (
        <header>
            <form 
                onSubmit={handleSearch}
                className="container"
            >
                <input
                    value={searchValue}
                    onChange={handleChange}
                    className="search-input"
                    placeholder="Search a repository">
                </input>
            </form>
        </header>
    );
}

export default Header;

【问题讨论】:

  • 仅供参考,您创建的 useFetch 是一种非常自然的模式,将被称为 custom hook。该文档还描述了为什么@Zachary 的答案是正确的。

标签: javascript reactjs


【解决方案1】:

你必须在主渲染函数中使用useFetch 钩子。您不能在另一个函数中使用它。您需要调整您的 useFetch 以单独工作。

以下是如何执行此操作的示例。在这种情况下,我会在 url 或选项更改时重新获取 useFetch

helper.js

import { useState, useEffect } from 'react';

export const GH_BASE_URL = 'https://api.github.com/';

export const useFetch = (url, options) => {
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    // Set up aborting
    const controller = new AbortController();
    const signal = controller.signal;
    const fetchData = async () => {
      setIsLoading(true);

      try {
        const res = await fetch(url, { ...options, signal });
        const json = await res.json();
        setResponse(json);
        setIsLoading(false);
      } catch (error) {
        // AbortError means that the fetch was cancelled, so no need to set error
        if (error.name !== 'AbortError') {
          setError(error);
        }
      }
    };

    if (url) {
      fetchData();
    }
    // Clear up the fetch by aborting the old one
    // That way there's no race condition issues here
    return () => {
      controller.abort();
    };
    // url and options need to be in the effect's dependency array
  }, [url, options]);

  return { response, error, isLoading };
};

Header.js

import React, { useState } from 'react';
import { useFetch, GH_BASE_URL } from '../helpers';

const REPO_SEARCH_URL = `${GH_BASE_URL}/search/repositories?q=`;

function Header(props) {
  const [searchValue, setSearchValue] = useState('');

  
  function handleChange(event) {
    this.setState({ searchValue: event.target.value });
  }

  // Instead of using the search directly, wait for submission to set it
  const [searchDebounce,setSearchDebounce] = useState('');
  
  async function handleSearch(event) {
    event.preventDefault();
    setSearchDebounce(searchValue);
  }
  // If you want to include the options, you should memoize it, otherwise the fetch will re-run on every render and it'll cause an infinite loop.
  // This will refetch everytime searchDebounce is changed
  const { response, error, isLoading } = useFetch(
    searchDebounce?`${REPO_SEARCH_URL}${searchDebounce}`:''
  );

  return (
    <header>
      <form onSubmit={handleSearch} className="container">
        <input
          value={searchValue}
          onChange={handleChange}
          className="search-input"
          placeholder="Search a repository"
        ></input>
      </form>
    </header>
  );
}

export default Header;

如果你想在响应发生变化时运行一个函数,你可以使用一个效果:

  useEffect(() => {
    if (error || isLoading) {
      return;
    }
    // Destructure this to prevent a deps issue on the hooks eslint config
    const { responseChanged } = props;
    responseChanged(response);
  }, [response, isLoading, error, props.responseChanged]);

【讨论】:

  • 太棒了!如何使用响应数据调用 props 方法?我正在将一个函数传递给 Header 以更新 App 中的 repos 状态,但响应为空...
  • @LazioTibijczyk,我在帖子末尾添加了一种方法来做到这一点,因为你应该确保 props.responseChanged (或任何你称之为的)将使用Callback 所以这不会运行比你想要的更频繁。或者将 props.responseChanged 放入 ref 并在效果中使用它
猜你喜欢
  • 1970-01-01
  • 2019-08-15
  • 2019-12-08
  • 2021-04-13
  • 1970-01-01
  • 1970-01-01
  • 2019-12-29
  • 1970-01-01
  • 2017-08-07
相关资源
最近更新 更多