Skip to content

React Integration

Add moderation-aware components to your React app.

Installation

bash
npm install @nextauralabs/vettly-react @nextauralabs/vettly-sdk

Quick Start

ModeratedTextarea

A textarea that checks content in real-time as users type.

tsx
import { ModeratedTextarea } from '@nextauralabs/vettly-react'
import '@nextauralabs/vettly-react/styles.css'

function CommentForm() {
  const [value, setValue] = useState('')
  const [isBlocked, setIsBlocked] = useState(false)

  return (
    <form>
      <ModeratedTextarea
        apiKey={process.env.NEXT_PUBLIC_VETTLY_API_KEY}
        value={value}
        onChange={setValue}
        onModerationResult={(result) => {
          setIsBlocked(result.action === 'block')
        }}
        placeholder="Write your comment..."
        debounceMs={500}
      />
      <button type="submit" disabled={isBlocked}>
        Submit
      </button>
    </form>
  )
}

ModeratedImageUpload

Moderate images before upload.

tsx
import { ModeratedImageUpload } from '@nextauralabs/vettly-react'
import '@nextauralabs/vettly-react/styles.css'

function AvatarUpload() {
  return (
    <ModeratedImageUpload
      apiKey={process.env.NEXT_PUBLIC_VETTLY_API_KEY}
      onUpload={(file, result) => {
        if (result.action === 'allow') {
          // Upload to your storage
          uploadToS3(file)
        }
      }}
      onBlock={(result) => {
        alert('Image contains inappropriate content')
      }}
      maxSizeMB={5}
      accept="image/*"
    />
  )
}

ModeratedVideoUpload

Moderate video content.

tsx
import { ModeratedVideoUpload } from '@nextauralabs/vettly-react'

function VideoUpload() {
  return (
    <ModeratedVideoUpload
      apiKey={process.env.NEXT_PUBLIC_VETTLY_API_KEY}
      onUpload={(file, result) => {
        console.log('Video moderation:', result)
      }}
      onBlock={(result) => {
        console.log('Video blocked:', result.categories)
      }}
      maxSizeMB={100}
    />
  )
}

useModeration Hook

For custom implementations:

tsx
import { useModeration } from '@nextauralabs/vettly-react'

function CustomInput() {
  const [text, setText] = useState('')

  const { check, result, isLoading, error } = useModeration({
    apiKey: process.env.NEXT_PUBLIC_VETTLY_API_KEY,
    debounceMs: 300
  })

  const handleChange = (e) => {
    const value = e.target.value
    setText(value)
    check(value, 'text')
  }

  return (
    <div>
      <input
        value={text}
        onChange={handleChange}
        className={result?.action === 'block' ? 'border-red-500' : ''}
      />
      {isLoading && <span>Checking...</span>}
      {result?.action === 'block' && (
        <span className="text-red-500">
          Content not allowed
        </span>
      )}
    </div>
  )
}

ModerationProvider

Share a client across components:

tsx
import { ModerationProvider } from '@nextauralabs/vettly-react'

function App() {
  return (
    <ModerationProvider apiKey={process.env.NEXT_PUBLIC_VETTLY_API_KEY}>
      <CommentForm />
      <ProfileEditor />
    </ModerationProvider>
  )
}

// Child components use the shared client
function CommentForm() {
  const { check } = useModeration() // Uses provider's client
  // ...
}

Custom Styling

Override default styles:

css
/* Your CSS */
.moderated-textarea {
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  padding: 12px;
}

.moderated-textarea.blocked {
  border-color: #ef4444;
  background-color: #fef2f2;
}

.moderation-feedback {
  font-size: 14px;
  margin-top: 4px;
}

.feedback-block {
  color: #dc2626;
}

.feedback-safe {
  color: #16a34a;
}

Or use Tailwind:

tsx
<ModeratedTextarea
  className="w-full p-3 border-2 rounded-lg focus:ring-2"
  feedbackClassName="text-sm mt-1"
  // ...
/>

Server-Side Validation

Always validate on the server too:

tsx
// Client component
function CommentForm() {
  const handleSubmit = async (e) => {
    e.preventDefault()

    // Client already checked, but server validates again
    const response = await fetch('/api/comments', {
      method: 'POST',
      body: JSON.stringify({ content: value })
    })

    if (response.status === 403) {
      // Server blocked it
      setError('Content not allowed')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <ModeratedTextarea
        apiKey={process.env.NEXT_PUBLIC_VETTLY_API_KEY}
        value={value}
        onChange={setValue}
      />
      <button type="submit">Submit</button>
    </form>
  )
}
ts
// API route (server)
export async function POST(request: Request) {
  const { content } = await request.json()

  const result = await vettly.check({ content, contentType: 'text' })

  if (result.action === 'block') {
    return Response.json({ error: 'Blocked' }, { status: 403 })
  }

  // Save to database
}

Props Reference

ModeratedTextarea

PropTypeDescription
apiKeystringYour Vettly API key
valuestringControlled value
onChange(value: string) => voidChange handler
onModerationResult(result: ModerationResult) => voidCallback with result
debounceMsnumberDebounce delay (default: 500)
policyIdstringCustom policy to use
showFeedbackbooleanShow status indicator
placeholderstringPlaceholder text
disabledbooleanDisable input

ModeratedImageUpload

PropTypeDescription
apiKeystringYour Vettly API key
onUpload(file: File, result: ModerationResult) => voidSuccess callback
onBlock(result: ModerationResult) => voidBlock callback
onError(error: Error) => voidError callback
maxSizeMBnumberMax file size
acceptstringAccepted file types
policyIdstringCustom policy to use

Next Steps