Hone logo
Hone
Problems

React useMounted Hook Challenge

Many React applications need to perform actions only when a component is mounted or prevent actions from running after a component has unmounted. This often leads to boilerplate code to manage these lifecycle concerns. This challenge asks you to create a custom React hook, useMounted, that simplifies this process.

Problem Description

You need to create a custom React hook named useMounted that returns a boolean value. This boolean should indicate whether the component using the hook is currently mounted. The hook should return true when the component has successfully mounted and false once it has unmounted.

Key Requirements:

  • Hook Name: useMounted
  • Return Value: A boolean (true if mounted, false if unmounted).
  • Lifecycle Management: The hook must accurately track the mounting and unmounting state of the component.
  • Efficiency: The hook should be performant and not introduce unnecessary re-renders.

Expected Behavior:

  1. When a component using useMounted first renders, the hook should return true.
  2. When the component unmounts, the hook should update its internal state to false. Any subsequent checks for the mounted status should return false.

Edge Cases:

  • Initial Render: Ensure the hook correctly returns true immediately after the initial render.
  • Concurrent Rendering (if applicable to your React version): While not strictly enforced by a constraint, consider how your solution behaves in concurrent mode.

Examples

Example 1:

Let's imagine a UserProfile component that fetches data. It should only update its state if it's still mounted.

import React, { useState, useEffect } from 'react';
import { useMounted } from './useMounted'; // Assuming your hook is in './useMounted'

function UserProfile({ userId }: { userId: string }) {
  const [userData, setUserData] = useState<any>(null);
  const isMounted = useMounted();

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

        // Crucial check: Only update state if the component is still mounted
        if (isMounted.current) {
          setUserData(data);
        }
      } catch (error) {
        console.error("Failed to fetch user:", error);
      }
    };

    fetchUser();

    // Cleanup function (optional for this hook, but good practice)
    return () => {
      // Any cleanup related to the fetch operation could go here
    };
  }, [userId, isMounted]); // isMounted.current is stable, so no infinite loop

  return (
    <div>
      {userData ? (
        <h2>{userData.name}</h2>
      ) : (
        <p>Loading user data...</p>
      )}
    </div>
  );
}

// Usage in App.tsx
function App() {
  const [showProfile, setShowProfile] = useState(true);

  return (
    <div>
      <button onClick={() => setShowProfile(!showProfile)}>
        {showProfile ? 'Hide Profile' : 'Show Profile'}
      </button>
      {showProfile && <UserProfile userId="123" />}
    </div>
  );
}

Output (when showProfile is true and the component mounts):

The UserProfile component will render, and isMounted.current will be true. Data fetching will proceed, and setUserData will be called with the fetched data.

Output (if the Hide Profile button is clicked before the data fetches and setUserData is called):

The UserProfile component will unmount. If the fetchUser promise resolves after unmounting, the if (isMounted.current) check will prevent setUserData from being called, thus avoiding a React "Can't perform a React update on an unmounted component" warning.

Explanation:

The useMounted hook is used within UserProfile. The useEffect hook attempts to fetch data. Before updating the state with the fetched data, it checks isMounted.current. If the component has unmounted, isMounted.current will be false, and the state update will be skipped.

Example 2:

Consider a component that logs messages to the console based on its mount status.

import React, { useEffect } from 'react';
import { useMounted } from './useMounted'; // Assuming your hook is in './useMounted'

function LoggerComponent() {
  const isMounted = useMounted();

  useEffect(() => {
    console.log('LoggerComponent mounted:', isMounted.current); // Will log true

    return () => {
      console.log('LoggerComponent unmounted:', isMounted.current); // Will log false
    };
  }, [isMounted]); // isMounted.current is stable

  return <div>Logging component. Check the console.</div>;
}

// Usage in App.tsx
function App() {
  const [showLogger, setShowLogger] = useState(true);

  return (
    <div>
      <button onClick={() => setShowLogger(!showLogger)}>
        {showLogger ? 'Hide Logger' : 'Show Logger'}
      </button>
      {showLogger && <LoggerComponent />}
    </div>
  );
}

Output (when toggling visibility):

When LoggerComponent mounts: LoggerComponent mounted: true

When LoggerComponent unmounts: LoggerComponent unmounted: false

Explanation:

This demonstrates the hook's ability to reflect the component's lifecycle. isMounted.current correctly shows true during the useEffect's initial run and false in the cleanup phase after unmounting.

Constraints

  • The hook must be implemented in TypeScript.
  • The hook should return a stable reference (e.g., a ref object) to the boolean value. This is important for avoiding unnecessary re-renders in useEffect dependencies.
  • The solution should not rely on external libraries beyond React itself.
  • The hook should be efficient and not introduce significant overhead.

Notes

  • Think about how useEffect and its cleanup functions work in React.
  • Consider using useRef to store the mounted state, as it provides a mutable value that persists across renders without causing re-renders itself.
  • The goal is to provide a reliable way to check if a component is still active in the DOM, preventing common errors related to asynchronous operations after unmounting.
Loading editor...
typescript