Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUESTION] How do you implement custom components with Typescript + forwardRef? #881

Closed
calvinwyoung opened this issue Apr 29, 2020 · 3 comments · Fixed by #1213
Closed

Comments

@calvinwyoung
Copy link

I'm trying to implement a custom label component with Typescript, building on the <Label /> component that comes with theme-ui. My first attempt looks something like this:

export const CustomLabel = React.forwardRef<HTMLLabelElement, LabelProps>(
  (props , ref) => <Label ref={ref} {...props} />
);

I would've expected this to work, but instead I get the following error on ref={ref}:

Type 'string | ((instance: HTMLLabelElement | null) => void) | RefObject<HTMLLabelElement> | null' is not assignable to type '((instance: HTMLLabelElement | null) => void) | RefObject<HTMLLabelElement> | null | undefined'.
Type 'string' is not assignable to type '((instance: HTMLLabelElement | null) => void) | RefObject<HTMLLabelElement> | null | undefined'.

Am I doing something very silly here? Has anyone else run into a similar issue?

@flo-sch
Copy link
Collaborator

flo-sch commented Apr 30, 2020

I have similar issues.

I fixed the type issues by unshamelessly stealing the Assign and ForwardRef types from the @theme-ui/components conversion to TypeScript PR

(I am not good enough with TypeScript to understand them fully, so copypasting them makes me feel even worse but... At least it seems to work 🙈)

import React from 'react';
import { Text, TextProps } from '@theme-ui/components';

type Assign<T, U> = {
  [P in keyof (T & U)]: P extends keyof T
    ? T[P]
    : P extends keyof U
    ? U[P]
    : never
}

type ForwardRef<T, P> =
  React.ForwardRefExoticComponent<
    React.PropsWithoutRef<P>& React.RefAttributes<T>
  >

export interface ParagraphProps
  extends Assign<React.ComponentPropsWithRef<'p'>, TextProps> {};

export const Paragraph: ForwardRef<HTMLParagraphElement, ParagraphProps> =
  React.forwardRef((props, ref) => (
    <Text
      as="p"
      {...props}
      ref={ref}
    />
  ));

But I am facing various type issues as soon as I want to use variants, themeKey and sx props.
I would love to understand that and help documenting eventually?

@flo-sch
Copy link
Collaborator

flo-sch commented Apr 30, 2020

More specifically by "various issues": I am using Storybook to document components, so my issue may be related to that, but it's hard to figure out at the moment.

I use a "decorator" to wrap all components under a <ThemeProvider /> with the following theme:

import { merge } from 'theme-ui';
import { base } from '@theme-ui/presets';

export const theme = merge(base, {
  colors: {
    primary: '#3d5a80'
  },
  text: {
    primary: {
      color: 'primary',
      textTransform: 'uppercase'
    }
  }
});

Then my story is:

import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { Text } from 'theme-ui';
import { Paragraph } from './Paragraph.tsx';

# Text

<Meta title="Typography/Text" component={Text} />

<Preview>
  <Story name="Text">
    <Text as="p" variant="primary">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    </Text>
  </Story>
</Preview>

<Preview>
  <Story name="Paragraph">
    <Paragraph variant="primary">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</Paragraph>
  </Story>
</Preview>

The first one renders correctly, the second does not.

However, that component in a codesandbox works fine:
https://codesandbox.io/s/theme-ui-components-uozex

Just wondering if there are specific MDX or props forwarding requirements to use the theme...?

@mzabriskie
Copy link
Contributor

@calvinwyoung the types are incorrect in Theme UI. They are largely missing ComponentPropsWithRef. I have created #1213 to address the issue.

As a temporary workaround, I created a type helper that I have been using:

type BoxPropsWithRef<
    T extends React.ElementType,
    P extends BoxOwnProps = BoxOwnProps
> = React.ComponentPropsWithRef<T> & P

I can then type my custom override component:

import React, { forwardRef } from 'react'
import { Label as ThemeUILabel, LabelProps as ThemeUILabelProps } from 'theme-ui'

export type LabelProps = BoxPropsWithRef<'label', ThemeUILabelProps>

export const Label = forwardRef<HTMLLabelElement, LabelProps>(
    (props, ref) => <ThemeUILabel ref={ref} {...props} />
)

@calvinwyoung calvinwyoung changed the title [QUESTION] How do implement custom components with Typescript + forwardRef? [QUESTION] How do you implement custom components with Typescript + forwardRef? Oct 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants