Running into an infinite loop when using useEffect() in react-native - reactjs

I have recently begun using react native and I'm building this mobile application using Expo. I am using useEffect() to be able to call a function inside it. All I want is to wait for the reponse of the API to finish completely before I display the result (which in this case is the connectionName variable). I gave the useEffect() function walletsas a second parameter because I want the API fetched again whenever wallets changes, but I seem to be running in an infinite loop even though wallets hasn't changed. Any help is much appreciated.
export default function LinksScreen() {
const [wallets, setWallets] = React.useState([]);
const [count, setCount] = React.useState(0);
const [connectionName, setConnectionName] = React.useState("loading...");
React.useEffect(() => {
(async () => {
fetchConnections();
})();
}, [wallets]);
async function fetchConnections() {
const res = await fetch('https://api.streetcred.id/custodian/v1/api/' + walletID + '/connections', {
method: 'GET',
headers: {
Accept: 'application/json',
},
});
res.json().then(res => setWallets(res)).then(setConnectionName(wallets[0].name))
}
return (
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
<OptionButton
icon="md-school"
label={connectionName}
onPress={() => WebBrowser.openBrowserAsync('https://docs.expo.io')}
/>
</ScrollView>
);
}

Your useEffect hook runs every time wallets changes, and it calls fetchConnections, which calls setWallets, which then triggers your useEffect hook because it changed wallets, and so on...
Pass an empty dependencies array to useEffect:
React.useEffect(() => {...}, [])
That will make it run only on mount.
If you still want to call fetchConnections whenever wallets changes, you shouldn't do it through an effect hook because you'll get the infinite loop you described. Instead, call fetchConnections manually whenever you call setWallets. You could make a function that does this for you:
const [wallets, setWallets] = useState([]);
const setWalletsAndFetch = (wallets) => {
setWallets(wallets);
fetchConnections(wallets);
}

You listen to a change and then as it changes you change it over and over without stopping
React.useEffect(() => {
(async () => {
fetchConnections();
})();
}, []);//remove wallets

Related

How do i put a setstate function that is already within an async function, within a useEffect hook?

I am working on a project, which is a django project with REACT as the frontend. For the homepage, there is a useState variable ('room_code') that is used. The setstate variable is set_room_code. So, i have an async function that fetches the room code from an api and then the idea is to use the set_room_code hook. But this is just not working. The issue is with the set_room_code as the code works if i simply remove it. I have tried to search up ideas but i am short on it. Any input would be appreciated.
useEffect( () => {
let fetch_code = async () => {
const response = await fetch('/api/user-room');
const data = await response.json();
console.log('hi');
console.log(data.room_code);
console.log('bhao');
set_room_code(data.room_code);
};
fetch_code();
console.log(hi);
}, []);
I have tried using an extra useEffect hook but that doesnt work as well
A few things, first its best practice to name the useState variable
const [roomCode, setRoomCode] = useState();
FYI.
Now as to your question--
useEffect( () => {
let fetch_code = () => {
fetch('/api/user-room').then((data) => {
setRoomCode(data.room_code);
console.log(data.room_code);
return response.json();
});
};
fetch_code();
console.log("this should show your RoomCode", roomCode)
}, [roomCode]);

ReactJS array from usestate is still empty after setTimeout

