How can I make separate components to loading in order? - reactjs

For example, I write this in the sandbox for demonstration only, I don't quite remember the structure of my old project, but I remember that I tried something like this, this is written in 1 component, but I think it's not very good practice, because later on, I remember that I split my components into many more for easier management, but at that point, I can't make it load in order, so whenever the Home components are called, it just fetches everything instead of in order, which makes my page load very long due to it try to fetch everything from every component that exists in the Home component.
import {React, useState, useEffect} from "react"
import axios from "axios"
function Home() {
const [loadingSlider, setLoadingSlider] = useState(true)
const [loadingCategory, setLoadingCategory] = useState(true)
const [loadingStuff, setLoadingStuff] = useState(true)
const [sliderData, setSliderData] = useState()
const [categoryData, setCategoryData] = useState()
const [stuffData, setStuffData] = useState()
useEffect(() => {
const fetchSlider = async () => {
const response = await axios.get("sliderUrl")
setSliderData(response.data)
setLoadingSlider(false)
}
const fetchCategory = async () => {
const response = await axios.get("categoryUrl")
setCategoryData(response.data)
setLoadingCategory(false)
}
const fetchStuff = async () => {
const response = await axios.get("stuffUrl")
setStuffData(response.data)
setLoadingStuff(false)
}
fetchSlider()
fetchCategory()
fetchStuff()
} , [])
return (
<>
{
loadingSlider ? "Loading slider" : {sliderData}
}
{
loadingCategory ? "Loading category" : {categoryData}
}
{
loadingStuff ? "Loading stuff" : {stuffData}
}
</>
)
}
So with this practice, if I split 3 sliderData, categoryData, stuffData into 3 different components, and I just want to call it in Home component for easier management, how can I let it load in order?
Each component will fetch inside of it separately.
For example:
function Home() {
return (
<>
<SliderComponent/>
<CategoryComponent/>
<StuffComponent/>
{/* <More components might be added in the future/> */}
</>
)
}
What do I need to do so it can load in order, and not load all of the components at the same time? I want it done fetching the slider first, then it'll proceed to fetch the category, and then so on.

You could do like this:
useEffect(() => {
fetchSlider();
} , [])
const fetchSlider = async () => {
const response = await axios.get("sliderUrl")
if(response.status === 200){
// Call another method you want to load
setSliderData(response.data)
setLoadingSlider(false)
fetchCategory(); // Once Slide data is set call category method
}
}
const fetchCategory = async () => {
const response = await axios.get("categoryUrl")
if(response.status === 200){
// Call another method you want to load
setCategoryData(response.data)
setLoadingCategory(false)
fetchStuff(); // Once Category data is set call stuff method
}
}
const fetchStuff = async () => {
const response = await axios.get("stuffUrl")
setStuffData(response.data)
setLoadingStuff(false)
}
In Short Once you fetch data from method with status 200 then only call another method. And Its your option also even if one method might failed whether you cant to call next method or you want to show error. Hope It might help with your problem.

Related

Avoid blocking UI rendering while api fetching data, React

