linuxfever
linuxfever

Reputation: 3823

Serialisation of react hooks for fetching data

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

Answers (1)

Ante Gulin
Ante Gulin

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

Related Questions