please I'm solving one problem (just learning purposes). I'm using useState() hook and then, after some timeout I want add next items into array from remote fetch.
My code snippet look like:
const [tasks, setTasks] = useState([]);
const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=5';
// asnynchronous call. Yes, it's possible to use axios as well
const fetchData = async () => {
try {
let tasksArray = [];
await fetch(url)
.then((response) => response.json())
.then((data) => {
data.results.map((task, index) => {
// first letter uppercase
const taskName = task.name.charAt(0).toUpperCase() + task.name.slice(1);
tasksArray.push({ id: index, name: taskName });
});
});
console.log('Added tasks:' + tasks.length);
setTasks(_.isEmpty(tasks) ? tasksArray : [...tasks, tasksArray]);
} catch (error) {
console.log('error', error);
}
};
useEffect(() => {
// Add additional example tasks from api after 5 seconds with
// fetch fetchData promise
setTimeout(fetchData, 5000);
}, []);
Code works fine with useEffect() hook. But in async function my array is empty when I add some tasks within five seconds and it will be replaced by fetched data and one empty
I added Butter and Milk within 5 seconds to my Shopping list
But after timeout my tasks array will be replaced by remote fetched data.
And as you can see, tasks array lenght is 0 (like console.log() says)
Please, can you exmplain me, why my tasks array is empty if there exists 2 items before 5 seconds.
Of course, I'm adding my tasks to the list normally after hit Enter and handleSubmit
const handleSubmit = (e) => {
e.preventDefault();
//creating new task
setTasks([
{
id: [...tasks].length === 0 ? 0 : Math.max(...tasks.map((task) => task.id)) + 1,
name: newTask,
isFinished: false,
},
...tasks,
]);
setNewTask('');
}
Thanks for help or explain. It the problem that useEffect is called after rendering? Of this causing async behavior?
I could not understand your code fully correctly but my guess is
the fetchData function you have declared might refer to the tasks at the time of declaration.
so every time you call fetchData you might not see the changed tasks state...
if this is the case try using useCallback with the dependency tasks...
what useCallback does is it stores the function in memory and if smth in dependency changes the function's logic changes to dependencies you declared.
If you have used eslint, calling a function inside useEffect will give you error similar to below
The ‘fetchOrg’ function makes the dependencies of useEffect Hook (at line 6) change on every render. Move it inside the useEffect callback. Alternatively, wrap the ‘fetchOrg’ definition into its own useCallback() Hook
Your code is confusing. You can place everything inside an useEffect, and I believe the thing you are trying to achieve is a long poll query. (for that you use setInterval() and you have to clear the function in useEffect
my solution for you:
const [tasks, setTasks] = useState([]);
const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=5';
const request = {method: GET, Headers: {"Content-type":"application/json"}}
useEffect(() => {
function fetchData(){
fetch(url, request).then(res => res.json().then(data => {
data.results.map((task, index) => {
const taskName = task.name.charAt(0).toUpperCase() + task.name.slice(1);
setTasks((prevState) => ([...prevState,{ id: index, name: taskName }]));
});
})
}
const interval = setInterval(() => {
fetchData();
}, 5000)
return () => clearInterval(interval)
}, []);
please do not forget two things:
This approach is only good when you have 5 to 10 simultaneous clients connected since it is not performance effective on the backend, I would recommend instead an alternative based on realtime communication (with the listening for new events.
please do not forget to specify the {method, headers, body) in the fetch function

custom hook -> useEffect -> while loop not breaking

I try to create a custom hook for polling.
Problem:
My hook is using a while loop inside useEffect, but it seems that while loop is never breaking, even when I change the condition to false.
Code sandbox reproducing the problem:
https://codesandbox.io/s/clever-worker-jm1p5?file=/src/App.tsx
Code:
I have 2 files:
usePolling.ts (which is my hook)
App.tsx (where I am executing/calling the hook)
usePolling.ts
/* eslint-disable no-await-in-loop */
import { useState, useCallback, useEffect } from "react";
export interface PollingOptions<T> {
fetchFunc: () => Promise<T> | undefined;
}
export const usePolling = <T>(
{ fetchFunc }: PollingOptions<T>,
interval: number
) => {
const [data, setData] = useState<T>();
const [error, setError] = useState<Error>();
const [condition, setCondition] = useState(true);
const stopPolling = useCallback(() => {
setCondition(false);
}, []);
const performPolling = useCallback(async () => {
try {
const res = await fetchFunc();
setData(res);
} catch (err) {
setCondition(false);
setError(err);
return;
} finally {
await new Promise((r) => setTimeout(r, interval));
}
}, [fetchFunc, interval]);
useEffect(() => {
(async () => {
while (condition) {
await performPolling();
}
})();
return () => stopPolling();
}, [condition, performPolling, stopPolling, data]);
return { data, error, stopPolling };
};
App.tsx
import { useCallback, useMemo } from "react";
import { usePolling } from "./usePolling";
import "./styles.css";
export default function App() {
const fetchFunc = useCallback(async () => {
// please consider this as an API call
await new Promise((resolve) => setTimeout(resolve, 500));
return { isSigned: Math.random() < 0.5 };
}, []);
const { data, error, stopPolling } = usePolling({ fetchFunc }, 3000);
console.log(data, error, ">>>>>>>");
const isSigned = data?.isSigned;
const isContractSigned = useMemo(() => {
if (isSigned) stopPolling();
return isSigned || false;
}, [isSigned, stopPolling]);
return (
<div className="App">
<h1>{`Is contract signed: ${isContractSigned}`}</h1>
</div>
);
}
Why am I not using setInterval instead of while loop
If I use setInterval, and if I choose to change the polling interval to 100ms for example. There are 3 problems:
there is a very high chance of calling API before previous API call responds.
suppose for any reason 2nd API call responded before the first one, then I am in trouble
Unnecessary API calls.
If I use while loop instead of setInterval, then all the above mentioned problems are solved. Please note that I am still using setTimeout inside while loop, so it will wait for specified interval before the next iteration.
Expected solution:
Any solution that does not use setInterval is expected. If you suggest any improvements to the existing code (even not related to this specific problem), you are most welcome :)
Thank you!
The callback you pass as the first argument of useEffect assumes that the dependencies are constants.
You are starting a while loop with the condition being a constant. There is no way to stop that while loop execution.
If you change the condition, it's not going to stop the previous while loop, but start a new condition with the new value.
If the new value is false, it won't start so nothing would happen. But if it's true, then you've got 2 while loops running.
Check out the SWR, React Hooks for Data Fetching, it's probably exactly what you are looking for.
Update:
The reason why the while loop won't stop
When you have a while loop in normal code, you control it using a condition based on a variable.
The dependencies of a useEffect are passed as constants to the callback. So any loops running based on a state variable (truthy) in a useEffect will not terminate.
The can cleanup a setInterval because there is clearInterval function implemented in the browser, which takes an id, finds the reference of the interval, and stops it.
There is no clearWhileLoop as it's not feasible.
Whenever a dependency mutates, the cleanup function runs, but you can't do anything about a while loop in a cleanup function.
The next callback is fired with new constants.
Check out the docs for why useEffects run after each render

Using React State Hook, call function after setting multiple states

I'm trying to make a function that will reset multiple states and then make an API call, however I'm having trouble making the API call happen AFTER the three states have been set. My function looks like this:
const resetFilters = () => {
setYearFilter("");
setProgressFilter("");
setSearchFilter("");
callAPI();
};
I've tried using Promise.resolve().then(), and tried using async await, but it seems the useState setter function doesn't return a promise. Is there a way to make this all happen synchronously?
You could use useEffect to listen on changes and do each task sequentially
const resetFilters = () => {
setYearFilter("")
}
useEffect(() => {
setProgressFilter("")
}, [yearFilter])
useEffect(() => {
setSearchFilter("")
}, [progressFilter])
useEffect(() => {
callAPI()
}, [searchFilter])

I cannot collect data from API using Axios + React

I'm beginner with React. I have 2 different cases where I'm using React Hooks which I cannot receive the data from my local API properly.
Case 1:
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test)
})
...
}
It works with the state test displaying correctly the content from the API but I don't know why/how the Axios continues calling the API infinity - endless. (Ps: the very first call it returns undefined, then the next ones it works) What am I doing wrong?
To fix this I've tried to use useEffect like this (Case 2):
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test);
})
}, [])
...
}
Now the Axios works only once but no data is coming from the API. Maybe I should use async/wait for this case but I cannot make it work. Does anyone know how to fix that (Case 1 or/and Case 2)?
Thanks.
Updating the state is an asynchronous operation. So the state is not really updated until the next time the component gets rendered. If you want to capture the correct state, you can either console.log(res.data) or wrap that inside the useEffect hook with test as dependency.
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
// effect only runs when component is mounted
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data);
});
}, []);
// effect runs whenever value of test changes
useEffect(() => {
console.log(test);
}, [test]);
}
That way it is guaranteed that the console.log runs when the value of test is updated.
Also the reason the API request is invoked once is you have not mentioned anything in the dependency array. [] empty dependency array runs the effect when the component is mounted for the first time.
async/await is just a wrapper around Promise object. So they would behave similarly.
The solution with useEffect is good. If you don't use it each render will call the request. This is the same if you put there console.log with any information. The reason why you don't see the data in the useEffect is that the value of the state is not updated in current render but in the next which is called by setter of the state. Move the console.log(test); after useEffect to see the data. On init it will be undefined but in the next render, it should contain the data from the request.

Resources