This in classes and regular functions JS - this

When we have an object and a method inside it, this refers to the object that it belongs to:
let myObj = {
data: 'someData',
test() {
console.log(this);
},
};
myObj.test();
here the result is : myObj
but what if my method is in a class with constructor?
export default class MovieCard extends React.Component {
constructor(props) {
super(props);
this.state = { content: 'test' };
}
changer() {
console.log(this);
}
render() {
return null;
}
}
It seems, in this case, this equals to 'undefined'
why is that? is it because it has not been called yet?

It is a special case when your class is a React.Component. The this keywords in methods (except the constructor) of such classes is not automatically bound to the instance of the component.
A common solution is to add the following line to the constructor (for each method using this):
constructor(props) {
// ...
this.changer = this.changer.bind(this);
}
It makes sure that your methods have correctly bound this's.
For details of bind(): MDN Web Docs

Related

NodJS how to get the Object a function belongs

some times code says it best. In below example code in Chain.add I have the function name and vars fed in to it. But I am trying to reference the object that the function is associated with. How can I do this
class Chainable {
constructor(...values) {
this._chainableConstruct={
name: this.constructor.name,
values
};
}
}
class Chain {
constructor() {
this.data=[];
}
add(func,vars) {
console.log(func.name); //returns fun
console.log(...vars); //returns test 45
console.log(func.parent); //return undefined want object t from line 28
}
}
class Test extends Chainable {
fun() {
console.log("fun");
}
}
let t=new Test();
let c=new Chain();
c.add(t.fun,["test",45]);
Out of the box, you can't. Furthermore, you can set property values of multiple objects with the same value, so the same function object might have multiple "parents".

TypeScript - Repository pattern with Sequelize

