kripod / react-polymorphic-box Goto Github PK
View Code? Open in Web Editor NEWBuilding blocks for strongly typed polymorphic components in React.
License: MIT License
Building blocks for strongly typed polymorphic components in React.
License: MIT License
The package is being compiled to ES2020 (see tsconfig.json) and modern JS features as arrow functions are not being transpiled.
As you can see in version 1.1.1 (https://unpkg.com/browse/[email protected]/dist-web/index.js) arrow functions are not transpiled.
For people giving support for older browsers (IE11, older versions of Edge, etc.) it woud be great to target ES5 instead of ES2020. The package is so small that it won't affect the size of it.
I can open a PR with this change.
PS: thanks for this package, I love it!
I just realized that PolymorphicComponent
is missing React's component attributes (defaultProps
, displayName
, etc.). One should probably add React.ComponentType
as an intersection type to the definition.
First off, thanks for a fantastic package! Long time happy user, first time bug reporter here :)
Updating TypeScript to 5.2.2 (possibly lower versions as well) causes/reveals a type error in the Box
component. My guess would be that TS has changes how it resolves the generics, but to be honest, I don't fully understand how this package works.
Take the example Code Sandbox and update the TypeScript version to 5.2.2 in package.json (it might require reloading the tab)
Here's a link to an updated Sandbox showing the error
Types should work
Box
component shows the following type error
Type '{ as: "h2"; style: { color: string | undefined; } & LibraryManagedAttributes<E, ComponentPropsWithRef<E>>[string]; } & Omit<...> & { ...; }' is not assignable to type 'IntrinsicAttributes & BoxOwnProps<"h2" & HeadingProps<E>["as"]> & Omit<LibraryManagedAttributes<"h2" & HeadingProps<E>["as"], ComponentPropsWithRef<...>>, "as">'.
Type '{ as: "h2"; style: { color: string | undefined; } & LibraryManagedAttributes<E, ComponentPropsWithRef<E>>[string]; } & Omit<...> & { ...; }' is not assignable to type 'Omit<LibraryManagedAttributes<"h2" & HeadingProps<E>["as"], ComponentPropsWithRef<"h2" & HeadingProps<E>["as"]>>, "as">'.ts(2322)
All / Code Sandbox
Creating polymorphic components generates TypeScript errors. It appears the TypeScript compiler doesn't like it when you pass in ...restProps
if it contains the as
prop. If you remove that it no longer complains so it appears there is some kind of mismatch for the as
type.
Create the Heading
component example from the README.
No TypeScript errors
Type '{ as: "h2"; style: { color: string | undefined; } & ComponentProps<E>[string]; } & Omit<HeadingProps<E>, "style" | "color">' is not assignable to type 'IntrinsicAttributes & BoxOwnProps<"h2" & HeadingProps<E>["as"]> & Omit<ComponentProps<"h2" & HeadingProps<E>["as"]>, "as">'.
Type '{ as: "h2"; style: { color: string | undefined; } & ComponentProps<E>[string]; } & Omit<HeadingProps<E>, "style" | "color">' is not assignable to type 'Omit<ComponentProps<"h2" & HeadingProps<E>["as"]>, "as">'.
I've got an error during jest test process when I test a component that import a component who use Box.
The problem is that this component is located in our private component library and the issue only happened in this particular case. If I directly test the component directly in the library there is no problem at all.
Here is my architecture :
Here is the error :
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
The react render works, the problem came only when using jest
To reproduce it you have to import a component from a library that use polymorphic.
Maybe the issue is the bundle generated
react-scripts: 3.4.1
jest: 24.9.0
@testing-library/react: 10.4.4
I can't create a codesandbox to reproduce this issue but if you have any question, please ask :)
I like this solution to the "as" prop issue, and attempted to use it, but am getting a type error that is pretty convoluted. I'm not sure how to diagnose it. Any ideas?
Here is a codesandbox:
https://codesandbox.io/s/naughty-jackson-f68fs
The expected behavior is that I should be able to define a polymorphic Button
without type errors.
This is the error we are seeing:
Type 'Pick<PolymorphicComponentProps<E, ButtonOwnProps>, "as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">>' is not assignable to type 'IntrinsicAttributes & BoxOwnProps<E> & Pick<LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, Exclude<...>>'.
Type 'Pick<PolymorphicComponentProps<E, ButtonOwnProps>, "as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">>' is not assignable to type 'Pick<LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">>'.
Type 'Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">' is not assignable to type '"as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">'.
Type 'keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>' is not assignable to type '"as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">'.
Type 'string | number | symbol' is not assignable to type '"as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">'.
Type 'string' is not assignable to type '"as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">'.
Type 'keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>' is not assignable to type '"as"'.
Type 'string | number | symbol' is not assignable to type '"as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">'.
Type 'string' is not assignable to type '"as" | Exclude<Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">, "variant">'.
Type 'Exclude<keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>, "as">' is not assignable to type '"as"'.
Type 'keyof LibraryManagedAttributes<E, ComponentPropsWithRef<E>>' is not assignable to type '"as"'.
Type 'string | number | symbol' is not assignable to type '"as"'.
Type 'string' is not assignable to type '"as"'.
Type 'string | number | symbol' is not assignable to type '"as"'.
Type 'string' is not assignable to type '"as"'.
Hi! First of all, thanks for the library, I spent quite a few hours thinking about how to solve this problem myself, and it's nice to see a community solution.
I tried out the library with styled-components and figured it would make sense to export a PolymorphicComponent
type as well as PolymorphicComponentProps
. A simple implementation would look like this:
export type PolymorphicComponent<P, D extends React.ElementType = "div"> = <
E extends React.ElementType = D
>(
props: PolymorphicComponentProps<E, P>
) => JSX.Element;
I found this to be especially handy when working with external libraries that already expose polymorphic components.
The Heading example from the README with styled-components would look like this:
import styled from "styled-components";
type HeadingProps = { color?: string };
const defaultElement = "h2";
const Heading: PolymorphicComponent<
HeadingProps,
typeof defaultElement // optional, defaults to "div"
> = styled(defaultElement)<HeadingProps>`
color: ${(props) => props.color};
`;
which makes the whole API a bit more concise since you don't have to manually merge props.
I have some time on my hands rn, so if you think it's a sensible addition I could make a pull request and add some docs.
Polymorphic box has partialized props if "as" component has unioned props.
import React from 'react';
import { Box } from 'react-polymorphic-box';
interface ITestA {
value: 'a';
}
interface ITestB {
value: 'b';
extra: string;
}
type Test = ITestA | ITestB;
const Test: React.FC<Test> = (props) => {
return <></>;
};
function App() {
return (
<>
<Test value="b" extra="extra" />
<Box as={Test} value="b" extra="extra" />
</>
);
}
Polymorphic box has correct props types.
Type '{ as: FC<Test>; value: "b"; extra: string; }' is not assignable to type 'IntrinsicAttributes & BoxOwnProps<FC<Test>> & Pick<PropsWithChildren<Test>, "value" | "children">'. Property 'extra' does not exist on type 'IntrinsicAttributes & BoxOwnProps<FC<Test>> & Pick<PropsWithChildren<Test>, "value" | "children">'
Hi I'm just wondering what the best way to approach building up a hierarchy of components would be. I.e. start off with the polymorphic box as the top level item and then compose this into a very basic "view" component and use this "view" component as the basis for all other components (in an approach similar to "styled system"). Would I be better creating my own polymorphic box for this root component?
Here's an approach I started to make:
interface ViewOwnProps {
px?: "0" | "1";
py?: "0" | "1";
}
export type ViewProps<E extends React.ElementType> = PolymorphicComponentProps<
E,
ViewOwnProps
>;
const viewDefaultProp = "div";
function View<E extends React.ElementType = typeof defaultElement>({
px,
py,
...restProps
}: ViewProps<E>): JSX.Element {
return (
<Box
as={viewDefaultProp}
{...restProps}
style={{ padding: `${px} ${py}` }}
/>
);
}
// Component-specific props should be specified separately
interface HeadingOwnProps extends ViewOwnProps {
color?: string;
}
// Merge own props with others inherited from the underlying element type
export type HeadingProps<
E extends React.ElementType
> = PolymorphicComponentProps<E, HeadingOwnProps>;
// An HTML tag or a different React component can be rendered by default
const defaultElement = "div";
function Heading<E extends React.ElementType = typeof defaultElement>({
color,
style,
...restProps
}: HeadingProps<E>): JSX.Element {
// The `as` prop may be overridden by the passed props
return (
<View as={defaultElement} style={{ color, ...style }} {...restProps} />
);
}
Here I create a heading that uses the view component and uses the various polymorphic box type utilities but also extends the heading own props from the view own props.
Hey @kripod
As you probably have seen I've been contributing to Stitches recently to help test it and make sure things are working as users would expect.
@christianalfoni has been working on improving the types and as part of the latest Stitches release, he upgraded to react-polymorphic-box v2.0.3
Since this upgrade, I'm unable to get my Next.js project to build correctly. Things were working fine before that. We suspect it's something to do with the new build strategy used in v2.
I've abstracted a reproducible branch in this repo which you're free to clone and test it out.
Do you think this could be related to the latest release?
Let me know if there's any way I can help.
Using displayName and propTypes on a forwardRef component shows error. I guess this is more of an issue with the example code than this library.
displayName and propTypes should be allowed.
displayName and propTypes are not allowed.
Function types (e.g. event handlers) are typed as any. I'm not convinced react-polymorphic-box
is at fault here but thought I'd raise it to discuss. In my example typescript seems to infer the correct type for the event handler (pictured below), however when implementing the handler it is not typed.
import React from 'react';
import { Box } from 'react-polymorphic-box';
const Test = () => (
<Box
as="li"
onMouseDown={(event) => {
// event is implicitly typed as any
}}
/>
);
The event handler should be typed as expected.
Event handlers seem to be typed as any.
Picking ref
from component's props produces an error. Picking it is necessary to assign proper type to innerRef
.
Error:
Warning: [object Object]: `ref` is not a prop. Trying to access it will result in `undefined` being returned.
If you need to access the same value within the child component, you should pass it as a different prop.
(https://fb.me/react-special-props)
My code is similar to example code from Readme.md
section on how to pass ref
:
export const Box = React.forwardRef(
<E extends React.ElementType = typeof defaultElement>(
{ ref, ...restProps }: BoxProps<E>,
innerRef: typeof ref
) => {
return <StyledBox ref={innerRef} as={defaultElement} {...restProps} />;
}
) as <E extends React.ElementType = typeof defaultElement>(
props: BoxProps<E>
) => JSX.Element;
innerRef
should be somehow typed without picking ref
from component's props
I am using Next.js 9.4.4
and React 16.13.1
Please see my tweet for further details.
Sandbox with all the information required: https://codesandbox.io/s/typescript-as-prop-with-react-polymorphic-box-vr269?file=/src/App.tsx
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.