skip to Main Content

Is it somehow possible to get the values in columns as a type in valueFormatter datacolumn?
So if i were to input id, firstname and lastname as columns like shown below i would only get those as a type on valueformatter function?

Goal is go get the columns (id, firstname and lastname) as type on valueformatter

import "./App.css";

interface TableColumn {
  name: string;
  // TODO: make this type same as type of the keys in one of the objects in rows
  valueFormatter?: (datacolumn: {
    id: number;
    firstname: string;
    lastname: string;
  }) => void;
}

interface TableProps {
  columns: TableColumn[];
  rows: any[];
}

const Table = ({ columns, rows }: TableProps) => {
  return (
    <div>
      <div style={{ display: "flex" }}>
        {rows.map((row, index) => (
          <div key={`row-${index}`} style={{ display: "inline-flex" }}>
            {columns.map((col) => (
              <div
                key={col.name}
                style={{ padding: "1rem", border: "1px solid white" }}
              >
                {col.valueFormatter ? col.valueFormatter(row) : row[col.name]}
              </div>
            ))}
          </div>
        ))}
      </div>
    </div>
  );
};

function App() {
  const columns = [
    {
      name: "id",
      valueFormatter: (row) => {
        return row.firstname + "some value";
      },
    },
    { name: "firstname" },
    { name: "lastname" },
  ];
  const rows = [{ id: 1, firstname: "John", lastname: "Doe" }];

  return (
    <div className="App">
      <Table columns={columns} rows={rows} />
    </div>
  );
}

export default App;



https://codesandbox.io/p/sandbox/sleepy-thunder-23vwdh?file=%2Fsrc%2FApp.tsx%3A1%2C1-48%2C1

2

Answers


  1. You could separate the row object’s type and valueFormatter function’s type to their own type variables and then use those in both TableColumn and the actual implementation of valueFormatter.

    type RowObject = {
      id: number;
      firstname: string;
      lastname: string;
    }
    
    // Note that you have the return type for the value formatter marked as void
    // but you return a string from it, so used that here.
    type ValueFormatter = (rowObject: RowObject) => string;
    
    interface TableColumn {
      name: string;
      valueFormatter?: ValueFormatter
    }
    
    // then in App.tsx:
    
    // Type the variable itself here
    const columns: TableColumn[] = [
      {
        name: "id",
        // No need to type the row here explicitly as the columns variable itself
        // is typed so TypeScript also knows the type of the row variable from that
        valueFormatter: (row) => {
          return row.firstname + "some value";
        },
      },
      { name: "firstname" },
      { name: "lastname" },
    ];
    
    // Type also this variable for safety
    const rows:: RowObject[] = [{ id: 1, firstname: "John", lastname: "Doe" }];
    
    

    Then you can also type the rows prop in Table component properly.

    Login or Signup to reply.
  2. The keyword you are looking for is generics. Something like this would work for you.

    // takes generic Row
    type TableColumn<Row> = {
      name: keyof Row & string;
      valueFormatter?: (datacolumn: Row) => string;
    };
    
    // takes generic Row and passes it to TableColumn.
    type TableProps<Row> = {
      columns: TableColumn<Row>[];
      rows: Row[];
    };
    
    // takes generic Row but this time it extends Record<string, any>
    // instead of any, you can specify string | number etc...
    // you can extend in the other generic Row implementations above too. 
    // It would make sense if you did actually. But not necessary.
    const Table = <Row extends Record<string, any>>({
      columns,
      rows,
    }: TableProps<Row>) => {
      return (
        <div>
          <div style={{ display: "flex" }}>
            {rows.map((row, index) => (
              <div key={`row-${index}`} style={{ display: "inline-flex" }}>
                {columns.map((col) => (
                  <div
                    key={col.name}
                    style={{ padding: "1rem", border: "1px solid white" }}
                  >
                    {col.valueFormatter ? col.valueFormatter(row) : row[col.name]}
                  </div>
                ))}
              </div>
            ))}
          </div>
        </div>
      );
    };
    
    function App() {
    
      // define a Row that you will use
      type Row = {
        id: number;
        firstname: string;
        lastname: string;
      };
    
      // use TableColumn<Row> type so that typescript holds your hand while writing the columns.
      const columns: TableColumn<Row>[] = [
        {
          name: "id",
          valueFormatter: (row) => {
            // you get nice editor support for row here.
            return row.firstname + "some value";
          },
        },
        { name: "firstname" },
        { name: "lastname" },
      ];
    
      // You can use Row[] here as well.
      const rows: Row[] = [{ id: 1, firstname: "John", lastname: "Doe" }];
    
      return (
        <div className="App">
          <Table columns={columns} rows={rows} />
        </div>
      );
    }
    
    export default App;
    

    This way, you can separate your Table component and use it with as many Row types as you wish. export TableColumn and TableProps so that you can use them in your components that will use the Table component.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search