I wrote a function that, when pressing Enter, calls the desired function.
And it looks something like this:
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
event.preventDefault ();
handleSubmit ();
}
}
And in the right place I just call it with onKeyDown = {handleKeyDown}. But it so happened that I use this function in many places, and somehow I don't want to just repeat the code. (even the names of the handleKeyDown functions are repeated everywhere)
And as a result, I created a separate file and threw the function there, but it did not work, I think it was due to the fact that I passed the event and props arguments to the function, and when I called this function I did not know what to pass instead of event (I can, of course, pass event to the function where I call it, but there are also props there, and this also does not work).
So how can I do this?
If I understand correctly, you wish to import the event handler, not a component, from a separate file. I am guessing you are asking about props because you want to pass a function handleSubmit from the component. In this case, one option is to pass the callback to a generator when attaching the handler. You might do this as follows.
my_event_handler.js
// Returns an event handler with the callback held in its closure
export default function createListener(handleSubmit) {
return (event) => {
if (event.key === 'Enter') {
event.preventDefault();
handleSubmit();
}
};
}
my_component.jsx
import createOnKeyHandler from './my_event_handler';
function MyComponent(props) {
const submitHandler = () => { console.log('woot'); };
return (
<input
placeholder='abc'
onKeyDown={createOnKeyHandler(submitHandler)}
/>
);
}
Related
In my React component, I have a method populateData()
This method is called at various places in my Component.
It is called from render, componentDidUpdate, componentDidMount etc.
Now, there is a special condition in the method populateData() which only needs to be executed when it is called from componentDidMount.
Is it possible to know which lifecycle method is the caller, without passing that as an argument to the method?
Example code for populateData():
const populateData = () => {
//do something
if(caller === 'componentDidMount') {
//do this also
}
}
You can add a default parameter to check if it's being called from componentDidMount
const populateData = (isFromComponentDidMount = false) => {
//do something
if(isFromComponentDidMount ) {
//do this also
}
}
Then just pass in true when being called from componentDidMount
componentDidMount(){
populateData(true)
}
Everywhere else your calling populateData can remain the same
componentDidMount() {
window.addEventListener("scroll", (e) => {console.log("Hello"});
}
componentWillUnmount() {
window.removeEventListener("scroll", (e) => {console.log("Hello"});
}
...do I really need to do window.removeEventListener("scroll", (e) => {console.log("Hello")});?
Or can I just do window.removeEventListener("scroll", ()=> {}); (with an empty function)? I don't understand why, when removing the eventListener, I need to pass it the exact same anonymous function?
what if I do window.removeEventListener("scroll", ()=> {console.log("Hello2")}); - is this considered a new function now? So will this not remove the original event listener (which has console.log("Hello");) ?
Yes, you have to pass exact same reference, since function is an object and variable holding a reference and function(){} is not equals function(){}
function stuff(){/* your stuff;*/}
window.addEventListener("event",stuff);
window.removeEventListener("event",stuff);
Your componentWillUnmount function will not remove event listener because the function reference of the event listener which was attached in componentDidMount is not the same as the one it(compWillUnmount) is trying to remove.
You can check it out by creating and comparing 2 functions in chrome console.
In general, define the eventListener function in your class and attach it in componentDidMount and remove it in componentWillUnmount. Since they are executed once, the function reference remains same.
class MyComponent extends React.Component {
...
myEventFun = (e) => {console.log("Hello")};
componentDidMount() {
window.addEventListener("scroll", this.myEventFun);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.myEventFun);
}
...
If you want to deal with adding/removing event listeners in functional component, see this and this
I have been using ReactJs for a couple of days now. And I find some syntax a bit curious.
For example, sometimes I have to call a function this way:
{this.functionName}
Without the parentheses at the end.
And sometimes I have to call it like this:
{this.functionName()}
Like in this example:
<button onClick={this.streamCamVideo}>Start streaming</button>
<h1>{this.logErrors()}</h1>
See the difference between calling this.streamCamVideo and this.logErrors().
Can someone please provide an explanation for this?
EDIT 1:
As requested, here are their definitions :
streamCamVideo() {
var constraints = { audio: true, video: { width: 1280, height: 720 } };
navigator.mediaDevices
.getUserMedia(constraints)
.then(function(mediaStream) {
var video = document.querySelector("video");
video.srcObject = mediaStream;
video.onloadedmetadata = function(e) {
video.play();
};
})
.catch(function(err) {
console.log(err.name + ": " + err.message);
}); // always check for errors at the end.
}
logErrors(){
return(navigator.mediaDevices.toString())
}
{this.streamCamVideo} is a reference to the streamCamVideo function. You can think of this.streamCamVideo as a variable whose value is a function. Think about it like this:
const myVariable = 'some text'
const myOtherVariable = function() {
console.log("You are inside the myOtherVariable function");
}
Both myVariable and myOtherVariable are variables. One has the value of a string, the other has the value of a function. Let's say you want to pass both of these variables to another function:
const anotherVariable = function(aStringVariable, aFunctionVariable) {
console.log(aStringVariable, aFunctionVariable)
}
anotherVariable(myVariable, myOtherVariable)
You might see something like this logged to the console:
some text
[Function]
Notice that you don't ever see the text "You are inside the myOtherVariable function" logged to the console. That's because the myOtherVariable function is never called. It's just passed to the anotherVariable function. In order to call the function, you would need to do something like this:
const anotherVariable = function(aStringVariable, aFunctionVariable) {
aFunctionVariable()
console.log(aStringVariable, aFunctionVariable)
}
Notice the parentheses after aFunctionVariable()? That's what it looks like to actually call a function. So in this case, you'd see something like this logged to the console:
You are inside the myOtherVariable function
some text
[Function]
The function is actually being called.
So in your example:
<button onClick={this.streamCamVideo}>Start streaming</button>
<h1>{this.logErrors()}</h1>
this.streamCamVideo is just being passed as a variable to the <button> element. When the button is clicked, whatever has been assigned to onClick will be executed. That's when the function you passed as a variable will actually be called.
Also, notice the parentheses after this.logErrors()? The logErrors function is being executed. It is not being passed as a variable to anything.
{this.functionName} means referencing the function on a particular trigger. this way function will get called only when triggered.
{this.functionName()} is an actual function call, this method can be used to pass arguments. this function call will get called when page renders. This way function will get called repeatedly without any triggers. To stop that repeated function call we can use callback. like the following,
{() => this.functionName()}. this way the function will get executed only once.
{this.functionName} is used a reference type and it does not create instance on every render but {this.functionName()} is creates an instance of functionName on every render
<button onClick={this.streamCamVideo}>Start streaming</button>
Here if you use this.streamCamVideo Now it uses the reference type it does not create an instance of streamCamVideo but instead of if you use like this
<button onClick={()=>{this.streamCamVideo()}}>Start streaming</button>
Now it creates an instance of streamCamVideo instead of using the reference of streamCamVideo.
Creating an instance on every render it slows the performance of your application
Moreover, When evaluated, the first one is just a reference to the function, in the second case the function gets executed, and the expression will be evaluated to be the return value of the function.
We can use this.logErrors() when you want the function to be invoked and its result returned immediately.
In React, we typically follow this approach to split parts of your JSX code to a separate function for readability or reusability.
For Example:
render() {
someFunction() {
return <p>Hello World</p>;
}
return (
<div>
{this.logErrors()}
</div>
);
}
We can use this.streamCamVideo when you want only to pass the reference to that function to do something else.
In React, this is used while handling an event handler which can be passed down to another child-component via props, so that component can call the event handler when it needs to or when it gets triggered.
For Example:
class myExample extends React.Component {
streamCamVideo() {
console.log("button clicked!");
}
render() {
return (
<div>
<Button someCustomFunction={this.streamCamVideo} />
</div>
);
}
}
class Button extends React.Component {
render() {
return (
<button onClick={this.props.someCustomFunction}>Click me</button>
);
}
}
...
this.functionName(args) {
...
}
When its called like
... onClick={this.functionName}
The react component accepts like
function SomeReactComponent({ onClick }) {
...
so that onClick function can be called like
...
onClick(someEvent);
...
so that your function can use those args
...
this.functionName(someEvent) {
...
}
When it calls like this
... onClick={this.functionName()}
onClick accepts the result of functionName, which should also be a function in this case.
One is attribute, another with "()" is function.
I had read the article about handle events in react with arrow functions. And the final way in that article is not likely the best because of re-render issue.
e.g.
class Example extends React.Component {
handleSelect = i => e => {
console.log(i)
}
render() {
return {this.state.language.map(item, i) => (
<ListItem
...
onPress={this.handleSelect(i)} // re-render sub component every time the Example render() because of the annoymous function
/>
)}
}
}
I wonder which way is the best to write event handler in React?
In order to get the best performance out of React, you need to minimize the number of objects that are being created during renders. And as a reminder a function declaration (e.g. function myFunc or const func = () => {}) creates an object.
I would say your code has an unsolvable issue because you're creating a new instance of the inner function when handleSelect is invoked:
handleSelect = i => e => {
console.log(i)
}
I'll rewrite this using the function notation because it's a bit more clear what happening (but I prefer arrow functions in practice):
handleSelect = function (i) {
return function (e) {
console.log(i);
}
}
The issue here is the with each render when you invoke handleSelect it creates a brand new inner function (i.e. function (e) {/* ... */}).
I mentioned that your code has an unsolvable issue because there is no way to split up your curried handleSelect function because you're passing the index i that's created inside the render function. Because that state doesn't exist anywhere else, you have to create a new function to close-over it every time and that's okay.
I would even refactor your code like so:
class Example extends React.Component {
// as #RickJolly mentioned, this method doesn't have to be arrow
handleSelect (i) {
console.log(i)
}
render() {
return {this.state.language.map(item, i) => (
<ListItem
...
onPress={() => this.handleSelect(i)}
)}
}
}
If you have to create a new function every time, then you might as well inline it vs returning a function. That's preference though.
Edit
As #RickJolly mentioned, if your method doesn't use this then it shouldn't be an arrow function.
From his comment:
since you're calling () => this.handleSelect(i) via an arrow function, this is bound to the this of the enclosing context [which is the class pointer]
I get this warning with the setState in the functin below, can anone tell me how I need to structure my code to get rid of it?
warning.js:46 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the FileInput component.
componentDidMount: function () {
var self = this;
this.initUploader();
this.uploader.init();
EVENTS.forEach(function (event) {
var handler = self.props['on' + event];
if (typeof handler === 'function') {
self.uploader.bind(event, handler);
}
});
this.uploader.bind('FileUploaded', function (up, file, res) {
var objResponse = JSON.parse(res.response);
console.log(objResponse.reference);
self.props.getFileRef(objResponse.reference);
var stateFiles = self.state.files;
_.map(stateFiles, function (val, key) {
if (val.id === file.id) {
val.uploaded = true;
stateFiles[key] = val;
}
});
// setState causing warning
self.setState({ files: stateFiles }, function () {
self.removeFile(file.id);
});
});
The FileUploaded event handler is invoking setState using a closure self reference. This causes leaks where the component has been unmounted and then the FileUploaded event triggers and setState is invoked on an unmounted component. You can read more about this in this article which is somewhat related - https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html.
Now how to fix this depends on if your uploader object allows for unbinding the event handler. If it allows, then you can do this -
Define the FileUploaded handler code as a named function (instead of anonymous). You need to do this to be able to unbind it later.
Change the code in componentDidMount to bind the named function as the FileUploaded event handler.
Add a componentWillUnmount event handler to your component and call the unbind mechanism of uploader, passing it the named handler reference.
This way, when the component gets unmounted, the corresponding handler will also be removed and this warning will no longer be reported.
PS: You should remove (unbind) all handlers you are registering in your code above, otherwise you will be leaking references all over the place and more importantly, will be left with a whole bulk of orphaned event handlers.
==UPDATE==
Per your Fiddle, you can -
Declare these new methods in your component -
registerHandler: function(uploader, event, handler){
this.handlers = this.handlers || [];
this.handlers.push({e: event, h: handler});
uploader.bind(event, handler);
},
unregisterAllHandlers : function(uploader){
for (var i = 0; i < this.handlers.length; i++){
var handler = this.handlers[i],
e = handler.e,
h = handler.h;
// REPLACE with the actual event unbinding method
// of uploader.
uploader.unbind(e, h);
delete this.handlers[i];
}
},
componentWillUnmount: function(){
this.unregisterAllHandlers(this.uploader);
}
Use registerHandler in all the places where you are invoking uploader.bind -
self.registerHandler(self.uploader, event, handler);
OR
this.registerHandler(this.uploader,'FilesAdded', function (up, files) {
if (_.get(self.props, 'multi_selection') === false) {...});
This is a very crude implementation, basically we are storing all event handler references in an array and then during unmount, removing them.