Reputation: 3823
I've just stated experimenting with react hooks and I haven't been able to find a way to answer the following. Assume I have two custom hooks for fetching data. For simplicity, assume the first one takes no argument and the second takes one argument as follows:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// fetching takes 3 secs
function useFetch1() {
const [text, setText] = useState("")
useEffect(() => {
const test = async () => {
await sleep(3000)
setText("text1")
}
test()
}, [])
return text
}
// fetching takes 1 sec
function useFetch2(input) {
const [text, setText] = useState("")
useEffect(() => {
const test = async () => {
await sleep(1000)
setText(input + "text2")
}
test()
}, [input])
return text
}
This allows me to use each hook independently of one another. So far, so good. What happens though when the argument to the second fetch depends on the output of the first fetch. In particular, assume that my App component is like this:
function App() {
const text1 = useFetch1()
const text2 = useFetch2(text1) // input is dependent on first fetch
// renders text2 and then text1text2
return (
<div>
{text2}
</div>
)
}
In this case, the renderer will first display "text2" and then (2 seconds later), it will display "text1text2" (which is when the first fetch finished). Is there a way to use these hooks in a way that ensures that useFetch2
will only be called after useFetch1
is finished?
Upvotes: 0
Views: 810
Reputation: 2020
You can achieve the behavior you're looking for, however: Hook functions, by design, have to be called on every render - You are not allowed to conditionally call hooks, React can't handle that.
Instead of conditionally calling hooks, you can implement the logic within hooks, for example, useFetch1
can return the state of the request, which can be used in another hook to conditionally do something based on it.
Example:
function useFetch1() {
const [text, setText] = useState("")
// a bool to represent whether the fetch completed
const isFinished = useState(false)
useEffect(() => {
const test = async () => {
await sleep(3000)
setText("text1")
}
test()
}, [])
return {text, isFinished} // Return the isFinished boolean
}
function useFetch2(input, isFetch1Finished) {
const [text, setText] = useState("")
useEffect(() => {
if (!isFetch1Finished) {
return; // fetch1 request not finished, don't fetch
}
const test = async () => {
await sleep(1000)
setText(input + "text2")
}
test()
}, [input])
return text
}
function App() {
const {text: text1, isFinished} = useFetch1()
const text2 = useFetch2(text1, isFinished) // input is dependent on first fetch, but is aware whether the first fetch is finished or not.
return (
<div>
{text2}
</div>
)
}
Now you can use the isFinished
boolean flag from the useFetch1
in your second fetch, to determine whether to do something or not.
A better approach
It all depends on your use-case, but you might not want to couple the logic of 2 hooks together. I suggest it would be better to have useFetch2
in a separate component, which is conditionally rendered based on the status useFetch1
.
return (
{isFetch1Finished && <SecondComponent text={text1}>}
)
Now, you simplified your hooks logic by conditionally rendering the second component once the first request is finished. This is a better design compared to the first approach.
Do you even need 2 hooks? Ask yourself if you even need 2 hooks to begin with, it would be trivial to implement the synchronous behavior in a single hook with the use of promises.
Example:
useFetch(() => {
const [state, setState] = useState("");
useEffect(() => {
fetch('http://..').then(res1 => {
fetch('http://..').then(res2 => {
// In this scope you have both `res1` and `res2`.
setState(res1 + res2);
})
})
}, [])
return state;
})
Upvotes: 2