Skip to content
This repository has been archived by the owner on Jan 2, 2025. It is now read-only.

Commit

Permalink
Avoid extra render on sidebar navigation change
Browse files Browse the repository at this point in the history
# Conflicts:
#	frontend/packages/app/utils/navigation-container.tsx
#	frontend/packages/app/utils/navigation.tsx
  • Loading branch information
ericvicenti committed Dec 9, 2023
1 parent 024dec6 commit d8f5b56
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 35 deletions.
28 changes: 15 additions & 13 deletions frontend/packages/app/app-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {GRPCClient} from '@mintter/shared'
import {TamaguiProvider, TamaguiProviderProps} from '@mintter/ui'
import {QueryClientProvider} from '@tanstack/react-query'
import {ReactQueryDevtools} from '@tanstack/react-query-devtools'
import {ReactNode, createContext, useContext, useEffect} from 'react'
import {ReactNode, createContext, useContext, useEffect, useMemo} from 'react'
import {AppIPC, Event, EventCallback} from './app-ipc'
import {useExperiments} from './models/experiments'
import {AppQueryClient} from './query-client'
Expand Down Expand Up @@ -44,21 +44,23 @@ export function AppContextProvider({
saveCidAsFile: (cid: string, name: string) => Promise<void>
darkMode: boolean
}) {
const appCtx = useMemo(
() => ({
// platform: 'win32', // to test from macOS
platform,
grpcClient,
queryClient,
ipc,
externalOpen,
windowUtils,
saveCidAsFile,
}),
[],
)
if (!queryClient)
throw new Error('queryClient is required for AppContextProvider')
return (
<AppContext.Provider
value={{
// platform: 'win32', // to test from macOS
platform,
grpcClient,
queryClient,
ipc,
externalOpen,
windowUtils,
saveCidAsFile,
}}
>
<AppContext.Provider value={appCtx}>
<QueryClientProvider client={queryClient.client}>
<StyleProvider darkMode={darkMode}>{children}</StyleProvider>
<ReactQueryTools />
Expand Down
36 changes: 20 additions & 16 deletions frontend/packages/app/utils/navigation-container.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {ReactNode, useEffect, useMemo, useReducer} from 'react'
import {writeableStateStream} from '@mintter/shared'
import {ReactNode, useEffect, useMemo} from 'react'
import {useIPC} from '../app-context'
import {useConfirmConnection} from '../components/contacts-prompt'
import {
DocumentsRoute,
NavAction,
NavContextProvider,
NavState,
navStateReducer,
Expand All @@ -24,43 +26,45 @@ export function NavigationContainer({
children: ReactNode
initialNav?: NavState
}) {
const [navState, dispatch] = useReducer(navStateReducer, initialNav)
const navigation = useMemo(() => {
const [updateNavState, navState] = writeableStateStream(initialNav)
return {
dispatch(action: NavAction) {
updateNavState(navStateReducer(navState.get(), action))
},
state: navState,
}
}, [])
const {send} = useIPC()

// const confirmConnection = useConfirmConnection()
useEffect(() => {
send('windowNavState', navState)
}, [navState, send])
return navigation.state.subscribe(() => {
send('windowNavState', navigation.state.get())
})
}, [navigation, send])

useEffect(() => {
// @ts-expect-error
return window.appWindowEvents?.subscribe((event: AppWindowEvent) => {
if (event === 'back') {
dispatch({type: 'pop'})
navigation.dispatch({type: 'pop'})
}
if (event === 'forward') {
dispatch({type: 'forward'})
navigation.dispatch({type: 'forward'})
}
})
}, [])

useEffect(() => {
setAppNavDispatch(dispatch)
setAppNavDispatch(navigation.dispatch)
return () => {
setAppNavDispatch(null)
}
}, [])

let value = useMemo(
() => ({
state: navState,
dispatch,
}),
[navState],
)

return (
<NavContextProvider value={value}>
<NavContextProvider value={navigation}>
{children}
<ConnectionConfirmer />
</NavContextProvider>
Expand Down
12 changes: 8 additions & 4 deletions frontend/packages/app/utils/navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {GRPCClient, unpackDocId} from '@mintter/shared'
import {GRPCClient, StateStream, unpackDocId} from '@mintter/shared'
import {
UnpackedHypermediaId,
createHmId,
unpackHmId,
} from '@mintter/shared/src/utils/entity-id-url'
import {useStream, useStreamSelector} from '@mintter/ui'
import {Buffer} from 'buffer'
import {createContext, useContext} from 'react'

Expand Down Expand Up @@ -91,7 +92,7 @@ export type NavState = {
lastAction: NavAction['type']
}
export type NavigationContext = {
state: NavState
state: StateStream<NavState>
dispatch: (action: NavAction) => void
}

Expand Down Expand Up @@ -207,14 +208,17 @@ export function useNavRoute() {
const nav = useContext(NavContext)
if (!nav)
throw new Error('useNavRoute must be used within a NavigationProvider')
return nav.state.routes[nav.state.routeIndex] || {key: 'documents'}
return useStreamSelector(
nav.state,
(state) => state.routes[state.routeIndex] || {key: 'documents'},
)
}

export function useNavigationState() {
const nav = useContext(NavContext)
if (!nav)
throw new Error('useNavigation must be used within a NavigationProvider')
return nav.state
return useStream(nav.state)
}

export function useNavigationDispatch() {
Expand Down
19 changes: 17 additions & 2 deletions frontend/packages/ui/src/use-stream.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import {useSyncExternalStore} from 'react'
import type {StateStream} from '@mintter/shared/src/utils/stream'
import {useEffect, useState, useSyncExternalStore} from 'react'

export function useStream<V>(stream: StateStream<V>): V {
export function useStream<StreamValue>(
stream: StateStream<StreamValue>,
): StreamValue {
return useSyncExternalStore(
(onStoreChange) => {
return stream.subscribe(onStoreChange)
},
() => stream.get(),
)
}

export function useStreamSelector<StreamValue, InternalValue>(
stream: StateStream<StreamValue>,
selector: (value: StreamValue) => InternalValue,
): InternalValue {
const [state, setState] = useState(selector(stream.get()))
useEffect(() => {
return stream.subscribe(() => {
setState(selector(stream.get()))
})
}, [stream, selector])
return state
}

0 comments on commit d8f5b56

Please sign in to comment.