From 0b5a7544ca3751310ebf51b3533fe978b318b65e Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Wed, 5 Jun 2024 01:02:17 +0200 Subject: [PATCH] Address white falshes in storybook --- .storybook/Containers.js | 13 --- .storybook/preview-head.html | 15 +++ src/tools/useInsertLinkTags.ts | 112 ++++++++++------------- src/tools/useInsertScriptTags.ts | 38 ++++---- stories/login/pages/Register.stories.tsx | 15 +++ 5 files changed, 93 insertions(+), 100 deletions(-) create mode 100644 .storybook/preview-head.html diff --git a/.storybook/Containers.js b/.storybook/Containers.js index abaa9ffb..09082b3a 100644 --- a/.storybook/Containers.js +++ b/.storybook/Containers.js @@ -34,7 +34,6 @@ export function DocsContainer({ children, context }) { .docblock-argstable-head th:nth-child(3), .docblock-argstable-body tr > td:nth-child(2) p { font-size: 13px; } - `} - {children} ); diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 00000000..68104445 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/src/tools/useInsertLinkTags.ts b/src/tools/useInsertLinkTags.ts index b419da78..dd3d2f2d 100644 --- a/src/tools/useInsertLinkTags.ts +++ b/src/tools/useInsertLinkTags.ts @@ -1,86 +1,68 @@ -import { useReducer, useEffect } from "react"; +import { useEffect, useState } from "react"; +import { + createStatefulObservable, + useRerenderOnChange +} from "keycloakify/tools/StatefulObservable"; +/** + * NOTE: The component that use this hook can only be mounded once! + * And can't rerender with different hrefs. + * If it's mounted again the page will be reloaded. + * This simulates the behavior of a server rendered page that imports css stylesheet in the head. + */ export function createUseInsertLinkTags() { - let linkTagsContext: - | { - styleSheetHrefs: string[]; - prAreAllStyleSheetsLoaded: Promise; - remove: () => void; - } - | undefined = undefined; + let isFistMount = true; + + const obsAreAllStyleSheetsLoaded = createStatefulObservable(() => false); - /** NOTE: The hrefs can't changes. There should be only one one call on this. */ function useInsertLinkTags(params: { hrefs: string[] }) { const { hrefs } = params; - const [areAllStyleSheetsLoaded, setAllStyleSheetLoaded] = useReducer( - () => true, - hrefs.length === 0 - ); + useRerenderOnChange(obsAreAllStyleSheetsLoaded); + + useState(() => { + if (!isFistMount) { + window.location.reload(); + return; + } + + isFistMount = false; + }); useEffect(() => { let isActive = true; - mount_link_tags: { - if (linkTagsContext !== undefined) { - if ( - JSON.stringify(linkTagsContext.styleSheetHrefs) === - JSON.stringify(hrefs) - ) { - break mount_link_tags; - } + let lastMountedHtmlElement: HTMLLinkElement | undefined = undefined; - linkTagsContext.remove(); + const prs: Promise[] = []; - linkTagsContext = undefined; + for (const href of hrefs) { + const htmlElement = document.createElement("link"); + + prs.push( + new Promise(resolve => + htmlElement.addEventListener("load", () => resolve()) + ) + ); + + htmlElement.rel = "stylesheet"; + + htmlElement.href = href; + + if (lastMountedHtmlElement !== undefined) { + lastMountedHtmlElement.insertAdjacentElement("afterend", htmlElement); + } else { + document.head.prepend(htmlElement); } - let lastMountedHtmlElement: HTMLLinkElement | undefined = undefined; - - const prs: Promise[] = []; - const removeFns: (() => void)[] = []; - - for (const href of hrefs) { - const htmlElement = document.createElement("link"); - - prs.push( - new Promise(resolve => - htmlElement.addEventListener("load", () => resolve()) - ) - ); - - htmlElement.rel = "stylesheet"; - - htmlElement.href = href; - - if (lastMountedHtmlElement !== undefined) { - lastMountedHtmlElement.insertAdjacentElement( - "afterend", - htmlElement - ); - } else { - document.head.prepend(htmlElement); - } - - removeFns.push(() => { - htmlElement.remove(); - }); - - lastMountedHtmlElement = htmlElement; - } - - linkTagsContext = { - styleSheetHrefs: hrefs, - prAreAllStyleSheetsLoaded: Promise.all(prs).then(() => undefined), - remove: () => removeFns.forEach(fn => fn()) - }; + lastMountedHtmlElement = htmlElement; } - linkTagsContext.prAreAllStyleSheetsLoaded.then(() => { + Promise.all(prs).then(() => { if (!isActive) { return; } - setAllStyleSheetLoaded(); + obsAreAllStyleSheetsLoaded.current = true; }); return () => { @@ -88,7 +70,7 @@ export function createUseInsertLinkTags() { }; }, []); - return { areAllStyleSheetsLoaded }; + return { areAllStyleSheetsLoaded: obsAreAllStyleSheetsLoaded.current }; } return { useInsertLinkTags }; diff --git a/src/tools/useInsertScriptTags.ts b/src/tools/useInsertScriptTags.ts index 230a7260..22b4b558 100644 --- a/src/tools/useInsertScriptTags.ts +++ b/src/tools/useInsertScriptTags.ts @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import { assert } from "tsafe/assert"; export type ScriptTag = ScriptTag.TextContent | ScriptTag.Src; @@ -16,20 +16,28 @@ export namespace ScriptTag { }; } +/** + * NOTE: The component that use this hook can only be mounded once! + * And can'r rerender with different scriptTags. + * If it's mounted again the page will be reloaded. + * This simulates the behavior of a server rendered page that imports javascript in the head. + */ export function createUseInsertScriptTags() { let areScriptsInserted = false; - let scriptTagsFingerprint: string | undefined; + let isFistMount = true; function useInsertScriptTags(params: { scriptTags: ScriptTag[] }) { const { scriptTags } = params; - if (scriptTagsFingerprint === undefined) { - scriptTagsFingerprint = getScriptTagsFingerprint(scriptTags); - } else if (getScriptTagsFingerprint(scriptTags) !== scriptTagsFingerprint) { - // NOTE: This is for storybook, when we switch to a page that has different scripts. - window.location.reload(); - } + useState(() => { + if (!isFistMount) { + window.location.reload(); + return; + } + + isFistMount = false; + }); const insertScriptTags = useCallback(() => { if (areScriptsInserted) { @@ -85,17 +93,3 @@ export function createUseInsertScriptTags() { return { useInsertScriptTags }; } - -function getScriptTagsFingerprint(scriptTags: ScriptTag[]) { - return scriptTags - .map((scriptTag): string => { - if ("textContent" in scriptTag) { - return scriptTag.textContent; - } - if ("src" in scriptTag) { - return scriptTag.src; - } - assert(false); - }) - .join("---"); -} diff --git a/stories/login/pages/Register.stories.tsx b/stories/login/pages/Register.stories.tsx index f8f92b03..4f047204 100644 --- a/stories/login/pages/Register.stories.tsx +++ b/stories/login/pages/Register.stories.tsx @@ -76,6 +76,21 @@ export const WithRecaptcha: Story = { ) }; +export const WithRecaptchaFrench: Story = { + render: () => ( + + ) +}; + export const WithPresets: Story = { render: () => (