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.
Related
I need to dynamically pass in a function name that will be added as an onclick event handler as part of a dynamic UI creator. Most of the function is easy but I can't work out how to turn the string function name into the function that is bound to the event handler.
I've tried things like:
// Add event handlers
Object.keys(compToAdd.events).forEach( (type) => {
newEl.addEventListener( type, Function.prototype.bind( compToAdd.events[type] ) )
})
But that doesn't work.
Also tried:
window.mycb = function() {
console.log('>>>> hello >>>>')
}
// ...
Object.keys(compToAdd.events).forEach( (type) => {
newEl.addEventListener( type, window['mycb'] )
})
Using window['mycb']() immediately executes the fn when applied to the event listener which is obviously not correct. Without (), nothing happens when the click event fires.
A simplest and arguably best approach would be loading all your callback functions into a single object, versus create them in global scope:
const compToAdd =
{
events:
{
click: "onClick",
mousedown: "onMouseDown",
mouseup: "onMouseUp",
}
}
const myCallbacks =
{
onClick: function(e)
{
console.log("onClick type:", e.type)
},
onMouseDown: function(e)
{
console.log("onMouseDown type:", e.type)
},
onMouseUp: "this is not a function"
}
// ...
Object.keys(compToAdd.events).forEach( (type) => {
const callback = myCallbacks[compToAdd.events[type]];
if (callback instanceof Function) //make sure it's a function
newEl.addEventListener( type, callback )
})
<div id="newEl" style="height: 100vh">click here</div>
P.S.
your second example works fine though. If it executed without () it means something triggered the event.
I have managed to find a couple of possible answers. However, I don't know that I like either of them and I am sure there are better ones.
Using eval
// Add event handlers
Object.keys(compToAdd.events).forEach( (type) => {
// Add the event listener - hate eval but it is the only way I can get it to work
try {
newEl.addEventListener( type, (evt) => {
eval(`${compToAdd.events[type]}(evt)`)
} )
} catch (err) {
console.error(`Add event '${type}' for element '${compToAdd.type}': Cannot add event handler. ${err.message}`)
}
})
Using setAttribute instead of addEventListener
// Add event handlers
Object.keys(compToAdd.events).forEach( (type) => {
if (type.toLowerCase === 'onclick' || type.toLowerCase === 'click') type = 'click'
// Add the event listener
try {
newEl.setAttribute( type, `${compToAdd.events[type]}()` )
} catch (err) {
console.error(`Add event '${type}' for element '${compToAdd.type}': Cannot add event handler. ${err.message}`)
}
})
I'm preferring (1) since it seems a bit more flexible and allows the use of pre-defined functions in any context reachable from the context when setting the event listener. But it is far from ideal.
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)}
/>
);
}
I'm looking for a way to achieve the following. I could build some mechanism to do this, I'm asking for something built-in or a really simple way, if it exists and I'm missing it.
Edit:
Please note that I'm talking about events in random objects, not in DOM elements. The events order cannot be managed using the parent, etc. as suggested in the possible duplicate.
Edit 2:
Maybe an after-all-handlers-have-been-called callback? Or an always-last-to-be-executed handler?
Given:
var someObject={};
$(someObject).on("event",function() { console.log('Default handler'); });
...
$(someObject).on("event",function() { console.log('Other handler'); });
When doing:
$(someObject).triggerHandler("event");
The output is:
Default handler
Other handler
We all know this. The problem is: What if I would want to make the first event the "default" handler, to be executed if there aren't other event handlers (not a problem there) or if the other handlers didn't stop the event propagation (here is the problem).
I'm looking for a way to be able to do something like:
$(someObject).on("event",function(ev) { ev.preventDefault(); });
and prevent the first event handler from executing. In this example is not working given the execution order. Reversing the execution order is not the correct way to do it.
TL;DR
Is it possible to set a default handler, one to be called in case there's no other handlers and the event hasn't been canceled?
Edit 3: To give you a better idea, the current approach is the following (names are made up for this example):
var eventCanceled=false;
function doTheEvent() {
$(someObject).triggerHandler("event");
if(!eventCanceled) defaultEventHandler();
}
//To be called inside the handlers to stop the default handler
function cancelTheEvent() {
eventCanceled=true;
}
I just want to get rid of this and be able to use triggerHandler directly.
Hope this is what you are looking for. This is called observer pattern.
var someObj = {};
someObj.eventCallbackList = {};
someObj.createEventObject = function(name) {
return {
type: name,
preventDefault: function() {
this.callDefault = false;
},
callDefault: true
}
}
someObj.on = function(eventName, callback, defaultFlag) {
if (!this.eventCallbackList[eventName]) {
// there can be multiple other handlers
this.eventCallbackList[eventName] = {
other: [],
default: null
};
}
if (defaultFlag) {
this.eventCallbackList[eventName]['default'] = callback;
} else {
this.eventCallbackList[eventName]['other'].push(callback);
}
}
someObj.triggerHandler = function(eventName) {
var event = this.createEventObject(eventName);
var callbacks = this.eventCallbackList[eventName];
if (callbacks) {
if (callbacks['other']) {
for (var i = 0; i < callbacks['other'].length; i++) {
callbacks['other'][i](event);
}
}
if (event.callDefault && callbacks['default']) {
callbacks['default'](event);
}
}
}
// Test
someObj.on('myCustomEvent', function(event) {
console.log('OtherHandler');
event.preventDefault();
});
someObj.on('myCustomEvent', function(event) {
console.log('default');
}, true);
$(document).on('click', function() {
someObj.triggerHandler('myCustomEvent');
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
you can add a parameter to set what codes you want to execute inside your triggerHandler function.
you can refer to this thread in adding parameter.
jQuery: receive extraParameters from triggerHandler()
While there is a way to detect if the preventDefault() was called, there is no such thing as a "default handler". Event handlers are executed in the order they are registered. So the first one registered is the first one executed.
If your "default handler" doesn't need to be executed synchronously, you could delay the "default handler" to be executed after all the other handlers are done and in any of the other handlers revert that if necessary:
var defaultHandlerTimeout;
$element.on('event', function() {
defaultHandlerTimeout = setTimeout(function() {
// your actual handler
});
});
$element.on('event', function() {
// prevent the "default handler" from being executed
clearTimeout(defaultHandlerTimeout);
});
jQuery does store all of it's events internally and you can access specific element events using $._data(elem,'events').
Whether this will help your situation or not I'm not sure but is worth digging into. Personally i've never found the need to use it.
See https://stackoverflow.com/a/2518441/1175966
I am wondering if there is an option in Socket.IO client library for Node.js for replacing listener function for specific event.
I have this simple function:
var Service = module.exports = function(address) {
var _socket = require('socket.io-client')(address);
this.connectAccount = function(account, callback) {
_socket.on('account/connected', function (response) {
callback(response.data, response.message);
});
_socket.emit('account/connect', account.getParameters());
};
}
The problem is, when I call function connectAccount() several times, all anonymous functions I pass each time in on() function get also called and after short while it reaches the limit and throws error.
So my question is if there is a way how to replace each time that listener so each time it gets called only once?
Thanks in advance.
You could either remove the listener before attaching new one or check if a listener is attached.
To remove listener:
_socker.removeListener('account/connected', [function]);
To check if an event has listeners:
_socker.hasListeners('account/connected');
An eventEmitter supports multiple event handlers. It does not have a mechanism for "replacing" an event handler. As such, you have a couple options:
You can keep a flag for whether the event handler is already set.
You can remove the listener before you add it. Removing it if it wasn't already set is just a noop.
Code example for only installing the event handler once:
var Service = module.exports = function(address) {
var initialized = false;
var _socket = require('socket.io-client')(address);
this.connectAccount = function(account, callback) {
if (!initialized) {
_socket.on('account/connected', function (response) {
callback(response.data, response.message);
});
initialized = true;
}
_socket.emit('account/connect', account.getParameters());
};
}
EDIT : I need to invoke one function which fetches data and reloads the page's contents. But this has to be invoked once another function has fetched data(webSql). I cannot use the WebSql callback as variables are out of scope. So I created a custom Event and added a listener in the second function scope. So when data is fetched I am dispatching the event in the first function scope. Now the issue if the page was reloaded more than once, listeners will get added multiple times and all will be invoked which I dont want.
I need to make sure that only one function is listening to a custom event. Right now am removing the listener once its invoked like this :
document.addEventListener("customEvent", function () {
actualCallBack(var1, var2); // Since I need to pass parameters I need to use callBack within an anonymous function.
this.removeEventListener("customEvent", arguments.callee, false);
}, false);
But the problem is anonymous function will be removed only after its invoked in the first place. There is a possibility of listener getting added mulitple times. How can I remove event listeners before adding a new one ?
document.removeEventListener("customEvent");
document.addEventListener(...);
I could have removed it, if a variable function was used instead, but I need to pass some parameters to callback so I need to use anonymous functions.
using felix's suggestion
var setSingletonEventListener = (function(element){
var handlers = {};
return function(evtName, func){
handlers.hasOwnProperty(evtName) && element.removeEventListener(evtName, handlers[evtName]);
if (func) {
handlers[evtName] = func;
element.addEventListener(evtName, func);
} else {
delete handlers[evtName];
}
};
})(document);
setSingletonEventListener("custom event", function(){
});
//replaces the previous
setSingletonEventListener("custom event", function(){
});
//removes the listener, if any
setSingletonEventListener("custom event");
Here's one way:
var singletonEventListenerFor = function (element, eventName) {
var callback = null;
element.addEventListener(eventName, function () {
callback && callback();
});
return function (set) {
callback = set;
};
};
Testing:
var event = document.createEvent("Event");
event.initEvent("customEvent", true, true);
var listener = singletonEventListenerFor(document, "customEvent");
var counter = 0;
listener(function () {
console.log(++counter);
});
// counter === 1
document.dispatchEvent(event);
// Remove the listener
listener();
// This shouldn't increment counter.
document.dispatchEvent(event);
listener(function () {
console.log(++counter);
});
// counter === 2
document.dispatchEvent(event);
// counter === 3
document.dispatchEvent(event);
console.log('3 === ' + counter);
http://jsfiddle.net/Dogbert/2zUZT/
API could be improved by returning an object with .set(callback) and .remove() functions instead of using a single function to do both things, if you like.
Store somewhere that you've already applied the listener, and only add it, if it hasn't been added already.