I am working on React app which fetches large data (Thousands of records) and then render it. Until api is fetching data, UI keep blocked and does not display anything. I am creating chunks in size of 10 for fetching api using Promise.allSettled and combining them all.
useEffect(() => {
fetchBatchedData()
},[])
fetchBatchedData is an async function and sets data in redux store only, I don`t need that data in UI at loading time. Until I get all the data, UI display nothing. How can I fetch data in background without blocking component rendering?
You could use a useState like this:
const [data, setData] = useState();
useEffect(() => {
const loadData = () => {
const tempData = fetchBatchedData();
setData(tempData)
}
},[])
if(!data){
return(
<h1>Loading...</h1>
)
} else {
return(
<h1>Got the data!</h1>
)
}
Maybe we can defer rendering the data? There is something called requestIdleCallback here.
Something like this maybe?
import { useState, useEffect } from 'react';
function RenderDeferred({ children, timeout }) {
const [render, setRender] = useState(false);
useEffect(() => {
if (render) setRender(false);
const id = requestIdleCallback(() => setRender(true), { timeout: idleTimeout });
return () => cancelIdleCallback(id);
}, [idleTimeout]);
if (!render) return null;
return children;
}
And then use it like this:
<RenderDeferred timeout={3000}>
<YourComponent />
</RenderDeferred>

Calling my Custom Fetch Components Directly Without Having to use a useState based variable

I am still learning React JS, so, I dont know if this is the way React JS works (so, I am doing it the right way), or I am doing the wrong way, and hence need some help.
I am using a three layer network abstraction for making fetch calls.
First, here is my raw network call making component.
export function useFetch(uri: string, something?: string, getThetoken?: any,
setrequesttype?: string,body? : Object,triggerapi? : string) {
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(true);
//by default we assume a GET request
var requesttype = "GET";
if (setrequesttype !== undefined) {
requesttype = setrequesttype;
}
useEffect(() => {
if (!uri) return;
if (something === "authorized" && !getThetoken) return;
fetch(uri, {
method: requesttype,
headers: {
Authorization: `Bearer ${getThetoken}`,
}
}
)
.then(data => data.json())
.then(setData)
.then(() => setLoading(false))
.catch(setError);
}, [uri, something, getThetoken,requesttype,triggerapi]);
return {
loading,
data,
error
};
}
Here is my middle layer for networking. A middleman between components and the above useFect network calling component.
const Fetch: FC<FetchProps> = ({
uri,
renderSuccess,
loadingFallback = <p>---</p>,
renderError = (error: any) => (
<div>
</div>
),
something,
getThetoken,
setrequesttype,
body,
triggerapi
}: FetchProps) => {
console.log("inside Fetch");
const { loading, data, error } = useFetch(uri, something, getThetoken,setrequesttype,body,triggerapi);
if (loading) return loadingFallback;
if (error) return renderError(error);
if (data) return renderSuccess({ data });
}
export default Fetch;
I built these components, and they work just fine. However, I do run into issues, like this. Here is a component that is able to successfully use it.
const RandomQuote = () => {
const [something, setsomething] = useState("hello");
function changeSomething() {
if (something === "bird") {
setsomething("hello");
} else {
setsomething("bird");
}
}
return (
<Fragment>
<RandomQuoteHelper something={something} />
<Button color="primary" onClick={changeSomething}>
{buttondisplaystring}
</Button>
</Fragment>
);
};
export default RandomQuote;
and here is the RandomQuoteHelper which makes the call to the api, and renders the result.
function RandomQuoteHelper({ something }: RandomQuoteHelperProps) {
//TODO - these things should be coming from a config file
const baseURL = "https://localhost:44372";
const endPoint = "/api/UserNotLoggedIn/GetHoldOfthem";
var url2 = baseURL + endPoint;
//manually set the request type
const setrequesttype = "GET";
return (
<Fetch
uri={url2}
renderSuccess={QuoteDetails}
something={something}
setrequesttype = {setrequesttype}/>
);
}
The above code works, and I get to call my API just fine. Unfortunately, I have to use the function 'changeSomething' to force a state change, which then calls the Fetch/useFetch components.
In the current form, the button click cannot directly call my network components. Not unless a dependent value is forcibly changed. I built it this way, by using a react js book. The author lady, built it this way, which suited her example perfectly. However, now, as I work on an actual project, it does not seem like the best approach. That means, throughout my project, anytime i want to trigger a api call, I have to keep using this 'changeSomething' type setup.
So, is there any way, I can directly call my fetch component?

Make a second API call and return data

I need some help figuring out an issue.
I'm working on a web app and basically I make an API call which is an array of data to get someone's details and return them on the page, but for one param (editedBy) I get the person' id. So, I was asked to make another API call from where I can get that person's name.
import React from 'react';
import {getData} from '.api/calls';
import { connect } from "react-redux";
import {getEditedBy} from './api/calls';
const PersonDetails = ({userId}) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [editedBy, setEditedBy] = useState('')
const setDetails = user => {
setFirstName(user.firstName);
setLastName(user.lastName);
}
useEffect(() => {
getData(userId).then(res => {
setDetails(res.data)
});
getEditedBy(userId).then(res => {
setEditedBy(res.fullName)
});
}, [userId])
return (
<div>
<p>First Name: {firstName}</p>
<p>LastName: {lastName}</p>
<p>Edited By: {editedBy}</p>
</div>
)
}
export default connect(
state => ({
userId: state.user.userId
})
)(PersonDetails);
How can I return the person's full name and keep that value in place, instead of the id? Any help is appreciated.
This should cover your case:
useEffect(() => {
async function getUserDetailsAsync() {
const userData = (await getData(userId)).data;
const editedBy = (await getEditedBy(userData.editedBy)).data;
setDetails(userData);
setEditedBy(editedBy.fullName);
}
getUserDetailsAsync();
}, [userId]);
If you're not a fan of the await syntax for some reason you can go with the .then chaining but in any case you should make the second API call dependent on the first one.
useEffect(() => {
getData(userId).then(res => {
const userData = res.data;
getEditedBy(userData.editedBy).then(res => {
const editedBy = res.data;
setDetails(userData);
setEditedBy(editedBy.fullName);
});
});
}, [userId]);
In your current solution, you're firing 2 requests simultaneously which will cause completely nondeterministic result for you. First of all, you're querying for the fullName not of the person who edited the post, but for the same person you just queried(if I understood your context correctly). Second, your state will be updated at different times due to the independent requests. Third - one of the requests may fail while the other succeed, thus leaving your component with inconsistent state. Furthermore, you might have problems with trying to update an already unmounted component and you should have this in mind.

How can I re-fetch an API using react hooks

devs,
I have decided to finally learn react hooks with what I thought would be a simple project. I can't quite figure out how I re-fetch an API using react hooks. Here is the code I have so far.
import React, { useState, useEffect } from "react"
import useFetch from "./utils/getKanya"
const kanye = "https://api.kanye.rest"
const Index = () => {
let [kanyaQuote, setKanyeQuote] = useState(null)
let data = useFetch(kanye)
const getMore = () => {
setKanyeQuote(useFetch(kanye))
}
return (
<>
<h1>Welcome to Next.js!</h1>
<p>Here is a random Kanye West quote:</p>
{!data ? <div>Loading...</div> : <p>{!kanyaQuote ? data : kanyaQuote}</p>}
<button onClick={getMore}>Get new quote</button>
</>
)
}
export default Index
I get the kanyeQuote state value to null
I fetch the initial data
I either show "Loading..." or the initial quote
I am trying to set up a button to re-fetch the API and store the data in kanyeQuote via getKanyeQuote (setState)
This is the error I get Error: Invalid hook call...
I would greatly appreciate any guidance you can provide on this.
The issue here is, that you can only use hooks directly inside the root of your component.
It's the number 1 'rule of hooks'. You can read more about that here
const getMore = () => {
setKanyeQuote(useFetch(kanye) /* This cannot work! */)
}
There are a few ways you could work around that. Without knowing the internal logic in your useFetch-hook I can only assume you are able to change it.
Change hook to handle its state internally
One way to work around that would be to change the logic of your custom useFetch hook to provide some form of function that fetches the data and updates the state internally. It could then look something like this:
const { data, doFetch } = useFetch(kanye);
useEffect(() => {
doFetch(); // initialFetch
}, []);
const getMore = () => {
doFetch();
};
// ...
You would then need to change the internal logic of your useFetch-hook to use useState internally and expose the getter of it. It would look something like this:
export const useFetch = (url) => {
const [data, setData] = useState(null);
const doFetch = () => {
// Do your fetch-Logic
setData(result);
};
return { data, doFetch };
};
Change hook not to handle any state at all.
If you only want to manage the state of the loaded data in the parent component, you could just provide the wrapped fetch function through the hook; Something like that:
const doFetch = useFetch(kanye);
const [data, setData] = useState(null);
useEffect(() => {
setData(doFetch()); // initialFetch
}, []);
const getMore = () => {
setData(doFetch())
};
// ...
You would then need to change the internal logic of your useFetch-hook to not have any internal state and just expose the wrapped fetch. It would look something like this:
export const useFetch = (url) => {
const doFetch = () => {
// Do your fetch-Logic
return result;
};
return doFetch;
};

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?
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;
})

Resources