From db46f826c84d4c1d8f24a6617aae408c6f903748 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Tue, 25 Jan 2022 09:55:58 -1000 Subject: [PATCH 1/2] fix: automatically assign keys to custom `` components --- src/PrismicRichText.tsx | 18 ++++++++++++++++- test/PrismicRichText.test.tsx | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/PrismicRichText.tsx b/src/PrismicRichText.tsx index 9a3083e..92ce9cc 100644 --- a/src/PrismicRichText.tsx +++ b/src/PrismicRichText.tsx @@ -240,7 +240,23 @@ export const PrismicRichText = ( defaultSerializer, ); - const serialized = prismicR.serialize(props.field, serializer); + // The serializer is wrapped in a higher-order function + // to automatically apply a key to React Elements if + // one is not already given. + const serialized = prismicR.serialize( + props.field, + (type, node, text, children, key) => { + const result = serializer(type, node, text, children, key); + + if (React.isValidElement(result)) { + return React.cloneElement(result, { + key: result.key || key, + }); + } else { + return result; + } + }, + ); return <>{serialized}; } diff --git a/test/PrismicRichText.test.tsx b/test/PrismicRichText.test.tsx index cfaa2b2..0537e7c 100644 --- a/test/PrismicRichText.test.tsx +++ b/test/PrismicRichText.test.tsx @@ -1,6 +1,7 @@ import test from "ava"; import * as prismicT from "@prismicio/types"; import * as React from "react"; +import * as sinon from "sinon"; import { renderJSON } from "./__testutils__/renderJSON"; @@ -583,3 +584,39 @@ test("components given to components prop overrides components given to PrismicP t.deepEqual(actual, expected); }); + +// This test spies on `console.log()`. As a result, it must be run serially to +// avoid affecting other tests. +test.serial("keys are automatically applied to custom components", (t) => { + const field: prismicT.RichTextField = [ + { + type: prismicT.RichTextNodeType.heading1, + text: "heading1", + spans: [], + }, + { + type: prismicT.RichTextNodeType.paragraph, + text: "paragraph", + spans: [], + }, + ]; + + const consoleErrorSpy = sinon.stub(console, "error"); + consoleErrorSpy.callsFake(() => { + // no-op + }); + + renderJSON( +

{children}

, + paragraph: ({ children }) =>

{children}

, + }} + />, + ); + + t.false(consoleErrorSpy.calledWith(sinon.match(/unique "key"/))); + + consoleErrorSpy.restore(); +}); From 648cb87a188a8d5845b8267174cd71cb9e8cae30 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Tue, 25 Jan 2022 13:49:17 -1000 Subject: [PATCH 2/2] refactor: only call `React.cloneElement` if necessary --- src/PrismicRichText.tsx | 21 +++++++++------------ test/PrismicRichText.test.tsx | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/PrismicRichText.tsx b/src/PrismicRichText.tsx index 92ce9cc..ba3e216 100644 --- a/src/PrismicRichText.tsx +++ b/src/PrismicRichText.tsx @@ -224,11 +224,6 @@ export const PrismicRichText = ( return null; } else { const linkResolver = props.linkResolver || context.linkResolver; - const defaultSerializer = createDefaultSerializer({ - linkResolver, - internalLinkComponent: props.internalLinkComponent, - externalLinkComponent: props.externalLinkComponent, - }); const serializer = prismicR.composeSerializers( typeof props.components === "object" @@ -237,21 +232,23 @@ export const PrismicRichText = ( typeof context.richTextComponents === "object" ? prismicR.wrapMapSerializer(context.richTextComponents) : context.richTextComponents, - defaultSerializer, + createDefaultSerializer({ + linkResolver, + internalLinkComponent: props.internalLinkComponent, + externalLinkComponent: props.externalLinkComponent, + }), ); // The serializer is wrapped in a higher-order function - // to automatically apply a key to React Elements if - // one is not already given. + // that automatically applies a key to React Elements + // if one is not already given. const serialized = prismicR.serialize( props.field, (type, node, text, children, key) => { const result = serializer(type, node, text, children, key); - if (React.isValidElement(result)) { - return React.cloneElement(result, { - key: result.key || key, - }); + if (React.isValidElement(result) && result.key == null) { + return React.cloneElement(result, { key }); } else { return result; } diff --git a/test/PrismicRichText.test.tsx b/test/PrismicRichText.test.tsx index 0537e7c..523c178 100644 --- a/test/PrismicRichText.test.tsx +++ b/test/PrismicRichText.test.tsx @@ -585,8 +585,8 @@ test("components given to components prop overrides components given to PrismicP t.deepEqual(actual, expected); }); -// This test spies on `console.log()`. As a result, it must be run serially to -// avoid affecting other tests. +// This test spies on `console.error()`. As a result, it must be run serially +// to avoid affecting other tests. test.serial("keys are automatically applied to custom components", (t) => { const field: prismicT.RichTextField = [ {