Form Submit
Demonstrates async submit handling with loading, success, and error states.
See interactive examples below.
1import { createSignal, createMemo } from '@barefootjs/dom'2import { Input } from '@/components/ui/input'3import { Button } from '@/components/ui/button'4import { Toast, ToastProvider, ToastTitle, ToastDescription } from '@/components/ui/toast'56const [email, setEmail] = createSignal('')7const [loading, setLoading] = createSignal(false)8const [success, setSuccess] = createSignal(false)910const isValid = createMemo(() => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email()))1112const handleSubmit = async () => {13 if (!isValid() || loading()) return1415 setLoading(true)1617 try {18 await fetch('/api/subscribe', {19 method: 'POST',20 body: JSON.stringify({ email: email() })21 })22 setSuccess(true)23 setTimeout(() => setSuccess(false), 3000)24 } finally {25 setLoading(false)26 }27}2829<Input30 inputValue={email()}31 onInput={(e) => setEmail(e.target.value)}32 inputDisabled={loading()}33/>34<Button onClick={handleSubmit} disabled={!isValid() || loading()}>35 {loading() ? 'Submitting...' : 'Subscribe'}36</Button>37<ToastProvider>38 <Toast variant="success" open={success()}>39 <ToastTitle>Success</ToastTitle>40 <ToastDescription>Subscribed!</ToastDescription>41 </Toast>42</ToastProvider>#Pattern Overview
Form submission in BarefootJS uses signals to manage async state transitions:idle → loading → success/error. This pattern provides reactive feedback without external state machines.
Key concepts:
- Loading signal: Tracks submission in progress
- Error signal: Stores error message from failed requests
- Success signal: Triggers success feedback (toast, message)
- Disabled state: Prevents double submission and shows loading
#Examples
#Basic Submit with Loading
Success
You have been subscribed successfully!
1import { createSignal, createMemo } from '@barefootjs/dom'2import { Input } from '@/components/ui/input'3import { Button } from '@/components/ui/button'4import { Toast, ToastProvider, ToastTitle, ToastDescription } from '@/components/ui/toast'56const [email, setEmail] = createSignal('')7const [loading, setLoading] = createSignal(false)8const [success, setSuccess] = createSignal(false)910const isValid = createMemo(() => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email()))1112const handleSubmit = async () => {13 if (!isValid() || loading()) return1415 setLoading(true)1617 try {18 await fetch('/api/subscribe', {19 method: 'POST',20 body: JSON.stringify({ email: email() })21 })22 setSuccess(true)23 setTimeout(() => setSuccess(false), 3000)24 } finally {25 setLoading(false)26 }27}2829<Input30 inputValue={email()}31 onInput={(e) => setEmail(e.target.value)}32 inputDisabled={loading()}33/>34<Button onClick={handleSubmit} disabled={!isValid() || loading()}>35 {loading() ? 'Submitting...' : 'Subscribe'}36</Button>37<ToastProvider>38 <Toast variant="success" open={success()}>39 <ToastTitle>Success</ToastTitle>40 <ToastDescription>Subscribed!</ToastDescription>41 </Toast>42</ToastProvider>#Network Error and Retry
Success
Message sent successfully!
Error
1import { createSignal, createMemo } from '@barefootjs/dom'23const [loading, setLoading] = createSignal(false)4const [errorMsg, setErrorMsg] = createSignal('')5const [success, setSuccess] = createSignal(false)67const handleSubmit = async () => {8 setLoading(true)9 setErrorMsg('')1011 try {12 const res = await fetch('/api/submit', { method: 'POST', ... })13 if (!res.ok) throw new Error('Request failed')14 setSuccess(true)15 } catch (err) {16 setErrorMsg(err.message || 'Network error. Please try again.')17 } finally {18 setLoading(false)19 }20}2122const handleRetry = () => {23 setErrorMsg('')24 handleSubmit()25}2627<Toast variant="error" open={errorMsg() !== ''}>28 <ToastTitle>Error</ToastTitle>29 <ToastDescription>{errorMsg()}</ToastDescription>30 <ToastAction onClick={handleRetry}>Retry</ToastAction>31</Toast>#Server Validation Error
Try "taken@example.com" to see server validation error
Success
Registration successful!
1import { createSignal, createMemo } from '@barefootjs/dom'23const [email, setEmail] = createSignal('')4const [serverError, setServerError] = createSignal('')56// Client-side validation7const clientError = createMemo(() => {8 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email())) return 'Invalid email'9 return ''10})1112const handleSubmit = async () => {13 const res = await fetch('/api/register', {14 method: 'POST',15 body: JSON.stringify({ email: email() })16 })1718 if (!res.ok) {19 const data = await res.json()20 // Display server validation error21 setServerError(data.error) // e.g., "Email already registered"22 return23 }2425 // Success handling...26}2728// Clear server error when user modifies input29<Input30 onInput={(e) => {31 setEmail(e.target.value)32 setServerError('')33 }}34/>35{serverError() && <p class="text-red-400">{serverError()}</p>}#Key Points
Async State Management
- Use
loadingsignal to track submission state - Disable form inputs and button during submission
- Show loading text in button:
loading() ? "Submitting..." : "Submit" - Signals are sufficient for typical forms - no state machine needed
Error Handling
- Store error message in signal:
setErrorMsg(err.message) - Display errors via Toast (error variant) or inline message
- Provide retry action for network errors
- Clear error when user modifies input or retries
Success Feedback
- Use Toast (success variant) for non-blocking feedback
- Auto-dismiss success toast:
setTimeout(() => setSuccess(false), 3000) - Reset form after successful submission if appropriate
Server Validation
- Separate client-side and server-side validation signals
- Display server errors inline near the relevant field
- Clear server error when user modifies the field
- Example: "Email already registered" from server response