-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement photo pagination and dynamic loading in Expo app #374
Implement photo pagination and dynamic loading in Expo app #374
Conversation
The commit adds infinite scroll functionality to the photo gallery, including: - Paginated photo loading with cursor-based navigation - Loading indicators and proper error handling - Performance optimizations for FlatList rendering
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Skipped Deployment
|
WalkthroughThis pull request introduces enhanced photo loading functionality in the Expo application, focusing on the Changes
Sequence DiagramsequenceDiagram
participant User
participant PhotoGrid
participant NewEventModal
participant MediaLibrary
User->>PhotoGrid: Scroll near list end
PhotoGrid->>NewEventModal: Trigger onEndReached
NewEventModal->>MediaLibrary: Request more photos
MediaLibrary-->>NewEventModal: Return additional photos
NewEventModal->>PhotoGrid: Update photo list
PhotoGrid->>User: Display new photos
Possibly related PRs
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/expo/src/store.ts (1)
201-207
: Avoid usingJSON.stringify
for state comparison insetRecentPhotos
Using
JSON.stringify
to comparerecentPhotos
arrays can lead to performance issues as the data grows. Consider using a more efficient deep equality check or a library likelodash.isequal
. Alternatively, if shallow comparison suffices, you can compare array lengths and elements directly.Apply this diff to optimize the comparison:
setRecentPhotos: (photos) => set((state) => { - if (JSON.stringify(state.recentPhotos) === JSON.stringify(photos)) { + if ( + state.recentPhotos.length === photos.length && + state.recentPhotos.every((photo, index) => photo.id === photos[index].id) + ) { return state; } return { recentPhotos: photos }; }),apps/expo/src/app/new.tsx (1)
Line range hint
443-492
: Refactor duplicated photo loading logic into a reusable functionThe logic for checking permissions and loading recent photos in
useFocusEffect
is similar toloadRecentPhotos
. Consider refactoring this into a shared function or custom hook to reduce code duplication and enhance maintainability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/expo/src/app/new.tsx
(6 hunks)apps/expo/src/store.ts
(6 hunks)
🔇 Additional comments (2)
apps/expo/src/store.ts (1)
Line range hint
218-246
: TheresetStore
function correctly resets the stateThe
resetStore
action properly initializes all state properties to their default values, ensuring a clean state.apps/expo/src/app/new.tsx (1)
204-219
: Verify the use ofremoveClippedSubviews
inFlatList
While
removeClippedSubviews
can improve performance by unmounting components outside the viewport, it may cause rendering issues, especially with images and dynamic content. Ensure that enabling this prop does not introduce visual glitches in thePhotoGrid
component.
if (isLoadingMore || !hasMorePhotos) return; | ||
|
||
setIsLoadingMore(true); | ||
|
||
try { | ||
const { assets } = await MediaLibrary.getAssetsAsync({ | ||
first: 15, | ||
const { | ||
assets, | ||
hasNextPage, | ||
endCursor: newEndCursor, | ||
} = await MediaLibrary.getAssetsAsync({ | ||
first: PHOTOS_PER_PAGE, | ||
after: endCursor, | ||
sortBy: MediaLibrary.SortBy.creationTime, | ||
mediaType: [MediaLibrary.MediaType.photo], | ||
}); | ||
const photos: RecentPhoto[] = assets.map((asset) => ({ | ||
id: asset.id, | ||
uri: asset.uri, | ||
})); | ||
setRecentPhotos(photos); | ||
|
||
// Validate each asset | ||
const accessibleAssets = await Promise.all( | ||
assets.map(async (asset) => { | ||
try { | ||
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset); | ||
return assetInfo.localUri | ||
? { id: asset.id, uri: assetInfo.localUri } | ||
: null; | ||
} catch { | ||
return null; | ||
} | ||
}), | ||
); | ||
|
||
const photos: RecentPhoto[] = accessibleAssets.filter( | ||
(asset): asset is RecentPhoto => asset !== null, | ||
); | ||
|
||
setRecentPhotos([...recentPhotos, ...photos]); | ||
setHasMorePhotos(hasNextPage); | ||
setEndCursor(newEndCursor); | ||
} catch (error) { | ||
console.error("Error loading recent photos:", error); | ||
toast.error("Failed to load photos."); | ||
} finally { | ||
setIsLoadingMore(false); | ||
} | ||
}, [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize photo loading by eliminating unnecessary asset info requests
In loadRecentPhotos
, calling MediaLibrary.getAssetInfoAsync
for each asset can significantly slow down loading times. Since MediaLibrary.getAssetsAsync
already provides the uri
property, you can use it directly to improve performance.
Apply this diff to optimize the photo loading:
try {
const {
assets,
hasNextPage,
endCursor: newEndCursor,
} = await MediaLibrary.getAssetsAsync({
first: PHOTOS_PER_PAGE,
after: endCursor,
sortBy: MediaLibrary.SortBy.creationTime,
mediaType: [MediaLibrary.MediaType.photo],
});
- // Validate each asset
- const accessibleAssets = await Promise.all(
- assets.map(async (asset) => {
- try {
- const assetInfo = await MediaLibrary.getAssetInfoAsync(asset);
- return assetInfo.localUri
- ? { id: asset.id, uri: assetInfo.localUri }
- : null;
- } catch {
- return null;
- }
- }),
- );
- const photos: RecentPhoto[] = accessibleAssets.filter(
- (asset): asset is RecentPhoto => asset !== null,
- );
+ const photos: RecentPhoto[] = assets.map((asset) => ({
+ id: asset.id,
+ uri: asset.uri,
+ }));
setRecentPhotos([...recentPhotos, ...photos]);
setHasMorePhotos(hasNextPage);
setEndCursor(newEndCursor);
} catch (error) {
console.error("Error loading recent photos:", error);
toast.error("Failed to load photos.");
} finally {
setIsLoadingMore(false);
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (isLoadingMore || !hasMorePhotos) return; | |
setIsLoadingMore(true); | |
try { | |
const { assets } = await MediaLibrary.getAssetsAsync({ | |
first: 15, | |
const { | |
assets, | |
hasNextPage, | |
endCursor: newEndCursor, | |
} = await MediaLibrary.getAssetsAsync({ | |
first: PHOTOS_PER_PAGE, | |
after: endCursor, | |
sortBy: MediaLibrary.SortBy.creationTime, | |
mediaType: [MediaLibrary.MediaType.photo], | |
}); | |
const photos: RecentPhoto[] = assets.map((asset) => ({ | |
id: asset.id, | |
uri: asset.uri, | |
})); | |
setRecentPhotos(photos); | |
// Validate each asset | |
const accessibleAssets = await Promise.all( | |
assets.map(async (asset) => { | |
try { | |
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset); | |
return assetInfo.localUri | |
? { id: asset.id, uri: assetInfo.localUri } | |
: null; | |
} catch { | |
return null; | |
} | |
}), | |
); | |
const photos: RecentPhoto[] = accessibleAssets.filter( | |
(asset): asset is RecentPhoto => asset !== null, | |
); | |
setRecentPhotos([...recentPhotos, ...photos]); | |
setHasMorePhotos(hasNextPage); | |
setEndCursor(newEndCursor); | |
} catch (error) { | |
console.error("Error loading recent photos:", error); | |
toast.error("Failed to load photos."); | |
} finally { | |
setIsLoadingMore(false); | |
} | |
}, [ | |
if (isLoadingMore || !hasMorePhotos) return; | |
setIsLoadingMore(true); | |
try { | |
const { | |
assets, | |
hasNextPage, | |
endCursor: newEndCursor, | |
} = await MediaLibrary.getAssetsAsync({ | |
first: PHOTOS_PER_PAGE, | |
after: endCursor, | |
sortBy: MediaLibrary.SortBy.creationTime, | |
mediaType: [MediaLibrary.MediaType.photo], | |
}); | |
const photos: RecentPhoto[] = assets.map((asset) => ({ | |
id: asset.id, | |
uri: asset.uri, | |
})); | |
setRecentPhotos([...recentPhotos, ...photos]); | |
setHasMorePhotos(hasNextPage); | |
setEndCursor(newEndCursor); | |
} catch (error) { | |
console.error("Error loading recent photos:", error); | |
toast.error("Failed to load photos."); | |
} finally { | |
setIsLoadingMore(false); | |
} | |
}, [ |
Summary by CodeRabbit
Release Notes
New Features
Improvements
User Experience