Jehlani Luciano Logo

Component Choice

Guidelines for choosing between Astro and React components

astro
          
            # Astro Component Choice Guidelines (`.astro` vs. React)

## Purpose

This rule guides the decision on when to use native Astro components (`.astro`) versus framework components (specifically React `.jsx`/`.tsx` in this context) within an Astro project. The goal is to leverage Astro's performance benefits while using framework components strategically for interactivity.

## 1. Default to Astro Components (`.astro`)

- **Priority:** Start with Astro components for most UI elements.
- **Use Case:** Ideal for static content, page/component structure, layouts, and presentational elements that do not require client-side JavaScript for state management or complex interactivity.
- **Benefits:**
  - Aligns with Astro's "zero JS by default" philosophy, leading to faster load times.
  - Renders to HTML/CSS only, minimizing client-side footprint.
- **Simple Interactivity:** For basic interactions (e.g., toggling a class), consider using standard `<script>` tags within the `.astro` component before reaching for a framework component.

## 2. Use React Components (`.jsx`/`.tsx`) For Interactivity

- **Use Case:** Choose React components when **client-side JavaScript is necessary** for the component's core functionality.
- **Specific Scenarios:**
  - **State Management:** Components needing to manage internal state (e.g., using `useState`, `useEffect`).
  - **Complex Event Handling:** User interactions tightly coupled with component state or requiring sophisticated logic.
  - **React Ecosystem Integration:** Utilizing React-specific libraries (state managers, hooks) or third-party React component libraries (e.g., Shadcn UI).
  - **Reusing Existing Logic:** Integrating pre-existing React components or hooks.

## 3. Understand the "Island Architecture"

- React components act as "Islands of Interactivity" within your Astro application.
- They are isolated units that Astro hydrates on the client-side using `client:*` directives (e.g., `client:load`, `client:idle`, `client:visible`). This controls _when_ the component's JavaScript is loaded and executed.

## 4. Performance Consideration

- Each React component island adds JavaScript to the client, increasing bundle size and potentially impacting performance compared to a pure `.astro` component.
- Be deliberate: Use React components where their interactive capabilities provide significant value, not just because React is available.

## Example Mindset

- **Site Layout (`src/layouts/MainLayout.astro`):** Use `.astro`. It's structure.
- **Blog Post Content (`src/pages/posts/[slug].astro`):** Use `.astro` (or `.md`/`.mdx` with an `.astro` layout). It's primarily content.
- **Interactive Data Table (`src/components/InteractiveTable.jsx`):** Use React. Needs client-side sorting, filtering, state. Embed with `<InteractiveTable client:visible />`.
- **Simple Button (`src/components/ui/button.tsx` from Shadcn):** Use React (as provided by Shadcn). Although visually simple, it might encapsulate accessibility logic or styles best handled within the React component model. Embed with `<Button client:load />` if needed immediately, or perhaps `client:idle`.

## Code Examples

### ❌ INCORRECT: Using React for static content

```jsx
// Unnecessary React component for static content
// src/components/StaticHero.jsx
import React from "react";

export default function StaticHero() {
  return (
    <div className="py-20 text-center bg-blue-100">
      <h1 className="text-4xl font-bold">Welcome to My Site</h1>
      <p className="mt-4">This content never changes or needs interactivity</p>
    </div>
  );
}
```

```astro
<!-- Using that React component in an Astro page -->
---
import StaticHero from '../components/StaticHero.jsx';
---
<StaticHero client:load />
```

### ✅ CORRECT: Using Astro for static content

```astro
<!-- src/components/StaticHero.astro -->
---
// No props or logic needed
---
<div class="py-20 text-center bg-blue-100">
  <h1 class="text-4xl font-bold">Welcome to My Site</h1>
  <p class="mt-4">This content never changes or needs interactivity</p>
</div>
```

```astro
<!-- Using the Astro component -->
---
import StaticHero from '../components/StaticHero.astro';
---
<StaticHero />
```

### ❌ INCORRECT: Complex script inside Astro component

```astro
<!-- src/components/ComplexForm.astro -->
---
// Import data or props
---
<form class="mt-6 space-y-4">
  <input type="text" id="name" class="border p-2 w-full" />
  <input type="email" id="email" class="border p-2 w-full" />
  <!-- more form fields -->
  <button type="submit" class="bg-blue-500 text-white px-4 py-2">Submit</button>
</form>

<script>
  // Complex form validation with state management
  let errors = {};
  let formData = {};

  function validateName() {
    const nameInput = document.getElementById('name');
    if (nameInput.value.length < 2) {
      errors.name = 'Name is too short';
      showError(nameInput, errors.name);
    } else {
      delete errors.name;
      clearError(nameInput);
    }
    formData.name = nameInput.value;
  }

  // Many more validation functions and state management
  // Getting verbose and hard to maintain
  // ...

  document.querySelector('form').addEventListener('submit', (e) => {
    e.preventDefault();
    validateForm();
    if (Object.keys(errors).length === 0) {
      submitData(formData);
    }
  });
</script>
```

### ✅ CORRECT: React component for complex interactive form

```tsx
// src/components/ComplexForm.tsx
import { useState } from "react";
import { z } from "zod";

const formSchema = z.object({
  name: z.string().min(2, "Name is too short"),
  email: z.string().email("Invalid email address"),
  // more validations
});

export default function ComplexForm() {
  const [formData, setFormData] = useState({ name: "", email: "" });
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    try {
      formSchema.parse(formData);
      // Submit data
      console.log("Submitting:", formData);
    } catch (error) {
      setErrors(error.flatten().fieldErrors);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="mt-6 space-y-4">
      <div>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
          className="border p-2 w-full"
        />
        {errors.name && <p className="text-red-500 text-sm">{errors.name}</p>}
      </div>

      <div>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          className="border p-2 w-full"
        />
        {errors.email && <p className="text-red-500 text-sm">{errors.email}</p>}
      </div>

      <button type="submit" className="bg-blue-500 text-white px-4 py-2">
        Submit
      </button>
    </form>
  );
}
```

```astro
<!-- Using the React form in an Astro page -->
---
import ComplexForm from '../components/ComplexForm.tsx';
---
<ComplexForm client:visible />
```