Creating string literal union from a property in an array of objects - javascript

Is it possible to get a literal string union type of a static array of object's specific property?
For example, I want to expose IconName as a type of literal strings from which is inferred from an array of objects.
I've seen it has been done to convert a read-only array to a string literal union, but not through an array of objects.
I don't think it's possible since we require some runtime interpretative functionality to access the array's values. But here's the example I'm trying to work through this svelte example https://codesandbox.io/s/svelte-typescript-forked-ef75y:
<script lang="ts">
interface Icon {
name: string
color: string
}
const icons: readonly Icon[] = <const>[
{
name: "close",
color: "red"
},
{
name: "open",
color: "blue"
}
];
const tuple = <K extends string[]>(...arr: K) => arr;
const iconNames = tuple(...icons.map(i => i.name));
type IconName = typeof iconNames[number];
export let name: IconName;
let displayIcon = icons.find((e) => e.name === name);
</script>
<h1>{displayIcon.color}</h1>

I guess the code below will do what you try to do. However, I suggest writing your type definitions in a separate file with .ts extension.
<script lang="ts">
interface Icon {
name: string;
color: string;
}
const icons = {
close: {
name: "close",
color: "red",
},
open: {
name: "open",
color: "blue",
},
} as const;
type IconName = keyof typeof icons;
export let name: IconName;
let displayIcon = icons[name];
</script>
<h1>{displayIcon.color}</h1>

Related

typescript use preset value for property value in array object error?

I have this simple typescript code for react
type fruitCode = 'apple' | 'banana'
interface fruitList {
name: fruitCode
}
const [arr, setArr] = useState<fruitList[] | []>([])
useEffect(() => {
const arrList = [{
name: 'apple'
}, {
name: 'banana'
}];
//error?
setArr(arrList)
}, [])
demo
https://codesandbox.io/s/react-typescript-forked-gi6fw?file=/src/App.tsx:103-391
how can I restrict my property value is either 'apple' and 'banana'?
Typescript cannot infer the type for arrList so set it directly
const arrList: fruitList[] = [
// ...
]
I would also remove | [] from the state type definition as it is redundant; an empty array still satisfies the <fruitList[]> requirement.
const [arr, setArr] = useState<fruitList[]>([])
Finally, your interfaces and types should not be part of the component. Move them above App
type fruitCode = "apple" | "banana";
interface fruitList {
name: fruitCode;
}
export default function App() {
// ...
}
You'll notice there are no warnings about your useEffect dependencies after this.

typescript: How to define type as any part of enum?

I'm trying to create a translation module using typescript.
I want to define the languages as an enum parameter to create-text function, as like:
export enum Language {
He = "he",
En = "en",
}
const { createI18n, createI18nText } = createTextFunctions(Language);
const firstExample = createI18nText({
he: {
firstText: "שלום",
sc: {
hello: "שלום שוב"
}
},
en: {
firstText: "hello",
sc: {
hello: "hello again"
}
}
})
export const i18n = createI18n({
welcome: firstExample,
})
But my problem is that because the languages are are passed as params to a typescript function and the function infers the types, typescript is not alarming anything. I can create text with non-existing language and it will pass it,like createI18nText({ ar:{ hi : "hi" }}).
My text functions are these:
export type Languages = { [key: string]: string };
export const createTextFunctions = (languages: LanguagesD) => {
type I18nText<T extends object> = {
[k in keyof typeof languages]: T;
}
const createI18n = <T extends { [key: string]: I18nText<any>; }>(i18n: T) => {
return i18n;
};
const createI18nText = <T extends object>(text: I18nText<T>) => {
return text;
}
return {
createI18n,
createI18nText
}
}
So the code is running and doing whatever it needs to do, but I'm losing type control.
I do prefer to have my enum values lower-cased, so its an issue too. If this is the solution so I'll take it, but if there is any way to pass an enum-param and to run by its values it would be great.
You can make createTextFunctions use a generic. You will be able to customise the keys as you want when you create the text function :
// *L* must be a union of strings.
const createTextFunctions = <L extends string>() => {
type I18nText<T extends object> = Record<L, T>;
type I18n = Record<string, I18nText<any>>;
const createI18n = <T extends I18n>(i18n: T): T => {
return i18n;
};
const createI18nText = <T extends object>(text: I18nText<T>): I18nText<T> => {
return text;
}
return {
createI18n,
createI18nText
}
}
Then specify the Language as a union of strings:
type Language = "en" | "he";
And create/use the text functions:
const { createI18n, createI18nText } = createTextFunctions<Language>();
const firstExample = createI18nText({
he: {
firstText: "שלום",
sc: {
hello: "שלום שוב"
}
},
// If the key *en* is missing, typescript will complain.
en: {
firstText: "hello",
sc: {
hello: "hello again"
}
},
// If we add the key *us*, typescript will complain.
})
export const i18n = createI18n({
welcome: firstExample,
})
IMO union types are more comfortable to use than enums.

