2

Using React with TypeScript, I try to dynamically render components based on a field name

// Referencing all components
const components = {
  ComponentA,
  ComponentB,
};

// Dynamic render the component based on fieldName
const displayComponent = (
  fieldName: keyof typeof components,
  content: ComponentType
): React.ReactNode => {
  const FieldNameComponent = components[fieldName];
  return <FieldNameComponent item={content} />;
};

My types:

type ComponentTypeA = {
  title: string;
  content: string;
};

type ComponentTypeB = {
  title: string;
  excerpt: string;
};

type ComponentType = ComponentTypeA | ComponentTypeB;

The compiler is not happy because it doesn't know which type to use (Component1 or Component2 which have different properties). I get this error

Type 'ComponentType' is not assignable to type 'ComponentTypeA & ComponentTypeB'.

Type 'ComponentTypeA' is not assignable to type 'ComponentTypeB & ComponentTypeA'.

Type 'ComponentTypeA' is missing the following properties from type 'ComponentTypeB': excerpt

So I have tried this way:

const displayComponent = (
  fieldName: keyof typeof components,
  content: ComponentType
) => {
  if (content instanceof ComponentTypeA) {
    return <ComponentA item={content} />;
  } else if (content instanceof ComponentTypeB) {
    return <ComponentB item={content} />;
  }
};

But the error remains even with the instanceof test.

1
  • What is the error that you are getting? You don't set the return type on those functions. Shouldn't the return type be ComponentType Commented May 20, 2022 at 15:10

2 Answers 2

1

You could use a type guard:

function isComponentTypeA(type: unknown): type is ComponentTypeA {
    return type && "title" in type && "content" in type;
}

And you would use it in the condition:

  if (isComponentTypeA(content)) {
    return <ComponentA item={content} />; // inferred as ComponentTypeA
  } else if (isComponentTypeB(content)) { // another type guard if needed
    return <ComponentB item={content} />; // inferred as ComponentTypeB
  }
Sign up to request clarification or add additional context in comments.

4 Comments

Are you telling, I can not make a dynamic render with TS, like this const FieldNameComponent = components[fieldName]; return <FieldNameComponent item={content} />;
That is possible, just not with your current function signature. If you want to do dynamic rendering, why don't you change the question to that instead?
Good suggestion, title updated
thanks for you answer. How can I preserve the dynamic rendering? Without a type guard for each component. If I have to write a type guard for each component, and then test it, the dynamic rendering will be far more verbose.
0

I ended up to solve this error this way:

const displayComponent = (
  content: ComponentType
): React.ReactNode => {
  switch (content.fieldName) {
    case "ComponentA":
      return <ComponentA item={ content as ComponentAType}/>
    case "ComponentB":
      return <ComponentB item={ content as ComponentBType}/>
    default:
      break;
  }
};

But I pretty unhappy with this, because if I have to deal with a lot to components, the switch statement will be so long... A better approach is welcome.

Comments

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.