It's maybe simple question, but the i'm not fully understand useState
I have confuse with when to use callback with useState, say example, i have an hook
const[count,setCount] = useState(0)
We can easy to update, for example , if i want to update the number, i can do:
setCount(count+1)
But if we have some code like setInterval,we want to increase the count every 1 second, when do the same as above
setTimeout(()=>{
setCount(count+1)
},1000)
console.log(count)
It will not increase even 1s passed
But we do
setTimeout(()=>{
setCount((count)=>{count+1}
},1000)
console.log(count)
But why it work???
My question is
When to use callback in useState?
In React, when you update a state property, with respect to its previous value, you
should use,
setState((state,props)=>{ return doSomethingWith(state.property)})
In your code (using hooks):
setCount((count)=>(count+1))
This is due to the fact that, state updates may be asynchronous in React.
By the way setCount((count)=>(count+1)) is not the same as setCount((count)=>{count+1}). In the 2nd case, the curly braces form an empty statement, & hence count+1 is not returned. In lambda functions the auto-return happens only in the case of a single expression.
Whenever a current state value update depends on the previous state , we can use a callback function.
if we have some code like setInterval,we want to increase the count every 1 second
For setInterval, Each second we need to update the count based on the previous state.This will update the count every 1 second interval.
setInterval(() => { setCount((count) => count + 1); }, 1000);
You use the callback when you want to keep the previous state and add something to it.
For example, let's say we have a counter in your case
const [counter, setCounter] = useState(0);
If I want this value to increase every second or when I press a button, I would use the following:
setCounter(prev => (prev + 1);
// Or
setCounter(counter => (counter + 1);
prev will be the same value as counter doesn't matter what you call it.
And since you will be using setInterval you will have to do this operation in a useEffect hook is the reason you will need the callback
otherwise if you keep setting the value to counter + 1 without using the callback you will just be 0 + 1 all the time.
Unless you wanna use it for something like increasing a like button counts. In that case you don't need callbacks.
Try reading and understanding those articles and docs:
setInterval in react hooks
useEffect Hook
Related
This is the syntax I'm working with.
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount((count + 1));
};
I understand that setCount is creating an instance of count, but I'm really not grasping how exactly count is being changed if it's a constant or how, if it's an instance, it's able to be called and return the most recent value.
Wouldn't every time React re-renders the page, it reads the constant count first?
It's working just fine for me, but I can't wrap my head around why.
count is 'constant' for the duration of the function. When setCount() is called, the local count doesn't change. Eventually your component gets rendered again with the new value.
During this new render the count is updated, but it will again be constant for the duration of the render/function.
I have an array with a list of elements(users in this case ).I have a variable called currentBeneficiary. I want to assign each user to be a currentBeneficiary for a specific amount of time like 10m,then we move on to the next member of array and assign them to the variable(currentBeneficiary) for same amount of time so on. I have used SetInterval() and it assigns an element of the array to the variable just for 1s after the specified period of time.And as mentioned i just want the vice versa.How can i achieve this kindly?
const members = merrygoround.members
let currentbeneficiary
if(merrygoround.interval === "daily"){
function DelayArray(array, delegate, delay) {
var i = 0
var interval = setInterval(function() {
delegate(array[i]);
if (i++ >= array.length - 1)
clearInterval(interval);
}, delay)
return interval
}
DelayArray(members, function(obj) {
currentbeneficiary = obj
console.log(`current beneficiary is ${currentbeneficiary}`)
},1000*60)
}
setInterval mean process will wait a "delay" before run your logic, after that wait a "delay" again. I am not sure I understand your question. I guess you want the first user must run right the moment DelayArray call, so you just call the delegate function for the first user before run DelayArray, variable i will start with 1.
I am trying to create a search function in jQuery:
$('input').on('keyup', function(){
var searchTerm = $("input").val().toLowerCase();
$('.item').each(function(){
if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1) {
$(this).parent().show();
} else {
$(this).parent().hide();
}
});
});
Each time the user types in the input, it gets compared to the data attribute value of .item divs. If the data attribute of that element contains the search query, it gets displayed - otherwise hidden.
This works perfectly in Chrome, however it is really laggy in Safari for some reason when the user is typing.
Is there a way to fix this?
There are about 1400 divs (.item), and the data-text attribute is only around 10-20 characters for each element
Edit, fixed by removing .show() and .hide() - and replacing with native Javascript
Solution
I have face similar issue before, I think you might want to try adding something called "debounce", which basically add a delay before doing any process. In the keyup case, it will wait for the user to stop typing for any set amount of time (let's say 0.5 second) and then do the process (searches or whatever) If you don't use debounce, it will do the search every single time the user trigger the keyup event.
You can search for articles on how to do debounce, I think there's a lot of them. But in essence it uses the setTimeout and clearTimeout function of JS
Here's from the first article I found: https://levelup.gitconnected.com/debounce-in-javascript-improve-your-applications-performance-5b01855e086
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
How to use this function? simple, just add your actual function (the search function) as the first parameter, and the delay (microseconds) in the second parameter, and then use the .call() function (why do this? because the debounce will return a function). So I guess something like this:
$('input').on('keyup', function(){
var searchTerm = $("input").val().toLowerCase();
debounce(function(){
$('.item').each(function(){
if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1) {
$(this).parent().show();
} else {
$(this).parent().hide();
}
});
}, 500).call();
});
This is how I will do it, because then I can add some stuff outside of the debounce into the keyup event, but you can just put the debounce returned function into a variable and then bind it with the keyup (like in the article), or just straight up put the debounce inside the keyup, like this:
$('input').on('keyup', debounce(function(){
...
},500));
How does it works?
You can read them in the articles, or find answer in StackOverflow, here's what I got Can someone explain the "debounce" function in Javascript
But if I'm using my own words, basically what you first need to understand is setTimeout set a timer before a function is called, and clearTimeout cancel that timer. Now in the debounce you can see that there's a clearTimeout before any setTimeout. So every time the keyup event is triggered it will basically cancel the last timeout set (if any), and then it will set a new timeout. In essence, it will reset the timer to what you set every time the event is triggered.
So for example:
The user want to search "abc"
They type "a" -> the debounce set a timer of 500ms before calling the
actual search of "a"
Before the 500ms is up, the user type "b", so the debounce cancel that "a" search, and search for "ab" instead, while also setting a timer of 500ms before doing it
Before the 500ms is up, the user type "c", so cancel the "ab" search, add a timer of 500ms to search for "abc"
The user stop typing until 500ms is up, now the debounce actually call the search for "abc"
What this results to? The heavy processing for the search is only done once for "abc", you can also put a loader or something to this heavy processing so it looks better for the user
Some quick fixes:
Collate the divs then show/hide in a single statement after the each rather than per iteration.
Changing the DOM is relatively expensive, so doing so in a single statement can greatly increase performance
If this is a table change to divs
Tables need to re-render the whole table on small changes. Fixed cell sizes can help. Not the case in this question, just a general improvement
Use an in-memory filter rather than read the DOM for each item.
Reading the DOM is much slower than in-memory (though in-memory uses more memory of course). For example, filter on .data() rather than [data-] as it will use in-memory. It's possible that this is quick in Chrome as Chrome may be caching the [data- attributes, so may not have an improvement in Chrome
debounce the input event so it only occurs when user has finished typing
Wait until the user has "finished" typing then run the action.
use operator associativity to your advantage
Although an edge case, this line
if ($(this).filter('[data-text *= ' + searchTerm + ']').length > 0 || searchTerm.length < 1)
will run the $(this).filter even when searchTerm.length < 1, change to
if (searchTerm.length < 1 || $(this).filter('[data-text *= ' + searchTerm + ']').length > 0)
Example showing this in action
function a() { console.log("a"); return true; }
function b() { console.log("b"); return true; } // b() doesn't need to exist
if (a() || b()) console.log("c")
consider server-side paging / filtering
substantially reduces the "footprint" on the page, so will be much quicker/more responsive, but with a potentially slightly longer delay retrieving the data. Depending on how it's displayed, 1400 records may be a lot for the user to view in one go (hence your filtering).
Am using custom search filter
HtML
<input type="text" pInputText class="ui-widget ui-text" [(ngModel)]
="gloablFilterValue" (ngModelChange)="splitCustomFilter()" placeholder="Find" />
I am using ngModelChange() event on search box
globalSearch(realData, searchText, columns) {
searchText = searchText.toLowerCase();
return realData.filter(function (o) {
return columns.some(function (k) {
return o[k].toString().toLowerCase().indexOf(searchText) !== -1;
});
});
}
splitCustomFilter() {
let columns =
['PartNoCompleteWheel', 'DescriptionCompleteWheel', 'PartNoTyre', 'DescriptionTyre', 'PartNoRim', 'DescriptionRim','DeletedDateFromKDPStr', 'DateFromKDPStr', 'Status'];
this.tyreAndRimList = this.globalSearch(this.tyreAndRimList, this.gloablFilterValue, columns);
}
The this.tyreAndRimList list of values for the columns which is mentioned in a column variable.
Problem
The filter is working good! But the main problem is filter performance is very poor while the record count is huge(more than 100 rows per every column)
When
The filter is working good if am entering a single character (like a). But when I was typing the character continuously the browser is hanging. the reason is the filter has been firing every typing on the filter box(because of am using ngModelChange()// onchange() event)
What I want
I want to stop filtering if the user typing continuously on the search box. Once the user has stop the typing then only I need to start filtering.
What I did
I have tried to handle this by using setTimeout(). But it just wait the filter call for a second. It is working if the user entered just 2 or 3 character's continuously. But if the user typing more than 7 or 8 or above character's, it continues to hang the browser. because of many filter callbacks are processing on the same time.
setTimeout(() => //code of filtering ,1000);
Question
How to stop filtering while user continuously entering value and start the filtering once the user has been stop the typing?
I am working in angular-2 and typescript. But this question is not related with only for angularjs or angular or JavaScript or typescript because of I want an idea not a solution. So I'll add those all tags for this question. Don't remove it. Thanks
Debounce the function. See how underscore does it here: Function Debouncing with Underscore.js.
You would then generate a debounced version of your function like this:
var globalSearchDebounced = _.debounce(globalSearch, 100, false);
It will only call after the user has stopped typing for at least one second.
It's not possible to interrupt the Array.filter method. Based on what you need you could handle this like this:
let timerId = null
function handleChange() {
if(timerId) {
clearTimeout(timerId)
}
timerId = setTimeout(() => /* Array.filter(...) */, 1000)
}
Explanation
Have a variable which will contain the timerId returned from the setTimeout function. Every time the model get changed the handleChange function will be called (in this example). The function checks if the variable which contains the timerId is set and contains a timerId, when the variable contains the timerId the clearTimeout function will be called to clear the previous timeout after that the handleChange creates a new timeout and assigns the timerId (returned from the setTimeout function) to the variable.
Documentation for setTimeout
Documentation for clearTimeout
Without underscore, and without a Timeout (that will trigger the whole Angular lifecycle by the way), you can use an Observable with the async pipe and a debounce.
In your global search function :
return Observable.of(/* filter here and return the filtered array */).debounceTime(1000)
In your list (that has to be somewhere I guess)
<list-item *ngFor="let x of myFilteredResults | async">...</list-item>
I have complete it by using Subject to debounceTime.
private subject = new Subject<string>()
ngOnInit() {
this.subject.debounceTime(300).subscribe(inputText => {
this.gloablFilterValue = inputText;
this.splitCustomFilter(); // filter method
});
}
Now when I change the value in this.gloablFilterValue object by using change event. It just waiting for the end of event completion.
This is a tricky problem. Is it possible to batch function calls without waiting for the next tick of the event loop?
Derived example for simplicity.
let numbers = []
let total = 0
const addNumber = (num) => {
numbers.push(num)
compute()
}
// for the sake of this argument let's say compute is very expensive to run
const compute = () => {
numbers.forEach((num) => {
total += num
})
numbers = []
}
This is currently what happens. compute runs after every addNumber because we need the total to be computed synchronously.
This is the example we need to work. We can only call addNumber we cannot directly call compute. Is there a way change addNumber so this example works but only calls compute once?
addNumber(2)
addNumber(4)
addNumber(10)
console.log(total === 16) // needs to be true
Thanks for any help!
No. What I'd suggest is move the placement of your compute function so it calculates the value of total just before you need to retrieve it.
Any other option I can think of would entail some kind of asynchronous functionality, which you've said you don't want.