TransWikia.com

React component dispatching generic payload

Stack Overflow Asked by andyroo on February 14, 2021

I trying to create a reusable <Column /> component that displays a list of items which each dispatch a generic payload specified by the caller when clicked.

My column takes an onItemClick prop which is a function that dispatches a payload (a Redux action in my actual code). I want my function to be able to accept and dispatch a generic <PayloadType>:

type ColumnProps<PayloadType> = {
  menuItems: { name: string; id: number }[];
  onItemClick: (payload: PayloadType) => void;
};

const Column = <PayloadType extends {}>(
  props: React.PropsWithChildren<ColumnProps<PayloadType>>
) => {
  const { menuItems, onItemClick } = props;
  const handleButtonClick = (menuItem: MenuItem) => {
    onItemClick({ info: menuItem.name });
    /* 
     Argument of type '{ info: string; }' is not assignable to parameter of type 
     'PayloadType'.
     '{ info: string; }' is assignable to the constraint of type 'PayloadType', but 
     'PayloadType' could be instantiated with a different subtype of constraint '{}'
    */
  };
  return (
    <>
      {menuItems.map((menuItem, index) => (
        <button key={index} onClick={(event) => handleButtonClick(menuItem)}>
          {menuItem.name}
        </button>
      ))}
    </>
  );
};

Using the component:

type MenuItem = {
  name: string;
  id: number;
};

const testMenuItems: MenuItem[] = [
  { name: "Vanilla", id: 0 },
  { name: "Strawberry", id: 1 },
  { name: "Chocolate", id: 2 },
  { name: "Cookies & Cream", id: 3 }
];

type ColumnPayload = {
  info: string;
};

export default function App() {
  const columnClickHandler = (payload: ColumnPayload) => {
    console.log(`clicked: ${payload.info}`);
  };
  return (
    <div className="App">
      <Column<ColumnPayload>
        menuItems={testMenuItems}
        onItemClick={columnClickHandler}
      />
    </div>
  );
}

As seen above, I’m receiving the error:

Argument of type '{ info: string; }' is not assignable to parameter of type 'PayloadType'.
  '{ info: string; }' is assignable to the constraint of type 'PayloadType', but 'PayloadType' could be instantiated with a different subtype of constraint '{}'.

How can I accept and dispatch a generic payload from my component? I’m fairly new to TypeScript so I’m not sure if I’m missing something or simply approaching the problem completely wrong.

Sandbox: https://codesandbox.io/s/sweet-kalam-tt5u5?file=/src/App.tsx

One Answer

The issue here is Column cannot create a specific type without help, its only able to be aware that there is some unknown type.

Type Intersections

That being said, one way you can achieve your generic callback is simply union the payload type with the menu item.

type MenuItemWithPayload<TPayload> = MenuItem & Payload

Then dispatch the callback with the entire menuitem. I provided some example code, notice how that ColumnMenuItem is both payload and menuitem type? this allows type inferencing were you no longer need to define the payload type when using the column component.

https://codesandbox.io/s/eager-framework-pu4qe?file=/src/App.tsx.

Generic payload field

A cleaner alternative might be to allow the menu item to contain a payload field. Which is similar to the union type but uses composition.

type MenuItem<TPayload = unknown> = { name: string; id: number; payload: TPayload }

https://codesandbox.io/s/friendly-currying-g1ynk?file=/src/App.tsx

Forwarding MenuItem

Finally you can simply forward the menu item in the callback and let the parent component generate the payload it needs.

https://codesandbox.io/s/hardcore-surf-ihz96?file=/src/App.tsx

Answered by Brenden on February 14, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP