Skip to content

Form Integration

How to integrate rut.ts with form libraries and validation.

Vanilla JavaScript#

HTML
<!DOCTYPE html>
<html>
<body>
  <input type="text" id="rut-input" placeholder="12.345.678-5" />
  <span id="error-message"></span>
  
  <script type="module">
    import { format, validate } from 'rut.ts'
    
    const input = document.getElementById('rut-input')
    const error = document.getElementById('error-message')
    
    input.addEventListener('input', (e) => {
      const value = e.target.value
      
      // Format as user types
      const formatted = format(value, { incremental: true })
      
      // Update input if different (avoid cursor jump)
      if (formatted !== value) {
        e.target.value = formatted
      }
      
      // Show validation
      if (validate(formatted)) {
        error.textContent = ''
        input.classList.remove('invalid')
      } else {
        error.textContent = 'RUT inválido'
        input.classList.add('invalid')
      }
    })
  </script>
</body>
</html>

React Hook Form#

TypeScript
import { useForm } from 'react-hook-form'
import { validate } from 'rut.ts'
 
type FormData = {
  rut: string
  name: string
}
 
function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>()
  
  const onSubmit = (data: FormData) => {
    console.log('Valid RUT:', data.rut)
  }
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('rut', {
          required: 'RUT is required',
          validate: (value) => 
            validate(value) || 'RUT is invalid'
        })}
        placeholder="12.345.678-5"
      />
      {errors.rut && <span>{errors.rut.message}</span>}
      
      <button type="submit">Submit</button>
    </form>
  )
}

Zod Schema Validation#

Requires zod >= 4.

Validate only#

TypeScript
import { z } from 'zod'
import { validate } from 'rut.ts'
 
const userSchema = z.object({
  name: z.string(),
  rut: z.string().refine(validate, 'RUT inválido'),
  email: z.string().email()
})
 
const result = userSchema.safeParse({
  name: 'Juan Pérez',
  rut: '12.345.678-5',
  email: 'juan@example.com'
})
 
if (result.success) {
  console.log('Valid data:', result.data)
}

Validate and normalize for storage#

Use .transform(clean) to store a consistent, separator-free RUT regardless of what the user typed.

TypeScript
import { z } from 'zod'
import { validate, clean } from 'rut.ts'
 
const userSchema = z.object({
  name: z.string(),
  rut: z.string().refine(validate, 'RUT inválido').transform(clean),
  email: z.string().email()
})
 
const result = userSchema.safeParse({
  name: 'Juan Pérez',
  rut: '12.345.678-5',   // user input (any valid format)
  email: 'juan@example.com'
})
 
if (result.success) {
  console.log(result.data.rut)  // '123456785' — clean, ready for DB
}

Formik#

TypeScript
import { Formik, Form, Field, ErrorMessage } from 'formik'
import { validate, format } from 'rut.ts'
 
function MyForm() {
  return (
    <Formik
      initialValues={{ rut: '' }}
      validate={(values) => {
        const errors: any = {}
        if (!validate(values.rut)) {
          errors.rut = 'Invalid RUT'
        }
        return errors
      }}
      onSubmit={(values) => {
        console.log('Submitted:', values)
      }}
    >
      {({ values, setFieldValue }) => (
        <Form>
          <Field
            name="rut"
            placeholder="12.345.678-5"
            onChange={(e: any) => {
              const formatted = format(e.target.value, { 
                incremental: true 
              })
              setFieldValue('rut', formatted)
            }}
          />
          <ErrorMessage name="rut" component="div" />
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  )
}

Next.js Server Actions#

TypeScript
'use server'
 
import { z } from 'zod'
import { validate, clean } from 'rut.ts'
 
const formSchema = z.object({
  rut: z.string().refine(validate, 'RUT inválido').transform(clean)
})
 
export async function createUser(formData: FormData) {
  const result = formSchema.safeParse({
    rut: formData.get('rut') as string
  })
 
  if (!result.success) {
    return { error: result.error.flatten() }
  }
 
  // result.data.rut is already clean — no manual clean() needed
  await db.users.create({
    data: { rut: result.data.rut }
  })
 
  return { success: true }
}

Yup Schema#

TypeScript
import * as yup from 'yup'
import { validate } from 'rut.ts'
 
const schema = yup.object({
  rut: yup
    .string()
    .required('RUT is required')
    .test('is-valid-rut', 'Invalid RUT', (value) => {
      return value ? validate(value) : false
    })
})
 
// Usage
try {
  await schema.validate({ rut: '12.345.678-5' })
  console.log('Valid!')
} catch (error) {
  console.error(error.message)
}

Custom Hook (React)#

TypeScript
import { useState, useCallback } from 'react'
import { format, validate } from 'rut.ts'
 
function useRutInput(initialValue = '') {
  const [value, setValue] = useState(initialValue)
  const [isValid, setIsValid] = useState(false)
  
  const handleChange = useCallback((input: string) => {
    const formatted = format(input, { incremental: true })
    setValue(formatted)
    setIsValid(validate(formatted))
  }, [])
  
  return {
    value,
    isValid,
    handleChange,
    props: {
      value,
      onChange: (e: React.ChangeEvent<HTMLInputElement>) => 
        handleChange(e.target.value)
    }
  }
}
 
// Usage
function MyComponent() {
  const rut = useRutInput()
  
  return (
    <div>
      <input {...rut.props} placeholder="12.345.678-5" />
      {!rut.isValid && rut.value && (
        <span className="error">Invalid RUT</span>
      )}
    </div>
  )
}