How to access typescript type keys? - reactjs

There was a following task and I cannot understand in any way how to make it.
I have TypeScript type. It is an object with enumerated keys. I want to make a universal function for filtering an array, which consists of objects of this type.
In the function below, oldArray is the array of objects to filter, and keyOldArray is one of the keys of type TObject . I know that I need to use this function in two different places and that the two keys that will be used are name and description for example. How do I specify this for TypeScript? Thank you all in advance!
type TObject = {
id: string;
description: string;
name: string;
tob: string;
};
const arrayFilter = (oldArray: TObject[], keyOldArray: ???) => {
return oldArray.filter((item) => item.keyOldArray === somethingConst);
};

You can use typescript's keyof operator: https://www.typescriptlang.org/docs/handbook/2/keyof-types.html
It looks like you are trying to filter based on the index of keyOldArray, so you'll have to pull out index from the filter callback as well.
const arrayFilter = (oldArray: TObject[], keyOldArray: (keyof TObject)[]) => {
return oldArray.filter((item, index) => item[keyOldArray[index]] === somethingConst);
};

Related

How to trim all string properties of a class instance?

I have an instance of some class.
Let's say this class is Person:
class Person {
name?: string | null;
age?: number | null;
friends!: Person[];
isLucky: boolean;
}
How to iterate over this instance and call trim() method on all properties that are strings? Because if I'm trying to do this:
(Object.keys(person) as (keyof typeof person)[]).forEach((key) => {
const value = person[key];
if (typeof value === 'string') {
person[key] = value.trim();
}
});
My friend Typescript shows this error:
Type 'string' is not assignable to type 'person[keyof person]'.
I want to write an all around method suitable for instances of different classes with many different properties.
Is there a way to achieve this in Typescript? May be some typing magic?
I would do it this way. Just cast it to any.
(Object.keys(person) as (keyof typeof person)[]).forEach((key) => {
const value = person[key];
if (typeof value === 'string') {
(person as any)[key] = value.trim();
}
});
TypeScript isn't able to determine if the key is associated with a specific typed key from Person, only that it's one of them. So you'll get a type of never when accessing person[key] without any other specific checks on key.
The quickest answer is to narrow your key:
(Object.keys(person) as (keyof typeof person)[]).forEach((key) => {
const value = person[key];
if (key === 'name' && typeof value === 'string') {
person[key] = value.trim();
}
});
Alternatively you could do the .trim() in your class constructor and avoid this entirely, but it's unclear what the context is for your issue.
Generally I avoid doing a forEach to map an Object and might favor Object.from over an Object.entries with a .map() or reconsider the data structure entirely depending on what your actual use case might be.

How to define structure of docs with interface

I have an interface describing the sctructure of my documents in a given collection:
interface IGameDoc {
playerTurn: string;
gameState: {
rowOne: [string, string, string]
rowTwo: [string, string, string]
rowThree: [string, string, string]
};
}
So if I fetch the docs in my collection
const gamesRef = useFirestore()
.collection('Games')
const { status, data } = useFirestoreCollectionData(gamesRef) //I would like data to be of type IGameDoc[]
Can this be achieved somehow?
You can define data to be of whatever type you want like below however you can't control what type is returned from the useFirestoreCollectionData service (in this piece of code).
You either need to transform it using a function.
The example below will cast it to a type and it will try to match as best as it can. It is hard for me to test without knowing more of your code.
const { status, data } : { status: any, data: IGameDoc[]} = useFirestoreCollectionData(gamesRef)

Generic function for two different types of interfaces in Typescript

I am writing a React application (in TypeScript), in which I've two useState objects that will tell if an addon/accessory have been removed from a product, for visual purposes. Products can have both accessories and addons.
First things first. I have two different interfaces for addons and accessories:
interface IAddon {
id: string;
// some other values
}
interface IAccessory {
id: string;
// some other values
}
Both of my above interfaces are contained within a parent interface known as a product:
interface IProduct {
rowId: string;
accessories: IAccessory[];
addons: IAddon[];
// other values
}
I have created two useStates that can contain a list of inactive accessories and addons sorted by the key of the product, as I have a list of products for which users can add or remove either accessories or addons. Once a product is inactive it's visuals are supposed to change.
interface IAddonsWithId {
[rowId: string]: IAddon[]
}
interface IAccessoriesWithId {
[rowId: string]: IAccessory[]
}
I am trying to create a key pair function that work for both above interfaces and can add or remove from either of them. I have the below mentioned useStates.
const [inactiveAddons, setInactiveAddons] = useState<IAddonsWithId>({});
const [inactiveAccessories, setInactiveAccessories] = useState<IAccessoriesWithId>({});
and I have created this tiny interface to handle in the following function:
interface IKeyPair {
rowId: string;
objectId: string;
object: IAddonsWithId | IAccessoriesWithId;
}
So with that I'm trying to create this function:
const sortOutObjectFromList = ({rowId, objectId, object}: IKeyPair) => {
return {
...object,
[rowId]: object[rowId].filter( // <--- This line troubles me (TS:2349)
(item: IAddon | IAccessory) => item.id !== objectId,
),
};
};
The idea of the function is to be able to run through both my addon and accessory lists and be able to sort out the item that should have removed it's inactive state. I will have to create a similar function that will add items to the inactive list, but first I need to figure out how to satisfy TSLint.
I am getting the following error on the line marked above:
TS2349: This expression is not callable. Each member of the union type '{ <S extends IAddon>(callbackfn: (value: IAddon, index: number, array: IAddon[]) => value is S, thisArg?: any): S[]; (callbackfn: (value: IAddon, index: number, array: IAddon[]) => unknown, thisArg?: any): IAddon[]; } | { ...; }' has signatures, but none of those signatures are compatible with each other.
My code should be compileable, as is, but I'd like to avoid the TSLint error as I have set up pre-commit hooks to check for that. I've tried searching google and here on stackoverflow, but have been unable to figure out completely how to fix this error.
I believe the issue is the mix of the two interfaces IAddon and IAccessory, as they do not share many of the same properties, except for the id. which is the only one I try to match on.
How do I remove this TSLint error?
Try this, I think object[rowId] is coming as undefined.
const sortOutObjectFromList = ({rowId, objectId, object}: IKeyPair) => {
return {
...object,
[rowId]:
object[rowId]?.filter((item: IAddon | IAccessory) => item.id !== objectId) ?? []
};
};

