Frontend Frameworks

React hooks & patterns, Vue 3 Composition API, Nuxt 3, Svelte 5 β€” components, state, routing & essentials

Framework / Frontend
Contents
βš›οΈ

React: Basics & JSX

// Function component
interface Props {
  name: string;
  count?: number;
  children: React.ReactNode;
}

const Greeting = ({ name, count = 0, children }: Props) => {
  return (
    <div className="card">
      <h1>Hello, {name}!</h1>
      <p>Count: {count}</p>
      {children}
    </div>
  );
};

// Conditional rendering
{isLoggedIn ? <Dashboard /> : <Login />}
{error && <ErrorBanner message={error} />}

// Lists
{users.map(user => (
  <UserCard key={user.id} user={user} />
))}

// Events
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<input onChange={(e) => setQuery(e.target.value)} />

// Styles
<div style={{ color: "red", fontSize: 16 }}>Styled</div>
<div className={`card ${isActive ? "active" : ""}`}>Toggle</div>
πŸͺ

React: Hooks

import { useState, useEffect, useRef, useMemo, useCallback, useContext } from "react";

// ── useState ──
const [count, setCount] = useState(0);
const [users, setUsers] = useState<User[]>([]);
setCount(prev => prev + 1);  // functional update

// ── useEffect ──
useEffect(() => {
  fetchUsers().then(setUsers);
}, []);  // [] = run once on mount

useEffect(() => {
  const timer = setInterval(() => tick(), 1000);
  return () => clearInterval(timer);  // cleanup
}, []);

// ── useRef ──
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();

// ── useMemo ── (cached computation)
const sorted = useMemo(
  () => users.sort((a, b) => a.name.localeCompare(b.name)),
  [users]
);

// ── useCallback ── (cached function)
const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

// ── useContext ──
const ThemeContext = createContext<"light" | "dark">("light");
const theme = useContext(ThemeContext);

// Custom hook
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url).then(r => r.json()).then(d => {
      setData(d);
      setLoading(false);
    });
  }, [url]);

  return { data, loading };
}
πŸ—οΈ

React: Patterns

// ── State management with useReducer ──
type Action = { type: "increment" } | { type: "decrement" } | { type: "reset" };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case "increment": return state + 1;
    case "decrement": return state - 1;
    case "reset": return 0;
  }
}
const [count, dispatch] = useReducer(reducer, 0);

// ── Suspense + lazy loading ──
const Dashboard = lazy(() => import("./Dashboard"));
<Suspense fallback={<Spinner />}>
  <Dashboard />
</Suspense>

// ── Server state with TanStack Query ──
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

const { data, isLoading } = useQuery({
  queryKey: ["users"],
  queryFn: () => fetch("/api/users").then(r => r.json()),
});

const queryClient = useQueryClient();
const mutation = useMutation({
  mutationFn: (newUser) => fetch("/api/users", { method: "POST", body: JSON.stringify(newUser) }),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ["users"] }),
});
🧭

React Router

import { BrowserRouter, Routes, Route, Link, useParams, useNavigate } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/users">Users</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/users" element={<Users />} />
        <Route path="/users/:id" element={<UserDetail />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

function UserDetail() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  return <button onClick={() => navigate("/users")}>Back</button>;
}
πŸ’š

Vue 3: Basics

<!-- Single File Component (.vue) -->
<script setup lang="ts">
import { ref, computed } from "vue";

interface Props { title: string; count?: number; }
const props = withDefaults(defineProps<Props>(), { count: 0 });
const emit = defineEmits<{ update: [value: number] }>();

const name = ref("Vue");
const greeting = computed(() => `Hello, ${name.value}!`);

function increment() {
  emit("update", props.count + 1);
}
</script>

<template>
  <div class="card">
    <h1>{{ greeting }}</h1>
    <p>{{ props.title }}</p>

    <!-- Two-way binding -->
    <input v-model="name" />

    <!-- Conditional -->
    <p v-if="count > 10">High!</p>
    <p v-else-if="count > 5">Medium</p>
    <p v-else>Low</p>

    <!-- List -->
    <ul>
      <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>

    <!-- Event -->
    <button @click="increment">+1</button>

    <!-- Slots -->
    <slot name="header">Default header</slot>
  </div>
</template>

<style scoped>
.card { padding: 1rem; }
</style>
πŸ”§

Vue 3: Composition API

import { ref, reactive, computed, watch, onMounted, toRefs } from "vue";

// ref (primitives) vs reactive (objects)
const count = ref(0);            // access via count.value
const state = reactive({ name: "Alice", age: 25 });

// Computed
const doubled = computed(() => count.value * 2);

// Watch
watch(count, (newVal, oldVal) => {
  console.log(`${oldVal} β†’ ${newVal}`);
});

watch(() => state.name, (newName) => {
  console.log("Name changed:", newName);
});

// Lifecycle
onMounted(() => { fetchData(); });

