1

I would like to implement the following (in React/TypeScript (.tsx))

Main.tsx (edit: botCommands is an array in which multiple objects can be (with command and botResponse())

function Main() {
  return (
    <TestComponent
      botVariables={{
        tasks: [],
        groups: []
      }}
      botCommands={[
        {
          command: "-help",
          botResponse(botVariables) {
            return botVariables.tasks[0]
          }
        }
      ]}
    />
  );
}

TestComponent.tsx

interface BotCommand {
  command: string;
  botResponse(botVariables: any): string;
}

interface TestComponentProps {
  botVariables: any;
  botCommands: BotCommand[];
}

export default function TestComponent(props: TestComponentProps) {
  return (
    // magic
  );
}

in Main.tsx I would like that in the function botResponse() the type (currently any) is automatically adjusted. So that automatically the content of botVariables is taken as type.

Simply put, if I write in Main.tsx in the botResponse() function "botVariables." that tasks and groups can be suggested to me by the IDE. The data in botVariables can change so you can't just create an interface in the TestComponent.tsx file.

I tried with typescript generics but unfortunately failed. Thanks for help!

1
  • What are the constraints of botVariables? Is it always an object with properties tasks and groups which are arrays? Do the contents of a tasks array vary, or are they always string? Commented Jan 24, 2021 at 4:04

2 Answers 2

1

If I am understanding your requirements correctly, botVariables could be anything, but the botVariables and the botCommands that we provide to the TestComponent need to match each other. That is, the arguments of the botResponse function in the botCommands are the same type as the botVariables prop.

In order to do this, we make the BotCommand interface generic, where the type T represents the type of the botResponse variables.

interface BotCommand<T> {
  command: string;
  botResponse(botVariables: T): string;
}

Our TestComponentProps are also generic, and we apply the same T to the botVariables as to the botCommands.

interface TestComponentProps<T> {
  botVariables: T;
  botCommands: BotCommand<T>[];
}

This means the component is generic as well.

export default function TestComponent<T>(props: TestComponentProps<T>) {...}

There is no problem creating a response from the passed variables, since both props share the same T.

export default function TestComponent<T>({ botVariables, botCommands }: TestComponentProps<T>) {
    return (
        <div>
            <h1>Bot Output</h1>
            {botCommands.map(command => (
                <div>
                    <div>Bot recieved command {command.command}</div>
                    <div>Bot responded {command.botResponse(botVariables)}</div>
                </div>
            ))}
        </div>
    );
}

You don't need to explicitly declare the generic T when you use the TestComponent because it can be inferred from the botVariables prop. If the botCommands don't match, typescript will complain.

In this example:

function Main() {
  return (
    <TestComponent
      botVariables={{
        tasks: ["first"],
        groups: ["something"]
      }}
      botCommands={[{
        command: "-help",
        botResponse(botVariables) {
          return botVariables.tasks[0]
        }
      }]}
    />
  );
}

The inferred signature for the botResponse function is complete and correct:

(method) BotCommand<{ tasks: string[]; groups: string[]; }>.botResponse(botVariables: {
    tasks: string[];
    groups: string[];
}): string

In order to get proper inference, I had to use ["first"] instead of an empty array because empty arrays are typed as never[] but we want string[]. You can use an empty array, but you would have to explicitly set the type for it.

Playground Link

Sign up to request clarification or add additional context in comments.

Comments

0

Welcome to StackOverflow community.

Typescript cannot automatically detect type of your parameter.

Anyway, you can choose to use Union Types or Generic Types

You can use Union Type if variables are a limited set of types.

interface MyObject {
    tasks: string[]
}

interface BotCommand {
  command: string;
  botResponse: (botVariables: string | number | MyObject) => string;
}

Example with Generic Types

interface BotCommand {
  command: string;
  botResponse: <T extends unknown>(botVariables: T) => string;
}

In both cases you need to "recognize" the current type of your object before using it. To do that you can use a Typeguard.

function isMyObject(x: unknown): x is MyObject {
  return (x as MyObject).tasks !== undefined
}

For more infos give a look to this playground.

2 Comments

What you are saying is all true if we are dealing with just the BotCommand interface alone but you are kind of missing the bigger picture. Within the context of a TestComponent component, the arguments of the botResponse aren't unknown anymore because they are the same type as the botVariables prop.
The parameter tasks:[] for example is not always specified. The answer from Linda Paiste helped me. Thanks for your help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.