Typescript define type for object will loose static object key (property) suggestion

Here is the code:
interface ButtonProps {
[key: string]: any
}
export const button: ButtonProps = {
primary: {
background: theme.background.primary,
color: theme.color.base,
},
base: {
background: theme.background.base,
color: theme.color.primary,
},
}
// button.pr..
// 👆 When I press primary, the vscode static suggestion won't show `primary` or `base` keys, because [key: string] could be any string.
Not expected:
Screenshot
Expected:
Screenshot - expected
Expected Behavior:
Suggestion or static type check for any properties that types defined also manually added keys.
like:
interface ButtonProps {
primary: ...,
[key: string]: ...
}
const button: ButtonProps = {
secondary: ...
}
button.primary // ✅
button.secondary // ✅
button.third // Okay, but no suggestion.
I actually ran into a similar situation myself recently. The issue is that you want to get the narrow type of the object your defining while ensuring it extends the broader type. To fix this, you can create a function that just returns its input but infers it's type.
interface ButtonProps {
primary: {background:string, color:string};
[key: string]: any;
}
const createButtonProps = <T extends ButtonProps>(props: T) => props;
export const button = createButtonProps({
primary: { // primary is required
background: theme.background.primary,
color: theme.color.base,
},
base: {
background: theme.background.base,
color: theme.color.primary,
},
});
// You'll now see intellisense for button.primary & button.base
As a bonus, if you ever need to get the exact value types in the object as well, which was the case for me, you can add as const to the end of the object you pass to the function.

Typescript with custom react hook

Trying to write a custom react hook in TypeScript that accepts an object with all optional React.CSSProperties as keys like so...
const something = useSomthing({
color: {
initial: 'red',
new: 'blue'
}
})
Can I write it in a way that i'll get all the css properties in the IDE autocomplete?
You can use keyof and read more here
type Config = {
[key in keyof React.CSSProperties]?: {
initial: React.CSSProperties[key];
new: React.CSSProperties[key];
}
};
const useSomething = (config:Config)=>{
...
}

Flow type: extendable React Component prop types

Let say there exist the SelectOption component, which props looks like:
type OptionType = {
+id: string,
+label: string,
}
type Props = {|
+options: OptionType[],
+onItemPress: OptionType => void
|}
Intentionally I wrote the OptionType type as a not exact because I expect that I will need to extend this type,
but I expected that id and label are always required.
But this doesn't work as I expected.
type CustomOptionType = {
+id: string,
+label: string,
+locationId: string,
}
const customOption = [
{
id: 'id',
label: "label",
locationId: 'ID-location'
}
]
class PlacePicker extends React.Component<{}> {
handleItemClick = (option: CustomOptionType) => {
//
}
render() {
const mockedCustomOption = [
{
id: 'id',
label: "label",
locationId: 'ID-location'
}
]
return(
<Select options={mockedCustomOption} onPressItem={this.handleItemClick} />
// Cannot create `Select` element because property `locationId` is missing in `CustomOptionType` [1] but exists in `OptionType` [2] in the first argument of property `onPressItem`.
)
}
}
With this approach I have this error:
Cannot create Select element because property locationId is
missing in CustomOptionType [1] but exists in OptionType [2] in
the first argument of property onPressItem.
How should I wrote props that have some required fields(id, label), but with possibility extend the OptionType?
I changed the OptionType to Interface which solved my issue.
https://flow.org/en/docs/types/interfaces/

Categories