TypeScript iterating over object and creating a new one

I have two objects of the same interface.
interface SizeFilter {
key: string | undefined
}
const defaultFilter = {
key: undefined,
} as SizeFilter;
I have a function which basically checks if there is a key set in a state and accordingly get filter options:
const { filters } = useSelector(({ sizeCurves }) => sizeCurves);
// Get FilterOptions for filtering on the page.
const getSelectedFilters = () => {
const pageFilter = defaultFilter;
// TODO: add an Object loop to check all properties and define value.
pageFilter.sizeCurveKey = filters.sizeCurveKey ?? undefined ;
return pageFilter;
};
I have to decide whether I should take a filter value from the filters or from a default filter. And for that, I am setting pageFilter in the getSelectedFilters function.
When I try to iterate over defaultFilter using Object.Keys().map,
Object.keys(defaultFilter).map(key =>{
const pageFilter[item] = (defaultFilter[ key ])? defaultFilter[ key ] : undefined;
});
My editor throws warning on defaultFilter[ key ]
Element implicitly has an 'any' type because expression of type
'string' can't be used to index type 'SizeFilter'. No index
signature with a parameter of type 'string' was found on type
'SizeFilter'.
I am not sure how to fix this issue.
The reason for this behavior is to prevent runtime errors that come from indexing an object by unknown keys. To prevent that you need to check first if the key is valid for defaultFilter. You can achieve this with a small helper function:
function hasKey<O>(obj: O, key: keyof any): key is keyof O { return key in obj }
You can use the function like this afterwards:
if (hasKey(defaultFilter, key)) {
defaultFilter[key]
}

Types for a function that receives an object of type `[key: string]: SOME_TYPE` and returns an array of type `SOME_TYPE[]`

I have some objects containing DB documents that I constantly need to convert to arrays.
Example:
const MY_OBJECT = {
docId_1: {...doc1},
docId_2: {...doc2},
docId_3: {...doc3},
// AND SO ON
}
I need to convert it to an array like this:
const MY_ARRAY_FROM_OBJECT = [
{...doc1},
{...doc2},
{...doc3},
// AND SO ON...
]
And this is code I use to do the conversion:
function buildArrayFromObject(obj) {
const keys = Object.keys(obj);
const arr = [];
for (const key of keys) {
arr.push(obj[key]);
}
return arr;
}
And I need to type that function so I can use it with objects that has types like these:
interface BLOGPOSTS_ALL {
[key: string]: BLOGPOST
}
interface PRODUCTS_ALL {
[key: string]: PRODUCT
}
So when I call them with each different object, I want Typescript to know what the return array type will be.
For example:
const BLOGPOSTS_ALL_ARRAY = buildArrayFromObject(BLOGPOSTS_ALL); // SHOULD BE "BLOGPOST[]"
const PRODUCTS_ALL_ARRAY = buildArrayFromObject(PRODUCTS_ALL); // SHOULD BE "PRODUCT[]"
Here is what I've tried:
function buildArrayFromObject<T, K extends keyof T>(obj: T): T[K][] {
const keys = Object.keys(obj);
const arr = [];
for (const key of keys) {
arr.push(obj[key]);
}
return arr;
}
But I'm getting this error:
And the returning type of the function is being evaluated by Typescript as type never[]
What you could do is the following:
Create an abstraction type to define what your database handles look like:
// Create an Entity type to define how your database objects look like
type Entities<T> = { [key: string]: T | undefined }
Therefore you can express your products and blogposts like this:
type BLOGPOSTS = Entities<BLOGPOST>;
type PRODUCTS = Entities<PRODUCT>;
In order to convert them in type-safe manner into an array you can use the Object.values method provided by the JavaScript API - for more information please see MDN
It's possible to replace the method buildArrayFromObject by something like this:
const isNil = <T>(a: T | null | undefined): a is null | undefined => a === null || a === undefined;
const isAssigned = <T>(a: T | null | undefined): a is T => !isNil(a);
const entitiesToArray = <T>(entity: Entities<T>): T[] => Object.values(entity).filter(isAssigned);
This method uses the Object.values method to convert the object into an array. Afterwards it get's filtered to contain an array without undefined values. Therefore I made use of two helper methods isAssigned and isNil. Please see this CodeSandbox for an example: Code SandBox
Based on your concern, that the Object.value method is not supported by IE11, you can add a polyfill for that one. Either by pasting a polyfill in or by adding it using npm to your project.
Alternatively you can replace this code by the following answer Use Object.keys to mimick Object.values

Resources