Skip to content

Commit

Permalink
more work
Browse files Browse the repository at this point in the history
  • Loading branch information
flybayer committed Oct 18, 2021
1 parent cb2e726 commit 6581428
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 224 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import {getPublicDataStore, useAuthorizeIf, useSession} from "next/data-client"
import {BlitzProvider} from "next/data-client"
import {formatWithValidation} from "next/dist/shared/lib/utils"
import {Head} from "next/head"
import {RedirectError} from "next/stdlib"
import {AppProps, BlitzPage} from "next/types"
import React, {ComponentPropsWithoutRef, useEffect} from "react"
import SuperJSON from "superjson"
const debug = require("debug")("blitz:approot")
import {
getPublicDataStore,
useAuthorizeIf,
useSession,
BlitzProvider,
} from '../data-client/index'
import { formatWithValidation } from '../shared/lib/utils'
import { Head } from '../shared/lib/head'
import { RedirectError } from './errors'
import { AppProps, BlitzPage } from '../types/index'
import React, { ComponentPropsWithoutRef, useEffect } from 'react'
import SuperJSON from 'superjson'
const debug = require('debug')('blitz:approot')

const customCSS = `
body::before {
Expand Down Expand Up @@ -34,15 +38,18 @@ const noscriptCSS = `
const NoPageFlicker = () => {
return (
<Head>
<style dangerouslySetInnerHTML={{__html: customCSS}} />
<style dangerouslySetInnerHTML={{ __html: customCSS }} />
<noscript>
<style dangerouslySetInnerHTML={{__html: noscriptCSS}} />
<style dangerouslySetInnerHTML={{ __html: noscriptCSS }} />
</noscript>
</Head>
)
}

