Skip to content

Commit

Permalink
fix excess rerendering
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeKarow committed Apr 9, 2024
1 parent 06f1e88 commit 3017ebf
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 77 deletions.
2 changes: 0 additions & 2 deletions apps/app/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,6 @@ const nextConfig = {
)
}

config.devtool = 'eval-source-map'

if (!isLocalDev) {
config.plugins.push(
new webpack.DefinePlugin({
Expand Down
84 changes: 46 additions & 38 deletions apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Head from 'next/head'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import { type GetServerSidePropsContext } from 'nextjs-routes'
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { z } from 'zod'

Expand Down Expand Up @@ -41,7 +41,7 @@ const formSchema = z.object({
})
type FormSchema = z.infer<typeof formSchema>
const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = ({
organizationId,
organizationId: _organizationId,
}) => {
const apiUtils = api.useUtils()
const { t } = useTranslation()
Expand All @@ -58,13 +58,16 @@ const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSide
{ id: orgLocationId },
{ enabled: router.isReady }
)
const { data: orgServices } = api.service.getNames.useQuery(
{ organizationId },
{
select: (data) => data.map(({ id, defaultText }) => ({ value: id, label: defaultText })),
refetchOnWindowFocus: false,
}
)

// for use with MultiSelectPopover

// const { data: orgServices } = api.service.getNames.useQuery(
// { organizationId },
// {
// select: (returnedData) => returnedData.map(({ id, defaultText }) => ({ value: id, label: defaultText })),
// refetchOnWindowFocus: false,
// }
// )
const defaultFormValues = data
? { id: data.id, name: data.name, services: data.services.map(({ serviceId }) => serviceId) }
: undefined
Expand Down Expand Up @@ -101,9 +104,32 @@ const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSide
}
}, [formMethods.formState, unsaved])
useEffect(() => {
if (data && status === 'success') setLoading(false)
if (data && status === 'success') {
setLoading(false)
}
}, [data, status])
if (loading || !data) return <OrgLocationPageLoading />

const tabHandler = useCallback((tab: string) => {
setActiveTab(tab)
switch (tab) {
case 'services': {
servicesRef.current?.scrollIntoView({ behavior: 'smooth' })
break
}
case 'photos': {
photosRef.current?.scrollIntoView({ behavior: 'smooth' })
break
}
case 'reviews': {
reviewsRef.current?.scrollIntoView({ behavior: 'smooth' })
break
}
}
}, [])

if (loading || !data) {
return <OrgLocationPageLoading />
}

const {
// emails,
Expand All @@ -130,7 +156,7 @@ const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSide
option: 'back',
backTo: 'dynamicText',
backToText: data.organization.name,
onClick: async () => {
onClick: () => {
router.push({
pathname: '/org/[slug]/edit',
query: { slug: data.organization.slug },
Expand All @@ -153,39 +179,19 @@ const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSide
))}
<ListingBasicInfo
data={{
name: data.name || data.organization.name,
id: data.id,
slug,
locations: [data],
description,
lastVerified: data.organization.lastVerified,
attributes,
name: data.name ?? data.organization.name,
id: data.id,
locations: [data],
lastVerified: data.organization.lastVerified,
isClaimed: data.organization.isClaimed,
}}
edit
location
/>
<Tabs
w='100%'
value={activeTab}
onTabChange={(tab) => {
setActiveTab(tab)
switch (tab) {
case 'services': {
servicesRef.current?.scrollIntoView({ behavior: 'smooth' })
break
}
case 'photos': {
photosRef.current?.scrollIntoView({ behavior: 'smooth' })
break
}
case 'reviews': {
reviewsRef.current?.scrollIntoView({ behavior: 'smooth' })
break
}
}
}}
>
<Tabs w='100%' value={activeTab} onTabChange={tabHandler}>
<Tabs.List className={classes.tabsList}>
<Tabs.Tab value='services'>{t('services')}</Tabs.Tab>
<Tabs.Tab value='photos'>{t('photo', { count: 2 })}</Tabs.Tab>
Expand Down Expand Up @@ -236,7 +242,9 @@ export const getServerSideProps = async ({
res,
}: GetServerSidePropsContext<'/org/[slug]/[orgLocationId]/edit'>) => {
const urlParams = z.object({ slug: z.string(), orgLocationId: prefixedId('orgLocation') }).safeParse(params)
if (!urlParams.success) return { notFound: true }
if (!urlParams.success) {
return { notFound: true }
}
const { slug, orgLocationId: id } = urlParams.data
const session = await checkServerPermissions({
ctx: { req, res },
Expand Down
57 changes: 45 additions & 12 deletions packages/ui/hooks/useGoogleMapMarker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { MantineProvider, Stack, Text, Title } from '@mantine/core'
import { useWhyDidYouUpdate } from 'ahooks'
import { useRouter } from 'next/router'
import { type Route } from 'nextjs-routes'
import { useCallback, useMemo } from 'react'
import { createRoot } from 'react-dom/client'

import { Link } from '~ui/components/core/Link'
Expand All @@ -22,7 +24,7 @@ const getHref: GetHref = ({ slug, locationId }) => {
}
type SlugOnly = {
slug: string
locationId?: undefined
locationId?: never
}
type LocationAndSlug = {
slug: string
Expand All @@ -33,15 +35,25 @@ export const useGoogleMapMarker = () => {
const router = useRouter()
const variant = useCustomVariant()
const { mapIsReady, infoWindow, marker, map } = useGoogleMaps()
return {
get(id: string) {
return marker.get(id)
},
add({ id, lat, lng, name, address, slug, locationId }: AddMarkerParams) {
if (!mapIsReady) throw new Error('map is not ready')
const markerInfoBoxLinkClickHandler = useCallback(
({ slug, locationId }: SlugOnly | LocationAndSlug) =>
() =>
router.push(getHref({ slug, locationId })),
[router]
)

// eslint-disable-next-line react-hooks/exhaustive-deps
const getMarker = useCallback((id: string) => marker.get(id), [])
const addMarker = useCallback(
({ id, lat, lng, name, address, slug, locationId }: AddMarkerParams) => {
if (!mapIsReady) {
throw new Error('map is not ready')
}
console.trace('adding new marker', { id, lat, lng, name, address, slug, locationId })
const position = new google.maps.LatLng({ lat, lng })
const newMarker = marker.get(id) ?? new google.maps.marker.AdvancedMarkerElement()

// console.trace('adding new marker', name, position.toString())
newMarker.map = map
newMarker.position = position

Expand All @@ -55,7 +67,7 @@ export const useGoogleMapMarker = () => {
{slug ? (
<Link
variant={variant.Link.inlineUtil1}
onClick={() => router.push(getHref({ slug, locationId }))}
onClick={markerInfoBoxLinkClickHandler({ slug, locationId })}
>
<Title order={3}>{name}</Title>
</Link>
Expand All @@ -75,17 +87,38 @@ export const useGoogleMapMarker = () => {
marker.set(id, newMarker)
return newMarker
},
remove(markerId: string) {
// eslint-disable-next-line react-hooks/exhaustive-deps
[infoWindow, map, mapIsReady, markerInfoBoxLinkClickHandler]
)

const removeMarker = useCallback(
(markerId: string) => {
const markerItem = marker.get(markerId)
if (!markerItem) return false
if (!markerItem) {
return false
}
markerItem.map = null
google.maps.event.clearInstanceListeners(markerItem)
marker.remove(markerId)
return true
},
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
const markerFns = useMemo(
() => ({ get: getMarker, add: addMarker, remove: removeMarker }),
[addMarker, getMarker, removeMarker]
)
useWhyDidYouUpdate('useGoogleMapMarker', {
router,
infoWindow,
map,
mapIsReady,
clickHandler: markerInfoBoxLinkClickHandler,
})

return markerFns
}
interface AddMarkerParams {
id: string
lat: number
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/hooks/useGoogleMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const useGoogleMaps = (): UseGoogleMapsReturn => {
if (!context) {
throw new Error('useGoogleMaps must be used within a GoogleMapsProvider')
}
if (context.map && context.isReady && context.infoWindow) {
if (context.isReady) {
return {
map: context.map,
infoWindow: context.infoWindow,
Expand Down
88 changes: 64 additions & 24 deletions packages/ui/providers/GoogleMaps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ import { useEventEmitter, useMap } from 'ahooks'
import { type EventEmitter } from 'ahooks/lib/useEventEmitter'
import { createContext, type ReactNode, useEffect, useMemo, useRef, useState } from 'react'

export const GoogleMapContext = createContext<GoogleMapContextValue | null>(null)
export const GoogleMapContext = createContext<GoogleMapContext | null>(null)
GoogleMapContext.displayName = 'GoogleMapContext'

export const GoogleMapsProvider = ({ children }: { children: ReactNode }) => {
const [map, setMap] = useState<google.maps.Map>()
const [infoWindow, setInfoWindow] = useState<google.maps.InfoWindow>()
const [isReady, setIsReady] = useState(false)
const [markers, marker] = useMap<string, google.maps.marker.AdvancedMarkerElement>()

const mapEvents = {
ready: useEventEmitter<boolean>(),
initialPropsSet: useEventEmitter<boolean>(),
}
const eventEmitter = useEventEmitter<boolean>()
const mapEvents = useMemo(
() => ({
ready: eventEmitter,
initialPropsSet: eventEmitter,
}),
[eventEmitter]
)
const mapEventRef = useRef<MapEvents>(mapEvents)
const initialCamera = useMemo(() => {
return {
Expand All @@ -32,33 +35,57 @@ export const GoogleMapsProvider = ({ children }: { children: ReactNode }) => {
useEffect(() => {
if (map && infoWindow) {
const infoListener = google.maps.event.addListener(infoWindow, 'closeclick', () => {
if (cameraRef.current.center) map.panTo(cameraRef.current.center)
if (cameraRef.current.center) {
map.panTo(cameraRef.current.center)
}
})
const mapListener = google.maps.event.addListener(map, 'click', () => {
infoWindow.close()
if (cameraRef.current.center) map.panTo(cameraRef.current.center)
if (cameraRef.current.center) {
map.panTo(cameraRef.current.center)
}
})
markers.forEach((marker) => (marker.map = map))
markers.forEach((singleMarker) => (singleMarker.map = map))

return () => {
google.maps.event.removeListener(infoListener)
google.maps.event.removeListener(mapListener)
}
}
return () => void 0
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [map, infoWindow])
const contextValue = {
map,
infoWindow,
setMap,
setInfoWindow,
isReady,
mapEvents,
camera: cameraRef.current,
marker,
markers,
}

const mapIsReady = typeof map !== 'undefined' && typeof infoWindow !== 'undefined'

const contextValue: ContextValue<typeof mapIsReady> = useMemo(
() =>
mapIsReady
? {
map,
infoWindow,
setMap,
setInfoWindow,
mapEvents,
marker,
markers,
isReady: true,
camera: cameraRef.current,
}
: {
setMap,
setInfoWindow,
mapEvents,
marker,
markers,
map: undefined,
infoWindow: undefined,
isReady: false,
camera: cameraRef.current,
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[isReady]
)
return <GoogleMapContext.Provider value={contextValue}>{children}</GoogleMapContext.Provider>
}

Expand All @@ -74,14 +101,27 @@ export interface MarkerState {
reset: () => void
}

interface GoogleMapContextValue {
map: google.maps.Map | undefined
infoWindow: google.maps.InfoWindow | undefined
interface GoogleMapContextBase {
setMap: (map: google.maps.Map) => void
setInfoWindow: (infoWindow: google.maps.InfoWindow) => void
isReady: boolean
mapEvents: MapEvents
camera: google.maps.CameraOptions
marker: MarkerState
markers: Map<string, google.maps.marker.AdvancedMarkerElement>
}

interface GoogleMapReadyContext extends GoogleMapContextBase {
map: google.maps.Map
infoWindow: google.maps.InfoWindow
isReady: true
}
interface GoogleMapNotReadyContext extends GoogleMapContextBase {
map: undefined
infoWindow: undefined
isReady: false
}

type GoogleMapContext = GoogleMapReadyContext | GoogleMapNotReadyContext
type ContextValue<TReady extends boolean> = TReady extends true
? GoogleMapReadyContext
: GoogleMapNotReadyContext
Loading

0 comments on commit 3017ebf

Please sign in to comment.