javascript 55 lines · 8 steps

Debounced autosave in React

A textarea that quietly saves itself after typing pauses, with cancellable requests and a live status indicator.

Explained by highlit
1import { useState, useEffect, useRef, useCallback } from "react";
2 
3function useDebouncedValue(value, delay) {
4 const [debounced, setDebounced] = useState(value);
5 
6 useEffect(() => {
7 const id = setTimeout(() => setDebounced(value), delay);
8 return () => clearTimeout(id);
9 }, [value, delay]);
10 
11 return debounced;
12}
13 
14export function DraftEditor({ draftId }) {
15 const [content, setContent] = useState("");
16 const [status, setStatus] = useState("idle");
17 const debouncedContent = useDebouncedValue(content, 800);
18 const lastSaved = useRef("");
19 
20 const save = useCallback(async (text, signal) => {
21 setStatus("saving");
22 try {
23 await fetch(`/api/drafts/${draftId}`, {
24 method: "PUT",
25 headers: { "Content-Type": "application/json" },
26 body: JSON.stringify({ content: text }),
27 signal,
28 });
29 lastSaved.current = text;
30 setStatus("saved");
31 } catch (err) {
32 if (err.name !== "AbortError") setStatus("error");
33 }
34 }, [draftId]);
35 
36 useEffect(() => {
37 if (debouncedContent === lastSaved.current) return;
38 const controller = new AbortController();
39 save(debouncedContent, controller.signal);
40 return () => controller.abort();
41 }, [debouncedContent, save]);
42 
43 return (
44 <div className="draft-editor">
45 <textarea
46 value={content}
47 onChange={(e) => setContent(e.target.value)}
48 placeholder="Start writing\u2026"
49 />
50 <span className={`draft-status draft-status--${status}`}>
51 {status === "saving" ? "Saving\u2026" : status === "saved" ? "All changes saved" : status === "error" ? "Save failed" : ""}
52 </span>
53 </div>
54 );
55}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Debouncing a value in a custom hook keeps expensive work off every keystroke and out of your components.
  2. 2AbortController lets a later effect cancel an in-flight request so stale saves never overwrite fresh ones.
  3. 3Tracking the last saved value in a ref avoids redundant network calls without triggering re-renders.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Debounced autosave in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code