DEV Community

Cover image for From Idea to App: A Fun Ride with React, Inertia & Rails
Josephine Opondo
Josephine Opondo

Posted on

From Idea to App: A Fun Ride with React, Inertia & Rails

Introduction

Building web apps doesn’t have to be boring! In this post, we’ll share quick wins and cool tricks we picked up while creating real features using React on the frontend and Ruby on Rails on the backend—with Inertia tying it all together. Think of it as a behind-the-scenes tour of turning ideas into a smooth, dynamic app.

We'll cover:

  • Decoupling UI Interactions: Ensuring UI elements behave independently.
  • Enhancing User Experience: Implementing confirmation flows for critical actions.
  • Achieving Seamless State Synchronization: Keeping frontend and backend data consistent.
  • Crafting Flexible Components: Building reusable form elements.
  • Extending Backend Data Models: Integrating new data attributes across the stack.

Let's dive into the specifics!

1. Decoupling UI Interactions: The Independent Bookmark Icon
The Challenge: Accidental Clicks
Imagine a bookmark icon on a card. When a user clicks the icon, the entire card also registers a click, leading to unintended navigation. This is a classic case of event bubbling, where a child element's event propagates up to its parents.

The Solution: Stopping Event Propagation
The fix is simple yet powerful: e.stopPropagation(). By adding this to the bookmark button's onClick handler, we prevent the click event from reaching the parent card. This ensures the bookmark action is isolated, providing a clean user experience.

// In your React component

<button

    type=\'button\'

    onClick={(e) => {

        e.stopPropagation(); // Prevents the parent card from also being clicked

        toggleBookmark(e);

    }}

    // ... styling and icon ...

>

    <BookmarkIcon />

</button>

Enter fullscreen mode Exit fullscreen mode

We also ensured the bookmark icon's visual state (e.g., its color) was independent of the card's hover state, allowing it to have its own distinct visual feedback.

Key Takeaway
Mastering event propagation is crucial for building complex UIs. e.stopPropagation() and e.preventDefault() are your best friends for precise control over user interactions, preventing unintended side effects and improving overall usability.

2. Enhancing User Experience: The Unbookmark Confirmation Modal
The Challenge: Preventing Accidental Unbookmarks
While bookmarking should be effortless, unbookmarking can be a destructive action. A single misclick could lead to the loss of a saved item. To prevent this, we needed a confirmation step. Think of it like deletion- you'd want the user to confirm deletion, especially if it's irreversible, right?

The Solution: A Well-Styled, Contextual Modal
We introduced a dedicated UnbookmarkModal component. This modal appears when a user attempts to unbookmark, providing a clear confirmation prompt with the specific item's name for context. It offers options to confirm or cancel the action.

Modal Features:

  • Modern Design: Clean aesthetics with backdrop blur and shadow effects.
  • Contextual Clarity: Displays the item's name (e.g., "Serene Retreat") to avoid ambiguity. Accessibility: Proper ARIA labels, focus management, and keyboard navigation. Clear Actions: Distinct buttons for "Cancel" and a prominent "Remove Bookmark" (often red for destructive actions).
// In your React Component (simplified)

const UnbookmarkModal = ({ isOpen, onClose, onConfirm, name }) => {

  if (!isOpen) return null;

  return (

    <div className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm flex justify-center items-center z-50">

      <div className="bg-white p-6 rounded-lg shadow-xl max-w-sm w-full">

        <h3 className="text-lg font-semibold mb-4">Remove Bookmark</h3>

        <p className="mb-4">Are you sure you want to remove the bookmark for "{name}"?</p>

        <div className="flex justify-end space-x-4">

          <button onClick={onClose} className="px-4 py-2 rounded-md text-gray-600 hover:bg-gray-100">Cancel</button>

          <button onClick={onConfirm} className="px-4 py-2 rounded-md bg-red-600 text-white hover:bg-red-700">Remove Bookmark</button>

        </div>

      </div>

    </div>

  );

};
Enter fullscreen mode Exit fullscreen mode

This modal integrates with our custom useBookmark hook, which now manages the modal's visibility and conditionally triggers it before making the API call to unbookmark.

Key Takeaway
For any critical or irreversible user action, a confirmation step is invaluable. Modals provide an effective pattern for this, and prioritizing clear messaging, accessibility, and intuitive interaction flows significantly enhances the user experience.

3. Achieving Seamless State Synchronization: Frontend and Backend Harmony
The Challenge: Inconsistent Data Across UI
Keeping UI elements consistent with backend data, especially when the same data appears in multiple places (e.g., a bookmark button on a card and on a profile page), is a common hurdle. The bookmark state needed to persist across sessions and update instantly everywhere.

