Skip to content

Modal

Build a modal that opens from a button and closes on its Close button or a mousedown outside the box, using a reusable useClickOutside hook.

Stage 4intro3 min read

Problem Statement

Build a modal that opens from a "Show Modal" button and closes when the user clicks its Close button or clicks outside it.

Functional Requirements

  1. Open: Clicking "Show Modal" sets showModal to true and shows the modal over a dark overlay.
  2. Close button: The Close button inside the modal calls closeModal.
  3. Click outside: A mousedown anywhere outside the modal box closes it. Clicks inside the box do not.
  4. Hidden when closed: When isOpen is false, the Modal component renders nothing.

Technical Constraints

  • Put the outside click logic in a reusable useClickOutside(elementRef, handler) hook.
  • Add the mousedown listener on document and remove it in the effect cleanup.

1. Code Flow

The implementation follows this sequence:

  1. User clicks "Show Modal" button in App component
  2. showModal state changes to true
  3. Modal component receives isOpen={true}
  4. Modal renders and sets up click-outside detection
  5. When clicking outside or the close button, showModal returns to false

2. Function Breakdown

App Component

function App() {
  const [showModal, setShowModal] = useState(false);
  // Controls modal visibility through state
  // Initial state is false (modal hidden)
}
const Modal = ({ isOpen, closeModal }) => {
  // Receives two props:
  // isOpen: boolean to control visibility
  // closeModal: function to hide modal

  // Early return pattern - if not open, render nothing
  if (!isOpen) return null;
};

useClickOutside Hook

const useClickOutside = (elementRef, handler) => {
  // Custom hook that takes:
  // elementRef: reference to modal element
  // handler: function to call when clicking outside

  useEffect(() => {
    const cb = (event) => {
      // Checks if click was outside modal
      // elementRef.current?.contains(event.target) checks if clicked element
      // is a child of modal
    };
  }, [elementRef, handler]);
};

3. Understanding useRef

useRef is crucial here for several reasons:

  1. DOM Reference:

    • const modalRef = useRef() creates a mutable reference
    • This reference stays consistent between re-renders
    • Allows direct access to DOM element via modalRef.current
  2. Use in Modal:

    <div ref={modalRef} className="modal-container">
    • Attaches reference to modal's DOM element
    • Enables checking if clicks occur inside/outside modal
  3. Performance:

    • Changes to ref don't trigger re-renders
    • Perfect for DOM manipulations and event handling

4. Click vs MouseDown Events

The difference between click and mousedown in this context is crucial:

  1. Event Order:

    mousedown โ†’ mouseup โ†’ click
    
  2. Why mousedown works better:

    • mousedown fires immediately when mouse button is pressed
    • click waits for complete click action (press + release)
    • Modal can interfere with click event completion
  3. Common Issue:

    // This might not work reliably
    document.addEventListener("click", cb);
    
    // This works better
    document.addEventListener("mousedown", cb);

Interview Tips ๐ŸŽฏ

  1. Key Points to Remember:

    • Modals are portals to render outside normal DOM hierarchy
    • useRef for DOM manipulation without re-renders
    • Event delegation for click-outside detection
    • Cleanup in useEffect to prevent memory leaks
  2. Common Interview Questions:

    • "How would you handle multiple modals?"
    • "How would you implement modal accessibility?"
    • "What are the advantages of useRef over direct DOM manipulation?"
    • "How would you optimize modal performance?"

React Modal Interview Questions & Answers ๐ŸŽฏ

1. How would you handle multiple modals?

Solution 1: Using Modal Manager

const [modals, setModals] = useState({
  deleteModal: false,
  editModal: false,
  shareModal: false,
});

const openModal = (modalKey) => {
  setModals({ ...modals, [modalKey]: true });
};

Solution 2: Modal Stack

const [modalStack, setModalStack] = useState([]);

// Push new modal
const pushModal = (modalType, modalProps) => {
  setModalStack([...modalStack, { type: modalType, props: modalProps }]);
};

// Pop modal
const popModal = () => {
  setModalStack((prev) => prev.slice(0, -1));
};

Key Interview Points ๐Ÿ”‘

  • Discuss Z-index management
  • Modal history management
  • Proper focus management between modals
  • Memory considerations
  • Event handling complexity

2. How would you implement modal accessibility?

Core Implementation

const Modal = ({ isOpen, onClose, title }) => {
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      tabIndex="-1"
    >
      <h2 id="modal-title">{title}</h2>
      {/* content */}
    </div>
  );
};

Key Interview Points ๐Ÿ”‘

  1. ARIA Attributes:

    • role="dialog"
    • aria-modal="true"
    • aria-labelledby
    • aria-describedby
  2. Keyboard Navigation:

    const handleKeyDown = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "Tab") handleTabKey(e);
    };
  3. Focus Management:

    useEffect(() => {
      const focusable = modalRef.current.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
    
      if (focusable.length) focusable[0].focus();
    
      // Store previous focus
      const previousFocus = document.activeElement;
      return () => previousFocus.focus();
    }, [isOpen]);

3. What are the advantages of useRef over direct DOM manipulation?

Key Interview Points ๐Ÿ”‘

  1. Persistent Reference:

    const elementRef = useRef(null);
    // Survives re-renders without causing them
  2. React Integration:

    // โœ… Good - React way
    const inputRef = useRef();
    inputRef.current.focus();
    
    // โŒ Bad - Direct DOM
    document.getElementById("input").focus();
  3. Performance Benefits:

    • No re-renders on reference updates
    • Clean integration with React lifecycle
    • Prevents stale closure issues
  4. Memory Management:

    • Automatically cleaned up by React
    • No manual event listener cleanup needed

4. How would you optimize modal performance?

Code-Level Optimizations

// 1. Lazy Loading
const Modal = React.lazy(() => import("./Modal"));

// 2. Memoization
const MemoizedModal = React.memo(Modal);

// 3. Portal Usage
return createPortal(<Modal />, document.getElementById("modal-root"));

Key Interview Points ๐Ÿ”‘

  1. Rendering Optimizations:

    • Use React.memo for modal components
    • Implement lazy loading for modals
    • Use CSS transform instead of display/visibility
  2. State Management:

    // Instead of
    const [isOpen, setIsOpen] = useState(false);
    
    // Consider using reducer for complex modals
    const [state, dispatch] = useReducer(modalReducer, initialState);
  3. Event Delegation:

    // Instead of multiple listeners
    document.addEventListener("mousedown", handleGlobalClick, {
      capture: true,
    });

In this topic

Done with this topic?

How well do you know it?

PreviousPagination
Practise 2 questions