-
Notifications
You must be signed in to change notification settings - Fork 6.2k
/
Copy pathLink.tsx
188 lines (171 loc) · 5.06 KB
/
Link.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
'use client';
// Fork of @react-navigation/native Link.tsx with `href` and `replace` support added and
// `to` / `action` support removed.
import { PropsWithChildren, forwardRef, useMemo, MouseEvent, ForwardedRef } from 'react';
import { Text, GestureResponderEvent, Platform } from 'react-native';
import { Slot } from './LinkSlot';
import { resolveHref } from './href';
import useLinkToPathProps from './useLinkToPathProps';
import { useRouter } from '../hooks';
import { Href } from '../types';
import { useFocusEffect } from '../useFocusEffect';
import { useInteropClassName, useHrefAttrs, LinkProps, WebAnchorProps } from './useLinkHooks';
export interface LinkComponent {
(props: PropsWithChildren<LinkProps>): JSX.Element;
/** Helper method to resolve a Href object into a string. */
resolveHref: (href: Href) => string;
}
export type RedirectProps = {
/**
* The path of the route to navigate to. It can either be:
* - **string**: A full path like `/profile/settings` or a relative path like `../settings`.
* - **object**: An object with a `pathname` and optional `params`. The `pathname` can be
* a full path like `/profile/settings` or a relative path like `../settings`. The
* params can be an object of key-value pairs.
*
* @example
* ```tsx Dynamic
* import { Redirect } from 'expo-router';
*
* export default function RedirectToAbout() {
* return (
* <Redirect href="/about">About</Link>
* );
*}
* ```
*/
href: Href;
/**
* Relative URL references are either relative to the directory or the document.
* By default, relative paths are relative to the document.
*
* @see [Resolving relative references in Mozilla's documentation](https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references).
*/
relativeToDirectory?: boolean;
/**
* Replaces the initial screen with the current route.
*/
withAnchor?: boolean;
};
/**
* Redirects to the `href` as soon as the component is mounted.
*
* @example
* ```tsx
* import { View, Text } from 'react-native';
* import { Redirect } from 'expo-router';
*
* export default function Page() {
* const { user } = useAuth();
*
* if (!user) {
* return <Redirect href="/login" />;
* }
*
* return (
* <View>
* <Text>Welcome Back!</Text>
* </View>
* );
* }
* ```
*/
export function Redirect({ href, relativeToDirectory, withAnchor }: RedirectProps) {
const router = useRouter();
useFocusEffect(() => {
try {
router.replace(href, { relativeToDirectory, withAnchor });
} catch (error) {
console.error(error);
}
});
return null;
}
/**
* Component that renders a link using [`href`](#href) to another route.
* By default, it accepts children and wraps them in a `<Text>` component.
*
* Uses an anchor tag (`<a>`) on web and performs a client-side navigation to preserve
* the state of the website and navigate faster. The web-only attributes such as `target`,
* `rel`, and `download` are supported and passed to the anchor tag on web. See
* [`WebAnchorProps`](#webanchorprops) for more details.
*
* > **Note**: Client-side navigation works with both single-page apps,
* and [static-rendering](/router/reference/static-rendering/).
*
* @example
* ```tsx
* import { Link } from 'expo-router';
* import { View } from 'react-native';
*
* export default function Route() {
* return (
* <View>
* <Link href="/about">About</Link>
* </View>
* );
*}
* ```
*/
export const Link = forwardRef(ExpoRouterLink) as unknown as LinkComponent;
Link.resolveHref = resolveHref;
function ExpoRouterLink(
{
href,
replace,
push,
// TODO: This does not prevent default on the anchor tag.
relativeToDirectory,
asChild,
rel,
target,
download,
withAnchor,
...rest
}: LinkProps,
ref: ForwardedRef<Text>
) {
// Mutate the style prop to add the className on web.
const style = useInteropClassName(rest);
// If not passing asChild, we need to forward the props to the anchor tag using React Native Web's `hrefAttrs`.
const hrefAttrs = useHrefAttrs({ asChild, rel, target, download });
const resolvedHref = useMemo(() => {
if (href == null) {
throw new Error('Link: href is required');
}
return resolveHref(href);
}, [href]);
let event;
if (push) event = 'PUSH';
if (replace) event = 'REPLACE';
const props = useLinkToPathProps({
href: resolvedHref,
event,
relativeToDirectory,
withAnchor,
});
const onPress = (e: MouseEvent<HTMLAnchorElement> | GestureResponderEvent) => {
if ('onPress' in rest) {
rest.onPress?.(e);
}
props.onPress(e);
};
const Element = asChild ? Slot : Text;
// Avoid using createElement directly, favoring JSX, to allow tools like NativeWind to perform custom JSX handling on native.
return (
<Element
ref={ref}
{...props}
{...hrefAttrs}
{...rest}
style={style}
{...Platform.select({
web: {
onClick: onPress,
} as any,
default: { onPress },
})}
/>
);
}
export { LinkProps, WebAnchorProps };