Skip to content

Cannot read properties of null (reading 'children') - dynamicCSS.ts #603

Closed

Description

@josepdaniel

Hi there, thank you for authoring these utilities.

We have run into a persistent problem when hydrating server-side-rendered react using cssinjs-based components (eg antd-v5) due to the following calls in dynamicCSS.ts.

function findStyles(container: ContainerType) {
  return Array.from(
    (containerCache.get(container) || container).children,
  ).filter(node => node.tagName === 'STYLE') as HTMLStyleElement[];
}

Where container is usually fetched from

function getContainer(option: Options) {
  if (option.attachTo) {
    return option.attachTo;
  }

  const head = document.querySelector('head');
  return head || document.body;
}

When hydrating an SSR react app, if there is a mismatch between the client-render and server-render then the client will re-render the component. For frameworks like remix, where the whole document is rendered, document.querySelector('head') and document.body may both be null at the time of rendering, leading to the following error

Uncaught TypeError: Cannot read properties of null (reading 'children')

I believe the following PR, which has already been open for quite some time, will address the bug. So we would like to request considering it for merge. Thank you in advance!

#455

@zombieJ

Activity

  1. changed the title [-]children is not a property of null[/-] [+]Cannot read properties of null (reading 'children') - dynamicCSS.ts[/+] on Dec 11, 2024
  2. 0xNoWater commented on Jan 11, 2025

    @0xNoWater

    omfg. This issue has been plaguing me! It took me a while to realize it was happening down in rc-util. Thanks for filing this!

    An ~18 month old open PR doesn't feel particularly promising. Is there any way to get this merged?

    Also @josepdaniel have you found any workarounds?

    Edit: For now my solution is a monkeypatch:

    diff --git a/node_modules/rc-util/es/Dom/dynamicCSS.js b/node_modules/rc-util/es/Dom/dynamicCSS.js
    index 8ddec00..d6c390d 100644
    --- a/node_modules/rc-util/es/Dom/dynamicCSS.js
    +++ b/node_modules/rc-util/es/Dom/dynamicCSS.js
    @@ -31,7 +31,7 @@ function getOrder(prepend) {
      * Find style which inject by rc-util
      */
     function findStyles(container) {
    -  return Array.from((containerCache.get(container) || container).children).filter(function (node) {
    +  return Array.from((containerCache.get(container) || container)?.children || []).filter(function (node) {
         return node.tagName === 'STYLE';
       });
     }
    @@ -56,7 +56,7 @@ export function injectCSS(css) {
       }
       styleNode.innerHTML = css;
       var container = getContainer(option);
    -  var firstChild = container.firstChild;
    +  var firstChild = container?.firstChild;
       if (prepend) {
         // If is queue `prepend`, it will prepend first style and then append rest style
         if (isPrependQueue) {
    @@ -77,9 +77,11 @@ export function injectCSS(css) {
         }
     
         // Use `insertBefore` as `prepend`
    -    container.insertBefore(styleNode, firstChild);
    +    if (firstChild) {
    +      container.insertBefore(styleNode, firstChild);
    +    }
       } else {
    -    container.appendChild(styleNode);
    +    container?.appendChild(styleNode);
       }
       return styleNode;
     }
    @@ -95,7 +97,7 @@ export function removeCSS(key) {
       var existNode = findExistNode(key, option);
       if (existNode) {
         var container = getContainer(option);
    -    container.removeChild(existNode);
    +    container?.removeChild(existNode);
       }
     }
     
    @@ -110,7 +112,7 @@ function syncRealContainer(container, option) {
         var placeholderStyle = injectCSS('', option);
         var parentNode = placeholderStyle.parentNode;
         containerCache.set(container, parentNode);
    -    container.removeChild(placeholderStyle);
    +    container?.removeChild(placeholderStyle);
       }
     }
     
    diff --git a/node_modules/rc-util/lib/Dom/dynamicCSS.js b/node_modules/rc-util/lib/Dom/dynamicCSS.js
    index 95a5191..1303898 100644
    --- a/node_modules/rc-util/lib/Dom/dynamicCSS.js
    +++ b/node_modules/rc-util/lib/Dom/dynamicCSS.js
    @@ -41,7 +41,7 @@ function getOrder(prepend) {
      * Find style which inject by rc-util
      */
     function findStyles(container) {
    -  return Array.from((containerCache.get(container) || container).children).filter(function (node) {
    +  return Array.from((containerCache.get(container) || container)?.children || []).filter(function (node) {
         return node.tagName === 'STYLE';
       });
     }
    @@ -66,7 +66,7 @@ function injectCSS(css) {
       }
       styleNode.innerHTML = css;
       var container = getContainer(option);
    -  var firstChild = container.firstChild;
    +  var firstChild = container?.firstChild;
       if (prepend) {
         // If is queue `prepend`, it will prepend first style and then append rest style
         if (isPrependQueue) {
    @@ -87,9 +87,11 @@ function injectCSS(css) {
         }
     
         // Use `insertBefore` as `prepend`
    -    container.insertBefore(styleNode, firstChild);
    +    if (firstChild) {
    +      container.insertBefore(styleNode, firstChild);
    +    }
       } else {
    -    container.appendChild(styleNode);
    +    container?.appendChild(styleNode);
       }
       return styleNode;
     }
    @@ -105,7 +107,7 @@ function removeCSS(key) {
       var existNode = findExistNode(key, option);
       if (existNode) {
         var container = getContainer(option);
    -    container.removeChild(existNode);
    +    container?.removeChild(existNode);
       }
     }
     
    @@ -120,7 +122,7 @@ function syncRealContainer(container, option) {
         var placeholderStyle = injectCSS('', option);
         var parentNode = placeholderStyle.parentNode;
         containerCache.set(container, parentNode);
    -    container.removeChild(placeholderStyle);
    +    container?.removeChild(placeholderStyle);
       }
     }
    
  3. divyeshsachan commented on Jan 27, 2025

    @divyeshsachan

    After hydration mismatch, when the react tries to switch to client rendering, sometimes document is blank and accessing children causes entire app to break and can't be restored.

    A user needs to hard refresh the page to recover.
    @zombieJ, there is an open pull request (> 1 year) #455 which should be merged.
    as of now, monkeypatch of @ethos-seth is the only solution.

  4. althafdemiandra commented on Apr 9, 2025

    @althafdemiandra

    bumping this as in react-routerv7

    Image

  5. arthurvaverko commented on Sep 7, 2025

    @arthurvaverko

    Same issue, react router 7 with Antd .. trying the monkey patch really waiting for an official fix

  6. hongfanmeng commented on Sep 15, 2025

    @hongfanmeng

    I've also run into this issue and do some analysis.

    The problem seems to stem from @ant-design/cssinjs, specifically within the useCacheToken hook. This hook utilizes useGlobalCache to inject CSS styles into the document's <head> (via rc-util's updateCSS function). Crucially, useGlobalCache relies on the useInsertionEffect React hook to perform this operation.

    However, when using Remix or React Router v7, useInsertionEffect appears to fire prematurely for reasons I haven't yet identified. It executes before the DOM is fully constructed, at a moment when document.head does not yet exist. This causes the updateCSS function to throw an error, as it cannot find the <head> element to append the style tag to. At this point, only a hard refresh resolves the problem.

    On a positive note, I recently discovered that after upgrading react and react-dom to 19.1.0, useInsertionEffect seems to behave as expected. It now fires at the correct time, after the document.head is available in the DOM. This prevents the updateCSS error from occurring and completely resolves the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions