React Hooks

useState

const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: "", age: 0 });

setCount(prev => prev + 1);
setUser(prev => ({ ...prev, name: "Alice" }));

useEffect

useEffect(() => {
  document.title = `Count: ${count}`;
  return () => {
    document.title = "App";
  };
}, [count]);

useEffect(() => {
  const controller = new AbortController();
  fetch("/api/data", { signal: controller.signal })
    .then(res => res.json())
    .then(setData);
  return () => controller.abort();
}, []);

useRef

const inputRef = useRef(null);
const renderCount = useRef(0);

const focus = () => inputRef.current.focus();

return <input ref={inputRef} />;

useContext

const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  );
}

function Child() {
  const theme = useContext(ThemeContext);
  return <div className={theme}>Current: {theme}</div>;
}

useMemo

const sorted = useMemo(
  () => items.filter(i => i.active).sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

const expensiveValue = useMemo(() => compute(a, b), [a, b]);

useCallback

const handleClick = useCallback(() => {
  setCount(prev => prev + 1);
}, []);

const handleSubmit = useCallback((e) => {
  e.preventDefault();
  saveForm(formData);
}, [formData]);

return <Child onClick={handleClick} onSubmit={handleSubmit} />;

useReducer

const reducer = (state, action) => {
  switch (action.type) {
    case "increment": return { count: state.count + 1 };
    case "decrement": return { count: state.count - 1 };
    case "reset": return { count: 0 };
    default: return state;
  }
};

const [state, dispatch] = useReducer(reducer, { count: 0 });

dispatch({ type: "increment" });

Custom Hooks

function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debounced;
}

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initial;
  });
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue];
}

useLayoutEffect

useLayoutEffect(() => {
  const height = ref.current.getBoundingClientRect().height;
  setTooltipPos(height + 8);
}, [deps]);

useImperativeHandle

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
    clear: () => { inputRef.current.value = ""; },
  }));
  return <input ref={inputRef} />;
});

const inputRef = useRef();
<inputRef.current?.focus() />;

useId

function Form() {
  const id = useId();
  return (
    <>
      <label htmlFor={id + "-name"}>Name</label>
      <input id={id + "-name"} />
      <label htmlFor={id + "-email"}>Email</label>
      <input id={id + "-email"} />
    </>
  );
}

useDebugValue

function useOnlineStatus() {
  const [online, setOnline] = useState(navigator.onLine);
  useEffect(() => {
    const goOnline = () => setOnline(true);
    const goOffline = () => setOnline(false);
    window.addEventListener("online", goOnline);
    window.addEventListener("offline", goOffline);
    return () => {
      window.removeEventListener("online", goOnline);
      window.removeEventListener("offline", goOffline);
    };
  }, []);
  useDebugValue(online ? "Online" : "Offline");
  return online;
}

Rules of Hooks

// Only call Hooks at the top level
function Bad() {
  if (condition) {
    useState(0);
  }
}

function Good() {
  const [val, setVal] = useState(0);
  if (condition) {
    // conditional logic using val
  }
}

// Only call Hooks from React functions
// Good: function components or custom hooks
// Bad: regular JS functions, class components

Common Patterns

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    fetch(url, { signal: controller.signal })
      .then(res => { if (!res.ok) throw new Error(res.statusText); return res.json(); })
      .then(setData)
      .catch(err => { if (err.name !== "AbortError") setError(err.message); })
      .finally(() => setLoading(false));
    return () => controller.abort();
  }, [url]);
  return { data, loading, error };
}

function useToggle(initial = false) {
  const [value, setValue] = useState(initial);
  const toggle = useCallback(() => setValue(v => !v), []);
  return [value, toggle];
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => { ref.current = value; });
  return ref.current;
}

useState

const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: "", age: 0 });

setCount(prev => prev + 1);
setUser(prev => ({ ...prev, name: "Alice" }));

useEffect

useEffect(() => {
  document.title = `Count: ${count}`;
  return () => {
    document.title = "App";
  };
}, [count]);

useEffect(() => {
  const controller = new AbortController();
  fetch("/api/data", { signal: controller.signal })
    .then(res => res.json())
    .then(setData);
  return () => controller.abort();
}, []);

useRef

const inputRef = useRef(null);
const renderCount = useRef(0);

const focus = () => inputRef.current.focus();

return <input ref={inputRef} />;

useContext

const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  );
}

function Child() {
  const theme = useContext(ThemeContext);
  return <div className={theme}>Current: {theme}</div>;
}

useMemo

const sorted = useMemo(
  () => items.filter(i => i.active).sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

const expensiveValue = useMemo(() => compute(a, b), [a, b]);

useCallback

const handleClick = useCallback(() => {
  setCount(prev => prev + 1);
}, []);

const handleSubmit = useCallback((e) => {
  e.preventDefault();
  saveForm(formData);
}, [formData]);

return <Child onClick={handleClick} onSubmit={handleSubmit} />;

useReducer

const reducer = (state, action) => {
  switch (action.type) {
    case "increment": return { count: state.count + 1 };
    case "decrement": return { count: state.count - 1 };
    case "reset": return { count: 0 };
    default: return state;
  }
};

const [state, dispatch] = useReducer(reducer, { count: 0 });

dispatch({ type: "increment" });

Custom Hooks

function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debounced;
}

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initial;
  });
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue];
}

useLayoutEffect

useLayoutEffect(() => {
  const height = ref.current.getBoundingClientRect().height;
  setTooltipPos(height + 8);
}, [deps]);

useImperativeHandle

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
    clear: () => { inputRef.current.value = ""; },
  }));
  return <input ref={inputRef} />;
});

const inputRef = useRef();
<inputRef.current?.focus() />;

useId

function Form() {
  const id = useId();
  return (
    <>
      <label htmlFor={id + "-name"}>Name</label>
      <input id={id + "-name"} />
      <label htmlFor={id + "-email"}>Email</label>
      <input id={id + "-email"} />
    </>
  );
}

useDebugValue

function useOnlineStatus() {
  const [online, setOnline] = useState(navigator.onLine);
  useEffect(() => {
    const goOnline = () => setOnline(true);
    const goOffline = () => setOnline(false);
    window.addEventListener("online", goOnline);
    window.addEventListener("offline", goOffline);
    return () => {
      window.removeEventListener("online", goOnline);
      window.removeEventListener("offline", goOffline);
    };
  }, []);
  useDebugValue(online ? "Online" : "Offline");
  return online;
}

Rules of Hooks

// 只在顶层调用 Hooks
function Bad() {
  if (condition) {
    useState(0);
  }
}

function Good() {
  const [val, setVal] = useState(0);
  if (condition) {
    // 基于 val 的条件逻辑
  }
}

// 只在 React 函数中调用 Hooks
// 正确:函数组件或自定义 Hook
// 错误:普通 JS 函数、类组件

Common Patterns

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    fetch(url, { signal: controller.signal })
      .then(res => { if (!res.ok) throw new Error(res.statusText); return res.json(); })
      .then(setData)
      .catch(err => { if (err.name !== "AbortError") setError(err.message); })
      .finally(() => setLoading(false));
    return () => controller.abort();
  }, [url]);
  return { data, loading, error };
}

function useToggle(initial = false) {
  const [value, setValue] = useState(initial);
  const toggle = useCallback(() => setValue(v => !v), []);
  return [value, toggle];
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => { ref.current = value; });
  return ref.current;
}