# Forms

QuickDapp uses simple React state management for form handling. Forms integrate with the GraphQL API and provide basic validation and error handling.

# Form Components

# Basic Form Structure

Forms use standard React state with simple validation:

import * as React from "react"
import { Button } from "./Button"
import { Form, Input, Label } from "./Form"

interface FormData {
  name: string
  symbol: string
  initialSupply: string
}

interface FormErrors {
  name?: string
  symbol?: string
  initialSupply?: string
}

export function TokenForm() {
  const [formData, setFormData] = React.useState<FormData>({
    name: '',
    symbol: '',
    initialSupply: ''
  })
  const [errors, setErrors] = React.useState<FormErrors>({})
  const [isSubmitting, setIsSubmitting] = React.useState(false)

  const validateForm = (): boolean => {
    const newErrors: FormErrors = {}
    
    if (!formData.name.trim()) {
      newErrors.name = 'Name is required'
    }
    
    if (!formData.symbol.trim()) {
      newErrors.symbol = 'Symbol is required'
    } else if (formData.symbol.length > 6) {
      newErrors.symbol = 'Symbol must be 6 characters or less'
    }
    
    if (!formData.initialSupply.trim()) {
      newErrors.initialSupply = 'Initial supply is required'
    }
    
    setErrors(newErrors)
    return Object.keys(newErrors).length === 0
  }

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    if (!validateForm()) return
    
    setIsSubmitting(true)
    try {
      // Handle form submission
      await submitForm(formData)
    } catch (error) {
      // Handle error
    } finally {
      setIsSubmitting(false)
    }
  }

  const handleInputChange = (field: keyof FormData) => (
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    setFormData(prev => ({ ...prev, [field]: e.target.value }))
    // Clear error when user starts typing
    if (errors[field]) {
      setErrors(prev => ({ ...prev, [field]: undefined }))
    }
  }

  return (
    <Form onSubmit={handleSubmit}>
      <div>
        <Label htmlFor="name">Token Name</Label>
        <Input
          id="name"
          value={formData.name}
          onChange={handleInputChange('name')}
          error={errors.name}
        />
      </div>
      
      <div>
        <Label htmlFor="symbol">Symbol</Label>
        <Input
          id="symbol"
          value={formData.symbol}
          onChange={handleInputChange('symbol')}
          error={errors.symbol}
        />
      </div>
      
      <div>
        <Label htmlFor="initialSupply">Initial Supply</Label>
        <Input
          id="initialSupply"
          value={formData.initialSupply}
          onChange={handleInputChange('initialSupply')}
          error={errors.initialSupply}
        />
      </div>
      
      <Button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Creating...' : 'Create Token'}
      </Button>
    </Form>
  )
}

# Form Components

# Form Container

Basic form wrapper:

interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
  children: React.ReactNode
}

export function Form({ children, ...props }: FormProps) {
  return (
    <form {...props} className="space-y-4">
      {children}
    </form>
  )
}

# Input Component

Input with error handling:

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  error?: string
}

export function Input({ error, className, ...props }: InputProps) {
  return (
    <div>
      <input
        {...props}
        className={`input ${error ? 'input-error' : ''} ${className || ''}`}
      />
      {error && <p className="text-red-600 text-sm mt-1">{error}</p>}
    </div>
  )
}

# Label Component

Form labels:

interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
  children: React.ReactNode
}

export function Label({ children, ...props }: LabelProps) {
  return (
    <label {...props} className="block text-sm font-medium mb-1">
      {children}
    </label>
  )
}

# Integration with GraphQL

Forms typically integrate with GraphQL mutations:

import { useMutation } from '@tanstack/react-query'
import { graphql } from '../lib/graphql'

const CREATE_TOKEN_MUTATION = graphql(`
  mutation CreateToken($input: CreateTokenInput!) {
    createToken(input: $input) {
      id
      name
      symbol
      address
    }
  }
`)

export function useCreateToken() {
  return useMutation({
    mutationFn: async (input: CreateTokenInput) => {
      const response = await fetch('/graphql', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          query: CREATE_TOKEN_MUTATION,
          variables: { input }
        })
      })
      return response.json()
    }
  })
}

Use in form:

export function TokenForm() {
  const createToken = useCreateToken()
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    if (!validateForm()) return
    
    try {
      await createToken.mutateAsync(formData)
      // Handle success
    } catch (error) {
      // Handle error
    }
  }
  
  // ... rest of form logic
}

# Error Handling

# Validation Errors

Client-side validation for immediate feedback:

const validateField = (field: string, value: string): string | undefined => {
  switch (field) {
    case 'name':
      return !value.trim() ? 'Name is required' : undefined
    case 'symbol':
      if (!value.trim()) return 'Symbol is required'
      if (value.length > 6) return 'Symbol must be 6 characters or less'
      return undefined
    case 'initialSupply':
      if (!value.trim()) return 'Initial supply is required'
      if (isNaN(Number(value))) return 'Must be a number'
      return undefined
    default:
      return undefined
  }
}

# Server Errors

Handle GraphQL errors:

const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault()
  
  try {
    await createToken.mutateAsync(formData)
    onSuccess()
  } catch (error: any) {
    if (error.graphQLErrors) {
      // Handle GraphQL validation errors
      const fieldErrors = error.graphQLErrors
        .filter(err => err.extensions?.field)
        .reduce((acc, err) => ({
          ...acc,
          [err.extensions.field]: err.message
        }), {})
      
      setErrors(fieldErrors)
    } else {
      // Handle general errors
      setGeneralError(error.message || 'An error occurred')
    }
  }
}

# Form Patterns

# Reset Form

Clear form after successful submission:

const resetForm = () => {
  setFormData({ name: '', symbol: '', initialSupply: '' })
  setErrors({})
}

const handleSubmit = async (e: React.FormEvent) => {
  // ... submission logic
  
  if (success) {
    resetForm()
  }
}

# Loading States

Show loading during submission:

<Button type="submit" disabled={isSubmitting}>
  {isSubmitting ? 'Creating...' : 'Create Token'}
</Button>

# Form Validation

Validate on submit and optionally on blur:

const handleBlur = (field: keyof FormData) => () => {
  const error = validateField(field, formData[field])
  setErrors(prev => ({ ...prev, [field]: error }))
}

<Input
  value={formData.name}
  onChange={handleInputChange('name')}
  onBlur={handleBlur('name')}
  error={errors.name}
/>

QuickDapp's form system keeps things simple while providing the essential functionality needed for user input and validation.