// ── Composable (custom hook equivalent) ──
export function useFetch<T>(url: string) {
  const data = ref<T | null>(null);
  const loading = ref(true);
  const error = ref<Error | null>(null);

  onMounted(async () => {
    try {
      const res = await fetch(url);
      data.value = await res.json();
    } catch (e) {
      error.value = e as Error;
    } finally {
      loading.value = false;
    }
  });

  return { data, loading, error };
}

// Provide / Inject (dependency injection)
import { provide, inject } from "vue";
provide("theme", "dark");           // parent
const theme = inject("theme");          // child
🧭

Vue Router

import { createRouter, createWebHistory } from "vue-router";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: "/", component: () => import("./views/Home.vue") },
    { path: "/users", component: () => import("./views/Users.vue") },
    { path: "/users/:id", component: () => import("./views/UserDetail.vue"), props: true },
    { path: "/:pathMatch(.*)*", component: NotFound },
  ],
});

// Navigation guard
router.beforeEach((to, from) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    return { path: "/login" };
  }
});

// In component
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
console.log(route.params.id);
router.push("/users");
πŸ’Ž

Nuxt 3

// npx nuxi@latest init my-app
// File-based routing: pages/index.vue β†’ /
// pages/users/[id].vue β†’ /users/:id

// ── pages/users/[id].vue ──
<script setup lang="ts">
const route = useRoute();

// Auto-imported data fetching
const { data: user, pending } = await useFetch(
  `/api/users/${route.params.id}`
);
</script>

<template>
  <div v-if="pending">Loading...</div>
  <div v-else>{{ user?.name }}</div>
</template>

// ── server/api/users/[id].get.ts ── (API route)
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, "id");
  return { id, name: "Alice" };
});

// ── server/api/users.post.ts ──
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  return { id: 1, ...body };
});

// Layouts: layouts/default.vue
// Middleware: middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  if (!useAuth().isLoggedIn) return navigateTo("/login");
});

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@nuxtjs/tailwindcss", "@pinia/nuxt"],
  runtimeConfig: {
    apiSecret: "",           // server only
    public: { apiBase: "" },  // client + server
  },
});
πŸ”Ά

Svelte 5: Basics (Runes)

<!-- Component.svelte (Svelte 5 with runes) -->
<script lang="ts">
  // Props
  let { name, count = 0 }: { name: string; count?: number } = $props();

  // Reactive state
  let message = $state("Hello");
  let users = $state<User[]>([]);

  // Derived (computed)
  let greeting = $derived(`${message}, ${name}!`);
  let userCount = $derived(users.length);

  // Effects
  $effect(() => {
    console.log("Count changed:", count);
  });

  // Functions
  function addUser(user: User) {
    users.push(user);  // $state is deeply reactive
  }

  // Lifecycle
  import { onMount } from "svelte";
  onMount(async () => {
    const res = await fetch("/api/users");
    users = await res.json();
  });
</script>

<!-- Template -->
<h1>{greeting}</h1>
<input bind:value={message} />

{#if userCount > 0}
  <ul>
    {#each users as user (user.id)}
      <li>{user.name}</li>
    {/each}
  </ul>
{:else}
  <p>No users yet.</p>
{/if}

<button onclick={() => count++}>Count: {count}</button>

<!-- Slots -->
<slot>Default content</slot>

<style>
  h1 { color: tomato; }
</style>
⚑

Svelte: Stores & SvelteKit

// ── Stores (shared state) ──
// stores/counter.ts
import { writable, derived } from "svelte/store";

export const count = writable(0);
export const doubled = derived(count, $c => $c * 2);

// In component
import { count } from "./stores/counter";
count.set(5);
count.update(n => n + 1);
<p>{$count}</p>  <!-- auto-subscribe with $ prefix -->

// ── SvelteKit ──
// npx sv create my-app

// File-based routing:
// src/routes/+page.svelte            β†’ /
// src/routes/users/+page.svelte      β†’ /users
// src/routes/users/[id]/+page.svelte β†’ /users/:id

// ── src/routes/users/+page.server.ts ── (data loader)
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async ({ fetch }) => {
  const res = await fetch("/api/users");
  return { users: await res.json() };
};

// ── +page.svelte ──
<script lang="ts">
  let { data } = $props();  // typed from load function
</script>
{#each data.users as user}
  <p>{user.name}</p>
{/each}

// ── API route: src/routes/api/users/+server.ts ──
import { json } from "@sveltejs/kit";
export async function GET() {
  return json([{ id: 1, name: "Alice" }]);
}
export async function POST({ request }) {
  const body = await request.json();
  return json({ id: 1, ...body }, { status: 201 });
}
βš–οΈ

Framework Comparison

Feature React Vue 3 Svelte 5
Reactivity Hooks (explicit) Proxy-based (ref/reactive) Runes ($state)
Template JSX HTML template + directives HTML template + {#if}
Bundle ~40KB runtime ~33KB runtime No runtime (compiled)
Meta Framework Next.js / Remix Nuxt 3 SvelteKit
Ecosystem Largest Large, growing Growing fast