Skip to content

Commit

Permalink
fix: removed hydration warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
meza authored Mar 10, 2023
2 parents 5c704ff + 3faefe7 commit 06fccb4
Show file tree
Hide file tree
Showing 17 changed files with 80 additions and 130 deletions.
2 changes: 1 addition & 1 deletion docs/adr/.adr-sequence.lock
Original file line number Diff line number Diff line change
@@ -1 +1 @@
10
11
39 changes: 39 additions & 0 deletions docs/adr/0011-suppressing-hydration-warnings-on-scripts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 11. Suppressing Hydration Warnings on Scripts

Date: 2023-03-10

## Status

Accepted

## Context

![Hydration Warning](../images/hydration.png)

When using the app in development mode, the console is filled with hydration warnings.
This usually is because the server and client render different content.
This is not a problem in production, but it is annoying in development.

In this case however the error stems from a change in the [DOM specification](https://github.com/whatwg/html/pull/2373)
which aims to reduce attack vectors by removing the nonce attribute from elements.

This inadvertently breaks causes differences between the server and the client side and React freaks out.

There is an [outstanding issue](https://github.com/facebook/react/issues/26028) to look into the problem.

We had
a [solution](https://github.com/meza/trance-stack/blob/12907646f56f58c604a1eaeae68f1549d9b570ec/src/components/StaticContent/StaticContent.tsx)
for this that was taken from a [stackoverflow post](https://stackoverflow.com/a/60365819).

This solution worked, but it seemed to be introducing a lot of "magic" to mask the problem.

## Decision

After some discussion, we decided to do the same
thing [Remix does](https://github.com/remix-run/remix/blob/8a07860f3eea1cbfc2c67d0018cb9ac76c6df9d1/packages/remix-react/scroll-restoration.tsx#L70)
and suppress the warnings at the script level without any additional magic.

## Consequences

- We will no longer see hydration warnings in development mode.
- We have to keep an eye on the React issue to see if they come up with a better solution.
4 changes: 3 additions & 1 deletion docs/adr/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
- [7. ARC takes a backseat](0007-arc-takes-a-backseat.md)
- [8. Use AWS CDK for deployments](0008-use-aws-cdk-for-deployments.md)
- [9. No more need to bundle environment variables](0009-no-more-need-to-bundle-environment-variables.md)
- [10. Authentication is done by Auth0](0010-authentication-is-done-by-auth0.md)
- [10. Auth by Auth0](0010-authentication-is-done-by-auth0.md)
- [11. Suppressing Hydration Warnings on Scripts](0011-suppressing-hydration-warnings-on-scripts.md)
- [12. Suppressing Hydration Warnings on Scripts](0012-suppressing-hydration-warnings-on-scripts.md)
Binary file added docs/images/hydration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/is-ci": "^3.0.0",
"@types/jsdom": "^21.1.0",
"@types/mixpanel-browser": "^2.38.1",
"@types/node": "^18.13.0",
"@types/react": "^18.0.25",
Expand Down
8 changes: 4 additions & 4 deletions src/components/CookieYes/CookieYes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ describe('CookieYes', () => {
it('Renders with the correct values', () => {
// eslint-disable-next-line new-cap
expect(CookieYes({ isProduction: true, token: '123' })).toMatchInlineSnapshot(`
<StaticContent
element="script"
<script
id="cookieyes"
src="https://cdn-cookieyes.com/client_data/123/script.js"
suppressHydrationWarning={true}
type="text/javascript"
/>
`);
// eslint-disable-next-line new-cap
expect(CookieYes({ isProduction: true, token: 'abc', nonce: 'a-nonce' })).toMatchInlineSnapshot(`
<StaticContent
element="script"
<script
id="cookieyes"
nonce="a-nonce"
src="https://cdn-cookieyes.com/client_data/abc/script.js"
suppressHydrationWarning={true}
type="text/javascript"
/>
`);
Expand Down
8 changes: 3 additions & 5 deletions src/components/CookieYes/CookieYes.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { StaticContent } from '~/components/StaticContent';

export const CookieYes = (props: { isProduction: boolean, token: string; nonce?: string; }) => {
if (props.isProduction) {
return (
<StaticContent
element={'script'}
<script
suppressHydrationWarning
nonce={props.nonce}
id={'cookieyes'}
type={'text/javascript'}
src={`https://cdn-cookieyes.com/client_data/${props.token}/script.js`}/>
src={`https://cdn-cookieyes.com/client_data/${props.token}/script.js`} />
);
}
return null;
Expand Down
6 changes: 2 additions & 4 deletions src/components/ExposeAppConfig/ExposeAppConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { StaticContent } from '~/components/StaticContent';

export const ExposeAppConfig = (props: { appConfig: AppConfig, nonce?: string }) => {
return (
<StaticContent
element={'script'}
<script
id={'app-config'}
suppressHydrationWarning
nonce={props.nonce}
dangerouslySetInnerHTML={{
__html: `window.appConfig = ${JSON.stringify(props.appConfig)}` //typed in the ../types/global.d.ts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Vitest Snapshot v1

exports[`ExposeAppConfig > can expose the app config correctly 1`] = `
<StaticContent
<script
dangerouslySetInnerHTML={
{
"__html": "window.appConfig = {\\"hotjarId\\":\\"a-hotjar-id\\",\\"googleAnalyticsId\\":\\"ga-id\\",\\"mixpanelToken\\":\\"a-mixpanel-token\\",\\"visitorId\\":\\"a-visitor-id\\",\\"isProduction\\":true,\\"mixpanelApi\\":\\"a-mixpanel-api\\",\\"splitToken\\":\\"a-split-token\\",\\"cookieYesToken\\":\\"a-cookieyes-token\\",\\"version\\":\\"0.0.0-dev\\",\\"sentryDsn\\":\\"a-sentry-dsn\\"}",
}
}
element="script"
id="app-config"
suppressHydrationWarning={true}
/>
`;
16 changes: 8 additions & 8 deletions src/components/GoogleAnalytics/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ describe('Google Analytics 4', () => {
nonce: 'a-nonce'
})).toMatchInlineSnapshot(`
<React.Fragment>
<StaticContent
<script
async={true}
element="script"
id="gtm"
nonce="a-nonce"
src="https://www.googletagmanager.com/gtag/js?id=123"
suppressHydrationWarning={true}
/>
<StaticContent
<script
dangerouslySetInnerHTML={
{
"__html": "window.dataLayer = window.dataLayer || [];
Expand All @@ -30,9 +30,9 @@ describe('Google Analytics 4', () => {
gtag('config', '123', { user_id: 'abc' });",
}
}
element="script"
id="google-analytics"
nonce="a-nonce"
suppressHydrationWarning={true}
/>
</React.Fragment>
`);
Expand All @@ -44,14 +44,14 @@ describe('Google Analytics 4', () => {
nonce: 'a-nonce2'
})).toMatchInlineSnapshot(`
<React.Fragment>
<StaticContent
<script
async={true}
element="script"
id="gtm"
nonce="a-nonce2"
src="https://www.googletagmanager.com/gtag/js?id=triangulation"
suppressHydrationWarning={true}
/>
<StaticContent
<script
dangerouslySetInnerHTML={
{
"__html": "window.dataLayer = window.dataLayer || [];
Expand All @@ -60,9 +60,9 @@ describe('Google Analytics 4', () => {
gtag('config', 'triangulation', { user_id: 'abc123' });",
}
}
element="script"
id="google-analytics"
nonce="a-nonce2"
suppressHydrationWarning={true}
/>
</React.Fragment>
`);
Expand Down
12 changes: 5 additions & 7 deletions src/components/GoogleAnalytics/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { StaticContent } from '~/components/StaticContent';

interface GoogleAnalyticsProps {
googleAnalyticsId: string;
visitorId: string;
Expand All @@ -9,23 +7,23 @@ interface GoogleAnalyticsProps {
export const GoogleAnalytics = (props: GoogleAnalyticsProps) => {
return (
<>
<StaticContent
element={'script'}
<script
id={'gtm'}
async
suppressHydrationWarning
nonce={props.nonce}
src={`https://www.googletagmanager.com/gtag/js?id=${props.googleAnalyticsId}`}
/>
<StaticContent
element={'script'}
<script
id={'google-analytics'}
suppressHydrationWarning
nonce={props.nonce}
dangerouslySetInnerHTML={{
__html: `window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${props.googleAnalyticsId}', { user_id: '${props.visitorId}' });`
}}/>
}} />
</>
);
};
16 changes: 8 additions & 8 deletions src/components/Hotjar/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Hotjar', () => {
nonce: 'a-nonce'
})).toMatchInlineSnapshot(`
<React.Fragment>
<StaticContent
<script
async={true}
dangerouslySetInnerHTML={
{
Expand All @@ -26,16 +26,16 @@ describe('Hotjar', () => {
",
}
}
element="script"
id="hotjar-init"
nonce="a-nonce"
suppressHydrationWarning={true}
/>
<StaticContent
<script
async={true}
element="script"
id="hotjar-script"
nonce="a-nonce"
src="https://static.hotjar.com/c/hotjar-123.js?sv=6"
suppressHydrationWarning={true}
/>
</React.Fragment>
`);
Expand All @@ -47,7 +47,7 @@ describe('Hotjar', () => {
nonce: 'a-nonce2'
})).toMatchInlineSnapshot(`
<React.Fragment>
<StaticContent
<script
async={true}
dangerouslySetInnerHTML={
{
Expand All @@ -59,16 +59,16 @@ describe('Hotjar', () => {
",
}
}
element="script"
id="hotjar-init"
nonce="a-nonce2"
suppressHydrationWarning={true}
/>
<StaticContent
<script
async={true}
element="script"
id="hotjar-script"
nonce="a-nonce2"
src="https://static.hotjar.com/c/hotjar-324123.js?sv=6"
suppressHydrationWarning={true}
/>
</React.Fragment>
`);
Expand Down
10 changes: 4 additions & 6 deletions src/components/Hotjar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { StaticContent } from '~/components/StaticContent';

interface HotjarProps {
hotjarId: string;
visitorId: string;
Expand All @@ -18,8 +16,8 @@ export const Hotjar = (props: HotjarProps) => {

return (
<>
<StaticContent
element={'script'}
<script
suppressHydrationWarning
{...inputProps}
async
id={'hotjar-init'}
Expand All @@ -32,8 +30,8 @@ export const Hotjar = (props: HotjarProps) => {
`
}}
/>
<StaticContent
element={'script'}
<script
suppressHydrationWarning
{...inputProps}
async
id={'hotjar-script'}
Expand Down
18 changes: 0 additions & 18 deletions src/components/StaticContent/StaticContent.test.tsx

This file was deleted.

44 changes: 0 additions & 44 deletions src/components/StaticContent/StaticContent.tsx

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion src/components/StaticContent/index.ts

This file was deleted.

0 comments on commit 06fccb4

Please sign in to comment.