Skip to content

Commit c5593cc

Browse files
committed
feat: Implement React portability patterns
1 parent b76bce0 commit c5593cc

16 files changed

+177
-73
lines changed

apps/expo/app/ExpoRootLayout.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
import { Stack } from 'expo-router'
22
import UniversalAppProviders from '@app/core/screens/UniversalAppProviders'
33
import UniversalRootLayout from '@app/core/screens/UniversalRootLayout'
4+
import { Link as ExpoContextLink } from '@app/core/navigation/Link.core.native'
5+
import { useRouter as useExpoContextRouter } from '@app/core/navigation/useRouter.core.native'
6+
import { useRouteParams as useExpoRouteParams } from '@app/core/navigation/useRouteParams.core.native'
47

58
// -i- Expo Router's layout setup is much simpler than Next.js's layout setup
69
// -i- Since Expo doesn't require a custom document setup or server component root layout
710
// -i- Use this file to apply your Expo specific layout setup:
811
// -i- like rendering our Universal Layout and App Providers
912

13+
/* --- <ExpoRootLayout/> ----------------------------------------------------------------------- */
14+
1015
export default function ExpoRootLayout() {
16+
// Navigation
17+
const expoContextRouter = useExpoContextRouter()
18+
19+
// -- Render --
20+
1121
return (
12-
<UniversalAppProviders>
22+
<UniversalAppProviders
23+
contextLink={ExpoContextLink}
24+
contextRouter={expoContextRouter}
25+
useContextRouteParams={useExpoRouteParams}
26+
>
1327
<UniversalRootLayout>
1428
<Stack
1529
screenOptions={{

apps/next/app/NextClientRootLayout.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
'use client'
22
import React from 'react'
33
import UniversalAppProviders from '@app/core/screens/UniversalAppProviders'
4+
import { Link as NextContextLink } from '@app/core/navigation/Link.core.web'
5+
import { useRouter as useNextContextRouter } from '@app/core/navigation/useRouter.core.web'
6+
import { useRouteParams as useNextRouteParams } from '@app/core/navigation/useRouteParams.core.web'
47

58
// -i- This is a regular react client component
69
// -i- It's still rendered on the server during SSR, but it also hydrates on the client
@@ -15,11 +18,22 @@ type NextClientRootLayoutProps = {
1518

1619
/* --- <NextClientRootLayout/> ---------------------------------------------------------------- */
1720

18-
const NextClientRootLayout = ({ children }: NextClientRootLayoutProps) => (
19-
<UniversalAppProviders>
20-
{children}
21-
</UniversalAppProviders>
22-
)
21+
const NextClientRootLayout = ({ children }: NextClientRootLayoutProps) => {
22+
// Navigation
23+
const nextContextRouter = useNextContextRouter()
24+
25+
// -- Render --
26+
27+
return (
28+
<UniversalAppProviders
29+
contextLink={NextContextLink}
30+
contextRouter={nextContextRouter}
31+
useContextRouteParams={useNextRouteParams}
32+
>
33+
{children}
34+
</UniversalAppProviders>
35+
)
36+
}
2337

2438
/* --- Exports --------------------------------------------------------------------------------- */
2539

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react'
2+
import { UniversalLinkProps } from '../navigation/Link.types'
3+
import { UniversalRouterMethods } from '../navigation/useRouter.types'
4+
import { UniversalRouteScreenProps } from '../navigation/useRouteParams.types'
5+
import type { useLocalSearchParams } from 'expo-router'
6+
7+
// -i- This context's only aim is to provide React Framework ejection patterns if required
8+
// -i- By allowing you to provide your own custom Link and Router overrides, you could e.g.:
9+
// -i- 1) Support Expo for Web by not defaulting to Next.js's Link and Router on web
10+
// -i- 2) Eject from Next.js entirely and e.g. use another framework's Link component and Router
11+
12+
/* --- Types ----------------------------------------------------------------------------------- */
13+
14+
export type CoreContextType = {
15+
contextLink: (props: UniversalLinkProps) => JSX.Element
16+
contextRouter: UniversalRouterMethods
17+
useContextRouteParams: (routeScreenProps: UniversalRouteScreenProps) => ReturnType<typeof useLocalSearchParams>
18+
}
19+
20+
/* --- Context --------------------------------------------------------------------------------- */
21+
22+
export const CoreContext = React.createContext<CoreContextType>({
23+
contextLink: null,
24+
contextRouter: null,
25+
useContextRouteParams: () => ({}),
26+
})
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Link as ExpoLink } from 'expo-router'
2+
import type { UniversalLinkProps } from './Link.types'
3+
4+
/* --- <Link/> --------------------------------------------------------------------------------- */
5+
6+
export const Link = (props: UniversalLinkProps) => {
7+
// Props
8+
const {
9+
children,
10+
href,
11+
style,
12+
replace,
13+
onPress,
14+
target,
15+
asChild,
16+
push,
17+
testID,
18+
nativeID,
19+
allowFontScaling,
20+
numberOfLines,
21+
maxFontSizeMultiplier
22+
} = props
23+
24+
// -- Render --
25+
26+
return (
27+
<ExpoLink
28+
href={href}
29+
style={style}
30+
onPress={onPress}
31+
target={target}
32+
asChild={asChild}
33+
replace={replace}
34+
push={push}
35+
testID={testID}
36+
nativeID={nativeID}
37+
allowFontScaling={allowFontScaling}
38+
numberOfLines={numberOfLines}
39+
maxFontSizeMultiplier={maxFontSizeMultiplier}
40+
>
41+
{children}
42+
</ExpoLink>
43+
)
44+
}
45+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Link } from './Link.core.native'
Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,14 @@
1-
import { Link as ExpoLink } from 'expo-router'
1+
import React from 'react'
22
import type { UniversalLinkProps } from './Link.types'
3+
import { CoreContext } from '../context/CoreContext'
34

45
/* --- <Link/> --------------------------------------------------------------------------------- */
56

67
export const Link = (props: UniversalLinkProps) => {
7-
// Props
8-
const {
9-
children,
10-
href,
11-
style,
12-
replace,
13-
onPress,
14-
target,
15-
asChild,
16-
push,
17-
testID,
18-
nativeID,
19-
allowFontScaling,
20-
numberOfLines,
21-
maxFontSizeMultiplier
22-
} = props
8+
// Context
9+
const { contextLink: ContextLink } = React.useContext(CoreContext)
2310

24-
// -- Render --
25-
26-
return (
27-
<ExpoLink
28-
href={href}
29-
style={style}
30-
onPress={onPress}
31-
target={target}
32-
asChild={asChild}
33-
replace={replace}
34-
push={push}
35-
testID={testID}
36-
nativeID={nativeID}
37-
allowFontScaling={allowFontScaling}
38-
numberOfLines={numberOfLines}
39-
maxFontSizeMultiplier={maxFontSizeMultiplier}
40-
>
41-
{children}
42-
</ExpoLink>
43-
)
11+
// Render
12+
return <ContextLink {...props} />
4413
}
4514

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useLocalSearchParams } from 'expo-router'
2+
import type { UniversalRouteScreenProps } from './useRouteParams.types'
3+
4+
/** --- useRouteParams() ----------------------------------------------------------------------- */
5+
/** -i- Gets the route search and query params on both web and mobile */
6+
export const useRouteParams = (routeScreenProps: UniversalRouteScreenProps) => {
7+
const { params, searchParams } = routeScreenProps
8+
const expoRouterParams = useLocalSearchParams()
9+
return {
10+
...params,
11+
...searchParams,
12+
...expoRouterParams,
13+
} as typeof expoRouterParams
14+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { useRouteParams } from './useRouteParams.core.native'
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { UniversalRouteScreenProps } from './useRouteParams.types'
2+
import type { useLocalSearchParams } from 'expo-router'
23

3-
/** --- useRouteParams() -------------------------------------------------------------- */
4+
/** --- useRouteParams() ----------------------------------------------------------------------- */
45
/** -i- Gets the route search and query params on both web and mobile */
56
export const useRouteParams = (routeScreenProps: UniversalRouteScreenProps) => {
67
const { params, searchParams } = routeScreenProps
7-
return { ...params, ...searchParams }
8+
return { ...params, ...searchParams } as ReturnType<typeof useLocalSearchParams>
89
}

0 commit comments

Comments
 (0)