Skip to content

Commit

Permalink
chore: refactor reset and reload
Browse files Browse the repository at this point in the history
  • Loading branch information
simonheys committed Jul 10, 2022
1 parent 68beaae commit 005a6a4
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 59 deletions.
9 changes: 9 additions & 0 deletions packages/extension/src/shared/utils/delay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Promise that will resolve after interval milliseconds
* @param ms - Delay in milliseconds
* @returns Promise that will resolve after the delay
*/

export const delay = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms))
}
37 changes: 20 additions & 17 deletions packages/extension/src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,32 @@ import { LoadingScreen } from "./features/actions/LoadingScreen"
import { useExtensionIsInTab } from "./features/browser/tabs"
import DevUI from "./features/dev/DevUI"
import { useTracking } from "./services/analytics"
import SoftReloadProvider from "./services/resetAndReload"
import { swrCacheProvider } from "./services/swr"
import { GlobalStyle, theme } from "./theme"

export const App: FC = () => {
useTracking()
const extensionIsInTab = useExtensionIsInTab()
return (
<SWRConfig value={{ provider: () => swrCacheProvider }}>
<ThemeProvider theme={theme}>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;600;700;900&display=swap"
rel="stylesheet"
/>
<GlobalStyle extensionIsInTab={extensionIsInTab} />
{process.env.SHOW_DEV_UI && <DevUI />}
<ErrorBoundary fallback={<AppErrorBoundaryFallback />}>
<Suspense fallback={<LoadingScreen />}>
<AppRoutes />
</Suspense>
</ErrorBoundary>
</ThemeProvider>
</SWRConfig>
<SoftReloadProvider>
<SWRConfig value={{ provider: () => swrCacheProvider }}>
<ThemeProvider theme={theme}>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;600;700;900&display=swap"
rel="stylesheet"
/>
<GlobalStyle extensionIsInTab={extensionIsInTab} />
{process.env.SHOW_DEV_UI && <DevUI />}
<ErrorBoundary fallback={<AppErrorBoundaryFallback />}>
<Suspense fallback={<LoadingScreen />}>
<AppRoutes />
</Suspense>
</ErrorBoundary>
</ThemeProvider>
</SWRConfig>
</SoftReloadProvider>
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { FC, useCallback, useMemo } from "react"
import { FC, useMemo } from "react"
import styled from "styled-components"
import browser from "webextension-polyfill"

import { useBackupRequired } from "../features/recovery/backupDownload.state"
import { useHardResetAndReload } from "../services/resetAndReload"
import { CopyTooltip } from "./CopyTooltip"
import { ErrorBoundaryState } from "./ErrorBoundary"
import {
Expand Down Expand Up @@ -91,10 +90,11 @@ export const coerceErrorToString = (error: any): string => {
const ErrorBoundaryFallbackWithCopyError: FC<
IErrorBoundaryFallbackWithCopyError
> = ({ error, errorInfo, message = "Sorry, an error occurred" }) => {
const hardResetAndReload = useHardResetAndReload()
const errorPayload = useMemo(() => {
try {
const displayError = coerceErrorToString(error)
const displayStack = errorInfo.componentStack || "No stack trace"
const displayStack = errorInfo?.componentStack || "No stack trace"
return `v${version}
${displayError}
Expand All @@ -106,28 +106,14 @@ ${displayStack}
return fallbackErrorPayload
}, [error, errorInfo])

const onReload = useCallback(() => {
const url = browser.runtime.getURL("index.html")

// reset cache
const backupState = useBackupRequired.getState()
localStorage.clear()
useBackupRequired.setState(backupState)

setTimeout(() => {
// ensure state got persisted before reloading
window.location.href = url
}, 100)
}, [])

return (
<MessageContainer>
<ErrorIcon />
<ErrorMessageContainer>
<P>{message}</P>
</ErrorMessageContainer>
<ActionsWrapper>
<ActionContainer onClick={onReload}>
<ActionContainer onClick={hardResetAndReload}>
<RefreshIcon />
<span>Retry</span>
</ActionContainer>
Expand Down
14 changes: 9 additions & 5 deletions packages/extension/src/ui/features/accountTokens/tokens.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const useTokensWithBalance = (
const {
data,
isValidating,
error: rawError,
error: maybeEmptyObjectError,
mutate,
} = useSWR(
// skip if no account selected
Expand All @@ -153,6 +153,10 @@ export const useTokensWithBalance = (
selectedAccount,
)

if (Math.random() < 0.5) {
throw "A random error"
}

return balances
},
{
Expand All @@ -174,19 +178,19 @@ export const useTokensWithBalance = (
*/

const error: any = useMemo(() => {
if (!rawError) {
if (!maybeEmptyObjectError) {
return
}
try {
if (JSON.stringify(rawError) === "{}") {
if (JSON.stringify(maybeEmptyObjectError) === "{}") {
console.warn("FIXME: Ignoring empty object {} error")
return
}
} catch (e) {
// ignore any stringify errors
}
return rawError
}, [rawError])
return maybeEmptyObjectError
}, [maybeEmptyObjectError])

const tokenDetailsIsInitialising = !error && !data && isValidating

Expand Down
30 changes: 12 additions & 18 deletions packages/extension/src/ui/features/dev/DevUI.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { FC, useCallback } from "react"
import { FC } from "react"
import styled from "styled-components"
import browser from "webextension-polyfill"

import { RowCentered } from "../../components/Row"
import { useBackupRequired } from "../recovery/backupDownload.state"
import {
useHardReload,
useResetCache,
useSoftReload,
} from "../../services/resetAndReload"

const Container = styled(RowCentered)`
position: fixed;
Expand All @@ -22,23 +25,14 @@ const DevButton = styled.div`
`

const DevUI: FC = () => {
const reset = useCallback(() => {
// reset cache
const backupState = useBackupRequired.getState()
localStorage.clear()
useBackupRequired.setState(backupState)
}, [])
const onReload = useCallback(() => {
const url = browser.runtime.getURL("index.html")
setTimeout(() => {
// ensure state got persisted before reloading
window.location.href = url
}, 100)
}, [])
const resetCache = useResetCache()
const softReload = useSoftReload()
const hardReload = useHardReload()
return (
<Container gap={"4px"}>
<DevButton onClick={reset}>Reset cache</DevButton>
<DevButton onClick={onReload}>Reload</DevButton>
<DevButton onClick={resetCache}>Reset cache</DevButton>
<DevButton onClick={softReload}>Reload UI</DevButton>
<DevButton onClick={hardReload}>Reload HTML</DevButton>
</Container>
)
}
Expand Down
79 changes: 79 additions & 0 deletions packages/extension/src/ui/services/resetAndReload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, {
FC,
ReactNode,
createContext,
useCallback,
useContext,
useState,
} from "react"
import browser from "webextension-polyfill"

import { delay } from "../../shared/utils/delay"
import { useAppState } from "../app.state"
import { useBackupRequired } from "../features/recovery/backupDownload.state"

export interface ISoftReloadContext {
key: string
softReload: () => void
}

export const SoftReloadContext = createContext<ISoftReloadContext | null>(null)

export const useSoftReloadContext = () =>
useContext(SoftReloadContext) as ISoftReloadContext

/** the key returned should be different each time, does not need to be a unique id */
const makeReloadKey = () => Date.now().toString()

const SoftReloadProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [key, setKey] = useState(makeReloadKey())

const softReload = useCallback(() => {
useAppState.setState({ isLoading: true, isFirstRender: true })
setKey(makeReloadKey())
}, [])

/** changing the key causes the children to re-render naturally */
return (
<SoftReloadContext.Provider value={{ key, softReload }}>
<React.Fragment key={key}>{children}</React.Fragment>
</SoftReloadContext.Provider>
)
}

export default SoftReloadProvider

/** re-render the component tree by changing the `key`, without re-loading the HTML page */
export const useSoftReload = () => {
const { softReload } = useSoftReloadContext()
return softReload
}

/** re-load the HTML page, can have some side-effects including localStorage+SWR state pollution */
export const useHardReload = () => {
return useCallback(() => {
const url = browser.runtime.getURL("index.html")
window.location.href = url
}, [])
}

/** reset local storage while preserving some UI state */
export const useResetCache = () => {
return useCallback(() => {
const backupState = useBackupRequired.getState()
localStorage.clear()
useBackupRequired.setState(backupState)
}, [])
}

/** reset and reload combo */
export const useHardResetAndReload = () => {
const resetCache = useResetCache()
const hardReload = useHardReload()
return useCallback(async () => {
resetCache()
// delay should allow state to persist
await delay(100)
hardReload()
}, [hardReload, resetCache])
}

0 comments on commit 005a6a4

Please sign in to comment.