Skip to content

Commit

Permalink
Move redirect and notFound to navigation exports (#41703)
Browse files Browse the repository at this point in the history
* Move `notFound` and `redirect` to `next/navigation`
* Mark createContext related hooks as client components
  • Loading branch information
huozhi committed Oct 24, 2022
1 parent 2d9e17c commit a9d1452
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 86 deletions.
@@ -1,22 +1,12 @@
// useLayoutSegments() // Only the segments for the current place. ['children', 'dashboard', 'children', 'integrations'] -> /dashboard/integrations (/dashboard/layout.js would get ['children', 'dashboard', 'children', 'integrations'])
'use client'

import type { FlightRouterState } from '../../server/app-render'
import { useContext, useMemo } from 'react'
import {
SearchParamsContext,
// ParamsContext,
PathnameContext,
// LayoutSegmentsContext,
} from './hooks-client-context'
import {
AppRouterContext,
LayoutRouterContext,
} from '../../shared/lib/app-router-context'

export {
ServerInsertedHTMLContext,
useServerInsertedHTML,
} from '../../shared/lib/server-inserted-html'
} from '../hooks-client-context'

const INTERNAL_URLSEARCHPARAMS_INSTANCE = Symbol(
'internal for urlsearchparams readonly'
Expand Down Expand Up @@ -81,85 +71,24 @@ export function useSearchParams() {
return readonlySearchParams
}

// TODO-APP: Move the other router context over to this one
/**
* Get the router methods. For example router.push('/dashboard')
* Get the current pathname. For example usePathname() on /dashboard?foo=bar would return "/dashboard"
*/
export function useRouter(): import('../../shared/lib/app-router-context').AppRouterInstance {
return useContext(AppRouterContext)
export function usePathname(): string {
return useContext(PathnameContext)
}

// TODO-APP: getting all params when client-side navigating is non-trivial as it does not have route matchers so this might have to be a server context instead.
// export function useParams() {
// return useContext(ParamsContext)
// }

/**
* Get the current pathname. For example usePathname() on /dashboard?foo=bar would return "/dashboard"
*/
export function usePathname(): string {
return useContext(PathnameContext)
}

// TODO-APP: define what should be provided through context.
// export function useLayoutSegments() {
// return useContext(LayoutSegmentsContext)
// }

// TODO-APP: handle parallel routes
function getSelectedLayoutSegmentPath(
tree: FlightRouterState,
parallelRouteKey: string,
first = true,
segmentPath: string[] = []
): string[] {
let node: FlightRouterState
if (first) {
// Use the provided parallel route key on the first parallel route
node = tree[1][parallelRouteKey]
} else {
// After first parallel route prefer children, if there's no children pick the first parallel route.
const parallelRoutes = tree[1]
node = parallelRoutes.children ?? Object.values(parallelRoutes)[0]
}

if (!node) return segmentPath
const segment = node[0]
const segmentValue = Array.isArray(segment) ? segment[1] : segment
if (!segmentValue) return segmentPath

segmentPath.push(segmentValue)

return getSelectedLayoutSegmentPath(
node,
parallelRouteKey,
false,
segmentPath
)
}

// TODO-APP: Expand description when the docs are written for it.
/**
* Get the canonical segment path from the current level to the leaf node.
*/
export function useSelectedLayoutSegments(
parallelRouteKey: string = 'children'
): string[] {
const { tree } = useContext(LayoutRouterContext)
return getSelectedLayoutSegmentPath(tree, parallelRouteKey)
}

// TODO-APP: Expand description when the docs are written for it.
/**
* Get the segment below the current level
*/
export function useSelectedLayoutSegment(
parallelRouteKey: string = 'children'
): string {
const selectedLayoutSegments = useSelectedLayoutSegments(parallelRouteKey)
if (selectedLayoutSegments.length === 0) {
throw new Error('No selected layout segment below the current level')
}

return selectedLayoutSegments[0]
}
export {
ServerInsertedHTMLContext,
useServerInsertedHTML,
} from '../../../shared/lib/server-inserted-html'
85 changes: 85 additions & 0 deletions packages/next/client/components/navigation/index.ts
@@ -0,0 +1,85 @@
// useLayoutSegments() // Only the segments for the current place. ['children', 'dashboard', 'children', 'integrations'] -> /dashboard/integrations (/dashboard/layout.js would get ['children', 'dashboard', 'children', 'integrations'])

import type { FlightRouterState } from '../../../server/app-render'
import { useContext } from 'react'

import {
AppRouterContext,
LayoutRouterContext,
} from '../../../shared/lib/app-router-context'

// TODO-APP: Move the other router context over to this one
/**
* Get the router methods. For example router.push('/dashboard')
*/
export function useRouter(): import('../../../shared/lib/app-router-context').AppRouterInstance {
return useContext(AppRouterContext)
}

// TODO-APP: handle parallel routes
function getSelectedLayoutSegmentPath(
tree: FlightRouterState,
parallelRouteKey: string,
first = true,
segmentPath: string[] = []
): string[] {
let node: FlightRouterState
if (first) {
// Use the provided parallel route key on the first parallel route
node = tree[1][parallelRouteKey]
} else {
// After first parallel route prefer children, if there's no children pick the first parallel route.
const parallelRoutes = tree[1]
node = parallelRoutes.children ?? Object.values(parallelRoutes)[0]
}

if (!node) return segmentPath
const segment = node[0]
const segmentValue = Array.isArray(segment) ? segment[1] : segment
if (!segmentValue) return segmentPath

segmentPath.push(segmentValue)

return getSelectedLayoutSegmentPath(
node,
parallelRouteKey,
false,
segmentPath
)
}

// TODO-APP: Expand description when the docs are written for it.
/**
* Get the canonical segment path from the current level to the leaf node.
*/
export function useSelectedLayoutSegments(
parallelRouteKey: string = 'children'
): string[] {
const { tree } = useContext(LayoutRouterContext)
return getSelectedLayoutSegmentPath(tree, parallelRouteKey)
}

// TODO-APP: Expand description when the docs are written for it.
/**
* Get the segment below the current level
*/
export function useSelectedLayoutSegment(
parallelRouteKey: string = 'children'
): string {
const selectedLayoutSegments = useSelectedLayoutSegments(parallelRouteKey)
if (selectedLayoutSegments.length === 0) {
throw new Error('No selected layout segment below the current level')
}

return selectedLayoutSegments[0]
}

export { redirect } from '../redirect'
export { notFound } from '../not-found'

export {
useSearchParams,
usePathname,
ServerInsertedHTMLContext,
useServerInsertedHTML,
} from './client'
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app/app/not-found/client-side/page.js
@@ -1,6 +1,6 @@
'use client'

import { notFound } from 'next/dist/client/components/not-found'
import { notFound } from 'next/navigation'
import React from 'react'

export default function Page() {
Expand Down
@@ -1,5 +1,5 @@
'use client'
import { notFound } from 'next/dist/client/components/not-found'
import { notFound } from 'next/navigation'

export default function ClientComp() {
notFound()
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app/app/not-found/servercomponent/page.js
@@ -1,5 +1,5 @@
// TODO-APP: enable when flight error serialization is implemented
import { notFound } from 'next/dist/client/components/not-found'
import { notFound } from 'next/navigation'

export default function Page() {
notFound()
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app/app/redirect/client-side/page.js
@@ -1,6 +1,6 @@
'use client'

import { redirect } from 'next/dist/client/components/redirect'
import { redirect } from 'next/navigation'
import React from 'react'

export default function Page() {
Expand Down
@@ -1,5 +1,5 @@
'use client'
import { redirect } from 'next/dist/client/components/redirect'
import { redirect } from 'next/navigation'

export default function ClientComp() {
redirect('/redirect/result')
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app/app/redirect/servercomponent/page.js
@@ -1,4 +1,4 @@
import { redirect } from 'next/dist/client/components/redirect'
import { redirect } from 'next/navigation'

export default function Page() {
redirect('/redirect/result')
Expand Down

0 comments on commit a9d1452

Please sign in to comment.