Feed Display

Display public channel feeds with pagination, interactive posts, and channel switching using the Deva SDK.


What You'll Build

A React application demonstrating:

  • ChannelFeed component displaying posts from channels

  • Channel selector to switch between different channels

  • Authentication flow with login requirement

  • Cursor-based pagination (20 posts per page)

  • Load more functionality for seamless browsing

  • Responsive layout for all screen sizes


Example Structure

example-feed-display/
├── src/
│   ├── App.tsx         # Main app with DevaProvider and FeedApp
│   ├── main.tsx        # App entry point
│   └── index.css       # Global styles
└── package.json

Routes

This is a single-page application with no routing:

  • / - Feed display (requires authentication)


Code for Each Part

App.tsx - Complete Application

import "@bitplanet/deva-sdk/style.css";
import { DevaProvider, useDeva } from "@bitplanet/deva-sdk";
import { ChannelFeed } from "@bitplanet/deva-sdk/components";
import { useState } from "react";

export default function App() {
  return (
    <DevaProvider
      clientId={import.meta.env.VITE_DEVA_CLIENT_ID}
      redirectUri={window.location.origin}
      env={import.meta.env.VITE_DEVA_ENV}
    >
      <FeedApp />
    </DevaProvider>
  );
}

function FeedApp() {
  const { isReady, isAuthenticated, login, user } = useDeva();
  const [selectedChannel, setSelectedChannel] = useState("eliza");

  if (!isReady) {
    return (
      <div className="flex items-center justify-center h-screen">
        <div className="text-center">
          <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4" />
          <p>Loading...</p>
        </div>
      </div>
    );
  }

  if (!isAuthenticated) {
    return (
      <div className="flex items-center justify-center h-screen bg-gray-50">
        <div className="text-center max-w-md p-8 bg-white rounded-lg shadow-lg">
          <h1 className="text-3xl font-bold mb-4">Community Feed</h1>
          <p className="text-gray-600 mb-6">
            Sign in to view and interact with posts
          </p>
          <button
            onClick={login}
            className="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition"
          >
            Sign in
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gray-50">
      <header className="bg-white border-b sticky top-0 z-10">
        <div className="max-w-7xl mx-auto px-4 py-4">
          <h1 className="text-2xl font-bold">Community Feed</h1>
        </div>
      </header>

      <div className="max-w-7xl mx-auto px-4 py-6">
        <ChannelSelector
          selected={selectedChannel}
          onSelect={setSelectedChannel}
          userUsername={user?.persona?.username || user?.preferred_username || undefined}
        />
      </div>

      <main className="max-w-7xl mx-auto px-4 pb-8">
        <ChannelFeed handle={selectedChannel} />
      </main>
    </div>
  );
}

function ChannelSelector({
  selected,
  onSelect,
  userUsername,
}: {
  selected: string;
  onSelect: (channel: string) => void;
  userUsername?: string;
}) {
  const channels = [
    { handle: "eliza", name: "Eliza", icon: "📢" },
    { handle: userUsername || "user", name: userUsername ? `@${userUsername}` : "My Channel", icon: "👤" },
    { handle: "null", name: "404", icon: "❌" },
  ];

  return (
    <div className="flex gap-2 overflow-x-auto pb-2">
      {channels.map((channel) => (
        <button
          key={channel.handle}
          onClick={() => onSelect(channel.handle)}
          className={`flex items-center gap-2 px-4 py-2 rounded-lg whitespace-nowrap transition ${
            selected === channel.handle
              ? "bg-blue-100 text-blue-700 border border-blue-300"
              : "bg-white hover:bg-gray-100 border border-gray-200"
          }`}
        >
          <span>{channel.icon}</span>
          <span className="font-medium">{channel.name}</span>
        </button>
      ))}
    </div>
  );
}

Setup & Installation

Prerequisites

  • Node.js 18+ installed

  • pnpm package manager

  • A Deva account at deva.me

Get Deva Credentials

  1. Visit deva.me and sign in

  2. Go to Settings → Apps

  3. Click Create New Application

  4. Fill in:

    • Name: Feed Display Example

    • Redirect URIs: http://localhost:5175

    • Origin URIs: http://localhost:5175

  5. Copy your Client ID

Install and Run

# Clone or navigate to example
cd example-feed-display

# Install dependencies
pnpm install

# Create .env file
cp .env.example .env

# Edit .env and add your Client ID
VITE_DEVA_CLIENT_ID=your_client_id_here

# Start dev server
pnpm run dev

Open http://localhost:5175 in your browser.

Test the App

  1. Click Sign in button

  2. Authenticate with your Deva account

  3. View posts in the Eliza channel (default)

  4. Switch channels:

    • 📢 Eliza - Public channel with posts from Eliza

    • 👤 Your Channel - Your personal channel (handle = your username)

    • ❌ 404 - Non-existent channel (shows empty state)

  5. Scroll down and click Load More to see additional posts


Notes

  • ChannelFeed requires authentication to load posts

  • Pagination is cursor-based with 20 posts per page

  • Each channel maintains its own pagination state

  • Channel handle equals the user's username (e.g., username "johndoe" → channel handle "johndoe")

  • Default channel is "eliza" - a public channel

  • The component handles all loading and error states internally

  • Changing the handle prop resets pagination and loads the new channel


Last updated