From 190853b4ff271d5955648986e72a9fbd2d008b98 Mon Sep 17 00:00:00 2001 From: Li Weinan Date: Wed, 1 Nov 2017 05:52:51 +0800 Subject: [PATCH] Support de-deduping head tags by setting key (#3170) * Support de-deduping head tags by setting key * move dedupe logic to `unique` function * fix head tag deduping logic * remove console.log * use `toContain` assertions * update de-duping head tags section in README --- lib/head.js | 5 +++++ readme.md | 20 ++++++++++++++++++++ server/document.js | 2 +- test/integration/basic/pages/head.js | 12 ++++++++++++ test/integration/basic/test/rendering.js | 10 ++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/head.js b/lib/head.js index 52c56d6e..34733320 100644 --- a/lib/head.js +++ b/lib/head.js @@ -48,11 +48,16 @@ const METATYPES = ['name', 'httpEquiv', 'charSet', 'itemProp', 'property'] // which shouldn't be duplicated, like . function unique () { + const keys = new Set() const tags = new Set() const metaTypes = new Set() const metaCategories = {} return (h) => { + if (h.key) { + if (keys.has(h.key)) return false + keys.add(h.key) + } switch (h.type) { case 'title': case 'base': diff --git a/readme.md b/readme.md index 9b895cef..520d0785 100644 --- a/readme.md +++ b/readme.md @@ -197,6 +197,26 @@ export default () => </div> ``` +To avoid duplicate tags in your `<head>` you can use the `key` property, which will make sure the tag is only rendered once: + +```jsx +import Head from 'next/head' +export default () => ( + <div> + <Head> + <title>My page title + + + + + +

Hello world!

+ +) +``` + +In this case only the second `` is rendered. + _Note: The contents of `` get cleared upon unmounting the component, so make sure each page completely defines what it needs in ``, without making assumptions about what other pages added_ ### Fetching data and component lifecycle diff --git a/server/document.js b/server/document.js index c33ffc9b..9af596c2 100644 --- a/server/document.js +++ b/server/document.js @@ -88,7 +88,7 @@ export class Head extends Component { {this.getPreloadDynamicChunks()} {this.getPreloadMainLinks()} - {(head || []).map((h, i) => React.cloneElement(h, { key: i }))} + {(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))} {styles || null} {this.props.children} diff --git a/test/integration/basic/pages/head.js b/test/integration/basic/pages/head.js index a9b13c87..95463be1 100644 --- a/test/integration/basic/pages/head.js +++ b/test/integration/basic/pages/head.js @@ -3,8 +3,20 @@ import Head from 'next/head' export default () =>
+ {/* this will not render */} + + {/* this will get rendered */} + + + {/* the following 2 links tag will be rendered both */} + + + + {/* only one tag will be rendered as they have the same key */} + +

I can haz meta tags

diff --git a/test/integration/basic/test/rendering.js b/test/integration/basic/test/rendering.js index 4ea7a37c..21a85c6e 100644 --- a/test/integration/basic/test/rendering.js +++ b/test/integration/basic/test/rendering.js @@ -28,6 +28,16 @@ export default function ({ app }, suiteName, render) { expect(html.includes('I can haz meta tags')).toBeTruthy() }) + test('header helper dedupes tags', async () => { + const html = await (render('/head')) + expect(html).toContain('') + expect(html).not.toContain('') + expect(html).toContain('') + expect(html).toContain('') + expect(html).toContain('') + expect(html).not.toContain('') + }) + test('renders styled jsx', async () => { const $ = await get$('/styled-jsx') const styleId = $('#blue-box').attr('class')