React useLifecycles Hook Challenge
You're tasked with creating a custom React hook, useLifecycles, that encapsulates the logic for handling component lifecycle methods. This hook should provide a clean and declarative way to manage side effects that occur during the mounting and unmounting phases of a React component.
Problem Description
Your goal is to implement a TypeScript hook named useLifecycles. This hook should accept two optional callback functions: onMount and onUnmount.
onMount: This function will be executed once when the component that uses this hook mounts.onUnmount: This function will be executed once when the component that uses this hook unmounts.
The hook should abstract away the complexities of useEffect for these specific lifecycle events, making the code more readable and maintainable.
Key Requirements:
onMountExecution: TheonMountcallback should be invoked precisely once, after the initial render.onUnmountExecution: TheonUnmountcallback should be invoked precisely once, before the component unmounts.- Type Safety: The hook must be written in TypeScript, ensuring type safety for the callback functions.
- Dependency Management: The hook should correctly handle dependencies to ensure
onMountruns only on mount andonUnmountruns only on unmount.
Expected Behavior:
When a component renders and uses useLifecycles, if an onMount callback is provided, it should execute. When that component is subsequently removed from the DOM, if an onUnmount callback is provided, it should execute.
Edge Cases to Consider:
- No callbacks provided: The hook should gracefully handle cases where neither
onMountnoronUnmountare passed. - Only
onMountprovided: TheonUnmountcallback should not execute. - Only
onUnmountprovided: TheonMountcallback should not execute. - Callbacks with dependencies: While the primary goal is mount/unmount, consider how the hook would behave if the provided callbacks themselves had internal dependencies. (For this challenge, we'll assume the callbacks don't need external dependencies passed to the
useLifecycleshook itself, but rather that theuseEffectinside the hook handles the core dependency logic).
Examples
Example 1: Basic Mount and Unmount
import React, { useState } from 'react';
import { useLifecycles } from './useLifecycles'; // Assuming your hook is in this file
function MyComponent() {
const [message, setMessage] = useState('Initializing...');
useLifecycles({
onMount: () => {
setMessage('Component Mounted!');
console.log('Component did mount');
},
onUnmount: () => {
console.log('Component will unmount');
},
});
return <div>{message}</div>;
}
// In your App.tsx:
function App() {
const [showComponent, setShowComponent] = useState(true);
return (
<div>
{showComponent && <MyComponent />}
<button onClick={() => setShowComponent(false)}>Unmount MyComponent</button>
</div>
);
}
Expected Output in Console:
When MyComponent first renders:
Component did mount
When the "Unmount MyComponent" button is clicked:
Component will unmount
Explanation:
The onMount callback logs a message and updates the component's state upon initial rendering. The onUnmount callback logs a message just before the component is removed from the DOM.
Example 2: Handling Missing Callbacks
import React from 'react';
import { useLifecycles } from './useLifecycles'; // Assuming your hook is in this file
function AnotherComponent() {
// Only onMount provided
useLifecycles({
onMount: () => {
console.log('AnotherComponent mounted with only onMount.');
},
});
return <div>Checking lifecycles...</div>;
}
// In your App.tsx:
function App() {
return <AnotherComponent />;
}
Expected Output in Console:
When AnotherComponent first renders:
AnotherComponent mounted with only onMount.
(No "unmount" message will appear as no onUnmount callback was provided.)
Example 3: Only Unmount Callback
import React from 'react';
import { useLifecycles } from './useLifecycles'; // Assuming your hook is in this file
function ThirdComponent() {
// Only onUnmount provided
useLifecycles({
onUnmount: () => {
console.log('ThirdComponent unmounting.');
},
});
return <div>Third component.</div>;
}
// In your App.tsx:
function App() {
const [showThird, setShowThird] = React.useState(true);
return (
<div>
{showThird && <ThirdComponent />}
<button onClick={() => setShowThird(false)}>Hide Third</button>
</div>
);
}
Expected Output in Console:
When the "Hide Third" button is clicked:
ThirdComponent unmounting.
(No "mount" message will appear as no onMount callback was provided.)
Constraints
- The
useLifecycleshook must be implemented in TypeScript. - The
onMountandonUnmountcallbacks should be optional. - The hook should leverage React's
useEffecthook internally for managing the lifecycle effects. - The hook should not introduce any performance bottlenecks. It should be as efficient as a direct
useEffectimplementation for mount/unmount.
Notes
- Think about how
useEffecthandles cleanup functions and empty dependency arrays. - Consider the types for the callback functions. They should be functions that return
voidor a cleanup function. - The goal is to create a reusable and declarative pattern for common lifecycle scenarios.