Hone logo
Hone
Problems

Implement a Custom useTransition Hook in React

This challenge asks you to build a custom React hook, useTransition, that mimics the functionality of React's built-in useTransition hook. This hook is crucial for managing UI updates, allowing you to defer non-urgent state updates and prevent them from blocking the main thread, leading to a smoother user experience.

Problem Description

You need to create a custom React hook named useTransition that takes a callback function as an argument. This callback function will likely contain a state update. The useTransition hook should return an array containing two elements:

  1. A transition function: This function, when called, will execute the provided callback but mark the associated state update as non-urgent. This means React can interrupt or defer this update if a more urgent update (like user input) comes along.
  2. A boolean indicating pending state: A boolean value that is true when a non-urgent transition is in progress and false otherwise.

Key Requirements:

  • The hook should accept a callback function. This callback is expected to trigger a state update.
  • The hook should return a tuple: [transitionFunction, isPending].
  • transitionFunction should execute the provided callback.
  • isPending should be true from the moment transitionFunction is called until the associated state update has been fully committed by React.
  • You should leverage React's built-in startTransition API to achieve the non-urgent nature of the state updates.
  • The isPending status should reflect the state of the transition itself, not necessarily the end state of the component after the transition.

Expected Behavior:

When transitionFunction is invoked, the state update within the callback should be performed in a deferred manner. While this update is being processed, isPending should be true. Once the update is complete and rendered, isPending should become false.

Edge Cases to Consider:

  • What happens if transitionFunction is called multiple times in quick succession? The isPending state should correctly reflect the latest transition.
  • What if the callback function throws an error? The hook should ideally not break and isPending should eventually reset. (For this challenge, we can assume the callback doesn't throw, or if it does, isPending can remain true or reset based on your implementation's robustness.)

Examples

Example 1: Simple Transition

Let's imagine a search input where typing triggers a search query. We want the search results to update without blocking the typing input.

// Assuming you have a component like this:
function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<string[]>([]);
  const [isSearching, startTransition] = useTransition(); // Our custom hook

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newQuery = e.target.value;
    setQuery(newQuery); // Urgent update for the input field

    // Defer the search result update
    startTransition(() => {
      // Simulate fetching search results
      setTimeout(() => {
        setResults([`Result for ${newQuery} 1`, `Result for ${newQuery} 2`]);
      }, 500);
    });
  };

  return (
    <div>
      <input type="text" value={query} onChange={handleChange} />
      {isSearching && <p>Searching...</p>}
      <ul>
        {results.map(result => <li key={result}>{result}</li>)}
      </ul>
    </div>
  );
}

Explanation: When the user types, setQuery is an immediate, urgent update. startTransition wraps the logic that fetches and sets search results. While the setTimeout is active and setResults hasn't completed, isSearching will be true, and "Searching..." will be displayed. As soon as the results are updated and rendered, isSearching becomes false.

Example 2: Handling Rapid Input

Consider a scenario with very fast typing.

// Component using the hook
function FastInputComponent() {
  const [text, setText] = useState('');
  const [processedText, setProcessedText] = useState('');
  const [isProcessing, startTransition] = useTransition();

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newText = e.target.value;
    setText(newText); // Urgent

    startTransition(() => {
      // Simulate a potentially slow processing step
      setTimeout(() => {
        setProcessedText(`Processed: ${newText.toUpperCase()}`);
      }, 300);
    });
  };

  return (
    <div>
      <input type="text" value={text} onChange={handleInputChange} />
      {isProcessing && <p>Processing...</p>}
      <p>{processedText}</p>
    </div>
  );
}

Explanation: If the user types very quickly, setText updates the input immediately. However, the setProcessedText will only happen after the 300ms delay. If another character is typed before the 300ms delay completes, the previous transition's "Processing..." might be briefly shown, but the isProcessing state will correctly reflect the latest pending transition. The final processedText will reflect the most recent input that completed its transition.

Constraints

  • Your useTransition hook must use React's startTransition API internally.
  • Your hook must return a tuple [transitionFunction: (...args: any[]) => void, isPending: boolean].
  • The isPending state should be managed correctly based on the startTransition lifecycle.
  • The implementation should be in TypeScript.

Notes

  • This exercise is about understanding how to wrap startTransition to provide both the transition function and the pending status.
  • Think about how React manages the lifecycle of a transition. You'll need to track when a transition starts and ends to update your isPending state.
  • Consider using useState and useEffect within your custom hook to manage the isPending state.
  • The startTransition function itself doesn't directly expose a "pending" status. You'll need to infer it. Think about how you might know when startTransition has finished its work.

Good luck! This is a fundamental hook for building performant and responsive React applications.

Loading editor...
typescript