diff --git a/package.json b/package.json index 4e44f69..a1e819f 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,11 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.544.0", + "nuqs": "^2.7.0", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.14", - "wouter": "^3.7.1" + "tailwindcss": "^4.1.14" }, "devDependencies": { "@eslint/js": "^9.36.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 659476f..39a3d56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: lucide-react: specifier: ^0.544.0 version: 0.544.0(react@19.2.0) + nuqs: + specifier: ^2.7.0 + version: 2.7.0(react@19.2.0) react: specifier: ^19.2.0 version: 19.2.0 @@ -44,9 +47,6 @@ importers: tailwindcss: specifier: ^4.1.14 version: 4.1.14 - wouter: - specifier: ^3.7.1 - version: 3.7.1(react@19.2.0) devDependencies: '@eslint/js': specifier: ^9.36.0 @@ -569,6 +569,9 @@ packages: peerDependencies: solid-js: ^1.6.12 + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@tailwindcss/node@4.1.14': resolution: {integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==} @@ -1228,9 +1231,6 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1245,6 +1245,27 @@ packages: node-releases@2.0.21: resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} + nuqs@2.7.0: + resolution: {integrity: sha512-p6j6AuOVbnIlMSOlymKVDQtDkSCgjKxikwrqE9zThEOrP0UMfB/dwHuc6nKCuBPveoNghbh15zwoyTTv0XLp+A==} + peerDependencies: + '@remix-run/react': '>=2' + '@tanstack/react-router': ^1 + next: '>=14.2.0' + react: '>=18.2.0 || ^19.0.0-0' + react-router: ^6 || ^7 + react-router-dom: ^6 || ^7 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@tanstack/react-router': + optional: true + next: + optional: true + react-router: + optional: true + react-router-dom: + optional: true + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1308,10 +1329,6 @@ packages: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} - regexparam@3.0.0: - resolution: {integrity: sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==} - engines: {node: '>=8'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1432,11 +1449,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - vite@7.1.8: resolution: {integrity: sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1486,11 +1498,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wouter@3.7.1: - resolution: {integrity: sha512-od5LGmndSUzntZkE2R5CHhoiJ7YMuTIbiXsa0Anytc2RATekgv4sfWRAxLEULBrp7ADzinWQw8g470lkT8+fOw==} - peerDependencies: - react: '>=16.8.0' - ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -1898,6 +1905,8 @@ snapshots: dependencies: solid-js: 1.9.9 + '@standard-schema/spec@1.0.0': {} + '@tailwindcss/node@4.1.14': dependencies: '@jridgewell/remapping': 2.3.5 @@ -2576,8 +2585,6 @@ snapshots: dependencies: minipass: 7.1.2 - mitt@3.0.1: {} - ms@2.1.3: {} nanoid@3.3.11: {} @@ -2586,6 +2593,11 @@ snapshots: node-releases@2.0.21: {} + nuqs@2.7.0(react@19.2.0): + dependencies: + '@standard-schema/spec': 1.0.0 + react: 19.2.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2638,8 +2650,6 @@ snapshots: react@19.2.0: {} - regexparam@3.0.0: {} - resolve-from@4.0.0: {} reusify@1.1.0: {} @@ -2766,10 +2776,6 @@ snapshots: dependencies: punycode: 2.3.1 - use-sync-external-store@1.6.0(react@19.2.0): - dependencies: - react: 19.2.0 - vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1): dependencies: esbuild: 0.25.10 @@ -2790,13 +2796,6 @@ snapshots: word-wrap@1.2.5: {} - wouter@3.7.1(react@19.2.0): - dependencies: - mitt: 3.0.1 - react: 19.2.0 - regexparam: 3.0.0 - use-sync-external-store: 1.6.0(react@19.2.0) - ws@8.18.3: {} yallist@3.1.1: {} diff --git a/src/app.tsx b/src/app.tsx index bbe7eb0..157dbb5 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,70 +1,76 @@ -import { Route, Link } from "wouter"; import { PokemonTabs } from "./components/pokemon-tabs"; +import { Button } from "./components/ui/button"; +import { useActivityEnabled, useSuspenseEnabled } from "./lib/utils"; function App() { + const [activityEnabled, setActivityEnabled] = useActivityEnabled(); + const [suspenseEnabled, setSuspenseEnabled] = useSuspenseEnabled(); + return (
{/* Header Navigation */} -
-
-
-
-

{` Component and Query Explorer - with Pokemon!`}

-

- Query components wrapped in React Activity Component -

+
+
+
+

{` Component and Query Explorer - with Pokemon!`}

+

+ Query components wrapped in React Activity Component +

+
+
+
+ +
- + +
{/* Main Content */}
- -
-

Non-Suspending Mode

-

- Handles loading states internally -

-
- -
- - -
-

Suspending Mode

-

- Uses React Suspense for loading states -

-
- -
+

+ {!activityEnabled && + !suspenseEnabled && + "Suspense and Activity disabled"} + {activityEnabled && + !suspenseEnabled && + "Activity enabled, Suspense disabled"} + {!activityEnabled && + suspenseEnabled && + "Activity disabled, Suspense enabled"} + {activityEnabled && + suspenseEnabled && + "Activity and Suspense enabled"} +

+
); diff --git a/src/components/pokemon-tabs.tsx b/src/components/pokemon-tabs.tsx index b440815..0907df0 100644 --- a/src/components/pokemon-tabs.tsx +++ b/src/components/pokemon-tabs.tsx @@ -1,5 +1,9 @@ -import { useState, Activity, Suspense } from "react"; -import { NonSuspendingPokemonCard, SuspendingPokemonCard } from "./pokemon-card"; +import { Activity, Suspense, useState } from "react"; +import { useActivityEnabled, useSuspenseEnabled } from "../lib/utils"; +import { + NonSuspendingPokemonCard, + SuspendingPokemonCard, +} from "./pokemon-card"; const POKEMON_TABS = [ { id: "bulbasaur", label: "Bulbasaur" }, @@ -7,17 +11,16 @@ const POKEMON_TABS = [ { id: "squirtle", label: "Squirtle" }, ] as const; -interface PokemonTabsProps { - mode: "suspending" | "non-suspending"; -} - -export function PokemonTabs({ mode }: PokemonTabsProps) { +export function PokemonTabs() { const [activePokemon, setActivePokemon] = useState("bulbasaur"); + const [activityEnabled] = useActivityEnabled(); + const [suspenseEnabled] = useSuspenseEnabled(); - const PokemonComponent = mode === "suspending" ? SuspendingPokemonCard : NonSuspendingPokemonCard; - const useSuspense = mode === "suspending"; + const PokemonComponent = suspenseEnabled + ? SuspendingPokemonCard + : NonSuspendingPokemonCard; - const content = ( + const content = activityEnabled ? ( <> @@ -29,6 +32,18 @@ export function PokemonTabs({ mode }: PokemonTabsProps) { + ) : ( + <> + {activePokemon === "bulbasaur" && ( + + )} + {activePokemon === "charmander" && ( + + )} + {activePokemon === "squirtle" && ( + + )} + ); return ( @@ -57,8 +72,10 @@ export function PokemonTabs({ mode }: PokemonTabsProps) { {/* Custom Tab Content */}
- {useSuspense ? ( - Loading...
}> + {suspenseEnabled ? ( + Loading...} + > {content} ) : ( @@ -68,4 +85,3 @@ export function PokemonTabs({ mode }: PokemonTabsProps) { ); } - diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bd0c391..a5d6c7e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,38 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from "clsx"; +import { parseAsBoolean, useQueryState } from "nuqs"; +import { useQueryClient } from "@tanstack/react-query"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); +} + +export function useActivityEnabled() { + const queryClient = useQueryClient(); + const [activityEnabled, _setActivityEnabled] = useQueryState( + "activityEnabled", + parseAsBoolean.withDefault(false) + ); + + const setActivityEnabled = (value: boolean) => { + queryClient.clear(); + _setActivityEnabled(value); + }; + + return [activityEnabled, setActivityEnabled] as const; +} + +export function useSuspenseEnabled() { + const queryClient = useQueryClient(); + const [suspenseEnabled, _setSuspenseEnabled] = useQueryState( + "activitySuspending", + parseAsBoolean.withDefault(false) + ); + + const setSuspenseEnabled = (value: boolean) => { + queryClient.clear(); + _setSuspenseEnabled(value); + }; + + return [suspenseEnabled, setSuspenseEnabled] as const; } diff --git a/src/main.tsx b/src/main.tsx index 791177e..e36772c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,7 @@ import React, { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { NuqsAdapter } from "nuqs/adapters/react"; import "./index.css"; import App from "./app.tsx"; @@ -20,9 +21,11 @@ const queryClient = new QueryClient(); createRoot(document.getElementById("root")!).render( - - - - + + + + + + );