useEffect executes even the state does not changes - javascript

const [urlList, setUrlList] = useState(0);
const callDayList = () => {
console.log(urlList);
Axios.post('http://localhost:3001/api/get', {
passNum: urlList,
});
};
useEffect(callDayList, [urlList]);
<td>
<Link to={"/study/"+item.num}
onClick={function() {
setUrlList(item.num);
// num = item.num
}}
>{item.title}</Link>
</td>
this code is sending a state, which is urlList to backend, and I have some problem.
as I studied about useEffect, it executes only if the urlList changes its value.
but when I click a url, it keep passes the initial value of urlList, which is 0.
to check urlList changes correctly, I changed Link in to "/", and saw the outcome of console.log(urlList) in callDayList, when I click, it shows correct value of state, but if set back the Link, and click, the value of the urlList is still initial value and send it to backend.
callDayList has to be execute when the urlList changes it's value, I can't figure out why it keep passes initial value of the state.

useEffect will run the first time it is set up as well as when anything changes.
If you want to avoid this initial run, you can try using a useRef variable to track whether it's the first run:
import { useState, useRef, useEffect } from 'react';
const isFirstRun = useRef(true);
const callDayList = () => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
// do your effect
Axios.post('http://localhost:3001/api/get', {
passNum: urlList,
});
return () => { isFirstRun.current = true; }
};
useEffect(callDayList, [urlList, isFirstRun]);
It looks cumbersome, but should get you the behaviour you require. If you end up needing to skip useEffect's initial run in more than one place in your code, it'd probably be worthwhile extracting this behaviour into a custom hook.

Related

how to detect if user is typing in react native?

