I am currently accepting interface for my component and it accept the array object "options" as one of its arguments. My question is how do you create an interface for array object. I believe you need to use index signature for the interface but I havent used it before and not sure how it need to be strcuture.
Currently I uses arrow function. And here is how I declare my interface of my function
interface IOption {
key: number;
text: string;
value: number
}
Interface DropDownInputInt {
name: string;
value: any;
label: string;
disabled: boolean;
onChange: Function;
extraClassName: string;
required: boolean;
options: IOption[] | string;
multiple: boolean;
clearable: boolean;
index?: number;
placeholder: string;
toolTipMessage: string;
addCollon: boolean;
}
const DropDownInput = ({
name,
value,
label,
disabled,
onChange,
extraClassName,
required,
options,
multiple,
clearable,
index,
placeholder,
toolTipMessage,
addCollon
}: DropDownInputInt) => {
//MY CODES HERE
})
My second question is how do you create in interface that accept both string and object.
Do you mean something like this?
interface IOption {
key: number;
text: string;
value: number; // or any
}
function func(options: IOption[]) {}
but if you are accepting other arguments how do you declare it on interface? I hope there is a way without declaring everything inline
I am not quite familiar with React.js, but normally you should be able to do it with TypeScript anyway
interface DropDownInputInt {
name: string;
value: any;
label: string;
// bla bla ...
}
const DropDownInput = (dropdownOptions: DropDownInputInt) => {
// your code here
}
As for your other question
My second question is how do you create in interface that accept both string and object.
Here is an example (see the pipe |):
interface DropDownInputInt {
somethindThatAcceptsBothStringAndObject: string | object
// bla bla ...
}
I tried to incorporate your answer in my code, see my change above but somehow it throws error inside the body of my function saying "Property 'value' does not exist on type 'string | IOption'. when I tried drilling down the options.value
Ok, so as per your DropDownInputInt interface, you accept string and IOption[] types for the options property. Since you have two different types, you should check the options type like so:
const DropDownInput = (dropdownOptions: DropDownInputInt) => {
// ...
if(typeof dropdownOptions.options === 'string') {
// you have a string here
} else if(Array.isArray(dropdownOptions.options) && dropdownOptions.options.length > 0) {
// you have IOption array here, which is not empty
dropdownOptions.options[0].value; // accessing the value of the first option
dropdownOptions.options[1].value; // accessing the value of the second option
// etc. or simply use a for-loop to evaluate the options
}
}
Related
I'm using react with typescript and I have this:
interface SomeProps {
stuff: {
optionalStuff?: object[];
}[];
}
The only problem with this approach is that if I try to insert any property besides optionalStuff inside the stuff object[], it will fail because those properties are not listed.
How can I do something like:
interface SomeProps {
stuff: {
ACCEPTS_ANY_GENERIC_STUFF_INSIDE: string | number | object | undefined
optionalStuff?: object[];
}[];
}
You could make SomeProps generic, so you could specify what other optional fields would stuff accept. In the example below, stuff except optionalStuff takes also foo field.
type SomeProps<T> = {
stuff: ({
optionalStuff?: object[];
} & T)[];
}
const arr: SomeProps<{ foo: string }> = {
stuff: [{
optionalStuff: [],
foo: 'abc'
}]
}
Typescript playground
The previous answer is correct, but in case you don't know in advance or the type of objects in stuff only share the optionalStuff array but not the other keys, you could use an index signature.
interface SomeProps {
stuff: {
optionalStuff?: object[];
[key: string]: string | number | object | undefined;
}[];
}
Then you get type checking for optionalStuff but the objects can be extended with any other property.
I have 2 types:
type Core = {
a: boolean;
b: number;
c: string
}
type CoreOptions = {
a?: boolean;
b?: number;
c?: string;
}
In this example, CoreOptions is meant to have some, none, or all of the properties that Core has depending on the use case, but never to have a property that isn't in Core.
I am trying to systematically tie them together so that as things evolve and Core gets new properties, I only have to change the type Core.
How can this be achieved?
To use a type, but make all properties optional, TypeScript has a Utility type called Partial. Official Documentation
Example from the TypeScript Documentation:
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "organize desk",
description: "clear clutter",
};
const todo2 = updateTodo(todo1, {
description: "throw out trash",
});
This might a be relatively noob question,
I have an interface
interface Employee {
name: string
}
and I would like to have an extended version of this after it being saved into the DB:
interface EmployeeDb {
id: string,
name: string
}
I would like to differentiate it when handling checks so after saving data in my storage, the type checker won't complain about not having id value. Meaning I want to avoid using this:
interface Employee {
id?: string,
name: string
}
so I don't have to check for id everywhere.
So I am trying to do it this way:
type Employee = {
name: string
}
type IDatabaseObject<T> = {
id: IDatabaseObjectId;
[P in keyof T]: T[P];
};
type EmployeeDb = IDatabaseObject<Employee>
which the IDE gives an error with the top syntax
A computed property name must be of type 'string', 'number', 'symbol',
or 'any'.ts(2464)
so I tried to use interface and extend it
interface IDatabaseObject {
id: string
}
interface EmployeeDb extends Employee, IDatabaseObject {}
but in the backend code when I try to use this setup I get an error from vscode eslint again. I have a small code here that adds the data to localstorage, generates a id and returns the data. see code:
class DbAsyncStorageTemplate<
InputDataType,
OutputDataType extends IDatabaseObject
> {
async addEntry(object: InputDataType): Promise<OutputDataType> {
const id: string = generateUuid()
const dbObject = { id, ...object }
dbObject.id = id
// add the item to AsyncStorage directly
await AsyncStorage.setItem(id, JSON.stringify(object))
// ERROR HERE: return the new object
return dbObject as OutputDataType
}
}
}
but I get an error from the IDE (eslint) for the last line
Conversion of type '{ id: string; } & InputDataType' to type
'OutputDataType' may be a mistake because neither type sufficiently
overlaps with the other. If this was intentional, convert the
expression to 'unknown' first. '{ id: string; } & InputDataType' is
assignable to the constraint of type 'OutputDataType', but
'OutputDataType' could be instantiated with a different subtype of
constraint 'any'.
any recommendation on how to do this properly?
I believe you're looking for intersections of types.
type Employee = {
name: string
}
type EmployeeDb = {
id: string;
} & Employee;
You could also define the raw DB interface and use Pick or Omit utilities as needed.
Pick Utility
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
I think you are looking for this: https://www.typescriptlang.org/docs/handbook/advanced-types.html#:~:text=an%20intersection%20type%3A-,//%20Use%20this%3A,%7D,-Try
You are trying to create a new type (IDatabaseObject) based on an old type (Employee, for instance; or T, in the generic case). This is a Mapped Type.
In your code,
[P in keyof T]: T[P]
returns your old type rather than members of that old type. So you need to close the curly brackets and intersect it with any other new members you want to add.
i.e. do the following for IDatabseObject
type IDatabaseObject<T> = {
id: number;
} & {
[P in keyof T]: T[P];
};
I remember there is a way to create, inside an interface, a field without specifying its name, something like this:
export interface myInterface{
[propName:string]:string;
}
If I remember well, that sinthax means I can create a field with whatever name I want, something like this:
ob:myInterface = {customName: "value"}
What I'm trying to do now is to add a new field to that interface, with a specific name, something like this:
export interface myInterface{
[propName:string]:string;
secondProperties: boolean;
}
When I try the code above I get this error:
Property 'secondProperties' of type 'boolean' is not assignable to string index type 'string'
What is my error?
You need to define all possible types for [propName: string]
So you need to do it in this way
export interface myInterface{
secondProperties: boolean
[propName:string]: string | boolean;
}
I never found a good solution but the problem arises from this.
You are trying to force a property to be of type boolean & string, which equals to never.
type myInterface = {
[propName:string]: string;
} & {
secondProperties: boolean;
};
const obj: myInterface = {
secondProperties: true,
};
playground
Thanks to #LaytonGB for hinting, that | can be used to make almost the type we wanted.
type myInterface = {
[propName:string]: string;
} | {
secondProperties: boolean;
};
const obj: myInterface = {
secondProperties: 'string' || true,
otherProps: 'string only', // booleans will result in error.
};
Because you have defined any string-named-property of your objects to have a value of string, you can only give secondProperties a string value, because secondProperties itself is a string.
Alternatively consider this:
interface myInterface {
[propName: string]: string | boolean;
["secondProperties"]: boolean;
}
Because secondProperties is a string, and its value returns a boolean, it is not throwing errors.
I have next situation, here are several interfaces and types:
interface Book {
id: number;
title: string;
author: string;
}
interface Magazine {
id: number;
title: string;
pageCount: string;
}
type Publication = Book | Magazine;
type BookFields = keyof Book;
type MagazineFields = keyof Magazine;
type PublicationFields = BookFields | MagazineFields;
interface Filter {
field: PublicationFields;
}
I use different react components per each entity, and want to have common sort function, for example:
function publicationSort (publications: Array<Publication>, filter: Filter){
return publications.sort((x, y) => {
const firstVal = x[filter.field];
const secondVal = y[filter.field];
//Some lexical or number sort
})
}
The problem is: I expect that when calls x[filter.field] then gets a value of given publication, for example I pass a book in the sort function and expect it would work like const firstVal = book[author], instead of it I get the Error: Element implicitly has an 'any' type because expression of type 'PublicationFields' can't be used to index type 'Publication'. Property 'author' does not exist on type 'Publication'
What's wrong?
The problem is the author field doesn't exist on the Magazine interface, so x[filter.field] could be an invalid field if x is a Magazine and field is "author". One solution is to ensure both interfaces include the same fields:
interface Book {
id: number;
title: string;
author: string;
pageCount?: undefined;
}
interface Magazine {
id: number;
title: string;
pageCount: string;
author?: undefined;
}