remix-run / react-router

Declarative routing for React
https://reactrouter.com
MIT License
53.1k stars 10.31k forks source link

React Native #743

Closed ryanflorence closed 8 years ago

ryanflorence commented 9 years ago

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:

  1. Looks like a component is mounted as the "top level" thing, the router listens to a location and then renders an app, so the basic usage is probably going to be slightly different, your top level component has route definitions and gets a url passed in as a prop or something.
  2. We need some sort of iOS Location and eventually an Android location, it'll probably be stored in memory and persisted to some sort of localStorage equivalent. When the app boots we look at the URI requesting the launch first, and then the localStorage (whatever it really is) second.

Again, I have no idea what I'm talking about yet. ¯(º_o)/¯

petehunt commented 9 years ago

There will be a localStorage with the same API as the regular one except async

petehunt commented 9 years ago

Possibly of interest: http://applinks.org/

mjackson commented 9 years ago

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?

ryanflorence commented 9 years ago

I'm just making wild guesses, but every app has Bundler.registerComponent('MoviesApp', () => MoviesApp); which appears to be rendered automatically.

appsforartists commented 9 years ago

The components would have to be different too. You don't have a DOM <a>, so <Link> needs to reimplement it.

gaearon commented 9 years ago

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.

appsforartists commented 9 years ago

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>
gaearon commented 9 years ago

Yes, I suppose you're right. I wonder how having different targets (like Web and React Native) would work for a library.

appsforartists commented 9 years ago

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.

agundermann commented 9 years ago

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.

lwansbrough commented 9 years ago

@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.)

johanneslumpe commented 9 years ago

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?

ryanflorence commented 9 years ago

we're working on this, don't think we need the issue anymore.

danscan commented 9 years ago

Any update on this? React Router on RN would be HUGE.

VonD commented 9 years ago

+1

brentvatne commented 9 years ago

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

lwansbrough commented 9 years ago

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?

danscan commented 9 years ago

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.

auser commented 9 years ago

+1

simonexmachina commented 9 years ago

+1

esnunes commented 9 years ago

Where can I find a sample of react-router + react-native?

ippy04 commented 9 years ago

:+1:

alexrecarey commented 9 years ago

+1

richarddewit commented 9 years ago

+1

keeth commented 9 years ago

:+1:

joshhornby commented 9 years ago

@ryanflorence Any update on this? Just about to start a big project in RN and would love to use React Router.

taion commented 9 years ago

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

skevy commented 9 years ago

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.

ryanflorence commented 9 years ago

getChildRoutes + location.state I've imagined being the levers to get "implied" routes.

ryanflorence commented 9 years ago

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.

skevy commented 9 years ago

@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.

ide commented 9 years ago

@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).

skevy commented 9 years ago

@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.

skevy commented 9 years ago

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.

taion commented 9 years ago

You don't nest your Relay routes? I thought that was something of your motivation in starting down this rabbit hole.

skevy commented 9 years ago

haha @taion I do - this is just one level. I have routes above this that are relay routes.

taion commented 9 years ago

@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...

skevy commented 9 years ago

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
taion commented 9 years ago

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?

skevy commented 9 years ago

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.

taion commented 9 years ago

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?

skevy commented 9 years ago

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.

lwansbrough commented 9 years ago

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.

taion commented 9 years ago

@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.

skevy commented 9 years ago

@taion agreed - the nested history (I think) is crucial here.

skevy commented 9 years ago

@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.

skevy commented 9 years ago

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).

taion commented 9 years ago

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 /
skevy commented 9 years ago

@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.
skevy commented 9 years ago

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.