从React中的按钮事件中调用钩子的方法

时间:2019-05-27 02:36:51

标签: javascript reactjs

这是我第一次开发React应用程序。我打算在用户单击按钮时拨打Api电话。是否可以直接从事件中调用钩子?如果是的话,如果钩子来自其他组件,我该怎么称呼它?

这是按钮(不同的组件)

 const paginationButtons = (
    <React.Fragment>
      <Button onClick={() => viewDetails()}>View Details</Button>
    </React.Fragment>
  );

function viewDetails() {
   //Is it Possible to call fetch data.js and pass the url from here?
  }

这是获取data.js的钩子

import axios from 'axios';
import PropTypes from 'prop-types';
import { useState, useEffect} from 'react';

const propTypes = {
  initialUrl: PropTypes.string.isRequired,
  initialParams: PropTypes.object
}

export const useFetchData = props => {
  PropTypes.checkPropTypes(propTypes, props, 'property', 'useFetchData');

  const { initialUrl, initialParams } = props;

  const [request, setRequest] = useState({
    url: initialUrl,
    params: initialParams
  });
  const [loading, setIsLoading] = useState(false);
  const [error, setError] = useState();
  const [data, setData] = useState([]);

  useEffect(() => {
    const {url, params} = request;

    const fetchData = async () => {
      setIsLoading(true);
      try {
        const result = await axios(url, {params: {...params}});
        setIsLoading(false);
        setData(result.data);
      } catch(err) {
        setIsLoading(false);
        setError(err);
      }
    }

    fetchData();
  }, [request]);

  const doFetch = (params) => {
    setRequest({url: request.url, params: params});
  };
  return {loading, error, data, doFetch};
}

非常感谢您的帮助。

3 个答案:

答案 0 :(得分:1)

您打算做的事情是可能的,但是在我看来,不应在实际应用程序中完成。这就是为什么。

首先,让我们考虑为什么要使用钩子。 React函数组件无法在重演之间保留本地状态处理生命周期事件。这就是为什么引入了钩子的原因。

在使用钩子之前,我会问自己:“我需要处理组件状态还是组件生命周期?”。如果不是这样,那么使用钩子要么是矫or过正,要么是反模式。

在您的示例中,让我们考虑一下您可能会尝试使用viewDetails函数。显然,您想执行fetch调用,但是之后您打算做什么?让我们看看一些可能性...

  1. 强制更新dom:除非有非常特殊的原因,否则不应执行强制性操作。假设您要显示警报,那么您当然可以调用命令式API(例如notie.js或sweet-alert)。
  2. 以被动方式更新dom:这是我认为的最常见情况。在这种情况下,您应该调用某个setState函数,或者触发一个事件以供其他组件捕获。无论哪种情况,都没有使用钩子来获取数据。

因此,您应该尝试调用类似useFetchData的函数,而不是尝试调用类似doFetchData的钩子。

我不建议您摆脱useFetchData钩子。只是,它们有不同的用途。将挂钩用于应该在加载时发生的所有提取操作,而不是作为对用户操作的响应,是很有意义的。


现在,我已经提出了自己的看法,这是您可以根据用户操作触发挂钩的一种方法。

// Here we are using a shouldFetch flag to conditionally call a function
function useFetch(url, options, shouldFetch, setShouldFetch) {
    const [state, setState] = useState({});
    useEffect(() => {
        if (shouldFetch) {
            doFetch(url, options)
                // set the fetch result to local state
                .then(data => {
                   setState(data)
                   setShouldFetch(false) // avoid infinite calls
                }) 
        }
    });

    return [state]; // You can maybe add fetch status as well
}

现在您可以像这样使用它,

function MyComponent() {
    const [shouldFetch, setShouldFetch] = useState(false);
    const [fetchData] = useFetch('url', {}, shouldFetch, setShouldFetch);

    return (
        <Button onClick={() => {setShouldFetch(true)}}>Click Me</Button>
    )
}

但是,实际上,您不应该这样使用钩子。常规的doFetchData通话是必经之路。如果需要,调用一些setState函数来保留获取的数据。

答案 1 :(得分:0)

这是我能够做到的方式:

  • 创建一个自定义钩子,该钩子将传递一个initialState对象;然后,该挂钩利用可变的refuseRef)和useEffect通过state函数的data更新fetchData。然后,自定义钩子返回data(当前值)和一个refreshData函数(一个自定义callback函数进行另一个API调用); (可选)利用另一个useCallback钩子使用不同的refreshData调用URL函数(您可以内联地进行此操作,但是来自类,我个人不喜欢内联函数,因为它们每次重新渲染组件时都会重新创建,因此很难对其进行测试。)
  • 导入此自定义挂钩并在功能组件中使用

