/ src / routes / demo / start.server-funcs.tsx
start.server-funcs.tsx
  1  import fs from 'node:fs'
  2  import { useCallback, useState } from 'react'
  3  import { createFileRoute, useRouter } from '@tanstack/react-router'
  4  import { createServerFn } from '@tanstack/react-start'
  5  
  6  /*
  7  const loggingMiddleware = createMiddleware().server(
  8    async ({ next, request }) => {
  9      console.log("Request:", request.url);
 10      return next();
 11    }
 12  );
 13  const loggedServerFunction = createServerFn({ method: "GET" }).middleware([
 14    loggingMiddleware,
 15  ]);
 16  */
 17  
 18  const TODOS_FILE = 'todos.json'
 19  
 20  async function readTodos() {
 21    return JSON.parse(
 22      await fs.promises.readFile(TODOS_FILE, 'utf-8').catch(() =>
 23        JSON.stringify(
 24          [
 25            { id: 1, name: 'Get groceries' },
 26            { id: 2, name: 'Buy a new phone' },
 27          ],
 28          null,
 29          2,
 30        ),
 31      ),
 32    )
 33  }
 34  
 35  const getTodos = createServerFn({
 36    method: 'GET',
 37  }).handler(async () => await readTodos())
 38  
 39  const addTodo = createServerFn({ method: 'POST' })
 40    .inputValidator((d: string) => d)
 41    .handler(async ({ data }) => {
 42      const todos = await readTodos()
 43      todos.push({ id: todos.length + 1, name: data })
 44      await fs.promises.writeFile(TODOS_FILE, JSON.stringify(todos, null, 2))
 45      return todos
 46    })
 47  
 48  export const Route = createFileRoute('/demo/start/server-funcs')({
 49    component: Home,
 50    loader: async () => await getTodos(),
 51  })
 52  
 53  function Home() {
 54    const router = useRouter()
 55    let todos = Route.useLoaderData()
 56  
 57    const [todo, setTodo] = useState('')
 58  
 59    const submitTodo = useCallback(async () => {
 60      todos = await addTodo({ data: todo })
 61      setTodo('')
 62      router.invalidate()
 63    }, [addTodo, todo])
 64  
 65    return (
 66      <div
 67        className="flex items-center justify-center min-h-screen bg-gradient-to-br from-zinc-800 to-black p-4 text-white"
 68        style={{
 69          backgroundImage:
 70            'radial-gradient(50% 50% at 20% 60%, #23272a 0%, #18181b 50%, #000000 100%)',
 71        }}
 72      >
 73        <div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
 74          <h1 className="text-2xl mb-4">Start Server Functions - Todo Example</h1>
 75          <ul className="mb-4 space-y-2">
 76            {todos?.map((t) => (
 77              <li
 78                key={t.id}
 79                className="bg-white/10 border border-white/20 rounded-lg p-3 backdrop-blur-sm shadow-md"
 80              >
 81                <span className="text-lg text-white">{t.name}</span>
 82              </li>
 83            ))}
 84          </ul>
 85          <div className="flex flex-col gap-2">
 86            <input
 87              type="text"
 88              value={todo}
 89              onChange={(e) => setTodo(e.target.value)}
 90              onKeyDown={(e) => {
 91                if (e.key === 'Enter') {
 92                  submitTodo()
 93                }
 94              }}
 95              placeholder="Enter a new todo..."
 96              className="w-full px-4 py-3 rounded-lg border border-white/20 bg-white/10 backdrop-blur-sm text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent"
 97            />
 98            <button
 99              disabled={todo.trim().length === 0}
100              onClick={submitTodo}
101              className="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-500/50 disabled:cursor-not-allowed text-white font-bold py-3 px-4 rounded-lg transition-colors"
102            >
103              Add todo
104            </button>
105          </div>
106        </div>
107      </div>
108    )
109  }