React hooks with AJAX requests - reactjs

I am using React and I was wondering if I am doing things correctly.
I want to make multiple ajax requests in React for multiple datalists for inputboxes.
I do something like this:
This is my fetch function
function GetData(url, processFunc) {
...
jquery ajax request using url
...
if (status === 'success') {
if (processFunc) {
processFunc(data);
}
}
}
A solution which only displays the results that were the fastest.
function ComponentA() {
const [ajaxDataA, setAjaxDataA] = useState(null);
const [ajaxDataB, setAjaxDataB] = useState(null);
const [ajaxDataC, setAjaxDataC] = useState(null);
const [dataA, setDataA] = useState(null);
...dataB..
...dataC..
const exampleFunctionA = function(data) {
..processes data into result
setDataA(result);
}
const exampleFunctionB = ....
const exampleFunctionC = ...
useEffect( () => {
GetData(url_1, exampleFunctionA);
GetData(url_2, exampleFunctionB);
GetData(url_3, exampleFunctionC);
}, []);
return (<>
...
{dataA}
{dataB}
...
<>);
}
B Solution is why Im asking this question. This works fine but I'm not sure it is correct or this is how hooks were meant to use.
function ComponentA() {
const [ajaxDataA, setAjaxDataA] = useState(null);
const [ajaxDataB, setAjaxDataB] = useState(null);
const [ajaxDataC, setAjaxDataC] = useState(null);
const [dataA, setDataA] = useState(null);
...dataB..
...dataC..
useEffect( () => {
GetData(url_1, setAjaxDataA);
GetData(url_2, setAjaxDataB);
GetData(url_3, setAjaxDataC);
}, []);
useEffect( () => {
..processes data into result
setDataA(result);
}, [ajaxDataA]);
..useEffect ... ,[ajaxDataB] ...
... [ajaxDataC] ...
return (<>
...
{dataA}
{dataB}
...
<>);
}
I have found this solution so I dont repeat the logic. I dont use this solution because I have to process the data so basically instead of
const a = GetData(..., processFunc);
I'd use this solution. And so I would still need the useEffects that watch when it refreshes
const a = useFetch(...)
Ajax request won't display in react render function
so basically the question is:
Is solution B a good solution?

Okay well.. I leave the question up if anyone else had gone on this awful path. So basically I was waiting for the AJAX requests to finish loading with the useEffect hook
At first it looked pretty logical:
I send the request.
when it's done it calls the useState hooks' setter function
the state variable updates
useEffect hook triggers
and I do something with the arrived data in the useEffect body
This was major dumb dumb
since it refreshed the state like it was forced to, having a gun pointed at its head
It caused inconsistencies: A child component would want to use the AJAX data before it arrived. It was like a random number generator. Half the time the page loaded and the other half it died running into nullreference exceptions.
solution: Promise

Related

Set state on changed dependencies without additional render (having a loader without additional renders)

I have a huge component which renders long enough to be noticeable by user if it happens too often.
Its contents are loaded asynchronously (from a server) every time when a function "getData" changes, showing a loader during the wait.
I'm trying to write a code which will render the component only 2 times when the function changes - first time to show the loaded and the second time to display the data.
Using a standard useEffect causes it to be rendered 3 times, first of which doesn't change anything visible for the user.
type tData = /*some type*/
const Component = (props: {getData: () => Promise<tData[]>}) => {
const {getData} = props;
const [loader, setLoader] = useState(true);
const [data, setData] = useState<tData[]>([]);
useEffect(() => {
setLoader(true);
setData([]);
getData().then((newData) => {
setData(newData);
setLoader(false);
});
}, [getData, setLoader, setData]);
// ... the rest of the component (that doesn't use the function getData)
};
The three renders are:
getData has changed - the effect runs and changes the states but there is 0 changes visible to the user (this is the render I want to get rid of)
loader has changed to true and data has changed to [] - a useful render that actually changes the UI
loader has changed to false and data has changed to a new value - a useful render that actually changes the UI
How could I modify this code to not have a barren render when getData changes?
this is a very common use case.
what you can do here is a if() in the useEffect().
using a .then() complicates things. use async await to make it asynchronus.
useEffect(() => {
async function loadData() {
const res = getData()
return res
}
let data = await loadData()
//check if it's undefined
if (data && data!=='init') {
setStateData(data)
}
}, [])
this is how i'd implement something like this. however it seems like the data fetching for you is somewhat complicated then this, but essentially, never fetch data synchronously unless you absolutely have to, and check if it's undefined in useEffect before setting the state variable so that it's only set once.

