Dispatch undefined after binding to class in redux - javascript

Issue with a pattern i'm trying to use with redux.
I have a a mapDispatchToProps as below,
const mapDispatchToProps = (dispatch) => {
return {
presenter: new Presenter(dispatch),
};
};
and my presenter constructor looks as below:
constructor(dispatch) {
this.dispatcher = dispatch;
}
If I check the value of it in the constructor and after it's set, all is well. However later when a method tries to use it, the value of dispatch is undefined.
If i save it to a var outside the class, i.e.
let dispatch;
class Presenter {
constructor(dispatcher) {
dispatch = dispatcher.bind(this)
}
}
I've tried using .bind() within the first constructor also but it keeps becoming undefined!

Class methods were of the form:
someMethod() {
//do stuff
}
which means they have their own this scope bound... I'd have to bind the individual methods in the constructor, such as:
constructor(dispatch) {
this.dispatch = dispatch;
this.someMethod = this.someMethod.bind(this);
}
Or turn them into => functions so they take their context from the surrounding class, i.e.
someMethod = () => dispatch(/* an action */);

Related

How to mock a specific method of a class whilst keeping the implementation of all other methods with jest when the class instance isn't accessible?

Based on this question (How to mock instance methods of a class mocked with jest.mock?), how can a specific method be mocked whilst keeping the implementation of all other methods?
There's a similar question (Jest: How to mock one specific method of a class) but this only applies if the class instance is available outside it's calling class so this wouldn't work if the class instance was inside a constructor like in this question (How to mock a constructor instantiated class instance using jest?).
For example, the Logger class is mocked to have only method1 mocked but then method2 is missing, resulting in an error:
// Logger.ts
export default Logger() {
constructor() {}
method1() {
return 'method1';
}
method2() {
return 'method2';
}
}
// Logger.test.ts
import Logger from './Logger';
jest.mock("./Logger", () => {
return {
default: class mockLogger {
method1() {
return 'mocked';
}
},
__esModule: true,
};
});
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
const logger = new Logger(); // Assume this is called in the constructor of another object.
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2'); // TypeError: logger.method2 is not a function.
});
});
One solution is to extend the Logger class but this results in an undefined error as the Logger is already mocked:
// ...
jest.mock("./Logger", () => {
return {
default: class mockLogger extends Logger {
override method1() {
return 'mocked';
}
},
__esModule: true,
};
});
// ...
expect(logger.method2()).toBe('method2'); // TypeError: Cannot read property 'default' of undefined
Therefore, what could be the correct way to mock only method1 but keep method2's original implementation?
You can use jest.spyOn and provide a mock implementation for method1.
// Logger.test.ts
import Logger from './Logger';
jest.spyOn(Logger.prototype, "method1").mockImplementation(() => "mocked")
describe("Logger", () => {
it("calls method1 & method2 but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
})
})
But in case you have many methods and you want to mock each one of them except one single method, then you can get the original implementation of this one single method using jest.requireActual.
// Logger.test.ts
import Logger from "./Logger";
const mockMethod1 = jest.fn().mockReturnValue("mocked");
const mockMethod3 = jest.fn().mockReturnValue("mocked");
const mockMethod4 = jest.fn().mockReturnValue("mocked");
const mockMethod5 = jest.fn().mockReturnValue("mocked");
jest.mock("./Logger", () =>
jest.fn().mockImplementation(() => ({
method1: mockMethod1,
method2: jest.requireActual("./Logger").default.prototype.method2,
method3: mockMethod3,
method4: mockMethod4,
method5: mockMethod5,
}))
);
describe("Logger", () => {
it("calls all methods but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
expect(l.method3()).toBe("mocked");
expect(l.method4()).toBe("mocked");
expect(l.method5()).toBe("mocked");
});
});
Note: You don't need to define an ES6 class for mocking, a constructor function also just works fine because ES6 classes are actually just syntactic sugar for constructor functions.
Mocking the prototype works:
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
Logger.prototype.method1 = jest.fn(() => 'mocked');
const logger = new Logger();
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2');
});
});
However, I'm not sure if this is the correct way to mock a specific method when the class instance isn't accessible so I'll leave the question open for while in case there are better solutions.

