A React library for implementing infinite scrolling with optimistic updates
npm install react-infinite-scroll-optimisticA lightweight and customizable React library for implementing infinite scrolling with built-in support for optimistic updates.
- ๐ Infinite scrolling with intersection observer
- โจ Built-in optimistic updates using React 19's useOptimistic
- ๐ฑ Fully responsive and customizable
- ๐งช Well-tested components and hooks
- ๐ก TypeScript support
- ๐ง Easy to integrate with any data source
``bash`
npm install react-infinite-scroll-optimisticor
yarn add react-infinite-scroll-optimistic
- React 19 or higher
- React DOM 19 or higher
`tsx
import { useInfiniteScroll, InfiniteScroll } from 'react-infinite-scroll-optimistic';
function MyList() {
const scrollResult = useInfiniteScroll({
fetchItems: async (page) => {
const response = await fetch(/api/items?page=${page});
return response.json();
},
});
return (
renderItem={(item, index, ref) => (
Optimistic Updates Example
`tsx
import { useInfiniteScroll, useOptimisticActions, InfiniteScroll } from 'react-infinite-scroll-optimistic';function MyList() {
const scrollResult = useInfiniteScroll({
fetchItems: async (page) => {
const response = await fetch(
/api/items?page=${page});
return response.json();
},
}); const [optimisticItems, addOptimisticAction] = useOptimisticActions(
scrollResult.items,
(state, action) => {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.item];
case 'REMOVE_ITEM':
return state.filter(item => item.id !== action.id);
default:
return state;
}
}
);
const handleAddItem = async () => {
// Show optimistic update immediately
const optimisticItem = { id: 'temp-' + Date.now(), name: 'New Item', status: 'pending' };
addOptimisticAction({ type: 'ADD_ITEM', item: optimisticItem });
// Make API call
try {
const response = await fetch('/api/items', {
method: 'POST',
body: JSON.stringify({ name: 'New Item' }),
});
const newItem = await response.json();
// Update actual state with server response
scrollResult.setItems([...scrollResult.items, newItem]);
} catch (error) {
console.error('Failed to add item:', error);
// Handle error, maybe revert the optimistic update
}
};
return (
<>
scrollResult={scrollResult}
renderItem={(item, index, ref) => (
{item.name} {item.status === 'pending' && '(Saving...)'}
)}
/>
>
);
}
`API Reference
$3
`tsx
function useInfiniteScroll({
fetchItems,
initialPage,
threshold,
loadImmediately,
initialItems,
}: {
fetchItems: (page: number) => Promise;
initialPage?: number;
threshold?: number;
loadImmediately?: boolean;
initialItems?: T[];
}): {
items: T[];
loading: boolean;
error: Error | null;
hasMore: boolean;
loadMore: () => Promise;
lastItemRef: (node: Element | null) => void;
reset: () => void;
setItems: React.Dispatch>;
page: number;
};
`$3
`tsx
function useOptimisticActions(
initialState: T[],
updateFn: (currentState: T[], action: A) => T[]
): [T[], (action: A) => void];
`$3
`tsx
scrollResult={/ result from useInfiniteScroll /}
renderItem={(item, index, lastItemRef) => (
/ render your item /
)}
loadingComponent={/ optional custom loading component /}
errorComponent={/ optional custom error component /}
endComponent={/ optional custom end of list component /}
emptyComponent={/ optional custom empty list component /}
className={/ optional class name /}
style={/ optional inline styles /}
>
{/ optional header content /}
``MIT