Clifford Fajardo
Clifford Fajardo

Reputation: 1447

Typescript React: generic-based callback event handler function as prop to component

I'm having trouble creating a generic-based callback event handler function that I want to pass as prop down to my component.

My goal: Allow user to pass a custom callback function that:

  1. always takes in the same argument
    • an event (not a react/dom event handler event, its coming from a library)
  2. can return different return type
    • for my use cases, this component is used in different context's so in one place a user will return a certain value over another

What I have Attempted

  1. I have my onNodeClick function defined using generics & when i use it in isolation, it works.
// ✅ Simple example of calling a generic function
const onNodeClick = <T,> (event:any) => {
  return null as unknown as T;
} 
const string_result = onNodeClick<string>(event_from_somewhere)

However, when I try to pass this method as a prop to my component, I am getting errors. I'm unsure of how to resolve it

Live Typescript Code Playground

import React from 'react';

type NodeComponentProps = {
  onNodeClick: <T>(event: any) => T;
};

export const NodeComponent = ({onNodeClick}: NodeComponentProps) => {
    return null;
}
  
const Homepage = () => {
  const handleNodeClick = <T,>(event: any): T => {
    return null as unknown as T;
  };

  return (
    <NodeComponent 
      onNodeClick={(event): string => {
         const string_result = handleNodeClick<string>(event); // ✅ correct type
         return string_result; // ❌ onNodeClick is throwing type error; see error message a few lines below
      }} 
    />
  )
}

/*
Type '<T>(event: any) => string' is not assignable to type '<T>(event: any) => T'.

Type 'string' is not assignable to type 'T'.

'T' could be instantiated with an arbitrary type which could be unrelated to 'string
*/

Upvotes: 3

Views: 2740

Answers (1)

Zac Delventhal
Zac Delventhal

Reputation: 4010

I think you want the type to be generic, not the function. If the function is generic, then it should be callable with angle brackets and work correctly. For example, the identity function works with any type, so it should pass your generic function definition:

const Homepage = () => {
  const onNodeClick = <T>(x: T): T => {
    return x;
  };

  return (
    <NodeComponent 
      onNodeClick={onNodeClick} 
    />
  )
}

However, the function you used will always return a string. If you tried to call it with <number>, it would fail. Therefore, not a generic function.

By contrast, if you make the type generic, you should be able to specify you are using it with a string:

Here is a Playground link

type NodeComponentProps<T> = {
  onNodeClick: (event: any) => T;
};

export const NodeComponent = <T,>({onNodeClick}: NodeComponentProps<T>) => {
    return null;
}

Upvotes: 2

Related Questions