React Testing Library TypeError: Failed to execute 'removeEventListener' on 'EventTarget': 2 arguments required, but only 1 present - reactjs

i have react class component that have event
constructor(props) {
super(props);
this.monthRef = React.createRef();
this.yearRef = React.createRef();
this.state = {
moIndexActive: props.initialMonth - 1,
yeIndexActive: years.findIndex(y => y === `${props.initialYear}`),
};
}
//some code blablabla
// this error occur in this line bellow
componentWillUnmount() {
this.monthRef.current.removeEventListener('scroll');
this.yearRef.current.removeEventListener('scroll');
}
this component used in functional component,
when i test the functional component there is error message
i am using React Testing Library for test this, i already searched on google but not yet found solution for this, please help me.
if there is any way to mock the removeEventListener on react testing library or Jest.
thanks in advance.

The removeEventListener needs two arguments, type and listener.
type specifies the type of event for which to remove an event listener and
listener is the EventListener function of the event handler to remove from the event target.
So try to put a null in the place of listener like this
this.monthRef.current.removeEventListener('scroll',null)
Hope it works.

alfan-juniyanto,
The error message tells you exactly what's wrong -- your call to the method removeEventListener('scroll'), in your componentWillUnmount() function, is expecting two parameters:
The event to stop listening to,
The current event handler that you had previously registered when you called addEventListener (which doesn't appear to be in the code snippet you supplied).
For more information, you can read here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
However, to specifically answer your question, you should pass a pointer to the event handler that you used in the addEventListener cycle.
If you just want to make this error message stop for the purposes of your test, you could do something like this:
componentWillUnmount() {
const _fnEmptyFunctionPointer = ()=>{};
this.monthRef.current.removeEventListener('scroll', _fnEmptyFunctionPointer);
this.yearRef.current.removeEventListener('scroll', _fnEmptyFunctionPointer);
}
The code snippet I provided above simply just passes an empty function as the second (required) parameter for the removeEventListener method (really however, like I said previously, you should be passing the pointer to the original function you registered for the listener).
Hope this helps at least explain whats going on with that error you recieved.
Good luck!

Related

How to test react components props (expect component to be called with)

I need to test that a react component is called with opened={true} prop after an button click is fired. I am using testing-library ( testing-library/react + testing-library/jest-dom).
I mocked the Component using something like
import Component from "./path-to-file/component-name"
...
jest.mock("./path-to-file/component-name", () => {
return jest.fn().mockImplementation(() => {
return null
})
})
I first tried with:
expect(Component).toBeCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenLastCalledWith(expect.objectContaining({"opened": true}))
but I got Error: expect(jest.fn()).toBeCalledWith(...expected).
Same went for expect.objectContaining({"opened": expect.anything()})
And even for expect(Component).toBeCalledWith(expect.anything())
And the difference is empty array:
I also tried with expect(ChartMenu.mock).toBeCalledWith(expect.anything()). I got a different error but still not working (this time the error was Error: expect(received).toBeCalledWith(...expected) + Matcher error: received value must be a mock or spy function)
Thank you in advice!
EDIT: here is a simplified version of the component I want to test:
const Component = () => {
const [chartMenuOpened, setChartMenuOpened] = useState(false)
return (
<Flex>
<EllipseIcon onClick={() => setChartMenuOpened(true)}>
+
</EllipseIcon>
<ChartMenu
opened={chartMenuOpened}
close={() => setChartMenuOpened(false)}
/>
</Flex>
)
}
Basically I want to make sure that when the + icon is clicked the menu will be opened (or called with open value). The issue is that I cannot render ChartMenu because it needs multiple props and redux state.
I was able in the end to mock useState in order to check that the setState was properly called from the icon component (in order to make sure there won't be future changes on the component that will break this using this post).
But I would still really appreciate an answer to the question: if there is any way to create a spy or something similar on a react component and check the props it was called with? Mostly because this was a rather simple example and I only have one state. But this might not always be the case. Or any good idea on how to properly test this kind if interaction would be really appeciated.
I think you are on the right track to test if the component has been called with that prop, it's probably the syntax issue in your code
I learn this trick from colleague and you can try to see if this helps fix your issue.
expect(Component).toHaveBeenCalledWith(
expect.objectContaining({
opened: true,
}),
expect.anything()
);
While the question on how to is answered, I did some extra research on this and it seems like in React components there is a second parameter refOrContext (so basically most of the time it's an empty object, but it can also be a ref or a context)
Despite pointing out the reason for the behavior, I also wanted to highlight that it is safer to use expect.anything() as the second argument (rather than just {} which would work only in most of the cases ):
More information about React second argument here

How to mock a specific object returned as a response to function call?

I have an implementation where I want to mock a service that I have created:
this.device = major.get('alp").devC === 'devType';
This is happening in the constructor of a component. I want to mock the same in my jest test case and what I have done is:
jest.spyOn(major, 'get').mockImplementation(() => {
return { devC: 'devType2' };
});
I checked and can see that my code is going inside my mock and is returning the values correctly, but when I run the test cases I get devC of undefined.
I checked by putting logs in the component but then test failed at test case stating the same error above but prints correctly on the console screen where test were running with watch.
I think I found the solution to the problem. So the issue is with the place where you put the jest.spyOn. Since I was having the method call inside the constructor, It was important to put the spy even before we create the wrapper instance or even before we take the reference of the component in shallow/mount.
If you are having any such requirement to spy on the method in the constructor call you best bet could be to put spy first thing in your test case or in beforeEach() (if you want it to be applicable on all test cases).

ReactJS events - this.props.onSubmit(this.state....)

The following code snippet is from a simple Todo list which stores information on a local EOS blockchain and has a frontend web interface built on ReactJS. The question is related to ReactJS, specifically the event handler code snippet
saveTodo(e) {
e.preventDefault();
this.props.onSubmit(this.state.description)
this.setState({ description: "" })
}
The full program can be found here...https://github.com/eosasia/eos-todo/blob/master/frontend/src/index.jsx
In the body of the event handler saveTodo(e), there is a line this.props.onSubmit(this.state.description). I would like to know what exactly is going on here?
I am new to ReactJS, and it looks to that the above line of code is somehow setting a property (props) by calling a built-in function onSubmit() with an argument retrieved from the state object. Is this correct? I don’t see how onSubmit() was assigned to props anywhere in this code, but somehow we are able to use it like so: this.props.onSubmit(this.state.description) …. What’s going on here?
Thank you kindly.
P.S. pardon the terminology. I'm not sure if "event handler" or "event listener" is the right word.
The TodoForm component takes a property "onSubmit".
That line is simply calling this property (that was passed to it by its parent) and passing in the description ( taken from the state of TodoForm ).
For example:
<TodoForm onSubmit={(description) => alert(description)} />
Read more about props in react here.

Binding in React: What does `this` refer to if I don't bind?

I had some problems to understand the whole this issue in React (or JS in general) and found this very helpful article:
https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56
However, there is one basic thing that I'm still not sure about.
Let's take the Approach 2 as an example:
// Approach 2: Bind in Render
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = { message: 'Hi' };
}
logMessage() {
// This works because of the bind in render below.
console.log(this.state.message);
}
render() {
return (
<input type="button" value="Log" onClick={this.logMessage.bind(this)} />
);
}
}
Now, let's look at the wrong version of this code where we just do not do the binding that is required in order to refer to the right this (the HelloWorld component):
// Wrong "naive" version of the code
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = { message: 'Hi' };
}
logMessage() {
// This works because of the bind in render below.
console.log(this.state.message);
}
render() {
return (
<input type="button" value="Log" onClick={this.logMessage} />
);
}
}
My question is very simple: In that wrong version, it is my understanding that the this in console.log(this.state.message) within the logMessage function does not refer to the HelloWorld class object anymore. What does it refer to instead? Thank you!
EDIT: Turns out my understanding is wrong. This is not the this that does not work anymore. It's "the other" this at onClick={this.logMessage}! The reason will be given in the answers below - just wanted to correct this right here in the question.
Whenever you call a function using - (), the function context - this is set automatically based on whatever goes before ().
For example,
let A = {
foo() {
console.log(this === A);
}
};
when you call foo like this -
A.foo() // prints true
the context function foo receives depends on A.foo, here it is A. But when you call the SAME function using a different member - for example -
let B = {
foo: A.foo
};
B.foo(); // prints false
Even though you're still calling the exact same function, the function/method foo receives a different context (here B) -
And sometimes, you can force the context of a function using one of these three things -
1. .bind
Docs: MDN
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
2. .call, .apply
Both of them call the function with a given this value.
Docs: MDN Function.prototype.call
Docs: MDN Function.prototype.apply
3. Arrow function
Docs: MDN Arrow functions
class
JavaScript class methods have the same concepts -
class A {
foo() {
console.log(this instanceof A);
}
}
let a = new A();
a.foo(); // prints true
and when you run the experiment of assigning this function to a different method of a different object,
let b = {
foo: a.foo
};
b.foo(); // prints false
Whenever you pass a function somewhere else, you're not passing the context along with it, but you're expecting some context inside your function by using this and
The source of the issue
In your example,
<input type="button" value="Log" onClick={this.logMessage} />
specifically,
onClick={this.logMessage}
which is analogous to the above example -
let b = {
foo: a.foo
};
So, now, whoever calls your function using b.foo (here React) needs to know the context. So you have to set the context for the function before you pass it to onClick
What does it refer to instead if you don't bind?
In strict mode, the following happens -
Thus for a strict mode function, the specified this is not boxed into an object, and if unspecified, this will be undefined
Source: MDN under the section "Securing JavaScript"
And in all of the above examples with b.foo, if you assign it to a variable instead of object property,
let c = a.foo;
c(); // this will be undefined in foo
So, in your logMessage method, the value of this will be undefined.
this always refers to your class' attributes and behaviors but you have to bind this to the functions in which you want to use those attributes and behaviors. If you do not bind class level this with a function so "this" in that function refers to the attributes within the context of that function only.
You can also bind this in component cycle's callbacks like componentWillMount, constructor or anywhere where this has the reference to the class.
Forexampe:
componentWillMount(){
this.anyCallBack = this.anyCallBack.bind(this);
}
anycallBack(){
//now this will refer to the class
console.log(this);
}
The default value of 'this' depends on which event you are binding to. With JavaScript, in general, DOM events, such as onClick or onChange have 'this' as a pointer to the DOM element that triggered the event. For instance, if you were to create a button and attach a click listener, and in that listener, use console.log(this) you will see the button is the value of this.
Note: React has 'this' set as undefined by default.
Event handlers for setTimeout and setInterval, will set 'this' as a pointer to the window object.
There is an inherit danger to using bind. Each time you bind to a method, JavaScript creates a new method. The new method is an anonymous method unless you set it's value to a property or variable:
let myBoundMethod = myMethod.bind(this);
If you need to detach an event from a DOM element, unless you have a reference to the bound method, you will not be able to remove the event listener from the event. This can be a source of memory leaks in your application. Always use your debugging tools to monitor memory usage. It is normal for it to go up and down, but it should have a relatively stable baseline.
DOM elements removed from the DOM will often garbage collect along with their event listeners, but if there is still a reference in memory to the element, it will not be garbage collected.

