Prettier prettied

This commit is contained in:
Leons Aleksandrovs
2025-07-04 19:11:47 +03:00
parent 5596e37b1e
commit aad8dd5674
24 changed files with 699 additions and 741 deletions
+10 -13
View File
@@ -1,14 +1,11 @@
{ {
"projectName": ".", "projectName": ".",
"mode": "file-router", "mode": "file-router",
"typescript": true, "typescript": true,
"tailwind": true, "tailwind": true,
"packageManager": "bun", "packageManager": "bun",
"git": true, "git": true,
"version": 1, "version": 1,
"framework": "react-cra", "framework": "react-cra",
"chosenAddOns": [ "chosenAddOns": ["form", "tanstack-query"]
"form", }
"tanstack-query"
]
}
+9
View File
@@ -0,0 +1,9 @@
const config = {
trailingComma: "es5",
printWidth: 120,
tabWidth: 4,
semi: true,
singleQuote: false,
};
export default config;
+84 -91
View File
@@ -1,4 +1,4 @@
Welcome to your new TanStack app! Welcome to your new TanStack app!
# Getting Started # Getting Started
@@ -6,7 +6,7 @@ To run this application:
```bash ```bash
bun install bun install
bunx --bun run start bunx --bun run start
``` ```
# Building For Production # Building For Production
@@ -29,10 +29,8 @@ bunx --bun run test
This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
## Routing ## Routing
This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`. This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.
### Adding A Route ### Adding A Route
@@ -68,32 +66,31 @@ In the File Based Routing setup the layout is located in `src/routes/__root.tsx`
Here is an example layout that includes a header: Here is an example layout that includes a header:
```tsx ```tsx
import { Outlet, createRootRoute } from '@tanstack/react-router' import { Outlet, createRootRoute } from "@tanstack/react-router";
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
export const Route = createRootRoute({ export const Route = createRootRoute({
component: () => ( component: () => (
<> <>
<header> <header>
<nav> <nav>
<Link to="/">Home</Link> <Link to="/">Home</Link>
<Link to="/about">About</Link> <Link to="/about">About</Link>
</nav> </nav>
</header> </header>
<Outlet /> <Outlet />
<TanStackRouterDevtools /> <TanStackRouterDevtools />
</> </>
), ),
}) });
``` ```
The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout. The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.
More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
## Data Fetching ## Data Fetching
There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
@@ -102,26 +99,26 @@ For example:
```tsx ```tsx
const peopleRoute = createRoute({ const peopleRoute = createRoute({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
path: "/people", path: "/people",
loader: async () => { loader: async () => {
const response = await fetch("https://swapi.dev/api/people"); const response = await fetch("https://swapi.dev/api/people");
return response.json() as Promise<{ return response.json() as Promise<{
results: { results: {
name: string; name: string;
}[]; }[];
}>; }>;
}, },
component: () => { component: () => {
const data = peopleRoute.useLoaderData(); const data = peopleRoute.useLoaderData();
return ( return (
<ul> <ul>
{data.results.map((person) => ( {data.results.map((person) => (
<li key={person.name}>{person.name}</li> <li key={person.name}>{person.name}</li>
))} ))}
</ul> </ul>
); );
}, },
}); });
``` ```
@@ -149,13 +146,13 @@ const queryClient = new QueryClient();
// ... // ...
if (!rootElement.innerHTML) { if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement); const root = ReactDOM.createRoot(rootElement);
root.render( root.render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<RouterProvider router={router} /> <RouterProvider router={router} />
</QueryClientProvider> </QueryClientProvider>
); );
} }
``` ```
@@ -165,13 +162,13 @@ You can also add TanStack Query Devtools to the root route (optional).
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
const rootRoute = createRootRoute({ const rootRoute = createRootRoute({
component: () => ( component: () => (
<> <>
<Outlet /> <Outlet />
<ReactQueryDevtools buttonPosition="top-right" /> <ReactQueryDevtools buttonPosition="top-right" />
<TanStackRouterDevtools /> <TanStackRouterDevtools />
</> </>
), ),
}); });
``` ```
@@ -183,24 +180,24 @@ import { useQuery } from "@tanstack/react-query";
import "./App.css"; import "./App.css";
function App() { function App() {
const { data } = useQuery({ const { data } = useQuery({
queryKey: ["people"], queryKey: ["people"],
queryFn: () => queryFn: () =>
fetch("https://swapi.dev/api/people") fetch("https://swapi.dev/api/people")
.then((res) => res.json()) .then((res) => res.json())
.then((data) => data.results as { name: string }[]), .then((data) => data.results as { name: string }[]),
initialData: [], initialData: [],
}); });
return ( return (
<div> <div>
<ul> <ul>
{data.map((person) => ( {data.map((person) => (
<li key={person.name}>{person.name}</li> <li key={person.name}>{person.name}</li>
))} ))}
</ul> </ul>
</div> </div>
); );
} }
export default App; export default App;
@@ -228,14 +225,12 @@ import "./App.css";
const countStore = new Store(0); const countStore = new Store(0);
function App() { function App() {
const count = useStore(countStore); const count = useStore(countStore);
return ( return (
<div> <div>
<button onClick={() => countStore.setState((n) => n + 1)}> <button onClick={() => countStore.setState((n) => n + 1)}>Increment - {count}</button>
Increment - {count} </div>
</button> );
</div>
);
} }
export default App; export default App;
@@ -253,23 +248,21 @@ import "./App.css";
const countStore = new Store(0); const countStore = new Store(0);
const doubledStore = new Derived({ const doubledStore = new Derived({
fn: () => countStore.state * 2, fn: () => countStore.state * 2,
deps: [countStore], deps: [countStore],
}); });
doubledStore.mount(); doubledStore.mount();
function App() { function App() {
const count = useStore(countStore); const count = useStore(countStore);
const doubledCount = useStore(doubledStore); const doubledCount = useStore(doubledStore);
return ( return (
<div> <div>
<button onClick={() => countStore.setState((n) => n + 1)}> <button onClick={() => countStore.setState((n) => n + 1)}>Increment - {count}</button>
Increment - {count} <div>Doubled - {doubledCount}</div>
</button> </div>
<div>Doubled - {doubledCount}</div> );
</div>
);
} }
export default App; export default App;
+1
View File
@@ -23,6 +23,7 @@
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
"prettier": "^3.6.2",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"vite": "^6.1.0", "vite": "^6.1.0",
"vitest": "^3.0.5", "vitest": "^3.0.5",
+15 -18
View File
@@ -1,20 +1,17 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta name="description" content="Web site created using create-tsrouter-app" />
name="description" <link rel="apple-touch-icon" href="/logo192.png" />
content="Web site created using create-tsrouter-app" <link rel="manifest" href="/manifest.json" />
/> <title>Create TanStack App - .</title>
<link rel="apple-touch-icon" href="/logo192.png" /> </head>
<link rel="manifest" href="/manifest.json" /> <body>
<title>Create TanStack App - .</title> <div id="app"></div>
</head> <script type="module" src="/src/main.tsx"></script>
<body> </body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html> </html>
+35 -35
View File
@@ -1,37 +1,37 @@
{ {
"name": "Cover letter frontend", "name": "Cover letter frontend",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --port 3000 --host", "dev": "vite --port 3000 --host",
"build": "vite build && tsc", "build": "vite build && tsc",
"serve": "vite preview", "serve": "vite preview",
"test": "vitest run" "test": "vitest run"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.0.6", "@tailwindcss/vite": "^4.0.6",
"@tanstack/react-form": "^1.0.0", "@tanstack/react-form": "^1.0.0",
"@tanstack/react-query": "^5.66.5", "@tanstack/react-query": "^5.66.5",
"@tanstack/react-query-devtools": "^5.66.5", "@tanstack/react-query-devtools": "^5.66.5",
"@tanstack/react-router": "^1.121.2", "@tanstack/react-router": "^1.121.2",
"@tanstack/react-router-devtools": "^1.121.2", "@tanstack/react-router-devtools": "^1.121.2",
"@tanstack/router-plugin": "^1.121.2", "@tanstack/router-plugin": "^1.121.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"tailwindcss": "^4.0.6", "tailwindcss": "^4.0.6",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
"typescript": "^5.7.2", "prettier": "^3.6.2",
"vite": "^6.1.0", "typescript": "^5.7.2",
"vitest": "^3.0.5", "vite": "^6.1.0",
"web-vitals": "^4.2.4" "vitest": "^3.0.5",
} "web-vitals": "^4.2.4"
}
} }
+23 -23
View File
@@ -1,25 +1,25 @@
{ {
"short_name": "TanStack App", "short_name": "TanStack App",
"name": "Create TanStack App Sample", "name": "Create TanStack App Sample",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon" "type": "image/x-icon"
}, },
{ {
"src": "logo192.png", "src": "logo192.png",
"type": "image/png", "type": "image/png",
"sizes": "192x192" "sizes": "192x192"
}, },
{ {
"src": "logo512.png", "src": "logo512.png",
"type": "image/png", "type": "image/png",
"sizes": "512x512" "sizes": "512x512"
} }
], ],
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#ffffff" "background_color": "#ffffff"
} }
+19 -19
View File
@@ -1,25 +1,25 @@
import { Link } from '@tanstack/react-router' import { Link } from "@tanstack/react-router";
export default function Header() { export default function Header() {
return ( return (
<header className="p-2 flex gap-2 bg-white text-black justify-between"> <header className="p-2 flex gap-2 bg-white text-black justify-between">
<nav className="flex flex-row"> <nav className="flex flex-row">
<div className="px-2 font-bold"> <div className="px-2 font-bold">
<Link to="/">Home</Link> <Link to="/">Home</Link>
</div> </div>
<div className="px-2 font-bold"> <div className="px-2 font-bold">
<Link to="/demo/form/simple">Simple Form</Link> <Link to="/demo/form/simple">Simple Form</Link>
</div> </div>
<div className="px-2 font-bold"> <div className="px-2 font-bold">
<Link to="/demo/form/address">Address Form</Link> <Link to="/demo/form/address">Address Form</Link>
</div> </div>
<div className="px-2 font-bold"> <div className="px-2 font-bold">
<Link to="/demo/tanstack-query">TanStack Query</Link> <Link to="/demo/tanstack-query">TanStack Query</Link>
</div> </div>
</nav> </nav>
</header> </header>
) );
} }
+92 -111
View File
@@ -1,127 +1,108 @@
import { useStore } from '@tanstack/react-form' import { useStore } from "@tanstack/react-form";
import { useFieldContext, useFormContext } from '../hooks/demo.form-context' import { useFieldContext, useFormContext } from "../hooks/demo.form-context";
export function SubscribeButton({ label }: { label: string }) { export function SubscribeButton({ label }: { label: string }) {
const form = useFormContext() const form = useFormContext();
return ( return (
<form.Subscribe selector={(state) => state.isSubmitting}> <form.Subscribe selector={(state) => state.isSubmitting}>
{(isSubmitting) => ( {(isSubmitting) => (
<button <button
type="submit" type="submit"
disabled={isSubmitting} disabled={isSubmitting}
className="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors disabled:opacity-50" className="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors disabled:opacity-50"
> >
{label} {label}
</button> </button>
)} )}
</form.Subscribe> </form.Subscribe>
) );
} }
function ErrorMessages({ function ErrorMessages({ errors }: { errors: Array<string | { message: string }> }) {
errors, return (
}: { <>
errors: Array<string | { message: string }> {errors.map((error) => (
}) { <div key={typeof error === "string" ? error : error.message} className="text-red-500 mt-1 font-bold">
return ( {typeof error === "string" ? error : error.message}
<> </div>
{errors.map((error) => ( ))}
<div </>
key={typeof error === 'string' ? error : error.message} );
className="text-red-500 mt-1 font-bold" }
>
{typeof error === 'string' ? error : error.message} export function TextField({ label, placeholder }: { label: string; placeholder?: string }) {
const field = useFieldContext<string>();
const errors = useStore(field.store, (state) => state.meta.errors);
return (
<div>
<label htmlFor={label} className="block font-bold mb-1 text-xl">
{label}
<input
value={field.state.value}
placeholder={placeholder}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</label>
{field.state.meta.isTouched && <ErrorMessages errors={errors} />}
</div> </div>
))} );
</>
)
} }
export function TextField({ export function TextArea({ label, rows = 3 }: { label: string; rows?: number }) {
label, const field = useFieldContext<string>();
placeholder, const errors = useStore(field.store, (state) => state.meta.errors);
}: {
label: string
placeholder?: string
}) {
const field = useFieldContext<string>()
const errors = useStore(field.store, (state) => state.meta.errors)
return ( return (
<div> <div>
<label htmlFor={label} className="block font-bold mb-1 text-xl"> <label htmlFor={label} className="block font-bold mb-1 text-xl">
{label} {label}
<input <textarea
value={field.state.value} value={field.state.value}
placeholder={placeholder} onBlur={field.handleBlur}
onBlur={field.handleBlur} rows={rows}
onChange={(e) => field.handleChange(e.target.value)} onChange={(e) => field.handleChange(e.target.value)}
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500" className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
/> />
</label> </label>
{field.state.meta.isTouched && <ErrorMessages errors={errors} />} {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
</div> </div>
) );
}
export function TextArea({
label,
rows = 3,
}: {
label: string
rows?: number
}) {
const field = useFieldContext<string>()
const errors = useStore(field.store, (state) => state.meta.errors)
return (
<div>
<label htmlFor={label} className="block font-bold mb-1 text-xl">
{label}
<textarea
value={field.state.value}
onBlur={field.handleBlur}
rows={rows}
onChange={(e) => field.handleChange(e.target.value)}
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</label>
{field.state.meta.isTouched && <ErrorMessages errors={errors} />}
</div>
)
} }
export function Select({ export function Select({
label, label,
values, values,
}: { }: {
label: string label: string;
values: Array<{ label: string; value: string }> values: Array<{ label: string; value: string }>;
placeholder?: string placeholder?: string;
}) { }) {
const field = useFieldContext<string>() const field = useFieldContext<string>();
const errors = useStore(field.store, (state) => state.meta.errors) const errors = useStore(field.store, (state) => state.meta.errors);
return ( return (
<div> <div>
<label htmlFor={label} className="block font-bold mb-1 text-xl"> <label htmlFor={label} className="block font-bold mb-1 text-xl">
{label} {label}
</label> </label>
<select <select
name={field.name} name={field.name}
value={field.state.value} value={field.state.value}
onBlur={field.handleBlur} onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)} onChange={(e) => field.handleChange(e.target.value)}
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500" className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
> >
{values.map((value) => ( {values.map((value) => (
<option key={value.value} value={value.value}> <option key={value.value} value={value.value}>
{value.label} {value.label}
</option> </option>
))} ))}
</select> </select>
{field.state.meta.isTouched && <ErrorMessages errors={errors} />} {field.state.meta.isTouched && <ErrorMessages errors={errors} />}
</div> </div>
) );
} }
+2 -3
View File
@@ -1,4 +1,3 @@
import { createFormHookContexts } from '@tanstack/react-form' import { createFormHookContexts } from "@tanstack/react-form";
export const { fieldContext, useFieldContext, formContext, useFormContext } = export const { fieldContext, useFieldContext, formContext, useFormContext } = createFormHookContexts();
createFormHookContexts()
+14 -19
View File
@@ -1,22 +1,17 @@
import { createFormHook } from '@tanstack/react-form' import { createFormHook } from "@tanstack/react-form";
import { import { Select, SubscribeButton, TextArea, TextField } from "../components/demo.FormComponents";
Select, import { fieldContext, formContext } from "./demo.form-context";
SubscribeButton,
TextArea,
TextField,
} from '../components/demo.FormComponents'
import { fieldContext, formContext } from './demo.form-context'
export const { useAppForm } = createFormHook({ export const { useAppForm } = createFormHook({
fieldComponents: { fieldComponents: {
TextField, TextField,
Select, Select,
TextArea, TextArea,
}, },
formComponents: { formComponents: {
SubscribeButton, SubscribeButton,
}, },
fieldContext, fieldContext,
formContext, formContext,
}) });
@@ -1,5 +1,5 @@
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
export default function LayoutAddition() { export default function LayoutAddition() {
return <ReactQueryDevtools buttonPosition="bottom-right" /> return <ReactQueryDevtools buttonPosition="bottom-right" />;
} }
@@ -1,15 +1,13 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient() const queryClient = new QueryClient();
export function getContext() { export function getContext() {
return { return {
queryClient, queryClient,
} };
} }
export function Provider({ children }: { children: React.ReactNode }) { export function Provider({ children }: { children: React.ReactNode }) {
return ( return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
} }
+30 -30
View File
@@ -1,48 +1,48 @@
import { StrictMode } from 'react' import { StrictMode } from "react";
import ReactDOM from 'react-dom/client' import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from '@tanstack/react-router' import { RouterProvider, createRouter } from "@tanstack/react-router";
import * as TanStackQueryProvider from './integrations/tanstack-query/root-provider.tsx' import * as TanStackQueryProvider from "./integrations/tanstack-query/root-provider.tsx";
// Import the generated route tree // Import the generated route tree
import { routeTree } from './routeTree.gen' import { routeTree } from "./routeTree.gen";
import './styles.css' import "./styles.css";
import reportWebVitals from './reportWebVitals.ts' import reportWebVitals from "./reportWebVitals.ts";
// Create a new router instance // Create a new router instance
const router = createRouter({ const router = createRouter({
routeTree, routeTree,
context: { context: {
...TanStackQueryProvider.getContext(), ...TanStackQueryProvider.getContext(),
}, },
defaultPreload: 'intent', defaultPreload: "intent",
scrollRestoration: true, scrollRestoration: true,
defaultStructuralSharing: true, defaultStructuralSharing: true,
defaultPreloadStaleTime: 0, defaultPreloadStaleTime: 0,
}) });
// Register the router instance for type safety // Register the router instance for type safety
declare module '@tanstack/react-router' { declare module "@tanstack/react-router" {
interface Register { interface Register {
router: typeof router router: typeof router;
} }
} }
// Render the app // Render the app
const rootElement = document.getElementById('app') const rootElement = document.getElementById("app");
if (rootElement && !rootElement.innerHTML) { if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement) const root = ReactDOM.createRoot(rootElement);
root.render( root.render(
<StrictMode> <StrictMode>
<TanStackQueryProvider.Provider> <TanStackQueryProvider.Provider>
<RouterProvider router={router} /> <RouterProvider router={router} />
</TanStackQueryProvider.Provider> </TanStackQueryProvider.Provider>
</StrictMode>, </StrictMode>
) );
} }
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log)) // to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals() reportWebVitals();
+11 -11
View File
@@ -1,13 +1,13 @@
const reportWebVitals = (onPerfEntry?: () => void) => { const reportWebVitals = (onPerfEntry?: () => void) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => { import("web-vitals").then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => {
onCLS(onPerfEntry) onCLS(onPerfEntry);
onINP(onPerfEntry) onINP(onPerfEntry);
onFCP(onPerfEntry) onFCP(onPerfEntry);
onLCP(onPerfEntry) onLCP(onPerfEntry);
onTTFB(onPerfEntry) onTTFB(onPerfEntry);
}) });
} }
} };
export default reportWebVitals export default reportWebVitals;
+15 -15
View File
@@ -1,25 +1,25 @@
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router' import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import Header from '../components/Header' import Header from "../components/Header";
import TanStackQueryLayout from '../integrations/tanstack-query/layout.tsx' import TanStackQueryLayout from "../integrations/tanstack-query/layout.tsx";
import type { QueryClient } from '@tanstack/react-query' import type { QueryClient } from "@tanstack/react-query";
interface MyRouterContext { interface MyRouterContext {
queryClient: QueryClient queryClient: QueryClient;
} }
export const Route = createRootRouteWithContext<MyRouterContext>()({ export const Route = createRootRouteWithContext<MyRouterContext>()({
component: () => ( component: () => (
<> <>
<Header /> <Header />
<Outlet /> <Outlet />
<TanStackRouterDevtools /> <TanStackRouterDevtools />
<TanStackQueryLayout /> <TanStackQueryLayout />
</> </>
), ),
}) });
+178 -187
View File
@@ -1,200 +1,191 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { useAppForm } from '../hooks/demo.form' import { useAppForm } from "../hooks/demo.form";
export const Route = createFileRoute('/demo/form/address')({ export const Route = createFileRoute("/demo/form/address")({
component: AddressForm, component: AddressForm,
}) });
function AddressForm() { function AddressForm() {
const form = useAppForm({ const form = useAppForm({
defaultValues: { defaultValues: {
fullName: '', fullName: "",
email: '', email: "",
address: { address: {
street: '', street: "",
city: '', city: "",
state: '', state: "",
zipCode: '', zipCode: "",
country: '', country: "",
}, },
phone: '', phone: "",
}, },
validators: { validators: {
onBlur: ({ value }) => { onBlur: ({ value }) => {
const errors = { const errors = {
fields: {}, fields: {},
} as { } as {
fields: Record<string, string> fields: Record<string, string>;
} };
if (value.fullName.trim().length === 0) { if (value.fullName.trim().length === 0) {
errors.fields.fullName = 'Full name is required' errors.fields.fullName = "Full name is required";
} }
return errors return errors;
}, },
}, },
onSubmit: ({ value }) => { onSubmit: ({ value }) => {
console.log(value) console.log(value);
// Show success message // Show success message
alert('Form submitted successfully!') alert("Form submitted successfully!");
}, },
}) });
return ( return (
<div <div
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white" className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
style={{ style={{
backgroundImage: backgroundImage: "radial-gradient(50% 50% at 5% 40%, #f4a460 0%, #8b4513 70%, #1a0f0a 100%)",
'radial-gradient(50% 50% at 5% 40%, #f4a460 0%, #8b4513 70%, #1a0f0a 100%)', }}
}}
>
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
form.handleSubmit()
}}
className="space-y-6"
> >
<form.AppField name="fullName"> <div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
{(field) => <field.TextField label="Full Name" />} <form
</form.AppField> onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
className="space-y-6"
>
<form.AppField name="fullName">{(field) => <field.TextField label="Full Name" />}</form.AppField>
<form.AppField <form.AppField
name="email" name="email"
validators={{ validators={{
onBlur: ({ value }) => { onBlur: ({ value }) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
return 'Email is required' return "Email is required";
} }
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) { if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return 'Invalid email address' return "Invalid email address";
} }
return undefined return undefined;
}, },
}} }}
> >
{(field) => <field.TextField label="Email" />} {(field) => <field.TextField label="Email" />}
</form.AppField> </form.AppField>
<form.AppField <form.AppField
name="address.street" name="address.street"
validators={{ validators={{
onBlur: ({ value }) => { onBlur: ({ value }) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
return 'Street address is required' return "Street address is required";
} }
return undefined return undefined;
}, },
}} }}
> >
{(field) => <field.TextField label="Street Address" />} {(field) => <field.TextField label="Street Address" />}
</form.AppField> </form.AppField>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<form.AppField <form.AppField
name="address.city" name="address.city"
validators={{ validators={{
onBlur: ({ value }) => { onBlur: ({ value }) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
return 'City is required' return "City is required";
} }
return undefined return undefined;
}, },
}} }}
> >
{(field) => <field.TextField label="City" />} {(field) => <field.TextField label="City" />}
</form.AppField> </form.AppField>
<form.AppField <form.AppField
name="address.state" name="address.state"
validators={{ validators={{
onBlur: ({ value }) => { onBlur: ({ value }) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
return 'State is required' return "State is required";
} }
return undefined return undefined;
}, },
}} }}
> >
{(field) => <field.TextField label="State" />} {(field) => <field.TextField label="State" />}
</form.AppField> </form.AppField>
<form.AppField <form.AppField
name="address.zipCode" name="address.zipCode"
validators={{ validators={{
onBlur: ({ value }) => { onBlur: ({ value }) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
return 'Zip code is required' return "Zip code is required";
} }
if (!/^\d{5}(-\d{4})?$/.test(value)) { if (!/^\d{5}(-\d{4})?$/.test(value)) {
return 'Invalid zip code format' return "Invalid zip code format";
} }
return undefined return undefined;
}, },
}} }}
> >
{(field) => <field.TextField label="Zip Code" />} {(field) => <field.TextField label="Zip Code" />}
</form.AppField> </form.AppField>
</div> </div>
<form.AppField <form.AppField
name="address.country" name="address.country"
validators={{ validators={{
onBlur: ({ value }) => { onBlur: ({ value }) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
return 'Country is required' return "Country is required";
} }
return undefined return undefined;
}, },
}} }}
> >
{(field) => ( {(field) => (
<field.Select <field.Select
label="Country" label="Country"
values={[ values={[
{ label: 'United States', value: 'US' }, { label: "United States", value: "US" },
{ label: 'Canada', value: 'CA' }, { label: "Canada", value: "CA" },
{ label: 'United Kingdom', value: 'UK' }, { label: "United Kingdom", value: "UK" },
{ label: 'Australia', value: 'AU' }, { label: "Australia", value: "AU" },
{ label: 'Germany', value: 'DE' }, { label: "Germany", value: "DE" },
{ label: 'France', value: 'FR' }, { label: "France", value: "FR" },
{ label: 'Japan', value: 'JP' }, { label: "Japan", value: "JP" },
]} ]}
placeholder="Select a country" placeholder="Select a country"
/> />
)} )}
</form.AppField> </form.AppField>
<form.AppField <form.AppField
name="phone" name="phone"
validators={{ validators={{
onBlur: ({ value }) => { onBlur: ({ value }) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
return 'Phone number is required' return "Phone number is required";
} }
if ( if (!/^(\+\d{1,3})?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/.test(value)) {
!/^(\+\d{1,3})?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/.test( return "Invalid phone number format";
value, }
) return undefined;
) { },
return 'Invalid phone number format' }}
} >
return undefined {(field) => <field.TextField label="Phone" placeholder="123-456-7890" />}
}, </form.AppField>
}}
>
{(field) => (
<field.TextField label="Phone" placeholder="123-456-7890" />
)}
</form.AppField>
<div className="flex justify-end"> <div className="flex justify-end">
<form.AppForm> <form.AppForm>
<form.SubscribeButton label="Submit" /> <form.SubscribeButton label="Submit" />
</form.AppForm> </form.AppForm>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
) );
} }
+52 -54
View File
@@ -1,65 +1,63 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { z } from 'zod' import { z } from "zod";
import { useAppForm } from '../hooks/demo.form' import { useAppForm } from "../hooks/demo.form";
export const Route = createFileRoute('/demo/form/simple')({ // Routing
component: SimpleForm, export const Route = createFileRoute("/demo/form/simple")({
}) component: SimpleForm,
});
const schema = z.object({ const schema = z.object({
title: z.string().min(1, 'Title is required'), title: z.string().min(1, "Title is required"),
description: z.string().min(1, 'Description is required'), description: z.string().min(1, "Description is required"),
}) });
function SimpleForm() { function SimpleForm() {
const form = useAppForm({ const form = useAppForm({
defaultValues: { defaultValues: {
title: '', title: "",
description: '', description: "",
}, },
validators: { validators: {
onBlur: schema, onBlur: schema,
}, },
onSubmit: ({ value }) => { onSubmit: ({ value }) => {
console.log(value) console.log(value);
// Show success message // Show success message
alert('Form submitted successfully!') alert("Form submitted successfully!");
}, },
}) });
return ( return (
<div <div
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white" className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
style={{ style={{
backgroundImage: backgroundImage: "radial-gradient(50% 50% at 5% 40%, #add8e6 0%, #0000ff 70%, #00008b 100%)",
'radial-gradient(50% 50% at 5% 40%, #add8e6 0%, #0000ff 70%, #00008b 100%)', }}
}}
>
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
form.handleSubmit()
}}
className="space-y-6"
> >
<form.AppField name="title"> <div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
{(field) => <field.TextField label="Title" />} <form
</form.AppField> onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
className="space-y-6"
>
<form.AppField name="title">{(field) => <field.TextField label="Title" />}</form.AppField>
<form.AppField name="description"> <form.AppField name="description">
{(field) => <field.TextArea label="Description" />} {(field) => <field.TextArea label="Description" />}
</form.AppField> </form.AppField>
<div className="flex justify-end"> <div className="flex justify-end">
<form.AppForm> <form.AppForm>
<form.SubscribeButton label="Submit" /> <form.SubscribeButton label="Submit" />
</form.AppForm> </form.AppForm>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
) );
} }
+20 -21
View File
@@ -1,26 +1,25 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { useQuery } from '@tanstack/react-query' import { useQuery } from "@tanstack/react-query";
export const Route = createFileRoute('/demo/tanstack-query')({ export const Route = createFileRoute("/demo/tanstack-query")({
component: TanStackQueryDemo, component: TanStackQueryDemo,
}) });
function TanStackQueryDemo() { function TanStackQueryDemo() {
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['people'], queryKey: ["people"],
queryFn: () => queryFn: () => Promise.resolve([{ name: "John Doe" }, { name: "Jane Doe" }]),
Promise.resolve([{ name: 'John Doe' }, { name: 'Jane Doe' }]), initialData: [],
initialData: [], });
})
return ( return (
<div className="p-4"> <div className="p-4">
<h1 className="text-2xl mb-4">People list</h1> <h1 className="text-2xl mb-4">People list</h1>
<ul> <ul>
{data.map((person) => ( {data.map((person) => (
<li key={person.name}>{person.name}</li> <li key={person.name}>{person.name}</li>
))} ))}
</ul> </ul>
</div> </div>
) );
} }
+31 -31
View File
@@ -2,38 +2,38 @@ import { createFileRoute } from "@tanstack/react-router";
import logo from "../logo.svg"; import logo from "../logo.svg";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: App, component: App,
}); });
function App() { function App() {
return ( return (
<div className="text-center"> <div className="text-center">
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]"> <header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]">
<img <img
src={logo} src={logo}
className="h-[40vmin] pointer-events-none animate-[spin_20s_linear_infinite]" className="h-[40vmin] pointer-events-none animate-[spin_20s_linear_infinite]"
alt="logo" alt="logo"
/> />
<p> <p>
Edit <code>src/routes/index.tsx</code> and save to reload. Edit <code>src/routes/index.tsx</code> and save to reload.
</p> </p>
<a <a
className="text-[#61dafb] hover:underline" className="text-[#61dafb] hover:underline"
href="https://reactjs.org" href="https://reactjs.org"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
Learn React Learn React
</a> </a>
<a <a
className="text-[#61dafb] hover:underline" className="text-[#61dafb] hover:underline"
href="https://tanstack.com" href="https://tanstack.com"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
Learn TanStack Learn TanStack
</a> </a>
</header> </header>
</div> </div>
); );
} }
+7 -8
View File
@@ -1,15 +1,14 @@
@import "tailwindcss"; @import "tailwindcss";
body { body {
@apply m-0; @apply m-0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", font-family:
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
sans-serif; "Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
monospace;
} }
+23 -23
View File
@@ -1,28 +1,28 @@
{ {
"include": ["**/*.ts", "**/*.tsx"], "include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"jsx": "react-jsx", "jsx": "react-jsx",
"module": "ESNext", "module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"], "types": ["vite/client"],
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"noEmit": true, "noEmit": true,
/* Linting */ /* Linting */
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true, "noUncheckedSideEffectImports": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"], "@/*": ["./src/*"]
}
} }
}
} }
+15 -19
View File
@@ -1,24 +1,20 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import viteReact from '@vitejs/plugin-react' import viteReact from "@vitejs/plugin-react";
import tailwindcss from '@tailwindcss/vite' import tailwindcss from "@tailwindcss/vite";
import { TanStackRouterVite } from '@tanstack/router-plugin/vite' import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import { resolve } from 'node:path' import { resolve } from "node:path";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [TanStackRouterVite({ autoCodeSplitting: true }), viteReact(), tailwindcss()],
TanStackRouterVite({ autoCodeSplitting: true }), test: {
viteReact(), globals: true,
tailwindcss(), environment: "jsdom",
],
test: {
globals: true,
environment: 'jsdom',
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
}, },
}, resolve: {
}) alias: {
"@": resolve(__dirname, "./src"),
},
},
});
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
file=development.yml
docker compose -f $file down && docker compose -f $file up --build -d