instantdb-react-ui
Version:
Customizable react components for InstantDB (forms/lists/etc.)
124 lines (123 loc) • 4.99 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useRef, useState } from 'react';
/** Standard list that loads all data at once */
const NormalList = (props) => {
const { entity, render, skeleton, query, noResults, db } = props;
const constructedQuery = query || { [entity]: {} };
const { isLoading, error, data: rawData } = db.useQuery(constructedQuery); // TODO: Bug in Instant, isLoading doesn't change to false when the query changes
// Extract the array from the entity property
const data = rawData ? rawData[entity] : null;
if (isLoading)
return skeleton || null;
if (error || !data)
return noResults || null;
if (data.length === 0)
return noResults || null;
return (_jsx(_Fragment, { children: data.map(item => (_jsx("div", { children: render(item, item.id) }, item.id))) }));
};
/** Infinite list that loads more data when you scroll to the end of the list */
const InfiniteList = (props) => {
const { entity, render, skeleton, query, noResults, pageSize = 10, db } = props;
const [limit, setLimit] = useState(pageSize);
const loadMoreRef = useRef(null);
const constructedQuery = {
[entity]: {
...(query?.[entity] || {}),
$: {
...(query?.[entity]?.$ || {}),
limit,
},
},
};
const { isLoading, error, data: rawData } = db.useQuery(constructedQuery);
const items = rawData ? rawData[entity] : [];
const hasMore = items.length === limit;
useEffect(() => {
const increaseLimit = () => setLimit(prev => prev + pageSize);
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
increaseLimit();
}
}, { threshold: 0.1 });
const currentRef = loadMoreRef.current;
if (currentRef) {
observer.observe(currentRef);
// Additional check to handle cases where intersection observer might miss
const checkVisibility = () => {
const entry = observer.takeRecords()[0];
if (entry?.isIntersecting && hasMore) {
increaseLimit();
}
};
const timer = setInterval(checkVisibility, 100);
return () => {
observer.disconnect();
clearInterval(timer);
};
}
return () => observer.disconnect();
}, [hasMore, pageSize]);
if (isLoading && items.length === 0)
return skeleton || null;
if (error)
return noResults || null;
if (items.length === 0)
return noResults || null;
return (_jsxs(_Fragment, { children: [items.map(item => (_jsx("div", { children: render(item, item.id) }, item.id))), isLoading && items.length > 0 && _jsx("div", { children: "Loading more..." }), _jsx("div", { ref: loadMoreRef, style: { height: '10px' } })] }));
};
/** Hook to get pagination state for a paginated list */
export const useIDBPagination = (props) => {
const db = props.db;
const pageSize = props.pageSize || 10;
const [page, setPage] = useState(1);
// Get all items to count total
const allItemsQuery = props.query || { [props.model]: {} };
const { data: allData } = db.useQuery(allItemsQuery);
const totalItems = allData?.[props.model]?.length || 0;
const totalPages = Math.ceil(totalItems / pageSize);
// Get paginated items
const itemQuery = {
[props.model]: {
...(props.query?.[props.model] || {}),
$: {
...(props.query?.[props.model]?.$ || {}),
limit: pageSize,
offset: (page - 1) * pageSize,
},
},
};
const { data: itemData } = db.useQuery(itemQuery);
const items = itemData?.[props.model] || [];
const goToPage = (newPage) => {
setPage(Math.max(1, Math.min(newPage, totalPages)));
};
return {
...props,
items: items,
page,
totalPages,
totalItems,
goToPage,
};
};
/** Paginated list */
const PaginatedList = (props) => {
const { render, skeleton, noResults, pagination } = props;
if (!pagination || !pagination.items)
return skeleton || null;
if (pagination.items.length === 0)
return noResults || null;
return (_jsx(_Fragment, { children: pagination.items.map(item => (_jsx("div", { children: render(item, item.id) }, item.id))) }));
};
/** instantdb-react-ui list component, with support for normal, infinite, and paginated modes */
export const IDBList = (props) => {
const { mode } = props;
if (mode === 'infinite') {
return _jsx(InfiniteList, { ...props });
}
else if (mode === 'paginated') {
return _jsx(PaginatedList, { ...props });
}
// Default to normal list
return _jsx(NormalList, { ...props });
};