ComponentsDisplay Ads

Display Ads Component

Create visual display ad components (banners, popups, videos) for your chat interface.

Basic Usage

import { useDisplayAd } from '@earnlayer/sdk/react';
import { useEffect, useRef } from 'react';
 
function DisplayAdComponent({ shouldRefetch }: { shouldRefetch: boolean }) {
  const { ad, isLoading, refetch, refetchWithRefresh } = useDisplayAd({
    adType: 'banner',
    autoFetch: false
  });
 
  const prevShouldRefetch = useRef(false);
 
  useEffect(() => {
    if (shouldRefetch && !prevShouldRefetch.current) {
      prevShouldRefetch.current = true;
      refetchWithRefresh(); // Use refresh to exclude last ad
    } else if (!shouldRefetch) {
      prevShouldRefetch.current = false;
    }
  }, [shouldRefetch, refetchWithRefresh]);
 
  if (isLoading) return <div>Loading ad...</div>;
  if (!ad) return null;
 
  return (
    <a 
      href={ad.url}
      target="_blank" 
      rel="noopener noreferrer"
      className="block p-4 border rounded-lg hover:shadow-lg transition"
    >
      {ad.imageUrl && (
        <img 
          src={ad.imageUrl} 
          alt={ad.title}
          className="w-full h-auto rounded mb-2"
        />
      )}
      <h3 className="font-bold text-lg">{ad.title}</h3>
      {ad.description && (
        <p className="text-sm text-gray-600 mt-1">{ad.description}</p>
      )}
      <span className="text-xs text-blue-600 mt-2 inline-block">AD</span>
    </a>
  );
}

Ad Types

Wide horizontal ads, typically in sidebars:

const { ad } = useDisplayAd({
  adType: 'banner'
});

Thinking Ads

Ads shown during AI loading states. These are contextual ads that update based on the conversation topic.

const { ad } = useDisplayAd({
  adType: 'thinking',
  autoFetch: true,
  thinkingAdTimeout: 4  // Wait up to 4 seconds for contextual ads
});

Important: The thinkingAdTimeout parameter (in seconds) tells the backend how long to wait for the MCP server to populate contextual ads. Recommended value is 4 seconds to account for:

  • Time for your AI to call the MCP server (typically 0.5-2 seconds)
  • MCP processing time
  • Network latency

If the timeout is reached, a fallback ad is shown instead. See Phase 2: Thinking Ads for detailed implementation guide.

Text-based sponsored links (typically handled via MCP):

const { ad } = useDisplayAd({
  adType: 'hyperlink'
});

Inline Banner Ad Placement

To show banner ads inline with chat messages (after assistant responses), use the assistantMessageCount pattern:

// In your messages component
{messages.map((message, index) => {
  const isLastAssistantMessage = 
    index === messages.length - 1 && 
    message.role === 'assistant';
  
  return (
    <div key={message.id}>
      <MessageComponent message={message} />
      {/* Show banner ad after last assistant message */}
      {isLastAssistantMessage && 
       status !== "submitted" && 
       status !== "streaming" && (
        <DisplayAdComponent
          assistantMessageCount={
            messages.filter(m => m.role === 'assistant').length
          }
        />
      )}
    </div>
  );
})}
 
// In DisplayAdComponent - refetch when message count increases
function DisplayAdComponent({ assistantMessageCount }: { assistantMessageCount: number }) {
  const { ad, isLoading, refetch } = useDisplayAd({
    adType: 'banner',
    autoFetch: false // Manual control
  });
  
  const prevMessageCountRef = useRef(assistantMessageCount);
  
  // Initial fetch on mount
  useEffect(() => {
    refetch();
  }, [refetch]);
  
  // Refetch when assistant message count increases
  useEffect(() => {
    if (assistantMessageCount > prevMessageCountRef.current) {
      prevMessageCountRef.current = assistantMessageCount;
      refetch();
    }
  }, [assistantMessageCount, refetch]);
  
  // ... render ad
}

For a complete production-ready implementation with error handling and styling, see the Advanced Display Ad Implementation guide.

Auto-Refresh Banner Ads

Banner ads can automatically refresh to show different ads from the queue:

