Enhancing Workout Tracking: Introducing Per-Set Routine Data in Gymflow

In the gymflow project, we're dedicated to building flexible and powerful fitness tracking tools. Traditionally, workout routines often prescribe a single set of repetitions and weight for an exercise. While simple, this approach doesn't always reflect the nuanced reality of progressive overload or varying intensity across sets. To address this, we've rolled out a significant enhancement: the ability to define distinct reps and weight for each individual set within an exercise.

The Problem with Static Data

Imagine an exercise like bicep curls. You might start with a warm-up set at a lighter weight, move to heavier working sets, and finish with a lighter drop set. A fixed "10 reps, 20kg" for all sets fails to capture this progression. Our previous system, storing a single reps and weight value per exercise, limited users from accurately logging or planning such dynamic workouts.

Introducing Per-Set Flexibility

The core of this feature lies in a new JSONB column, sets_data, added to our routine_exercises table. This column now allows us to store a flexible array of objects, each representing a specific set with its own reps and weight values. This approach provides immense flexibility, adapting to anything from pyramiding sets to individualized warm-ups.

Database Schema Evolution (SQL)

To support this, a migration was necessary. We leveraged Supabase's PostgreSQL capabilities to add the sets_data column, ensuring it's optional for backward compatibility.

ALTER TABLE routine_exercises
ADD COLUMN sets_data JSONB;

This SQL command introduces the sets_data column, capable of storing structured JSON data directly within the database, making it ideal for our per-set requirements. For example, sets_data could store [{"set_number": 1, "reps": 12, "weight": 10}, {"set_number": 2, "reps": 10, "weight": 12.5}].

Frontend Impact: React Components (TypeScript)

On the frontend, built with React and TypeScript, this change necessitated updates across several key components.

  • Admin Editor: The routine editor now features a toggle to switch between scalar (old) and per-set (new) input modes. When per-set is active, sub-rows appear for desktop or collapsible sections for mobile, allowing users to define each set individually.
  • User Workout: During a live workout, users now see individual input fields for reps and weight for each set. Each completed set results in a distinct workout_log entry, ensuring granular progress tracking. Baselines are also tracked per (exercise_id, set_number).
  • Progress View: A new "By Day" tab in the progress section allows users to compare their performance set-by-set, visualizing improvements or changes over time.

Here's a simplified TypeScript interface for how sets_data might be represented and used in a React component:

interface SetData {
  set_number: number;
  reps: number | null;
  weight: number | null;
}

interface RoutineExercise {
  id: string;
  exercise_name: string;
  // Old scalar values
  reps: number | null;
  weight: number | null;
  // New per-set data
  sets_data: SetData[] | null;
}

function WorkoutSetInputs({ exercise }: { exercise: RoutineExercise }) {
  if (exercise.sets_data && exercise.sets_data.length > 0) {
    return (
      <div>
        {exercise.sets_data.map((set, index) => (
          <div key={index}>
            <span>Set {set.set_number}: </span>
            <input type="number" placeholder="Reps" defaultValue={set.reps || ''} />
            <input type="number" placeholder="Weight" defaultValue={set.weight || ''} />
          </div>
        ))}
      </div>
    );
  }
  // Fallback for old scalar data
  return (
    <div>
      <input type="number" placeholder="Reps" defaultValue={exercise.reps || ''} />
      <input type="number" placeholder="Weight" defaultValue={exercise.weight || ''} />
    </div>
  );
}

This WorkoutSetInputs component demonstrates how we dynamically render input fields based on whether sets_data is available, gracefully handling both new and old data structures.

Ensuring Backward Compatibility

A crucial aspect of this rollout was maintaining backward compatibility. Existing routines and exercises that do not have sets_data defined continue to function seamlessly, relying on the older scalar reps and weight values. This ensures that all users can benefit from the update without needing to modify their existing workout plans.

Conclusion

By introducing per-set routine data, gymflow now offers a significantly more precise and adaptable way to track workouts. This allows users to accurately log complex routines, visualize progress with greater granularity, and ultimately achieve their fitness goals more effectively. The use of JSONB for flexible schema and careful frontend implementation ensures a smooth transition and enhanced user experience.

Actionable Takeaway: When evolving your application's data models, consider using flexible data types like JSONB in your database to accommodate future changes and varied data structures, especially when dealing with nested or dynamic attributes, while always prioritizing backward compatibility for a seamless user experience.


Generated with Gitvlg.com

K

KamelotDeveloper

Author

Share: