google-map-react
- Our primary wrapper around Google Maps that converts the native maps API into a React component
- Supports rendering arbitrary
- Vendor docs, pt 2
redux-toolkit
- We use Redux to hold the view state of the map since it needs to be accessible by many parts of the app
- Redux Toolkit is an abstraction around Redux with helpers for common tasks. Reduces a lot of boilerplate with using traditional Redux
- One abstraction is
createAsyncThunk
which automatically dispatchpending
andsucceeded
actions from any asynchronous request- e.g. We use this to wrap fetching locations and clusters, so that the
pending
action can signal that the map is loading
- e.g. We use this to wrap fetching locations and clusters, so that the
components/map/Map.js
- Thin wrapper around
google-map-react
's map component
- Thin wrapper around
components/map/MapPage.js
- Connects to the Redux store
- Populates
Map
with locations and cluster data from Redux
redux/mapSlice.js
- Actions and reducers for changing internal view state of map (center and zoom)
- Actions for fetching locations and clusters from API
- Actions for various map actions
redux/viewChange.js
- Primary event handler that gets called when the map loads or is moved by the user
- Dispatched in
MapPage
and given the new view of the map - Dispatches other actions in
mapSlice
like storing the new view, fetching location and cluster data, and fetching counts for the type filter
Usage:
<GoogleMapReact
...
center={view.center}
zoom={view.zoom}
onChange={onViewChange}
...
>
GoogleMapReact
is a controlled component. This means that the view state is always driven by the center
and zoom
props passed to it.
onChange
is called in two cases:
- The user moves the map
When the user moves the map, onChange
is called with the new view state. The view state object looks like this:
{
center: { lat, lng }, // current map center
zoom: 4, // current map zoom
bounds: { nw, se, sw... }, // map corners in lat lng
size: { width, height... } // map size in px
}
Although the view change handler gives the bounds of the visible map back to us, notice that they are not required as props. This is because center
and zoom
uniquely determine the bounds of the map anyway.
- We explicitly change
center
and/orzoom
(e.g. user clicks on cluster, user clicks on coordinates in sidebar to re-center on location)
However, when we explicitly change center
and/or zoom
, onChange
will still be called, presumably so that we can receive the new bounds. This is useful because we can update the bounds of the map in our state, and pass them to the API to fetch new clusters and locations.
Contrast this with a traditional controlled form component, like an input
with value
and onChange
. If we explicitly change value
, onChange
is not called again with the value
we pass it.
(For tracking Redux actions in general, highly recommend downloading the Redux Devtools extension)
-
Page loads
- We explicitly pass the default center and zoom, or the center and zoom as parsed from the URL if it is available
- Because we explicitly passed the center and zoom,
GoogleMapReact
callsonChange
to provide us with the map bounds Map
callsonViewChange
MapPage
dispatchesviewChangeAndFetch
with the new view- Updates URL
- Stops map tracking geolocation if user moved too far from current location
- Stores the new view in Redux state
viewChange
action, not to be confused withviewChangeAndFetch
(this should be renamed)- Stored in
map.view
- Fetches filter counts if the filter is open
-
User moves the map
- Same as in Step 1, except
GoogleMapReact
callsonChange
on its own because the user moved the map
- Same as in Step 1, except
-
User clicks a cluster
Map
callsonClusterClick
MapPage
dispatchesclusterClick
action inmapSlice.js
clusterClick
zooms in by changing the zoom part of the internal view state- A new
zoom
is explicitly passed toGoogleMapReact
, soGoogleMapReact
callsonChange
- ...continue as in Step 1
-
Other app-initiated actions are same as Step 3.
Direct changes to the internal view state (the center and zoom passed to
GoogleMapReact
) are always initiated byviewChangeAndFetch
inviewChange.js
or other reducers inmapSlice.js
.