next-superjson-plugin
next-superjson-plugin copied to clipboard
Each child in a list should have a unique "key" prop.
Verify Next.js canary release
- [X] I verified that the issue exists in the latest Next.js canary release
Describe the bug
I see the Each child in a list should have a unique "key" prop.
warning message on the client side on multiple occassions, basically every
I figured out, that is, because I'm feeding the data to the react context api.
<DocumentListContextProvider
documents={documents}
data-superjson
>
<div />
<SomeServerComponentSvg />
<SomeClientComponentAccessingContext />
</DocumentListContextProvider>
Which components are affected? Apparently the warning appears for every node where there are more than 1 children. That goes for normal components as well as plain jsx.
A full error log:
hydration-error-info.js?7847:27 Warning: Each child in a list should have a unique "key" prop. See https://reactjs.org/link/warning-keys for more information.
at div
at DocumentListContextProvider (webpack-internal:///(app-client)/./app/(index)/documents/document-list-context.tsx:25:11)
at WithSuperJSON (webpack-internal:///(app-client)/../../node_modules/next-superjson-plugin/dist/tools.js:153:55)
at SuperJSONComponent (webpack-internal:///(app-client)/../../node_modules/next-superjson-plugin/dist/client.js:18:24)
at ScrollAndFocusHandler (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:153:1)
at InnerLayoutRouter (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:195:11)
at RedirectErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:361:9)
at RedirectBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:368:11)
at NotFoundBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:404:11)
at LoadingBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:317:11)
at ErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/error-boundary.js:72:11)
at RenderFromTemplateContext (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/render-from-template-context.js:12:34)
at OuterLayoutRouter (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:23:11)
at div
at ScrollAndFocusHandler (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:153:1)
at InnerLayoutRouter (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:195:11)
at RedirectErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:361:9)
at RedirectBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:368:11)
at NotFoundBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:404:11)
at LoadingBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:317:11)
at ErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/error-boundary.js:72:11)
at RenderFromTemplateContext (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/render-from-template-context.js:12:34)
at OuterLayoutRouter (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:23:11)
at div
at div
at ScrollAndFocusHandler (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:153:1)
at InnerLayoutRouter (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:195:11)
at RedirectErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:361:9)
at RedirectBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:368:11)
at NotFoundErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:397:9)
at NotFoundBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:404:11)
at LoadingBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:317:11)
at ErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/error-boundary.js:72:11)
at RenderFromTemplateContext (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/render-from-template-context.js:12:34)
at OuterLayoutRouter (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/layout-router.js:23:11)
at body
at html
at ReactDevOverlay (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:61:9)
at HotReload (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:20:11)
at Router (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/app-router.js:48:11)
at ErrorBoundaryHandler (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/error-boundary.js:59:9)
at ErrorBoundary (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/error-boundary.js:72:11)
at AppRouter (webpack-internal:///(app-client)/../../node_modules/next/dist/client/components/app-router.js:24:13)
at ServerRoot (webpack-internal:///(app-client)/../../node_modules/next/dist/client/app-index.js:147:11)
at RSCComponent
at Root (webpack-internal:///(app-client)/../../node_modules/next/dist/client/app-index.js:164:11)
I am using the 13.2.4-canary.8
version of next.
Expected behavior
I expect to see no warning about missing unique key props
Reproduction link
No response
Version
0.5.6
Config
module.exports = {
reactStrictMode: true,
experimental: {
appDir: true,
swcPlugins: [
[ 'next-superjson-plugin', {} ]
]
},
async redirects() {
return [
{
source: '/',
destination: '/documents',
permanent: true,
},
]
},
transpilePackages: ['ui'],
};
Additional context
File
document-list-context.tsx
'use client';
import type { Dispatch, ReactNode } from 'react';
import { createContext, useReducer } from 'react';
import type { DocumentRevisionModel } from 'api-client';
export enum DocumentListContextActionKind {
SetDocuments = 'setDocuments',
AddDocument = 'addDocument',
}
export type DocumentListContextAction =
| {
type: DocumentListContextActionKind.SetDocuments;
documents: Array<DocumentRevisionModel>;
}
| {
type: DocumentListContextActionKind.AddDocument;
document: DocumentRevisionModel;
};
export interface DocumentListContextState {
documents: Array<DocumentRevisionModel>;
}
export interface DocumentListContextInterface extends DocumentListContextState {
documents: Array<DocumentRevisionModel>;
dispatch: Dispatch<DocumentListContextAction>;
}
export const DocumentListContext = createContext<DocumentListContextInterface>({
documents: [],
dispatch: () => {
throw new Error('DocumentListContext not initialized');
},
});
interface DocumentListContextProviderProps {
children: ReactNode;
initialDocuments?: Array<DocumentRevisionModel>;
}
export function DocumentListContextProvider({
children,
initialDocuments = [],
}: DocumentListContextProviderProps) {
const [state, dispatch] = useReducer(
(state: DocumentListContextState, action: DocumentListContextAction) => {
switch (action.type) {
case DocumentListContextActionKind.SetDocuments:
return {
...state,
documents: action.documents,
};
case DocumentListContextActionKind.AddDocument:
return {
...state,
documents: [...state.documents, action.document],
};
default:
return state;
}
},
{
documents: initialDocuments,
},
);
return (
<DocumentListContext.Provider
value={{
documents: state.documents,
dispatch,
}}
>
{children}
</DocumentListContext.Provider>
);
}
File
page.tsx
export default async function Page() {
const apiConfiguration = createConfiguration({
baseServer: new ServerConfiguration('http://localhost:3001', {}),
});
const documentsApi = new DocumentsApi(apiConfiguration);
const documents = await documentsApi.documentsControllerList();
return (
<DocumentListContextProvider
initialDocuments={documents.documents.map((document) => ({
...document,
}))}
data-superjson
>
{/* both will warn on this layer*/}
<div />
<div>
<div>this div will not warn because there is only one child</div>
</div>
</DocumentListContextProvider>
);
}
```
Im on next 13.5.3
and next-superjson-plugin 0.5.9
, bug is still alive.
For me it's happening even with just 1 child. Notice, that ProductDetails
is a server component, while ProductProvider
(ofc) is not.
<ProductProvider value={state} data-superjson>
<ProductDetails />
</ProductProvider>
observed on [email protected]
, [email protected]
A workaround that worked for me at the moment is wrapping the values passed in a "wrapper" using the class-transformer
.
import { instanceToPlain } from 'class-transformer';
export function superjsonWrapper<T extends object>(obj: T): T {
return instanceToPlain(obj) as T;
}
That seems to work for me!
experience the exact same issue as described and have no idea how to fix it. For now, I don't use the plugin and simply ignore the warnings about dates passed around between server and client components. 🤷🏻
The solution proposed by @AmazingTurtle sadly didn't work for me.