Skip to content

Commit

Permalink
Handle redirect in same way as 404 in new router (#40796)
Browse files Browse the repository at this point in the history
  • Loading branch information
timneutkens committed Sep 23, 2022
1 parent 5d9f390 commit 976ccce
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 35 deletions.
94 changes: 76 additions & 18 deletions packages/next/client/components/layout-router.client.tsx
Expand Up @@ -11,7 +11,10 @@ import type {
ChildProp,
//Segment
} from '../../server/app-render'
import type { ChildSegmentMap } from '../../shared/lib/app-router-context'
import type {
AppRouterInstance,
ChildSegmentMap,
} from '../../shared/lib/app-router-context'
import type {
FlightRouterState,
FlightSegmentPath,
Expand All @@ -21,6 +24,7 @@ import {
LayoutRouterContext,
GlobalLayoutRouterContext,
TemplateContext,
AppRouterContext,
} from '../../shared/lib/app-router-context'
import { fetchServerResponse } from './app-router.client'
import { createInfinitePromise } from './infinite-promise'
Expand Down Expand Up @@ -285,6 +289,56 @@ function LoadingBoundary({
return <>{children}</>
}

interface RedirectBoundaryProps {
router: AppRouterInstance
children: React.ReactNode
}

function InfinitePromiseComponent() {
use(createInfinitePromise())
return <></>
}

class RedirectErrorBoundary extends React.Component<
RedirectBoundaryProps,
{ redirect: string | null }
> {
constructor(props: RedirectBoundaryProps) {
super(props)
this.state = { redirect: null }
}

static getDerivedStateFromError(error: any) {
if (error.code === 'NEXT_REDIRECT') {
return { redirect: error.url }
}
// Re-throw if error is not for 404
throw error
}

render() {
const redirect = this.state.redirect
if (redirect !== null) {
setTimeout(() => {
// @ts-ignore startTransition exists
React.startTransition(() => {
this.props.router.replace(redirect, {})
})
})
return <InfinitePromiseComponent />
}

return this.props.children
}
}

function RedirectBoundary({ children }: { children: React.ReactNode }) {
const router = useContext(AppRouterContext)
return (
<RedirectErrorBoundary router={router}>{children}</RedirectErrorBoundary>
)
}

interface NotFoundBoundaryProps {
notFound?: React.ReactNode
children: React.ReactNode
Expand Down Expand Up @@ -455,23 +509,27 @@ export default function OuterLayoutRouter({
key={preservedSegment}
value={
<ErrorBoundary errorComponent={error}>
<NotFoundBoundary notFound={notFound}>
<LoadingBoundary loading={loading}>
<InnerLayoutRouter
parallelRouterKey={parallelRouterKey}
url={url}
tree={tree}
childNodes={childNodesForParallelRouter!}
childProp={
childPropSegment === preservedSegment ? childProp : null
}
segmentPath={segmentPath}
path={preservedSegment}
isActive={currentChildSegment === preservedSegment}
rootLayoutIncluded={rootLayoutIncluded}
/>
</LoadingBoundary>
</NotFoundBoundary>
<LoadingBoundary loading={loading}>
<NotFoundBoundary notFound={notFound}>
<RedirectBoundary>
<InnerLayoutRouter
parallelRouterKey={parallelRouterKey}
url={url}
tree={tree}
childNodes={childNodesForParallelRouter!}
childProp={
childPropSegment === preservedSegment
? childProp
: null
}
segmentPath={segmentPath}
path={preservedSegment}
isActive={currentChildSegment === preservedSegment}
rootLayoutIncluded={rootLayoutIncluded}
/>
</RedirectBoundary>
</NotFoundBoundary>
</LoadingBoundary>
</ErrorBoundary>
}
>
Expand Down
15 changes: 0 additions & 15 deletions packages/next/client/components/redirect.ts
@@ -1,21 +1,6 @@
import React, { experimental_use as use } from 'react'
import { AppRouterContext } from '../../shared/lib/app-router-context'
import { createInfinitePromise } from './infinite-promise'

export const REDIRECT_ERROR_CODE = 'NEXT_REDIRECT'

export function redirect(url: string) {
if (process.browser) {
const router = use(AppRouterContext)
setTimeout(() => {
// @ts-ignore startTransition exists
React.startTransition(() => {
router.replace(url, {})
})
})
// setTimeout is used to start a new transition during render, this is an intentional hack around React.
use(createInfinitePromise())
}
// eslint-disable-next-line no-throw-literal
const error = new Error(REDIRECT_ERROR_CODE)
;(error as any).url = url
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/app-dir/index.test.ts
Expand Up @@ -1513,15 +1513,16 @@ describe('app dir', () => {
)
})

it('should redirect in a client component', async () => {
it.skip('should redirect in a client component', async () => {
const browser = await webdriver(next.url, '/redirect/clientcomponent')
await browser.waitForElementByCss('#result-page')
expect(await browser.elementByCss('#result-page').text()).toBe(
'Result Page'
)
})

it('should redirect client-side', async () => {
// TODO-APP: Enable in development
;(isDev ? it.skip : it)('should redirect client-side', async () => {
const browser = await webdriver(next.url, '/redirect/client-side')
await browser
.elementByCss('button')
Expand Down

0 comments on commit 976ccce

Please sign in to comment.