Skip to content

Commit

Permalink
[docs] Expo Router: add docs for missing API reference (expo#23355)
Browse files Browse the repository at this point in the history
# Why

- As mentioned by @amandeepmittal, we need docs for the hooks API
expo#23243

# How

- Added API ref and ported docs.
- Added a URL switching component to help users understand how different
runtimes work.

<img width="643" alt="Screenshot 2023-07-06 at 5 34 02 PM"
src="https://github.com/expo/expo/assets/9664363/19e824ef-d22e-460e-9a2a-08fc956e872a">
<img width="639" alt="Screenshot 2023-07-06 at 5 34 08 PM"
src="https://github.com/expo/expo/assets/9664363/a813891c-2fae-4179-b379-e1bc0ed39b01">
<img width="636" alt="Screenshot 2023-07-06 at 5 34 16 PM"
src="https://github.com/expo/expo/assets/9664363/db96627f-93b1-4da0-a148-0d86eb59cd29">
<img width="637" alt="Screenshot 2023-07-06 at 5 34 21 PM"
src="https://github.com/expo/expo/assets/9664363/38151aeb-7f62-480d-ac82-b37d05d3d962">

---------

Co-authored-by: Aman Mittal <[email protected]>
Co-authored-by: Bartosz Kaszubowski <[email protected]>
  • Loading branch information
3 people authored Jul 12, 2023
1 parent 0a10144 commit 343338e
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 3 deletions.
2 changes: 2 additions & 0 deletions docs/constants/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ const general = [
makePage('router/advanced/apple-handoff.mdx'),
]),
makeGroup('Reference', [
makePage('router/reference/hooks.mdx'),
makePage('router/reference/search-parameters.mdx'),
makePage('router/reference/redirects.mdx'),
makePage('router/reference/static-rendering.mdx'),
makePage('router/reference/async-routes.mdx'),
makePage('router/reference/sitemap.mdx'),
Expand Down
168 changes: 168 additions & 0 deletions docs/pages/router/reference/hooks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
title: Hooks API
description: Learn how to interact with the in-app URL in Expo Router.
---

import { FileTree } from '~/ui/components/FileTree';
import { RouteUrlGroup, RouteUrl } from '~/ui/components/RouteUrl';

<RouteUrlGroup>

In Expo Router, there's always a valid URL that represents the currently focused route. Use hooks to observe changes and interact with the URL.

## `usePathname`

Returns the currently selected route location without search parameters. For example, `/acme?foo=bar` -> `/acme`. Segments will be normalized: `/[id]?id=normal` -> `/normal`

<RouteUrl>/profile/baconbrix?extra=info</RouteUrl>

```js app/profile/[user].tsx
import { Text } from 'react-native';
/* @info */
import { usePathname } from 'expo-router';
/* @end */

export default function Route() {
/* @info <b>pathname = "/profile/baconbrix"</b> */
const pathname = usePathname();
/* @end */

return <Text>User: {user}</Text>;
}
```

## `useLocalSearchParams`

Returns the URL search parameters for the contextually selected route. Refer to the [local vs. global search params](/router/reference/search-parameters/#local-vs-global-search-parameters) guide for more information.

<FileTree files={['app/_layout.tsx', 'app/[first]/home.tsx', 'app/[second]/shop.tsx']} />

When `/abc/home` pushes `/123/shop`, `useGlobalSearchParams` returns `{ first: undefined, second: '123' }` on **/app/[first]/home.tsx** because the global URL has changed. However, you may want the params to remain `{ first: 'abc' }` to reflect the context of the screen. In this case, you can use `useLocalSearchParams` to ensure the params `{ first: 'abc' }` are still returned in **/app/[first]/home.tsx**.

<RouteUrl>/profile/baconbrix?extra=info</RouteUrl>

```js app/profile/[user].tsx
import { Text } from 'react-native';
/* @info */
import { useLocalSearchParams } from 'expo-router';
/* @end */

export default function Route() {
/* @info */
const { user, extra } = useLocalSearchParams();
/* @end */
return <Text>User: {user}</Text>;
}
```

## `useGlobalSearchParams`

Returns the URL search parameters for the globally selected route. For example, `/acme?foo=bar` -> `{ foo: "bar" }`.

Refer to the [local vs global search params](/router/reference/search-parameters/#local-vs-global-search-parameters) guide for more info.

<RouteUrl>/profile/baconbrix?extra=info</RouteUrl>

```js app/profile/[user].tsx
import { Text } from 'react-native';
/* @info */
import { useGlobalSearchParams } from 'expo-router';
/* @end */

export default function Route() {
/* @info <b>user=baconbrix</b> & <b>extra=info</b> */
const { user, extra } = useGlobalSearchParams();
/* @end */
return <Text>User: {user}</Text>;
}
```

### `Href` type

The `Href` type is a union of the following types:

- **string**: A full path like `/profile/settings` or a relative path like `../settings`.
- **object**: An object with a `pathname` and optional `params` object. 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.

## `useSegments`

Returns a list of segments for the currently selected route. Segments are not normalized so that they will be the same as the file path. For example, `/[id]?id=normal` -> `["[id]"]`.

```js app/profile/[user].tsx
import { Text } from 'react-native';
/* @info */
import { useSegments } from 'expo-router';
/* @end */

export default function Route() {
/* @info <b>segments = ["profile", "[user]"]</b> */
const segments = useSegments();
/* @end */
return <Text>Hello</Text>;
}
```

This function can be typed using an abstract of string arrays:

```js app/profile/[user].tsx
import { useSegments } from 'expo-router';

export default function Route() {
/* @info */
const segments = useSegments<['profile'] | ['profile', '[user]']>();
/* @end */

return </>
}
```

## `useNavigation`

Access the underlying React Navigation [`navigation` prop](https://reactnavigation.org/docs/navigation-prop) to imperatively access layout-specific functionality like `navigation.openDrawer()` in a Drawer layout. [Learn more](https://reactnavigation.org/docs/navigation-prop/#navigator-dependent-functions).

```js
/* @info */
import { useNavigation } from 'expo-router';
/* @end */

export default function Route() {
/* @info Access the current navigation object for the current route */
const navigation = useNavigation();
/* @end */
return (
<View>
<Text
onPress={() => {
/* @info Open the drawer view */
navigation.openDrawer();
/* @end */
}}>
Open Drawer
</Text>
</View>
);
}
```

## `useFocusEffect`

Given a function, the `useFocusEffect` hook will invoke the function whenever the route is "focused".

```js
/* @info */
import { useFocusEffect } from 'expo-router';
/* @end */

export default function Route() {

useFocusEffect(() => {
/* @info Invoked whenever the route is focused */
console.log('Hello')
/* @end */
})

return </>
}
```

</RouteUrlGroup>
55 changes: 55 additions & 0 deletions docs/pages/router/reference/redirects.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Redirects
description: Learn how redirect URLs in Expo Router.
---

You can redirect a request to a different URL based on some in-app criteria. Expo Router supports a number of different redirection patterns.

## Redirect

You can immediately redirect from a particular screen by using the `Redirect` component:

```js
import { View, Text } from 'react-native';
/* @info */
import { Redirect } from 'expo-router';
/* @end */

export default function Page() {
/* @info Some logic to determine if the user is logged in. */
const { user } = useAuth();
/* @end */

if (!user) {
/* @info Redirect to the login screen if the user is not authenticated. */
return <Redirect href="/login" />;
/* @end */
}

return (
<View>
<Text>Welcome Back!</Text>
</View>
);
}
```

You can also redirect imperatively with the `useRouter` hook:

```js
import { Text } from 'react-native';
import { useRouter, useFocusEffect } from 'expo-router';

function MyScreen() {
const router = useRouter();

useFocusEffect(() => {
// Call the replace method to redirect to a new route without adding to the history.
// We do this in a useFocusEffect to ensure the redirect happens every time the screen
// is focused.
router.replace('/profile/settings');
});

return <Text>My Screen</Text>;
}
```
5 changes: 5 additions & 0 deletions docs/pages/routing/navigating-pages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ The `router` object is immutable and contains the following functions:
- **push**: `(href: Href) => void` Navigate to a route. You can provide a full path like **/profile/settings** or a relative path like **../settings**. Navigate to dynamic routes by passing an object like `{ pathname: 'profile', params: { id: '123' } }`.
- **replace**: `(href: Href) => void` Same API as push but replaces the current route in the history instead of pushing a new one. This is useful for redirects.
- **back**: `() => void` Navigate back to previous route.
- **canGoBack**: `() => boolean` Returns `true` if a valid history stack exists and the `back()` function can pop back.
- **setParams**: `(params: Record<string, string>) => void` Update the query params for the currently selected route.

## Linking to dynamic routes
Expand Down Expand Up @@ -142,6 +143,10 @@ Expo Router supports the standard `<a>` element when running on web, however thi

Client-side navigation works with both single-page apps, and [static rendering](/router/reference/static-rendering).

## Usage in simulators

See the [testing URLs](/guides/linking#testing-urls) guide to learn how you can emulate deep links in simulators and emulators.

## Next steps

<BoxLink
Expand Down
83 changes: 83 additions & 0 deletions docs/ui/components/RouteUrl/RuntimePopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { css } from '@emotion/react';
import { ExpoGoLogo, shadows, theme, typography } from '@expo/styleguide';
import { breakpoints, spacing } from '@expo/styleguide-base';
import { ChevronDownIcon, Monitor01DuotoneIcon, Phone01DuotoneIcon } from '@expo/styleguide-icons';
import { useEffect, useState } from 'react';

type PopupActionProps<T extends string> = {
items: { name: string; id: T }[];
selected: string;
onSelect: (value: T) => void;
};
export function RuntimePopup<T extends string>({ items, selected, onSelect }: PopupActionProps<T>) {
const Icon = [ExpoGoLogo, Phone01DuotoneIcon, Monitor01DuotoneIcon][
items.findIndex(item => item.id === selected)
];
const [isLoaded, setLoaded] = useState(false);

useEffect(function didMount() {
setLoaded(true);
}, []);

return (
<div className="relative">
<select
aria-label="Runtime URL format selector"
title="Select runtime URL format"
css={selectStyle}
className="focus-visible:-outline-offset-2 border-0 rounded-none border-l border-l-default h-10 leading-10 px-10 hocus:bg-subtle hocus:shadow-none"
value={selected}
onChange={e => {
onSelect(e.target.value as T);
}}>
{items.map((item, index) => (
<option key={String(index)} value={item.id}>
{item.name}
</option>
))}
</select>
{isLoaded && (
<div
style={{ lineHeight: 1.3 }}
className="absolute inset-x-2.5 inset-y-0 flex items-center justify-between gap-2 text-icon-secondary pointer-events-none select-none">
<Icon className={ICON_CLASSES} />
<ChevronDownIcon className="icon-xs text-icon-secondary pointer-events-none" />
</div>
)}
</div>
);
}

const ICON_CLASSES = 'icon-sm text-icon-secondary pointer-events-none inline-block';

const selectStyle = css`
${typography.fontSizes[14]}
display: flex;
align-items: center;
justify-content: center;
color: ${theme.text.default};
line-height: 1.3;
padding: 0 ${spacing[8]}px;
color: ${theme.text.default};
text-indent: 0;
box-shadow: ${shadows.xs};
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background-color: ${theme.background.default};
cursor: pointer;
:hover {
background-color: ${theme.background.element};
}
:focus-visible {
background-color: ${theme.background.element};
}
@media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) {
padding: 0 0;
text-indent: -9999px;
}
`;
Loading

0 comments on commit 343338e

Please sign in to comment.