Skip to content
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

Track selected school across map views and map state for back-button return to map from school detail #241

Merged
merged 14 commits into from
Oct 3, 2024

Conversation

thomhickey
Copy link
Collaborator

@thomhickey thomhickey commented Sep 28, 2024

MapContext keeps track of the selected school and the state of the map. This enables a ux where once a user clicks through to a school and then uses the browser back button, they are returned to exactly where they were. Note that MapContext is available across back-button refresh of pages via a combination of React + Next.js caching. Typically of course this would not work as a back-button navigation bootstraps the entire app again, clearing any context. A hard-refresh or nav-landing onto a page will not preserve context, which is what we want.

Details:

  • uses React Provider pattern to provide map context to components. context includes the state of the map view (map or list) and what the selected school is along with a setter function to set the selected school. this context can be used on back-button return to the map to reset the user's last view and last selected school.
  • map and list view now persist the selected school across the two views.
  • removes the /map#list hashed route solution for back button return to previous view.
  • removes the need to set className on the div id="root" element from the map, instead we can also use MapContext from RootLayout to determine how to set the h-auto and h-dvh-with-fallback css classes.
  • tracks initial interaction of the user with the map so that on back-button return to the map view we don't zoom/flyTo as it is dizzying to the user.
  • mapbox is now torn-down correctly to avoid leaks, etc.

Copy link

vercel bot commented Sep 28, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
support-sfusd ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 28, 2024 4:03pm

@thomhickey
Copy link
Collaborator Author

Here's a screen grab of the interaction

map.context.mov

@thomhickey thomhickey changed the title Adds map context using React provider pattern Track selected school across map views and map state for back-button return to map from school detail Sep 28, 2024
@thomhickey thomhickey marked this pull request as ready for review September 28, 2024 16:28
@@ -30,6 +30,7 @@ yarn-error.log*

# vercel
.vercel
.vscode
Copy link
Collaborator Author

@thomhickey thomhickey Sep 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be standard in our .gitignore?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed this ... we need to whitelist some files. will demo in upcoming PR.

@@ -4,7 +4,7 @@ import { School } from "@/types/school";

type MapListProps = {
schools: School[];
setSelectedSchool: (school: School | false | null) => void;
setSelectedSchool: (school: School | null) => void;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

false no longer needed/valid

const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
if (e.propertyName === "max-height" && isExpanded) {
cardRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
};

function onClick(e: React.MouseEvent<HTMLDivElement>) {
if (learnMoreRef.current && learnMoreRef.current.contains(e.target as Node)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was causing a setSelectedSchool(null), which we don't want because then we lose the context

if (isExpanded) {
setSelectedSchool(null);
} else {
setSelectedSchool(school);
}
}

useEffect(() => {
if (selectedSchool && selectedSchool.id === school.id) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrolls to selected school on back-button return

@@ -56,7 +63,8 @@ const MapboxMap = ({

mapRef.current = map;
map.on("click", () => {
setSelectedSchool(false);
setSelectedSchool(null);
userHasInteracted.current = true;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed to differentiate between back-button return and hard landing on the map

@@ -20,7 +20,8 @@
],
"paths": {
"@/*": ["./src/*"]
}
},
"sourceMap": true
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enables front-end debugging inside vscode

});

return () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

teardown function tears down mapbox on close, avoiding memory leaks, etc.

@@ -169,26 +179,49 @@ const MapboxMap = ({
new mapboxgl.Marker(bayBridgeEl)
.setLngLat([-122.3778, 37.7983])
.addTo(map);

setMapLoaded(true);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid race condition where useEffect runs before map.on("load") which prevented scrolling to correct school in list on back-button refresh

Copy link
Collaborator

@BeeSeeWhy BeeSeeWhy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look good. Thanks for the breakdown of everything in the PR!

@BeeSeeWhy BeeSeeWhy merged commit 5289a2c into develop Oct 3, 2024
2 checks passed
useEffect(() => {
if (mapRef.current && selectedSchool && typeof selectedSchool !== 'boolean') {
// check mapLoaded to avoid race condition where markersRef is not yet initialized
if (mapLoaded && mapRef.current && selectedSchool) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice

const userHasInteracted = useRef(false);

const flyToOptions = useMemo(() => ({
zoom: 14, // zoom level to fly to
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm noticing that we are always zooming. I think it's best not to change zoom unless the user explicitly requests it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah @nickvisut, it can get zoomy, happy to change anything. I already worked on the settings to slow down the zoom effect to make it less dizzying,. But we can remove the auto-zoom completely. Design team didn't ask for any changes, but maybe they don't know how easy it is to change, not sure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe with 100+ schools it might actually make more sense to auto-zoom as the pins will be right on top of each other. i still can't imagine so many pins in my mind's eye, it is going to look awfully crowded.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that that could be a future problem, but the experience as of now is suboptimal. Consider: A user arrives at the map and clicks on a school. How do they then click on another school? They will have to manually zoom out in most cases.

There may be a good solution that is the best of both worlds, but let's lean towards best UX w/current set of schools while we figure that out.

// the empty instruction card)
/* TODO: consider using something more descriptive like an enum instead */
setSelectedSchool(false);
setSelectedSchool(null);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double checking here: Is this no longer needed?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(false vs null as per the comments)

@nickvisut
Copy link
Collaborator

Thanks for putting up with my post-hoc code review 🙂

@nickvisut
Copy link
Collaborator

The changes look good. Thanks for the breakdown of everything in the PR!

💯 on the detailed breakdown!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants