Closed ryanflorence closed 8 years ago
There will be a localStorage
with the same API as the regular one except async
Possibly of interest: http://applinks.org/
I'd prefer to have a synchronous way to store URLs, just because all our transitions are sync by default.
@rpflorence Ideally we would be able to render the top-level component and not have to embed the router in some other component, no?
I'm just making wild guesses, but every app has Bundler.registerComponent('MoviesApp', () => MoviesApp);
which appears to be rendered automatically.
The components would have to be different too. You don't have a DOM <a>
, so <Link>
needs to reimplement it.
Strictly I wouldn't say <Link>
is beneficial in React Native. I rarely see iOS interfaces using links.
Most of the times I assume you'd just use transitionTo
and friends.
Perhaps, though, links are the navigation paradigm on the web (not just for text). As part of "Learn Once, Write Everywhere," there's a strong case to be made for maintaining a declarative way to create transitions (even if they don't default to blue text).
For instance, I think this should work with react-native:
<Link to = "home">
<Image source = { ix("logo.svg") } />
</Link>
Yes, I suppose you're right. I wonder how having different targets (like Web and React Native) would work for a library.
The naive solution would be something like this:
try {
var React = require("react/addons");
} catch (error) {
if (!error.message.includes("Cannot find"))
throw error;
var React = require("react-native/addons");
}
and then choose a "div"
or a View
depending on what's available. It would be nice if they made this easier in the standard library though.
I came really close to submitting a feature request to have DOM shims for the RN primitives (View
, Text
, and Image
) added to React, but I backed down when I realized that's probably too close to "write once, run everywhere". For instance, img
uses srcSet
and Image
uses source
. I imagine reimplementing srcSet
on top of Image
is exactly the kind of maintenance hell they are trying to avoid by discouraging component reuse across platforms.
Perhaps branch via process.env
in some way? Webpack and browserify users could remove dead code, and library authors could offer pre-bundled versions for each target (require('library/android')
). Default could be web, or determining target at runtime.
@appsforartists You're right, the goal of React Native is not to enable "write once" mentality, thus I think your example would be an anti-pattern. You should maintain different code bases for different platforms. But I do agree with you that Links should be available much like they are on the web. Views should be linkable. Otherwise I'd expect that it would be possible to route using regular JS from a click handler. (Not sure if this is already possible with React router - I'm assuming it is.)
I know there hasn't been any major progress here yet, but I think it would be nice, if the router would be "smart" about where to transition to. If we have a navigation stack which already contains a
, b
and c
, and we then want to transition to a
, it would be nice if the router then popped b
and c
from the stack in order to get to a
, instead of adding a new instance of a
to the stack. This type of "infinite pushing" is a common mistake/error I have seen in iOS applications and if we could tackle that on the router level, that would be pretty nice I think.
edit: Since there actually is already a Navigator
component available, which is being used to navigate between scenes and to determine which scene to render initially etc., the router could probably somehow leverage that under the hood?
we're working on this, don't think we need the issue anymore.
Any update on this? React Router on RN would be HUGE.
+1
ping @ryanflorence - curious to hear how this is coming along and if there is anything that we can do to help! I see the most recent commit on the react-native branch was about 21 days ago: https://github.com/rackt/react-router/commit/bf6f9a723f94b967f56196a9a711518ecd0b8e01 - if you could put up a checklist for remaining tasks perhaps a few of us could try to tackle them
I too would like to know. I see there's a branch here: https://github.com/rackt/react-router/tree/react-native
Do you guys have an estimate for the completion of this? Would you like help?
Anybody...? It looks like the react-native
branch has been deleted. I know support for react-native was being developed for history
, but would love an update on where things stand for react-router
.
+1
+1
Where can I find a sample of react-router + react-native?
:+1:
+1
+1
:+1:
@ryanflorence Any update on this? Just about to start a big project in RN and would love to use React Router.
This isn't shippable in a reasonable way without the RN/React merge without something like https://github.com/rackt/react-router/pull/2031. And this issue is asking the wrong question anyway - the question is integration with Navigator, not some abstract support on its own.
cc @skevy
Yah just a report on what I've been up to -
I have a working prototype of this in an app that's currently in beta at the moment. It's rough around the edges, but I think there's definitely going to be a way to make it work in a stable manner. Some of the harder parts around the integration is not actually developing a solution, but really figuring out the API around the right solution. Routing on the web does not map directly to routing on native. @ide talks about it a bit here: https://medium.com/the-exponent-log/routing-and-navigation-in-react-native-6b27bee39603.
The main issue is that a url of /app/me/profile
doesn't give any information about the routes that came before it, which is something you need in a mobile app -- a concept of "implied" routes. In addition, most mobile apps have multiple navigators (each tab in a tabbed application usually has a nested navigator)...so the problem of how to deal with hierarchal history is an issue. There are multiple ways to solve the problem, but that's kind of the state of the world (as I'm aware) at the moment.
getChildRoutes + location.state I've imagined being the levers to get "implied" routes.
But also, ignoring that use case, I've imagined we don't try to make react router understand iOS routing semantics, we bring web routing semantics to iOS.
@ryanflorence I agree with the second comment - that the goal of using RR on RN would be to allow the developer to not spend a lot of time thinking about native navigation, and instead allow them to focus on reaping the benefits of RR - fast screen creation, making the URL your first thought, etc.
Nevertheless, we're not going to change how mobile apps actually work. Thanks to Apple, people expect a certain experience when they use an app - it's our job as developers to provide that. So, from an implementation perspective, we do have to care about iOS/Android routing semantics - it's just not necessarily what the public API represents.
@ryanflorence I agree with your last comment. iOS doesn't really have strong opinions about routing anyway.
One feature I would like to see in a routing system (however it ends up looking) is that URLs are handled top-to-bottom if there is some kind of route or router hierarchy, as opposed to bubbling them upwards. The conceptual equivalent of window.location = X would work in that world. This opens the door to building a history of URLs and making it easy to move within that history (ex: building back and forward functionality).
@ryanflorence and regarding getChildRoutes
+ location.state
-- yes, that's approximately what I'm doing.
The real thing I've struggled with, personally, is with managing history. Right now, I basically am doing the equivalent of replaceState
when I want to navigate somewhere...I don't keep a history in the traditional way. This is gross really, and I'd like the user to be able to use normal semantics in rackt/history like push, pop, etc. I think something like what @ide is proposing is the right direction...but haven't had a lot of time as of late to sit with actually implementing.
But I will say, that no matter what the final APIs end up looking like for RR+RN - being able to do things like this:
const routes = (
<NavigatorRoute id="me" path="me" tabItem={{
title: 'Me',
icon: require('image!icon-me')
}}>
<IndexRoute onEnter={(location, replaceWith) => { replaceWith(null, '/app/me/profile'); }}/>
{/** Profile **/}
<RelayRoute path="profile" component={Main} />
<RelayRoute path="settings" component={Settings} />
{/** Payment **/}
<RelayRoute path="payment-details" component={PaymentDetails} />
<RelayRoute path="add-payment-method" component={AddPaymentMethod} />
{/** History **/}
<RelayRoute path="history" component={ReceiptHistory} />
<RelayRoute path="history-summary/:ticketId/" component={ReceiptSummary} />
</NavigatorRoute>
);
on mobile is life changing.
You don't nest your Relay routes? I thought that was something of your motivation in starting down this rabbit hole.
haha @taion I do - this is just one level. I have routes above this that are relay routes.
@ryanflorence I'm going to re-open this issue for now because I feel like we have a bit to discuss here; just another thing to postpone for post-1.0.0 though.
Regarding
In addition, most mobile apps have multiple navigators (each tab in a tabbed application usually has a nested navigator)...so the problem of how to deal with hierarchal history is an issue.
Seems like this maps to a top-level "meta-router" that swaps in and out "child router" objects with separate history
objects, each of which manages its own history stack, no? Do these hierarchies need to go arbitrarily deep, or is one parent/child level sufficient? Multiple nested tab bars would be... odd...
The meta-router thing is something I've thought about, but not yet explored.
A very common pattern for an app (as far as navigator hierarchy is concerned) is this:
- App level Navigator (to go between things like onboarding and the main app)
---> Tab bar
---> Navigator 1
---> Navigator 2
---> Navigator 3
---> Navigator 4
---> Navigator 5
---> Some arbitrary modal
---> Navigator inside Modal
What do you mean "to go between things like onboarding and the main app"? You can't go back to onboarding scenes once you hit the main app, can you?
You could go back to the sign in screen when you sign out.
Note - this is just one way. You could also structure it where the onboarding is in basically a modal-type view, that then goes away when you've gone through it.
Though, in RN, it's recommended to use Navigator to do modal views in a fully React Native app, rather than using the <Modal>
component (which is recommended for hybrid apps).
Point is, it really depends. But we should be able to support this use case in one way or another.
Ah, okay, didn't understand that Modal/Navigator thing.
So the idea would be to have something like what e.g. ExNavigator
has - i.e. associate a createNavigatorHistory()
object and a routing context with each NavigatorRoute
in your above example.
Then to show a modal, do something like (stealing the syntax from #2186)
this.context.history.parent.push({
pathname: '/my-modal',
state: whatever
});
or whatever?
Yah that could work, though it would be great if the user didn't have to care whether or not they were pushing to the parent navigator or not.
It would be awesome if when you did this.context.history.push
-- it would just go to wherever it needed in the app, no matter where you are (this would be the whole "bringing web semantics to native" thing).
But then this.context.history.goBack
is more difficult...because you don't usually want to go back to some arbitrary route in your application (though I can see times when you would want to do that)...you usually want to pop the most recent view off the current navigator. Maybe it's in this case where the user of the API does have to concern themselves with their current nesting...and whether or not they want to "go back" in the context of the app, or "go back" in the context of their current nested Navigator
.
Navigator
in React Native is a JS component anyway (we don't get native perf), so I think we're ok to not use that if it's non-trivial. I think the big thing would be the ability to use Animated
to customize how the router transitions. That way we can emulate the style in each iOS and Android, or customize it.
@skevy
Got it - so essentially, expose something like a hierarchy of history
objects for go(n)
, but not for push
/replace
because it's "obvious" in some sense which history object should receive the new history entry.
Regardless I think the main point is that a lot of the complexity here would be in setting up a createNavigatorHistory
- then the routing piece itself should more or less fall out naturally.
If we completely lost all the code in this repo tomorrow, we'd still be able to build a workable if primitive router without too much effort off of history
. Going the other way around would be tougher.
@taion agreed - the nested history (I think) is crucial here.
@lwansbrough I disagree that we wouldn't want to use Navigator
inside of whatever this implementation ended up looking like. It does get relatively good perf, and will only continue to get better as Facebook dedicates more time to it -- I feel like we would want those benefits and wouldn't to reimplement from scratch. Doing it with Navigator is definitely doable...as I said, I already have a working version of this, but like @taion mentioned, it's really not the Navigator/TabBar/whatever integration that's non-trivial...it's deciding how to deal with History and how to maintain the state.
I guess most importantly, I'm unconvinced that "rolling our own" Navigator, in one way or another, would result in better performance. The performance problems with Navigator as it exists today arguably are not related to strictly Navigator -- they're underlying React Native problems (that could be solved with things like off-thread animation and incremental rendering) that should be fixed in RN core. In addition, I'm pretty sure there's a WIP implementation of Navigator
with Animated
being used under the hood at FB (not yet released).
Just to take this a little farther, is the core routing abstraction w/nested histories that we want to figure out something like... assuming the routing hierarchy looks like
<NavigatorRoute path="/">
<Route path="tabs" component={Tabs}>
<NavigatorRoute path="tab1" component={Tab1}>
<IndexRoute component={Tab1Index} />
<Route path="scene2" component={Tab1Scene2} />
</NavigatorRoute>
<Route path="tab2" component={Tab1} />
</Route>
<NavigatorRoute path="modal" component={Modal}>
<IndexRoute component={ModalIndex} />
<Route path="scene2" component={ModalScene2} />
<Route path="scene3" component={ModalScene3} />
</NavigatorRoute>
</NavigatorRoute>
Some interesting examples of transitions are e.g.
From | Action | To | Notes |
---|---|---|---|
/tabs/tab1/scene2 |
PUSH |
/modal |
PUSH onto the history for / to activate the modal, then activate the history for /modal |
/modal |
PUSH |
/modal/scene2 |
PUSH onto the history for /modal |
/modal/scene2 |
PUSH |
/modal/scene3 |
PUSH onto the history for /modal |
/modal/scene3 |
POP |
/modal/scene2 |
POP from the history for /modal |
/modal/scene2 |
POP |
/tabs/tab1/scene2 |
POP from the history for / |
@taion this looks right to me (from a routing abstraction perspective).
You might want to add to the list of "interesting examples of transitions though". For instance:
From | Action | To | Notes |
---|---|---|---|
/tabs/tab1/scene2 |
PUSH |
/tabs/tab2/scene3 |
There is now an implied routing stack possibility here...that the history for "tab2" should contain /tabs/tab2/scene1 and /tabs/tab2/scene2 . And then when the user wants to pop back to scene2 by hitting the back button, they should be able to do so. |
In that example, you wouldn't want the back button to go back to /tabs/tab1/scene2
...it wouldn't be what the user would expect.
Just tracking react native thoughts here. My assumptions are probably all wrong since 1) I don't know much about native dev and 2) I've spent 5 minutes with react native but:
Location
and eventually anAndroid
location, it'll probably be stored in memory and persisted to some sort oflocalStorage
equivalent. When the app boots we look at the URI requesting the launch first, and then thelocalStorage
(whatever it really is) second.Again, I have no idea what I'm talking about yet. ¯(º_o)/¯