Skip to content

Commit

Permalink
Rework persisted state to split individual contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
pfrazee committed Nov 7, 2023
1 parent 8b0a05c commit b6adb00
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 237 deletions.
26 changes: 10 additions & 16 deletions src/App.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ import {QueryClientProvider} from '@tanstack/react-query'

import 'view/icons'

import {
Schema,
Provider as PersistedStateProvider,
init as initPersistedState,
usePersisted,
} from '#/state/persisted'
import {init as initPersistedState} from '#/state/persisted'
import {useColorMode} from 'state/shell'
import {ThemeProvider} from 'lib/ThemeContext'
import {s} from 'lib/styles'
import {RootStoreModel, setupState, RootStoreProvider} from './state'
Expand All @@ -30,7 +26,7 @@ import {Provider as ShellStateProvider} from 'state/shell'
SplashScreen.preventAutoHideAsync()

const InnerApp = observer(function AppImpl() {
const persisted = usePersisted()
const colorMode = useColorMode()
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
undefined,
)
Expand All @@ -53,7 +49,7 @@ const InnerApp = observer(function AppImpl() {
}
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={persisted.colorMode}>
<ThemeProvider theme={colorMode}>
<RootSiblingParent>
<analytics.Provider>
<RootStoreProvider value={rootStore}>
Expand All @@ -70,22 +66,20 @@ const InnerApp = observer(function AppImpl() {
})

function App() {
const [persistedState, setPersistedState] = useState<Schema>()
const [isReady, setReady] = useState(false)

React.useEffect(() => {
initPersistedState().then(setPersistedState)
initPersistedState().then(() => setReady(true))
}, [])

if (!persistedState) {
if (!isReady) {
return null
}

return (
<PersistedStateProvider data={persistedState}>
<ShellStateProvider>
<InnerApp />
</ShellStateProvider>
</PersistedStateProvider>
<ShellStateProvider>
<InnerApp />
</ShellStateProvider>
)
}

Expand Down
26 changes: 10 additions & 16 deletions src/App.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ import {RootSiblingParent} from 'react-native-root-siblings'

import 'view/icons'

import {
Schema,
Provider as PersistedStateProvider,
init as initPersistedState,
usePersisted,
} from '#/state/persisted'
import {init as initPersistedState} from '#/state/persisted'
import {useColorMode} from 'state/shell'
import * as analytics from 'lib/analytics/analytics'
import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {Shell} from 'view/shell/index'
Expand All @@ -23,7 +19,7 @@ import {queryClient} from 'lib/react-query'
import {Provider as ShellStateProvider} from 'state/shell'

const InnerApp = observer(function AppImpl() {
const persisted = usePersisted()
const colorMode = useColorMode()
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
undefined,
)
Expand All @@ -43,7 +39,7 @@ const InnerApp = observer(function AppImpl() {

return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={persisted.colorMode}>
<ThemeProvider theme={colorMode}>
<RootSiblingParent>
<analytics.Provider>
<RootStoreProvider value={rootStore}>
Expand All @@ -60,22 +56,20 @@ const InnerApp = observer(function AppImpl() {
})

function App() {
const [persistedState, setPersistedState] = useState<Schema>()
const [isReady, setReady] = useState(false)

React.useEffect(() => {
initPersistedState().then(setPersistedState)
initPersistedState().then(() => setReady(true))
}, [])

if (!persistedState) {
if (!isReady) {
return null
}

return (
<PersistedStateProvider data={persistedState}>
<ShellStateProvider>
<InnerApp />
</ShellStateProvider>
</PersistedStateProvider>
<ShellStateProvider>
<InnerApp />
</ShellStateProvider>
)
}

Expand Down
32 changes: 0 additions & 32 deletions src/state/colorMode/index.ts

This file was deleted.

91 changes: 91 additions & 0 deletions src/state/persisted/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import EventEmitter from 'eventemitter3'
import {logger} from '#/logger'
import {defaults, Schema} from '#/state/persisted/schema'
import {migrate} from '#/state/persisted/legacy'
import * as store from '#/state/persisted/store'
import BroadcastChannel from '#/state/persisted/broadcast'

export type {Schema} from '#/state/persisted/schema'
export {defaults as schema} from '#/state/persisted/schema'

const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL')
const UPDATE_EVENT = 'BSKY_UPDATE'

let _state: Schema = defaults
const _emitter = new EventEmitter()

/**
* Initializes and returns persisted data state, so that it can be passed to
* the Provider.
*/
export async function init() {
logger.debug('persisted state: initializing')

broadcast.onmessage = onBroadcastMessage

try {
await migrate() // migrate old store
const stored = await store.read() // check for new store
if (!stored) await store.write(defaults) // opt: init new store
_state = stored || defaults // return new store
} catch (e) {
logger.error('persisted state: failed to load root state from storage', {
error: e,
})
// AsyncStorage failured, but we can still continue in memory
return defaults
}
}

export function get<K extends keyof Schema>(key: K): Schema[K] {
return _state[key]
}

export async function write<K extends keyof Schema>(
key: K,
value: Schema[K],
): Promise<void> {
try {
_state[key] = value
await store.write(_state)
// must happen on next tick, otherwise the tab will read stale storage data
setTimeout(() => broadcast.postMessage({event: UPDATE_EVENT}), 0)
logger.debug(`persisted state: wrote root state to storage`)
} catch (e) {
logger.error(`persisted state: failed writing root state to storage`, {
error: e,
})
}
}

export function onUpdate(cb: () => void): () => void {
_emitter.addListener('update', cb)
return () => _emitter.removeListener('update', cb)
}

async function onBroadcastMessage({data}: MessageEvent) {
// validate event
if (typeof data === 'object' && data.event === UPDATE_EVENT) {
try {
// read next state, possibly updated by another tab
const next = await store.read()

if (next) {
logger.debug(`persisted state: handling update from broadcast channel`)
_state = next
_emitter.emit('update')
} else {
logger.error(
`persisted state: handled update update from broadcast channel, but found no data`,
)
}
} catch (e) {
logger.error(
`persisted state: failed handling update from broadcast channel`,
{
error: e,
},
)
}
}
}
122 changes: 0 additions & 122 deletions src/state/persisted/index.tsx

This file was deleted.

Loading

0 comments on commit b6adb00

Please sign in to comment.