React / Next.js
React hooks and component integration
React Component (Functional)
Complete component with hooks:
import { useEffect, useCallback } from 'react';
// Declare global type
declare global {
interface Window {
TheWebRatings: any;
}
}
interface RatingButtonProps {
userEmail: string;
userName?: string;
onSuccess?: (data: any) => void;
onError?: (error: any) => void;
}
export function RatingButton({
userEmail,
userName,
onSuccess,
onError
}: RatingButtonProps) {
// Load widget script
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cdn.thewebratings.com/twr.min.js';
script.async = true;
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, []);
// Setup event listeners
useEffect(() => {
if (typeof window === 'undefined' || !window.TheWebRatings) return;
const handleSuccess = (data: { ratingId: string; rating: number }) => {
console.log('Rating submitted:', data.ratingId, data.rating);
onSuccess?.(data);
};
window.TheWebRatings.on('rating:submitted', handleSuccess);
return () => {
window.TheWebRatings.off('rating:submitted', handleSuccess);
};
}, [onSuccess]);
// Open widget
const handleClick = useCallback(() => {
if (typeof window === 'undefined' || !window.TheWebRatings) {
console.error('TheWebRatings not loaded');
return;
}
window.TheWebRatings.open(userEmail, userName);
}, [userEmail, userName]);
return (
<button
onClick={handleClick}
className="rating-button"
>
⭐ Rate This App
</button>
);
}Usage in Your App
import { RatingButton } from './components/RatingButton';
import { useAuth } from './hooks/useAuth'; // Your auth hook
export function MyApp() {
const { user } = useAuth();
const handleRatingSuccess = (data: any) => {
// Grant reward
if (data.rating >= 4) {
grantDiscount(user.id, '10OFF');
}
// Track analytics
analytics.track('rating_submitted', {
rating: data.rating
});
};
const handleRatingError = (error: any) => {
// Show error toast
toast.error(error.userMessage || 'Failed to submit rating');
};
return (
<div>
<h1>My App</h1>
{user && (
<RatingButton
userEmail={user.email}
userName={user.name}
onSuccess={handleRatingSuccess}
onError={handleRatingError}
/>
)}
</div>
);
}Next.js App Router
For Next.js 13+ with App Router:
'use client';
import { useEffect, useState } from 'react';
export function RatingWidget() {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
// Load script only on client
const script = document.createElement('script');
script.src = 'https://cdn.thewebratings.com/twr.min.js';
script.async = true;
script.onload = () => setLoaded(true);
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, []);
const openWidget = () => {
if (!loaded || !window.TheWebRatings) return;
// Get user from your auth (e.g., Clerk, Auth0, NextAuth)
const user = getCurrentUser(); // Your function
window.TheWebRatings.open(user.email, user.name);
};
if (!loaded) {
return <button disabled>Loading...</button>;
}
return (
<button onClick={openWidget} className="btn-primary">
Rate This App
</button>
);
}Custom Hook
Create a reusable hook:
import { useEffect, useCallback, useState } from 'react';
export function useRatingWidget(options?: {
onSuccess?: (data: any) => void;
onError?: (error: any) => void;
}) {
const [isLoaded, setIsLoaded] = useState(false);
const [isOpen, setIsOpen] = useState(false);
// Load script
useEffect(() => {
if (typeof window === 'undefined') return;
const script = document.createElement('script');
script.src = 'https://cdn.thewebratings.com/twr.min.js';
script.async = true;
script.onload = () => setIsLoaded(true);
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, []);
// Setup event listeners
useEffect(() => {
if (!window.TheWebRatings) return;
const handleSuccess = (data: { ratingId: string; rating: number }) => {
setIsOpen(false);
options?.onSuccess?.(data);
};
const handleOpened = () => setIsOpen(true);
const handleClosed = () => setIsOpen(false);
window.TheWebRatings.on('rating:submitted', handleSuccess);
window.TheWebRatings.on('modal:opened', handleOpened);
window.TheWebRatings.on('widget:closed', handleClosed);
return () => {
window.TheWebRatings.off('rating:submitted', handleSuccess);
window.TheWebRatings.off('modal:opened', handleOpened);
window.TheWebRatings.off('widget:closed', handleClosed);
};
}, [options]);
const openWidget = useCallback((userEmail: string, userName?: string) => {
if (!isLoaded || !window.TheWebRatings) {
console.error('TheWebRatings not loaded yet');
return;
}
window.TheWebRatings.open(userEmail, userName);
}, [isLoaded]);
return {
isLoaded,
isOpen,
openWidget
};
}Usage:
function MyComponent() {
const { user } = useAuth();
const { isLoaded, openWidget } = useRatingWidget({
onSuccess: (data) => {
toast.success(`Thank you for your ${data.rating}-star rating!`);
}
});
return (
<button
onClick={() => openWidget(user.email, user.name)}
disabled={!isLoaded}
>
Rate App
</button>
);
}TypeScript Types
// types/thewebratings.d.ts
// rating:submitted payload
interface TWRRatingSubmittedData {
ratingId: string;
rating: number;
timestamp?: number;
version?: string;
}
interface TheWebRatings {
open(userEmail: string, userName?: string): void;
on(event: 'rating:submitted', handler: (data: TWRRatingSubmittedData) => void): void;
on(event: 'modal:opened' | 'widget:closed', handler: (data: any) => void): void;
off(event: string, handler: Function): void;
}
declare global {
interface Window {
TheWebRatings: TheWebRatings;
}
}
export {};Smart Rating Prompt
Show rating prompt after user completes certain actions:
import { useEffect, useState } from 'react';
import { useRatingWidget } from './useRatingWidget';
export function useSmartRatingPrompt(options: {
triggerAfterActions: number;
cooldownDays?: number;
}) {
const { openWidget } = useRatingWidget();
const [actionsCount, setActionsCount] = useState(0);
const trackAction = useCallback(() => {
const newCount = actionsCount + 1;
setActionsCount(newCount);
localStorage.setItem('actions_count', newCount.toString());
// Check if we should prompt
if (newCount === options.triggerAfterActions) {
const lastPrompt = localStorage.getItem('last_rating_prompt');
const cooldown = options.cooldownDays || 30;
if (!lastPrompt || isDaysAgo(lastPrompt, cooldown)) {
setTimeout(() => {
promptForRating();
}, 2000);
}
}
}, [actionsCount, options]);
const promptForRating = () => {
const user = getCurrentUser();
if (!user) return;
if (confirm('Enjoying the app? Leave us a rating!')) {
openWidget(user.email, user.name);
localStorage.setItem('last_rating_prompt', new Date().toISOString());
}
};
return { trackAction };
}
function isDaysAgo(dateStr: string, days: number) {
const date = new Date(dateStr);
const now = new Date();
const diffDays = (now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24);
return diffDays >= days;
}Usage:
function MyApp() {
const { trackAction } = useSmartRatingPrompt({
triggerAfterActions: 3,
cooldownDays: 30
});
const handleTaskComplete = () => {
// Your task completion logic
completeTask();
// Track for rating prompt
trackAction();
};
return <TaskList onComplete={handleTaskComplete} />;
}Server Components (Next.js 13+)
For Next.js App Router with Server Components:
// app/rating-widget.tsx
'use client';
import { useEffect, useState } from 'react';
import Script from 'next/script';
export function RatingWidget({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Fetch user data client-side
fetchUser(userId).then(setUser);
}, [userId]);
if (!user) return null;
return (
<>
<Script
src="https://cdn.thewebratings.com/twr.min.js"
strategy="lazyOnload"
/>
<button onClick={() => {
if (window.TheWebRatings) {
window.TheWebRatings.open(user.email, user.name);
}
}}>
Rate App
</button>
</>
);
}Next Steps
- Event Reference - All available events
- Framework Guides - Vue, Angular, Svelte, Astro, and more