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 />
```