14 KiB
14 KiB
Frontend Development Rules for AI/LLM
This document provides guidelines and rules that AI/LLM assistants should follow when writing or modifying frontend code for the Crafting Shop application.
Architecture Principles
State Management
- ALWAYS use React Context API for global state management
- NEVER use Zustand, Redux, or other state management libraries
- Store context in
frontend/src/context/ - Use useContext() hook for accessing context
// ✅ CORRECT
import { useContext } from "react"
import { AppContext } from "../context/AppContext"
function Component() {
const { user, setUser } = useContext(AppContext)
// ...
}
// ❌ WRONG
import { create } from "zustand"
API Calls
- ALWAYS use the custom
useApihook for all API calls - NEVER use axios directly in components
- Import useApi from
frontend/src/hooks/useApi.js
// ✅ CORRECT
import useApi from "../hooks/useApi"
function Products() {
const { api } = useApi()
const [products, setProducts] = useState([])
useEffect(() => {
api.get("/api/products")
.then(response => setProducts(response.data))
.catch(error => console.error(error))
}, [api])
}
// ❌ WRONG
import axios from "axios"
function Products() {
useEffect(() => {
axios.get("/api/products") // Don't do this!
}, [])
}
Code Style
Formatting
- Use double quotes for strings and object properties
- Use functional components with hooks (no class components)
- Use arrow functions for callbacks
- Follow React best practices
- Maximum line length: 100 characters
Naming Conventions
- Components: PascalCase (e.g.,
ProductCard,Navbar) - Hooks: camelCase with
useprefix (e.g.,useProducts,useAuth) - Files: kebab-case (e.g.,
product-card.jsx,navbar.jsx) - Directories: lowercase (e.g.,
components/,pages/)
Component Rules
Functional Components
- ALWAYS use functional components with React hooks
- NEVER use class components
- Destructure props at the top of component
// ✅ CORRECT
import React from "react"
function ProductCard({ name, price, onClick }) {
return (
<div className="product-card" onClick={onClick}>
<h3>{name}</h3>
<p>${price}</p>
</div>
)
}
export default ProductCard
Props Validation
- Use PropTypes for component props
- Define PropTypes at the bottom of file
- Mark required props
import PropTypes from "prop-types"
function ProductCard({ name, price, onClick }) {
// ...
}
ProductCard.propTypes = {
name: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
onClick: PropTypes.func
}
Conditional Rendering
- Use ternary operators for simple conditions
- Use logical AND for conditional elements
- Keep JSX readable
// ✅ CORRECT
{isLoading ? <Loader /> : <Content />}
{error && <ErrorMessage message={error} />}
{user && <WelcomeMessage user={user} />}
Lists Rendering
- ALWAYS provide unique
keyprop - Use
.map()for transforming arrays to elements
// ✅ CORRECT
{products.map(product => (
<ProductCard
key={product.id}
product={product}
/>
))}
Hook Rules
Custom Hooks
- Create custom hooks for reusable logic
- Name hooks with
useprefix - Place hooks in
frontend/src/hooks/
// ✅ CORRECT - Create custom hook
import { useState, useEffect } from "react"
import useApi from "./useApi"
function useProducts() {
const { api } = useApi()
const [products, setProducts] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
api.get("/api/products")
.then(response => setProducts(response.data))
.finally(() => setLoading(false))
}, [api])
return { products, loading }
}
export default useProducts
Effect Rules
- ALWAYS include all dependencies in dependency array
- Use proper cleanup in useEffect
- Avoid infinite loops
// ✅ CORRECT
useEffect(() => {
const fetchData = async () => {
const response = await api.get("/api/products")
setData(response.data)
}
fetchData()
return () => {
// Cleanup
}
}, [api]) // Include all dependencies
Styling Rules
Tailwind CSS
- ALWAYS use Tailwind CSS for styling
- Use utility classes for styling
- Avoid custom CSS files when possible
- Use
classNameprop (notclass)
// ✅ CORRECT
<div className="bg-gray-900 text-white p-4 rounded-lg">
<h1 className="text-2xl font-bold">Welcome</h1>
</div>
// ❌ WRONG
<div class="custom-card"> // Wrong prop name
<h1 class="title">Welcome</h1>
</div>
Dark Mode
- Default to dark mode styling
- Use Tailwind's dark mode utilities if needed
- Maintain consistent dark theme
// ✅ CORRECT
<div className="bg-gray-900 text-gray-100">
<p className="text-gray-300">Dark mode text</p>
</div>
Responsive Design
- Use Tailwind responsive prefixes
- Mobile-first approach
- Test on multiple screen sizes
// ✅ CORRECT
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Cards */}
</div>
Routing Rules
React Router
- Use
react-router-domfor navigation - Define routes in
frontend/src/main.jsx - Use
<Link>for navigation (not<a>)
// ✅ CORRECT
import { Link } from "react-router-dom"
function Navbar() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/products">Products</Link>
<Link to="/cart">Cart</Link>
</nav>
)
}
Protected Routes
- Use context to check authentication
- Redirect to login if not authenticated
- Store JWT in context
// ✅ CORRECT
import { useContext } from "react"
import { Navigate } from "react-router-dom"
import { AppContext } from "../context/AppContext"
function ProtectedRoute({ children }) {
const { token } = useContext(AppContext)
if (!token) {
return <Navigate to="/login" />
}
return children
}
Form Rules
Form Handling
- Use controlled components for forms
- Store form state in useState
- Handle onChange events properly
// ✅ CORRECT
function LoginForm() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
// Handle submission
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="border rounded p-2"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="border rounded p-2"
/>
<button type="submit" className="bg-blue-500 text-white p-2">
Login
</button>
</form>
)
}
Form Validation
- Validate form data before submission
- Show error messages to users
- Disable submit button during submission
// ✅ CORRECT
function RegisterForm() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const handleSubmit = async (e) => {
e.preventDefault()
setError("")
// Validation
if (!email || !password) {
setError("Please fill in all fields")
return
}
if (password.length < 6) {
setError("Password must be at least 6 characters")
return
}
setLoading(true)
try {
// Submit form
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
{error && <div className="text-red-500">{error}</div>}
<button type="submit" disabled={loading} className="...">
{loading ? "Submitting..." : "Register"}
</button>
</form>
)
}
Error Handling Rules
Error Boundaries
- Use error boundaries for component errors
- Display user-friendly error messages
- Log errors for debugging
API Errors
- Catch errors from useApi hook
- Display error messages to users
- Use try-catch for async operations
// ✅ CORRECT
function Products() {
const { api } = useApi()
const [error, setError] = useState(null)
const loadProducts = async () => {
try {
const response = await api.get("/api/products")
setProducts(response.data)
} catch (err) {
setError("Failed to load products")
console.error(err)
}
}
if (error) {
return <div className="text-red-500">{error}</div>
}
// Render products
}
Performance Rules
Memoization
- Use
React.memo()for expensive components - Use
useMemo()for expensive calculations - Use
useCallback()for stable function references
// ✅ CORRECT
import React, { useMemo, useCallback } from "react"
function ExpensiveComponent({ items }) {
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.name.localeCompare(b.name))
}, [items])
const handleClick = useCallback((id) => {
console.log("Clicked:", id)
}, [])
return (
<div>
{sortedItems.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</div>
))}
</div>
)
}
export default React.memo(ExpensiveComponent)
Lazy Loading
- Use React.lazy() for code splitting
- Use Suspense for loading states
- Lazy load routes and heavy components
// ✅ CORRECT
import { lazy, Suspense } from "react"
const Products = lazy(() => import("./pages/Products"))
const Cart = lazy(() => import("./pages/Cart"))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/products" element={<Products />} />
<Route path="/cart" element={<Cart />} />
</Routes>
</Suspense>
)
}
File Structure for New Features
When adding new features, follow this structure:
frontend/src/
├── components/
│ └── feature/
│ ├── FeatureList.jsx
│ └── FeatureCard.jsx
├── pages/
│ └── FeaturePage.jsx
├── context/
│ └── FeatureContext.jsx (if needed)
├── hooks/
│ └── useFeature.js (if needed)
└── services/
└── featureService.js (if needed)
Common Patterns
Creating a New Component
import React, { useState } from "react"
import PropTypes from "prop-types"
function NewComponent({ title, onAction }) {
const [isOpen, setIsOpen] = useState(false)
return (
<div className="bg-gray-800 p-4 rounded">
<h2 className="text-xl font-bold text-white">{title}</h2>
<button
onClick={() => setIsOpen(!isOpen)}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
{isOpen ? "Close" : "Open"}
</button>
{isOpen && (
<div className="mt-4">
<p>Content goes here</p>
<button onClick={onAction}>Action</button>
</div>
)}
</div>
)
}
NewComponent.propTypes = {
title: PropTypes.string.isRequired,
onAction: PropTypes.func.isRequired
}
export default NewComponent
Creating a New Page
import React, { useEffect } from "react"
import useApi from "../hooks/useApi"
function NewPage() {
const { api } = useApi()
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
try {
const response = await api.get("/api/endpoint")
setData(response.data)
} catch (err) {
setError("Failed to load data")
} finally {
setLoading(false)
}
}
fetchData()
}, [api])
if (loading) {
return <div className="text-white">Loading...</div>
}
if (error) {
return <div className="text-red-500">{error}</div>
}
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold text-white mb-4">
New Page
</h1>
{/* Page content */}
</div>
)
}
export default NewPage
Creating a New Context
import React, { createContext, useContext, useState } from "react"
const NewContext = createContext(null)
export function NewContextProvider({ children }) {
const [value, setValue] = useState(null)
return (
<NewContext.Provider value={{ value, setValue }}>
{children}
</NewContext.Provider>
)
}
export function useNewContext() {
const context = useContext(NewContext)
if (!context) {
throw new Error("useNewContext must be used within NewContextProvider")
}
return context
}
DO NOT DO List
❌ NEVER use Zustand, Redux, or other state management
❌ NEVER use axios directly (always use useApi hook)
❌ NEVER use class components
❌ NEVER use class attribute (use className)
❌ NEVER forget key prop in lists
❌ NEVER ignore useEffect dependencies
❌ NEVER use this.state or this.props
❌ NEVER create custom CSS files (use Tailwind)
❌ NEVER use single quotes for strings
❌ NEVER forget error handling
❌ NEVER hardcode API URLs
❌ NEVER skip PropTypes validation
❌ NEVER use <a> for navigation (use <Link>)
Checklist Before Committing
- Component uses functional syntax with hooks
- All API calls use useApi hook
- State uses Context API (not Redux/Zustand)
- All props have PropTypes validation
- Tailwind classes used for styling
- Dark mode styling applied
- Responsive design considered
- Error handling implemented
- Loading states added
- Keys provided for list items
- useEffect dependencies correct
- Code follows project conventions
- Files properly named (kebab-case)