Replies: 4 comments
-
Thanks for starting such an interesting discussion. The Navigation router brings scene-based navigation to React and React Native. I'll explain what scene navigation is and hopefully answer some of your questions along the way Scene navigationWith the Navigation router, you navigate to scenes. You pass the name of the scene you want to go to and the strongly-typed data you want to pass. It's pretty standard in a React Native app but the Navigation router is the only library that brings this idea to React. It means the Navigation router has a single API across all platforms. Let's use the Navigation router to build Twitter for example. Twitter has a timeline scene where you can read through a user's tweets. We can navigate to the timeline scene in both React and React Native by passing the scene's name and the selected user. stateNavigator.navigate('timeline', { user: 'achmadmahardi' }) On both web and native, we access the user in the same way in the timeline scene const { data: { user } } = useContext(NavigationContext) But the web and native are very different. The web has browser history and URLs; and native has stacks. Even though the Navigation router provides a single API, underneath it handles the navigation completely differently between React and React Native. This guarantees the Navigation router won't compromise the user experience in pursuit of the best developer experience. Scenes in ReactIn React, when you navigate the Navigation router generates a URL and pushes it onto browser history. If you go to the timeline scene in our Twitter example then the URL in the browser will be '/timeline?user=achmadmahardi'. By default, the Navigation router uses the name of the scene as the route and adds all data into the query string. But this is no good. We want the URL to be '/achmadmahardi' just like in the actual Twitter website. No need to worry because we can turn the URL into '/achmadmahardi' without changing any code. We'll configure the route to be '{user}' and this time the browser bar will show '/achmadmahardi'. With scene navigation we never hard-code URLs. We navigate to scenes and pass strongly-typed data and we can have whatever URLs we want. new StateNavigator([
{ key: 'timeline', route: '{user}' }
]) Ah, you're probably thinking that was just a simple example. Let's make it a bit more difficult and show how the Navigation router supports nested routes. On the timeline scene when we select the 'Tweets and Replies' tab we want the URL to be 'achmadmahardi/with_replies'. This is still the same timeline scene but now we need to access it with a nested route. With the Navigation router, we never hard-code URLs. To go to the 'Tweets and Replies' tab we pass the scene, the user name and the tab stateNavigator.navigate('timeline', { user: 'achmadmahardi', tab: 'with_replies' }) If we run the Twitter example and select the tab, the browser URL will be 'achmadmahardi?tab=with_replies'. Remember, by default the Navigation router puts the tab in the query string. We fix this by configuring the tab as a route parameter. We use the + syntax to indicate we need to support two URL patterns: 'achmadmahardi' and 'achmadmahardi/with_replies'. new StateNavigator([
{ key: 'timeline', route: '{user}+/{tab}' }
]) Hopefully, you're now more convinced that scene navigation doesn't limit your choice of URLs. I don't think there is a URL scheme that the Navigation router can't support. There's also a <NavigationLink stateKey="timeline" navigationData={{ user: 'achmadmahardi' }} /> Scenes in React NativeIn React Native, when you navigate the Navigation router pushes the new scene onto the top of the native stack. If you have configured new StateNavigator([
{ key: 'timeline', trackCrumbTrail: true }
]) You can see this in action in the Twitter sample where you can build up a stack of timelines of whatever length you want. But you have complete control over the crumb trail in JavaScript so you could constrain its length if you prefer. The Navigation router uses the native stack primitives that come with Android and iOS. On Android this is the Native stack on the WebThe Twitter timeline scene has an infinite scrolling list of tweets. But so far we'd have to implement this very differently on web and native. On native, we don't have to do anything to restore the scroll position when we navigate back because native keeps all our scenes in memory. On the web, we have to work hard to restore the scroll because the timeline scene is mounted fresh when we navigate back. This doesn't seem fair. Is there a way to bring the web experience up to scratch with native? Yes, with the Navigation router there is. Because we have a single navigation API on React and React Native, we can run our native app on the web (using react native web). Here's the Twitter app running on all three platforms from a single code base. The three main changes are
I thought it best to outline how scene navigation works rather than try to answer all your questions in one go. I'll leave it up to you where this discussion goes next. |
Beta Was this translation helpful? Give feedback.
-
Your answer definitely helps! One of the parts that I'm still not convinced of before is the concept that URL is just a "byproduct" of application state - mostly because I usually perform user behavior tracking/analytics based on the URL that the user is on. but when I think again - and based on your explanation above, seems that the scene approach is actually better since we're not constrained by URL anymore, and directly attribute the user behavior to the scene itself - thanks! For the |
Beta Was this translation helpful? Give feedback.
-
I'm glad my answer helped!
Yes, thinking in scenes and strongly-typed data is powerful. For example, we can create reusable components that contain Hyperlinks like Pagers and Sorters. It’s just not possible with hard-coded URLs. I want to make it clear that the Navigation router uses the same navigation primitives you’d use if you were building a native app without React Native. So there are a lot of memory optimizations that Android and iOS provide out of the box. For example, only the current scene is added to the window. You can see this if you use the visual debuggers from Xcode and Android Studio to inspect the layout hierarchy - you’ll only see the views from the current scene. I'd encourage you to test a production build of your app to see if you really do need to optimize the memory. If you do find a problem then the steps you need to take will depend on what the problem is. For example, in native apps it's common to unsubscribe listeners when scenes are removed from the window and resubscribe when they’re added back to the window. It prevents scenes hidden in the stack from consuming resources unnecessarily. You could do a similar thing in React Native using the Navigation router's lifecycle hooks - unsubscribe in |
Beta Was this translation helpful? Give feedback.
-
yeah, those memory concerns should be proved first - I'll need to port the rest of the app first beforehand, and might get back to you if I found some memory-related issues. anyway based on my experience - the one that causes memory-related issues in RN is always the those |
Beta Was this translation helpful? Give feedback.
-
Hi, thanks for opening the discussion feature @grahammendick!
coming from the browser environment, I still had some questions about the "native" part of this library.
let me give you the context first:
In my understanding, browser generally only had "one layer" of a screen - and most of the time, the commonly-used routing/navigation pattern is to perform the whole screen mount/unmount lifecycle in that one screen, while maintaining the "history stack" in-memory or using browser's history API (pushState, popState).
that means, because of the whole mount-unmount process, when the user goes to another route, the previous screen state will be lost - e.g scroll position, which is crucial if you use the infinite scroll pattern since you need the user to correctly scrolled to the item that he/she previously sees on the screen before when perform route
popState
.In my previous work, we're bringing the whole "one layer" web concepts to the react-native - which means two major cons points:
But on the plus side:
So I went to research a different approach to routing/navigation while keeping the single codebase approach. first I look at react-navigation since they provided the support react-native and web (both client-side & SSR), but I find that the routing definition, deep linking handling, and the native integration, in general, are different from what I expected.
So I write my own routing abstraction, then using react-native-screens (which from my research is the native-renderer part of react-navigation) to render each of the user's history entry to its own "Screen".
That approach works but not optimized at all. route switching feels very slow (because it might be not the correct way to handle navigation on react native lol) and I am still confused about the slowness causes.
Also, I'm afraid with the memory usage, since for each route that the user visit, my implementations will stack them without destroying the previous screen (which in my research also happens when you use react-navigation).
sure the screen state is automatically saved and it opens the possibility to have "transitions" when switching routes, since I'm not destroying the previous screen at all, but the memory usage concerns still haunt me.
That's when I read this issue on the react-native-screens page which @grahammendick mentions navigation library. Even though I still don't have clue about the "scene" concepts, I still curious and went to implement navigation to my research codebase.
First, the APIs seems unnatural - e.g event-handler pattern (is that a correct term? cmiiw!) such as
onNavigate
when SSR because the server request handler basically works in a single request-response pattern instead of long-running client-side code, which makes more sense if you useonNavigate
. but I also find references that are more "synchronous" and (in my opinion) correctly fit the request-response pattern of the server request handler on the navigation-react-mobile sample here.Second, the
navigate
method is different since it points to a "scene" instead of a "route" - that's actually not a big of a problem, because the navigation library provides theparseLink
method to basically convert a traditional route to navigationscene
&state
definitions, which later can be passed to thenavigate
method. (@grahammendick still recommends that directly callnavigate
is the correct way to go, since the navigation approach is more of scene-based rather than URL/route-based one)Now that I successfully integrated the navigation library into my research app, It still works as before, and actually solved the slowness issue that I had with my previous homebrew approach
what I want to discuss/ask is as follows:
MobileHistoryManager
which doesn't print the crumb on the URL query parameters.Sorry for the long post beforehand, and looking forward to the discussions 😄
Beta Was this translation helpful? Give feedback.
All reactions