Skip to main content

Overview

useEquipments is a custom React hook built on React Query that fetches equipment data from the BodyWorks API. It supports an optional limit parameter for controlling the number of results, making it suitable for filters, reference lists, and equipment selection interfaces.

Hook Signature

const useEquipments = (
  limit?: number
) => {
  const {
    isLoading,
    data: equipments,
    error,
    refetch,
    isRefetching,
  } = useQuery({
    queryKey: ["equipments", limit],
    queryFn: () => getEquipments(limit),
    placeholderData: keepPreviousData,
  });
  return { isLoading, equipments, error, refetch, isRefetching };
};

Parameters

limit
number
Optional limit on the number of equipment items to fetch. When omitted, returns all available equipment.
The limit parameter is optional. Call useEquipments() without arguments to fetch all equipment, or specify a number to limit results.

Return Values

The hook returns an object with the following properties:
isLoading
boolean
Indicates whether the initial data is being loaded. true during the first fetch, false once data is available or an error occurs.
equipments
IEquipmentData | undefined
The fetched equipment data object containing:
  • totalEquipments (number): Total number of equipment items available
  • data (IEquipment[]): Array of equipment objects
error
Error | null
Contains error information if the request fails, otherwise null.
refetch
() => Promise<QueryObserverResult>
Function to manually refetch the equipment data.
isRefetching
boolean
Indicates whether the data is being refetched. true during background updates, false otherwise.

Equipment Data Structure

interface IEquipment {
  equipment: string;    // Equipment name (e.g., "barbell", "dumbbell", "bodyweight")
  imageUrl: string;     // Image URL for the equipment
}

interface IEquipmentData {
  totalEquipments: number;
  data: IEquipment[];
}

Usage Examples

import useEquipments from '@/hooks/useEquipments';

function EquipmentList() {
  const { isLoading, equipments, error } = useEquipments();

  if (isLoading) return <div>Loading equipment...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>Available Equipment ({equipments?.totalEquipments})</h2>
      <div className="equipment-grid">
        {equipments?.data.map((item) => (
          <div key={item.equipment} className="equipment-card">
            <img src={item.imageUrl} alt={item.equipment} />
            <h3>{item.equipment}</h3>
          </div>
        ))}
      </div>
    </div>
  );
}

React Query Features

Automatic Caching

The hook uses React Query’s caching with a query key based on the limit parameter:
queryKey: ["equipments", limit]
Different limits create separate cache entries:
  • useEquipments() caches all equipment
  • useEquipments(4) caches the first 4 equipment items
  • Both queries maintain independent caches

Placeholder Data

placeholderData: keepPreviousData
When switching between different limit values, previous data remains visible until new data loads.

Background Refetching

React Query automatically refetches equipment data when:
  • The browser window regains focus
  • The network reconnects
  • The cache becomes stale (configurable)
Use isRefetching to show subtle loading indicators during background updates.

Best Practices

Fetch all for filters: When building filter UIs, call useEquipments() without arguments to ensure all equipment options are available to users.
Use limit for previews: For featured sections or quick-select interfaces, use a small limit (e.g., 4-6) to reduce data transfer.
Display formatted names: Equipment names are stored in lowercase. Transform them for display:
{equipment.equipment.split(' ').map(word => 
  word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')}
Consider icons: For common equipment types, consider using icons alongside or instead of images for faster loading and cleaner UI.
Equipment data is relatively static reference data. Consider configuring longer stale times to minimize unnecessary API calls.

Common Patterns

Equipment Grid with Images

import useEquipments from '@/hooks/useEquipments';
import { useNavigate } from 'react-router-dom';

function EquipmentGrid() {
  const { equipments, isLoading } = useEquipments();
  const navigate = useNavigate();

  if (isLoading) {
    return (
      <div className="grid grid-cols-4 gap-4">
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="skeleton h-32" />
        ))}
      </div>
    );
  }

  return (
    <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
      {equipments?.data.map((item) => (
        <button
          key={item.equipment}
          onClick={() => navigate(`/exercises?equipment=${item.equipment}`)}
          className="group p-4 border rounded-lg hover:border-blue-500 transition"
        >
          <img 
            src={item.imageUrl} 
            alt={item.equipment}
            className="w-full h-24 object-contain mb-2"
          />
          <p className="text-sm font-medium group-hover:text-blue-500">
            {item.equipment}
          </p>
        </button>
      ))}
    </div>
  );
}

Combined with Exercise Filter

import { useState } from 'react';
import useEquipments from '@/hooks/useEquipments';
import useExercises from '@/hooks/useExercises';

function FilteredExercises() {
  const [selectedEquipment, setSelectedEquipment] = useState<string | null>(null);
  const { equipments } = useEquipments();
  const { exercises, isLoading } = useExercises(20, 1);

  const filteredExercises = exercises?.data.filter(exercise => 
    !selectedEquipment || exercise.equipment === selectedEquipment
  );

  return (
    <div>
      <div className="mb-6">
        <h3 className="text-lg font-semibold mb-2">Filter by Equipment</h3>
        <div className="flex flex-wrap gap-2">
          <button
            onClick={() => setSelectedEquipment(null)}
            className={`px-4 py-2 rounded ${
              !selectedEquipment ? 'bg-blue-500 text-white' : 'bg-gray-200'
            }`}
          >
            All
          </button>
          {equipments?.data.map(item => (
            <button
              key={item.equipment}
              onClick={() => setSelectedEquipment(item.equipment)}
              className={`px-4 py-2 rounded ${
                selectedEquipment === item.equipment 
                  ? 'bg-blue-500 text-white' 
                  : 'bg-gray-200'
              }`}
            >
              {item.equipment}
            </button>
          ))}
        </div>
      </div>

      <div className="grid grid-cols-3 gap-4">
        {isLoading ? (
          <p>Loading exercises...</p>
        ) : (
          filteredExercises?.map(exercise => (
            <ExerciseCard key={exercise.id} exercise={exercise} />
          ))
        )}
      </div>
    </div>
  );
}

Radio Button Group

import { useState } from 'react';
import useEquipments from '@/hooks/useEquipments';

function EquipmentSelector() {
  const [selected, setSelected] = useState<string>('');
  const { equipments, isLoading } = useEquipments();

  return (
    <fieldset>
      <legend className="text-lg font-semibold mb-2">
        Select Equipment
      </legend>
      {isLoading ? (
        <p>Loading options...</p>
      ) : (
        <div className="space-y-2">
          {equipments?.data.map((item) => (
            <label 
              key={item.equipment}
              className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded"
            >
              <input
                type="radio"
                name="equipment"
                value={item.equipment}
                checked={selected === item.equipment}
                onChange={(e) => setSelected(e.target.value)}
                className="w-4 h-4"
              />
              <img 
                src={item.imageUrl} 
                alt={item.equipment}
                className="w-8 h-8 object-contain"
              />
              <span className="capitalize">{item.equipment}</span>
            </label>
          ))}
        </div>
      )}
    </fieldset>
  );
}

Lazy Loading Images

import useEquipments from '@/hooks/useEquipments';

function LazyEquipmentList() {
  const { equipments } = useEquipments();

  return (
    <div className="equipment-list">
      {equipments?.data.map((item) => (
        <div key={item.equipment} className="equipment-item">
          <img 
            src={item.imageUrl} 
            alt={item.equipment}
            loading="lazy"
            className="equipment-image"
          />
          <h3>{item.equipment}</h3>
        </div>
      ))}
    </div>
  );
}

Build docs developers (and LLMs) love