Skip to content

React Integration

Ctrovalidate integrates seamlessly with React by working directly with the DOM, keeping validation logic outside the React render cycle.


Installation

bash
npm install ctrovalidate

Basic Pattern

Use useRef to maintain a stable validator instance across re-renders.

Complete Example

tsx
import { useEffect, useRef } from 'react';
import { Ctrovalidate, LogLevel } from 'ctrovalidate';

export function SignupForm() {
  const formRef = useRef<HTMLFormElement>(null);
  const validatorRef = useRef<Ctrovalidate | null>(null);

  useEffect(() => {
    if (formRef.current && !validatorRef.current) {
      validatorRef.current = new Ctrovalidate(formRef.current, {
        realTime: true,
        logLevel: LogLevel.WARN
      });
    }

    return () => {
      validatorRef.current?.destroy();
      validatorRef.current = null;
    };
  }, []);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    if (await validatorRef.current?.validate()) {
      const formData = new FormData(formRef.current!);
      console.log('Form valid:', Object.fromEntries(formData));
      // Submit to API
    }
  };

  return (
    <form ref={formRef} noValidate onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          name="email"
          type="email"
          data-ctrovalidate-rules="required|email"
        />
        <div className="error-message" />
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          name="password"
          type="password"
          data-ctrovalidate-rules="required|minLength:8"
        />
        <div className="error-message" />
      </div>

      <button type="submit">Sign Up</button>
    </form>
  );
}

Dynamic Fields

When adding or removing fields based on React state, call refresh() to update validation tracking.

Example: Conditional Fields

tsx
import { useEffect, useRef, useState } from 'react';
import { Ctrovalidate } from 'ctrovalidate';

export function DynamicForm() {
  const formRef = useRef<HTMLFormElement>(null);
  const validatorRef = useRef<Ctrovalidate | null>(null);
  const [showPhone, setShowPhone] = useState(false);

  useEffect(() => {
    if (formRef.current) {
      validatorRef.current = new Ctrovalidate(formRef.current, {
        realTime: true
      });
    }
    return () => validatorRef.current?.destroy();
  }, []);

  // Refresh validator when fields are added/removed
  useEffect(() => {
    validatorRef.current?.refresh();
  }, [showPhone]);

  return (
    <form ref={formRef} noValidate>
      <div>
        <label>
          <input
            type="checkbox"
            checked={showPhone}
            onChange={(e) => setShowPhone(e.target.checked)}
          />
          Add phone number
        </label>
      </div>

      {showPhone && (
        <div>
          <label htmlFor="phone">Phone</label>
          <input
            id="phone"
            name="phone"
            data-ctrovalidate-rules="required|phone"
          />
          <div className="error-message" />
        </div>
      )}

      <button type="submit">Submit</button>
    </form>
  );
}

TypeScript Support

Import types for full type safety:

tsx
import { Ctrovalidate, LogLevel, type CtrovalidateOptions } from 'ctrovalidate';

const options: CtrovalidateOptions = {
  realTime: true,
  logLevel: LogLevel.DEBUG,
  errorClass: 'border-red-500',
  errorMessageClass: 'text-red-600 text-sm mt-1'
};

const validator = new Ctrovalidate(formElement, options);

Custom Rules

Register custom rules before initializing the validator:

tsx
import { Ctrovalidate } from 'ctrovalidate';

// Register custom rule globally
Ctrovalidate.addRule(
  'strongPassword',
  (value) => {
    return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(value);
  },
  'Password must contain uppercase, lowercase, number, and special character.'
);

export function PasswordForm() {
  // ... component code
  return (
    <input
      name="password"
      data-ctrovalidate-rules="required|strongPassword"
    />
  );
}

Best Practices

1. Initialize Once

Always check if the validator already exists before creating a new instance:

tsx
useEffect(() => {
  if (formRef.current && !validatorRef.current) {
    validatorRef.current = new Ctrovalidate(formRef.current);
  }
}, []);

2. Clean Up on Unmount

Always call destroy() to prevent memory leaks:

tsx
useEffect(() => {
  // ... initialization
  return () => {
    validatorRef.current?.destroy();
  };
}, []);

3. Use noValidate

Disable browser validation to let Ctrovalidate handle it:

tsx
<form ref={formRef} noValidate onSubmit={handleSubmit}>

4. Call refresh() for Dynamic Fields

When fields are added/removed, update the validator:

tsx
useEffect(() => {
  validatorRef.current?.refresh();
}, [fieldCount]);

Benefits

  • No Re-renders: Validation happens in the DOM, not React state
  • TypeScript Support: Full type definitions included
  • Accessibility: Automatic ARIA attribute management
  • Performance: Validation runs outside React's render cycle

Next Steps

Released under the MIT License.