Search code examples
reactjstypescriptblueprintjs

useEffect appears to be triggered before render completion


I have this React component where I'm trying to move focus to a specific div after the component has finished rendering.

I've tried debugging in my browser and the containerRef is never set. When I pause execution on the line that reads: if (containerRef.current) { I can also see that the component has not been rendered yet. And the component is currently only rendered once (passing hardcoded data in Storybook, but also tried mocking it inside the real app, thinking that some wrapper component in Storybook might be triggering the hook somehow).

Any suggestions why this might be?

export function DuplicateSelect(props: DuplicateSelectProps) {
  const containerRef = useRef(null);

  useEffect(() => {
    if (containerRef.current) {
      containerRef.current.focus();
    }
  });

  return (
    <Overlay isOpen={true}>
      <div className="flex-center">
        <Card className="duplicate-select" elevation={1}>
          <div
            className="duplicate-select-options"
            tabIndex={0}
            ref={containerRef}
          >
            {props.results.map((result) => (
              <Item data={result} />
            ))}
          </div>
        </Card>
      </div>
    </Overlay>
  );
}

Made a prototype in Codesandbox and that works fine: https://codesandbox.io/s/damp-dust-4xiv0


Solution

  • I am using Blueprint.js as a UI library and on further investigation I discovered that the problem was due to the <Overlay> component rendering inside a portal. There was a prop to disable this behaviour and that solved the problem. Seems like maybe refs break when using portals?

    export function DuplicateSelect(props: DuplicateSelectProps) {
      const containerRef = useRef(null);
    
      useEffect(() => {
        if (containerRef.current) {
          containerRef.current.focus();
        }
      });
    
      return (
        <Overlay isOpen={true} usePortal={false} >
          <div className="flex-center">
            <Card className="duplicate-select" elevation={1}>
              <div
                className="duplicate-select-options"
                tabIndex={0}
                ref={containerRef}
              >
                {props.results.map((result) => (
                  <Item data={result} />
                ))}
              </div>
            </Card>
          </div>
        </Overlay>
      );
    }