From 680f72f0be40c6b1c564631aabd269b84ed3283e Mon Sep 17 00:00:00 2001 From: Liam Hongman Cho Date: Fri, 24 Jan 2025 11:58:42 +0900 Subject: [PATCH] fix: Update useStyledComponentsTarget to support case where head tag is removed. (#414) ## Changes - Re-fixed an issue where widget style is not applied due to style tag being dynamically removed and then re-added in the head tag in a WordPress like environment ticket: [AA-772](https://sendbird.atlassian.net/browse/AA-722) ## Additional Notes - ## Checklist Before requesting a code review, please check the following: - [ ] **[Required]** CI has passed all checks. - [ ] **[Required]** A self-review has been conducted to ensure there are no minor mistakes. - [ ] **[Required]** Unnecessary comments/debugging code have been removed. - [ ] **[Required]** All requirements specified in the ticket have been accurately implemented. - [ ] Ensure the ticket has been updated with the sprint, status, and story points. [AA-772]: https://sendbird.atlassian.net/browse/AA-772?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- packages/self-service/vite.config.ts | 2 +- src/hooks/useStyledComponentsTarget.ts | 51 ++++++++++++++++---------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/self-service/vite.config.ts b/packages/self-service/vite.config.ts index 143962de4..a6bd670c6 100644 --- a/packages/self-service/vite.config.ts +++ b/packages/self-service/vite.config.ts @@ -8,7 +8,7 @@ const version = getWidgetVersion(); // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), cssInjectedByJsPlugin()], + plugins: [react(), cssInjectedByJsPlugin({ styleId: 'sendbird-css-inject-id' })], build: { outDir: `./dist/${version}`, rollupOptions: { diff --git a/src/hooks/useStyledComponentsTarget.ts b/src/hooks/useStyledComponentsTarget.ts index e07ad3dce..83431e1fe 100644 --- a/src/hooks/useStyledComponentsTarget.ts +++ b/src/hooks/useStyledComponentsTarget.ts @@ -1,6 +1,8 @@ import { useLayoutEffect, useState } from 'react'; import { version } from 'styled-components/package.json'; +const StyledId = 'sendbird-css-inject-id'; + function isSCTarget(node: Node): node is HTMLStyleElement { return node instanceof HTMLStyleElement && node.getAttribute('data-styled-version') === version; } @@ -10,35 +12,46 @@ function isSCTarget(node: Node): node is HTMLStyleElement { * When styled-components, which has already been initialized, is re-added to the head, for example `document.head.innerHTML += ''`, the styles may not render correctly. * Therefore, the target is moved to the body tag. * Similarly, the issue could also rise in below cases and the hook handles them accordingly: - * - If styles are removed from , switch to . + * - If styles are removed from , re-add to head if head exists or switch to . * This is a short-term solution, and in the long run, we plan to remove styled-components altogether. * */ export function useStyledComponentsTarget() { const [target, setTarget] = useState(document.head); useLayoutEffect(() => { + const handleRemovedStyle = (styleElement: HTMLElement) => { + if (styleElement && styleElement.parentElement !== document.body) { + if (document.head) { + console.warn('[useStyledComponentsTarget]: Head exists, re-adding style element ${StyledId} to .'); + document.head.appendChild(styleElement); + setTarget(document.head); + } else { + console.warn('[useStyledComponentsTarget]: Head missing, moving style element ${StyledId} to .'); + document.body.appendChild(styleElement); + setTarget(document.body); + } + } + }; + const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { - // Case 1: Detect if styles are added to - if (mutation.target === document.head && mutation.addedNodes.length > 0) { - for (const node of mutation.addedNodes) { - if (isSCTarget(node)) { - console.warn('Styled Components styles re-injected, switching to '); - setTarget(document.body); - return; - } + // Handle added nodes + Array.from(mutation.addedNodes).forEach((node) => { + if (isSCTarget(node)) { + console.warn('[useStyledComponentsTarget]: Styled Components styles re-injected, switching to '); + setTarget(document.body); } - } - // Case 2: Detect if styles are removed from - if (mutation.target === document.head && mutation.removedNodes.length > 0) { - for (const node of mutation.removedNodes) { - if (isSCTarget(node)) { - console.warn('Styled Components styles removed, switching to '); - setTarget(document.body); - return; - } + }); + + // Handle removed nodes + Array.from(mutation.removedNodes).forEach((node) => { + if (isSCTarget(node)) { + console.warn('[useStyledComponentsTarget]: Styled Components styles removed, switching to '); + setTarget(document.body); + } else if (node instanceof HTMLElement && node.id === StyledId) { + handleRemovedStyle(node); } - } + }); }); });