Search code examples
reactjsreactstrap

Reactstrap innerRef not setting reference to element


I am trying to use Reactstrap 8.5.1 innerRef attribute along with useRef() to focus the Input within a Modal whenever the Modal opens. The code below shows the Button which opens the Modal, but when clicked I get the error "Cannot read property 'focus' of null". It also writes inputRef to the console, which shows that .current is null.

I've tried various ways to set innerRef, but nothing seems to work. I'd be very grateful if someone can point out to me what I am missing.

import React, { useState, useRef, useEffect } from 'react';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Input } from 'reactstrap';


export const ModalSave = (props) => {
    const [modalIsOpen, setModalIsOpen] = useState(false);
    const toggle = () => setModalIsOpen(!modalIsOpen);

    const inputRef = useRef(null);

    useEffect(() => {
        console.log(inputRef);
        if (modalIsOpen === true) {
            inputRef.current.focus();
        }
    }, [modalIsOpen]);

    return (
        <div>
            <Button
                onClick={() => { setModalIsOpen(true); }}
            >Save</Button>
            <Modal isOpen={modalIsOpen} toggle={toggle}>
                <ModalHeader toggle={toggle}>Save</ModalHeader>
                <ModalBody>
                    Name:
                    <Input
                        innerRef={inputRef.current}
                    />
                </ModalBody>
                <ModalFooter>
                    <Button>Save</Button>
                    <Button onClick={toggle}>Close</Button>
                </ModalFooter>
            </Modal>
        </div>
    );
}

Solution

  • The issue is that opening the modal doesn't trigger the component to re-render which is needed to get the input ref value, and so, the ref will remain null unless some state is called to trigger the re-render. As a workaround, you can use setTimeout() method to kind of force it like so:

    useEffect(() => {
      if (modalIsOpen) {
       setTimeout(() => inputRef.current.focus(), 0);
      }
    }, [modalIsOpen]);
    

    A better solution is to use the onOpened method which is called after the modal has opened:

    export default function App() {
      const inputRef = useRef(null);
      const [modalIsOpen, setModalIsOpen] = useState(false);
    
      const toggle = () => setModalIsOpen(!modalIsOpen);
      const handleOpen = () => inputRef.current.focus();
    
      return (
        <div className="App">
          <div>
            <Button onClick={() => setModalIsOpen(true)}>Save</Button>
            <Modal isOpen={modalIsOpen} toggle={toggle} onOpened={handleOpen}>
              <ModalHeader toggle={toggle}>Save</ModalHeader>
              <ModalBody>
                Name:
                <Input innerRef={inputRef} />
              </ModalBody>
              <ModalFooter>
                <Button>Save</Button>
                <Button onClick={toggle}>Close</Button>
              </ModalFooter>
            </Modal>
          </div>
        </div>
      );
    }
    

    Edit reactstrap-modal-input-focus