useEffect on infinite loop using async fetch function

I am trying to understand why the following useEffect is running in an infinite loop. I made the fetchSchedule helper function to call the getSchedule service (using Axios to query the API endpoint). Reason I did not define this function inside the useEffect hook is because I would like to alternatively also call it whenever the onStatus function is invoked (which toggles a Boolean PUT request on a separate endpoint).
The eslinter is requiring fetchSchedule be added to the array of dependencies, which seems to be triggering the infinite loop.
The way it should work is fetching the data from the database on first render, and then only each time either the value prop is updated or the onStatus button is toggled.
So far my research seems to point that this may have something to do with the way useEffect behaves with async functions and closures. I’m still trying to understand Hooks and evidently there’s something I’m not getting in my code…
import React, { useEffect, useCallback } from 'react';
import useStateRef from 'react-usestateref';
import { NavLink } from 'react-router-dom';
import { getSchedule, updateStatus } from '../../services/scheduleService';
import Status from './status';
// import Pagination from './pagination';
const List = ({ value }) => {
// eslint-disable-next-line
const [schedule, setSchedule, ref] = useStateRef([]);
// const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
}, [value, setSchedule]);
const onStatus = (id) => {
updateStatus(id);
fetchSchedule();
console.log('fetch', ref.current[0].completed);
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
return (...)
Update March 2021
After working with the repo owner for react-usestateref, the package now functions as originally intended and is safe to use as a replacement for useState as of version 1.0.5. The current implementation looks like this:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(state);
var dispatch = React.useCallback(function(val) {
ref.current = typeof val === "function" ?
val(ref.current) : val;
setState(ref.current);
}, []);
return [state, dispatch, ref];
};
You would be fine if it weren't for this react-usestateref import.
The hook returns a plain anonymous function for setting state which means that it will be recreated on every render - you cannot usefully include it in any dependency array as that too will be updated on every render. However, since the function is being returned from an unknown custom hook (and regardless, ESLint would correctly identify that it is not a proper setter function) you'll get warnings when you don't.
The 'problem' which it tries to solve is also going to introduce bad practice into your code - it's a pretty way to avoid properly handling dependencies which are there to make your code safer.
If you go back to a standard state hook I believe this code will work fine. Instead of trying to get a ref of the state in onStatus, make it async as well and return the data from fetchSchedule as well as setting it.
const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
return data;
}, [value]);
const onStatus = async (id) => {
updateStatus(id);
const data = await fetchSchedule();
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
Alternatively, although again I wouldn't really recommend using this, we could actually write a safe version of the useStateRef hook instead:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(defaultValue);
ref.current = state;
return [state, setState, ref];
}
A state setter function is always referentially identical throughout the lifespan of a component so this can be included in a dependency array without causing the effect/callback to be recreated.

React Hooks infinite loop Memory leak?

