Git Product home page Git Product logo

polymorphic-react-component's Introduction

https://i.imgur.com/oNNDQDC.png


Polymorphic: occurring in several different forms, in particular with reference to species or genetic variation...



How to read the code

Read the implementation snippets from 01 to 06

01: A basic polymorphic component implementation

02: Handling relevant component props:

03: Providing a default generic type:

04: Handling unique component props

05: Building a reusable polymorphic type utility

06: Handling polymorphic components with Ref



Relevant Links



Read more



Download the accompanying ebook and receive my 5-day typescript secrets newsletter

Download ebook here.


https://i.imgur.com/U62L81u.png

You'll automatically receive my 5-day newsletter to get you thinking and writing Typescript like a pro ๐Ÿ•บ

polymorphic-react-component's People

Contributors

ohansemmanuel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

polymorphic-react-component's Issues

How do I wrap my Polymorphic Component?

Thank you for your excellent tutorial. I'm still new to Typescript.

I'm not sure how to write a component that wraps my Polymorphic created using your tutorial. Lets call the polymorphic component "Atom" for the sake of this question.

The wrapper component is called "Molecule" and I want "Molecule" to accept have all the same props as "Atom" plus a few more.

Should my wrapper component also use your Polymorphic types to take advantage of the strict typing? That's what I'm doing right now. There is a small problem, though. I can't pass the "as" prop from Molecule to Atom.

As an example, I've copied your example code for the Text component and also added a Wrapper component that wraps Text and provides an additional style. I can't use Wrapper to pass the "as" prop to the Text component.

import React from 'react';

type AsProp<C extends React.ElementType> = { as?: C };

type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);

// This is the first reusable type utility we built
type PolymorphicComponentProp<
  C extends React.ElementType,
  Props = object,
> = React.PropsWithChildren<Props & AsProp<C>> &
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;

// This is a new type utility with ref!
type PolymorphicComponentPropWithRef<
  C extends React.ElementType,
  Props = object,
> = PolymorphicComponentProp<C, Props> & { ref?: PolymorphicRef<C> };

// This is the type for the "ref" only
type PolymorphicRef<C extends React.ElementType> =
  React.ComponentPropsWithRef<C>['ref'];

// -----------------------------------------------------------------------------
// Text Component
// -----------------------------------------------------------------------------

type Rainbow =
  | 'red'
  | 'orange'
  | 'yellow'
  | 'green'
  | 'blue'
  | 'indigo'
  | 'violet';

/**
 * This is the updated component props using PolymorphicComponentPropWithRef
 **/
type TextProps<C extends React.ElementType> = PolymorphicComponentPropWithRef<
  C,
  { color?: Rainbow | 'black' }
>;

/**
 * This is the type used in the type annotation for the component
 **/
type TextComponent = <C extends React.ElementType = 'span'>(
  props: TextProps<C>
) => React.ReactNode | null;

export const Text: TextComponent = React.forwardRef(function Text<
  C extends React.ElementType = 'span',
>({ as, color, children }: TextProps<C>, ref?: PolymorphicRef<C>) {
  const Component = as ?? 'span';
  const style = color ? { style: { color } } : {};
  return (
    <Component {...style} ref={ref}>
      {' '}
      {children}{' '}
    </Component>
  );
});

// -----------------------------------------------------------------------------
// Wrapper Component
// -----------------------------------------------------------------------------

/**
 * This is the updated component props using PolymorphicComponentPropWithRef
 **/
type WrapperProps<C extends React.ElementType> =
  PolymorphicComponentPropWithRef<
    C,
    {
      color?: Rainbow | 'black';
      size?: 'big' | 'small';
    }
  >;

/**
 * This is the type used in the type annotation for the component
 **/
type WrapperComponent = <C extends React.ElementType = 'span'>(
  props: WrapperProps<C>
) => React.ReactNode | null;

export const Wrapper: WrapperComponent = React.forwardRef(function Text<
  C extends React.ElementType = 'span',
>(
  { as, color, children, size = 'small' }: WrapperProps<C>,
  ref?: PolymorphicRef<C>
) {
  const style = {
    style: {
      color,
      fontSize: size === 'small' ? '10px' : '40px',
    },
  };
  return (
    <Text 
      as={as}  // ๐Ÿ‘ˆ This causes a typeerror in Text.  If I set it to as='div' it works. But I want wrapper to be able to specify a tag.  How do we make as a string?
      {...style} 
      ref={ref}
    >
      {children}
    </Text>
  );
});