The Solution: Backend as the Single Source of Truth
The most robust strategy is to treat your backend as the definitive source of truth. Frontend components should always initialize their state from backend data. Any changes are sent to the backend via API calls, and the frontend updates only after a successful response.

3.1 Enhancing the useBookmark Hook
Our useBookmark hook was refactored to accept id, type (e.g., "card", "profile"), and initialBookmarked status. This allows each instance of the hook to manage the bookmark state for a specific entity.

// In your custom React hook 

export const useBookmark = ({ id, type, initialBookmarked = false }) => {

  const [bookmarked, setBookmarked] = useState(initialBookmarked);

  // Sync internal state with external prop changes

  useEffect(() => {

    setBookmarked(initialBookmarked);

  }, [initialBookmarked]);

  const toggleBookmark = async () => {

    // ... API call logic to POST or DELETE bookmark ...

    // On success: setBookmarked(!bookmarked);

  };

  return { bookmarked, toggleBookmark, /* ... modal state/handlers ... */ };

};
Enter fullscreen mode Exit fullscreen mode

3.2 Backend Integration with Ruby on Rails
We introduced a bookmarks table in Rails, using polymorphic associations to link bookmarks to various bookmarkable models.

Key Backend Components:

  • Database Migration: Adds user_id, bookmarkable_id, and bookmarkable_type to the bookmarks table.
  • Rails Models: Bookmark model (belongs_to :bookmarkable, polymorphic: true), User model (has_many :bookmarks), and Card model (has_many :bookmarks, as: :bookmarkable).
  • BookmarksController: Provides API endpoints (/bookmarks) for creating (POST) and deleting (DELETE) bookmarks. It ensures user authentication and whitelists bookmarkable_type for security.

Crucially, our backend API responses for Card objects now include an is_bookmarked flag. This flag is passed directly to the frontend components as initialBookmarked, ensuring the UI always reflects the true state from the server.

Key Takeaway
Backend as the single source of truth is non-negotiable for data persistence and consistency. Polymorphic associations offer flexibility for handling diverse relationships. Frontend components should derive their state from backend data, and useEffect is vital for reacting to prop changes. For real-time updates, consider WebSockets (Action Cable).

4. Crafting Flexible Form Components: The Dynamic Dropdown
The Challenge: Reusable Dropdown for Single and Multiple Selections
Forms often need dropdowns that can handle both single and multiple selections. Building separate components for each leads to redundancy. Our goal was a single, configurable DropdownSelection component.

The Solution: A Configurable DropdownSelection Component
We enhanced the DropdownSelection component with new props:

fieldName: Specifies which formData field to update.
maxSelections: Sets the maximum items for multi-select.
singleSelection: A boolean that, when true, enables single-selection mode.

Key Logic:

  • Conditional Handling: handleItemClick checks singleSelection. If true, it replaces the current selection; otherwise, it appends.
  • Read-Only Input: In single-select mode, the input becomes readOnly to enforce selection from the dropdown.
  • Auto-Close: Single-select dropdowns close automatically after an item is chosen.
// simplified handleItemClick

const handleItemClick = (item) => {

  if (singleSelection) {

    setFormData({ ...formData, [fieldName]: item });

    setIsOpen(false); // Close dropdown

  } else {

    // ... multi-select logic ...

  }

};
Enter fullscreen mode Exit fullscreen mode

This component allowed us to use it for a single category selection in one part of the application while maintaining its multi-select capabilities for other parts of the application.

Key Takeaway
Building reusable, configurable components is a cornerstone of efficient frontend development. By using props to control behavior, a single component can serve multiple purposes, reducing code duplication and improving maintainability. This makes your component library robust and adaptable.

Conclusion: The Power of Iterative Development
This journey through various feature developments highlights the power of iterative refinement. Each challenge, from UI glitches to complex state management, provided an opportunity to improve our codebase, enhance user experience, and reinforce best practices.

Key overarching lessons:

  • Attention to UI Detail: Small details like event propagation and independent component states significantly impact usability.
  • User-Centric Design: Confirmation modals prevent errors and provide clear feedback.
  • Robust State Management: A clear source of truth (backend) and consistent data flow are critical.
  • Responsive and Flexible Components: Adaptable components lead to maintainable and scalable applications.
  • Full-Stack Coordination: New features demand changes across both frontend and backend, requiring clear communication.

By systematically addressing these challenges and applying sound engineering principles, we build web applications that are not only powerful and feature-rich but also delightful for users. The continuous cycle of development, testing, and refinement is what ultimately leads to high-quality software.

Top comments (0)