React Components

React Components

Pre-built React components that make it easy to add contextual feedback collection to your app.

Ready to Use

Drop-in feedback widget

Customizable

Theme and style options

TypeScript

Full type safety

Modern React

Hooks and latest patterns

Installation

Install Feedback Widgetbash
npm install @feedbackkit/widget

Feedback Widget

Basic Usage

Add a contextual feedback widget to your app with interactive overlay

import { FeedbackWidget } from '@feedbackkit/widget';

export default function MyApp() {
  return (
    <div>
      <h1>My Website</h1>
      <FeedbackWidget 
        apiKey="feedbackkit_sk_your_api_key_here"
        appName="My App"
        trigger="floating"
        position="bottom-right"
        onSuccess={() => {
          console.log('Feedback submitted!');
        }}
      />
    </div>
  );
}

Custom Feedback Form

Custom Implementation

Build your own feedback form with full control over the UI

import { useState } from 'react';

interface FeedbackFormProps {
  onSubmit: (feedback: FeedbackData) => Promise<void>;
  onCancel?: () => void;
}

export function CustomFeedbackForm({ onSubmit, onCancel }: FeedbackFormProps) {
  const [formData, setFormData] = useState({
    type: 'bug' as const,
    priority: 'medium' as const,
    title: '',
    description: '',
    email: ''
  });
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsSubmitting(true);
    
    try {
      await onSubmit({
        ...formData,
        metadata: {
          url: window.location.href,
          userAgent: navigator.userAgent,
          timestamp: new Date().toISOString()
        }
      });
      
      // Reset form
      setFormData({
        type: 'bug',
        priority: 'medium',
        title: '',
        description: '',
        email: ''
      });
    } catch (error) {
      console.error('Failed to submit feedback:', error);
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label className="block text-sm font-medium text-white mb-2">
          Feedback Type
        </label>
        <select
          value={formData.type}
          onChange={(e) => setFormData(prev => ({ ...prev, type: e.target.value as any }))}
          className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
        >
          <option value="bug">Bug Report</option>
          <option value="feature">Feature Request</option>
          <option value="error">Error Report</option>
          <option value="general">General Feedback</option>
        </select>
      </div>

      <div>
        <label className="block text-sm font-medium text-white mb-2">
          Priority
        </label>
        <select
          value={formData.priority}
          onChange={(e) => setFormData(prev => ({ ...prev, priority: e.target.value as any }))}
          className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
        >
          <option value="low">Low</option>
          <option value="medium">Medium</option>
          <option value="high">High</option>
          <option value="critical">Critical</option>
        </select>
      </div>

      <div>
        <label className="block text-sm font-medium text-white mb-2">
          Title
        </label>
        <input
          type="text"
          value={formData.title}
          onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
          placeholder="Brief description of the issue"
          className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
          required
        />
      </div>

      <div>
        <label className="block text-sm font-medium text-white mb-2">
          Description
        </label>
        <textarea
          value={formData.description}
          onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
          placeholder="Detailed description of the feedback"
          rows={4}
          className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
          required
        />
      </div>

      <div>
        <label className="block text-sm font-medium text-white mb-2">
          Email (optional)
        </label>
        <input
          type="email"
          value={formData.email}
          onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
          placeholder="your@email.com"
          className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
        />
      </div>

      <div className="flex space-x-3">
        <button
          type="submit"
          disabled={isSubmitting}
          className="flex-1 bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700 disabled:opacity-50"
        >
          {isSubmitting ? 'Submitting...' : 'Submit Feedback'}
        </button>
        
        {onCancel && (
          <button
            type="button"
            onClick={onCancel}
            className="px-4 py-2 border border-slate-600 text-slate-300 rounded hover:bg-slate-700"
          >
            Cancel
          </button>
        )}
      </div>
    </form>
  );
}

Feedback Hooks

useFeedbackSubmission

import { useState, useCallback } from 'react';

export function useFeedbackSubmission() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const submitFeedback = useCallback(async (feedbackData: {
    type: 'bug' | 'feature' | 'error' | 'general';
    priority: 'low' | 'medium' | 'high' | 'critical';
    title: string;
    description: string;
    email?: string;
    screenshot?: string;
  }) => {
    setIsSubmitting(true);
    setError(null);

    try {
      const response = await fetch('https://api.feedbackkit.io/feedback', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': process.env.REACT_APP_FEEDBACKKIT_API_KEY,
        },
        body: JSON.stringify({
          ...feedbackData,
          metadata: {
            url: window.location.href,
            userAgent: navigator.userAgent,
            appName: 'My App',
            timestamp: new Date().toISOString(),
          },
        }),
      });

      if (!response.ok) {
        throw new Error('Failed to submit feedback');
      }

      const result = await response.json();
      return result;
    } catch (err: any) {
      setError(err.message);
      throw err;
    } finally {
      setIsSubmitting(false);
    }
  }, []);

  return {
    submitFeedback,
    isSubmitting,
    error,
  };
}

useScreenshotCapture

import { useState, useCallback } from 'react';

export function useScreenshotCapture() {
  const [isCapturing, setIsCapturing] = useState(false);
  const [screenshot, setScreenshot] = useState<string | null>(null);

  const captureScreenshot = useCallback(async () => {
    setIsCapturing(true);
    
    try {
      // Use html2canvas or similar library
      const canvas = await import('html2canvas').then(mod => 
        mod.default(document.body)
      );
      
      const dataUrl = canvas.toDataURL('image/png');
      setScreenshot(dataUrl);
      
      return dataUrl;
    } catch (error) {
      console.error('Failed to capture screenshot:', error);
      throw error;
    } finally {
      setIsCapturing(false);
    }
  }, []);

  const clearScreenshot = useCallback(() => {
    setScreenshot(null);
  }, []);

  return {
    captureScreenshot,
    clearScreenshot,
    screenshot,
    isCapturing,
  };
}

Performance Optimization

Lazy Loading

import { lazy, Suspense } from 'react';

// Lazy load the feedback widget
const FeedbackWidget = lazy(() => 
  import('@feedbackkit/widget').then(mod => ({ 
    default: mod.FeedbackWidget 
  }))
);

export default function MyApp() {
  return (
    <div>
      <h1>My Website</h1>
      <Suspense fallback={<div>Loading feedback widget...</div>}>
        <FeedbackWidget 
          apiKey={process.env.REACT_APP_FEEDBACKKIT_API_KEY}
          appName="My App"
        />
      </Suspense>
    </div>
  );
}

Memoization

import { useMemo, useCallback } from 'react';

export default function MyApp() {
  // Memoize theme configuration
  const theme = useMemo(() => ({
    primaryColor: "#ec4899",
    borderRadius: "8px"
  }), []);

  // Memoize success callback
  const handleSuccess = useCallback((feedback) => {
    console.log('Feedback submitted:', feedback);
    // Track analytics
    analytics.track('feedback_submitted', {
      type: feedback.type,
      priority: feedback.priority
    });
  }, []);

  return (
    <div>
      <h1>My Website</h1>
      <FeedbackWidget 
        apiKey={process.env.REACT_APP_FEEDBACKKIT_API_KEY}
        appName="My App"
        theme={theme}
        onSuccess={handleSuccess}
      />
    </div>
  );
}

Best Practices

Error Handling

Always handle errors

Provide fallback UI and user-friendly error messages

Loading states

Show loading indicators during feedback submission

Validation

Validate feedback data before submission

Accessibility

Keyboard navigation

Ensure all feedback forms are keyboard accessible

Screen readers

Add proper ARIA labels and descriptions

Focus management

Manage focus when opening/closing feedback forms