Typescript named function inside functional component - javascript

I need to type handleFoo with MyType['foo'].
type MyType {
foo: () => void
}
const Comp: React.FunctionComponent<{}> = () => {
function handleFoo() {}
return ...
}
I don't want to use Anonymous function like const handleFoo: MyType['foo'] = () => {}
I also tried <MyType['foo']>function handleFoo() {} like recommended here but it's not working in tsx (Operator '>' cannot be applied to types 'string[]')

Whatever your reason for avoiding the anonymous function, you can still type a function by assigning it to a variable, even if it isn't anonymous:
const handleFoo: MyType["foo"] = function () {}
Edit: as #jonrsharpe pointed out, there are some utility classes you could use here, but the results, uh, leave something to be desired:
function handleFoo(...args: Parameters<MyType["foo"]>): ReturnType<MyType["foo"]> {}

Related

Type function's arguments only via Interface TypeScript

How do I type the function's arguments bellow and keep them clean (without Typescript), using Interface?
// external file
export interface TSomeFunctionArgs {
someKey: string
// also here should be a type for a function
}
// main file
import { TSomeFunctionArgs } from "pathToFile"
const someFunction = (args: TSomeFunctionArgs) => {
// ... function's logic
}
someFunction({ someKey: "some string" }, someAnotherFunction)
In my example I'm passing two arguments, the first one is an object with the particular key - value pair, and a second one is a function (it could be any function).
How do I describe it in the Interface above?
In this case you can use generics for, and the code will be something like this:
interface TSomeFunctionArgs {
someKey: string
}
const someFunction = <U, T>(args: U, callback: T) => {
// ... function's logic
}
function someAnotherFunction() {}
// With generics you can give the type of the two parameters when you use it.
someFunction<TSomeFunctionArgs, () => void>({ someKey: "some string" }, someAnotherFunction)

Using decorators in Node.js