Ref type check doesn't work for default "as" prop

Hi, thanks for a great article.

const videoRef = useRef<HTMLVideoElement | null>(null);
// pass a video ref to a "span".
<Text ref={videoRef}>
  Hello Text world
</Text>

there is no type check in this case. we can pass any ElementType to ref.

'Component' cannot be used as a JSX component

Copying final code gives me following error:

'Component' cannot be used as a JSX component. Its element type 'Component<any, any, any> | ReactElement<any, any> | null' is not a valid JSX element. Type 'Component<any, any, any>' is not assignable to type 'Element | ElementClass | null'. Type 'Component<any, any, any>' is not assignable to type 'ElementClass'. The types returned by 'render()' are incompatible between these types. Type 'ReactNode' is not assignable to type 'false | Element | null'. Type 'undefined' is not assignable to type 'false | Element | null'.ts(2786)

Running on React 17.0.2, tslib 2.3.1, @types/react 17.0.40 and @babel/preset-typescript 7.16.7

Q: How to extend types and spread props

Hello, Thanks providing such a helpful example. It has greatly assisted me in the process of migrating our components that utilize polymorphic props from JavaScript to TypeScript. Specifically, our components rely on a Box component as a child, which contains style utility props such as margin and padding. I'm interested in understanding how I can extend this pattern to all of our components.

To illustrate the issue, I have created a CodeSandbox environment https://codesandbox.io/s/polymorphic-prop-type-q4pwvl?file=/src/text.tsx:622-888

In the above example I am trying to use the Box component that has the polymorphic as prop as the base for the Text component that can also forward refs, enables correct polymorphic typing but has extra logic that the Box doesn't need. I'm not sure my types are quite right or if I'm over complicating them? Any suggestions would be greatly appreciated!

TS Error Type 'ForwardRefExoticComponent<Omit<TextProps<ElementType<any>>, "ref"> & RefAttributes<unknown>>' is not assignable to type 'TextComponent'.

Firstly, thank you for an amazing and very thorough tutorial on logrocket!

I've followed the tutorial, and had this error on the Text compomnent:

Type 'ForwardRefExoticComponent<Omit<TextProps<ElementType<any>>, "ref"> & RefAttributes<unknown>>' is not assignable to type 'TextComponent'.
  Type 'ReactNode' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>> | null'.
    Type 'undefined' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>> | null'.(2322)

Check it in the TS playground:
Typescript playground

Use polymorphic component props in array

Hei. First of all thanks for the great example!

But I am struggling to figure out how to use the polymoprhic component props in another components array prop.
I created an example https://codesandbox.io/s/polymorphic-issue-k2i18n?file=/src/App.tsx

We have a polymorphic <Text> component, that can be a <span>, <p> or <label>. When using the component normally as <Text as="label" htmlFor="test">Label</Text>everything works good. Component only allows props for tag that we define in "as".

However I want to use <List> as a wrapper to render out a list of <Text> components. This is a simple example, but in real world I would have a complex <Header> component that I can pass in list of polymorphic link props and those get rendered somewhere deep in <Header> component tree.

The problem I have with the approach I tried in codesandbox example is that I make the <List> generic as well and it seems to me that depending on what as i use in the items prop all other items inherit that one generic. I can't figure out how each of the items in <List> items prop would act in "isolation" and only allow props that come from the tag defined in its own "as".

Would be very grateful if we can shoot some ideas if this is even possible.

Q: How to limit the Element Type

Is there a way we can limit the element type to for eg: Button and a?

Been trying to use this helper but I want to narrow it down in some scenarios like <Button as="a"> while disallowing <Button as="form> or any other element other than button | a.

Haven't been able to figure out how to do it yet. Any pointers would be greatly appreciated!

`onChange` error: Parameter 'event' implicitly has an 'any' type.

Hi,

I was following your tutorial

Everything went perfectly, but I'm facing one slight problem.

When I try to pass as="input" and use onChange={(event) => ...}

It throws error: Parameter 'event' implicitly has an 'any' type.

I thought I was doing something wrong,
so I cloned this repo to test. (used the 04.tsx example)

But got the same result.

image

However, it works if I do like this:
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.