useDropper Hook

The useDropper hook provides access to the Dropper client, configuration, and theme from any component within a DropperProvider.

Basic Usage

import { useDropper } from '@dropper/react'

function MyComponent() {
  const { client, config, theme } = useDropper()
  
  // Use client to interact with API
  const uploadFile = async (file: File) => {
    const result = await client.uploadFile(file)
    console.log('Uploaded:', result.url)
  }
  
  return <div>My Component</div>
}

Return Value

interface DropperContextValue {
  client: DropperClient      // Core API client
  config: DropperConfig      // Provider configuration
  theme: ResolvedTheme       // Resolved theme values
}

client

The DropperClient instance for API interactions:

const { client } = useDropper()

// Upload file
await client.uploadFile(file)

// List files
const files = await client.listFiles()

// Create folder
const folder = await client.createFolder({ name: 'My Folder' })

See Client Setup for full API reference.

config

The provider configuration:

const { config } = useDropper()

console.log(config.publishableKey)  // "pk_dropper_test_xxx"
console.log(config.appId)           // "app_123" (if provided)
console.log(config.baseUrl)         // "https://api.dropper.com" (if provided)

theme

The resolved theme configuration:

const { theme } = useDropper()

console.log(theme.primary)      // "#3b82f6"
console.log(theme.primary500)   // Computed color shade
console.log(theme.radius)       // "md"

useTheme Hook

For theme-only access, use the useTheme hook:

import { useTheme } from '@dropper/react'

function ThemedComponent() {
  const theme = useTheme()
  
  return (
    <div style={{ color: theme.primary }}>
      Themed content
    </div>
  )
}

Complete Examples

Custom File Upload

import { useDropper } from '@dropper/react'
import { useState } from 'react'

function CustomFileUpload() {
  const { client } = useDropper()
  const [uploading, setUploading] = useState(false)
  const [progress, setProgress] = useState(0)
  
  const handleUpload = async (file: File) => {
    setUploading(true)
    
    try {
      const result = await client.uploadFile(file, {
        onProgress: (p) => setProgress(p),
      })
      
      console.log('Upload successful:', result.url)
    } catch (error) {
      console.error('Upload failed:', error)
    } finally {
      setUploading(false)
      setProgress(0)
    }
  }
  
  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0]
          if (file) handleUpload(file)
        }}
        disabled={uploading}
      />
      {uploading && <progress value={progress} max={100} />}
    </div>
  )
}

File List with Delete

import { useDropper } from '@dropper/react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

function FileList() {
  const { client } = useDropper()
  const queryClient = useQueryClient()
  
  // Fetch files
  const { data: files = [] } = useQuery({
    queryKey: ['files'],
    queryFn: () => client.listFiles().then(res => res.data),
  })
  
  // Delete mutation
  const deleteMutation = useMutation({
    mutationFn: (fileId: string) => client.deleteFile(fileId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['files'] })
    },
  })
  
  return (
    <div>
      {files.map((file) => (
        <div key={file.id}>
          <span>{file.originalFilename}</span>
          <button onClick={() => deleteMutation.mutate(file.id)}>
            Delete
          </button>
        </div>
      ))}
    </div>
  )
}

Folder Management

import { useDropper } from '@dropper/react'
import { useState } from 'react'

function FolderManager() {
  const { client } = useDropper()
  const [folders, setFolders] = useState([])
  const [loading, setLoading] = useState(false)
  
  const loadFolders = async () => {
    setLoading(true)
    try {
      const response = await client.listFolders()
      setFolders(response.folders)
    } catch (error) {
      console.error('Failed to load folders:', error)
    } finally {
      setLoading(false)
    }
  }
  
  const createFolder = async (name: string) => {
    try {
      await client.createFolder({ name })
      await loadFolders() // Refresh list
    } catch (error) {
      console.error('Failed to create folder:', error)
    }
  }
  
  return (
    <div>
      <button onClick={loadFolders} disabled={loading}>
        Load Folders
      </button>
      
      <button onClick={() => createFolder('New Folder')}>
        Create Folder
      </button>
      
      <ul>
        {folders.map((folder) => (
          <li key={folder.id}>{folder.name}</li>
        ))}
      </ul>
    </div>
  )
}

Themed Button

