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:
The hook expects to receive either:
- A cleanup function (to run when component unmounts or dependencies change)
undefined
(if no cleanup is needed)
Returning any other value type (like
null
, a promise, or primitive) causes this error
In the provided example, the problematic code returns null
conditionally:
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
// ✅ 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:
// ✅ 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:
// ❌ Wrong: async directly in useEffect
useEffect(async () => {
const result = await fetchData();
setData(result);
}, []);
Correct Solutions:
Option 1: Define async function inside useEffect
useEffect(() => {
const fetchData = async () => {
const result = await fetchData();
setData(result);
};
fetchData();
}, []);
Option 2: IIFE pattern
useEffect(() => {
(async () => {
const user = await getUser();
setUser(user);
})();
}, []);
Option 3: External async function
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:
// ❌ Implicitly returns the result of functionA() -> might not be function
useEffect(() =>
condition ? functionA() : functionB(),
[]);
Solution: Add explicit function body
// ✅ Explicit body -> returns undefined
useEffect(() => {
condition ? functionA() : functionB();
}, []);
Best Practices for useEffect
Cleanup discipline:
- Return only functions or nothing
- Always return cleanup functions for subscriptions and event listeners
jsxuseEffect(() => { const timer = setInterval(() => {}, 1000); return () => clearInterval(timer); // ✅ Proper cleanup }, []);
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]);
Async pattern choices:
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]);
- Never disable
Real-World Example Fix
Original error case fix with optimizations:
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:
- Removed invalid
return null
- Added optional chaining (
user?.
) - Included
navigate
in dependencies - Removed ESLint disable comment
- 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.