Skip to main content

Overview

useBodyParts is a custom React hook built on React Query that fetches body part data from the BodyWorks API. Unlike pagination-focused hooks, this hook supports an optional limit parameter for controlling the number of results, making it ideal for dropdowns, filters, and reference data.

Hook Signature

export const useBodyParts = (
  limit?: number
) => {
  const {
    isLoading,
    data: bodyParts,
    error,
    refetch,
    isRefetching,
  } = useQuery({
    queryKey: ["body-parts", limit],
    queryFn: () => getBodyParts(limit),
    placeholderData: keepPreviousData,
  });
  return { isLoading, bodyParts, error, isRefetching, refetch };
};

Parameters

limit
number
Optional limit on the number of body parts to fetch. When omitted, returns all available body parts.
The limit parameter is optional. Call useBodyParts() without arguments to fetch all body parts, 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.
bodyParts
IBodyPartData | undefined
The fetched body part data object containing:
  • totalBodyParts (number): Total number of body parts available
  • data (IBodyPart[]): Array of body part objects
error
Error | null
Contains error information if the request fails, otherwise null.
isRefetching
boolean
Indicates whether the data is being refetched. true during background updates, false otherwise.
refetch
() => Promise<QueryObserverResult>
Function to manually refetch the body part data.

Body Part Data Structure

interface IBodyPart {
  bodyPart: string;     // Body part name (e.g., "chest", "back", "legs")
  imageUrl: string;     // Image URL for the body part
}

interface IBodyPartData {
  totalBodyParts: number;
  data: IBodyPart[];
}

Usage Examples

import { useBodyParts } from '@/hooks/useBodyParts';

function BodyPartList() {
  const { isLoading, bodyParts, error } = useBodyParts();

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

  return (
    <div>
      <h2>Body Parts ({bodyParts?.totalBodyParts})</h2>
      <div className="body-parts-grid">
        {bodyParts?.data.map((bodyPart) => (
          <div key={bodyPart.bodyPart} className="body-part-card">
            <img src={bodyPart.imageUrl} alt={bodyPart.bodyPart} />
            <h3>{bodyPart.bodyPart}</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: ["body-parts", limit]
Different limits create separate cache entries:
  • useBodyParts() caches all body parts
  • useBodyParts(6) caches the first 6 body parts
  • Both can coexist without conflicts

Placeholder Data

placeholderData: keepPreviousData
Maintains previous data while new data loads, useful when switching between different limit values.

Stale Time Configuration

For relatively static reference data like body parts, you can configure longer stale times:
import { useQuery } from '@tanstack/react-query';

const { bodyParts } = useBodyParts();

// Customize in your own wrapper:
const useBodyPartsWithConfig = (limit?: number) => {
  return useQuery({
    queryKey: ["body-parts", limit],
    queryFn: () => getBodyParts(limit),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  });
};

Best Practices

Use without limit for filters: When building filter UIs, fetch all body parts by calling useBodyParts() without arguments to ensure users can see all available options.
Use with limit for previews: When displaying a preview or featured section, use a small limit (e.g., 4-8) to reduce payload size and improve performance.
Capitalize display names: Body part names are stored in lowercase. Use text transformation for better UI presentation:
{bodyPart.bodyPart.charAt(0).toUpperCase() + bodyPart.bodyPart.slice(1)}
Optimize images: Body part images should be optimized and lazy-loaded, especially when displaying all body parts at once.
Body parts are reference data that typically don’t change frequently. Consider implementing longer cache times to reduce API calls.

Common Patterns

Icon Grid

function BodyPartGrid() {
  const { bodyParts } = useBodyParts();
  const navigate = useNavigate();

  return (
    <div className="grid grid-cols-3 gap-4">
      {bodyParts?.data.map((bodyPart) => (
        <button
          key={bodyPart.bodyPart}
          onClick={() => navigate(`/exercises?bodyPart=${bodyPart.bodyPart}`)}
          className="flex flex-col items-center p-4 hover:bg-gray-100"
        >
          <img 
            src={bodyPart.imageUrl} 
            alt={bodyPart.bodyPart}
            className="w-16 h-16 object-contain"
          />
          <span className="mt-2 text-sm font-medium">
            {bodyPart.bodyPart}
          </span>
        </button>
      ))}
    </div>
  );
}

Combo with Exercises

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

function ExercisesByBodyPart() {
  const [selectedBodyPart, setSelectedBodyPart] = useState<string | null>(null);
  const { bodyParts } = useBodyParts();
  const { exercises } = useExercises(12, 1);

  const filteredExercises = exercises?.data.filter(exercise => 
    !selectedBodyPart || exercise.bodyPart === selectedBodyPart
  );

  return (
    <div>
      <div className="filters">
        <button onClick={() => setSelectedBodyPart(null)}>All</button>
        {bodyParts?.data.map(bodyPart => (
          <button
            key={bodyPart.bodyPart}
            onClick={() => setSelectedBodyPart(bodyPart.bodyPart)}
            className={selectedBodyPart === bodyPart.bodyPart ? 'active' : ''}
          >
            {bodyPart.bodyPart}
          </button>
        ))}
      </div>

      <div className="exercises">
        {filteredExercises?.map(exercise => (
          <ExerciseCard key={exercise.id} exercise={exercise} />
        ))}
      </div>
    </div>
  );
}

Loading State

function BodyPartSelector() {
  const { isLoading, bodyParts, error } = useBodyParts();

  if (error) {
    return <div className="error">Failed to load body parts</div>;
  }

  return (
    <div className="selector">
      {isLoading ? (
        // Show skeleton loaders
        Array.from({ length: 6 }).map((_, i) => (
          <div key={i} className="skeleton" />
        ))
      ) : (
        bodyParts?.data.map(bodyPart => (
          <BodyPartCard key={bodyPart.bodyPart} bodyPart={bodyPart} />
        ))
      )}
    </div>
  );
}

Build docs developers (and LLMs) love