One thing that confuses me is the onChange event.
As the code below, onChange event work with handleChange function.
So what confuses me here is the event parameter.
class ControlledInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: "empty"
};
// change code below this line
this.handleChange = this.handleChange.bind(this);
// change code above this line
}
// change code below this line
handleChange(event) {
this.setState({
input: event.target.value
})
}
// change code above this line
render() {
return (
<div>
{ /* change code below this line */}
<input type="text" value={this.state.input} onChange={this.handleChange}></input>
{ /* change code above this line */}
<h4>Controlled Input:</h4>
<p>{this.state.input}</p>
</div>
);
}
};
Does every function work with onChange event need to have a parameter. If the answer is yes, why?
I replaced the keyword ‘event’ with some other keywords and it still works normally. So is it okay if I use a different word instead of the keyword ‘event’?
Does every function work with onChange event need to have a parameter. If the answer is yes, why?
They don't need to use the event parameter, therefore you don't need to declare that parameter in the callback function and the function will get called anyways (the invoker will still pass the parameter, of course, but you can ignore it if you don't need it). However, if you want to know what has changed, then you probably will want to use the info of the event parameter, right?
I replaced the keyword ‘event’ with some other keywords and it still works normally. So is it okay if I use a different word instead of the keyword ‘event’?
The word event is not a reserved word in JavaScript, you can name the parameter of the function however you want, it will still work just the same, yes.
Maybe the following example will help you understand how callbacks work with JS.
Consider the following function... What it does is it lets you register a function that will get invoked every certain number of milliseconds. Whenever the function gets invoked you will receive the current epoch time:
const timerSubscription = (ms, fn) => {
const id = setInterval(() => {
fn(Date.now());
}, ms);
return () => clearInterval(id);
}
So you could use it like this:
let count = 0;
const unsubscribe = timerSubscription(1000, () => {
count++;
console.log('One second passed');
if (count === 5) {
unsubscribe();
}
});
Or you could use it like this:
let start = Date.now();
const unsubscribe = timerSubscription(1000, (now) => {
console.log('One second passed');
if (now + 5000 >= start) {
unsubscribe();
}
});
They do almost exactly the same thing, one uses the argument that the invoker passes to it and the other one doesn't. Either way the invoker is always passing the argument.
Related
I am not a skilled react programmer but still hope someone would care to explain what I am missing:
What I want
I would like to change accounts in Metamask, detect the "accountsChanged" event, and trigger the testFunction.
What works
I am able to trigger the testFunction by clicking the test function button.
I can detect account change (for some reason it is detected around 5 times every time I change).
What does not work
I am not able to trigger the testFunction upon account change and get the message TypeError: this.testFunction is not a function
Suspect there is something fundamental about react I am missing here...Thanks for all replies!
class App extends Component {
...
componentDidMount = async () => {
...
};
testFunction = async =>{
console.log("triggered the test function");
};
render() {
window.ethereum.on('accountsChanged', function (accounts) {
console.log("account change detected");
this.testFunction(); --> this is not working
});
return (
<div className="App">
<button type="button" onClick={this.testFunction}>test function</button>
</div>
);
}
}
You need to convert your normal function to arrow function. Because normal function derives this from the object which is calling it, but arrow function derives it's this from surrounding scope, hence in arrow function this will point to your class and will have access to the methods.
window.ethereum.on('accountsChanged', accounts => {
Also, you can continue using normal function, but in that case you can store the this in some other variable like that' or 'self and use it inside the normal function to call the methods of the class.
let that = this;
window.ethereum.on('accountsChanged', function(accounts){
that.testFunction() //this will work
I struggled to update the component of my app when an account was changed using MetaMask. What I did was what Vivek suggested: create a reference of this and then handle the callback. At the end my function using etherjs and the same event of metamask (ethereun.on('accountsChanged'..was this
const here = this
provider.provider.on('accountsChanged', function (accounts) {
console.log('Account changed!!')
here.currentAccount = accounts[0]
})
This code also work with Vue
I'm creating a search function for a website that I'm working on. When a user types in a keyword, a get request will be sent to retrieve the matching information. However, it feels very wasteful to have it fire every time a key is pressed. Say the user wants to search for sandwiches, they will most likely enter that in pretty quick succession I just want to fire it after a certain amount of time after the user stopped typing(say 250ms). My idea is to set a timeout, which will be cleared on a consecutive keystroke. Unfortunately, the timeout does not reset and just ends up being delayed. I tried having it in a useEffect hook, and then the timeout worked fine but I had some other problems which prompted me to try and do it this way.
const onChangeBrand = (e) => {
const brand = e.target.value
setBrand(brand)
const timeout = setTimeout(()=>{
url.get(`brands?search=${encodeURI(brand.toLowerCase())}&rows=5`)
.then(res => {
setBrands(res.data)
if(res.data.length === 1 && res.data[0].urlEncoding === encodeURI(brand.toLowerCase())){
setPageType("models")
}else{
setPageType("brands")
}
})
},2500)
return () => clearTimeout(timeout);
}
Any help would be much appreciated!
You've got the right idea but the wrong execution. Returning a function from an onChange handler inherently does nothing–this would have worked fine with useEffect so I see where it came from. This pattern is known as throttling / debouncing a function and there are tons of premade libraries out there to help you throttle a function (like lodash.throttle) but it's perfectly cool to spin your own!
The key here would be:
Use a timeout variable that is scoped outside the method
At the start of execution of your onChange, check to see if the timeout variable has a value–if it does, clear it.
Execute onChange, assign new timeout.
You could use a ref or something here but I personally think it's easiest to define your timeout holder outside the scope of your component entirely.
let CHANGE_TIMEOUT = null;
function MyComponent(props) {
// .. component code
const onChangeBrand = (e) => {
if (CHANGE_TIMEOUT) {
// we already have a previous timeout, clear it.
clearTimeout(CHANGE_TIMEOUT);
}
const brand = e.target.value
setBrand(brand)
// Set the timeout again
CHANGE_TIMEOUT = setTimeout(()=>{
url.get(`brands?search=${encodeURI(brand.toLowerCase())}&rows=5`)
.then(res => {
setBrands(res.data)
if(res.data.length === 1 && res.data[0].urlEncoding === encodeURI(brand.toLowerCase())){
setPageType("models")
}else{
setPageType("brands")
}
})
},2500);
}
// .. other component code here
}
Hi I have been reading on react as well as doing some coding and I can't help but notice that in certain portions of the code we will use this.function() and in some we will call using this.function, I do not understand what is the difference between them and how do i determine when should i call with () and when should i not.
For example I can have the following code which will use this.function
//Arrow function used to bind the necessary variables
CallFunction = (event) =>{
console.log("Event was called");
}
render(){
return(
<form onSubmit={this.CallFunction} />
);
}
Next I can have the following code
CallFunction () {
console.log("Event was called");
}
render(){
return(
<div>{this.CallFunction()}</div>
);
}
This is just plain ole javascript, it's the difference between executing a function and just referencing a function. If you have the parens, that function will get executed immediately upon render. So in your second example you would see 'Event was called' immediately in your console when this component mounts. There are several reasons why we might have to do this, a simple one is that we just want to break out some of our code to a new function to make things easier to read.
With callbacks we don't want to execute the function right away, so we don't use the parens. We are just referencing the function. We're basically saying here's the function I want you to execute when someone submits this form. If we used the parens, that function would execute immediately on the component mounting - not wait until the form is submitted.
edit: Sometimes you need to have a function execute on render and then return a new function you want to execute as an event handler callback:
function thisRunsOnRender () {
return function thisRunsOnSubmit (e) {
console.log(e) // this would be the submit event
}
}
....
render () (
<form onSubmit={thisRunsOnRender()} />
)
On button pressed my App calls the handlePress function, which returns random objects from an array.
handlePress function:
handlePress = () => {
this.setState({
vitamin: getRandomIngredient(vitaminArray),
}, ()=> matchMealPreference())
}
If I replace matchMealPreference() with a console.log() it works just fine.
After setting the new states I want to call another function immediately with a fat arrow. Otherwise I run into async problems.
The matchMealPreference function looks like this:
matchMealPreference = () => {
if(this.props.mealPreference === this.state.protein.getIngredientFlag()){
return state
} else {
handlePress()
}
}
The function to get a random object from an array:
function getRandomIngredient (arr){
if (arr && arr.length) {
return arr[Math.floor(Math.random() * arr.length)];
}
}
I get the error:
reference error. matchMealPreference is not defined
I'm almost certain that I'm missing something trivial. But I've been stuck at that problem for over a day now, so I thought I'd turn to you for help.
Is there another way to call the matchMealPrefence without the asynchronous problems occuring?
Is it at all possible to call a function at the position where matchMealPreference is called?
Is it unwise to call the handlePress function within the matchMealPrefence function again?
Any help is much appreciated
Edit: Vijay Menon's answer was correct. I needed to add 'this'. Thanks!
You have to reference "matchMealPreference" with "this" keyword inside setState. You would have to do the same for calling "handlePress" inside "matchMealPreference" function
https://codesandbox.io/s/542919430l
changeStr = () => {
this.setState({str:"Changed"})
}
click = () => {
this.setState({num:this.state.num+1},() => {
this.changeStr()
})
}
I'd like to know how does React "freezes" the closure while using the useCallback hook (and with others as well), and then only updates variables used inside the hook when you pass them into the inputs parameter.
I understand that the "freeze" may not be very clear, so I created a REPL.it that shows what I mean: https://repl.it/repls/RudeMintcreamShoutcast. Once you open the code, open your web browser console and start clicking on the count button.
How come the value outside compared to the one inside, for the same variable, is different, if they're under the same closure and referencing the same thing? I'm not familiar with React codebase and so I suppose I'm missing an under the hood implementation detail here, but I tried to think how that could work for several minutes but couldn't come up with a good understanding on how React is achieving that.
The first time the component is rendered, the useCallback hook will take the function that is passed as its argument and stores it behind the scenes. When you call the callback, it will call your function. So far, so good.
The second time that the component is rendered, the useCallback hook will check the dependencies you passed in. If they have not changed, the function you pass in is totally ignored! When you call the callback, it will call the function you passed in on the first render, which still references the same values from that point in time. This has nothing to do with the values you passed in as dependencies - it's just normal JavaScript closures!
When the dependencies change, the useCallback hook will take the function you pass in and replace the function it has stored. When you call the callback, it will call the new version of the function.
So in other words, there's no "frozen"/conditionally updated variables - it's just storing a function and then re-using it, nothing more fancy than that :)
EDIT: Here's an example that demonstrates what's going on in pure JavaScript:
// React has some component-local storage that it tracks behind the scenes.
// useState and useCallback both hook into this.
//
// Imagine there's a 'storage' variable for every instance of your
// component.
const storage = {};
function useState(init) {
if (storage.data === undefined) {
storage.data = init;
}
return [storage.data, (value) => storage.data = value];
}
function useCallback(fn) {
// The real version would check dependencies here, but since our callback
// should only update on the first render, this will suffice.
if (storage.callback === undefined) {
storage.callback = fn;
}
return storage.callback;
}
function MyComponent() {
const [data, setData] = useState(0);
const callback = useCallback(() => data);
// Rather than outputting DOM, we'll just log.
console.log("data:", data);
console.log("callback:", callback());
return {
increase: () => setData(data + 1)
}
}
let instance = MyComponent(); // Let's 'render' our component...
instance.increase(); // This would trigger a re-render, so we call our component again...
instance = MyComponent();
instance.increase(); // and again...
instance = MyComponent();
I came here with a similar, rather vague uncertainty about the way useCallback works, its interaction with closures, and the way they are "frozen" by it. I'd like to expand a bit on the accepted answer by proposing to look at the following setup, which shows the working of useCallback (the important aspect is to ignore the linter's warning, for pedagogical reasons):
function App() {
const [a, setA] = useState(0)
const incrementWithUseCallback = useCallback(() => {
// As it closes on the first time `App` is called, the closure is "frozen" in an environment where a=0, forever
console.log(a)
setA(a + 1)
}, []) // but.. the linter should complain about this, saying that `a` should be included!
const incrementWithoutUseCallback = () => {
// This will see every value of a, as a new closure is created at every render (i.e. every time `App` is called)
console.log(a)
setA(a + 1)
}
return (
<div>
<button onClick={incrementWithUseCallback}>Increment with useCallback</button>
<button onClick={incrementWithoutUseCallback}>Increment without useCallback</button>
</div>
)
}
So we clearly see that useCallback effectively "freezes" its closure at a certain moment in time, which is a concept that must be understood clearly, in order to avoid confusing problems, which are sometimes also referred as "stale closures". This article probably does a better job of explaining it than me: https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures
Here's a slightly another view on example code provided by Joe Clay, which emphasizes closure context in which callback is called.
//internal store for states and callbacks
let Store = { data: "+", callback: null };
function functionalComponent(uniqClosureName) {
const data = Store.data;//save value from store to closure variable
const callback = Store.callback = Store.callback || (() => {
console.log('Callback executed in ' + uniqClosureName + ' context');
return data;
});
console.log("data:", data, "callback():", callback());
return {
increase: () => Store.data = Store.data + "+"
}
}
let instance = functionalComponent('First render');
instance.increase();
instance = functionalComponent('Second render');
instance.increase();
instance = functionalComponent('Third render');
As you see, callback without dependencies will be always executed in the closure where it was memorized by useCallback, thus 'freezing' closure.
It happens because when function for callback is created, it is created only once, during first 'render'. Later this function is re-used, and use value of data which was recorded from Store.data during first call.
In the next example you can see the closure 'freezing' logic "in essence".
let globalX = 1;
const f = (() => {
let localX = globalX; return () => console.log(localX); }
)();
globalX = 2;//does not affect localX, it is already saved in the closure
f();//prints 1