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 paginationlimit
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