Skip to content

[labs/react] Issues using lit-labs wrappers in Next.js #2951

@WickyNilliams

Description

@WickyNilliams

Description

I have some react wrappers which i have generated from my lit components. These work great in a pure client-side react app. However when using in a next.js app i hit a number of a problems.

Elliot on slack asked me to raise an issue here. Even though i think some of it is an issue on the next.js side, it might help in tracking your next integration story.

Steps to Reproduce

  1. Write this code
import React from "react";
import dynamic from "next/dynamic";

// @org/react contains lit-labs/react wrappers...

// (1) ts error
const MyInput = dynamic(() => import("@org/react").then(({ Input }) => Input), {
  ssr: false,
});

export const Page = () => {
  const ref = React.useRef();

  // (2) ref issue
  const focusInput = () => ref.current.focus();

  return (
    <>
      <MyInput ref={ref} />
      <button onClick={focusInput}>Click me</button>
    </>
  );
};
  1. See this output...

(1) TS complains about the type of the react wrapper component. To fix this I had to cast to any and then back to my actual type. I created a helper function like:

type ComponentModule = typeof import("@org/react");

function createDynamic<
  K extends keyof ComponentsModule,
  T extends ComponentsModule[K]
>(component: K) {
  return dynamic(
    () => import("@org/react").then((module) => module[component] as any),
    { ssr: false }
  ) as T;
}

const MyInput = createDynamic("Input");

(2) when clicking the button you will get an error because the ref is not pointing to the underlying custom-element as you would expect, but rather (i think) the return type of dynamic.

I was able to work around this by adding some indirection and threading through a ref myself:

type ComponentsModule = typeof import("@org/react")

function createDynamic<
  K extends keyof ComponentsModule, 
  T extends ComponentsModule[K]
>(component: K) {
  const Wrapped = dynamic(
    () =>
      import("@org/react").then(module => {
        const Component = module[component] as any

        // @ts-expect-error innerRef is unknown
        const Wrapped = ({ innerRef, ...props }) => <Component ref={innerRef} {...props} />
        Wrapped.displayName = `Wrapped${component}`

        return Wrapped as any
      }),
    { ssr: false }
  )

  // @ts-expect-error innerRef is unknown
  const Forwarded = React.forwardRef((props, ref) => <Wrapped {...props} innerRef={ref} />)
  Forwarded.displayName = `Forwarded${component}`

  return Forwarded as T
}

const MyInput = createDynamic("Input")

This is of course, horrible. I tried various other combinations of forwardRef and/or custom ref properties, but i coudln't get it to work. I even had to suppress some TS issues.

This got so complex, i had to add next.js specific integration to my react package as it would be very difficult to document these issues in such a way that devs consuming the components would understand. It was easier to just give them something they can import for next.js only

Expected Results

(1) no TS errors
(2) refs should work out of the box

Metadata

Metadata

Type

No type

Projects

Status

✅ Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions