0

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

zkwsk
  • 1,960
  • 4
  • 26
  • 34
  • How did you ascertain that app had not completed render phase? – Agney Apr 01 '20 at 18:25
  • imo this is an issue with `focus()`ing on an Element that can't be focused, and the browsers opinion what the right element is to get the focus ( the `body`) – Thomas Apr 01 '20 at 18:41

2 Answers2

1

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>
  );
}
zkwsk
  • 1,960
  • 4
  • 26
  • 34
-1

Add second argument to useEffect hook so it will trigger when that ref will be set

useEffect(() => {
    if (containerRef.current) {
      containerRef.current.focus();
    }
 }, [containerRef.current]);
Zohaib Ijaz
  • 21,926
  • 7
  • 38
  • 60
  • refs are for values that do not require a re-render, so would be an [antpattern](https://github.com/facebook/react/issues/14387#issuecomment-503616820) – Agney Apr 01 '20 at 18:29
  • @Agney Then what's the way to focus in this case? I am waiting for your answer using best practices – Zohaib Ijaz Apr 01 '20 at 18:35
  • The prototype that the OP made works completely fine, I'm not sure what the issue is to warrant an answer – Agney Apr 01 '20 at 18:56