diff --git a/.changeset/fix-with-key-attribute.md b/.changeset/fix-with-key-attribute.md
new file mode 100644
index 000000000..c2ad6c6ec
--- /dev/null
+++ b/.changeset/fix-with-key-attribute.md
@@ -0,0 +1,5 @@
+---
+'@suspensive/react': patch
+---
+
+fix(react): allow the React-reserved `key` attribute on components created by `with`
diff --git a/packages/react/src/ClientOnly.test-d.tsx b/packages/react/src/ClientOnly.test-d.tsx
new file mode 100644
index 000000000..018d58a9e
--- /dev/null
+++ b/packages/react/src/ClientOnly.test-d.tsx
@@ -0,0 +1,22 @@
+import { ClientOnly } from './ClientOnly'
+
+describe('', () => {
+ describe('ClientOnly.with', () => {
+ it('should accept the React-reserved key attribute on components wrapped without props', () => {
+ const Wrapped = ClientOnly.with({ fallback: null }, () => <>>)
+
+ expectTypeOf([, ]).toEqualTypeOf>()
+
+ assertType(
+ // @ts-expect-error arbitrary props should still be rejected
+
+ )
+ })
+
+ it('should accept the key attribute along with inferred props', () => {
+ const Wrapped = ClientOnly.with({ fallback: null }, ({ text }: { text: string }) => <>{text}>)
+
+ expectTypeOf().toEqualTypeOf()
+ })
+ })
+})
diff --git a/packages/react/src/ClientOnly.tsx b/packages/react/src/ClientOnly.tsx
index 31ca1b512..8ee7dd793 100644
--- a/packages/react/src/ClientOnly.tsx
+++ b/packages/react/src/ClientOnly.tsx
@@ -17,7 +17,7 @@ export const ClientOnly = Object.assign(
({ children, fallback }: ClientOnlyProps) => <>{useIsClient() ? children : fallback}>,
{
displayName: 'ClientOnly',
- with: = Record>(
+ with: = Record>(
clientOnlyProps: PropsWithoutChildren,
Component: ComponentType
) =>
diff --git a/packages/react/src/Delay.test-d.tsx b/packages/react/src/Delay.test-d.tsx
index 1f367bbf4..e4574da4f 100644
--- a/packages/react/src/Delay.test-d.tsx
+++ b/packages/react/src/Delay.test-d.tsx
@@ -39,4 +39,23 @@ describe('', () => {
).toEqualTypeOf()
})
+
+ describe('Delay.with', () => {
+ it('should accept the React-reserved key attribute on components wrapped without props', () => {
+ const Wrapped = Delay.with({ ms: 1000 }, () => <>>)
+
+ expectTypeOf([, ]).toEqualTypeOf>()
+
+ assertType(
+ // @ts-expect-error arbitrary props should still be rejected
+
+ )
+ })
+
+ it('should accept the key attribute along with inferred props', () => {
+ const Wrapped = Delay.with({ ms: 1000 }, ({ text }: { text: string }) => <>{text}>)
+
+ expectTypeOf().toEqualTypeOf()
+ })
+ })
})
diff --git a/packages/react/src/Delay.tsx b/packages/react/src/Delay.tsx
index 500d3c437..0fefa8069 100644
--- a/packages/react/src/Delay.tsx
+++ b/packages/react/src/Delay.tsx
@@ -49,7 +49,7 @@ export const Delay = Object.assign(
},
{
displayName: 'Delay',
- with: = Record>(
+ with: = Record>(
delayProps: PropsWithoutChildren,
Component: ComponentType
) =>
diff --git a/packages/react/src/ErrorBoundary.test-d.tsx b/packages/react/src/ErrorBoundary.test-d.tsx
index c7b623110..eb25fada8 100644
--- a/packages/react/src/ErrorBoundary.test-d.tsx
+++ b/packages/react/src/ErrorBoundary.test-d.tsx
@@ -1230,4 +1230,26 @@ describe('', () => {
expectTypeOf(example).toEqualTypeOf()
})
})
+
+ describe('ErrorBoundary.with key attribute', () => {
+ it('should accept the React-reserved key attribute on components wrapped without props', () => {
+ const Wrapped = ErrorBoundary.with({ fallback: ({ error }) => <>{error.message}> }, () => <>>)
+
+ expectTypeOf([, ]).toEqualTypeOf>()
+
+ assertType(
+ // @ts-expect-error arbitrary props should still be rejected
+
+ )
+ })
+
+ it('should accept the key attribute along with inferred props', () => {
+ const Wrapped = ErrorBoundary.with(
+ { fallback: ({ error }) => <>{error.message}> },
+ ({ text }: { text: string }) => <>{text}>
+ )
+
+ expectTypeOf().toEqualTypeOf()
+ })
+ })
})
diff --git a/packages/react/src/ErrorBoundary.tsx b/packages/react/src/ErrorBoundary.tsx
index 538a4a880..c0fdb27eb 100644
--- a/packages/react/src/ErrorBoundary.tsx
+++ b/packages/react/src/ErrorBoundary.tsx
@@ -263,7 +263,7 @@ export const ErrorBoundary = Object.assign(
{
displayName: 'ErrorBoundary',
with: <
- TProps extends ComponentProps = Record,
+ TProps extends ComponentProps = Record,
TShouldCatch extends ShouldCatch = ShouldCatch,
>(
errorBoundaryProps: PropsWithoutChildren>,
diff --git a/packages/react/src/ErrorBoundaryGroup.test-d.tsx b/packages/react/src/ErrorBoundaryGroup.test-d.tsx
index 91ce4f867..80600bb23 100644
--- a/packages/react/src/ErrorBoundaryGroup.test-d.tsx
+++ b/packages/react/src/ErrorBoundaryGroup.test-d.tsx
@@ -10,4 +10,23 @@ describe('ErrorBoundaryGroup', () => {
{() => <>>}
).not.toEqualTypeOf()
})
+
+ describe('ErrorBoundaryGroup.with', () => {
+ it('should accept the React-reserved key attribute on components wrapped without props', () => {
+ const Wrapped = ErrorBoundaryGroup.with({}, () => <>>)
+
+ expectTypeOf([, ]).toEqualTypeOf>()
+
+ assertType(
+ // @ts-expect-error arbitrary props should still be rejected
+
+ )
+ })
+
+ it('should accept the key attribute along with inferred props', () => {
+ const Wrapped = ErrorBoundaryGroup.with({}, ({ text }: { text: string }) => <>{text}>)
+
+ expectTypeOf().toEqualTypeOf()
+ })
+ })
})
diff --git a/packages/react/src/ErrorBoundaryGroup.tsx b/packages/react/src/ErrorBoundaryGroup.tsx
index fae84dc4c..4e3f1c736 100644
--- a/packages/react/src/ErrorBoundaryGroup.tsx
+++ b/packages/react/src/ErrorBoundaryGroup.tsx
@@ -54,7 +54,7 @@ export const ErrorBoundaryGroup = Object.assign(
},
{
displayName: 'ErrorBoundaryGroup',
- with: = Record>(
+ with: = Record>(
errorBoundaryGroupProps: PropsWithoutChildren,
Component: ComponentType
) =>
diff --git a/packages/react/src/Suspense.test-d.tsx b/packages/react/src/Suspense.test-d.tsx
new file mode 100644
index 000000000..3a817a888
--- /dev/null
+++ b/packages/react/src/Suspense.test-d.tsx
@@ -0,0 +1,22 @@
+import { Suspense } from './Suspense'
+
+describe('', () => {
+ describe('Suspense.with', () => {
+ it('should accept the React-reserved key attribute on components wrapped without props', () => {
+ const Wrapped = Suspense.with({ fallback: null }, () => <>>)
+
+ expectTypeOf([, ]).toEqualTypeOf>()
+
+ assertType(
+ // @ts-expect-error arbitrary props should still be rejected
+
+ )
+ })
+
+ it('should accept the key attribute along with inferred props', () => {
+ const Wrapped = Suspense.with({ fallback: null }, ({ text }: { text: string }) => <>{text}>)
+
+ expectTypeOf().toEqualTypeOf()
+ })
+ })
+})
diff --git a/packages/react/src/Suspense.tsx b/packages/react/src/Suspense.tsx
index b2712dca4..f8c5796fb 100644
--- a/packages/react/src/Suspense.tsx
+++ b/packages/react/src/Suspense.tsx
@@ -31,7 +31,7 @@ export const Suspense = Object.assign(
},
{
displayName: 'Suspense',
- with: = Record>(
+ with: = Record>(
suspenseProps: PropsWithoutChildren,
Component: ComponentType
) =>