Theme-ui: Type [...] is missing the following properties from type Pick<ButtonHTMLAttributes,[...]: css, sx TS2739

Created on 4 Jul 2020  Â·  6Comments  Â·  Source: system-ui/theme-ui

Describe the bug
When consuming this component:

type Props = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className">;

const Button = React.forwardRef<HTMLButtonElement, Props>((props) => {
  return <button {...props} />
});

resulting in type (as reported by VS Code on hover & by inspecting the compiled d.ts):

const Button: React.ForwardRefExoticComponent<
  Pick<
    ButtonHTMLAttributes<HTMLButtonElement>,
    | "autoFocus"
    | "disabled"
    | "form"
    | "formAction"
    | "formEncType"
    | "formMethod"
    | ... 259 more ...
    | "sx"
  > &
    React.RefAttributes<...>
>;

from another Theme UI-agnostic project it encounters the following type error:

Type '{ children: string; }' is missing the following properties from type 'Pick, "autoFocus" | "disabled" | "form" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | ... 258 more ... | "sx">': css, sx TS2739

To Reproduce

  1. Clone https://github.com/yuriybelike/repro__theme-ui__css_sx_props
  2. On components-library, run npm i
  3. On consumer, run npm i
  4. On consumer – _in order to see the TypeScript error_ – either:

    • open "src/App.tsx" on VS Code

    • run npm run start

Expected behavior

Consumer isn't affected by Theme UI related types on primitive JSX elements like <button>

Additional context

  • I was unable to reproduce the issue when removing Omit<> or forwardRef()
  • I understand this is partially TypeScript's issue, as it converts Omit<> into Pick<>
  • I've used tsdx for the library scaffolding, but I don't think it is related, as my original project is using create-react-library
  • On my original project the issue was still reproducible on TypeScript v3.8.3

Workaround

You can just omit both props like

type Props = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className" | "css" | "sx">;

but I find it far from ideal having to account for Theme UI specific props on such a primitive type as it is ButtonHTMLAttributes

types

Most helpful comment

@hasparus the more docs, the better, in my opinion. Feel free to add stuff as you go, even if it's WIP or something we don't link to

All 6 comments

but I find it far from ideal having to account for Theme UI specific props on such a primitive type as it is ButtonHTMLAttributes

I would be nice if TypeScript recognized sx and css prop only if jsx imported from Theme UI is set in the pragma comment for current file.

Unfortunately, JSX support in TypeScript isn't perfect. It doesn't work like this, and both Theme UI and Emotion inject its pragma supported prop into all React components.

Problem

I reproduced the problem.

  1. Our Omit is compiled to Pick. (Why? :o)
  2. Because theme-ui is not a dependency of consumer, we don't have sx prop (from Theme UI) nor css prop (from Emotion) types in it.
  3. So Pick<ButtonHTMLAttributes<HTMLButtonElement>, 'sx'> is unknown. sx was optional, but we don't know it in consumer.

I have two workarounds for you.

Workaround 1

Add theme-ui as peer dependency.

Theme UI with its 20.3kB gzipped (okay, it's treeshakable but still) may be too big to use as a hidden dependency. Due to component libraries hiding their dep on styling libraries, big applications often end up with multiple styling libs in the bundle (I once had styled-components, emotion and goober).

Since Theme UI uses React context for theming, the theme for your component library can be overriden by your user. I find it nice to be frank about it and advertise it as a feature instead of a surprising bug.

Both React context and JSX types make Theme UI hard to hide.

Workaround 2

Make sure Props are not inlined with interface keyword.

Type aliases can be inlined during the build.

We can use interface keyword and export our props. This is generally a good idea. They're public anyway and they can be accessed with React.ComponentProps<typeof Button>, but exporting ButtonProps for convenience is quite nice IMO.

import React, { ButtonHTMLAttributes } from 'react';

export interface ButtonProps
  extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {}

export const Button = React.forwardRef<HTMLButtonElement, Props>(props => {
  return <button {...props}
});

I'd prefer interface for library public API, because it introduces a named type (which won't get inlined), allows declaration merging, and error messages with them are more succint and thus less scary.

https://www.typescriptlang.org/docs/handbook/advanced-types.html#interfaces-vs-type-aliases

@mxstbr @jxnblk as Theme UI is being used for more component libraries, should the docs include _general not Theme UI specific_ advice for building component libraries? A page in Recipes?

@hasparus the more docs, the better, in my opinion. Feel free to add stuff as you go, even if it's WIP or something we don't link to

This is fixed as of 0.5 (which isn’t released as stable but available nonetheless). Can you test the alpha version?

(Closing as a discussion)

@lachlanjc Just tried it, it works beautifully 🎉

Thanks for your hard work, guys!

@lachlanjc what commit was the fix for this issue? Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

folz picture folz  Â·  3Comments

blummis picture blummis  Â·  4Comments

mxstbr picture mxstbr  Â·  3Comments

cwgw picture cwgw  Â·  3Comments

K-Kit picture K-Kit  Â·  4Comments