Wildhoney / ReactShadow

:beginner: Utilise Shadow DOM in React with all the benefits of style encapsulation.
https://react-shadow.herokuapp.com/
MIT License
1.29k stars 80 forks source link

Children of the shadow root cannot get retrieved at parent level within a useEffect #149

Closed c-falardeau closed 1 year ago

c-falardeau commented 1 year ago

I'm building a HOC component in our library that basically wraps a shadow root component of this library. Putting this component in our story book I have a story book control to allow changing between the mode 'open' vs closed. Within a useEffect at parent level, I can't access any children within the shadow tree. There seems to be a multiple cycles redering happening that that the parent level is not aware of. Putting a setTimeout do the thick, but I bet there is a better fix that could be done inside the library or maybe I'm using it wrong. The shadow root seems to be there first render when useEffect runs, but the children not yet populated.

Basically when I do:

  const BUTTON_IN_SHADOW_DOM_ID = 'button-in-shadow-dom';
const PlaygroundTemplate: ComponentStory<typeof ShadowDom> = props => {
  const elementRef = useRef<HTMLElement>(null);

  useEffect(() => {
      console.log(elementRef.current?.shadowRoot?.getElementById(BUTTON_IN_SHADOW_DOM_ID)); <- logs null in the console
  });

  useEffect(() => {
    setTimeout(() => {
      console.log(elementRef.current?.shadowRoot?.getElementById(BUTTON_IN_SHADOW_DOM_ID)); <- logs the component in the console
    });
  });

  return (
    <>
      <ShadowDom
        ref={elementRef}
        {...props}
      >
        <button id={BUTTON_IN_SHADOW_DOM_ID}>This is a button with default style</button>
      </ShadowDom>
    </>
  );
};
export enum ShadowDomMode {
  OPENED = 'OPENED',
  CLOSED = ' CLOSED',
}

export type ShadowDomElementType = keyof Pick<
  JSX.IntrinsicElements,
  'main' | 'nav' | 'article' | 'header' | 'section' | 'aside' | 'footer' | 'div' | 'span'
>;

export type ShadowDomProps = PropsWithChildren<{
  delegatesFocus?: boolean;
  mode?: ShadowDomMode;
  elementType?: ShadowDomElementType;
}> &
  HTMLAttributes<HTMLElement>;

const ShadowDom = forwardRef(
  (
    { children, delegatesFocus, elementType = 'section', mode = ShadowDomMode.OPENED, ...otherProps }: ShadowDomProps,
    ref: ForwardedRef<HTMLElement>
  ): JSX.Element => {
    const Component = root[elementType];

    return (
      <Component
        {...otherProps}
        delegatesFocus={delegatesFocus}
        key={`${mode}:${delegatesFocus}`}
        mode={mode === ShadowDomMode.OPENED ? 'open' : 'closed'}
        ref={ref}
        role={elementType}
        style={{ all: 'initial' }}
      >
        <div className={'shadow-dom-content'}>{children}</div>
      </Component>
    );
  }
);

ShadowDom.displayName = 'ShadowDom';

export default ShadowDom;

I suspect would it be related to that ?