I'm trying to rewrite my application from redux to the new context + hooks, but unfortunately I'm having a hard finding a good way to handle series of synchronous side-effects that are depending on the response of the previous.
In my current redux application I make heavy use of synchronous/chained actions and API requests which I typically handle through redux-saga or thunks. So when the response of the first API request is returned, that data is used for the next API request etc.
I've made a custom hook "useFetch" (in this example it does not do much, since it's a simplified version, also I had to make a small adjustment for it to work on codesandbox - see the code below). The problem with that is that due to the "rules of hooks", I cannot use a custom hook inside the useEffect hook. So how to await the response of the first request before doing the next etc, if you have your own hook for fetching data? And even if I ended up giving up on useFetch abstraction and create a vanilla fetch request, how to avoid ending up with a bloated mess of many useEffects hooks? Can this be done a little more elegantly, or is context + hooks still to premature to compete with redux saga/thunk for handling side effects?
The example code below is kept very simple. What it should try to simulate is that:
Here's the code. Added a delay to useFetch hook to simulate latency in real world:
import React, { useEffect, useState } from "react";
import { render } from "react-dom";
import "./styles.css";
const useFetch = (url, delay = 0) => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
// const result = await fetch(url, {
// method: "GET",
// headers: { "Content-Type": "application/json" }
// });
//const response = await result.json();
const response = await import(url);
setTimeout(function() {
setData(response);
}, delay);
};
fetchData();
}, [url]);
return data;
};
function App() {
const [person, setPerson] = useState();
const [job, setJob] = useState();
const [collegues, setCollegues] = useState();
// first we fetch the person /api/person based on the jwt most likely
const personData = useFetch("./person.json", 5000);
// now that we have the person data, we use the id to query for the
// persons job /api/person/1/jobs
const jobData = useFetch("./job.json", 3000);
// now we can query for a persons collegues at job x /api/person/1/job/1/collegues
const colleguesData = useFetch("./collegues.json", 1000);
console.log(personData);
console.log(jobData);
console.log(colleguesData);
// useEffect(() => {
// setPerson(useFetch("./person.json", 5000));
// }, []);
// useEffect(() => {
// setJob(useFetch("./job.json", 3000));
// }, [person]);
// useEffect(() => {
// setCollegues(useFetch("./collegues.json",1000));
// }, [job]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Running example: https://codesandbox.io/s/2v44lron3n?fontsize=14 (you might need to make a change - space or remove a semicolon - to make it work)
Hopefully something like this (or a better solution) is possible, or I will simply not be able to migrate from the awesome redux-saga/thunks to context + hooks.
Best Answer: https://www.youtube.com/watch?v=y55rLsSNUiM
You can't use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component.
If you're familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount , componentDidUpdate , and componentWillUnmount combined. There are two common kinds of side effects in React components: those that don't require cleanup, and those that do.
Don't call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.
Hooks wont replace the way you handle async actions, they are just an abstraction to some things you were used to do, like calling componentDidMount
, or handling state
, etc.
In the example you are giving, you don't really need a custom hook:
function App() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const job = await import("./job.json");
const collegues = await import("./collegues.json");
const person = await import("./person.json");
setData({
job,
collegues,
person
})
};
fetchData()
}, []);
return <div className="App">{JSON.stringify(data)}</div>;
}
That being said, maybe if you provide an example of an actual redux-saga or thunks code you have, that you want to refactor, we can see what the steps are to accomplish that.
Edit:
That being said if you still want to do something like this, you can take a look at this:
https://github.com/dai-shi/react-hooks-async
import React from 'react';
import { useFetch } from 'react-hooks-async/dist/use-async-task-fetch';
const UserInfo = ({ id }) => {
const url = `https://reqres.in/api/users/${id}?delay=1`;
const { pending, error, result, abort } = useFetch(url);
if (pending) return <div>Loading...<button onClick={abort}>Abort</button></div>;
if (error) return <div>Error:{error.name}{' '}{error.message}</div>;
if (!result) return <div>No result</div>;
return <div>First Name:{result.data.first_name}</div>;
};
const App = () => (
<div>
<UserInfo id={'1'} />
<UserInfo id={'2'} />
</div>
);
EDIT
This is an interesting approach https://swr.now.sh/#dependent-fetching
This is a common scenario in real life,where you want to wait for the first fetch is finished and then do the next fetch.
Please check out the new codesandbox: https://codesandbox.io/s/p92ylrymkj
I used generator when you do fetch request. The data is retrieved in the correct order. After you click fetch data button, go to console and have a look.
Hope this is what you are looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With