admin 管理员组文章数量: 1086019
I have a use-case where a page have to call the same fetch function on first render and on button click.
The code is similar to the below (ref: .tsx):
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';
const App: FunctionComponent = () => {
const [selected, setSelected] = useState<string>('a');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [data, setData] = useState<string | undefined>(undefined);
const query = useCallback(async () => {
setLoading(true)
try {
const res = await fetchBackend(selected);
setData(res);
setError(false);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
query();
}, [query])
return (
<div>
<select onChange={e => setSelected(e.target.value)} value={selected}>
<option value="a">a</option>
<option value="b">b</option>
</select>
<div>
<button onClick={query}>Query</button>
</div>
<br />
{loading ? <div>Loading</div> : <div>{data}</div>}
{error && <div>Error</div>}
</div>
)
}
export default App;
The problem for me is the fetch function always triggers on any input changed because eslint-plugin-react-hooks
forces me to declare all dependencies (ex: selected state) in the useCallback
hook. And I have to use useCallback
in order to use it with useEffect
.
I am aware that I can put the function outside of the ponent and passes all the arguments (props, setLoading, setError, ..etc.) in order for this to work but I wonder whether it is possible to archive the same effect while keeping the fetch function inside the ponent and ply to eslint-plugin-react-hooks
?
[UPDATED] For anyone who is interested in viewing the working example. Here is the updated code derived from the accepted answer. .tsx
I have a use-case where a page have to call the same fetch function on first render and on button click.
The code is similar to the below (ref: https://stackblitz./edit/stackoverflow-question-bink-62951987?file=index.tsx):
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';
const App: FunctionComponent = () => {
const [selected, setSelected] = useState<string>('a');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [data, setData] = useState<string | undefined>(undefined);
const query = useCallback(async () => {
setLoading(true)
try {
const res = await fetchBackend(selected);
setData(res);
setError(false);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
query();
}, [query])
return (
<div>
<select onChange={e => setSelected(e.target.value)} value={selected}>
<option value="a">a</option>
<option value="b">b</option>
</select>
<div>
<button onClick={query}>Query</button>
</div>
<br />
{loading ? <div>Loading</div> : <div>{data}</div>}
{error && <div>Error</div>}
</div>
)
}
export default App;
The problem for me is the fetch function always triggers on any input changed because eslint-plugin-react-hooks
forces me to declare all dependencies (ex: selected state) in the useCallback
hook. And I have to use useCallback
in order to use it with useEffect
.
I am aware that I can put the function outside of the ponent and passes all the arguments (props, setLoading, setError, ..etc.) in order for this to work but I wonder whether it is possible to archive the same effect while keeping the fetch function inside the ponent and ply to eslint-plugin-react-hooks
?
[UPDATED] For anyone who is interested in viewing the working example. Here is the updated code derived from the accepted answer. https://stackblitz./edit/stackoverflow-question-bink-62951987-vxqtwm?file=index.tsx
Share Improve this question edited Jan 25, 2021 at 5:54 binkpitch asked Jul 17, 2020 at 10:21 binkpitchbinkpitch 7171 gold badge8 silver badges19 bronze badges 1- How do you protect against memory leaks with this approach? If a user navigates away from the page before the response es back, you've not protected yourself. – BryceBy Commented Feb 17, 2022 at 20:13
3 Answers
Reset to default 4Add all of your dependecies to useCallback
as usual, but don't make another function in useEffect:
useEffect(query, [])
For async callbacks (like query in your case), you'll need to use the old-styled promise way with .then
, .catch
and .finally
callbacks in order to have a void function passed to useCallback
, which is required by useEffect
.
Another approach can be found on React's docs, but it's not remended according to the docs.
After all, inline functions passed to useEffect
are re-declared on each re-render anyways. With the first approach, you'll be passing new function only when the deps of query change. The warnings should go away, too. ;)
There are a few models to achieve something where you need to call a fetch function when a ponent mounts and on a click on a button/other. Here I bring to you another model where you achieve both by using hooks only and without calling the fetch function directly based on a button click. It'll also help you to satisfy eslint rules for hook deps array and be safe about infinite loop easily. Actually, this will leverage the power of effect hook called useEffect
and other being useState
. But in case you have multiple functions to fetch different data, then you can consider many options, like useReducer approach. Well, look at this project where I tried to achieve something similar to what you wanted.
https://codesandbox.io/s/fetch-data-in-react-hooks-23q1k?file=/src/App.js
Let's talk about the model a bit
export default function App() {
const [data, setDate] = React.useState("");
const [id, setId] = React.useState(1);
const [url, setUrl] = React.useState(
`https://jsonplaceholder.typicode./todos/${id}`
);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
fetch(url)
.then(response => response.json())
.then(json => {
setDate(json);
setIsLoading(false);
});
}, [url]);
return (
<div className="App">
<h1>Fetch data from API in React Hooks</h1>
<input value={id} type="number" onChange={e => setId(e.target.value)} />
<button
onClick={() => {
setIsLoading(true);
setUrl(`https://jsonplaceholder.typicode./todos/${id}`);
}}
>
GO & FETCH
</button>
{isLoading ? (
<p>Loading</p>
) : (
<pre>
<code>{JSON.stringify(data, null, 2)}</code>
</pre>
)}
</div>
);
}
Here I fetched data in first rendering using the initial link, and on each button click instead of calling any method I updated a state that exists in the deps array of effect hook, useEffect
, so that useEffect
runs again.
I think you can achieve the desired behavior easily as
useEffect(() => {
query();
}, [data]) // Only re-run the effect if data changes
For details, navigate to the end of this official docs page.
本文标签:
版权声明:本文标题:javascript - How to prevent useCallback from triggering when using with useEffect (and comply with eslint-plugin-react-hooks)? - 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://roclinux.cn/p/1744010457a2518053.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论