import { useTheme } from '@dropper/react'

function ThemedButton({ children, onClick }) {
  const theme = useTheme()
  
  return (
    <button
      onClick={onClick}
      style={{
        backgroundColor: theme.primary,
        color: 'white',
        borderRadius: theme.radius === 'sm' ? '4px' : 
                      theme.radius === 'md' ? '8px' : '12px',
        padding: '8px 16px',
        border: 'none',
        cursor: 'pointer',
      }}
    >
      {children}
    </button>
  )
}

Access Configuration

import { useDropper } from '@dropper/react'

function ConfigDisplay() {
  const { config } = useDropper()
  
  return (
    <div>
      <h3>Dropper Configuration</h3>
      <dl>
        <dt>API Key:</dt>
        <dd>{config.publishableKey.substring(0, 20)}...</dd>
        
        {config.appId && (
          <>
            <dt>App ID:</dt>
            <dd>{config.appId}</dd>
          </>
        )}
        
        {config.baseUrl && (
          <>
            <dt>Base URL:</dt>
            <dd>{config.baseUrl}</dd>
          </>
        )}
      </dl>
    </div>
  )
}

Error Handling

The hook will throw an error if used outside DropperProvider:

import { useDropper } from '@dropper/react'

function MyComponent() {
  try {
    const { client } = useDropper()
    // Use client
  } catch (error) {
    // Error: useDropper must be used within a DropperProvider
    return <div>Please wrap this component with DropperProvider</div>
  }
}

TypeScript Support

The hook is fully typed:

import { useDropper, useTheme } from '@dropper/react'
import type { DropperClient } from '@dropper/core'
import type { ResolvedTheme } from '@dropper/react'

function TypedComponent() {
  const { client, config, theme } = useDropper()
  
  // client is typed as DropperClient
  const uploadFile = async (file: File) => {
    const result = await client.uploadFile(file)
    // result is typed as FileResponseDto
  }
  
  // theme is typed as ResolvedTheme
  const primaryColor: string = theme.primary
  
  return <div />
}

Best Practices

1. Use at Component Level

Call the hook in components, not at module level:

// Good
function MyComponent() {
  const { client } = useDropper()
  return <div />
}

// Bad
const { client } = useDropper() // Error: Outside component
function MyComponent() {
  return <div />
}

2. Destructure What You Need

Only destructure the values you need:

// Good: Only need client
const { client } = useDropper()

// Avoid: Destructuring unused values
const { client, config, theme } = useDropper()

3. Combine with React Query

Use with React Query hooks for data fetching:

import { useDropper } from '@dropper/react'
import { useQuery } from '@tanstack/react-query'

function FileList() {
  const { client } = useDropper()
  
  const { data: files } = useQuery({
    queryKey: ['files'],
    queryFn: () => client.listFiles().then(res => res.data),
  })
  
  return <div>{/* Render files */}</div>
}

4. Use useTheme for Styling

Use useTheme when you only need theme values:

import { useTheme } from '@dropper/react'

function StyledComponent() {
  const theme = useTheme()
  
  return (
    <div style={{ color: theme.primary }}>
      Themed content
    </div>
  )
}

Common Patterns

Conditional Rendering

function ConditionalComponent() {
  const { config } = useDropper()
  
  if (config.appId === 'demo') {
    return <DemoMode />
  }
  
  return <ProductionMode />
}

Custom Hooks

Build custom hooks on top of useDropper:

import { useDropper } from '@dropper/react'
import { useQuery } from '@tanstack/react-query'

function useFiles(folderId?: string) {
  const { client } = useDropper()
  
  return useQuery({
    queryKey: ['files', folderId],
    queryFn: () => client.listFiles({ folderId }).then(res => res.data),
  })
}

// Usage
function MyComponent() {
  const { data: files } = useFiles('folder_123')
  return <div>{/* Render files */}</div>
}

Memoization

Memoize expensive operations:

import { useDropper } from '@dropper/react'
import { useMemo } from 'react'

function ExpensiveComponent() {
  const { theme } = useDropper()
  
  const computedStyles = useMemo(() => ({
    primary: theme.primary,
    secondary: theme.secondary,
    // Expensive computation
  }), [theme])
  
  return <div style={computedStyles}>Content</div>
}

Next Steps