When writing React components, one of the utility functions that I find useful and commonly use is the callAll
function, which I learnt from Kent C. Dodds from one of his classes.
The use of callAll
is to wrap few functions together (some of them optional, thus could be undefined
), and create another function that will call each of them with the parameters.
For example:
function fn1(x) {
console.log('x from fn1: ', x);
}
function fn2(x, y) {
console.log('result from fn2: ', x + y);
}
const missingFn = undefined;
const wrapperFn = callAll(fn1, fn2, missingFn);
wrapperFn(2, 3);
// x from fn1: 2
// result from fn2: 5
It is commonly used in React when you want to wrap some component to have additional callBack, without writing the null check in the already ugly arrow function in jsx:
// instead of this
<input
onChange={(ev) => {
if (props.onChange) {
props.onChange(ev);
}
onChangeValue(ev);
}}
/>;
// we can do this!
<input onChange={callAll(props.onChange, onChangeValue)} />;
Its implementation in javascript is pretty simple:
const callAll =
(...fns) =>
(...args) =>
fns.forEach((fn) => typeof fn === 'function' && fn(...args));
However, when I wanted to use that function in Typescript, I had problem providing the typings for the fns
and args
parameters. I had to resort to use any
, which lose proper typechecking and intellisense for the function parameters.
type type CallBack = (...args: any[]) => void
CallBack = (...args: any[]
args: any[]) => void;
const const callAll: (...fns: Array<CallBack | undefined>) => (...args: any[]) => void
callAll =
(...fns: (CallBack | undefined)[]
fns: interface Array<T>
Array<type CallBack = (...args: any[]) => void
CallBack | undefined>) =>
(...args: any[]
args: any[]) =>
fns: (CallBack | undefined)[]
fns.Array<CallBack | undefined>.forEach(callbackfn: (value: CallBack | undefined, index: number, array: (CallBack | undefined)[]) => void, thisArg?: any): void
Performs the specified action for each element in an array.forEach((fn: CallBack | undefined
fn) => typeof fn: CallBack | undefined
fn === 'function' && fn: (...args: any[]) => void
fn(...args: any[]
args));
Recently I’ve do some googling (or you could say “researching”), and I find out that we could have typesafe callAll
by utilizing rest elements in tuple types that is introduced in Typescript 3.
The implementation:
interface interface CallBack<Params extends any[]>
CallBack<function (type parameter) Params in CallBack<Params extends any[]>
Params extends any[]> {
(...args: Params extends any[]
args: function (type parameter) Params in CallBack<Params extends any[]>
Params): void;
}
const const callAll: <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>) => (...args: Params) => void
callAll =
<function (type parameter) Params in <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>): (...args: Params) => void
Params extends any[]>(...fns: (CallBack<Params> | undefined)[]
fns: interface Array<T>
Array<interface CallBack<Params extends any[]>
CallBack<function (type parameter) Params in <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>): (...args: Params) => void
Params> | undefined>) =>
(...args: Params extends any[]
args: function (type parameter) Params in <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>): (...args: Params) => void
Params) =>
fns: (CallBack<Params> | undefined)[]
fns.Array<CallBack<Params> | undefined>.forEach(callbackfn: (value: CallBack<Params> | undefined, index: number, array: (CallBack<Params> | undefined)[]) => void, thisArg?: any): void
Performs the specified action for each element in an array.forEach((fn: CallBack<Params> | undefined
fn) => typeof fn: CallBack<Params> | undefined
fn === 'function' && fn: CallBack
(...args: Params) => void
fn(...args: Params extends any[]
args));
// Use cases
const const resultCb: (secondInput: number) => void
resultCb = const callAll: <[secondInput: number]>(...fns: (CallBack<[secondInput: number]> | undefined)[]) => (secondInput: number) => void
callAll(
(inputNumber: number
inputNumber: number) => var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(inputNumber: number
inputNumber * 3),
(secondInput: number
secondInput) => var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(secondInput: number
secondInput / 2)
);
const resultCb: (secondInput: number) => void
resultCb(100);
const resultCb: (secondInput: number) => void
resultCb(true);
interface interface CallBack<Params extends any[]>
CallBack<function (type parameter) Params in CallBack<Params extends any[]>
Params extends any[]> {
(...args: Params extends any[]
args: function (type parameter) Params in CallBack<Params extends any[]>
Params): void;
}
const const callAll: <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>) => (...args: Params) => void
callAll =
<function (type parameter) Params in <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>): (...args: Params) => void
Params extends any[]>(...fns: (CallBack<Params> | undefined)[]
fns: interface Array<T>
Array<interface CallBack<Params extends any[]>
CallBack<function (type parameter) Params in <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>): (...args: Params) => void
Params> | undefined>) =>
(...args: Params extends any[]
args: function (type parameter) Params in <Params extends any[]>(...fns: Array<CallBack<Params> | undefined>): (...args: Params) => void
Params) =>
fns: (CallBack<Params> | undefined)[]
fns.Array<CallBack<Params> | undefined>.forEach(callbackfn: (value: CallBack<Params> | undefined, index: number, array: (CallBack<Params> | undefined)[]) => void, thisArg?: any): void
Performs the specified action for each element in an array.forEach((fn: CallBack<Params> | undefined
fn) => typeof fn: CallBack<Params> | undefined
fn === 'function' && fn: CallBack
(...args: Params) => void
fn(...args: Params extends any[]
args));
// Use cases
const const resultCb: (secondInput: number) => void
resultCb = const callAll: <[secondInput: number]>(...fns: (CallBack<[secondInput: number]> | undefined)[]) => (secondInput: number) => void
callAll(
(inputNumber: number
inputNumber: number) => var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(inputNumber: number
inputNumber * 3),
(secondInput: number
secondInput) => var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(secondInput: number
secondInput / 2)
);
const resultCb: (secondInput: number) => void
resultCb(100);
const resultCb: (secondInput: number) => void
resultCb(true);
That’s it!