Is it possible to use destructuring in an interface where a property depends on other one?
My arguments are the following:
interface PropsVariationA {
functionToExecute?: () => void;
executeExtraFunction: true;
}
interface PropsVariationB {
executeExtraFunction: false;
}
type Props = PropsVariationA | PropsVariationB;
So when I try to use it with destructuring I (obviously) get an error:
function SomeFunctionWithDestructuring({
executeExtraFunction,
functionToExecute = () => {} // Error: property 'functionToExecute' doesnt exist on type 'Props'
}: Props) {
if (executeExtraFunction) functionToExecute();
return null;
}
Check out this demo.
I managed to make it work without destructuring but I wonder if there is any way.
If you set functionToExecute to undefined in PropsVariationB then it would work:
interface PropsVariationA {
functionToExecute?: () => void;
executeExtraFunction: true;
}
interface PropsVariationB {
functionToExecute: undefined;
executeExtraFunction: false;
}
type Props = PropsVariationA | PropsVariationB;
// function without destructuring
function SomeFunction(props: Props) {
if (props.executeExtraFunction && props.functionToExecute)
props.functionToExecute();
return "a";
}
// function with destructuring
function SomeFunctionWithDestructuring({
executeExtraFunction,
functionToExecute = () => {} // Any way to do destructuing here?
}: Props) {
if (executeExtraFunction) functionToExecute();
return null;
}
// This error is right
SomeFunction({ executeExtraFunction: false, functionToExecute: () => {} });
Related
I extended HTMLElement prototype for adding multiple eventListener.
My Approach
declare global {
HTMLElement {
addEventListeners(): any // with a 's'
}
}
type CallBackFunction<T = void> = () => T
HTMLElement.prototype.addEventListeners() = function(events: Array<HTMLElementEventMap>, callback: CallBackFunction) {
events.forEach((event) => {
this.addEventListener(event, callback)
})
}
but the above code giving me :
Type '(events: Array<keyof HTMLElementEventMap>, callback: CallBackFunction) => void' is not assignable to type '() => any'
Object is possibly 'undefined'
For first I tried
type CallBackFunction<T = any> = () => T
also
declare global {
HTMLElement {
addEventListeners(): void // with a 's'
}
}
and too solve second error I even put a check but still no luck!
if (typeof this !== 'undefined') {
this.addEventListener(event, callback)
}
I think this is what you are looking
declare global {
interface HTMLElement {
addEventListeners<K extends keyof HTMLElementEventMap>(type: K[], listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
}
}
HTMLElement.prototype.addEventListeners = function (events, listener) {
events.forEach((event) => {
this.addEventListener(event, listener);
});
};
Usage
document.querySelector('button').addEventListeners(['click', 'focus'], () => {
//do something
});
they expect that the function return nothing (void). so, try this
I have the following code, which is very repetitious:
const flags = {
get logged() {
return localStorage.getItem("logged") === "true";
},
set logged(val: boolean) {
if (val) {
localStorage.setItem("logged", "true");
} else {
localStorage.removeItem("logged");
}
},
get notificationsMuted() {
return localStorage.getItem("notifications-muted") === "true";
},
set notificationsMuted(val: boolean) {
if (val) {
localStorage.setItem("notifications-muted", "true");
} else {
localStorage.removeItem("notifications-muted");
}
}
}
As you can see, the get and set for each flag type is identical, save for the property names. I would like to do something like this instead:
function getter(prop: string) {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged: getter("logged")
set logged: setter("logged")
get notificationsMuted: getter("notifications-muted")
set notificationsMuted: setter("notifications-muted")
}
But I'm not sure if Javascript / Typescript has support for this sort of thing. Is such a thing possible, and if so, how? If not, is there any other way I can cut down on the repetition here?
You can use a proxy with get and set traps, use TS types to allow only props you wish to handle (TS playground)):
interface Flags {
logged: boolean,
'notifications-muted': boolean;
}
type Prop = keyof Flags;
const handlers = {
get(_: Flags, prop: Prop) {
return localStorage.getItem(prop) === "true";
},
set(_: Flags, prop: Prop, val: any) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
return true;
}
};
const flags = new Proxy<Flags>({} as Flags, handlers);
All you really need is to use Object.defineProperty with an object with a get and set properties. Or, with multiple properties, use Object.defineProperties to define them all at once.
One approach which will help with code organization is to not use lots of local storage keys, but instead use a single object that gets stored.
const props = ['logged', 'notificationsMuted'] as const;
const defaultStorage = Object.fromEntries(props.map(prop => [prop, false]));
const getStorage = () => JSON.parse(localStorage.getItem('settings') || JSON.stringify(defaultStorage));
const flags = Object.defineProperties(
{},
Object.fromEntries(
props.map(
prop => [
prop,
{
get: () => getStorage()[prop],
set: (newVal: boolean) => {
const store = getStorage();
store.prop = newVal;
localStorage.setItem('settings', JSON.stringify(store));
}
}
]
)
)
) as Record<(typeof props)[number], boolean>;
This is the current "best" solution I can come up with. Open to anyone who can provide an improvement over this:
function getter(prop: string): boolean {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean): void {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged() { return getter("logged") },
set logged(val: boolean) { setter("logged", val) },
get notificationsMuted() { return getter("notifications-muted"); },
set notificationsMuted(val: boolean) { setter("notifications-muted", val); }
}
Anyone know why this doesn't work and how to fix? I am trying to say that the onChange function takes a key from the generic State as it's argument. But it's not working the way I thought it would.
function test<State>() {
const onChange = (key: $Keys<State>) => {}
return onChange;
}
const INITIAL = { foo: 'bar' };
function hey() {
const change = test<typeof INITIAL>();
// Error here
// Cannot call `change` with `'foo'` bound to `key` because property `foo` is missing in `State`
change('foo');
}
https://flow.org/try/#0GYVwdgxgLglg9mABFApgZygHgMpQIaoB8AFAJSIDeAUAJAQIaIIDCAFnmAOYqIC8ixANYoAngC5EAEgDSotDnxFyvQpQC+VRFsQAnFFBA6kLdlxQBuKhqr0wjAJIA5ewBV7AQQAyfSomBw4CQByACM8HSDENUsqUEhYBERWUTJKTToGKEQIU24fVAxMKBEABxQ4YEQnVw9PElJLLTpclGIg-zgghqsgA
Typing the return type of the function fixes it.
function test<State: {}>(): (key: $Keys<State>) => void {
const onChange = (key) => {}
return onChange;
}
const INITIAL = { foo: 'bar' };
function hey() {
const change = test<typeof INITIAL>();
change('foo');
}
https://flow.org/try/#0GYVwdgxgLglg9mABFApgZygHgMpQIaoBciA3gL4B8AFAJTFUDWKAnsQCQDSLaO+qFNRAF4KiAG5wYAE1IAoAJAQEGRAgDCACzxgA5imGJGLQSNJlZiS4gBOKKCGtJ1W3SgDcs80rAqAkgDlfABVfAEEAGQMSRGA4OGIAcgAjPGsExDIPUEhYBEQNFlo5REVlKEQIFz0DVAxMKGYABxQ4YEQA4LDw6hoPS0UqlCoE2LgE3s8gA
In C# there is the posibility to get the name of an object property as a string value.
nameof(object.myProperty) --> "myProprty"
Can this be done in Javascript/Typescript?
Object.Keys() is the only thing i found, but it gives me all the keys.
Example what I want to achieve:
export interface myObject {
myProperty: string;
}
console.log(myObject["myProperty"]);
Let's say I change my interface for some reason to:
export interface myObject {
myPropertyOther: string;
}
console.log(myObject["myProperty"]); // This will not give an error on build
So I want to have something like this:
console.log(myObject[nameOf(myObject.myProperty)]
// This will give an error on build when the interface is changed
There is no nameof operator in Javascript/Typescript. You can creat a function that takes the key of another object and this is checked by the typescript compiler:
function keyOf<T>(o: T, k: keyof T) {
return k
}
let o = { a: 1 }
keyOf(o, 'a'); //ok
keyOf(o, 'b'); //err
I made a library that fetches the name of a property at runtime, even for types that don't exist at runtime (interfaces or types in TypeScript):
It can be found on NPM here: #fluffy-spoon/name-of
The source code is simple (just a few lines of code): https://github.com/ffMathy/FluffySpoon.JavaScript.NameOf
Usage
import { getPropertyName, getDeepPropertyName } from '#fluffy-spoon/name-of';
interface SomeType {
foo: boolean;
someNestedObject: {
bar: string;
}
}
console.log(getPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "bar"
console.log(getDeepPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "someNestedObject.bar"
Library source code
In case you don't want to install the NPM package.
function getPropertyNameInternal<T = unknown>(expression: (instance: T) => any, options: {
isDeep: boolean
}) {
let propertyThatWasAccessed = "";
var proxy: any = new Proxy({} as any, {
get: function(_: any, prop: any) {
if(options.isDeep) {
if(propertyThatWasAccessed)
propertyThatWasAccessed += ".";
propertyThatWasAccessed += prop;
} else {
propertyThatWasAccessed = prop;
}
return proxy;
}
});
expression(proxy);
return propertyThatWasAccessed;
}
export function getPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: false
});
}
export function getDeepPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: true
});
}
Yes, this can be done in Javascript/Typescript. There is a typescript module.
Shamelessly copied from the manual.
nameof(console);
nameof(console.log);
nameof(console["warn"]);
Transforms to:
"console";
"log";
"warn";
There are more nice examples in the manual.
The solution for yours question:
interface IMyObject {
myPropertyOther: string;
}
let myObject: IMyObject = {
myProperty: 'Hello world'
};
console.log(myObject[nameof<IMyObject>((o) => o.myProperty)]);
Is there a way in typescript to set a property name from a variable?
Something like this
export function objectFactory(prop: string) {
return {
prop: {
valid: false
}
};
}
You are looking for computed properties, this is an ES6 feature and not specific to TypeScript.
export function objectFactory(prop: string) {
return {
[prop]: {
valid: false
}
};
}
You can do it like this:
export function objectFactory(prop: string) {
let data: any = {};
data[prop] = {};
data[prop].valid = false;
return data;
}