function getAuthValues(Page: BlitzPage, props: ComponentPropsWithoutRef<BlitzPage>) {
function getAuthValues(
Page: BlitzPage,
props: ComponentPropsWithoutRef<BlitzPage>
) {
let authenticate = Page.authenticate
let redirectAuthenticatedTo = Page.redirectAuthenticatedTo

Expand All @@ -54,7 +61,10 @@ function getAuthValues(Page: BlitzPage, props: ComponentPropsWithoutRef<BlitzPag
while (true) {
const type = layout.type

if (type.authenticate !== undefined || type.redirectAuthenticatedTo !== undefined) {
if (
type.authenticate !== undefined ||
type.redirectAuthenticatedTo !== undefined
) {
authenticate = type.authenticate
redirectAuthenticatedTo = type.redirectAuthenticatedTo
break
Expand All @@ -69,51 +79,57 @@ function getAuthValues(Page: BlitzPage, props: ComponentPropsWithoutRef<BlitzPag
}
}

return {authenticate, redirectAuthenticatedTo}
return { authenticate, redirectAuthenticatedTo }
}

export function withBlitzInnerWrapper(Page: BlitzPage) {
const BlitzInnerRoot = (props: ComponentPropsWithoutRef<BlitzPage>) => {
// We call useSession so this will rerender anytime session changes
useSession({suspense: false})
useSession({ suspense: false })

let {authenticate, redirectAuthenticatedTo} = getAuthValues(Page, props)
let { authenticate, redirectAuthenticatedTo } = getAuthValues(Page, props)

useAuthorizeIf(authenticate === true)

if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
const publicData = getPublicDataStore().getData()
// We read directly from publicData.userId instead of useSession
// so we can access userId on first render. useSession is always empty on first render
if (publicData.userId) {
debug("[BlitzInnerRoot] logged in")
debug('[BlitzInnerRoot] logged in')

if (typeof redirectAuthenticatedTo === "function") {
redirectAuthenticatedTo = redirectAuthenticatedTo({session: publicData})
if (typeof redirectAuthenticatedTo === 'function') {
redirectAuthenticatedTo = redirectAuthenticatedTo({
session: publicData,
})
}

if (redirectAuthenticatedTo) {
const redirectUrl =
typeof redirectAuthenticatedTo === "string"
typeof redirectAuthenticatedTo === 'string'
? redirectAuthenticatedTo
: formatWithValidation(redirectAuthenticatedTo)

debug("[BlitzInnerRoot] redirecting to", redirectUrl)
debug('[BlitzInnerRoot] redirecting to', redirectUrl)
const error = new RedirectError(redirectUrl)
error.stack = null!
throw error
}
} else {
debug("[BlitzInnerRoot] logged out")
if (authenticate && typeof authenticate === "object" && authenticate.redirectTo) {
let {redirectTo} = authenticate
if (typeof redirectTo !== "string") {
debug('[BlitzInnerRoot] logged out')
if (
authenticate &&
typeof authenticate === 'object' &&
authenticate.redirectTo
) {
let { redirectTo } = authenticate
if (typeof redirectTo !== 'string') {
redirectTo = formatWithValidation(redirectTo)
}

const url = new URL(redirectTo, window.location.href)
url.searchParams.append("next", window.location.pathname)
debug("[BlitzInnerRoot] redirecting to", url.toString())
url.searchParams.append('next', window.location.pathname)
debug('[BlitzInnerRoot] redirecting to', url.toString())
const error = new RedirectError(url.toString())
error.stack = null!
throw error
Expand All @@ -126,33 +142,39 @@ export function withBlitzInnerWrapper(Page: BlitzPage) {
for (let [key, value] of Object.entries(Page)) {
;(BlitzInnerRoot as any)[key] = value
}
if (process.env.NODE_ENV !== "production") {
if (process.env.NODE_ENV !== 'production') {
BlitzInnerRoot.displayName = `BlitzInnerRoot`
}
return BlitzInnerRoot
}

export function withBlitzAppRoot(UserAppRoot: React.ComponentType<any>) {
const BlitzOuterRoot = (props: AppProps) => {
const component = React.useMemo(() => withBlitzInnerWrapper(props.Component), [props.Component])
const component = React.useMemo(
() => withBlitzInnerWrapper(props.Component),
[props.Component]
)

const {authenticate, redirectAuthenticatedTo} = getAuthValues(props.Component, props.pageProps)
const { authenticate, redirectAuthenticatedTo } = getAuthValues(
props.Component,
props.pageProps
)

const noPageFlicker =
props.Component.suppressFirstRenderFlicker ||
authenticate !== undefined ||
redirectAuthenticatedTo

useEffect(() => {
document.documentElement.classList.add("blitz-first-render-complete")
document.documentElement.classList.add('blitz-first-render-complete')
}, [])

let {dehydratedState, _superjson} = props.pageProps
let { dehydratedState, _superjson } = props.pageProps
if (dehydratedState && _superjson) {
const deserializedProps = SuperJSON.deserialize({
json: {dehydratedState},
json: { dehydratedState },
meta: _superjson,
}) as {dehydratedState: any}
}) as { dehydratedState: any }
dehydratedState = deserializedProps?.dehydratedState
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import {Router} from "next/router"
import {RedirectError} from "next/stdlib"
import * as React from "react"
import {RouterContext} from "./router"
const debug = require("debug")("blitz:errorboundary")
import { Router } from '../client/router'
import { RedirectError } from './errors'
import * as React from 'react'
import { RouterContext } from '../shared/lib/router-context'
const debug = require('debug')('blitz:errorboundary')

const changedArray = (a: Array<unknown> = [], b: Array<unknown> = []) =>
//eslint-disable-next-line es5/no-es6-static-methods
a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]))

interface FallbackProps {
error: Error
interface ErrorFallbackProps {
error: Error & Record<any, any>
resetErrorBoundary: (...args: Array<unknown>) => void
}

interface ErrorBoundaryPropsWithComponent {
onResetKeysChange?: (
prevResetKeys: Array<unknown> | undefined,
resetKeys: Array<unknown> | undefined,
resetKeys: Array<unknown> | undefined
) => void
onReset?: (...args: Array<unknown>) => void
onError?: (error: Error, info: {componentStack: string}) => void
onError?: (error: Error, info: { componentStack: string }) => void
resetKeys?: Array<unknown>
fallback?: never
FallbackComponent: React.ComponentType<FallbackProps>
FallbackComponent: React.ComponentType<ErrorFallbackProps>
fallbackRender?: never
}

declare function FallbackRender(
props: FallbackProps,
): React.ReactElement<unknown, string | React.FunctionComponent | typeof React.Component> | null
props: ErrorFallbackProps
): React.ReactElement<
unknown,
string | React.FunctionComponent | typeof React.Component
> | null

interface ErrorBoundaryPropsWithRender {
onResetKeysChange?: (
prevResetKeys: Array<unknown> | undefined,
resetKeys: Array<unknown> | undefined,
resetKeys: Array<unknown> | undefined
) => void
onReset?: (...args: Array<unknown>) => void
onError?: (error: Error, info: {componentStack: string}) => void
onError?: (error: Error, info: { componentStack: string }) => void
resetKeys?: Array<unknown>
fallback?: never
FallbackComponent?: never
Expand All @@ -46,10 +48,10 @@ interface ErrorBoundaryPropsWithRender {
interface ErrorBoundaryPropsWithFallback {
onResetKeysChange?: (
prevResetKeys: Array<unknown> | undefined,
resetKeys: Array<unknown> | undefined,
resetKeys: Array<unknown> | undefined
) => void
onReset?: (...args: Array<unknown>) => void
onError?: (error: Error, info: {componentStack: string}) => void
onError?: (error: Error, info: { componentStack: string }) => void
resetKeys?: Array<unknown>
fallback: React.ReactElement<
unknown,
Expand All @@ -64,9 +66,9 @@ type ErrorBoundaryProps =
| ErrorBoundaryPropsWithComponent
| ErrorBoundaryPropsWithRender

type ErrorBoundaryState = {error: Error | null}
type ErrorBoundaryState = { error: Error | null }

const initialState: ErrorBoundaryState = {error: null}
const initialState: ErrorBoundaryState = { error: null }

class ErrorBoundary extends React.Component<
React.PropsWithRef<React.PropsWithChildren<ErrorBoundaryProps>>,
Expand All @@ -75,7 +77,7 @@ class ErrorBoundary extends React.Component<
static contextType = RouterContext

static getDerivedStateFromError(error: Error) {
return {error}
return { error }
}

state = initialState
Expand All @@ -92,37 +94,43 @@ class ErrorBoundary extends React.Component<

async componentDidCatch(error: Error, info: React.ErrorInfo) {
if (error instanceof RedirectError) {
debug("Redirecting from ErrorBoundary to", error.url)
debug('Redirecting from ErrorBoundary to', error.url)
await (this.context as Router)?.push(error.url)
return
}
this.props.onError?.(error, info)
}

componentDidMount() {
const {error} = this.state
const { error } = this.state

if (error !== null) {
this.updatedWithError = true
}

// Automatically reset on route change
;(this.context as Router)?.events?.on("routeChangeComplete", this.handleRouteChange)
;(this.context as Router)?.events?.on(
'routeChangeComplete',
this.handleRouteChange
)
}

handleRouteChange = () => {
debug("Resetting error boundary on route change")
debug('Resetting error boundary on route change')
this.props.onReset?.()
this.reset()
}

componentWillUnmount() {
;(this.context as Router)?.events?.off("routeChangeComplete", this.handleRouteChange)
;(this.context as Router)?.events?.off(
'routeChangeComplete',
this.handleRouteChange
)
}

componentDidUpdate(prevProps: ErrorBoundaryProps) {
const {error} = this.state
const {resetKeys} = this.props
const { error } = this.state
const { resetKeys } = this.props

// There's an edge case where if the thing that triggered the error
// happens to *also* be in the resetKeys array, we'd end up resetting
Expand All @@ -142,9 +150,9 @@ class ErrorBoundary extends React.Component<
}

render() {
const {error} = this.state
const { error } = this.state

const {fallbackRender, FallbackComponent, fallback} = this.props
const { fallbackRender, FallbackComponent, fallback } = this.props

if (error !== null) {
const props = {
Expand All @@ -156,13 +164,13 @@ class ErrorBoundary extends React.Component<
return null
} else if (React.isValidElement(fallback)) {
return fallback
} else if (typeof fallbackRender === "function") {
} else if (typeof fallbackRender === 'function') {
return fallbackRender(props)
} else if (FallbackComponent) {
return <FallbackComponent {...props} />
} else {
throw new Error(
"<ErrorBoundary> requires either a fallback, fallbackRender, or FallbackComponent prop",
'<ErrorBoundary> requires either a fallback, fallbackRender, or FallbackComponent prop'
)
}
}
Expand All @@ -173,7 +181,7 @@ class ErrorBoundary extends React.Component<

function withErrorBoundary<P>(
Component: React.ComponentType<P>,
errorBoundaryProps: ErrorBoundaryProps,
errorBoundaryProps: ErrorBoundaryProps
): React.ComponentType<P> {
const Wrapped: React.ComponentType<P> = (props) => {
return (
Expand All @@ -184,7 +192,7 @@ function withErrorBoundary<P>(
}

// Format for display in DevTools
const name = Component.displayName || Component.name || "Unknown"
const name = Component.displayName || Component.name || 'Unknown'
Wrapped.displayName = `withErrorBoundary(${name})`

return Wrapped
Expand All @@ -197,9 +205,9 @@ function useErrorHandler(givenError?: unknown): (error: unknown) => void {
return setError
}

export {ErrorBoundary, withErrorBoundary, useErrorHandler}
export { ErrorBoundary, withErrorBoundary, useErrorHandler }
export type {
FallbackProps,
ErrorFallbackProps,
ErrorBoundaryPropsWithComponent,
ErrorBoundaryPropsWithRender,
ErrorBoundaryPropsWithFallback,
Expand Down
2 changes: 2 additions & 0 deletions nextjs/packages/next/stdlib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export { Routes } from '.blitz'
export * from './errors'
export * from './zod-utils'
export * from './prisma-utils'
export { withBlitzAppRoot } from './blitz-app-root'

export const isServer = typeof window === 'undefined'
export const isClient = typeof window !== 'undefined'
Expand Down
Loading

0 comments on commit 6581428

Please sign in to comment.