React & React NativeEntity ListsOverview

Entity Lists

Entity lists in Replyke provide powerful feed and list management functionality using Redux state management. The useEntityList hook allows you to create, manage, and filter lists of entities with built-in pagination, caching, and real-time updates.

Quick Start

import { useEntityList } from "@replyke/core";
 
function MyFeed() {
  const entityList = useEntityList({
    listId: "main-feed",
    infuseData: optionalExternalDataFunction
  });
 
  // Load initial data
  useEffect(() => {
    entityList.fetchEntities(
      { sortBy: "hot", timeFrame: "day" },
      { sourceId: "posts", limit: 20 }
    );
  }, []);
 
  return (
    <div>
      {entityList.loading && <div>Loading...</div>}
      {entityList.entities.map(entity => (
        <div key={entity.id}>{entity.title}</div>
      ))}
      {entityList.hasMore && (
        <button onClick={entityList.loadMore}>Load More</button>
      )}
    </div>
  );
}

Core Concepts

List IDs

Each entity list requires a unique listId. This is a client-side identifier that allows multiple independent lists to coexist in your application with their own state, filters, and cached data.

Think of listId as creating separate “instances” of entity lists in your Redux store. Each list maintains:

  • Its own entities array
  • Independent filter states
  • Separate loading states
  • Individual pagination state
// Different lists for different UI purposes
const mainFeed = useEntityList({ listId: "main-feed" });
const userProfile = useEntityList({ listId: `user-${userId}` });
const searchResults = useEntityList({ listId: "search-results" });
const trending = useEntityList({ listId: "trending-posts" });

Common listId patterns:

  • Static feeds: "main-feed", "trending", "popular"
  • User-specific: "user-${userId}", "user-${userId}-favorites"
  • Search/filters: "search-${query}", "category-${categoryId}"
  • Temporary lists: "modal-feed", "drawer-content"

Important: The listId is purely for client-side state management and doesn’t affect which entities are fetched from the server. Use sourceId in your fetch configuration to control which entities are returned from Replyke.

Source IDs (Critical Concept)

Always use sourceId when creating entities. The sourceId logically separates different types of content within your project, preventing them from mixing inappropriately.

Why sourceId Matters

Imagine you’re building an image-sharing app but also have a product blog. Without sourceId:

  • Your image feed could suddenly show blog articles
  • Blog subscribers might see random user photos
  • Content gets mixed unintentionally

With proper sourceId separation:

// When creating entities
await createEntity({
  title: "Beautiful sunset",
  content: "Amazing photo from today",
  sourceId: "image-posts" // Images stay in image feeds
});
 
await createEntity({
  title: "Product Update v2.0",
  content: "New features and improvements...",
  sourceId: "blog-articles" // Articles stay in blog feeds
});

Fetching by sourceId

// Image feed - only gets images
const imageFeed = useEntityList({ listId: "main-images" });
imageFeed.fetchEntities(
  { sortBy: "hot" },
  { sourceId: "image-posts" }
);
 
// Blog feed - only gets articles
const blogFeed = useEntityList({ listId: "blog-feed" });
blogFeed.fetchEntities(
  { sortBy: "new" },
  { sourceId: "blog-articles" }
);

Best Practices for sourceId

✅ Always use sourceId, even initially:

// Good - even if you only have one content type now
{ sourceId: "user-posts" }
 
// Bad - will cause problems later
{ sourceId: null }

✅ Use descriptive names:

  • "user-posts" - User-generated content
  • "blog-articles" - Official blog posts
  • "product-reviews" - Product reviews
  • "help-docs" - Documentation content
  • "announcements" - Company announcements

Filters vs Configuration

Entity lists accept two types of parameters:

  • Filters: User-controlled parameters like sorting, time frames, and content filters
  • Configuration: System-level parameters like sourceId and pagination limit
entityList.fetchEntities(
  // Filters (what content to show)
  {
    sortBy: "hot",
    timeFrame: "week",
    userId: "specific-user"
  },
  // Configuration (how to fetch)
  {
    sourceId: "blog-posts", // Critical: separates content types
    limit: 25
  }
);

Hook Interface

interface UseEntityListProps {
  listId: string;
  infuseData?: (foreignId: string) => Promise<Record<string, any> | null>;
}
 
interface UseEntityListValues {
  // Data
  entities: Entity[];
  infusedEntities: (Entity & Record<string, any>)[];
 
  // State
  loading: boolean;
  hasMore: boolean;
 
  // Current filters (read-only)
  sortBy: EntityListSortByOptions | null;
  timeFrame: TimeFrame | null;
  sourceId: string | null;
  userId: string | null;
  followedOnly: boolean;
  keywordsFilters: KeywordsFilters | null;
  titleFilters: TitleFilters | null;
  contentFilters: ContentFilters | null;
  attachmentsFilters: AttachmentsFilters | null;
  locationFilters: LocationFilters | null;
  metadataFilters: MetadataFilters | null;
 
  // Actions
  fetchEntities: (filters, config?, options?) => void;
  loadMore: () => void;
  createEntity: (props) => Promise<Entity | undefined>;
  deleteEntity: (props) => Promise<void>;
}

Available Filters

Entity lists support comprehensive filtering options:

  • sortBy: "hot", "new", "top", "controversial"
  • timeFrame: "hour", "day", "week", "month", "year"
  • userId: Filter by specific user
  • followedOnly: Show only followed users’ content
  • Content filters: Title, content, keywords, attachments, location, metadata

See the filters documentation for detailed examples of each filter type.

Advanced Features

External Data Integration

Use the infuseData callback to merge external data with entities:

const entityList = useEntityList({
  listId: "products",
  infuseData: async (foreignId) => {
    const inventory = await fetch(`/api/inventory/${foreignId}`);
    return inventory.json();
  }
});
 
// Access merged data
entityList.infusedEntities.forEach(entity => {
  console.log(entity.title); // From Replyke
  console.log(entity.infusion?.stock); // From external API (under 'infusion')
});

Dynamic Filtering

Update filters by calling fetchEntities with new parameters:

const handleSearch = (query: string) => {
  entityList.fetchEntities({
    sortBy: "new",
    titleFilters: { includes: [query] }
  }, undefined, {
    immediate: true,
    clearImmediately: true
  });
};

Multiple Lists

Create multiple independent lists for different use cases:

function Dashboard() {
  const hotPosts = useEntityList({ listId: "hot-posts" });
  const newPosts = useEntityList({ listId: "new-posts" });
  const userPosts = useEntityList({ listId: `user-${userId}` });
 
  useEffect(() => {
    hotPosts.fetchEntities({ sortBy: "hot" });
    newPosts.fetchEntities({ sortBy: "new" });
    userPosts.fetchEntities({ sortBy: "new", userId });
  }, []);
 
  return (
    <div>
      <div>Hot Posts: {hotPosts.entities.length}</div>
      <div>New Posts: {newPosts.entities.length}</div>
      <div>User Posts: {userPosts.entities.length}</div>
    </div>
  );
}

Error Handling

The hook automatically handles errors and provides loading states:

const entityList = useEntityList({ listId: "my-feed" });
 
// Check loading state
if (entityList.loading) {
  return <div>Loading...</div>;
}
 
// Handle empty state
if (!entityList.loading && entityList.entities.length === 0) {
  return <div>No posts found</div>;
}

Performance Notes

  • Entity lists use Redux for state management, providing automatic caching and persistence
  • The fetchEntities function includes debouncing for filter changes (800ms delay)
  • Use options.immediate: true to bypass debouncing when needed
  • Each list maintains independent state based on its listId