Data Flow
Understanding how data flows through Y3NKO is key to working with the codebase.
Request Lifecycle
Browser Request
│
▼
┌─────────────────┐
│ Middleware │ ← Auth check, redirects
└─────────────────┘
│
▼
┌─────────────────┐
│ Layout.tsx │ ← Wraps all pages
└─────────────────┘
│
▼
┌─────────────────┐
│ Page.tsx │ ← Server Component (default)
│ (async data) │
└─────────────────┘
│
▼
┌─────────────────┐
│ Client Comps │ ← Interactive elements
└─────────────────┘Server Components vs Client Components
Server Components (Default)
Used for:
- Fetching data
- Accessing backend resources
- Keeping sensitive info on server
- Large dependencies
// app/accommodations/page.tsx (Server Component)
import { getListings } from '@/lib/actions/listings'
export default async function AccommodationsPage() {
const listings = await getListings()
return (
<div>
{listings.map(listing => (
<ListingCard key={listing.id} listing={listing} />
))}
</div>
)
}Client Components
Used for:
- Interactivity (onClick, onChange)
- Browser APIs (localStorage, window)
- React hooks (useState, useEffect)
- Real-time updates
// components/features/search/search-bar.tsx
"use client"
import { useState } from 'react'
export function SearchBar({ onSearch }: SearchBarProps) {
const [query, setQuery] = useState('')
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && onSearch(query)}
/>
)
}Data Fetching Patterns
1. Server Component Fetch
Direct database access in Server Components:
// app/accommodations/[slug]/page.tsx
import { prisma } from '@/lib/db'
export default async function ListingPage({ params }) {
const listing = await prisma.listing.findUnique({
where: { slug: params.slug },
include: { images: true, amenities: true }
})
return <ListingDetail listing={listing} />
}2. Server Actions
For mutations (create, update, delete):
// lib/actions/bookings.ts
"use server"
export async function createBooking(data: BookingInput) {
const session = await getServerSession()
if (!session) throw new Error('Unauthorized')
return await prisma.booking.create({
data: {
...data,
userId: session.user.id
}
})
}Using in a Client Component:
"use client"
import { createBooking } from '@/lib/actions/bookings'
function BookingForm() {
async function handleSubmit(formData: FormData) {
const result = await createBooking({
listingId: formData.get('listingId'),
checkIn: formData.get('checkIn'),
// ...
})
}
return <form action={handleSubmit}>...</form>
}3. API Routes
For webhooks and external integrations:
// app/api/webhooks/paystack/route.ts
export async function POST(request: Request) {
const body = await request.json()
const signature = request.headers.get('x-paystack-signature')
// Verify webhook, process payment
return Response.json({ received: true })
}State Management
Y3NKO uses a minimal state approach:
| State Type | Solution |
|---|---|
| Server state | Server Components, Server Actions |
| URL state | Search params, route params |
| Form state | React Hook Form |
| UI state | useState, useReducer |
| Global UI | React Context (minimal) |
URL as State
Filters and search are stored in URL params:
// /accommodations?region=Accra&minPrice=100&maxPrice=500
"use client"
import { useSearchParams, useRouter } from 'next/navigation'
function Filters() {
const searchParams = useSearchParams()
const router = useRouter()
function updateFilter(key: string, value: string) {
const params = new URLSearchParams(searchParams)
params.set(key, value)
router.push(`/accommodations?${params.toString()}`)
}
}Caching Strategy
Next.js 14 caching layers:
| Cache | Scope | Duration | Revalidation |
|---|---|---|---|
| Request Memoization | Single request | Request lifetime | Automatic |
| Data Cache | All requests | Indefinite | revalidatePath(), revalidateTag() |
| Full Route Cache | Static routes | Build time | Rebuild or on-demand |
Revalidation Example
// After creating a booking
"use server"
import { revalidatePath } from 'next/cache'
export async function createBooking(data) {
const booking = await prisma.booking.create({ data })
// Revalidate affected pages
revalidatePath('/dashboard/bookings')
revalidatePath(`/accommodations/${data.listingSlug}`)
return booking
}Error Handling
Errors flow through the component tree:
Page.tsx
│
├── try/catch in Server Component
│ └── Show error UI inline
│
└── error.tsx boundary
└── Catches unhandled errors// app/accommodations/error.tsx
"use client"
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}