Home > Enterprise >  Infer types from parent props
Infer types from parent props

Time:08-17

Is there a way for my child component to know about the parent component's prop types?

I don't want to hardcode them into the child, as the child will be used by multiple parents with slightly different props.

A simplified example:

// Parent.tsx

import React, {ReactElement} from "react";

interface Row {
  id: string;
  name: string;
  tags: string[];
}

const Parent = (): ReactElement => {
  const rows: Row[] = [
    {
      id: "1",
      name: "Foo",
      tags: ["new"],
    },
    {
      id: "1",
      name: "Bar",
      tags: ["new", "sale"],
    },
  ];

  const formatRow = (row: Row): ReactElement => (
    <tr key={row.id}>
      <td>{row.id}</td>
      <td>{row.name}</td>
      <td>{row.tags.map(tag => <strong>{tag}</strong>)}</td>
    </tr>
  );

  return <Child formatRow={formatRow} rows={rows} />;
};

export default Parent;
// Child.tsx

import React, {ReactElement} from "react";

const Child = ({formatRow, rows}): ReactElement => {
  const columnCount = Object.keys(rows[0]).length;

  return (
    <table>
      <tbody>
        {rows.map(row => formatRow(row))}
      </tbody>
      <tfoot>
        <tr>
          <td colSpan={columnCount}>Footer</td>
        </tr>
      </tfoot>
    </table>
  );
};

export default Child;

There could then also be another parent, Parent2 with this shape for it's own implementation of Row:

// Parent2.tsx

interface Row {
  id: string;
  count: number;
}

const Parent2 = (): ReactElement => {
  // similar code to `Parent` here
};

export default Parent2;

I'm not sure how to let the Child component know that formatRow expects a Row and returns a ReactElement, and that rows is an array of Row objects?

I think Generics might be the answer, but I have extremely limited knowledge on that, so any help whether Generics or otherwise is appreciated.

CodePudding user response:

Usually, if you want to be argument agnostic you should use generics:


import React, { ReactElement } from "react";

interface Row {
  id: string;
  name: string;
  tags: string[];
}

const Child = <R,>({ formatRow, rows }: { rows: R[], formatRow: (row: R) => ReactElement }): ReactElement => {
  const columnCount = Object.keys(rows[0]).length;

  return (
    <table>
      <tbody>
        {rows.map(row => formatRow(row))}
      </tbody>
      <tfoot>
        <tr>
          <td colSpan={columnCount}>Footer</td>
        </tr>
      </tfoot>
    </table>
  );
};

const Parent = (): ReactElement => {
  const rows: Row[] = [
    {
      id: "1",
      name: "Foo",
      tags: ["new"],
    },
    {
      id: "1",
      name: "Bar",
      tags: ["new", "sale"],
    },
  ];

  const formatRow = (row: Row): ReactElement => (
    <tr key={row.id}>
      <td>{row.id}</td>
      <td>{row.name}</td>
      <td>{row.tags.map(tag => <strong>{tag}</strong>)}</td>
    </tr>
  );

  return <Child formatRow={formatRow} rows={rows} />;
};

Playground

Here you have simplified version:

interface ChildProps<R> {
  rows: R[],
  formatRow: (row: R) => ReactElement
}

const Child = <R,>({ formatRow, rows }: ChildProps<R>): ReactElement => {
  const columnCount = Object.keys(rows[0]).length;

  return (
    <table>
      <tbody>
        {rows.map(row => formatRow(row))}
      </tbody>
      <tfoot>
        <tr>
          <td colSpan={columnCount}>Footer</td>
        </tr>
      </tfoot>
    </table>
  );
};

So, R is infered type of rows property. This is how inference works. YOu provide any value to rows and R will represent type of the value. Then, you can use R for other proeprties.

If you are interested in type inferece on function arguments you can check my article

  • Related