Avoid blocking UI rendering while api fetching data, React - reactjs

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>

Related

How can I make separate components to loading in order?

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.

After useEffect API call, state set by useState for json data being passed to a component as props returns empty array

I'm still a beginner in React and I'm trying to use useEffect to fetch data from an API and then useState to set the state and then pass that state as props to a child component.
But in my child component, it appears as an empty array each time when I do console.log. I understand that on the first render the state of my initial state is an empty array []. But I've been trying to combat this and send the right JSON data but can't seem to do so.
I am trying to do this as I have multiple child components that I wanna send data to.
Below is a workaround I coded up with some digging around but doesn't work either:
const api = 'url string'
const [races, setRaces] = useState([]);
const [races2, setRaces2] = useState([races]);
useEffect(() => {
fetch(api)
.then((resp) => resp.json())
.then((response) => setRaces(response));
}, []);
useEffect(() => {
if (races.length) setRaces2(races);
}, [races]);
<Child data={races2}
But this does not seem work to work either when I do console.log(props.data) in the child component.
This is how normally one would fetch data and try and send the data but in both cases, it's been the same.
const api = 'url string'
const [races, setRaces] = useState([]);
useEffect(() => {
fetch(api)
.then((resp) => resp.json())
.then((response) => setRaces(response));
}, []);
<Child data={races}
Following is a rough flow diagram explaining what I wanna do:
Thank you for your help in advance.
I made this quick example.
Here is what the code does:
Fetching the Data using UseEffect
Storing into State
Passing the State into Component as Props
Fetching the Props and Displaying the data.
Code for App.js
import "./styles.css";
import ChildComponent from "./ChildComponent";
import { useEffect, useState } from "react";
export default function App() {
const [title, setTitle] = useState(null);
// * Init on Page Load
useEffect(() => {
fetchTitle();
}, []);
const fetchTitle = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
const data = await response.json();
setTitle(data.title); //Setting the response into state
};
return (
<div className="App">
<ChildComponent data={title} />
</div>
);
}
Code for ChildComponent.js
export default function ChildComponent({ data }) {
return <div>{data}</div>;
}
I created this Codesandbox. This might help.
https://codesandbox.io/s/elegant-lumiere-cg66nt
Array and object are referential data types, passing as array dependency will not re-run side effect. useEffect dependencies should be primitive data type (string, number, boolean,undefined or null).
useEffect(() => {
if (races.length) setRaces2(races);
}, [races.length])// Dependencies must be primitive data type not referencial.

How to pass data into useState using useEffect - next.js ( swr hook for fetching and cache )

I want to pass data into my useState, the problem is that, useEffect hasn't been able to to perform this operation too well...
This is what i have
const PublicationsHome = ({ data: allPubs }) => {
// All pubs
const { data: Publications }: thePublication = useSWR(
`${process.env.URL}/api/publication`,
{
initialData: allPubs,
revalidateOnFocus: false
}
);
const [pubRequested, setPubRequested] = useState<Ipublication[]>([]);
useEffect(() => {
setPubRequested(Publications);
}, []);
console.log(pubRequested);
`;
return ()
};
export const getStaticProps: GetStaticProps = async () => {
const { data } = await axios.get(`${process.env.URL}/api/publication`);
return {
props: data
};
};
export default PublicationsHome;
So, the logic is quite simple, i'm gettin data by using getStaticProps, that is server side rendering, but i want to use SWR hook, so i'm using initialData so i can use it, and, the las thing is, i'm using useEffect and i want to pass Publications data from swr to my useState, the problem is that, Publications data is slow enough to not be able to pass the data.
As you can see, i have a console.log() and it returns me undefined
What can i do about it, i need that data in my useState, any ideas ?
Thanks people
You need to wait for the data to load.
useEffect(() => {
if(data){
setPubRequested(Publications);
}
}, [data]);

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

How do I fetch data in a React custom hook only once?

I have a custom hook that fetches a local JSON file that many components make use of.
hooks.js
export function useContent(lang) {
const [content, setContent] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
fetch(`/locale/${lang}.json`, { signal: signal })
.then((res) => {
return res.json();
})
.then((json) => {
setContent(json);
})
.catch((error) => {
console.log(error);
});
return () => {
abortController.abort();
};
}, [lang]);
return { content };
}
/components/MyComponent/MyComponent.js
import { useContent } from '../../hooks.js';
function MyComponent(props) {
const { content } = useContent('en');
}
/components/MyOtherComponent/MyOtherComponent.js
import { useContent } from '../../hooks.js';
function MyOtherComponent(props) {
const { content } = useContent('en');
}
My components behave the same, as I send the same en string to my useContent() hook in both. The useEffect() should only run when the lang parameter changes, so seeing as both components use the same en string, the useEffect() should only run once, but it doesn't - it runs multiple times. Why is that? How can I update my hook so it only fetches when the lang parameter changes?
Hooks are run independently in different components (and in different instances of the same component type). So each time you call useContent in a new component, the effect (fetching data) is run once. (Repeated renders of the same component will, as promised by React, not re-fetch the data.) Related: React Custom Hooks fetch data globally and share across components?
A general React way to share state across many components is using a Context hook (useContext). More on contexts here. You'd want something like:
const ContentContext = React.createContext(null)
function App(props) {
const { content } = useContent(props.lang /* 'en' */);
return (
<ContentContext.Provider value={content}>
<MyComponent>
<MyOtherComponent>
);
}
function MyComponent(props) {
const content = useContext(ContentContext);
}
function MyOtherComponent(props) {
const content = useContext(ContentContext);
}
This way if you want to update the content / language / whatever, you would do that at the app level (or whatever higher level you decide makes sense).

Resources