Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add layout and screenLayout props for screens
This adds a new `layout` prop that can be used to wrap screens. It makes it easier to provide things such as a global error boundary and suspense fallback for a group of screens without having to manually add HOCs for every screen separately. See #11152
- Loading branch information
Showing
10 changed files
with
385 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { Button } from '@react-navigation/elements'; | ||
import type { ParamListBase } from '@react-navigation/native'; | ||
import { | ||
createStackNavigator, | ||
type StackNavigationOptions, | ||
type StackScreenProps, | ||
} from '@react-navigation/stack'; | ||
import * as React from 'react'; | ||
import { ScrollView, StyleSheet, Text, View } from 'react-native'; | ||
|
||
export type SimpleStackParams = { | ||
SuspenseDemo: { author: string } | undefined; | ||
}; | ||
|
||
let cached: number | undefined; | ||
|
||
const createPromise = () => | ||
new Promise<number>((resolve) => { | ||
setTimeout(() => resolve(42), 1000); | ||
}).then((result) => { | ||
cached = result; | ||
return result; | ||
}); | ||
|
||
const SuspenseDemoScreen = ({ | ||
navigation, | ||
}: StackScreenProps<SimpleStackParams, 'SuspenseDemo'>) => { | ||
const [promise, setPromise] = React.useState(createPromise); | ||
const [error, setError] = React.useState<Error | null>(null); | ||
|
||
// Naive implementation for suspense intended for demo purposes | ||
// We need to suspend when there's no cached value by throwing a promise | ||
if (cached == null) { | ||
throw promise; | ||
} | ||
|
||
if (error) { | ||
throw error; | ||
} | ||
|
||
return ( | ||
<ScrollView> | ||
<View style={styles.buttons}> | ||
<Button | ||
variant="filled" | ||
onPress={() => { | ||
cached = undefined; | ||
setPromise(createPromise()); | ||
}} | ||
> | ||
Suspend | ||
</Button> | ||
<Button | ||
variant="tinted" | ||
onPress={() => { | ||
setError(new Error('Something went wrong')); | ||
}} | ||
> | ||
Crash | ||
</Button> | ||
<Button variant="tinted" onPress={() => navigation.goBack()}> | ||
Go back | ||
</Button> | ||
</View> | ||
</ScrollView> | ||
); | ||
}; | ||
|
||
class ErrorBoundary extends React.Component< | ||
{ children: React.ReactNode }, | ||
{ hasError: boolean } | ||
> { | ||
static getDerivedStateFromError() { | ||
return { hasError: true }; | ||
} | ||
|
||
state = { hasError: false }; | ||
|
||
render() { | ||
if (this.state.hasError) { | ||
return ( | ||
<View style={styles.fallback}> | ||
<Text style={styles.text}>Something went wrong</Text> | ||
<Button onPress={() => this.setState({ hasError: false })}> | ||
Try again | ||
</Button> | ||
</View> | ||
); | ||
} | ||
|
||
return this.props.children; | ||
} | ||
} | ||
|
||
const Stack = createStackNavigator<SimpleStackParams>(); | ||
|
||
export function LayoutsStack({ | ||
navigation, | ||
screenOptions, | ||
}: StackScreenProps<ParamListBase> & { | ||
screenOptions?: StackNavigationOptions; | ||
}) { | ||
React.useLayoutEffect(() => { | ||
navigation.setOptions({ | ||
headerShown: false, | ||
}); | ||
}, [navigation]); | ||
|
||
return ( | ||
<Stack.Navigator screenOptions={screenOptions}> | ||
<Stack.Screen | ||
name="SuspenseDemo" | ||
component={SuspenseDemoScreen} | ||
options={{ title: 'Suspense & ErrorBoundary' }} | ||
layout={({ children }) => ( | ||
<ErrorBoundary> | ||
<React.Suspense | ||
fallback={ | ||
<View style={styles.fallback}> | ||
<Text style={styles.text}>Loading…</Text> | ||
</View> | ||
} | ||
> | ||
{children} | ||
</React.Suspense> | ||
</ErrorBoundary> | ||
)} | ||
/> | ||
</Stack.Navigator> | ||
); | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
buttons: { | ||
flexDirection: 'row', | ||
flexWrap: 'wrap', | ||
gap: 12, | ||
margin: 12, | ||
}, | ||
fallback: { | ||
flex: 1, | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}, | ||
|
||
text: { | ||
textAlign: 'center', | ||
margin: 12, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.