how to process constants in typescript? - javascript

I have a react component like so:
const myComponent = ({constant}: Iprops) => (
<div>
{CONSTANTS[constant].property ? <showThis /> : null
</div>
)
it's complaining that element implicitly has an 'any' type because type 'object' has no index signature
how do I add CONSTANTS to my interface? I've tried
interface IProps {
[CONSTANTS: any]: {
constant: boolean;
}
}
but obviously it doesn't like that. how can I declare the type of each key in my object?
thanks

I'm not sure I understanf the shape of the objects you actually need. But you can type an object with this syntax:
const CONSTANTS: { [key:string]: boolean } = {}
CONSTANTS["something1"] = false; // ok
CONSTANTS["something2"] = "Hey"; // not ok
It's a bit tricky as the key can actually be either string or number but the value type is properly enforced so you can still have more complex types there instead of a boolean.

Related

How to use either this type or another based on props only? React Typescript Discriminating Unions

I have a componenet:
type Base = {
color: string
}
type Button = {
to: string
} & Base
type Link = {
link: string
linkNewTab: boolean
} & Base
type ComponentProps = Button | Link
export const Button: React.FC<ComponentProps> = (props) => {
return (<>
{props.link ? <Link newTab={props.linkNewTab}>...</Link> : props.to && <Button>...</Button>}
</>)
}
There is a base type with props which both types have in common.
The Component should either has Button or Link type based on the given props. But if it has Link, these props are preferred.
Typescript Error:
Property 'link' does not exist on type 'PropsWithChildren<ComponentProps>'.
Property 'link' does not exist on type '{ to: string; } & Base & { children?: ReactNode; }'.ts(2339)
What I don't want:
I can solve the problem by adding a type to the Base type and decide from their which props are allowed. I would like to automatically decide that based on props.
Information: 4.5.4 TypeScript Version
The problem is that you are attempting to access a value at property which might not exist. Instead, you can check to see if that property exists in the object (by using the in operator) before trying to access its value. This will also discriminate the union:
TS Playground
// instead of this:
if (props.link) {}
// ^^^^
// Property 'link' does not exist on type 'Button | Link'.
// Property 'link' does not exist on type 'Button'.(2339)
// prop in obj
if ('link' in props) {
props.color // ok
props.link // ok
props.linkNewTab // ok
}
else {
props.color // ok
props.to // ok
}
You can check whether the link property exists by using the in operator. This will narrow down the type without accessing the property, and so typescript will allow it:
<>
{"link" in props ? (
<Link newTab={props.linkNewTab}>...</Link>
) : (
"to" in props && <Button>...</Button>
)}
</>

Typescript: mark an object property as non-enumerable?

I'm using nominal typing/branding to represent values that passed through React's useMemo.
type Memoed<T> = T extends Primitive ? T
: T extends { __MEMOED: true } ? T
: T & { __MEMOED: true };
declare function useMemo<T extends any>(
callback: () => T,
deps: ReadonlyArray<Primitive | Memoed<object>>,
): Memoed<T>;
This works fine most of the time. However, this causes issues when I'm using Object.keys, Object.values, or when I'm spreading objects. E.g. if I spread a memoized object to create a new object, the new object isn't memoized. However, it would still have the __IS_USE_MEMO property, so Typescript would allow it as an argument to useMemo. E.g.:
const objA = useMemo(() => ({ a: 1 }), []);
const objB = { ...objA };
const objC = useMemo(() => objB, [objB]); // Should be error
TS Playground
If I can mark __MEMOED has non-enumerable, then this problem should go away. Is that possible?
It seems the only way to do this would be to define a class with your "memoed" mark as a method (doesn't technically need to be a method, but extending a prototype with anything else is more complicated).
This seems to actually work (instead of your definition for __MEMOED):
declare class __MEMOED {
memoed(): true
}
type Memoed<T> = T extends Primitive ? T
: T extends __MEMOED ? T
: T & __MEMOED
Although… if you memo anything that's not a basic Object, things get really weird and at that point you really need to extend the base class.

Checking one conditional React prop does not satisfy TS

I'm having trouble understanding Typescript. I want to define a <Component/> with one required prop requiredProp, and a condition prop extend which if true, allows to use extendedProp.
e.g.
<Component requiredProp={''} /> // OK
<Component requiredProp={''} extend={true} requiredProp={Function} /> // OK
<Component requiredProp={''} requiredProp={Function} /> // ERROR
<Component requiredProp={''} extend={true} /> // ERROR
My code:
// types
interface RequiredProps {
requiredProp: 'string';
}
type ExtendedProps =
| {
extend?: false;
extendedProp?: never;
}
| {
extend: true;
extendedProp: Function;
};
type ComponentProps = RequiredProps & ExtendedProps;
// component
const Component: React.FC<ComponentProps> = ({requiredProp, extend, extendedProp}) => {
if (extend) {
extendedProp(); // ERROR: Cannot invoke an object which is possibly 'undefined'.
}
}
If I am checking extend to be true, shouldn't TS automatically know from ExtendedProps that extendedProp is also defined?
Compiler is only satisfied if I am explicitly checking extendedProp to be defined:
if (extendedProp) {
extendedProp(); // no error
}
If you do this using the entire props object, it will work. checking for props.extend will narrow down the type on props, and thus props.extendedProp will be allowed:
const Component: React.FC<ComponentProps> = (props) => {
if (props.extend) {
props.extendedProp();
}
}
But if you've already split them up into separate variables, then this can't happen. props isn't involved in your if statement at all, so the type on props can't be narrowed. There are just two unrelated local variables, and checking the type on one doesn't do anything to the type on the other.
You and i can recognize that the two variables are related to eachother, but typescript doesn't have the ability to back track to find the origin of the type, and deduce what that means for other types. It may seem simple in this case, but keep in mind that there are a huge variety of lines of code that could be written that would break the linkage, such as:
const Component: React.FC<ComponentProps> = ({requiredProp, extend, extendedProp}) => {
extend = Math.random() > 0.5
if (extend) {
extendedProp();
}
}
Figuring out the implications of those kinds of things is impractical (or maybe impossible);
Playground link

How to add default value in function parameters in typescript?

I am trying to add the last piece of type in my function, but typescript complained whatever I do. I know there is a way to solve it, and need your help.
export const getPropertiesArray = <T extends object, K extends keyof T>(data: T[], property: K) => {
return data.map(item => item[property]).sort();
};
This is my function and I need to add a default value to property, in this case, the default value is "ID".
Passing default "ID" is not assignable to constraint of object.
Indeed you should assume that ID is always a key of your object.
Here is a workaround:
// T extends { ID: any } - You may replace any by your ID type
export const getPropertiesArray = <T extends { ID: any }>(data: T[], property: keyof T = "ID") => {
return data.map(item => item[property]).sort();
};
In general the answer to the question as stated is simple - the format for a default parameter is myParam = "default value", so for example
function myFunc(param1 = "default value") {
//...
}
You can read more about default parameters in Typescript here: https://www.typescriptlang.org/docs/handbook/functions.html#optional-and-default-parameters
HOWEVER: the OP's particular scenario involves a keyof parameter, which requires special handling. See Simon Bruneaud's answer for this.

Typescript: How To Use Generics Properly To Infer Return Type of Function Correctly?

I've the following types
type ItemDefaultType = object | null | string
interface ItemToString<Item = ItemDefaultType> {
(item: ItemDefaultType): string;
}
interface AutosuggestState<Item = ItemDefaultType> {
highlightedIndex: number | null
inputValue: string | null
isOpen: boolean
selectedItem: Item
}
interface AutosuggestProps<Item = ItemDefaultType> extends AutosuggestState<Item> {
itemToString?: ItemToString<Item>;
initialSelectedItem?: Item;
initialInputValue?: string;
initialHighlightedIndex?: number | null;
initialIsOpen?: boolean;
defaultHighlightedIndex?: number | null;
defaultIsOpen?: boolean;
id?: string;
inputId?: string;
labelId?: string;
menuId?: string;
itemCount?: number
}
I want to make a getInitialValue function with the intended signature –
// this has to be done correctly using generics, not been able to do it, read on…
(
props: Props extends AutosuggestProps,
stateKey: StateKey extends keyof AutosuggestState
) => AutosuggestState[StateKey] // return the correct type of AutosuggestState property based on which stateKey was passed
The behaviour of getInitialValue looks like this
// javascript version
const defaultStateValues = {
highlightedIndex: -1,
isOpen: false,
selectedItem: null,
inputValue: ''
}
function getDefaultValue(props, statePropKey) {
const defaultPropKey = `default${capitalizeString(statePropKey)}`
if (defaultPropKey in props) {
return props[defaultPropKey]
}
return defaultStateValues[statePropKey]
}
function getInitialValue(props, statePropKey) {
if (statePropKey in props) {
return props[statePropKey]
}
const initialPropKey = `initial${capitalizeString(statePropKey)}`
if (initialPropKey in props) {
return props[initialPropKey]
}
return getDefaultValue(props, statePropKey)
}
I'm finding it hard to write types of both getInitialValue and getDefaultValue such that getInitialValue correctly infers the right type as below –
const selectedItem = getInitialValue(props, 'selectedItem') // selectedItem variable should correctly be inferred as **object | null | string** since that's what its type is in **AutosuggestState** interface
Can someone help me write the types?
Here's a possible typing, which might or might not fit your use case:
function getDefaultValue<P extends AutosuggestProps, K extends keyof AutosuggestState>(
props: P,
statePropKey: K) {
const defaultPropKey = `default${capitalizeString(statePropKey)}`
if (defaultPropKey in props) {
return props[defaultPropKey as K] // assert here
}
return defaultStateValues[statePropKey]
}
function getInitialValue<P extends AutosuggestProps, K extends keyof AutosuggestState>(
props: P, statePropKey: K) {
if (statePropKey in props) {
return props[statePropKey]
}
const initialPropKey = `initial${capitalizeString(statePropKey)}`
if (initialPropKey in props) {
return props[initialPropKey as K] // assert here
}
return getDefaultValue(props, statePropKey)
}
All I've really done is make the functions generic in P and K of the types you alluded to in your pseudocode.
One decision you need to make is what to do when you use defaultPropKey and initialPropKey. The compiler has no way to verify that prepending "default" or "initial" to a property name will, if it exists, pick out another property of the same type. The compiler does not understand string concatenation at the type level (the suggestion for doing so in Github seems to be microsoft/TypeScript#12754). If you could refactor your interfaces so that the default and initial values are stored in properties named default and initial which are themselves objects holding properties of the same keys, then you could make the compiler figure it out. Assuming that you're keeping it this way, I've used a type assertion to say, for example, "treat initialPropKey as if it were of type K". Then the compiler will assume that props[initialPropKey] is of the same type as props[statePropKey].
Anyway, now the return types of those functions are inferred as
P[K] | {
highlightedIndex: number;
isOpen: boolean;
selectedItem: null;
inputValue: string;
}[K]
which makes sense since it uses defaultStateValues at the end, which is a concrete type not known to be the extended P type. This might be tightened up by using a conditional return type, but your example code didn't show a need for that.
Let's see if it works:
declare const props: AutosuggestProps;
const selectedItem = getInitialValue(props, 'selectedItem');
// const selectedItem: ItemDefaultType
Looks reasonable to me. Okay, hope that helps; good luck!
Link to code

Categories

Resources