What just randomly reading, but I think there's a typo
```
['ab', 'aba'].sort((a, b) => {
if (a.startsWith(b)) {
return -1;
}
if (b.startsWith(a)) {
return 1;
}
});
(2) ["aba", "ab"]
['ab', 'a'].sort((a, b) => {
if (a.startsWith(b)) {
return -1;
}
if (b.startsWith(a)) {
return 1;
}
});
(2) ["ab", "a"]
```
A lot of times, we want to prompt before leaving a screen if we have unsaved changes. Currently, we need to handle multiple cases to prevent this:
- Disable swipe gestures
- Override the back button in header
- Override the hardware back button on Android
This PR adds a new event which is emitted before a screen gets removed, and the developer has a chance to ask the user before closing the screen.
Example:
```js
React.useEffect(
() =>
navigation.addListener('beforeRemove', (e) => {
if (!hasUnsavedChanges) {
return;
}
e.preventDefault();
Alert.alert(
'Discard changes?',
'You have unsaved changes. Are you sure to discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
onPress: () => navigation.dispatch(e.data.action),
},
]
);
}),
[navigation, hasUnsavedChanges]
);
```
The PR changes a few things about linking configuration:
- Moves the configuration for screens to a screens property so that it's possible to specify other options like `initialRouteName` for the navigator at root
- The nesting in the configuration needs to strictly match the shape of the navigation tree, it can't just rely on URL's shape anymore
- If a screen is not specified in the configuration, it won't be parsed to/from the URL (this is essential to handle unmatched screens)
- Treat `path: ''` and no specified path in the same way, unless `exact` is specified
- Disallow specifying unmatched screen with old format
- Add support for `initialRouteName` at top level
- Automatically adapt old configuration to new format
The `devtools` package extracts the redux devtools extension integration to a separate package. In future we can add more tools such as flipper integration to this package.
Usage:
```js
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
export default function App() {
const navigationRef = React.useRef();
useReduxDevToolsExtension(navigationRef);
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
```
Currently, to access the focused child screen, we need to do something like this:
```js
const routeName = route.state
? route.state.routes[route.state.index].name
: route.params?.screen || 'Feed';
```
However, it doesn't handle some cases, such as when `route.state` is partial. This helper will make it easier:
```js
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Feed';
```
The PR reworks history integration to better integrate with browser's history stack and supports nested navigation more reliably:
- On each navigation, save the navigation in memory and use it to reset the state when user presses back/forward
- Improve heuristic to determine if we should do a push, replace or back
This closes#8230, closes#8284 and closes#8344
Currently, if we don't have matching routes for a path, we'll reuse the path name for the route name. This doesn't produce an error, and renders the initial route in the navigator. However, the user doesn't have a way of handling this with the default configuration.
This PR adds support for a wildcard pattern ('*'). The wildcard pattern will be matched after all other patterns were matched and will always match unmatched screens. This allows the user to implement a 404 screen.
Example:
```js
{
Home: '',
Profile: 'user/:id',
404: '*',
}
```
This config will return the `404` route for paths which didn't match `Home` or `Profile`, e.g. - `/test`
Closes#8019
Co-authored-by: Evan Bacon <baconbrix@gmail.com>
User can pass a `ref` to the container to get current options, like they can with `NavigationContainer`:
```js
const ref = React.createRef();
const html = renderToString(
<ServerContainer ref={ref}>
<App />
</ServerContainer>
);
ref.current.getCurrentOptions(); // Options for screen
```
Currently, when we define path patterns in the linking config, they ignore the parent screen's path when parsing, for example, say we have this config:
{
Home: {
path: 'home',
screens: {
Profile: 'u/:id',
},
},
}
If we parse the URL /home, we'll get this state:
{
routes: [{ name: 'Home' }],
}
If we parse the URL /home/u/cal, we'll get this state:
{
routes: [
{
name: 'Home',
state: {
routes: [
{
name: 'Home',
state: {
routes: [
{
name: 'Profile',
params: { id: 'cal' },
},
],
},
},
],
},
},
],
}
Note how we have 2 Home screens nested inside each other. This is not something people usually expect and it seems to trip up a lot of people. Something like following will be more intuitive:
{
routes: [
{
name: 'Home',
state: {
routes: [
{
name: 'Profile',
params: { id: 'cal' },
},
],
},
},
],
}
Essentially, we're treating patterns as relative to their parent rather than matching the exact segments. This PR changes the behavior of parsing links to treat configs like so. I hope it'll be easier to understand for people.
There is also a new option, exact, which brings back the olde behavior of matching the exact pattern:
{
Home: {
path: 'home',
screens: {
Profile: {
path: 'u/:id',
exact: true,
},
},
},
}
Which will be useful if home is not in the URL, e.g. /u/cal.
This change only affects situations where both parent and child screen configuration have a pattern defined. If the parent didn't have a pattern defined, nothing changes from previous behavior:
{
Home: {
screens: {
Profile: {
path: 'u/:id',
},
},
},
}