Unable to focus form element on modal open with react-hook-form

Viewed 369

I'm trying to solve a problem that seems quite straight forward. I want to focus a form element as soon as a modal opens.

Since react-hook-form does not provide a direct handle to the form refs, I'm trying to use a useEffect hook and react-hook-forms howngrown setFocus functions but I keep seeing the error:

s.focus is not a function

Code

--> Link to coding sandbox

import { useEffect } from "react";
import { useForm } from "react-hook-form";
import "./styles.css";
import {
  ChakraProvider,
  Modal,
  ModalOverlay,
  ModalContent,
  ModalHeader,
  ModalFooter,
  ModalBody,
  Button,
  ModalCloseButton,
  useDisclosure
} from "@chakra-ui/react";

export default function App() {
  const { setFocus, register } = useForm();
  const { isOpen, onClose, onOpen } = useDisclosure();

  useEffect(() => {
    if (!isOpen) return;
    setFocus("name");
  }, [setFocus, isOpen]);

  return (
    <ChakraProvider>
      <div className="App">
        <Button onClick={onOpen}>Open</Button>
        <Modal isOpen={isOpen} onClose={onClose}>
          <ModalOverlay />
          <ModalContent>
            <ModalHeader>Modal Title</ModalHeader>
            <ModalCloseButton />
            <ModalBody>
              <input {...register("name")} />
            </ModalBody>
            <ModalFooter>
              <Button colorScheme="blue" mr={3} onClick={onClose}>
                Close
              </Button>
            </ModalFooter>
          </ModalContent>
        </Modal>
      </div>
    </ChakraProvider>
  );
}
2 Answers

Read the Chakra documentation there is one prop initialFocusRef, you can pass the ref to the element you want to focus on initially

Chakra provides 2 props for this use case:

initialFocusRef: The ref of the component that receives focus when the modal opens.
finalFocusRef: The ref of the component that receives focus when the modal closes.

import { useEffect, useRef } from "react";
import { useForm } from "react-hook-form";
import "./styles.css";
import {
  ChakraProvider,
  Modal,
  ModalOverlay,
  ModalContent,
  ModalHeader,
  ModalFooter,
  ModalBody,
  Button,
  ModalCloseButton,
  useDisclosure
} from "@chakra-ui/react";

export default function App() {
  const { setFocus, register } = useForm();
  const { isOpen, onClose, onOpen } = useDisclosure();

  const initialRef = useRef();

  useEffect(() => {
    //setFocus("name");
  }, [setFocus, isOpen]);

  return (
    <ChakraProvider>
      <div className="App">
        <Button onClick={onOpen}>Open</Button>
        <Modal isOpen={isOpen} onClose={onClose} initialFocusRef={initialRef}>
          <ModalOverlay />
          <ModalContent>
            <ModalHeader>Modal Title</ModalHeader>
            <ModalCloseButton />
            <ModalBody>
              <input {...register("name")} ref={initialRef} />
            </ModalBody>
            <ModalFooter>
              <Button colorScheme="blue" mr={3} onClick={onClose}>
                Close
              </Button>
            </ModalFooter>
          </ModalContent>
        </Modal>
      </div>
    </ChakraProvider>
  );
}


UPDATED BASED ON COMMENT

By default, Chkra focus enabled elements if you just add tabIndex to your elements and give order value appropriately then it will get focused. Previously close button was focused by default, now in the below code you will notice I have given tabIndex=2 to the close button and tabIndex=1 to the input element and it is working as expected.

<ChakraProvider>
      <div className="App">
        <Button onClick={onOpen}>Open</Button>
        <Modal isOpen={isOpen} onClose={onClose}>
          <ModalOverlay />
          <ModalContent>
            <ModalHeader>Modal Title</ModalHeader>
            <ModalCloseButton tabIndex="2" />
            <ModalBody>
              <input tabIndex="1" {...register("name")} />
            </ModalBody>
            <ModalFooter>
              <Button colorScheme="blue" mr={3} onClick={onClose}>
                Close
              </Button>
            </ModalFooter>
          </ModalContent>
        </Modal>
      </div>
    </ChakraProvider>

If you want to use that using ref then you can check "How to share ref usage?", maybe it will help you.

Maybe setFocus can't run because the modal content (and the input inside) is first unmounted.

An easy solution using setFocus is to make the form as an independant component :

const Form = () => {
  const { setFocus, register } = useForm();

  useEffect(() => {
    setFocus("name");
  }, [setFocus]);

  return <input {...register("name")} />;
};

and then include it inside the modalBody.

Fixed sandbox link

Related