Hone logo
Hone
Problems

React Dependency Tracking with Custom Hook

Dependency tracking is crucial for optimizing React component re-renders. While React's useEffect hook provides dependency arrays, sometimes you need more granular control or want to track dependencies beyond simple variables. This challenge asks you to implement a custom hook that provides dependency tracking and prevents unnecessary re-renders based on changes to those dependencies.

Problem Description

You need to create a custom React hook called useDependencyTrack. This hook should accept a function to execute and an array of dependencies. The hook should only re-execute the function when one or more of the dependencies change. It should also return a value that indicates whether the function was re-executed due to a dependency change. This allows components to react differently based on whether a re-render was triggered by a dependency update.

Key Requirements:

  • Dependency Array: The hook must accept an array of dependencies.
  • Re-execution: The provided function should only be re-executed when one or more dependencies change.
  • Change Detection: The hook must accurately detect changes in dependencies using Object.is for comparison.
  • Return Value: The hook must return a tuple: [returnVal, reExecuted], where returnVal is the result of the function execution and reExecuted is a boolean indicating whether the function was re-executed due to a dependency change.
  • Initial Execution: The function should be executed on the initial render.

Expected Behavior:

  • On the initial render, the function should execute, reExecuted should be false, and the return value should be returned.
  • On subsequent renders, if any dependency has changed (according to Object.is), the function should execute, reExecuted should be true, and the return value should be returned.
  • If no dependencies have changed, the function should not execute, reExecuted should be false, and the previously returned value should be returned.

Edge Cases to Consider:

  • Dependencies that are objects or arrays. Object.is is crucial here.
  • Dependencies that are null or undefined.
  • Empty dependency array (function should execute on every render).
  • Function that returns different values on different executions.

Examples

Example 1:

Input:
function MyComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("hello");

  const result = useDependencyTrack(() => {
    console.log("Function executed!");
    return count * 2;
  }, [count, text]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <button onClick={() => setText("world")}>Change Text</button>
    </div>
  );
}

Output:

  • Initial Render: console.log is executed, result is 0, reExecuted is false.
  • Click "Increment Count": console.log is executed, result is 2, reExecuted is true.
  • Click "Change Text": console.log is executed, result is -2, reExecuted is true.
  • Click "Increment Count" again: console.log is not executed, result is 2, reExecuted is false.

Explanation: The function is only re-executed when count or text changes.

Example 2:

Input:
function MyComponent() {
  const [obj, setObj] = useState({ a: 1 });

  const result = useDependencyTrack(() => {
    console.log("Function executed!");
    return obj.a * 2;
  }, [obj]);

  return (
    <div>
      <p>Obj.a: {obj.a}</p>
      <button onClick={() => setObj({ ...obj, a: obj.a + 1 })}>Increment Obj.a</button>
    </div>
  );
}

Output:

  • Initial Render: console.log is executed, result is 2, reExecuted is false.
  • Click "Increment Obj.a": console.log is executed, result is 4, reExecuted is true.

Explanation: Because obj is a new object reference on each update, the dependency change is detected.

Example 3: (Edge Case - Empty Dependency Array)

Input:
function MyComponent() {
  const result = useDependencyTrack(() => {
    console.log("Function executed!");
    return Math.random();
  }, []);

  return (
    <div>
      <p>Result: {result}</p>
    </div>
  );
}

Output:

  • On every render: console.log is executed, result is a new random number, reExecuted is true.

Explanation: With an empty dependency array, the function executes on every render.

Constraints

  • The hook must be implemented in TypeScript.
  • The function passed to the hook must be a function that takes no arguments and returns any type.
  • The dependency array must be an array of any type.
  • The hook must use Object.is for dependency comparison.
  • The solution should be performant and avoid unnecessary re-renders.

Notes

  • Consider using useRef to store the previous dependency values.
  • Think about how to handle the initial execution of the function.
  • Pay close attention to the reExecuted flag and ensure it accurately reflects whether the function was re-executed due to a dependency change.
  • The useDependencyTrack hook should be reusable across different components.
Loading editor...
typescript