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
Related
So right now I have a verifyScreen which users are sent to when they don't have a verified email. I have a setInterval function that runs every 5 seconds to see if the user has confirmed their email.
I also need to have a second setInterval for the resend email button in case a user has not received it. It counts down by 1 until it hits 0 and the user can resend an email.
If I have one or the other in my useEffect everything works fine. They also won't run together if the other is running so I need some help understanding how setInterval works behind the scenes and how I can fix my issue. Thank you.
const [emailTimer, setEmailTimer] = useState(0);
const [, setUser] = useAtom(userAtom);
useEffect(() => {
const userTimer = setInterval(async () => {
const refreshedUser = auth().currentUser;
console.log('checked user');
if (refreshedUser?.emailVerified) {
clearInterval(userTimer);
setUser(refreshedUser);
} else {
await auth().currentUser?.reload();
}
}, 5000);
const resendEmailTimer = setInterval(() => {
if (emailTimer <= 0) {
clearInterval(resendEmailTimer);
} else {
console.log('second subtracted');
setEmailTimer(emailTimer - 1);
}
}, 1000);
return () => {
clearInterval(userTimer);
clearInterval(resendEmailTimer);
};
}, [emailTimer]);
If you know the solution and can also explain to me the why behind all this, I would really appreciate that.
Edit, my component which sets the email timer:
<View style={{ paddingBottom: 10 }}>
<PrimaryButton
disabled={emailTimer > 0}
onPress={async () => {
setEmailTimer(60);
await handleSendEmailConfirmation();
}}>
<CustomText
text={emailTimer > 0 ? `${emailTimer}` : 'Resend email'}
color={theme.colors.white}
/>
</PrimaryButton>
</View>
<OutlinedButton
onPress={async () => {
await handleLogOut();
}}>
<CustomText text="Cancel" />
</OutlinedButton>
</View>
Edit:
What happens with the code right now:
The component renders, and the user interval runs every ~5 seconds.
I click on the resend email button, which triggers the email timer every 1 second.
While the email counter is going, the user counter pauses until it hits 0.
Then the user counter resumes.
Console logs for more description:
Edit 2: Ok I managed to get both intervals working at the same time by moving the user interval outside of the useEffect. This way the email interval triggers when the state updates and it's added to the dependency array.
The one problem is the user interval that is outside of the useEffect sometimes triggers twice in the console. Not sure if that's because I'm running on a simulator or an error.
const [emailTimer, setEmailTimer] = useState(0);
const [, setUser] = useAtom(userAtom);
const userTimer = setInterval(async () => {
const refreshedUser = auth().currentUser;
console.log('checked user');
if (refreshedUser?.emailVerified) {
clearInterval(userTimer);
setUser(refreshedUser);
} else {
await auth().currentUser?.reload();
}
}, 10000);
useEffect(() => {
const resendEmailTimer = setInterval(() => {
if (emailTimer <= 0) {
clearInterval(resendEmailTimer);
} else {
console.log('second subtracted');
setEmailTimer(emailTimer - 1);
}
}, 1000);
return () => {
clearInterval(userTimer);
clearInterval(resendEmailTimer);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [emailTimer]);
If you have two setIntervals, they work totally independently. Of course, if both intervals change the same variable, both will try to update the variable at the same time and sometimes, it could cause an unexpected result, but that's not your case.
One huge issue your code has is, you're creating an interval every time setUser or emailTimer gets updated. setUser will not change after the initialization but emailTimer gets updated frequently and thus will cause hundreds of setIntervals to be created. If it works correctly, that's a miracle. I'm not sure how it could work when you have one interval.
Correct approach is adding them one time when the page mounts:
useEffect(() => {
const userTimer = setInterval(...);
const resendEmailTimer = setInterval(...);
}, []); // this empty [] is the key
ESLint may complain about react-hooks/exhaustive-deps rule but it doesn't make sense in your case, so simply suppress it. There's a lot of debate on whether this rule should be strictly enforced or not, but that's another topic.
Side note: I don't like the naming of your emailTimer variable. All other intervals have xxxTimer format, and it could easily mislead emailTimer is also an interval, so maybe consider renaming your state variable to emailTimerCount.
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])
I have a function where I'd like to set the successMessage to "success" and then after 5 seconds I'd like to set successMessage to an empty string again, but I haven't been able to figure it out.
Preferably I'd like to create a helper function where I can pass in 3 variables - function to run before (setSuccessMessage('Success!')), function to run after (setSuccessMessage('')) and the delay (5000ms)
How can I achieve this?
The Simpliest solution that runs timeout whenever the message changes to anything but empty string. Bear in mind that this example is written by hand without any testing ;)
const [successMessage, setSuccessMessage] = useState('');
const timerRef = useRef(null);
const resetMessage = () => {
clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setSuccessMessage(''), 5000)
}
useEffect(() => {
if (successMessage !== '') resetMessage();
}, [successMessage, resetMessage]);
I'm working on a react project, and i've been trying to have Message components render at a certain pace, this is what i have now:
const [chatMessages, setChatMessages] = useState([])
function handleSubmit(event) {
event.preventDefault()
const input = inputValue;
setinputValue("")
if (input && inputHandler(input)) {
const newMessages = inputHandler(input)
const interval = setInterval(() => {
if (!newMessages.length) return clearInterval(interval)
setChatMessages([...chatMessages, newMessages.shift()])
}, 500);
}
}
inputHandler() returns an array of objects that i want to add individually to the state, setChatMessages() changes the state, and then the chatMessages state gets mapped to create my Message components.
This code works properly for single messages, but if inputHandler() returns more than 1 message, only one new message is added, and it cycles between all the messages in the array until it stops at the last one.
It seems that the chatMessages state isn't updating between iterations, anyone knows how i can work around this?
First, cache newMessages in a queue after const newMessages = inputHandler(input). Second, check the queue every 500ms, you can use an array to do that. If the queue is not empty, pop the first one.
Ok so i managed to behave like i want it to with this:
let interval = setInterval(() => {
if (!newMessages.length) return clearInterval(interval)
chatMessages = [...chatMessages, newMessages.shift()]
setChatMessages([...chatMessages])
}, 500);
feel like this code is a war crime, but it works
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.