如果您希望datarefreshData成为所有组件都可以访问的同一个实例,则需要使用ContextRedux(最好是Redux )。

有效的useFetchData示例:

Edit Fetching Data - useEffect


钩子/ useFetchData

import { useEffect, useCallback, useRef, useState } from "react";
import PropTypes from "prop-types";
import axios from "axios";

const useFetchData = initialState => {
  // define an "isFetching" ref
  const isFetching = useRef(true);

  // initialize "data" with "initialState"
  const [data, setData] = useState(initialState);

  // an asynchronous function that updates state with data or an error
  const fetchData = async () => {
    try {
      // fetches a photo album by id
      const res = await axios.get(`${data.URL}`);

      // not required, but this creates a 300ms timeout to prevent
      // an abrupt flashing when the UI changes from "Placeholder"
      // to "DisplayData"
      await new Promise(res => {
        setTimeout(() => {
          res();
        }, 300);
      });

      // updates state with data
      setData({
        photos: res.data,
        error: "",
        isLoading: false
      });
    } catch (error) {
      // updates state with an error
      setData({
        photos: [],
        error: error.toString(),
        isLoading: false
      });
    }
  };

  // callback function to reset back to "initialState" with a new URL
  // that invokes "fetchData" again (in the useEffect below)
  const refreshData = useCallback(
    URL => {
      setData({ ...initialState, URL });
      isFetching.current = true;
    },
    [initialState]
  );

  // due to asynchronous nature of data fetching, we must utilize
  // the "isFetching" ref to make sure our API is only called once.
  // by manipulating this mutatable ref, we can ensure
  // "fetchData" is consistent across repaints. if we just tried to utilize
  // state, the "fetchData" will be called twice, instead of once.
  useEffect(() => {
    if (isFetching.current) {
      isFetching.current = false;
      fetchData();
    }
  }, [isFetching.current]); // eslint-disable-line react-hooks/exhaustive-deps
  return {
    data,
    refreshData
  };
};

useFetchData.propTypes = {
  initialState: PropTypes.shape({
    photos: PropTypes.any,
    error: PropTypes.string,
    isLoading: PropTypes.bool.isRequired,
    URL: PropTypes.string.isRequired
  }).isRequired
};

export default useFetchData;

组件/ FetchData

import React, { Fragment, useCallback } from "react";
import { FaRedoAlt } from "react-icons/fa";
import DisplayData from "../DisplayData";
import Placeholder from "../Placeholder";
import useFetchData from "../../hooks/useFetchData";

const id = () => Math.floor(Math.random() * 1000) + 1;
const URL = "https://jsonplaceholder.typicode.com/photos";

const FetchData = () => {
  // initializing our custom hook with some "initialState"
  const { data, refreshData } = useFetchData({
    photos: [],
    error: "",
    isLoading: true,
    URL: `${URL}?id=${id()}`
  });

  // when the button is pressed, the "data" is updated 
  // via the "refreshData" function which gets passed another "URL"
  const fetchOtherData = useCallback(() => {
    refreshData(`${URL}?id=${id()}`);
  }, [refreshData]);

  return (
    <Fragment>
      <div className="container">
        {data.isLoading ? <Placeholder /> : <DisplayData {...data} />}
      </div>
      <div className="button-container">
        <button
          className="uk-button uk-button-primary"
          onClick={fetchOtherData}
        >
          <FaRedoAlt /> Refresh Data
        </button>
      </div>
    </Fragment>
  );
};

export default FetchData;

答案 2 :(得分:0)

您可能需要上下文

const Context = React.createContext(null);

function FetchProvider(props){
   const state = useFetchData(props);
   return (
     <Context.Provider value={state}>{props.children}</Context.Provider>
   )
}

function Button(){
   const { doFetch } = React.useContext(Context);
   const handleClick = React.useCallback(()=> doFetch({id: ""}), [])
   return <button onClick={handleClick}>Fetch</button>
}

function ViewDetails(){
   const {data, error, loading} = React.useContext(Context);
   if (loading) { return <div>Loading</div> }
   if (error) { return <div>{error.message}</div> }
   return <div>{JSON.stringify(data)}</div>
}

function Page(){
   return (
     <FetchProvider initialUrl="/url" initialParams={{id: ""}}>
       <ViewDetails/>
       <Button/>
     </FetchProvider >
   );
}