I'm converting my Express API Template to TypeScript and I'm having some issues with the repositories.
With JavaScript, I would do something like this:
export default class BaseRepository {
async all() {
return this.model.findAll();
}
// other common methods
}
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository {
constructor() {
super();
this.model = User;
}
async findByEmail(email) {
return this.model.findOne({
where: {
email,
},
});
}
// other methods
Now, with TypeScript, the problem is that it doesn't know the type of this.model, and I can't pass a concrete model to BaseRepository, because, well, it is an abstraction. I've found that sequelize-typescript exports a ModelCtor which declares all the static model methods like findAll, create, etc., and I also could use another sequelize-typescript export which is Model to properly annotate the return type.
So, I ended up doing this:
import { Model, ModelCtor } from 'sequelize-typescript';
export default abstract class BaseRepository {
protected model: ModelCtor;
constructor(model: ModelCtor) {
this.model = model;
}
public async all(): Promise<Model[]> {
return this.model.findAll();
}
// other common methods
}
import { Model } from 'sequelize-typescript';
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository {
constructor() {
super(User);
}
public async findByEmail(email: string): Promise<Model | null> {
return this.model.findOne({
where: {
email,
},
});
}
// other methods
}
Ok, this works, TypeScript doesn't complain about methods like findOne or create not existing, but that generates another problem.
Now, for example, whenever I get a User from the repository, if I try to access one of its properties, like user.email, TypeScript will complain that this property does not exist. Of course, because the type Model does not know about the specifics of each model.
Ok, it's treason generics then.
Now BaseRepository uses a generic Model type which the methods also use:
export default abstract class BaseRepository<Model> {
public async all(): Promise<Model[]> {
return Model.findAll();
}
// other common methods
}
And the concrete classes pass the appropriate model to the generic type:
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository<User> {
public async findByEmail(email: string): Promise<User | null> {
return User.findOne({
where: {
email,
},
});
}
// other methods
}
Now IntelliSense lights up correctly, it shows both abstract and concrete classes methods and the model properties (e.g. user.email).
But, as you have imagined, that leads to more problems.
Inside BaseRepository, where the methods use the Model generic type, TypeScript complains that 'Model' only refers to a type, but is being used as a value here. Not only that, but TypeScript also doesn't know (again) that the static methods from the model exist, like findAll, create, etc.
Another problem is that in both abstract and concrete classes, as the methods don't use this anymore, ESLint expects the methods to be static: Expected 'this' to be used by class async method 'all'. Ok, I can just ignore this rule in the whole file and the error is gone. It would be even nicer to have all the methods set to static, so I don't have to instantiate the repository, but maybe I'm dreaming too much.
Worth mentioning that although I can just silence those errors with // #ts-ignore, when I execute this, it doesn't work: TypeError: Cannot read property 'create' of undefined\n at UserRepository.<anonymous>
I researched a lot, tried to make all methods static, but static methods can't reference the generic type (because it is considered an instance property), tried some workarounds, tried to pass the concrete model in the constructor of BaseRepository along with the class using the generic type, but nothing seems to work so far.
In case you want to check the code: https://github.com/andresilva-cc/express-api-template/tree/main/src/App/Repositories
EDIT:
Found this: Sequelize-Typescript typeof model
Ok, I removed some unnecessary code from that post and that kinda works:
import { Model } from 'sequelize-typescript';
export default abstract class BaseRepository<M extends Model> {
constructor(protected model: typeof Model) {}
public async all(attributes?: string[]): Promise<M[]> {
// Type 'Model<{}, {}>[]' is not assignable to type 'M[]'.
// Type 'Model<{}, {}>' is not assignable to type 'M'.
// 'Model<{}, {}>' is assignable to the constraint of type 'M', but 'M' could be instantiated with a different subtype of constraint 'Model<any, any>'.
return this.model.findAll({
attributes,
});
}
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository<User> {
constructor() {
super(User);
}
}
I mean, if I put some // #ts-ignore it at least executes, and IntelliSense lights up perfectly, but TypeScript complains.
We faced the same problem. The solution was to declare returning types with an interface that an abstract repository class implements.
Code for the interface:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
export interface IRepo<M> {
save(model: M): RepoResult<M>;
findById(id: string): RepoResult<M>;
search(parameterName: string, parameterValue: string, sortBy: string, order: number, pageSize: number, pageNumber: number): RepoResult<M[]>;
getAll(): RepoResult<M[]>;
deleteById(id: string): RepoResult<M>;
findByIds(ids: string[]): RepoResult<M[]>;
deleteByIds(ids: string[]): RepoResult<any>;
};
Code for the abstract class:
export abstract class Repo<M extends sequelize.Model> implements IRepo<M> {
protected Model!: sequelize.ModelCtor<M>;
constructor(Model: sequelize.ModelCtor<M>) {
this.Model = Model;
}
public async save(doc: M) {
try {
const savedDoc = await doc.save();
return Result.ok(savedDoc);
} catch (ex: any) {
logger.error(ex);
return Result.fail(new RepoError(ex.message, 500));
}
}
public async findById(id: string) {
try {
const doc = await this.Model.findOne({where: {
id: id
}});
if (!doc) {
return Result.fail(new RepoError('Not found', 404));
}
return Result.ok(doc);
} catch (ex: any) {
return Result.fail(new RepoError(ex.message, 500));
}
}
}
Hope it helps. Have a nice day:)
EDIT:
Result is a class that looks like this:
export class Result<V, E> {
public isSuccess: boolean;
public isFailure: boolean;
private error: E;
private value: V;
private constructor(isSuccess: boolean, value: V, error: E) {
if (isSuccess && error) {
throw new Error('Successful result must not contain an error');
} else if (!isSuccess && value) {
throw new Error('Unsuccessful error must not contain a value');
}
this.isSuccess = isSuccess;
this.isFailure = !isSuccess;
this.value = value;
this.error = error;
}
public static ok<V>(value: V): Result<V, undefined> {
return new Result(true, value, undefined);
}
public static fail<E>(error: E): Result<undefined, E> {
return new Result(false, undefined, error);
}
public getError(): E {
if (this.isSuccess) {
throw new Error('Successful result does not contain an error');
}
return this.error;
}
public getValue(): V {
if (this.isFailure) {
throw new Error('Unsuccessful result does not contain a value');
}
return this.value;
}
}
RepoError class:
type RepoErrorCode = 404 | 500;
export class RepoError extends Error {
public code: RepoErrorCode;
constructor(message: string, code: RepoErrorCode) {
super(message);
this.code = code;
}
}
RepoResult type:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
You can find more info on the pattern at the link below:
https://khalilstemmler.com/articles/enterprise-typescript-nodejs/functional-error-handling/

React.JS - Calling a function on `onClick` event causes `this` to be undefined in function

I have the following React.JS (Next.JS) code:
export default class NavBar extends React.Component {
constructor(props) {
super(props);
this.state = {
menuOpen: false
};
}
render() {
return <img id="menu-button" src="/static/menu.svg" onClick={this.toggleMenu}></img>;
}
toggleMenu() {
this.setState({ menuOpen: this.state.menuOpen ? false : true });
}
};
However, when the image is clicked, and toggleMenu is called, I get an error saying that this is undefined. This makes sense, since the function is being put into the onClick event, but how can I fix this?
You need to either explicitly bind this context to your function like:
this.toggleMenu.bind(this)
or re-define your toggleMenu function using arrow notation which does this implicitly:
toggleMenu = () => {
// method body here...
}

Can I overload method in module.export in node.js?

I have an app.js with this code:
var addnote = (title,body) => { /* enter code here */ }
module.exports = {addnote};
Can I add another addnotes function with different parameters to that file?
Function overloading in JavaScript does not exist like in other programming languages such as C# and Java.
What you should be looking to do is pass an object as a parameter that has properties attached and filter them out there..
You could call different functions from your little 'mapping function' just implement the logic there if it isn't big (to keep the code clear).
function foo(parameters){
var title = parameters.title;
var body = parameters.body;
if(parameters.extraProperty){
// oh we have extraProperty passed in too, run a different function?
bar(title, body, parameters.extraProperty); // ??
}
}
foo({title: 'Title', body: 'Body', extraProperty: 'This is extra...'});
If this is your own custom module, you can use the concept of function overriding, where each child class can have its own way to handle something and also have a default way to do things.
class Parent {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello ${this.name}`);
}
}
class Child1 extends Parent {
constructor(name) {
super(name);
}
greet() {
console.log(`Hey there ${this.name}. This is Child 1`);
}
}
class Child2 extends Parent {
constructor(name) {
super(name);
}
greet() {
console.log(`Hi there ${this.name}. This is Child 2`);
}
}
const o1 = new Child1('Foo')
const o2 = new Child2('Foo')
o1.greet();
o2.greet();
But if you are trying to override a function in an external module(You do not have access to that code, like a library), my suggestion is to create a wrapper and add functionality there.

In react, I define a function but it's appear undefine

I defined a function, but it's undefined in the reference precess. I tried to add breakpoints, the results show that it is a function, but continues to perform an error again, you mean form the callback?
That's the part I quoted, prompt requestServer is not the function, but it has been defined in the code below, the reason why I don't know whether the callback
import React, {Component, PropTypes} from 'react';
import GLogin from './Login';
var serverMethon = require('../../server/requestServer');
export default class LoginContainer extends Component {
constructor(props) {
super(props);
this.state = {
}
}
getLogin = (value) => {
const {selectView} = this.props;
const requestServer = serverMethon.requestServer;
requestServer('login', value, function(t) {
const data = JSON.parse(t.text);
if (data.state != "successful") {
alert("Login fail!")
return;
}
selectView('SearchContainer');
})();
}
render() {
return (
<GLogin
getLogin={this.getLogin}
{...this.props}/>
)
}
}
Function definitions section
var superagent = require('superagent');
export const requestServer = (position, info, callback) => {
superagent.post(`http://localhost:3000/${position}`)
.send(info)
.end((error, doc)=>{
if(error){
throw error
}
callback(doc)
})
}
// proper way
class App extends React.Component{
constructor (props){
super(props);
this.sampleMethod = this.sampleMethod.bind(this);
}
sampleMethod(){
console.log('Sample method called');
}
render(){
return <button onClick={this.sampleMethod}>Click Me</buttom>
}
}
Your mistake is you called your method as this.getLogin. But you don't bind the method. So you can't use this method with this.getLogin. You can use getLogin method without binding. This time you called the method by getLogin but you get context inside getLogin method the context is not be your component. At the time your context is window so you get window object.
So first bind your method then use it.
// your mistake
getLogin = (value)=>{
// your logic
}
// proper way
getLogin(value){
// your logic
}

Resources