Skip to main content

Overview

Tune Me In includes several custom React hooks for common UI patterns and functionality. These hooks provide reusable logic for adaptive image sizing, managing timeouts, toggling boolean states, and tracking previous values.

useAdaptiveImage

Hook for dynamically calculating optimal image sizes based on container dimensions and screen size.

Import

import useAdaptiveImage from '../hooks/useAdaptiveImage';

Parameters

imageRef
HTMLElement
Reference to the image DOM element (from useRef or ref callback)
adaptiveSize
boolean
Whether to enable adaptive sizing. When false, returns default values.

Return Value

boundingRect
DOMRect | null
The bounding rectangle of the image element
preferredWidth
number | null
The calculated preferred width from the set: [200, 400, 600, 900, 1200, 1600]
isAdaptive
boolean
Whether adaptive sizing is currently active

Usage Example

import {useRef} from 'react';
import useAdaptiveImage from '../hooks/useAdaptiveImage';

function ResponsiveImage({src, alt}) {
  const imageRef = useRef(null);
  const {preferredWidth, isAdaptive} = useAdaptiveImage(imageRef.current, true);

  const imageUrl = preferredWidth
    ? `${src}?w=${preferredWidth}`
    : src;

  return (
    <img
      ref={imageRef}
      src={imageUrl}
      alt={alt}
      className="responsive-image"
    />
  );
}

How It Works

  1. Listens for window resize events
  2. Calculates the current image container width
  3. Rounds down to nearest 100px
  4. Selects the closest preferred size from [200, 400, 600, 900, 1200, 1600]
  5. Re-calculates when screen dimensions change

Preferred Sizes

The hook uses these predefined sizes for optimal image delivery:
const preferredSizes = [200, 400, 600, 900, 1200, 1600];
These sizes are chosen to balance image quality with performance across common device widths.

useTimeout

Hook for managing setTimeout with automatic cleanup and reset functionality.

Import

import useTimeout from '../hooks/useTimeout';

Parameters

callback
function
required
The function to execute after the delay
delay
number
required
Delay in milliseconds before executing the callback

Return Value

reset
function
Function to clear and restart the timeout
clear
function
Function to clear the timeout without restarting

Usage Example

import {useState} from 'react';
import useTimeout from '../hooks/useTimeout';

function SearchInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const searchCallback = () => {
    if (query) {
      // Perform search after user stops typing
      fetch(`/api/search?q=${query}`)
        .then(res => res.json())
        .then(setResults);
    }
  };

  const {reset} = useTimeout(searchCallback, 500);

  const handleChange = (e) => {
    setQuery(e.target.value);
    reset(); // Reset timeout on each keystroke
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleChange}
        placeholder="Search..."
      />
      {/* Render results */}
    </div>
  );
}

Features

  • Automatically sets up timeout on mount and when dependencies change
  • Cleans up timeout on unmount
  • Callback reference is kept up to date without resetting the timer
  • Provides manual control with reset() and clear() functions

useToggle

Hook for managing boolean state with toggle functionality.

Import

import {useToggle} from '../hooks';
// or
import useToggle from '../hooks/useToggle';

Parameters

defaultValue
boolean
The initial boolean value (defaults to undefined)

Return Value

Returns a tuple [value, toggleValue]:
value
boolean
The current boolean value
toggleValue
function
Function to toggle or set the valueSignature:
(newValue?: boolean) => void
  • Called with no arguments: toggles the current value
  • Called with a boolean: sets the value explicitly

Usage Examples

Basic Toggle

src/components/simplistic/ProductCardDetails.client.jsx
import {useToggle} from '../../hooks';

function ProductCardDetails() {
  const [isHovering, setIsHovering] = useToggle(false);

  return (
    <div
      onMouseEnter={() => setIsHovering(true)}
      onMouseLeave={() => setIsHovering(false)}
      className={`product-card ${isHovering ? 'hovered' : ''}`}
    >
      {/* Product content */}
    </div>
  );
}
import {useToggle} from '../hooks';

function ModalTrigger() {
  const [isOpen, setIsOpen] = useToggle(false);

  return (
    <>
      <button onClick={() => setIsOpen()}>Toggle Modal</button>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      <button onClick={() => setIsOpen(false)}>Close Modal</button>
      
      {isOpen && (
        <Modal onClose={() => setIsOpen(false)}>
          <p>Modal content</p>
        </Modal>
      )}
    </>
  );
}
function NavigationMenu() {
  const [isExpanded, toggleExpanded] = useToggle(false);

  return (
    <nav>
      <button onClick={() => toggleExpanded()}>
        {isExpanded ? 'Collapse' : 'Expand'} Menu
      </button>
      
      {isExpanded && (
        <ul>
          <li><a href="/">Home</a></li>
          <li><a href="/about">About</a></li>
        </ul>
      )}
    </nav>
  );
}

usePrevious

Hook for tracking the previous value of a variable across renders.

Import

import usePrevious from '../hooks/usePrevious';

Parameters

value
any
required
The value to track. Can be any type.

Return Value

previousValue
any
The value from the previous render, or undefined on first render

Usage Example

src/components/simplistic/HeaderScroll.client.jsx
import {useState, useEffect} from 'react';
import {useLocation} from 'react-router-dom';
import usePrevious from '../../hooks/usePrevious';

function HeaderScroll() {
  const location = useLocation();
  const prevLocation = usePrevious(location.pathname);
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  useEffect(() => {
    // Close mobile menu when route changes
    if (location.pathname !== prevLocation) {
      setMobileMenuOpen(false);
    }
  }, [location, prevLocation]);

  return (
    <header>
      {/* Header content */}
    </header>
  );
}

Common Use Cases

Detecting Value Changes

function AnimatedCounter({count}) {
  const prevCount = usePrevious(count);
  const isIncreasing = count > (prevCount ?? 0);

  return (
    <div className={isIncreasing ? 'counting-up' : 'counting-down'}>
      {count}
    </div>
  );
}

Comparing Props

function UserProfile({userId}) {
  const prevUserId = usePrevious(userId);
  
  useEffect(() => {
    if (userId !== prevUserId) {
      console.log(`User changed from ${prevUserId} to ${userId}`);
      // Fetch new user data
    }
  }, [userId, prevUserId]);

  return <div>{/* Profile content */}</div>;
}

Tracking Form Changes

function FormWithUnsavedChanges({initialData}) {
  const [formData, setFormData] = useState(initialData);
  const prevFormData = usePrevious(formData);
  const hasChanges = JSON.stringify(formData) !== JSON.stringify(prevFormData);

  return (
    <form>
      {hasChanges && (
        <div className="alert">You have unsaved changes</div>
      )}
      {/* Form fields */}
    </form>
  );
}

Implementation Details

The hook uses a ref to store the previous value:
import {useEffect, useRef} from 'react';

const usePrevious = (value) => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
Key characteristics:
  • Returns undefined on the first render
  • Updates after each render completes
  • Doesn’t cause re-renders when updated
  • Safe for any value type (primitives, objects, arrays)

Hook Exports

The hooks are exported from src/hooks/index.js:
import {useToggle} from './useToggle';

export {useToggle};
Other hooks are imported directly from their individual files:
import useAdaptiveImage from '../hooks/useAdaptiveImage';
import usePrevious from '../hooks/usePrevious';
import useTimeout from '../hooks/useTimeout';