Hone logo
Hone
Problems

Create a usePrevious Hook in React (TypeScript)

This challenge focuses on building a custom React hook, usePrevious, that allows you to easily access the previous value of any state or prop within your functional components. This is a common requirement for tracking changes, implementing animations, or performing side effects based on value transitions.

Problem Description

Your task is to implement a TypeScript hook called usePrevious that accepts a value as an argument and returns the value from the previous render cycle. This hook should be generic, meaning it can work with any type of value.

Key Requirements:

  • The hook should accept a single argument: the current value.
  • The hook should return the value that was passed to it in the previous render.
  • On the initial render, the hook should return undefined because there is no previous value.
  • The hook must be implemented in TypeScript and be type-safe.

Expected Behavior:

When a component using usePrevious re-renders, the hook should return the value that was present before the re-render occurred. The current value will be stored internally to be used as the "previous" value in the next render.

Edge Cases to Consider:

  • Initial Render: As mentioned, the hook should return undefined on the very first render.
  • Any Data Type: The hook should correctly handle and preserve the type of the value passed to it.

Examples

Example 1: Simple Counter

Let's imagine a component that displays a counter and its previous value.

import React, { useState } from 'react';
import { usePrevious } from './usePrevious'; // Assuming your hook is in this file

function CounterComponent() {
  const [count, setCount] = useState(0);
  const previousCount = usePrevious(count);

  return (
    <div>
      <h1>Current Count: {count}</h1>
      <p>Previous Count: {previousCount === undefined ? 'N/A' : previousCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

// In your App.tsx or similar:
// <CounterComponent />

Input (State and Render Cycles):

  1. Initial Render: count is 0.
  2. After clicking "Increment": count becomes 1.
  3. After clicking "Increment" again: count becomes 2.

Expected Output (Rendered HTML):

  1. Initial Render:
    <div>
      <h1>Current Count: 0</h1>
      <p>Previous Count: N/A</p>
      </div>
    
  2. After first increment:
    <div>
      <h1>Current Count: 1</h1>
      <p>Previous Count: 0</p>
      </div>
    
  3. After second increment:
    <div>
      <h1>Current Count: 2</h1>
      <p>Previous Count: 1</p>
      </div>
    

Explanation:

  • On the initial render, usePrevious(0) returns undefined.
  • When count changes to 1, usePrevious(1) is called. Internally, the hook stores 0 from the previous render and returns 0.
  • When count changes to 2, usePrevious(2) is called. Internally, the hook stores 1 from the previous render and returns 1.

Example 2: Handling Different Data Types

import React, { useState } from 'react';
import { usePrevious } from './usePrevious';

function DataDisplayComponent() {
  const [data, setData] = useState<{ name: string } | null>(null);
  const previousData = usePrevious(data);

  return (
    <div>
      <h2>Current Data: {data ? data.name : 'None'}</h2>
      <p>Previous Data: {previousData ? previousData.name : 'None'}</p>
      <button onClick={() => setData({ name: 'Alice' })}>Set Data 1</button>
      <button onClick={() => setData({ name: 'Bob' })}>Set Data 2</button>
      <button onClick={() => setData(null)}>Clear Data</button>
    </div>
  );
}

// In your App.tsx or similar:
// <DataDisplayComponent />

Input (State and Render Cycles):

  1. Initial Render: data is null.
  2. After clicking "Set Data 1": data becomes { name: 'Alice' }.
  3. After clicking "Set Data 2": data becomes { name: 'Bob' }.
  4. After clicking "Clear Data": data becomes null.

Expected Output (Rendered HTML):

  1. Initial Render:
    <div>
      <h2>Current Data: None</h2>
      <p>Previous Data: None</p>
      </div>
    
  2. After "Set Data 1":
    <div>
      <h2>Current Data: Alice</h2>
      <p>Previous Data: None</p>
      </div>
    
  3. After "Set Data 2":
    <div>
      <h2>Current Data: Bob</h2>
      <p>Previous Data: Alice</p>
      </div>
    
  4. After "Clear Data":
    <div>
      <h2>Current Data: None</h2>
      <p>Previous Data: Bob</p>
      </div>
    

Explanation:

This example demonstrates the hook's ability to handle complex object types and null values correctly, preserving the previous state across re-renders.

Constraints

  • The hook must be implemented using React's useState and useEffect hooks (or equivalent principles).
  • The solution must be written in TypeScript.
  • The hook should be performant and not introduce unnecessary re-renders.
  • The hook must be generic, accepting and returning a value of type T.

Notes

  • Think about when the "previous" value should be updated. It's crucial that the update happens after the current value has been rendered and is available to the component.
  • Consider how React's rendering lifecycle affects the timing of updates within your hook.
  • The generic type T for your hook will allow it to work seamlessly with any JavaScript data type.
  • A successful implementation will be reusable, type-safe, and correctly track previous values across renders.
Loading editor...
typescript