Toggle focus between buttons in a side menu in React using autoFocus

Viewed 653

I'm trying to toggle focus between buttons in a side menu. The button gets clicked should be focused. My side menu code:

import React, { useRef } from "react";
import styles from "./SideMenu.module.scss";

interface Props {
  buttons: Array<any>;
  clicked: Function;
}

const sideMenu: React.FC<Props> = ({ buttons, clicked }) => {
  const buttonsList = buttons.map((b, index) => {
    return (
        <button 
            key={b.label} 
            id={b.label}
            autoFocus={b.isSelected}
            onClick={() => clicked(b.label)}>
            {b.label.toUpperCase()}
        </button>
    );
  });

  return <div className={styles.sideMenu}>{buttonsList}</div>;
};

export default sideMenu;

And here is the code that changes button state when clicked

const layout: React.FC<{}> = ({ children }) => {
  const [buttons, setButtons] = useState([
    { label: "btn1", isSelected: true, routePath: "/btn1" },
    { label: "btn2", isSelected: false, routePath: "/btn2" },
  ]);

  const router = useRouter()

  const clickButtonHandler = (buttonLabel: string) => {
    const updatedButtons = [...buttons];
    let path = "/";
    updatedButtons.map((b, index) => {
      if (b.label === buttonLabel) {
        b.isSelected = true
        path = b.routePath
      } else {
        b.isSelected = false
      }
    });
    setButtons(updatedButtons)
    router.push(path)
  };

  return (
    <div className={styles.mainWrapper}>
      <h1 className={styles.mainHeader}>My Web App</h1>
      <div className={styles.wrapper}>
        <SideMenu buttons={buttons} clicked={clickButtonHandler} />
        <div className={styles.content}>{children}</div>
      </div>
    </div>
  );
};

export default layout;

When I render the page, the btn1 is auto focused(this is expected), when I click on btn2, the focus first changed to btn2 but then immediately go back to btn1, if I click btn2 again, the focus will change to btn2 and stay.

But everytime I click on btn1, the focus will change to btn1 and stay.

If I comment out the "router.push(path)", the focusing behavior is working correctly.

How can I get it working without commenting "router.push(path)"?

1 Answers

autoFocus doesn't work correctly until your controls are not placed inside the <form> tag. You can render a form, but there is one way which is "more React".

const sideMenu: React.FC<Props> = ({ buttons, clicked }) => {
  const focusedButton = null;

  useEffect(() => {
      if(focusedButton) focusedButton.focus();
  });

  const buttonsList = buttons.map((b, index) => {
    return (
        <button 
            key={b.label} 
            id={b.label}
            ref={el => {focusedButton = b.isSelected ? el : focusedButton}}
            onClick={() => clicked(b.label)}>
            {b.label.toUpperCase()}
        </button>
    );
  });

  return <div className={styles.sideMenu}>{buttonsList}</div>;
};

Official documentation suggests using ref for focusing elements. Here is more details about ref callbacks.

Related