Skip to content

Commit

Permalink
feat: support passing refs to intrinsic elements
Browse files Browse the repository at this point in the history
  • Loading branch information
jonlambert authored Apr 27, 2022
1 parent f2fb783 commit 57baa16
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 3 deletions.
20 changes: 19 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,36 @@ import React, {
createElement,
JSXElementConstructor,
ComponentProps,
forwardRef,
ForwardedRef,
RefCallback,
Ref,
} from 'react';

type IntrinsicElementKeys = keyof JSX.IntrinsicElements;

export function propose<
ComponentType extends JSXElementConstructor<any> | IntrinsicElementKeys,
OriginalProps extends ComponentProps<ComponentType>,
SuppliedProps extends Partial<OriginalProps>
SuppliedProps extends Partial<OriginalProps>,
RefType extends ComponentType extends IntrinsicElementKeys
?
| Ref<JSX.IntrinsicElements[ComponentType]>
| RefCallback<JSX.IntrinsicElements[ComponentType]>
: "Unable to pass a ref to a function component"
>(Component: ComponentType, props: SuppliedProps, displayName?: string) {
type FinalProps = Omit<OriginalProps, keyof SuppliedProps> &
Partial<SuppliedProps>;

if (typeof Component === 'string') {
const NewComponent = forwardRef<RefType, FinalProps>((p, ref) => {
const combinedProps = { ...props, ...p, ref };
return createElement(Component, combinedProps);
});
NewComponent.displayName = displayName;
return NewComponent;
}

const NewComponent: React.FC<FinalProps> = (p) => {
const combinedProps = { ...props, ...p };
return createElement(Component, combinedProps);
Expand Down
12 changes: 10 additions & 2 deletions src/propose.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'jest';
import '@testing-library/jest-dom/extend-expect';

import React from 'react';
import { render } from '@testing-library/react';
import React, { createRef, useRef } from 'react';
import { act, render } from '@testing-library/react';
import { propose } from '.';

describe('propose', () => {
Expand All @@ -28,6 +28,7 @@ describe('propose', () => {

const Decorated = propose(Base, { title });
const { getByRole, getByText } = render(<Decorated message={message} />);

expect(getByRole('heading')).toHaveTextContent(title);
expect(getByText(message)).toBeInTheDocument();
});
Expand Down Expand Up @@ -73,4 +74,11 @@ describe('propose', () => {
const Derived = propose(Base, { variant: 'primary' });
render(<Derived onClick={() => {}} variant="primary" />);
});

it('accepts and passes down ref', () => {
const Decorated = propose('button', {});
const ref = createRef<HTMLButtonElement>();
render(<Decorated ref={ref as any}>Hello, world</Decorated>);
expect(ref.current?.innerHTML).toEqual('Hello, world');
});
});

0 comments on commit 57baa16

Please sign in to comment.