1

I defined a method called filterByFront

export const filterByFront = <T>(
  value: string,
  jsonData: T[],
  setData: (data: T[]) => void,
  filterKey: keyof T
) => {
  const originData = jsonData;
  if (!!value && !!value.trim()) {
    setData(originData);
  } else {
    const filterData = originData.filter((item) => {
      const newFilterValue = item[filterKey];
      return newFilterValue
        ?.toLowerCase()    //error message shows in this line 
        ?.includes(value.trim().toLowerCase());
    });

    setData(filterData);
  }
};

and i called it in components like this

   <Input.Search
            key="filter_role"
            placeholder={t('common.filter')}
            onSearch={(value) =>
              filterByFront<IRoleListItem>(value, jsonData, setRoleData, 'id')
            }
          />,

the type definition

export interface IRoleListItem {
  authority?: string;

  id?: string;

  privilege?: string;

  prohibit?: string;

  tag_list?: string;
}

it shows error :

Property 'toLowerCase' does not exist on type 'NonNullable<T[keyof T]>'.ts(2339)

How can I make this error disappeared

yue yan
  • 27
  • 1
  • 4

2 Answers2

0

Please use this restriction T extends Record<PropertyKey, { toLowerCase: () => string }>:

export const filterByFront = <T extends Record<PropertyKey, { toLowerCase: () => string }>>(
  value: string,
  jsonData: T[],
  filterKey: keyof T
) => {
  const originData = jsonData;
  if (!!value && !!value.trim()) {
    console.log(originData);
  } else {
    const filterData = originData.filter((item) => {
      const newFilterValue = item[filterKey];
      return newFilterValue
        ?.toLowerCase()
        ?.includes(value.trim().toLowerCase());
    });

    console.log(filterData);
  }
};

Playground

  • 1
    I don't think this works. If you add OP's usage of the function, you get an error about a missing index signature: [TS Playground](https://tsplay.dev/wgX3MN). I think it might be necessary to do a runtime check rather than using a generic constraint? e.g. [TS Playground](https://tsplay.dev/WYv1bw) – Jimmy Feb 16 '22 at 09:09
  • @Jimmy good point. However, if OP using interface, they might not need even generic parameter. See [here](https://tsplay.dev/NnQjVw) – captain-yossarian from Ukraine Feb 16 '22 at 09:26
  • Thanks ,it worked as @jimmy said ,but generic parameter is still needed I think,because this is a public metods,the jsonData passed may have other type – yue yan Feb 16 '22 at 09:36
  • I think in this case @Jimmy should provide their answer. Also it worth mentioning that there is a difference between interfaces and types in terms of indexing, see [here](https://stackoverflow.com/questions/37233735/interfaces-vs-types-in-typescript#answer-64971386). If you use `type IRoleListItem` instead of `interface IRoleListItem` it will work – captain-yossarian from Ukraine Feb 16 '22 at 09:48
0

The crux of the issue is that T has no constraints, so we can't assume that it will have a property called toLowerCase. As captain-yossarian notes in the comments on their answer, types created with type have an implicit index signature, while types created with interface do not. If you are able to define your role list item type using type, their solution should work. If not, you might need to do a runtime check to see if T is a string before you can proceed to treat it as one:

export interface IRoleListItem {
  authority?: string;
  id?: string;
  privilege?: string;
  prohibit?: string;
  tag_list?: string;
}

export const filterByFront = <T, K extends keyof T>(
  value: string,
  jsonData: T[],
  setData: (data: T[]) => void,
  filterKey: K
) => {
  const originData = jsonData;
  if (!!value && !!value.trim()) {
    setData(originData);
  } else {
    const filterData = originData.filter((item) => {
      const newFilterValue = item[filterKey];

      if (typeof newFilterValue === "string") {
        return newFilterValue?.toLowerCase()?.includes(value.trim().toLowerCase());
      } else {
        return false;
      }
    });

    setData(filterData);
  }
};

declare const value: string;
declare const jsonData: IRoleListItem[];
declare const setRoleData: (data: IRoleListItem[]) => {};

filterByFront(value, jsonData, setRoleData, 'id');

TS Playground

Jimmy
  • 35,686
  • 13
  • 80
  • 98