Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hybrid App Hooks Support #41767

Merged
merged 14 commits into from Nov 1, 2022
Merged
7 changes: 5 additions & 2 deletions .eslintrc.json
Expand Up @@ -238,7 +238,6 @@
"no-obj-calls": "warn",
"no-octal": "warn",
"no-octal-escape": "warn",
"no-redeclare": ["warn", { "builtinGlobals": false }],
"no-regex-spaces": "warn",
"no-restricted-syntax": [
"warn",
Expand Down Expand Up @@ -330,6 +329,10 @@
"react/style-prop-object": "warn",
"react-hooks/rules-of-hooks": "error",
// "@typescript-eslint/non-nullable-type-assertion-style": "warn",
"@typescript-eslint/prefer-as-const": "warn"
"@typescript-eslint/prefer-as-const": "warn",
"@typescript-eslint/no-redeclare": [
"warn",
{ "builtinGlobals": false, "ignoreDeclarationMerge": true }
]
}
}
2 changes: 1 addition & 1 deletion packages/next/client/components/app-router.tsx
Expand Up @@ -26,7 +26,7 @@ import {
// ParamsContext,
PathnameContext,
// LayoutSegmentsContext,
} from './hooks-client-context'
} from '../../shared/lib/hooks-client-context'
import { useReducerWithReduxDevtools } from './use-reducer-with-devtools'
import { ErrorBoundary, GlobalErrorComponent } from './error-boundary'

Expand Down
25 changes: 16 additions & 9 deletions packages/next/client/components/layout-router.tsx
Expand Up @@ -19,12 +19,12 @@ import {
LayoutRouterContext,
GlobalLayoutRouterContext,
TemplateContext,
AppRouterContext,
} from '../../shared/lib/app-router-context'
import { fetchServerResponse } from './app-router'
import { createInfinitePromise } from './infinite-promise'
import { ErrorBoundary } from './error-boundary'
import { matchSegment } from './match-segments'
import { useRouter } from './navigation'

/**
* Add refetch marker to router state at the point of the current layout segment.
Expand Down Expand Up @@ -109,11 +109,13 @@ export function InnerLayoutRouter({
path: string
rootLayoutIncluded: boolean
}) {
const {
changeByServerResponse,
tree: fullTree,
focusAndScrollRef,
} = useContext(GlobalLayoutRouterContext)
const context = useContext(GlobalLayoutRouterContext)
if (!context) {
throw new Error('invariant global layout router not mounted')
}

const { changeByServerResponse, tree: fullTree, focusAndScrollRef } = context

const focusAndScrollElementRef = useRef<HTMLDivElement>(null)

useEffect(() => {
Expand Down Expand Up @@ -275,7 +277,7 @@ interface RedirectBoundaryProps {
}

function HandleRedirect({ redirect }: { redirect: string }) {
const router = useContext(AppRouterContext)
const router = useRouter()

useEffect(() => {
router.replace(redirect, {})
Expand Down Expand Up @@ -312,7 +314,7 @@ class RedirectErrorBoundary extends React.Component<
}

function RedirectBoundary({ children }: { children: React.ReactNode }) {
const router = useContext(AppRouterContext)
const router = useRouter()
return (
<RedirectErrorBoundary router={router}>{children}</RedirectErrorBoundary>
)
Expand Down Expand Up @@ -389,7 +391,12 @@ export default function OuterLayoutRouter({
notFound: React.ReactNode | undefined
rootLayoutIncluded: boolean
}) {
const { childNodes, tree, url } = useContext(LayoutRouterContext)
const context = useContext(LayoutRouterContext)
if (!context) {
throw new Error('invariant expected layout router to be mounted')
}

const { childNodes, tree, url } = context

// Get the current parallelRouter cache node
let childNodesForParallelRouter = childNodes.get(parallelRouterKey)
Expand Down
22 changes: 19 additions & 3 deletions packages/next/client/components/navigation.ts
Expand Up @@ -11,7 +11,7 @@ import {
// ParamsContext,
PathnameContext,
// LayoutSegmentsContext,
} from './hooks-client-context'
} from '../../shared/lib/hooks-client-context'
import { staticGenerationBailout } from './static-generation-bailout'

const INTERNAL_URLSEARCHPARAMS_INSTANCE = Symbol(
Expand Down Expand Up @@ -72,9 +72,15 @@ class ReadonlyURLSearchParams {
export function useSearchParams() {
staticGenerationBailout('useSearchParams')
const searchParams = useContext(SearchParamsContext)
if (!searchParams) {
throw new Error('invariant expected search params to be mounted')
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const readonlySearchParams = useMemo(() => {
return new ReadonlyURLSearchParams(searchParams)
}, [searchParams])

return readonlySearchParams
}

Expand All @@ -83,7 +89,12 @@ export function useSearchParams() {
*/
export function usePathname(): string {
staticGenerationBailout('usePathname')
return useContext(PathnameContext)
const pathname = useContext(PathnameContext)
if (pathname === null) {
throw new Error('invariant expected pathname to be mounted')
}

return pathname
}

// 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.
Expand All @@ -106,7 +117,12 @@ export {
* Get the router methods. For example router.push('/dashboard')
*/
export function useRouter(): import('../../shared/lib/app-router-context').AppRouterInstance {
return useContext(AppRouterContext)
const router = useContext(AppRouterContext)
if (router === null) {
throw new Error('invariant expected app router to be mounted')
}

return router
}

// TODO-APP: handle parallel routes
Expand Down
36 changes: 27 additions & 9 deletions packages/next/client/index.tsx
Expand Up @@ -34,6 +34,16 @@ import { ImageConfigContext } from '../shared/lib/image-config-context'
import { ImageConfigComplete } from '../shared/lib/image-config'
import { removeBasePath } from './remove-base-path'
import { hasBasePath } from './has-base-path'
import { AppRouterContext } from '../shared/lib/app-router-context'
import {
adaptForAppRouterInstance,
adaptForPathname,
adaptForSearchParams,
} from '../shared/lib/router/adapters'
import {
PathnameContext,
SearchParamsContext,
} from '../shared/lib/hooks-client-context'

const ReactDOM = process.env.__NEXT_REACT_ROOT
? require('react-dom/client')
Expand Down Expand Up @@ -306,15 +316,23 @@ function AppContainer({
)
}
>
<RouterContext.Provider value={makePublicRouterInstance(router)}>
<HeadManagerContext.Provider value={headManager}>
<ImageConfigContext.Provider
value={process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete}
>
{children}
</ImageConfigContext.Provider>
</HeadManagerContext.Provider>
</RouterContext.Provider>
<AppRouterContext.Provider value={adaptForAppRouterInstance(router)}>
<SearchParamsContext.Provider value={adaptForSearchParams(router)}>
<PathnameContext.Provider value={adaptForPathname(asPath)}>
<RouterContext.Provider value={makePublicRouterInstance(router)}>
<HeadManagerContext.Provider value={headManager}>
<ImageConfigContext.Provider
value={
process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
}
>
{children}
</ImageConfigContext.Provider>
</HeadManagerContext.Provider>
</RouterContext.Provider>
</PathnameContext.Provider>
</SearchParamsContext.Provider>
</AppRouterContext.Provider>
</Container>
)
}
Expand Down