97 lines
2.8 KiB
Markdown
97 lines
2.8 KiB
Markdown
# Agent Coding Guide: Laravel + React SPA with SWR & Zustand
|
||
|
||
Use this for coding guide and use agent-tasks.md for backlog and tracking progress.
|
||
|
||
## 1. Project Structure
|
||
|
||
**Backend (Laravel):**
|
||
- Standard folders:
|
||
- `app/Http/Controllers` (thin, validate, call services)
|
||
- `app/Services` (business logic)
|
||
- `routes/api.php` (API endpoints, RESTful JSON)
|
||
- `app/Models` (Eloquent ORM)
|
||
- `app/Http/Resources` (format API responses)
|
||
- Use Laravel Sanctum for SPA authentication.
|
||
|
||
**Frontend (React):**
|
||
- React app inside `/resources/js/react` for seamless Laravel integration.
|
||
- Functional components with hooks.
|
||
- React Router for client-side routing.
|
||
- **Zustand** for global state management.
|
||
- **SWR** for all server data fetching and caching.
|
||
- Axios as HTTP client used within SWR fetcher function.
|
||
|
||
## 2. Code & Style
|
||
|
||
**Backend (Laravel/PHP):**
|
||
- PSR-12 standard.
|
||
- Controllers handle request, validation, call service methods, return API Resources.
|
||
- Services contain business logic.
|
||
- Use JSON responses throughout.
|
||
|
||
**Frontend (React/JS):**
|
||
- Functional components with hooks and JSX.
|
||
- Manage UI state (modals, forms) locally or via Zustand.
|
||
- Use Axios inside SWR for API calls to leverage SWR caching and revalidation.
|
||
- Leverage SWR hooks (`useSWR`) for data fetching with automatic caching, re-fetching, and error handling—avoid manual state for server data.
|
||
- Use Zustand only for client state that doesn’t come from API (e.g., UI toggles, theme, user session info).
|
||
- Use React Router for SPA navigation, avoiding full page reloads.
|
||
|
||
## 3. Example Usage
|
||
|
||
### Zustand Store (Client UI State)
|
||
```js
|
||
// store/useStore.js
|
||
import create from 'zustand';
|
||
|
||
export const useStore = create(set => ({
|
||
isModalOpen: false,
|
||
openModal: () => set({ isModalOpen: true }),
|
||
closeModal: () => set({ isModalOpen: false }),
|
||
editingPlayer: null,
|
||
setEditingPlayer: (player) => set({ editingPlayer: player }),
|
||
}));
|
||
```
|
||
|
||
### SWR Data Fetching with Axios
|
||
```js
|
||
// utils/api.js
|
||
import axios from 'axios';
|
||
|
||
export const apiClient = axios.create({
|
||
baseURL: '/api',
|
||
withCredentials: true,
|
||
});
|
||
|
||
export const fetcher = url => apiClient.get(url).then(res => res.data);
|
||
|
||
// components/PlayerList.js
|
||
import useSWR from 'swr';
|
||
import { fetcher } from '../utils/api';
|
||
import { useStore } from '../store/useStore';
|
||
|
||
function PlayerList() {
|
||
const { data, error } = useSWR('/players', fetcher);
|
||
const { openModal, setEditingPlayer } = useStore();
|
||
|
||
if (error) return <div>Failed to load</div>;
|
||
if (!data) return <div>Loading...</div>;
|
||
|
||
const handleEdit = (player) => {
|
||
setEditingPlayer(player);
|
||
openModal();
|
||
};
|
||
|
||
return (
|
||
<ul>
|
||
{data.players.map(player => (
|
||
<li key={player.id}>
|
||
{player.name}
|
||
<button onClick={() => handleEdit(player)}>Edit</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
);
|
||
}
|
||
```
|