reactjstypescriptnext.jstypescript-generics

Typescript says the property does not exixts


Im fairly new to generic/typescrypt.

Context: Im trying to pass data from a pages (page.tsx) to a generic components (header.tsx). The data is fetched from a react-query function.

Issue: In the header.tsx, the Checkbox component, Typescrypt is saying

Property every does not exist on type T

Parameter 'record' implicitly has an 'any' type

And I am quite confuse why that is the case when the data im passing guarantee to be an array and existed.

What I thought: TypeScript also notifies that record has an any type, but my thought process was that since the component doesn’t recognize the type of the 'data' prop, if I was able to somehow get the type for the 'data' prop in headers.tsx, TypeScript can then infer it.

I have attached link to playground.

types.ts

export type ProductRow = {
    id: string;
    name?: string;
    sku?: string;
};

page.tsx

export const page = () => {
  //data fetched from react-query useQuery
  const data = [
    {
      brand: {
        id: 'clywipo320000cayabq4iub7e', 
        name: 'Nero', 
        minMargin: 30, 
        maxMargin: 40, 
      },
      id: '1',
      name: "100mm Round Tile Insert Floor Waste 50mm Outlet Brushed Gold",
      priceRecords: [
          {
              price: 120, 
              date: 'Fri Aug 23 2024 10:00:00 GMT+1000 (Australian Eastern Standard Time)'
          }
      ],
      sku: 'NRFW003GD'
    },
    {
      brand: {
        id: 'clywipo320000cayabq4iub7e', 
        name: 'Nero', 
        minMargin: 30, 
        maxMargin: 40, 
      },
      id: '2',
      name: "100mm Round Tile Insert Floor Waste 50mm Outlet Brushed Bronze",
      priceRecords: [
          {
              price: 112, 
              date: 'Fri Aug 23 2024 10:00:00 GMT+1000 (Australian Eastern Standard Time)'
          }
      ],
      sku: 'NRFW003BZ'
    }
  ]

  const [selectedRows, setSelectedRows] = useState<ProductRow[]>([]);

  return (
    <div>
      <Header data={data} selectedRows={selectedRows} />
      { data.map(record => 
        <input 
            type={`checkbox`} 
            checked={selectedRows.map(row => row.id).includes(record.id)} 
            onClick={() => setSelectedRows(prevValue => [...prevValue, { id: record.id, name: record.name, sku: record.sku }])} />
      )}
    </div>
  )
}

header.tsx

type HeaderProps<T> = {
  data: T,
  selectedRows: ProductRow[]
};

export const Header = <T,>({ data, selectedRows }: HeaderProps<T>) => {
  return (
    <div className={``}>
      <div className={`flex gap-3 items-start`}>
        <input 
          type={`checkbox`}
          checked={data.every(record => selectedRows.map(row => row.id).includes(record.id))}
        />
        <p>SKU</p>
      </div>
      <p className={`col-span-3`}>Product Name</p>
      <p>Brand</p>
      <p className={`text-center`}>RRP</p>
      <p className={`text-center`}>Max Discount (%)</p>
      <p className={`text-center`}>Buildmat Price</p>
      <p className={`text-center`}>TheBlueSpace Price</p>
    </div>
  )
};

Solution

  • This particular concept was discussed in the handbook under Generic Constraints. [typescriptlang.org]

    Basically, if want to write a generic function that works on a set of types where you have some knowledge about what capabilities that set of types will have, it is completely normal. The only issue with this is that the compiler cannot prove that every type has a given property, so it must warn you that you can’t make this assumption which seems reasonable because in your case, what if typeof data.priceRecords doesn’t return a type that has a property every()?

    The concept of generic constraints is basically the idea that we should constrain our generic procedure to work with any type T but for any property that must be used on T, that property must be assured to exist.


    interface EveryWise {
      every(callbackfn: (value, index: number, array) => boolean, thisArg?: any): boolean;
    }
    
    export const Header = <T extends EveryWise>({ data, selectedRows }: HeaderProps<T>) => { /* •••Rest of code••• */ }

    Example of Generic Constraint. [1]


    [1] The interface EveryWise should be constrained even more with a type U representing the type of value but since the type of value is left out, this is the strictest we can go. The reason for this mention is that record is of type any hence record.id is used without type safety.