这是我第一次开发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};
}
非常感谢您的帮助。
答案 0 :(得分:1)
您打算做的事情是可能的,但是在我看来,不应在实际应用程序中完成。这就是为什么。
首先,让我们考虑为什么要使用钩子。 React函数组件无法在重演之间保留本地状态和处理生命周期事件。这就是为什么引入了钩子的原因。
在使用钩子之前,我会问自己:“我需要处理组件状态还是组件生命周期?”。如果不是这样,那么使用钩子要么是矫or过正,要么是反模式。
在您的示例中,让我们考虑一下您可能会尝试使用viewDetails
函数。显然,您想执行fetch调用,但是之后您打算做什么?让我们看看一些可能性...
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
对象;然后,该挂钩利用可变的ref
(useRef
)和useEffect
通过state
函数的data
更新fetchData
。然后,自定义钩子返回data
(当前值)和一个refreshData
函数(一个自定义callback
函数进行另一个API调用); (可选)利用另一个useCallback
钩子使用不同的refreshData
调用URL
函数(您可以内联地进行此操作,但是来自类,我个人不喜欢内联函数,因为它们每次重新渲染组件时都会重新创建,因此很难对其进行测试。)如果您希望data
和refreshData
成为所有组件都可以访问的同一个实例,则需要使用Context
或Redux
(最好是Redux )。
有效的useFetchData示例:
钩子/ 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 >
);
}