@letar/forms

Autosave to Server

Automatic server-side form data saving with debounce, offline fallback, and draft recovery

Full example

The complete sandbox example, read directly from the form-develop-app source at build time — switch framework/skin to see useFormAutosave wired to a real form. No shadcn/Vue/Angular equivalent exists yet — those tabs show as disabled.

apps/form-develop-app/src/app/autosave-demo/page.tsx
'use client'

import { Box, Code, Heading, Text, VStack } from '@chakra-ui/react'
import { AutosaveIndicator, Form, useFormAutosave } from '@letar/forms'
import { DemoPageLayout } from '../_components'

/** Имитация серверного endpoint */
function AutosaveForm() {
  return (
    <Form
      debug
      initialValue={{ title: '', description: '', category: '' }}
      onSubmit={(data) => alert(JSON.stringify(data, null, 2))}
    >
      {/* Autosave подключается внутри Form через render prop */}
      <AutosaveFormInner />
    </Form>
  )
}

function AutosaveFormInner() {
  // В реальном приложении: endpoint: '/api/drafts'
  // Здесь для демо — endpoint который возвращает 200
  const autosave = useFormAutosave(null, {
    endpoint: '/api/autosave-mock',
    draftId: 'demo-draft-1',
    interval: 3000,
    debounce: 1000,
  })

  return (
    <VStack gap={4} align="stretch">
      <Form.Field.String name="title" label="Заголовок" />
      <Form.Field.Textarea name="description" label="Описание" />
      <Form.Field.String name="category" label="Категория" />
      <AutosaveIndicator status={autosave.status} lastSavedAt={autosave.lastSavedAt} error={autosave.error} />
      <Form.Button.Submit>Отправить</Form.Button.Submit>
    </VStack>
  )
}

export default function AutosaveDemoPage() {
  return (
    <DemoPageLayout
      title="Autosave to Server"
      description="Серверное автосохранение с debounce, fallback на localStorage, восстановление черновиков"
    >
      <VStack gap={8} align="stretch">
        <Box>
          <Heading size="md" mb={3}>
            Автосохранение каждые 3 секунды
          </Heading>
          <Text fontSize="sm" color="fg.muted" mb={4}>
            Данные отправляются на сервер через POST. При отсутствии сети — сохраняются в localStorage. Индикатор
            показывает статус: &quot;Сохраняю...&quot;&quot;Сохранено (время)&quot;.
          </Text>
          <AutosaveForm />
        </Box>

        <Box p={4} bg="bg.subtle" borderRadius="md">
          <Heading size="sm" mb={2}>
            API:
          </Heading>
          <Code display="block" whiteSpace="pre" fontSize="xs" p={3}>
            {`const autosave = useFormAutosave(form, {
  endpoint: '/api/drafts',
  draftId: 'application-123',
  interval: 5000,   // каждые 5 сек
  debounce: 1000,   // не чаще 1 раз в сек
})

// Статус: autosave.status — 'idle' | 'saving' | 'saved' | 'error'
// Принудительно: autosave.saveNow()
// Восстановить: autosave.loadDraft()`}
          </Code>
        </Box>
      </VStack>
    </DemoPageLayout>
  )
}

Overview

useFormAutosave periodically saves form data to a server endpoint. If the network is unavailable, data is stored in localStorage and synced when connectivity returns.

Basic Usage

import { useFormAutosave, AutosaveIndicator } from '@letar/forms'

function MyForm() {
  const autosave = useFormAutosave(form, {
    endpoint: '/api/drafts',
    draftId: 'application-123',
    interval: 5000,
    debounce: 1000,
  })

  return (
    <>
      <Form.Field.String name="title" />
      <AutosaveIndicator status={autosave.status} lastSavedAt={autosave.lastSavedAt} error={autosave.error} />
    </>
  )
}

Config

OptionTypeDefaultDescription
endpointstringrequiredServer URL for POST/PUT
intervalnumber5000Save interval (ms)
debouncenumber1000Min delay between saves (ms)
draftIdstringDraft identifier for recovery
method'POST' | 'PUT' | 'PATCH''POST'HTTP method
headersRecord<string, string>Extra headers
onSave(response) => voidSuccess callback
onError(error) => voidError callback

Result

PropertyTypeDescription
status'idle' | 'saving' | 'saved' | 'error'Current status
lastSavedAtDate | nullLast successful save time
errorstring | nullError message
saveNow()() => Promise<void>Force save immediately
loadDraft()() => Promise<object | null>Load draft from server

Features

  • Deduplication — skips save if data has not changed
  • Offline fallback — saves to localStorage when offline, clears on successful server save
  • Draft recoveryloadDraft() tries server first, then localStorage

Live Example

Try the interactive example on forms-example.letar.best.

On this page