I'm a new developper in Node.js (coming from Python), and I'm quite surprised to see that there are no decorators in Javascript. I would like to use it, however, because it greatly simplifies the code.
After some research, I found an ES6 specification in stage 2 for this (https://github.com/tc39/proposal-decorators), but it is apparently not supported in Node.js. I also found this in TypeScript, but their function is limited (only in classes).
So my question is: Is there a way to have decorators similar to those of Python with Node.js, and if not, what features can be used as a substitute ?
Thank you in advance for your answers !
According to Wikipedia decorators in python are just syntactic sugar for a function call, which takes the original class/function and stores the returned value under the variable containing the class. Thus the equivalent js would be:
const Decorated = decorator(class ToBeDecorated {
/*...*/
});
const decoratedFunction = decorateFunction(function decorated() {
/*...*/
});
// Some sample decorator implementations:
const decorator = Parent => class WithDuck extends Parent {
quack() { }
};
const decorateFunction = fn => (...args) => {
console.log(`${fn.name} called with`, ...args);
return fn(...args);
};
#jonas Wilms 's answer is correct. I wanted to post this here to expand on a specific implementation technique I used. My use case was with Koa, using async/await functions.
First, I created my decorator:
export function Cocoon( { param=[] }, callback )
{
//Return a function from the decorator
return async (ctx) => {
//Do a thing with Param
const params = { usr: param }
return await callback( ctx, params )
}
}
Using the decorator to annotate a function:
export const list = Cocoon({
param: [5]
},
async (ctx, { usr }) => {
//Function body
ctx.response.body = usr
}
)
Now I can export 'list' to Koa and it'll be called as expected.

Using bracket notation depending on input data

I'm in the process of optimizing some code in my library, however, I have a bit of an issue regarding why bracket notation isn't working when trying to call an imported class.
Parameter type accepts a string that is camelCased, such as: myString.
The parameter data can be anything.
import { foo } from './example';
export const find = (type: string, data: any) => {
// This next line effectively deletes the end of the string starting
// from the first capital letter.
const f = type.replace(/[A-Z][a-z]+/, '');
try {
return [f][type](data);
} catch (e) {
return e;
}
};
this is what I expect it to look like if I was to visualize it using dot notation:
foo.fooBar(someRandomData)
This should call the static method fooBar(data) on the imported class foo,
however, I receive an error message:
TypeError: [f][type] is not a function
If I was to revert it back to my if..else if style, it works:
if (type.startsWith('foo')) return foo[type](data);
How can I do what is desired above without getting the defined error message?
Thank you for your help in advance!
EDIT: This is an example I modified from already existing code, therefore, I fixed a few typos.
EDIT #2: as per requested, the imported class foo looks like this:
export class foo{
static fooBar(data){
// Do stuff with data...
}
In the end you need some reference to the classes or object to get started with. Here is a working example of how you could do this type of functionality, but you have start with a map of your class instances so you can get to them:
class foo {
fooBar(data: any) { return { name: 'foo', data } };
}
class example {
exampleFood(data: any) { return { name: 'example', data } };
}
var lookup: { [classes: string]: any; } = { };
lookup['foo'] = new foo();
lookup['example'] = new example();
const find = (encodedType: string, data: any) => {
// This next line effectively deletes the end of the string starting
// from the first capital letter.
const f = encodedType.replace(/[A-Z][a-z]+/, '');
try {
return lookup[f][encodedType](data);
} catch (e) {
return e;
}
};
alert(JSON.stringify(find("fooBar", "Found you foo")));
alert(JSON.stringify(find("exampleFood", "Found you example")));
I would suggest you instead move over to using the nodeJS built-in EventEmitter.
You can do something like:
import * as EventEmitter from 'events';
import { foo } from './example';
import { bar } from './example2';
export const discordEventEmitter = new EventEmitter();
discordEventEmitter.on('fooBar', foo.fooBar);
discordEventEmitter.on('fooToo', foo.fooToo);
discordEventEmitter.on('barBell', bar.barBell);
Then, when you want to fire an event, you can simply:
discordEventEmitter.emit('fooBar', someData);
You can also simplify the event handler registration by writing:
const fooProps = Object.getOwnPropertyNames(foo) as (keyof typeof foo)[];
fooProps.filter(k => typeof foo[k] === 'function').forEach(funcName => {
discordEventEmitter.on(funcName, foo[funcName]);
});
const barProps = Object.getOwnPropertyNames(bar) as (keyof typeof bar)[];
fooProps.filter(k => typeof bar[k] === 'function').forEach(funcName => {
discordEventEmitter.on(funcName, bar[funcName]);
});

Flowtype: How to properly use $ObjMap for making all functions chainable?

I'm working on some JS package and I want to provide separated flow type definitions as index.js.flow because internal behavior is slightly different.
I have this function for create component definition
function defineComponent(name, createFunc);
createFunc is function which grabs elements and returns object containing particular user defined actions upon that component
so you can call defineComponent in this manner:
const loginForm = defineComponent("login form", () => {
...
...
return {
fillUsername: () => { ...doesn't matter what is return type... },
fillPassword: () => { ...doesn't matter what is return type... }
};
});
and those actions should be chainable, but I don't want to burden user with always mentioning return type within each user defined action. So final chain should look like this:
loginForm
.fillUsername()
.fillPassword()
So internally defineComponent will wrap each user defined action like this for chaining ability:
function defineComponent(..., createFunc) {
const actions = createFunc(...);
return actions.map(action => {
return (...args) => {
action(...args);
return actions;
}
})
}
I already tried this (my whole testing code):
type ComponentType<T> = $ObjMap<T, <V>((V) => any) => V => ComponentType<T>>;
declare function defineComponent<T>(
name: string,
createFunc: () => T
): ComponentType<T>;
const Component = defineComponent("foo", () => {
return {
fooAction: () => {},
barAction: () => {}
};
});
Component.fooAction().barAction()
I proceeds with flow's No errors! but flow is showing no errors also when I do something like
Component.fooAction.barAction()
And also VS Code provides no autocompletion above that :/
Thanks!
This is what I was able to come up with. The main idea is that we map the original value type V => mixed (i.e., a unary function) to V => T (i.e., same single argument but now it returns the action map T).
function chainable<T: { [actionName: string]: Function }>(
actions: T
): $ObjMap<T, <V>(V => mixed) => (V => T)> {
return Object.keys(actions).reduce((newActions, actionName) => {
newActions[actionName] = function(...args) {
actions[actionName](...args);
return this;
}
return newActions;
}, {});
}
Try Flow
NOTE: I don't think this is completely typesafe because this isn't really of type T. But, I don't think that Flow has the complexity to annotate the type of this in the newActions values. Additionally, this only works if all actions have unary functions for values. Unfortunately, Flow does not have the vocabulary to describe variadic generics (see https://github.com/microsoft/TypeScript/issues/5453 for information on what this means and how it could be used).

Jest spyOn a function not Class or Object type

I am familiar with setting spies on Class or Object methods, but what about when the function is just an export default - such that the method itself is independent, like a utility?
I have some existing code like so:
const Funct1 = props => {
if(props){
Funct2(args);
}
// or return something
};
const Funct2 = props => {
// do something
return true
};
export default Funct1; //Yes the existing export is named the same as the "entry" method above.
And, for example, I'd like to spy on Funct1 getting called and Funct2 returns true.
import Funct1 from "../../../src/components/Funct1";
describe("Test the Thing", () => {
it("New Test", () => {
let props = {
active: true,
agentStatus: "online"
};
const spy = spyOn(Funct2, "method name"); <-- how doe this work if not an obj or class?
Funct1(props);
//If I try Funct2(props) instead, terminal output is "Funct2 is not defined"
expect(spy).toHaveBeenCalledWith(props);
});
});
I am not expert in jest, but my recommendation to think about:
1) When the function is exported as default I use something like:
import Funct1 from "../../../src/components/Funct1";
...
jest.mock("../../../src/components/Funct1");
...
expect(Funct1).toHaveBeenCalledWith(params);
2) When the module (utils.js) has multiple exports as
export const f1 = () => {};
...
export const f8 = () => {};
You can try
import * as Utils from "../../../src/components/utils"
const f8Spy = jest.spyOn(Utils, 'f8');
...
expect(f8Spy).toHaveBeenCalledWith(params);
Similar discussion here
Wrap your function with jest.fn. Like this:
const simpleFn = (arg) => arg;
const simpleFnSpy = jest.fn(simpleFn);
simpleFnSpy(1);
expect(simpleFnSpy).toBeCalledWith(1); // Passes test
I think Jest requires mocks to be inside an Object or Class, but if they aren't like that in your code, you can still put them there in the test:
jest.mock('../pathToUtils', () => ({
func2: jest.fun().mockImplementation((props) => "ok") //return what you want
otherfuncs: ...etc
}));
const mockUtils = require('../pathToUtils')
const func1 = require('./pathToFunc1')
Then in the test:
func1() // call the main function
expect(mockUtils.func2).toHaveBeenCalled
expect(mockUtils.func2).toHaveBeenCalledTimes(1)
expect(mockUtils.func2).toHaveBeenCalledWith(arg1, arg2, etc)
expect(mockUtils.func2).toHaveBeenLastCalledWith(arg1, arg2, etc)
You'll have already done unit testing of the Util function in another file, so you won't actually be running the function again here, this is just mocking for the purpose of the integration testing.
I believe that is not possible to test Funct1 calling Funct2 without modifying the existing code. If the latter is an option, here are two options to introduce dependency injection:
Create and export a factory function:
const Funct2 = props => {
// do something
return true;
};
const Funct1 = CreateFunct1(Funct2);
export function CreateFunct1(Funct2) {
return props => {
if (props) {
Funct2(props);
}
// or return something
};
}
export default Funct1;
// and here is the test:
describe('Test the Thing', () => {
it('New Test', () => {
// Arrange
const funct2Spy = jasmine.createSpy('Funct2');
const funct1 = CreateFunct1(funct2Spy);
const props = "some data";
// Act
funct1(props);
// Assert
expect(funct2Spy).toHaveBeenCalledWith(props);
});
});
Export Funct2 too. Here is a thread on this topic. Maybe its example would have to be tweaked a little bit for your scenario because of the export syntax.

Categories