import * as React from 'react'; import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; import type { SceneRendererProps, EventEmitterProps, NavigationState, Route, } from './types'; type Props = SceneRendererProps & EventEmitterProps & { navigationState: NavigationState; lazy: boolean; lazyPreloadDistance: number; index: number; children: (props: { loading: boolean }) => React.ReactNode; style?: StyleProp; }; type State = { loading: boolean; }; export default class SceneView extends React.Component< Props, State > { static getDerivedStateFromProps(props: Props, state: State) { if ( state.loading && Math.abs(props.navigationState.index - props.index) <= props.lazyPreloadDistance ) { // Always render the route when it becomes focused return { loading: false }; } return null; } state = { loading: Math.abs(this.props.navigationState.index - this.props.index) > this.props.lazyPreloadDistance, }; componentDidMount() { if (this.props.lazy) { // If lazy mode is enabled, listen to when we enter screens this.unsubscribe = this.props.addEnterListener(this.handleEnter); } else if (this.state.loading) { // If lazy mode is not enabled, render the scene with a delay if not loaded already // This improves the initial startup time as the scene is no longer blocking this.timerHandler = setTimeout( () => this.setState({ loading: false }), 0 ); } } componentDidUpdate(prevProps: Props, prevState: State) { if ( this.props.lazy !== prevProps.lazy || this.state.loading !== prevState.loading ) { // We only need the listener if the tab hasn't loaded yet and lazy is enabled if (this.props.lazy && this.state.loading) { this.unsubscribe?.(); this.unsubscribe = this.props.addEnterListener(this.handleEnter); } else { this.unsubscribe?.(); } } } componentWillUnmount() { this.unsubscribe?.(); if (this.timerHandler) { clearTimeout(this.timerHandler); this.timerHandler = undefined; } } private timerHandler: NodeJS.Timeout | undefined; private unsubscribe: (() => void) | null = null; private handleEnter = (value: number) => { const { index } = this.props; // If we're entering the current route, we need to load it if (value === index) { this.setState((prevState) => { if (prevState.loading) { return { loading: false }; } return null; }); } }; render() { const { navigationState, index, layout, style } = this.props; const { loading } = this.state; const focused = navigationState.index === index; return ( { // Only render the route only if it's either focused or layout is available // When layout is not available, we must not render unfocused routes // so that the focused route can fill the screen focused || layout.width ? this.props.children({ loading }) : null } ); } } const styles = StyleSheet.create({ route: { flex: 1, overflow: 'hidden', }, });