Working of a Autobind Decorator in typescript?

Hello I am curious about the working of the decorator in Typescript for binding 'this' to functions in Typescript.
function autoBind(
target:any,
methodName:String,
descriptor:PropertyDescriptor
){
console.log("Calling Decorator");
const originalMethod = descriptor.value;
const adjustableDescriptor: PropertyDescriptor = {
configurable : true,
get(){
console.log("Calling get");
const boundFn = originalMethod.bind(this);
return boundFn;
}
}
return adjustableDescriptor;
}
class ProjectInput {
constructor(){
this.configure();
}
#autoBind
private submitHandler(event: Event){
console.log("Calling submit handler");
event.preventDefault();
console.log("Submitting data ...");
console.log(this.titleInputElement.value);
}
private configure() {
this.element.addEventListener("submit",this.submitHandler);
}
}
const projInput = new ProjectInput();
What I did :
I created a Class ProjectInput and in the constructor i am calling the configure method so that i can add EventListeners and handle user submit data and for binding 'this' so that it reference the right object.
I created a Decorator in typescript that will call automatically as soon as the class declared
Everything is fine but I want to know the behind the scenes of the decorator how it binds the this to the function.
I came here, hoping I'd get a more thorough answer than what I'd be able to find, but at least it encouraged me to dig a little further.
Taken directly from React & Autobinding:
Autobind Decorator is an NPM package which binds methods of a class to the correct instance of this, even when the methods are detached. The package uses #autobind before methods to bind this to the correct reference to the component's context.
import autobind from 'autobind-decorator'
class MyComponent extends React.Component {
constructor() {
/* ... */
}
#autobind
addTask(task) {
/* ... */
this.setState({ task });
}
#autobind
myMethod2(task) {
/* ... */
this._anotherBindedMethod(task);
}
render() {
return (
/* ... */
)
}
}
his seems like a simple solution, but I'd rather not have to add a line above each individual method inside each of my React components. Not to worry, Autobind Decorator is smart enough to let us bind all methods inside a component class at once. Like so:
import autobind from 'autobind-decorator'
#autobind
class MyComponent extends React.Component {
constructor() {
/* ... */
}
addTask(task) {
/* ... */
this.setState({ task });
}
/* ... */
}
And just like that, this issue is resolved.
Anyway, hope that helps. Reading it a couple times, helped me. Cheers.
Be careful cause in your code getter returns a new function each time is called and this can potentially lead to memory leaks. This happen cause .bind returns a new function.
So for example if you do .addEventListener('click', this.submitHandler) you're adding a new function each time. .removeEventListener('click', this.submitHandler) will not remove nothing cause will not match any listener
You can easily test this is truth like this
const projectInput = new ProjectInput();
projectInput.submitHandler === projectInput.submitHandler; // false
So an easy fix to your code could be this one
function autobindFunction(
target:any,
methodName:String,
descriptor:PropertyDescriptor
){
console.log("Calling Decorator");
if(typeof descriptor.value !== 'function') {throw new TypeError("cannot decorate prop that is not a function")}
const bound = descriptor.value
const adjustableDescriptor: PropertyDescriptor = {
configurable : true,
value: function (...args: any[]) {
return bound.apply(this, args)
}
}
return adjustableDescriptor;
}
class Test {
#autobindFunction
hi() {
console.log("asd")
}
}
const a = new Test()
console.log(a.hi === a.hi) // true
In this way the reference of the function is stable and the function will be always the same

Difference between arrow functions and normal functions in React Native

