My React code keeps triggering EventEmitter callbacks that should have been removed. When I inspect my React class that extends EventEmiiter, I found that the callbacks are still present in the events fields, even though removeListener has already been called on those callbacks.
I suspect this is happening because I add callbacks to the EventEmitter as arrow functions, but when I remove them, I do not. Removing them as arrow functions do not work. In my code below, FirebaseStore extends EventEmitter:
_onFirebaseChange() {
this.setState({
refId: this.getRefId()
});
}
componentDidMount() {
FirebaseStore.addChangeListener(() => this._onFirebaseChange());
}
componentWillUnmount() {
FirebaseStore.removeChangeListener(this._onFirebaseChange);
}
I need to use arrow functions because the change events (_onFirebaseChange), need access to this.state.
When I look at my source from Chrome dev tools, it's difficult for my to determine whether addChangeListener and removeChangleListener refer to the same callback:
key: 'componentDidMount',
value: function componentDidMount() {
var _this2 = this;
_FirebaseStore2.default.addChangeListener(function () {
return _this2._onFirebaseChange();
});
}
},{
key: 'componentWillUnmount',
value: function componentWillUnmount() {
_FirebaseStore2.default.removeChangeListener(this._onFirebaseChange);
}}
Here is the relevant code from Firebase store:
addChangeListener(callback) {
this.on(FIREBASE_CHANGE_EVENT, callback);
}
removeChangeListener(callback) {
this.removeListener(FIREBASE_CHANGE_EVENT, callback);
}
}
It would be possible to fix this with a closure, but I think that's overkill.
Update your componentDidMount method.
componentDidMount() {
FirebaseStore.addChangeListener(this._onFirebaseChange);
}
The function needs to be binded directly.
This was a simple fix that I should have caught earlier. The closure solution Alexi posted would have also worked, but all that was missing was to bind * _onFirebaseChange* to this in the constructor.
So in the constructor of my React class, I added this line:
this._onFirebaseChange = this._onFirebaseChange.bind(this);
Then my componentDidMount function was able to refer to the same callback as componentWillUnmount, while also being able to access this.state.
componentDidMount() {
FirebaseStore.addChangeListener(this._onFirebaseChange);
}
componentWillUnmount() {
FirebaseStore.removeChangeListener(this._onFirebaseChange);
}
Related
I'm using React JS and Redux JS.
I know that Redux actions functions should be a pure function
But, I was reading about Object Oriented Programming (OOP) and its benefits.
So, Can I use OOP in Redux action (Class) instead of pure functions ??
For example:
If I've a state named "articles", It may have actions like:
function getArtcles() { ... }
function addArtcle({data}) { ... }
function deleteArtcle(id) { ... }
......
So, can I replce that with:
class Artice {
getArtcles() {...}
addArtcle() {...}
deleteArtcle() {...}
}
??
I assume those functions are action creators? Ie, they return an object with a type property, and possibly some additional data?
function deleteArticle(id) {
return { type: 'deleteArticles', id };
}
Or they may create thunks:
function deleteArticle(id) {
return async function (dispatch) {
dispatch({ type: 'deleteStarting' });
// do something asynchronous
dispatch({ type: 'deleteSuccessful' });
}
}
If so, yes, you can put them in a class if you want. I don't see a benefit to putting these in a class, but redux doesn't even have a way to know how you created the actions, so it won't mind.
In one of my components I have the following functions:
addNewIndicator(attrs = {}) {
const value = attrs.value || 'Indicator'
const type = attrs.type || 'Generic Type'
this.createIndicator(value).then(
console.log('Indicator Created.')
)
}
async createIndicator(value) {
await this.props.createIndicatorMutation({
variables: {
value
},
update: (store, { data: { indicator }} ) => {
const data = store.readQuery({ query: INDICATOR_FEED_QUERY })
data.indicatorFeed.splice(0, 0, indicator)
store.writeQuery({
query: INDICATOR_FEED_QUERY,
data,
})
}
})
}
addNewIndicator() is triggered on a button click. When it runs, I get the following error:
TypeError: this.createIndicator is not a function
It is pointing to this line:
this.createIndicator(value).then(
I've done quite a bit of Googling, but haven't been able to figure out why this is the case. My understanding is that async functions can be called like that, but perhaps I'm missing something. Sorry if this is a silly question, I'm still learning React!
Also, I created the project using create-react-app and haven't modified it much other than adding some packages. Thanks for any help!
Edit to add how it is called. It is called from a child component props:
<Button primary onClick={this.handleAddSelectionClick}>Add Selected As Indicator</Button>
and handleAddSelectionClick:
handleAddSelectionClick = () => {
...snip...
this.props.addNewIndicator({
value: new_indicator_str,
})
}
try to validate that your 2 methods using the same context (this) -
you might need to do something like that in the constructor:
this.addNewIndicator = this.addNewIndicator.bind(this);
this.createIndicator = this.createIndicator.bind(this);
You probably just forgot to bind “this”. As you passed addNewIndicator as a callback, it lost its context. In this article several methods of binding callbacks are described, with all pros and cons of each.
https://reactjs.org/docs/handling-events.html
I simply want to fetch a string from a Firebase database and update the "eventName" field in my component, but I'm having trouble doing this.
My code so far:
import React from 'react';
import {ref} from '../Firebase';
class EventDisplay extends React.Component {
constructor () {
super();
this.state = {
eventName: "No event Selected."
};
}
render () {
return (<div>
<h1>Name of Event: {this.state.eventName}</h1>
<h1>Date and Time: Dummy Name</h1>
<h1>Event Description: Dummy Name</h1>
</div>);
}
changeEventName(str) {
this.setState({eventName: str});
}
componentWillMount() {
const events = ref.child('events/event1/Title');
var param;
events.on('value', (function(snapshot) {
param = snapshot.val();
console.log(snapshot.val());
changeEventName(param);
})
);
console.log(param);
}
}
export default EventDisplay;
However, changeEventName seems to be undefined where it is. Also "undefined" shows up in the console where I try to log param, but snapshot.val() has the desired string.
Thanks
changeEventName seems to be undefined where it is
Where you're calling changeEventName you need to prepend it with this.
this.changeEventName
Also since you're calling it inside of a callback you first need to bind the method in order to preserve the value of this. There're many ways you can do this, most common being:
Explicitly inside of a constructor:
this.changeEventName = this.changeEventName.bind(this)
Or using an arrow function:
events.on('value', ((snapshot) => { ... }));
Also "undefined" shows up in the console where I try to log param
That's because events.on is asynchronous, you need to move your console.log inside of the callback.
Try this.changeEventName when invoking the function; you need to do this because the function is available only within the context of the class.
Logging param returned undefined because events.on is an asynchronous function. What that means is that this function will go do whatever it was designed to do (fetch value) and only then execute the callback when its ready; param is only available in this callback method that you provided.
I'm fairly new to Ember.
This is the code for my component:
import Ember from 'ember';
export default Ember.Component.extend({
profiles: Ember.inject.service(),
tagName: 'td',
classNames: ['grey'],
classNameBindings: ['unreadMessages'],
unreadMessages: null,
onInit: function() {
const id = this.get('conversation.id');
return this.get('profiles').getMessages(id)
.then(function(bool) {
this.set('unreadMessage', bool);
});
}.on('init')
});
This throws:
TypeError: Cannot read property 'set' of undefined
So I can gather that I don't have the this context that I need to call this.set inside of the .then()
I need to assign the result of return this.get('profiles').getMessages(id) to the unreadMessages property in my component. So that I can use it for the classNameBinding.
Here is the method I'm calling from the service
getMessages(id){
return this.get('ajax').request('/messages?id=' + id)
.then((obj) => {
const unreadMessages = obj.messages.filter((e) => e.read === false);
if (unreadMessages === []) {
return false;
} else {
return true;
}
});
}
I've only been able to access the boolean value that getMessages returns inside of its .then() and I am not able to call this.set() inside of the .then() I'm looking for a work around. I think I'm close and am struggling to due to my lack of experience with Ember.
getMessages makes a 'GET' request to my back end and filters through the messages to check if there are any that are unread and then returns true or false. The purpose of the classNameBinding is to notify the user of whether they have any unread messages for that thread. This is a very simple email style messaging app that I am building for practice.
Thanks!
Change
onInit: function() {
const id = this.get('conversation.id');
return this.get('profiles').getMessages(id)
.then(function(bool) {
this.set('unreadMessage', bool);
});
}.on('init')
});
to
onInit: function() {
const id = this.get('conversation.id');
return this.get('profiles').getMessages(id)
.then((bool) => {
this.set('unreadMessage', bool);
});
}.on('init')
});
The thing here is, the scope changes when you write function(){} inside then and this won't refer to the component this.
That's why in ES6 the concept of lexical this was introduced. This will retain the this. So use Arrow function instead and it'll work smoothly..
I am using Vue.Js v2. I want to call component1->c1method in component2->c2method for reload data after submitting.
Vue.component('component1', {
methods: {
c1method: function(){
alert('this is c1method')
},
}
})
Vue.component('component2', {
methods: {
c2method: function(){
component('component1').c1method()//like this
},
}
})
For non-parent-child relation, then this is the same as this one. Call one method, apparently any method of a component from any other component. Just add a $on function to the $root instance and call form any other component accessing the $root and calling $emit function.
On First component
....
mounted() {
this.$root.$on('component1', () => {
// your code goes here
this.c1method()
}
}
and in the second component call the $emit function in $root
...
c2method: function(){
this.$root.$emit('component1') //like this
},
It acts more like a socket. Reference here
https://stackoverflow.com/a/50343039/6090215
// Component A
Vue.component('A', {
created() {
this.$root.$refs.A = this;
},
methods: {
foo: function() {
alert('this is A.foo');
}
}
});
// Component B
Vue.component('B', {
methods: {
bar: function() {
this.$root.$refs.A.foo();
}
}
});
No need for hacky solutions.
In vuejs we can create events that can be listened globally.
With this feature, whenever we want to call our beloved function, we just emit this event.
Now, we just listen to this event all the time from the component. whenever this global event happens we can execute our method we want to call.
It's pretty simple:
you go to main.js, before creating the vue instance, write this:
export const eventBus = new Vue(); // added line
new Vue({
...
...
...
render: h => h(App)
}).$mount('#app');
Anywhere we want to fire the target function, we dont fire it, we just emit this event:
eventBus.$emit('fireMethod');
Now in our component that has the target function, we always listen to this event:
created() {
eventBus.$on('fireMethod', () => {
this.myBelovedMethod();
})
}
Dont forget to import eventBus in top.
import {eventBus} from "path/to/main.js";
thats it, few lines of code, no hacky, all vuejs power.
The docs address this situation
https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
If your components have the same parent, you can emit an event that the parent listens to. Then with the ref property set, you can call the c1method from the parent.
https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs
Try this out.
<template>
...
<component1 ref='componentOne'>
...
</template>
<script>
Vue.component('component2', {
methods: {
c2method: function(){
this.$refs.componentOne.c1method();
}
}
});
</script>
If anyone is looking for a solution in Vue.js v3:
https://v3-migration.vuejs.org/breaking-changes/events-api.html#event-bus
https://github.com/developit/mitt#install
import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// listen to all events
emitter.on('*', (type, e) => console.log(type, e) )
// fire an event
emitter.emit('foo', { a: 'b' })
// clearing all events
emitter.all.clear()