I have an application in react native where i'm developing a search feature like Instagram.
It is like if user stop typing show him his query result.
my current approach is messing up redux. And sometimes it returns same element multiple times or sometime random elements which are irrelevant of that query.
right now. I'm calling search api immediately as use start typing in searchbar.
here is code below of my component.
import { getSearchDataApi } from "../../api/search/search";
import { clearSearchData, setSearchData } from "../../redux/action/search";
const SearchScreen =(props)=>{
const [autoFocus,setAutoFocus] = useState(true)
const [keyWord,setKeyWord] = useState(null)
const [isLoading,setIsLoading] = useState(false)
const [isError,setIsError] = useState(false)
const [pageNumber,setPageNumber] = useState(1)
const [loadMore,setLoadMore] = useState(true)
const loadMoreDataFunc =()=>{
if (pageNumber <= props.totalSearchPage) {
setPageNumber(pageNumber+1)
}
else {
setLoadMore(false)
}
}
const searchData = async(keyWord)=>{
console.log(keyWord,pageNumber)
try {
setIsLoading(true)
var searchResponse = await getSearchDataApi(keyWord,pageNumber)
props.setSearchData(searchResponse.data)
setIsLoading(false)
}
catch (e) {
setIsError(true)
console.log("Error --- ", e.response.data.message)
showMessage({
message: e.response.data.message,
type: "danger",
});
}
}
return (
<View>
....
</View>
)
}
const mapStateToProps = (state)=>({
searchData: state.searchReducer.searchData,
totalSearchPage: state.searchReducer.totalSearchPage,
})
export default connect(mapStateToProps,{setSearchData,clearSearchData})(SearchScreen);
I will really every thankful to someone how can help me in fixing. Appreciation in advance!
GOAL :
The goal that i want to achieve is when user stop typing then i call searchAPI with the keyword he/she entered in searchBar that's all.
I have also tried setTimeOut but that made things more worse.
The best solution to your problem is to debounce the state variable that is responsible for the user input. This way, you can use the effect hook to watch for changes on the debounced variable, and call the search API if/when conditions for the search API variables are met.
Well, I have put some effort to solve it with setTimeout once again and i have done it by following code of snippet.
useEffect(()=>{
setPageNumber(1)
props.clearSearchData()
const delayDebounceFn = setTimeout(() => {
console.log(keyWord)
if (keyWord) {
searchData(keyWord)
}
}, 500)
return () => clearTimeout(delayDebounceFn)
},[keyWord])
You can use a setInterval to create a countDown starting from 2 to 0, or 3 to 0, put it a state.
whenever user types, onChange is called, the from the callback you reset the countDown.
using useEffect with the countDown as dependency, you can open the search result whenever the countdown reaches 0. (which means the user hasn't typed anything since 2s ago)
this might help for creating the countdown https://blog.greenroots.info/how-to-create-a-countdown-timer-using-react-hooks

Why is useEffect running once?

When I try to test this code portion by running useEffect on the info state, I notice that the info state runs 1 time less than it should be. Can I know why?
data.length = 2
const fetchData = async function (contractAddress) {
setContractAddress(contractAddress)
const loan = await readLoanAmount({"address": contractAddress});
setLoanAmount(loan)
const collected = await readCollectedAmount({"address": contractAddress});
setCollectedAmount(collected);
setInfo([...info, {"contractAddress":contractAddress,"loanAmount": loanAmount, "collectedAmount":collectedAmount}])
}
useEffect(() => {
checkWalletIsConnected();
fetch('http://localhost:5000/loanContract/getloanContracts').then((response) => response.json()).then((data) => {
for(let i =0 ; i< data.length; i++){
let ca = data[i]["contractAddress"];
fetchData(ca);
}
}).catch((err) => {
console.log(err.message);
});
}, []);
Whenever we supply an empty array to the second argument of useEffect, the effect runs only once after render. That is by definition how React built the hook.
Link to useEffect expalaination in React Docs Beta: https://beta.reactjs.org/learn/synchronizing-with-effects
Did you mean for loop inside your useEffect is running once only instead of twice (as it should for data.length = 2)?
It could be due to the way you have used setInfo setter. Instead of getting the info value directly, do the following:
setInfo(prevInfo => {
return [
...prevInfo,
{
"contractAddress":contractAddress,
"loanAmount": loanAmount,
"collectedAmount":collectedAmount
}
];
});
Receiving prevInfo this way makes sure you have the latest value of info especially when dealing with synchronous consecutive calls to the setter function of useState. This might solve the issue you're facing...

How to prevent useEffect infinite loop when checking Apollo response for changes?

I'm calling a mutation like so:
const [deleteTask, deleteTaskResponse] = useMutation(deleteTaskMutation);
// this is called in a different component (all of this logic is in a context provider)
const deleteTaskFunction = (taskId) => {
deleteTask({variables: { taskId }})
}
I have a a useEffect set up to monitor for when the deleteTaskResponse is changed to update state with the response like so:
useEffect(()=>{
const { data, loading, error } = deleteTaskResponse;
if (data && data.deleteTask){
console.log(data.deleteTask)
setTasks(data.deleteTask.tasks) // state
}
}, [deleteTaskResponse])
I don't understand why this is causing an infinite effect loop as the response shouldn't be changing since the function is only called once. I've tried putting a console.log in the deleteTaskFunction and can confirm that it is only being called once when the delete button is pressed.

Problem with reset state function and only than fetching data (React hooks)

I am showing a list of users(profiles), and fetch it from some users DB.
I am in the search page which include sub pages for diffrenet filters - like which users are currently online.
Each time i am moving inside the search sub pages, i have to reset only once the main filtering variable in order ot get the correct result.
The problem is the fetch request happpend before the setState variable changed.
I saw other people asked how to fetch after, while i need it to first reset the variables of setState and the to go and fetch according to the correct values.
code:
const [isPopUpShowState,setIsPopUpShowState] = useState(false);
const [profilesloading,setProfilesLoading] = useState(<Spinner/>);
const [profilesLength,setProfilesLength] = useState(0);
const [profilesPerPage] = useState(4);
const [searchStartPoint,setSearchStartPoint] = useState(0);
const [lastUserConnIndex,setLastUserConnIndex] = useState(1);
useEffect( ()=> {
restoreStatesToDefault(); // reset states+list --> the variables doesnt changed before the the fetch
getProfilesMatchingPage(); // init profiles
},[history.location.pathname]);
const restoreStatesToDefault = () => {
list = {};
setSearchStartPoint(0);
setLastUserConnIndex(1);
setProfilesLength(0);
}
const getSearchProfilesParmsInObj = () => {
const parmsObj = {};
if(currUser.loginObj){
parmsObj['isMale'] = !currUser.loginObj.data.isMale;
parmsObj['profilesPerPage'] = profilesPerPage;
parmsObj['searchStartPoint'] = searchStartPoint;
parmsObj['lastUserConnIndex'] = lastUserConnIndex;
parmsObj['allProfiles'] = list;
}
return parmsObj;
}
const getProfilesMatchingPage = () => {
switch(history.location.pathname){
case '/search/online':
dispatch(getProfilesOnline(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setProfilesLength));
break;
case '/search/pics':
dispatch(getProfilesOnlyWithPics(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setSearchStartPoint,setProfilesLength));
break;
case '/search/recently':
dispatch(getProfilesRecentlyVisited(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setSearchStartPoint,setProfilesLength));
break;
case '/search/news':
dispatch(getProfilesNewUsersRegistered(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setSearchStartPoint,setProfilesLength));
}
}
The problem is that both functions are called within the same lifecycle of the function, so the states haven't updated yet (They are within the same closure). After your useEffect finishes, then the next render is called with the updated state values, but they are not dependencies of your useEffect so they don't trigger it to fire again (which is a good thing in this case).
Basically what you want is two useEffect -> one is triggered on path change, and that one should update state that is a dependency of another useEffect that triggers the fetch.
A simple example would be:
const [shouldFetch, setShouldFetch] = useState(false) // Set this to true if you want to fetch on initial render
useEffect( ()=> {
restoreStatesToDefault(); // reset states+list --> the variables doesnt changed before the the fetch
setShouldFetch(true);
},[history.location.pathname]);
useEffect(() => {
if (shouldFetch) {
setShouldFetch(false);
getProfilesMatchingPage(); // init profiles
}
}, [shouldFetch])

Bind event handler to document & have access to firebase api data via useEffect

Quick version:
My ultimate goal is to do something like the link below but with an async call to firebase per useEffect where the list data is composed of firebase object content.
https://codesandbox.io/s/usage-pxfy7
Problem
In the code below useEffect encapsulates code that pings firebase and gets some data back called "clients". The data is retrieved perfectly.
I then store that data using useState to two different instances of useState. The data is stored at clientList and clientListForRender.
So far so good.
Now the problem starts.
I have a third instance of useState that takes a number. I want to set a keypress event to the document so that I can use the up/down arrows to toggle the counter and access each value of the clientListForRender array.
When I set the eventListener I do not have access to the array (presumably due to the async calls not being in an order that allows for it).
I am not sure how to write my hooks in a way that gives me the result I want.
Thank you.
const clientsRef = firebase.database().ref('clients');
const [clientList,setClientListState] = useState([]);
const [clientListForRender,setClientListStateForRender] = useState([]);
const [selectedIndex, updateSelectedIndex] = useState(0);
useEffect(() => {
function handleKeyPress(event,arr){
console.log(arr)
if(event.key === "ArrowDown"){
updateSelectedIndex((prev)=>{
return prev += 1
});
}
}
clientsRef.on('child_added', snapshot => {
const client = snapshot.val();
client.key = snapshot.key; // __________________________1. get firebase data
setClientListState(function(prev){
setClientListStateForRender(()=>[client,...prev]); //_______2 store data
// document.addEventListener('keydown', handleKeyPress); <---I am not sure where to put this. I have experimented and
// I decided to omit my cluttered "experiments" to protect your eyes
return[client,...prev]
});
});
},[]);
Ok there are few issues with the code you posted:
1) You should definitely not add your keyboard listener in the child_ added listener ( this means that every time the child_added listener is called, you are going to create a new listener, leading to unexpected results and memory leak)
2) You are calling setState in a setState updater function (the callback function you provided for, setClientListState), which is an anti pattern and makes your code hard to follow and understand, and will cause unexpected effects once the component grows. If you want to update a state based on a previous state then use the useEffect callback
3) the useEffect function takes a second parameter, called array of dependencies. When you have provided it with an empty array, it means that you want your effect to run only once, which is problematic because we see that the function depends on clientsRef variable. ( from this actually comes your problem because the keyboard listener was having the old value of your clientsList which is the empty array, and so it was always returning 0, when keys where pressed, i explained more in the code sandbox)
4)You should return a callback function from the useEffect function to clean the effects you created, turning off the listeners you attached (or else you might have memory leaks depending on how much the component gets mounted/unmounted)
ok here is how the code should be to work:
const clientsRef = firebase.database().ref('clients');
const [clientList, setClientListState] = useState([]);
// I don't understand why you wanted another list, so for now i only use on list
// const [clientListForRender,setClientListStateForRender] = useState([]);
const [selectedIndex, updateSelectedIndex] = useState(0);
useEffect(() => {
function handleKeyPress(event, arr) {
if (event.key === 'ArrowDown') {
updateSelectedIndex(prev => {
if (prev >= clientList.length - 1) {
return (prev = 0);
} else {
return prev + 1;
}
});
}
}
clientsRef.on('child_added', snapshot => {
const client = snapshot.val();
client.key = snapshot.key; // __________________________1. get firebase data
setClientListState(function(prev) {
return [client, ...prev];
});
});
document.addEventListener('keydown', handleKeyPress);
// here you should return a callback to clear/clean your effects
return () => {
document.removeEventListener('keydown', handleKeyPress);
clientsRef.off();
};
// Its important to add these here, or else each time your keyboard listener runs it will have the initial value of
// clientsList ([]), and so clientsList.length = 0, and so you will always updateSelectedIndex(0)
}, [clientList, clientsRef]);
//here render based on selected list as you wish
Finally i have set up a working codesandbox that emulated data fetching based on the example you give https://codesandbox.io/s/usage-4sn92, i added some comments there to help explain what i said above.

Categories