So I've always thought of arrow functions to be a new better and version of normal js functions until today. I was following a tutorial on how to use firestore to store data when I came across a problem that made realise the two are different and work in a weird way.
His code looked like this:
//component
function Todos() {
const [ todo, setTodo ] = useState('');
const ref = firestore().collection('todos');
// ...
async function addTodo() {
await ref.add({ title: todo, complete: false});
setTodo('');
}
// ...
}
My code looked like this:
//component
const Todos = () => {
const ref = firestore().collection('todos');
const [todo, setTodo] = useState('');
const addTodo = async () => {
const res = await ref.add({ title: todos, complete: false });
setTodo('');
};
};
Now his version worked, while mine didn't.
After changing my code to look like his, it worked. But the weird thing i realised was this: after clicking on the button that invoked that function for the first time (with his function), i changed the code back to mine and it worked the second time. I did some reading on the two functions but i couldn't get to reasoning behind why this happened.
Arrow functions and normal function are not equivalent.
Here is the difference:
Arrow function do not have their own binding of this, so your this.setState refer to the YourClass.setState.
Using normal function, you need to bind it to the class to obtain Class's this reference. So when you call this.setState actually it refer to YourFunction.setState().
Sample Code
class FancyComponent extends Component {
handleChange(event) {
this.setState({ event }) // `this` is instance of handleChange
}
handleChange = (event) => {
this.setState({ event }) // `this` is instance of FancyComponent
}
}

mobx computed / reactive context with event handlers?

With MobX, #computed properties are only cached when accessed from an observer or reactive context. So for example:
class Foo {
#computed
get somethingExpensive() { return someReallyExpensiveOperation() }
}
const f = new Foo();
setInterval(() => console.log(f.somethingExpensive), 1);
Will always call someReallyExpensiveOperation() because the computed is being called outside of a reactive context.
Is there a way to "enter" a reactive context to gain the benefits of #computed for setTimeout callbacks, EventEmitter event handlers etc?
EDIT: Another way to put this.. if I change the decorator to
class Foo {
#computed({ requiresReaction: true })
get somethingExpensive() { return someReallyExpensiveOperation() }
}
..it will throw when accessed from the setInterval example.
I use an instantly disposed autorun:
autorun((reaction) => {
console.log(f.somethingExpensive)
reaction.dispose()
})
Also made a npm module:
import runInReactiveContext from 'mobx-run-in-reactive-context'
// ...
runInReactiveContext(() => {
console.log(f.somethingExpensive)
})

Run chained method before anything in constructor?

I have this simple class:
class Foo {
constructor() {
this.init();
return this;
}
init() {
this.onInit();
}
onInit(callback) {
this.onInit = () => callback();
return this;
}
}
new Foo().onInit(() => console.log('baz'));
It's obviously flawed, because it will call init before the onInit method is able to define the onInit property/callback.
How can I make this work without change the interface?
How can I make this work without change the interface?
You can't, the interface is inherently flawed. That's really the answer to your question.
Continuing, though, with "what can I do instead":
If you need to have a callback called during initialization, you need to pass it to the constructor, not separately to the onInit method.
class Foo {
constructor(callback) {
this.onInit = () => {
callback(); // Call the callback
return this; // Chaining seemed important in your code, so...
};
// Note: Constructors don't return anything
}
}
new Foo(() => console.log('baz'));
In a comment you've said:
I see your point, the fact is that my library is new Something().onCreate().onUpdate()
It sounds like you might want to adopt the builder pattern instead:
class Foo {
constructor(callbacks) {
// ...use the callbacks here...
}
// ...
}
Foo.Builder = class {
constructor() {
this.callbacks = {};
}
onCreate(callback) {
this.callbacks.onCreate = callback;
}
onUpdate(callback) {
this.callbacks.onUpdate = callback;
}
// ...
build() {
// Validity checks here, do we have all necessary callbacks?
// Then:
return new Foo(this.callbacks);
}
};
let f = new Foo.Builder().onCreate(() => { /*...*/}).onUpdate(() => { /*... */}).build();
...although to be fair, a lot of the advantages (though not all) of the builder pattern can be realized in JavaScript by just passing an object into constructor directly and doing your validation there, e.g.:
let f = new Foo({
onCreate: () => { /*...*/},
onUpdate: () => { /*...*/}
});
Assuming that onInit is supposed to be some sort of hook to be called synchronously whenever an object is instantiated, you can't solve this on the instance level.
You can make onInit a static function, like so:
class Foo {
constructor() {
// whatever
Foo.onInit();
}
static onInit() {} // empty default
}
Foo.onInit = () => console.log('baz'); // Override default with your own function
const f = new Foo();
const f2 = new Foo();

Categories

Resources