Rooks
HooksState Management

useSafeSetState

Calls setState only if the component is still mounted, preventing memory leaks.

About

A hook that provides a safe version of useState that prevents state updates after the component has unmounted. This is particularly useful for async operations to avoid memory leaks and React warnings.

Examples

Basic example

import { useSafeSetState } from "rooks";

export default function App() {
  const [count, setSafeCount] = useSafeSetState(0);

  const handleAsyncIncrement = async () => {
    // Simulate an async operation
    await new Promise((resolve) => setTimeout(resolve, 2000));

    // This will only update state if the component is still mounted
    setSafeCount((prev) => prev + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleAsyncIncrement}>Async Increment (2s delay)</button>
      <button onClick={() => setSafeCount(0)}>Reset</button>
    </div>
  );
}

Example with fetch operation

import { useSafeSetState } from "rooks";
import { useEffect } from "react";

export default function UserProfile({ userId }) {
  const [user, setSafeUser] = useSafeSetState(null);
  const [loading, setSafeLoading] = useSafeSetState(true);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setSafeLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();

        // Safe to call even if component unmounts during fetch
        setSafeUser(userData);
        setSafeLoading(false);
      } catch (error) {
        console.error("Failed to fetch user:", error);
        setSafeLoading(false);
      }
    };

    fetchUser();
  }, [userId, setSafeUser, setSafeLoading]);

  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Example with functional state updates

import { useSafeSetState } from "rooks";

export default function Counter() {
  const [state, setSafeState] = useSafeSetState({
    count: 0,
    lastUpdated: Date.now(),
  });

  const increment = () => {
    setSafeState((prevState) => ({
      count: prevState.count + 1,
      lastUpdated: Date.now(),
    }));
  };

  const delayedIncrement = () => {
    setTimeout(() => {
      // This update will be ignored if component unmounts
      setSafeState((prevState) => ({
        ...prevState,
        count: prevState.count + 1,
      }));
    }, 3000);
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <p>Last updated: {new Date(state.lastUpdated).toLocaleTimeString()}</p>
      <button onClick={increment}>Increment Now</button>
      <button onClick={delayedIncrement}>Increment in 3s</button>
    </div>
  );
}

Parameters

Argument valueTypeDescriptionDefault
initialStateTRequired initial state value

Return value

Returns an array with two elements:

Return valueTypeDescriptionDefault
stateTThe current state value
safeSetStateDispatch<SetStateAction<T>>Updates state only while the component is mounted

Behavior and lifecycle

The setter first calls the stable mounted-state reader from useGetIsMounted. It forwards the value or functional updater to React only while mounted; after the layout-effect cleanup marks the component unmounted, calls are ignored.

It does not cancel a request, timer, subscription, or other asynchronous work. Clean those resources up independently when possible; this hook only prevents their late completion callbacks from changing state.

Use Cases

  • Async API calls: Prevent state updates from completed requests after navigation
  • Timers and intervals: Avoid state updates from delayed operations
  • Event handlers: Safe state updates in long-running event callbacks
  • Cleanup-sensitive operations: Any operation that might complete after component unmount

Compatibility and accessibility

The state logic itself is platform-independent and SSR-safe. As with useState, accessibility depends on the controls and status UI built from the value.

On this page