Reputation: 1563
Example to make the context clear:
I am trying to render a component with two sets of data coming from API calls. I am also returning early if the first API call fails. The second API call depends on the data of the first API result. I don't want to combine both effects because that would mean the whole of component does not render till I get bot API results.
This is the psuedo code
const DataList = () => {
const [dataFromEffect1, setDataFromEffect1] = useState([]);
const [dataFromEffect2, setDataFromEffect2] = useState([]);
useEffect(() => {
const callApi1 = async () => setDataFromEffect1(await (await fetch('/api1')).json());
callApi1();
}, []);
// early return so that all the complex logic below is not called on ever render
if (!dataFromEffect1) return <div>No Data1</div>;
const data1 = complexMassagingOver(dataFromEffect1); // data1 to be used in second effect
useEffect(() => {
const callApi2 = async () => setDataFromEffect2(await (await fetch('/api2', { headers: data1 })).json());
callApi2();
}, [data1]);
return (
<div>
{/* no need to null check here, because of the early return on top */}
{dataFromEffect1}
{/* null check required here, so that it doesnt render this child component to not render till we get the data for it */}
{dataFromEffect2 ? (
<div>
{dataFromEffect2}
</div>
) : null}
</div>
);
};
Problem
The above code does not work because you cannot add a useEffect conditionally (the early return messes it up) Trying to find the best workaround for this problem.
Upvotes: 0
Views: 436
Reputation: 1563
Two options ( I went with the second one for now):
Upvotes: 0
Reputation: 433
You can try something like this. It will kill both useEffects, but it will not run the second one unless it retrieved data from the first. Also, I did not fix this in your code but you should not use async
code within useEffect
. This can lead to memory leaks and unnexpected bugs. You are also not cleaning up the fetch from within the useEffect
. Academind has a nice blog explaining how to fix this and what will happen if you keep the code like this https://academind.com/tutorials/useeffect-abort-http-requests/
const DataList = () => {
const [dataFromEffect1, setDataFromEffect1] = useState([]);
const [dataFromEffect2, setDataFromEffect2] = useState([]);
useEffect(() => {
const callApi1 = async () => setDataFromEffect1(await (await fetch('/api1')).json());
callApi1();
}, []);
useEffect(() => {
if(!dataFromEffect1.length) return;
const data1 = complexMassagingOver(dataFromEffect1); // data1 to be used in second effect
const callApi2 = async () => setDataFromEffect2(await (await fetch('/api2', { headers: data1 })).json());
callApi2();
}, [dataFromEffect1]);
// early return so that all the complex logic below is not called on ever render
if (!dataFromEffect1) return <div>No Data1</div>;
return (
<div>
{/* no need to null check here, because of the early return on top */}
{dataFromEffect1}
{/* null check required here, so that it doesnt render this child component to not render till we get the data for it */}
{dataFromEffect2 ? (
<div>
{dataFromEffect2}
</div>
) : null}
</div>
);
};
Upvotes: 1
Reputation: 12807
Just creat a component for dataFromEffect2
:
const DataList = () => {
const [dataFromEffect1, setDataFromEffect1] = useState([]);
const [dataFromEffect2, setDataFromEffect2] = useState([]);
useEffect(() => {
const callApi1 = async () =>
setDataFromEffect1(await (await fetch("/api1")).json());
callApi1();
}, []);
if (!dataFromEffect1) return <div>No Data1</div>;
const data1 = complexMassagingOver(dataFromEffect1); // data1 to be used in second effect
return (
<div>
{dataFromEffect1}
<Component data1={data1} dataFromEffect2={dataFromEffect2} setDataFromEffect2={setDataFromEffect2} />
</div>
);
};
const Component = ({ data1, dataFromEffect2, setDataFromEffect2 }) => {
useEffect(() => {
const callApi2 = async () =>
setDataFromEffect2(
await (await fetch("/api2", { headers: data1 })).json()
);
callApi2();
}, [data1]);
return <div>{dataFromEffect2}</div>;
};
Upvotes: 1