Skip to content

Commit 46240ed

Browse files
committed
Scroll persistance with going back to the top on search finished
1 parent 8350dfa commit 46240ed

7 files changed

Lines changed: 69 additions & 38 deletions

File tree

my-app/firebase.js

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,31 +85,29 @@ export function syncModelToFirebase(model) {
8585

8686
export function syncScrollPositionToFirebase(model, containerRef) {
8787
if (!containerRef?.current) return;
88-
89-
const throttledSet = throttle((scrollPixel) => {
90-
if (model?.user?.uid) {
91-
const userRef = ref(db, `users/${model.user.uid}/scrollPosition`);
92-
set(userRef, scrollPixel).catch(console.error);
93-
}
94-
}, 500);
88+
let lastSavedPosition = 0;
89+
90+
// const throttledSet = throttle((scrollPixel) => {
91+
// if (model?.user?.uid) {
92+
// const userRef = ref(db, `users/${model.user.uid}/scrollPosition`);
93+
// set(userRef, scrollPixel).catch(console.error);
94+
// }
95+
// }, 500);
9596

9697
const handleScroll = () => {
9798
const scrollTop = containerRef.current.scrollTop;
99+
// make a 100px threshold
100+
if (Math.abs(scrollTop - lastSavedPosition) < 100)
101+
return;
102+
103+
lastSavedPosition = scrollTop;
98104
model.setScrollPosition(scrollTop);
99-
if (!model.user) {
100-
localStorage.setItem("scrollPosition", scrollTop);
101-
}
102-
throttledSet(scrollTop);
105+
localStorage.setItem("scrollPosition", scrollTop);
106+
// throttledSet(scrollTop);
103107
};
104108

105109
containerRef.current.addEventListener('scroll', handleScroll);
106-
107-
// Return cleanup function
108-
return () => {
109-
if (containerRef.current) {
110-
containerRef.current.removeEventListener('scroll', handleScroll);
111-
}
112-
};
110+
return () => containerRef.current?.removeEventListener('scroll', handleScroll);
113111
}
114112

115113

my-app/src/index.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ configure({ enforceActions: "never" });
1515
const reactiveModel = makeAutoObservable(model);
1616
connectToFirebase(reactiveModel);
1717

18-
// ✅ Add /share route here
1918
export function makeRouter(reactiveModel) {
2019
return createHashRouter([
2120
{

my-app/src/model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const model = {
2323
},
2424

2525
setScrollPosition(position) {
26-
this.scrollPosition = position; // This method updates the scroll position
26+
this.scrollPosition = position;
2727
},
2828

2929
setCourses(courses){

my-app/src/presenters/ListViewPresenter.jsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,38 @@ import {ReviewPresenter} from "../presenters/ReviewPresenter.jsx"
88
import {syncScrollPositionToFirebase} from "../../firebase.js"
99

1010
const ListViewPresenter = observer(({ model }) => {
11-
1211
const scrollContainerRef = useRef(null);
12+
let attempts = 0;
13+
const MAX_Depth = 49;
14+
15+
function persistantScrolling(fetchMoreCourses, hasMore){
16+
const container = scrollContainerRef.current;
17+
if (!container || !model.scrollPosition) return;
18+
19+
20+
21+
const attemptScroll = () => {
22+
23+
// refresh on significant change (same as in firebase)
24+
if (Math.abs(container.scrollTop - model.scrollPosition) < 100)
25+
return;
26+
27+
attempts++;
28+
if (attempts > MAX_Depth) {
29+
return;
30+
}
31+
const needsMoreCourses = container.scrollHeight < model.scrollPosition && hasMore;
32+
33+
if (needsMoreCourses) {
34+
fetchMoreCourses();
35+
setTimeout(attemptScroll, 100); // Add delay between attempts
36+
} else {
37+
container.scrollTop = model.scrollPosition;
38+
syncScrollPositionToFirebase(model, scrollContainerRef)
39+
}
40+
};
41+
attemptScroll();
42+
}
1343

1444
useEffect(() => {
1545
// Load initial scroll position
@@ -24,7 +54,7 @@ const ListViewPresenter = observer(({ model }) => {
2454
useEffect(() => {
2555
const cleanup = syncScrollPositionToFirebase(model, scrollContainerRef);
2656
return () => cleanup;
27-
}, [model.user, scrollContainerRef]);
57+
}, [model.user, model.currentSearch, scrollContainerRef]);
2858

2959
const addFavourite = (course) => {
3060
model.addFavourite(course);
@@ -60,16 +90,20 @@ const ListViewPresenter = observer(({ model }) => {
6090
return <ListView
6191
courses={model.courses}
6292
searchResults={model.currentSearch}
93+
6394
favouriteCourses={model.favourites}
6495
addFavourite={addFavourite}
6596
removeFavourite={removeFavourite}
97+
handleFavouriteClick={handleFavouriteClick}
98+
6699
isPopupOpen={isPopupOpen}
67100
setIsPopupOpen={setIsPopupOpen}
68101
setSelectedCourse={setSelectedCourse}
69102
popup={popup}
70-
handleFavouriteClick={handleFavouriteClick}
103+
71104
targetScroll={model.scrollPosition}
72105
scrollContainerRef={scrollContainerRef}
106+
persistantScrolling={persistantScrolling}
73107
/>;
74108
});
75109

my-app/src/presenters/SearchbarPresenter.jsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ const SearchbarPresenter = observer(({ model }) => {
3333
}
3434
};
3535

36+
function resetScoll(){
37+
model.setScrollPosition(0.01);
38+
}
39+
3640
const creditsSum = (favouriteCourses) => {
3741
return favouriteCourses.reduce((sum, course) => sum + parseFloat(course.credits), 0);
3842
};
@@ -57,10 +61,10 @@ const SearchbarPresenter = observer(({ model }) => {
5761
reviewPresenter={reviewPresenter}
5862
prerequisiteTree={preP}
5963
/>;
64+
6065

6166
return (
6267
<SearchbarView
63-
model={model}
6468
searchCourses={searchCourses}
6569
favouriteCourses={model.favourites}
6670
removeAllFavourites={removeAllFavourites}
@@ -72,6 +76,7 @@ const SearchbarPresenter = observer(({ model }) => {
7276
popup={popup}
7377
handleFavouriteClick={handleFavouriteClick}
7478
totalCredits={creditsSum(model.favourites)}
79+
resetScrollPosition={resetScoll}
7580
/>
7681
);
7782
});

my-app/src/views/ListView.jsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,18 @@ function ListView(props) {
3535

3636
const fetchMoreCourses = useCallback(() => {
3737
if (!hasMore) return;
38-
const nextItems = coursesToDisplay.slice(displayedCourses.length, displayedCourses.length + 10);
38+
const nextItems = coursesToDisplay.slice(displayedCourses.length, displayedCourses.length + 50);
3939
setDisplayedCourses(prevCourses => [...prevCourses, ...nextItems]);
4040
setHasMore(displayedCourses.length + nextItems.length < coursesToDisplay.length);
4141
}, [displayedCourses.length, coursesToDisplay, hasMore]);
4242

43+
const [isRestoringScroll, setIsRestoringScroll] = useState(false);
4344
useEffect(() => {
44-
const container = props.scrollContainerRef.current;
45-
if (!container || !props.targetScroll) return;
46-
47-
const attemptScroll = () => {
48-
if (container.scrollHeight >= props.targetScroll) {
49-
container.scrollTop = props.targetScroll;
50-
} else if (hasMore) {
51-
fetchMoreCourses();
52-
setTimeout(attemptScroll, 100);
53-
}
54-
};
55-
56-
attemptScroll();
45+
if (props.targetScroll > 0 && !isRestoringScroll) {
46+
setIsRestoringScroll(true);
47+
props.persistantScrolling(fetchMoreCourses, hasMore);
48+
setIsRestoringScroll(false);
49+
}
5750
}, [props.targetScroll, hasMore, displayedCourses.length]);
5851

5952
return (
@@ -76,6 +69,7 @@ function ListView(props) {
7669
endMessage={<p className="text-center py-2">No more courses</p>}
7770
scrollThreshold={0.9} // 90% of the container height
7871
scrollableTarget="scrollableDiv"
72+
initialScrollY={0}
7973
>
8074
{displayedCourses.map(course => (
8175
<div

my-app/src/views/SearchbarView.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function SearchbarView(props) {
2020
}, [auth]);
2121

2222
const handleSearch = (query) => {
23+
props.resetScrollPosition();
2324
setSearchQuery(query);
2425
props.searchCourses(query);
2526
};

0 commit comments

Comments
 (0)