Closed yangm97 closed 1 year ago
Hey there, it's great to hear from you! Could you tell me what you're trying to do, please? If you tell me what you want to happen I'll recommend the best way(s) to get there using the Navigation router.
Firstly, after finally realizing the wonders of scene based navigation, I want to keep everything as flat as possible. I'm using TabBar
as the main way to navigate in my app (similar to the twitter example).
So given the following navigator:
export const stateNavigator = new StateNavigator([
// Unauthenticated
{key: 'login'},
{key: 'register'},
{key: 'password_reset'},
// Authenticated but no central selected
{key: 'central_picker'},
// Authenticated and central selected, TabBar
// TODO: consider using central_id as a route param here
{key: 'main'},
// Authenticated and central selected, TabBarItem collection
{key: 'home'},
{key: 'ambients'},
{key: 'notifications'},
{key: 'dashboard'},
// Here I may probably need a nested navigator eventually
{key: 'settings'},
// Authenticated and central selected, state should be reachable from any tab, screen should cover TabBar
{key: 'ambient', trackCrumbTrail: true, defaultTypes: {id: 'number'}},
// More to come... ;)
]);
I want to be able to open an ambient
screen from both home
and ambients
tabs. The only way I was able to achieve the desired behavior on iOS and other platforms was to do something like this:
export default () => {
const isUserLoggedIn = useAppSelector(selectIsAuthenticated);
const isCentralSelected = useAppSelector(selectIsCentralSelected);
// TODO: handle current url properly on refresh and figure out flickers
useEffect(() => {
if (!isUserLoggedIn) stateNavigator.navigate('login');
else if (!isCentralSelected) {
// stateNavigator.navigate('')
} else {
stateNavigator.navigate('main');
}
}, [isUserLoggedIn, isCentralSelected]);
return (
<NavigationHandler stateNavigator={stateNavigator}>
<NavigationStack
crumbStyle={from => (from ? 'scale_in' : 'scale_out')}
unmountStyle={from => (from ? 'slide_in' : 'slide_out')}>
{/* */}
<Scene stateKey="login">
<View style={{flex: 1, backgroundColor: brandPrimary}}>
<Login />
</View>
</Scene>
<Scene stateKey="password_reset">
<View style={{flex: 1, backgroundColor: brandPrimary}}>
<ResetPassword />
</View>
</Scene>
<Scene stateKey="central_picker">
<View style={{flex: 1, backgroundColor: brandPrimary}}>
<CentralPicker />
</View>
</Scene>
<Scene stateKey="main">
<BottomTab />
</Scene>
{/* This gets ignored on iOS because each TabBarItem needs its own navigator otherwise the app crashes */}
<Scene stateKey="ambient">
<AmbientDetail />
</Scene>
</NavigationStack>
</NavigationHandler>
);
};
// ...
const useStateNavigator = () => {
const {stateNavigator} = useContext(NavigationContext);
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(() => new StateNavigator(stateNavigator), []);
};
export const BottomTab = () => {
const homeNavigator = useStateNavigator();
const ambientsNavigator = useStateNavigator();
const notificationsNavigator = useStateNavigator();
const dashboardNavigator = useStateNavigator();
const settingsNavigator = useStateNavigator();
return (
<>
<NavigationBar hidden />
<TabBar primary>
<TabBarItem title="Favorites">
{Platform.OS === 'ios' ? (
<NavigationHandler stateNavigator={homeNavigator}>
<NavigationStack
hidesTabBar={(state, data, crumbs) => {
switch (state.key) {
case 'ambient':
return true;
default:
return false;
}
}}>
<Scene stateKey="home">
<Home />
</Scene>
<Scene stateKey="ambient">
<AmbientDetail />
</Scene>
</NavigationStack>
</NavigationHandler>
) : (
<Home />
)}
</TabBarItem>
<TabBarItem title="Ambients">
{Platform.OS === 'ios' ? (
<NavigationHandler stateNavigator={ambientsNavigator}>
<NavigationStack
hidesTabBar={(state, data, crumbs) => {
switch (state.key) {
case 'ambient':
return true;
default:
return false;
}
}}>
<Scene stateKey="ambients">
<Ambients />
</Scene>
<Scene stateKey="ambient">
<AmbientDetail />
</Scene>
</NavigationStack>
</NavigationHandler>
) : (
<Ambients />
)}
</TabBarItem>
<TabBarItem title="Notifications">
{Platform.OS === 'ios' ? (
<NavigationHandler stateNavigator={notificationsNavigator}>
<NavigationStack>
<Scene stateKey="notifications">
<Notifications />
</Scene>
</NavigationStack>
</NavigationHandler>
) : (
<Notifications />
)}
</TabBarItem>
<TabBarItem title="Dashboard">
{Platform.OS === 'ios' ? (
<NavigationHandler stateNavigator={dashboardNavigator}>
<NavigationStack>
<Scene stateKey="dashboard">
<Dashboard />
</Scene>
</NavigationStack>
</NavigationHandler>
) : (
<Dashboard />
)}
</TabBarItem>
<TabBarItem title="Settings">
{Platform.OS === 'ios' ? (
<NavigationHandler stateNavigator={settingsNavigator}>
<NavigationStack>
<Scene stateKey="settings">
<Settings />
</Scene>
</NavigationStack>
</NavigationHandler>
) : (
<Settings />
)}
</TabBarItem>
</TabBar>
</>
);
};
Then, on the Ambient
component shown in both screens I navigate like this:
import {NavigationContext} from 'navigation-react';
import {useContext} from 'react';
export const useStateNavigation = () => useContext(NavigationContext);
export const useNavigation = () => {
const {stateNavigator} = useStateNavigation();
return stateNavigator;
};
// ...
export const Ambient = (props: AmbientProps) => {
const navigation = useNavigation();
// ...
const onPress = () => {
// console.log(
// navigation.states,
// '\n',
// navigation.stateContext,
// '\n',
// navigation.historyManager,
// );
// navigation.navigate('Ambients');
// navigation.navigate('main')
// const link = stateNavigator
// .fluent()
// .navigate('main')
// .navigate('Ambients')
// .navigate('ambient', {id}).url;
// stateNavigator.navigateLink(link);
navigation.navigate('ambient', {id});
};
// ...
Offtopic: As you can see I had some fun before figuring out the simple, obvious, solution there hehe
So you've got the same kind of set up as the Twitter sample, right? That's where there's a stack per tab on iOS and a single stack on Android. You can have a stack per tab on Android too if you want. But you can't have a single stack on iOS because the native UITabBarController from UIKit only supports a stack per tab.
If I understand,
ambient
scene from both tabsambient
sceneIs that right?
What you've done looks good to me. Any reason you don't want to open the ambient
scene in a Modal? You can have a stack inside a Modal, too, like I've done in the medley sample.
But you can't have a single stack on iOS because the native UITabBarController from UIKit only supports a stack per tab.
😔
Is that right?
Yep.
Any reason you don't want to open the ambient scene in a Modal?
First reason would be to ease transition from the wrong navigation lib. The screen has a bottom thing with some buttons (i.e. a filter button).
Another reason would be because this screen is somewhat heavy, has a SectionList and I will have other stuff coming up inside a modal.
I know I could bring another modal on top here, but at this point the device would be rendering 3+ screens simultaneously and low end Android devices can get really emotional then.
ease transition from the wrong navigation lib
I live for that. Thank you
Ok, your code sample looks good to me. Are you happy with it?
Are you happy with it?
Not truly 100% cool with declaring some scenes n
times, because even hiding stuff behind functions/components, I can still myself forgetting to declare some scene here or there.
The alternatives I've thought so far:
What about something like this?
const tweetScenes = () => (
<>
<Scene stateKey="tweet"><Tweet /></Scene>
<Scene stateKey="timeline"><Timeline /></Scene>
</>
)
<NavigationStack>
<Scene stateKey="home"><Home /></Scene>
{tweetScenes()}
</NavigationStack>
Another way you could do it is have a shared stack
const Stack = () => (
<NavigationStack>
<Scene stateKey="home"><Home /></Scene>
<Scene stateKey="notifications"><Notifications /></Scene>
<Scene stateKey="home"><Home /></Scene>
<Scene stateKey="tweet"><Tweet /></Scene>
<Scene stateKey="timeline"><Timeline /></Scene>
</NavigationStack>
)
<TabBarItem title="Home">
<NavigationHandler stateNavigator={homeNavigator}>
<Stack />
</NavigationHandler>
</TabBarItem>
<TabBarItem title="Notifications">
<NavigationHandler stateNavigator={notificationsNavigator}>
<Stack />
</NavigationHandler>
</TabBarItem>
Then you can decide the start scene when you create the navigator
const useStateNavigator = (start) => {
const {stateNavigator} = useContext(NavigationContext);
return useMemo(() => {
const nav = new StateNavigator(stateNavigator)
nav.navigate(start)
return nav;
}, [])
};
const homeNavigator = useStateNavigator('home');
const notificationsNavigator = useStateNavigator('notifications');
If you don't like it that you can accidentally navigate to the home
scene when you're inside the notifications tab then you can control the valid states by creating different state navigators. So instead of cloning the state navigators and having different stacks you can reverse that. You clone the stack and have different state navigators.
const homeNavigator = new StateNavigator([
{key: 'home'},
{key: 'tweet', trackCrumbTrail: true},
{key: 'timeline', trackCrumbTrail: true}
]);
const notificationsNavigator = new StateNavigator([
{key: 'notifications'},
{key: 'tweet', trackCrumbTrail: true},
{key: 'timeline', trackCrumbTrail: true}
]);
Then you can do this to share scenes
const tweetScenes = [
{key: 'tweet', trackCrumbTrail: true},
{key: 'timeline', trackCrumbTrail: true}
]
const homeNavigator = new StateNavigator([
{key: 'home'},
...tweetScenes
]);
const notificationsNavigator = new StateNavigator([
{key: 'notifications'},
...tweetScenes
]);
@yangm97 you happy with this?
If I don't hear from you in the next week I'll close this. Don't worry if you're too busy, we can always reopen again whenever you're free
Originally posted by @grahammendick in https://github.com/grahammendick/navigation/issues/636#issuecomment-1236074277
I guess the twitter example has changed since this was posted. Specifically, some scenes are getting declared multiple times, probably to make them render inside the active tab as a native iOS app would under these circumstances.
Still, I couldn't get navigation to work like the first video, where tabs are hidden simply because navigating "up" in the stack. I get no errors, no nothing.
The second approach does work but in my case would result in a lot of duplication, as I want some scenes to be accessible regardless of the tab the user is on and to hide the TabUI.
I tried to remove the nested navigator but that seems to crash on iOS (but not on other platforms).
PS: this library is a gold mine. Thank you for your hard work!