Hello i have a problem with infinite loops. All the exampels i look at makes the loop go away. But then it wont update my view if i dont refresh. I want the items to appear when something gets added. i thought my example would work but it does not. I dont know how to fix it
const [ReadBookingTimes, SetBookingTimes] = useState([]);
const [readStatus, setStatus] = useState("");
const getBookingTimes = async () => {
try {
const response = await Axios.get(`${API_ENDPOINT}`);
console.log(response);
// debugger;
SetBookingTimes(response.data);
// setStatus("Connection sucsessfull");
} catch (err) {
setStatus("Error getting BookingTimes");
}
};
//reupdate State and prevent infinite loop
useEffect(() => {
getBookingTimes(ReadBookingTimes);
}, [ReadBookingTimes]); //);
Your useEffect has a dependancy ReadBookingTimes which updates every time. Bascially, you update the ReadBookingTimes every single time over and over again.
What you should do is to add a condition to check if your data is already loaded and if so, don't call the function:
const [ReadBookingTimes, SetBookingTimes] = useState([]);
const [readStatus, setStatus] = useState("");
const [dataLoaded, setDataLoaded] = useState(false);
const getBookingTimes = async () => {
// Your fetch code
setDataLoaded(true)
}
useEffect(() => {
// don't go any further of data is already loaded.
if (dataLoaded) return;
getBookingTimes(ReadBookingTimes);
}, [ReadBookingTimes, dataLoaded]);
Now you won't get stuck in a loop. And anytime you want to reload the data, you can change the dataLoaded to false and useEffect will call the getBookingTimes function again.
In case you want to get the data every 2 minutes for example, you can add a timeout to change the dataLoaded state every time causing another data reload.

Custom hooks not working properly with useEffect

I wasn't sure on a title for this issue, but I can better explain it here. I have a custom hook that is relying on some information. Part of the information must rely on an async call.
I have three situations happening.
Tried conditionally rendering the custom hook, but react does not like that due to rendering more hooks on a different render.
The custom hook is only mounting once and not passing in the updated information it needs.
I tried passing the dependency to the custom hook and it causes an infinite loop.
Here is a small example of what I'm doing.
Custom Hook:
export function useProducts(options){
const [products, setProducts] = useContext(MyContext)
useEffect(() => {
// only gets called once with `options.asyncValue` === null
// needs to be recalled once the new value is passed in
const loadProducts = async () => {
const data = await asyncProductReq(options)
setProducts(data)
}
loadProducts()
}, []) // if I pass options here it causes the infinite loop
return [products, setProducts]
}
Inside function calling:
export function(props){
const [asyncValue, setValue] = useState(null)
useEffect(() => {
const loadValue = async () => {
const data = await asyncFunc()
setValue(data)
}
loadValue()
}, []}
const options = {...staticValues, asyncValue}
const [products] = useProducts(options)
return (
<h2>Hello</h2>
)
}
I know that I need to pass the options to be a dependency, but I can't figure out why it's causing an infinite reload if the object isn't changing once the async call has been made inside the func.
You were correct in adding options in the dependencies list for your custom hook.
The reason it is infinitely looping is because options IS constantly changing.
The problem is you need to take it one step further in the implementation and make use of the useMemo hook so options only changes when the async value changes, instead of the whole component changing.
So do this:
const options = React.useMemo(() => ({...staticValues, asyncValue}), [asyncValue])

Correct way to update react component from secondary source

I'm kind of new with React Hooks and I've encountered a problem when making a component. My App has a simple form with a few fields and a "Calculate" button which fetches info from an API and displays the results on a table. The app uses two currencies, they can be switched with a pair of buttons. What I want is to update the table(re fetch the data) when currency is changed, but only is there was already something calculated via the main "Calculate" button before changing the currency. My component is something along the lines of:
const ProductionCosts = () => {
const [data, setData] = useState({});
const [useXCurrency, setUseXCurrency] = useState(true);
const calcCosts = useCallback(async () => {
fetchCalcData(args);
}, [args]);
useEffect(() => {
if (Object.keys(data).length > 0) //check data isn't empty, hence it was already calculated
fetchCalcData();
}, [useXCurrency]);
return (
......
);
};
Doing something similar to the above works, but the linter will say that data needs to be in the dependency list of the useEffect, but adding it will result on a loop given that fetchCalcData modifies data and triggers the effect, I DO know that the linter suggestions aren't absolute, but at the same time I know that there must be a better way. So besides adding Boolean flags or something like that, there is a better approach to this case?
Typically you want to use a refenence with the initial value and update it on success, on next useEffect the condition will be falsy:
const ProductionCosts = () => {
const [data, setData] = useState({});
const dataRef = useRef(data);
useEffect(() => {
if (Object.keys(dataRef.current).length > 0) {
const data = // fetch your data
dataRef.current = data;
}
}, [useXCurrency]);
return <></>;
};

Resources