How can I tell when this.setState exists in ES6?

I have been struggling with trying to migrate my React code from ES5 to ES6. As I have found out by now, this is no longer automatically bound which causes all sorts of hell.
I am trying to figure out by trial and error what objects are being passed around. So far I can find everything and adjust accordingly. However when it comes to this.setState I am having problems because it is not visible in console.log!!!! See screenshot in ES5:
and here is the same kind of code in ES6:
Please teach me how to fish i.e. help me figure out how to understand when an object has this.setState or not?
things i have tried
from googling around i understand you might be able to default bind everything by changing the base component. unfortunately this did not work when it came to this.setState. It looks identical to the ES5 version of this in console so I concluded that setState is still not being bound somehow
To oversimplify how this works in JS:
If you call a function as an object method (e.g., instance.foo()) then this refers to that object instance.
If you call a function by itself (e.g., foo()), then this is either undefined or the global object, depending on whether strict mode is in effect.
If you take a reference to an object method then call it, that means you're calling it by itself, even though it was originally an object method. (E.g., var bar = instance.foo; bar();.
Again, this is an oversimplification; MDN has details.
As this applies to React, and as explained in the React documentation on "Handling Events":
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
In your code, you render your RawInput as
<RawInput value={this.state.value} updateValue={this.updateValue}/>
You're passing a reference updateValue function in as a simple function, so this will not be bound within updateValue.
Basically, any time you pass a function as a React prop, unless you've bound it yourself, it's likely an error. The symptom is typically that this is undefined. In your code, it's a little more complicated:
this.props.updateValue(modifiedValue);
The RawInput's updateValue property is the unbound function App.updateValue, but because you're invoking it as this.props.updateValue, it's being called as if it were a method of this.props - so this refers to the RawInput's props. That's why your console.log is showing an object with only two properties (start and updateValue): it isn't that setState isn't bound or went away, it's that updateValue wasn't bound, so this isn't what you expect within updateValue.
To fix the issue, as the React docs explain:
Use a fat arrow function: updateValue={(value) => this.updateValue(value)}
Use the experimental property initializer syntax: Replace updateValue(modifiedValue) {...} with updateValue = (modifiedValue) => {...}.
Not mentioned in the React docs, but an approach I often use: Bind updateValue yourself. For example:
constructor(props) {
super(props);
this.updateValue = this.updateValue.bind(this);
}
you can replace console.log with this:
console.shallowCloneLog = function(){
var typeString = Function.prototype.call.bind(Object.prototype.toString)
console.log.apply(console, Array.prototype.map.call(arguments, function(x){
switch (typeString(x).slice(8, -1)) {
case 'Number': case 'String': case 'Undefined': case 'Null': case 'Boolean': return x;
case 'Array': return x.slice();
default:
var out = Object.create(Object.getPrototypeOf(x));
out.constructor = x.constructor;
for (var key in x) {
out[key] = x[key];
}
Object.defineProperty(out, 'constructor', {value: x.constructor});
return out;
}
}));
}
any way, regarding your question, you can add a method like this:
updateValue = () => {...}
in m POV - es6 is cool and great. React components by es6' classes are useless. stick with createClass and you'll be fine (and have mixins if you want!)
Try Object.prototype.hasOwnProperty(). For example:
var X = function() {};
X.prototype.setSomething = 'a';
var x = new X();
x.setSomething; // log 'a' here
x.hasOwnPrperty('setSomething') // log false here
In your case, just console.log(this.hasOwnProperty('setState')).
You have to bind your updateValue function with the component in order to have the correct context (this).
In your case, your parent class BaseComponent allows you to use the herited method _bind like that :
class App extends BaseComponent {
constructor(props){
super(props);
this.state={value:'start'};
this._bind('updateValue');
...

Resources