React infity loop on function call - javascript

i've a (certainly) stupid problem.
my function getDataTbable is called in infinity loop I don't understand why... So the request is infinitive called.
export const TableResearch = ({setSelectedSuggestion,setImages}) => {
const [research, setResearch] = useState('');
const [suggestions, setSuggestions] = useState ([]);
const [table, setTable]= useState ([]);
const getDataTable = async () => {
const {data} = await jsonbin.get('/b/5f3d58e44d93991036184474');
setTable(data);
console.log(table)
};
getDataTable();

That is because the TableResearch function is called multiple times (every time this component is rendered). If you want to run a function only when the component is mounted, you'll have to use useEffect. Here is an example:
useEffect(() => {
const {data} = await jsonbin.get('/b/5f3d58e44d93991036184474');
setTable(data);
}, []);
The second parameter [] passed to useEffect is important. It makes the function run only once.
You can learn more about useEffect from HERE

The component re-renders every time you change it's state (setTable).
You should use useEffect to only execute your function the first time it renders.
Also you might encounter this warning:
Warning: Can't perform a React state update on an unmounted component.
if the async call finishes after component has unmounted. To account for that, write useEffect like this:
// outside component
const getDataTable = async () => {
const { data } = await jsonbin.get("/b/5f3d58e44d93991036184474");
return data;
};
useEffect(() => {
let mounted = true;
getDataTable()
.then(data => {
if (!mounted) return;
setTable(data);
});
return () => {
mounted = false;
};
}, []);

You can try this
useEffect(() => {
const getDataTable = async () => {
const { data } = await jsonbin.get("/b/5f3d58e44d93991036184474");
setTable(data);
console.log(table);
};
getDataTable();
}, []); // [] makes it runs once

Yes, getDataTable(); is being executed everytime the view is rendered, including when the data is returned.
Try wrapping getDataTable() like this:
if (!table.length) {
getDataTable()
}
But you will need to handle the case for if the requests returns no results, in which case it will still run infinitely:
const [table, setTable]= useState([]);
const [loading, setLoading]= useState();
const getDataTable = async () => {
setLoading(true);
const {data} = await jsonbin.get('/b/5f3d58e44d93991036184474');
setTable(data);
setLoading(false);
};
if (typeof loading === 'undefined' && !table.length) {
getDataTable();
}

Related

Why am i getting and empty array when fetching an api with react hooks?

I am new with react hooks, i'm trying to get info from an API but when i do the request i get 2 responses first an empty array and then the data of the API, why am i getting that empty array! , this is my first question, i'm sorry.
Thanks for helping me !
import {useState, useEffect} from 'react';
const getSlides = (API) => {
const[data,setData] = useState([]);
const getData = () =>
fetch(`${API}`)
.then((res) => res.json())
useEffect(() => {
getData().then((data) => setData(data))
},[])
return data
}
export default getSlides;
The useEffect() hook runs after the first render. Since you've initialized the data state with an empty array, the first render returns an empty array.
If you're component depends on data to render, you can always conditionally return null until your data is loaded.
Also, I recommend using an async function for api requests, it allows you to use the await keyword which makes your code easier to read. The only caveat, is that you cannot pass an async function to useEffect, instead define an async function inside your hook, and then call it.
import React, { useState, useEffect } from "react";
const API = "https://example.com/data";
const GetSlides = (props) => {
const [data, setData] = useState();
useEffect(() => {
async function getData() {
const request = fetch(API);
const response = await request;
const parsed = await response.json();
setData(parsed);
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (data === undefined) {
return null;
}
return <>data</>;
};
export default GetSlides;
Of course, you can still use Promise chaining if you desire.
useEffect(() => {
async function getData() {
await fetch(API)
.then((res) => res.json())
.then((data) => setData(data));
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
<GetSlides api="https://yay.com" />
react components need to be title case
import React, { useState, useEffect } from 'react'
const GetSlides = ({ api }) => {
const [data, setData] = useState(null)
const getData = async () =>
await fetch(`${api}`)
.then((res) => res.json())
.then((data) => setData(data))
useEffect(() => {
getData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
console.log(data)
return <div>slides</div>
}
export default GetSlides
The effect callback function is called after the render of your component. (Just like componentDidMount) So during the first render phase, the data state has not been set yet.
You initialize your data with and empty array here:
const[data,setData] = useState([] <- empty array);
useEffect runs after your component is mounted, and then calls the API, that it might take a few seconds or minutes to retrieve the data, but you return the data right away before knowing if the API finished its call.
If you want to return the data after it has been retrieved from the API, you should declare and async method
const getSlides = async (API) => {
try {
const res = await fetch(API);
const data = await res.json();
return data;
} catch (e) {
throw new Error(e);
}
}
Note that it is not necessary hooks for this function

Invalid Hook Call with Async API Call

I am trying to use a react hook like useMemo or useEffect inside my functional component. The API call is async, and I think that may be what's causing the error.
Service file:
export const getData = (): wretch =>
fetch('/d/get_data')
.get()
.json();
Data formatting logic file:
import {getData} from './services';
export const formatData = (onClick, onSort) => {
// logic here - omitted
const formattedData = [];
return getData().then(res => {
res.forEach(
// more data formatting logic
)
return {formattedData: formattedData, originalData: res};
})};
Rendering file:
import {formatData} from './formatData';
const MyTable = () => {
useEffect(() => formatData({onClick: handleClick, onSort: handleSort}).then(res => setData(res)), []);
Error message:
You're correct about the part where you cant have async code in your useEffect. A workaroud for that is similar to what you're doing.
useEffect(() => {
async function myfunc(){
// Do async work
const response = await apiCall();
setData(response);
}
myFunc();
},[])
This might not answer your question, but it is a common pattern you might find useful :)
Please try this one.
const MyTable = () => {
useEffect(async () => {
const data = await formatData({onClick: handleClick, onSort: handleSort});
setData(data);
},
[]);
}

Data from API call are stored in a Array but when i try to use that array in a function for further use it shows that array is empty . why?

React code for storing Data from API to an Array and Using the same Array's event_date value for further use.
export const UpcomingHolidays = (props: UpcomingHolidaysProps) => {
const [holidayPlans, setHolidayPlans] = useState([]);
const [dateArray, setDate] = useState([]);
useEffect(() => {
getHolidayPlans();
}, []);
const getHolidayPlans = async () => {
const holidayResp = await PortalHolidayService.getInstance().getHolidayPlans();
if (holidayResp) {
setCities(() => holidayResp.cityModule);
setHolidayPlans(() => holidayResp.holidayModule);
setDate(() => holidayResp.holidayModule);
}
let today = new Date();
console.log(holidayPlans);
holidayPlans.filter((date) => {
const eventDate = new Date(date.event_date);
console.log(eventDate);
});
};
So what the thing is when i use the Same (holidayPlans) array to display some contents in html it shows the values and displays properly but when i use inside a function it shows there is no data inside the array .
console.log(holidayPlans) shows this
Same Array used to display in html
Here's a challenge: write a JavaScript function useState such that the console.log outputs a 4 and then a 5:
function render() {
let [thing, setThing] = useState(4);
console.log(thing); // 4
setThing(5);
console.log(thing); // 5
}
No matter what you do, you'll never be able to write this function, because no external JavaScript function will be able to set the value of the thing variable; that's because an external JavaScript has no way to modify the thing variable. All useState would be able to do is set its own internal state and change what it returns. Silly example here:
let hiddenState;
function useState(initialValue) {
if (hiddenState === undefined) {
hiddenState = initialValue;
}
const setState = value => {
hiddenState = value;
}
return [hiddenState, setState];
}
That means render will only be able to get a new value if useState is called again:
function render() {
let [thing, setThing] = useState(4);
console.log(thing); // 4
setThing(5);
[thing, setThing] = useState(4);
console.log(thing); // 5
}
This is essentially what useState does but in a way where the hidden state is unique per instance. As you can see, setState is to be considered "asynchronous" in that state changes aren't reflected until the next render. setState queues up a re-render request. The next time your render function is called, useState will be called again, and it will return a new value.
Notice with these code modifications, rather than us referencing the state variable before it has updated, we can still reference your response object to get the data:
export const UpcomingHolidays = (props: UpcomingHolidaysProps) => {
// On the first rendering of `UpcomingHolidays`, holidayPlans will be [].
// After setHolidayPlans is called, a re-render will be queued, and this
// UpcomingHolidays function will be called again. When useState is called
// the second time, it will have the value passed into setHolidayPlans.
const [holidayPlans, setHolidayPlans] = useState([]);
// Same for dateArray.
const [dateArray, setDate] = useState([]);
useEffect(() => {
getHolidayPlans();
}, []);
async function getHolidayPlans() {
const holidayResp = await PortalHolidayService.getInstance().getHolidayPlans();
if (!holidayResp) {
return;
}
// These will flag the component as needing to re-render after the effect
// completes. They do not change the local variables; they update the
// internal data of the useState hooks so that the next time those useState
// calls occur, they'll return new values.
setCities(holidayResp.cityModule);
setHolidayPlans(holidayResp.holidayModule);
setDate(holidayResp.holidayModule.map(date => new Date(date.event_date));
// If you want to log here, don't reference state, which hasn't updated yet.
// Either store response data as variables or reference the response itself.
console.log('Holidays are', holidayResp.holidayModule);
}
return <div>Your content</div>;
}
If you move your console.log(holidayPlans); out of getHolidayPlans function, you get an updated value.
export const UpcomingHolidays = (props: UpcomingHolidaysProps) => {
const [holidayPlans, setHolidayPlans] = useState([]);
const [dateArray, setDate] = useState([]);
useEffect(() => {
const getHolidayPlans = async () => {
const holidayResp = await PortalHolidayService.getInstance().getHolidayPlans();
if (holidayResp) {
setCities(holidayResp.cityModule);
setHolidayPlans(holidayResp.holidayModule); // you may filter data here
setDate(holidayResp.holidayModule);
}
};
getHolidayPlans();
}, []);
console.log(holidayPlans);
This happens because when you use the useState hook, you are assigning the state values holidayPlans and dateArray to local constants (or variables, this does not matter), and these values are assigned each time the component is rendered. This means that the constant value in your component will not get updated immediately, but it will be reflected in the next render, which will be triggered by the state updates that you do within getHolidayPlans. This is why, if you place the console.log() call outside getHolidayPlans, the value is printed properly.
export const UpcomingHolidays = (props: UpcomingHolidaysProps) => {
const [holidayPlans, setHolidayPlans] = useState([]);
const [dateArray, setDate] = useState([]);
useEffect(() => {
getHolidayPlans();
}, []);
const getHolidayPlans = async () => {
const holidayResp = await PortalHolidayService.getInstance().getHolidayPlans();
if (holidayResp) {
setCities(() => holidayResp.cityModule);
setHolidayPlans(() => holidayResp.holidayModule);
setDate(() => holidayResp.holidayModule);
}
// ...
};
console.log(holidayPlans);
Basically this is what happens:
First render
|
V
useEffect executes getHolidayPlans()
|
V
getHolidayPlans() performs state changes,
triggering a new render cycle
|
V
Second render,
which will have new state values
It is important to notice that in the end UpcomingHolidays is just a function, and its body is executed on each render cycle.
Based on this, the recommended way to go is to use constant/variables local to the caller function (getHolidayPlans()) instead of using the state constant/variables immediately after their respective setState function has been called, because they are updated after the completion of the function that it was called in.
export const UpcomingHolidays = (props: UpcomingHolidaysProps) => {
const [holidayPlans, setHolidayPlans] = useState([]);
const [dateArray, setDate] = useState([]);
useEffect(() => {
getHolidayPlans();
}, []);
const getHolidayPlans = async () => {
const holidayResp = await PortalHolidayService.getInstance().getHolidayPlans();
const holidayPlansLocal = holidayResp.holidayModule;
if (holidayResp) {
setCities(() => holidayResp.cityModule);
setHolidayPlans(() => holidayResp.holidayModule);
setDate(() => holidayResp.holidayModule);
}
let today = new Date();
console.log(holidayPlansLocal);
holidayPlansLocal.filter((date) => {
const eventDate = new Date(date.event_date);
console.log(eventDate);
});
};

Custom React hook, infinite loop only if I add the second dependency. Bug or something I can't understand?

I've made a really simple React hook. That's something seen on many guides and websites:
import { useEffect, useState } from 'react';
import axios from 'axios';
export const useFetchRemote = (remote, options, initialDataState) => {
const [data, setData] = useState(initialDataState);
useEffect(() => {
const fetchData = async () => {
const result = await axios.get(remote, options);
setData(result.data);
};
fetchData();
}, [remote]);
return data;
};
Example usage:
import { useFetchRemote } from '../utils';
export const UserList = () => {
const users = useFetchRemote('/api/users', {}, []);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>}
</ul>
);
}
This is working. If I understand correctly:
With no dependencies like useEffect(() => { /*...*/ }), setting the state into the function would trigger a re-render, calling useEffect again, in an infinite loop.
With empty dependencies like useEffect(() => { /*...*/ }, []), my function will be called only the "very first time" component is mounted.
So, in my case, remote is a dependency. My function should be called again if remote changes. This is true also for options. If I add also options, the infinite loop starts. I can't understand... why this is happening?
export const useFetchRemote = (remote, options, initialDataState) => {
// ...
useEffect(() => {
// ...
}, [remote, options]);
// ...
};
The infinite loop is caused by the fact that your options parameter is an object literal, which creates a new reference on every render of UserList. Either create a constant reference by defining a constant outside the scope of UserList like this:
const options = {};
const initialDataState = [];
export const UserList = () => {
// or for variable options instead...
// const [options, setOptions] = useState({});
const users = useFetchRemote('/api/users', options, initialDataState);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>}
</ul>
);
}
or if you intend the options parameter to be effectively constant for each usage of the userFetchRemote() hook, you can do the equivalent of initializing props into state and prevent the reference from updating on every render:
export const useFetchRemote = (remote, options, initialDataState) => {
const [optionsState] = useState(options);
const [data, setData] = useState(initialDataState);
useEffect(() => {
const fetchData = async () => {
const result = await axios.get(remote, optionsState);
setData(result.data);
};
fetchData();
}, [remote, optionsState]);
// ---------^
return data;
};
This second approach will prevent a new fetch from occuring though, if the options are dynamically changed on a particular call site of useFetchRemote().

Issues triggering Modal to show inside useEffect hook [duplicate]

I get this error:
Can't perform a React state update on an unmounted component. This is
a no-op, but it indicates a memory leak in your application. To fix,
cancel all subscriptions and asynchronous tasks in a useEffect cleanup
function.
when fetching of data is started and component was unmounted, but function is trying to update state of unmounted component.
What is the best way to solve this?
CodePen example.
default function Test() {
const [notSeenAmount, setNotSeenAmount] = useState(false)
useEffect(() => {
let timer = setInterval(updateNotSeenAmount, 2000)
return () => clearInterval(timer)
}, [])
async function updateNotSeenAmount() {
let data // here i fetch data
setNotSeenAmount(data) // here is problem. If component was unmounted, i get error.
}
async function anotherFunction() {
updateNotSeenAmount() //it can trigger update too
}
return <button onClick={updateNotSeenAmount}>Push me</button> //update can be triggered manually
}
The easiest solution is to use a local variable that keeps track of whether the component is mounted or not. This is a common pattern with the class based approach. Here is an example that implement it with hooks:
function Example() {
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
let isCancelled = false;
simulateSlowNetworkRequest().then(() => {
if (!isCancelled) {
setText("done!");
}
});
return () => {
isCancelled = true;
};
}, []);
return <h2>{text}</h2>;
}
Here is an alternative with useRef (see below). Note that with a list of dependencies this solution won't work. The value of the ref will stay true after the first render. In that case the first solution is more appropriate.
function Example() {
const isCancelled = React.useRef(false);
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
fetch();
return () => {
isCancelled.current = true;
};
}, []);
function fetch() {
simulateSlowNetworkRequest().then(() => {
if (!isCancelled.current) {
setText("done!");
}
});
}
return <h2>{text}</h2>;
}
You can find more information about this pattern inside this article. Here is an issue inside the React project on GitHub that showcase this solution.
If you are fetching data from axios(using hooks) and the error still occurs, just wrap the setter inside the condition
let isRendered = useRef(false);
useEffect(() => {
isRendered = true;
axios
.get("/sample/api")
.then(res => {
if (isRendered) {
setState(res.data);
}
return null;
})
.catch(err => console.log(err));
return () => {
isRendered = false;
};
}, []);
TL;DR
Here is a CodeSandBox example
The other answers work of course, I just wanted to share a solution I came up with.
I built this hook that works just like React's useState, but will only setState if the component is mounted. I find it more elegant because you don't have to mess arround with an isMounted variable in your component !
Installation :
npm install use-state-if-mounted
Usage :
const [count, setCount] = useStateIfMounted(0);
You can find more advanced documentation on the npm page of the hook.
Here is a simple solution for this. This warning is due to when we do some fetch request while that request is in the background (because some requests take some time.)and we navigate back from that screen then react cannot update the state. here is the example code for this. write this line before every state Update.
if(!isScreenMounted.current) return;
Here is Complete Example
import React , {useRef} from 'react'
import { Text,StatusBar,SafeAreaView,ScrollView, StyleSheet } from 'react-native'
import BASEURL from '../constants/BaseURL';
const SearchScreen = () => {
const isScreenMounted = useRef(true)
useEffect(() => {
return () => isScreenMounted.current = false
},[])
const ConvertFileSubmit = () => {
if(!isScreenMounted.current) return;
setUpLoading(true)
var formdata = new FormData();
var file = {
uri: `file://${route.params.selectedfiles[0].uri}`,
type:`${route.params.selectedfiles[0].minetype}`,
name:`${route.params.selectedfiles[0].displayname}`,
};
formdata.append("file",file);
fetch(`${BASEURL}/UploadFile`, {
method: 'POST',
body: formdata,
redirect: 'manual'
}).then(response => response.json())
.then(result => {
if(!isScreenMounted.current) return;
setUpLoading(false)
}).catch(error => {
console.log('error', error)
});
}
return(
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Text>Search Screen</Text>
</ScrollView>
</SafeAreaView>
</>
)
}
export default SearchScreen;
const styles = StyleSheet.create({
scrollView: {
backgroundColor:"red",
},
container:{
flex:1,
justifyContent:"center",
alignItems:"center"
}
})
This answer is not related to the specific question but I got the same Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. and as a React newcomer could not find a solution to it.
My problem was related to useState in an unmounted component.
I noticed that I was calling a set state function (setIsLoading) after the function that unmounted my component:
const Login = () => {
const [isLoading, setIsLoading] = useState(false);
const handleLogin = () => {
setIsLoading(true);
firebase.auth().then(
functionToUnMountLoginSection();
// the problem is here
setIsLoading(false);
)
}
}
The correct way is to call setIsLoading when the component is still mounted, before calling the function to unmount/process user login in my specific case:
firebase.auth().then(
setIsLoading(false);
functionToUnMountLoginSection();
)
You add the state related datas into the useEffect body for not rerunning them every rerendering process. This method will solve the problem.
useEffect(() => {
let timer = setInterval(updateNotSeenAmount, 2000)
return () => clearInterval(timer)
}, [notSeenAmount])
REF: Tip: Optimizing Performance by Skipping Effects
Custom Hook Solution (ReactJs/NextJs)
Create a new folder named 'shared' and add two folders named 'hooks', 'utils' in it. Add a new file called 'commonFunctions.js' inside utils folder and add the code snippet below.
export const promisify = (fn) => {
return new Promise((resolve, reject) => {
fn
.then(response => resolve(response))
.catch(error => reject(error));
});
};
Add a new file called 'fetch-hook.js' inside hooks folder and add the code snippet below.
import { useCallback, useEffect, useRef } from "react";
import { promisify } from "../utils/commonFunctions";
export const useFetch = () => {
const isUnmounted = useRef(false);
useEffect(() => {
isUnmounted.current = false;
return () => {
isUnmounted.current = true;
};
}, []);
const call = useCallback((fn, onSuccess, onError = null) => {
promisify(fn).then(response => {
console.group('useFetch Hook response', response);
if (!isUnmounted.current) {
console.log('updating state..');
onSuccess(response.data);
}
else
console.log('aborted state update!');
console.groupEnd();
}).catch(error => {
console.log("useFetch Hook error", error);
if (!isUnmounted.current)
if (onError)
onError(error);
});
}, []);
return { call }
};
Folder Structure
Our custom hook is now ready. We use it in our component like below
const OurComponent = (props) => {
//..
const [subscriptions, setSubscriptions] = useState<any>([]);
//..
const { call } = useFetch();
// example method, change with your own
const getSubscriptions = useCallback(async () => {
call(
payment.companySubscriptions(userId), // example api call, change with your own
(data) => setSubscriptions(data),
);
}, [userId]);
//..
const updateSubscriptions = useCallback(async () => {
setTimeout(async () => {
await getSubscriptions();
}, 5000);// 5 seconds delay
}, [getSubscriptions]);
//..
}
In our component, we call 'updateSubscriptions' method. It will trigger 'getSubscriptions' method in which we used our custom hook. If we try to navigate to a different page after calling updateSubscriptions method before 5 seconds over, our custom hook will abort state update and prevent that warning on the title of this question
Wanna see opposite?
Change 'getSubscriptions' method with the one below
const getSubscriptions = useCallback(async () => {
const response = await payment.companySubscriptions(userId);
setSubscriptions(response);
}, [userId]);
Now try to call 'updateSubscriptions' method and navigate to a different page before 5 seconds over
Try this custom hook:
import { useEffect, useRef } from 'react';
export const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => (isMounted.current = false);
}, []);
return isMounted;
};
function Example() {
const isMounted = useIsMounted();
const [text, setText] = useState();
const safeSetState = useCallback((callback, ...args) => {
if (isMounted.current) {
callback(...args);
}
}, []);
useEffect(() => {
safeSetState(setText, 'Hello')
});
}, []);
return <h2>{text}</h2>;
}

Categories