Skip to content

React "destroy is not a function" Error: Causes and Solutions

Problem Statement

The "Uncaught TypeError: destroy is not a function" error occurs in React when a useEffect hook incorrectly returns a value that isn't a function. In React's useEffect architecture:

  1. The hook expects to receive either:

    • A cleanup function (to run when component unmounts or dependencies change)
    • undefined (if no cleanup is needed)
  2. Returning any other value type (like null, a promise, or primitive) causes this error

In the provided example, the problematic code returns null conditionally:

jsx
useEffect(() => {
  if (auth && user && user.emailVerified && user.storeName) {
    navigate(`/app/overview`);
    return null; // ❌ Invalid return type
  }
}, [auth, user]);

This error typically appears in development but causes silent failures in production, leading to blank screens or unexpected navigation issues.

Why This Matters

  • Breaks component unmount behavior
  • Causes memory leaks in applications
  • Leads to inconsistent UI states
  • Blocks navigation in React Router

Common Causes and Solutions

Cause 1: Returning Non-Function Values

The most common cause is returning invalid values like null or promises.

Solution: Remove unnecessary returns or provide cleanup function

jsx
// ✅ Correct implementation
useEffect(() => {
  if (auth && user?.emailVerified && user?.storeName) {
    navigate(`/app/overview`);
  }
  // No return needed = returns undefined (valid)
}, [auth, user?.emailVerified, user?.storeName]);

If you intentionally need a cleanup function:

jsx
// ✅ Valid cleanup function
useEffect(() => {
  if (auth && verifiedUser) {
    navigate(`/app/overview`);
    return () => {
      // Cleanup operations here
    };
  }
}, [auth, verifiedUser]);

Cause 2: Async Functions in useEffect

Using async directly in useEffect causes issues because async functions always return a promise.

WARNING

Async functions implicitly return promises. Promises ≠ functions!

Problematic approach:

jsx
// ❌ Wrong: async directly in useEffect
useEffect(async () => {
  const result = await fetchData();
  setData(result);
}, []);

Correct Solutions:

Option 1: Define async function inside useEffect

jsx
useEffect(() => {
  const fetchData = async () => {
    const result = await fetchData();
    setData(result);
  };
  
  fetchData();
}, []);

Option 2: IIFE pattern

jsx
useEffect(() => {
  (async () => {
    const user = await getUser();
    setUser(user);
  })();
}, []);

Option 3: External async function

jsx
const fetchUser = async () => {
  return await auth.getUser();
};

useEffect(() => {
  fetchUser().then(setUser);
}, []);

Cause 3: Syntax Errors in Arrow Functions

Missing curly braces causes implicit returns that might not be functions.

Problem:

jsx
// ❌ Implicitly returns the result of functionA() -> might not be function
useEffect(() => 
  condition ? functionA() : functionB(), 
[]);

Solution: Add explicit function body

jsx
// ✅ Explicit body -> returns undefined
useEffect(() => {
  condition ? functionA() : functionB();
}, []);

Best Practices for useEffect

  1. Cleanup discipline:

    • Return only functions or nothing
    • Always return cleanup functions for subscriptions and event listeners
    jsx
    useEffect(() => {
      const timer = setInterval(() => {}, 1000);
      return () => clearInterval(timer); // ✅ Proper cleanup
    }, []);
  2. Dependency management:

    • Include all dependencies used inside effect
    • Use object destructuring for deep comparisons:
      jsx
      // Instead of [user], use specific properties:
      const { emailVerified, storeName } = user;
      useEffect(() => {}, [emailVerified, storeName]);
  3. Async pattern choices:

  4. ESLint rules:

    • Never disable exhaustive-deps without justification
    • Fix dependency warnings properly:
      jsx
      // ❌ Avoid
      // eslint-disable-next-line react-hooks/exhaustive-deps
      
      // ✅ Better
      useEffect(() => {}, [user.id, user.profile.status]);

Real-World Example Fix

Original error case fix with optimizations:

jsx
const AuthenticatedRedirect = () => {
  const navigate = useNavigate();
  const { state: { auth, user } } = useContext(AppContext);

  useEffect(() => {
    if (auth && user?.emailVerified && user?.storeName) {
      navigate("/app/overview");
    }
  }, [auth, user?.emailVerified, user?.storeName, navigate]);

  // ... component JSX
};

Key improvements:

  1. Removed invalid return null
  2. Added optional chaining (user?.)
  3. Included navigate in dependencies
  4. Removed ESLint disable comment
  5. Used specific dependencies instead of whole user object

Remember

  • Return nothing if no cleanup needed
  • Never return promises from useEffect
  • Always return functions for cleanup operations

Proper handling of useEffect return values prevents this error and ensures stable component behavior. By following these patterns, you maintain clean component lifecycles and avoid runtime exceptions.