typescript 39 lines · 7 steps

A dismiss-on-outside-click hook in React

A reusable React hook that closes a menu or popover when the user clicks elsewhere or presses Escape.

Explained by highlit
1import { useEffect, useRef, useState } from "react";
2 
3export function useClickOutside<T extends HTMLElement>() {
4 const ref = useRef<T>(null);
5 const [isOpen, setIsOpen] = useState(false);
6 
7 useEffect(() => {
8 if (!isOpen) return;
9 
10 function handlePointerDown(event: PointerEvent) {
11 const target = event.target as Node;
12 if (ref.current && !ref.current.contains(target)) {
13 setIsOpen(false);
14 }
15 }
16 
17 function handleKeyDown(event: KeyboardEvent) {
18 if (event.key === "Escape") {
19 setIsOpen(false);
20 }
21 }
22 
23 document.addEventListener("pointerdown", handlePointerDown);
24 document.addEventListener("keydown", handleKeyDown);
25 
26 return () => {
27 document.removeEventListener("pointerdown", handlePointerDown);
28 document.removeEventListener("keydown", handleKeyDown);
29 };
30 }, [isOpen]);
31 
32 return {
33 ref,
34 isOpen,
35 open: () => setIsOpen(true),
36 close: () => setIsOpen(false),
37 toggle: () => setIsOpen((prev) => !prev),
38 };
39}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Gating an effect on open state avoids attaching global listeners you don't need.
  2. 2Always return a cleanup function from useEffect that mirrors every listener you added.
  3. 3A generic ref parameter lets one hook serve any HTML element while keeping types accurate.

Related explainers

typescript
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';

How a JWT strategy authenticates in NestJS

authentication jwt passport
Intermediate 7 steps
typescript
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, filter, take, switchMap } from 'rxjs/operators';

Refreshing auth tokens in an Angular interceptor

http-interceptor token-refresh rxjs
Advanced 8 steps
typescript
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';

How a GraphQL auth guard works in NestJS

authentication authorization jwt
Intermediate 7 steps
typescript
type ResizeCallback = (size: { width: number; height: number }) => void;
 
export function observeResize(callback: ResizeCallback, delay = 150) {
  let timeoutId: ReturnType<typeof setTimeout> | undefined;

Debouncing window resize in TypeScript

debounce closures event-listeners
Intermediate 6 steps
javascript
const MAX_BATCH_SIZE = 20;
const FLUSH_INTERVAL = 5000;
const ENDPOINT = '/api/analytics';
 

Batching analytics events in the browser

batching buffering sendbeacon
Intermediate 10 steps
typescript
import { Injectable, Logger, OnApplicationShutdown } from '@nestjs/common';
import { InjectRedis } from '@nestjs-modules/ioredis';
import Redis from 'ioredis';
import { DataSource } from 'typeorm';

Graceful shutdown hooks in NestJS

graceful-shutdown lifecycle-hooks dependency-injection
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

A dismiss-on-outside-click hook in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code