function AutoRefreshBannerAd() {
  const { ad, isLoading, refetchWithRefresh } = useDisplayAd({
    adType: 'banner',
    autoFetch: false
  });
 
  const [refreshCount, setRefreshCount] = useState(0);
  const MAX_REFRESHES = 3; // Show 4 total ads: initial + 3 refreshes
 
  // Auto-refresh every 5 seconds
  useEffect(() => {
    if (!ad || isLoading || refreshCount >= MAX_REFRESHES) return;
 
    const timer = setTimeout(() => {
      refetchWithRefresh();
      setRefreshCount(prev => prev + 1);
    }, 5000); // 5 seconds
 
    return () => clearTimeout(timer);
  }, [ad?.id, isLoading, refreshCount, refetchWithRefresh]);
 
  // Reset count when manually fetching new ad
  const handleManualRefresh = () => {
    refetch();
    setRefreshCount(0);
  };
 
  if (isLoading || !ad) return null;
 
  return (
    <a 
      href={ad.url}
      target="_blank" 
      rel="noopener noreferrer"
      className="block p-4 border rounded-lg hover:shadow-lg transition"
    >
      {ad.imageUrl && (
        <img 
          src={ad.imageUrl} 
          alt={ad.title}
          className="w-full h-auto rounded mb-2"
        />
      )}
      <h3 className="font-bold text-lg">{ad.title}</h3>
      {ad.description && (
        <p className="text-sm text-gray-600 mt-1">{ad.description}</p>
      )}
      <span className="text-xs text-blue-600 mt-2 inline-block">AD</span>
    </a>
  );
}

Best Practices:

  • Limit refreshes to avoid overwhelming users (recommended: 3-5 refreshes)
  • Use refetchWithRefresh() instead of refetch() to avoid showing the same ad twice
  • Reset refresh count when manually fetching new ads
  • Timer automatically resets when ad changes (new ad loaded)

Fade Animations

Display ads can use fade in/out animations for smooth transitions during refreshes:

CSS Animations

Add to your global CSS:

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
 
.animate-fade-in {
  animation: fadeIn 0.3s ease-in-out;
}

Component Implementation

function AnimatedBannerAd() {
  const { ad, isLoading, refetchWithRefresh } = useDisplayAd({
    adType: 'banner',
    autoFetch: false
  });
 
  const [isVisible, setIsVisible] = useState(false);
  const [displayAd, setDisplayAd] = useState(ad);
 
  // Handle ad changes with fade animation
  useEffect(() => {
    if (!ad) {
      setIsVisible(false);
      return;
    }
 
    if (displayAd?.id !== ad.id && displayAd) {
      // Fade out current ad
      setIsVisible(false);
      
      // After fade out, update and fade in
      setTimeout(() => {
        setDisplayAd(ad);
        setIsVisible(true);
      }, 300);
    } else if (!displayAd || displayAd.id === ad.id) {
      setDisplayAd(ad);
      setTimeout(() => setIsVisible(true), 10);
    }
  }, [ad, displayAd?.id]);
 
  if (!displayAd || isLoading) return null;
 
  return (
    <div 
      className={`transition-opacity duration-300 ${
        isVisible ? 'opacity-100' : 'opacity-0'
      }`}
    >
      <a 
        href={displayAd.url}
        target="_blank" 
        rel="noopener noreferrer"
        className="block p-4 border rounded-lg hover:shadow-lg transition"
      >
        {displayAd.imageUrl && (
          <img 
            src={displayAd.imageUrl} 
            alt={displayAd.title}
            className="w-full h-auto rounded mb-2"
          />
        )}
        <h3 className="font-bold text-lg">{displayAd.title}</h3>
        {displayAd.description && (
          <p className="text-sm text-gray-600 mt-1">{displayAd.description}</p>
        )}
      </a>
    </div>
  );
}

Key Points:

  • Use displayAd state to hold currently displayed ad during transitions
  • Fade out (300ms) → Update ad → Fade in (300ms)
  • Smooth user experience during ad refreshes
  • Works great with auto-refresh functionality

Key Points

Automatic Tracking ad.url includes backend redirect for automatic click tracking.

Contextual Updates Use refetch() or refetchWithRefresh() after each AI response for contextually relevant ads. Use refetchWithRefresh() to avoid showing the same ad twice.

Customizable Styling Style ads however you want - the component is fully customizable.

Example: Sidebar Ad

return (
  <div className="flex gap-4">
    <div className="flex-1">
      {/* Your chat messages */}
    </div>
    
    <aside className="w-80">
      <h2 className="text-xl font-bold mb-2">Sponsored</h2>
      {conversationId && <DisplayAdComponent shouldRefetch={shouldRefetchAd} />}
    </aside>
  </div>
);

DisplayAd Object

interface DisplayAd {
  id: string;
  impressionId: string;
  title: string;
  description?: string;
  url: string;              // Use in <a href={ad.url}>
  imageUrl?: string;
  adType: 'hyperlink' | 'thinking' | 'banner';
  source: 'queue' | 'fallback';
}

Next Steps