GithubHelp home page GithubHelp logo

navigation-rfc's Introduction

Note: Use React Navigation now instead

This project was developed in early 2016 and has since matured and combined with Expo's Ex-Navigation project to form a new effort: React Navigation. Please use that instead, and collaborate with the community to improve it!

DEPRECATED NavigationExperimental

This was an 2016 RFC for a new Navigation system for react native, with focus on the following improvements over <Navigator />:

  • Single-directional data flow, using reducers to manipulate top-level state object
  • Navigation logic and routing must be independent from view logic to allow for a variety of native and js-based navigation views
  • Improved modularity of scene animations, gestures, and navigation bars

If you're confused about all the navigation options, look at the Navigation Comparison doc.

Code

NavigationExperimental has recently been moved from here to the open source RN repo: https://github.com/facebook/react-native/commit/a3085464f6ea36fc6b53cd0c711c048ffb1516f9

Docs

navigation-rfc's People

Contributors

chenxsan avatar corbt avatar ericvicenti avatar jasonkuhrt avatar jlebensold avatar jondot avatar kamuelafranco avatar mofelee avatar owencm avatar ptomasroos avatar rt2zz avatar rystraum avatar satya164 avatar shidhincr avatar sorrycc avatar vikeri avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

navigation-rfc's Issues

Swipe gesture to pop?

I doesn't work for latest 0.22.0-rc, it allows to swipe, but after touch finishes returns to previous state (no pop).

In RootContainer, cannot center child elements vertically

Hi,

Encountering a weird issue with regards to the RootContainer. I render various scenes via renderNavigation prop. For several of the scenes rendered, I need to center some View elements and their children (images and text) vertically and horizontally.

Seems that centering horizontally works but centering vertically does not. I wrapped almost everything with a center style for alignItems and justifyContent props to no use. Seems there is something inside RootContainer that truncates the height of the View to fit the child elements exactly so centering vertically won't do anything.

Can confirm that rendering the individual scenes outside of RootContainer will center vertically properly.

Stephen

Animated transitions using redux

This issue is a follow-up for #29.

As It was mentioned in the #29, there is a pretty simple way to make this reference implementation working with a redux (see an issue above for details). But the one question left: animated transitions using redux.

I believe we all want to keep our components as stateless as possible, but for me it looks crazy to pipe a transition animation values thru the redux flow. I'm wondering if during this discussion we can find a nice solution for this.

Variants I can see so far:

  • Use a native code for the transition management. Simply put, it's about writing a native implementation for the animation queue. Every time we need to render a navigation route, we send a route component and a bunch of animation instructions to the native platform. A native platform builds an RCTView based on the passed component and perform a transition using an animation instructions we supplied.
    I think this variant can help us solving a performance issue we have with Navigator. But of course, on the other hand it means having a two native implementation for iOS and Android. If we go this way, we need to have a strong iOS and Android developers.
  • Use Animated library. That will be a bit crazy: just imagine sending thousands of actions for every transition ๐Ÿ˜ƒ. Plus it doesn't solve a performance issue - if JS thread will be busy, it'll drop a FPS. But, this is the simplest possible implementation I can imagine.

If you have any ideas about this, please, let me know.

navigationProps in NavigationHeader?

Since rc5, There seems to be a property in NavigationHeader that replaces navigationState, called navigationProps? Seems out of place to change the name of this variable.

Component will Unmount

I'm having this warning multiple times when i do something in componentWillUnmount. Any solution to this?
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.

RFC: Rework the NavigationHeader

The current <NavigationHeader /> is not very customizable. So, here's a propsal for the API, which allows the user to customize the title, left button and right button, as well as adds a crossfade transition between the buttons. Need comments before sending a PR.

First, the following can be the code for the renderOverlay method (it's kinda similar to the NavigationBarRouteMapper,

/* @flow */
/* eslint-disable react/no-multi-comp, react/jsx-no-bind */

import React from "react-native";
import NavigationHeader from "./NavigationHeader";
import AppText from "../views/AppText";
import AppbarTouchable from "../views/AppbarTouchable";
import AppbarIcon from "../views/AppbarIcon";
import Colors from "../../Colors.json";
import routeMapper from "../routes/routeMapper";

const {
    View,
    StyleSheet,
    Platform,
    NavigationReducer
} = React;

const APPBAR_HEIGHT = Platform.OS === "ios" ? 44 : 56;

const styles = StyleSheet.create({
    header: {
        backgroundColor: Colors.primary
    },

    title: {
        flex: 1,
        flexDirection: "row",
        alignItems: "center",
        height: APPBAR_HEIGHT
    },

    titleText: {
        flex: 1,
        lineHeight: 27,
        fontSize: 18,
        fontWeight: "bold",
        color: Colors.white,
        textAlign: Platform.OS === "ios" ? "center" : "left"
    },

    button: {
        flex: 1,
        alignItems: "center",
        justifyContent: "center"
    }
});

const _getTitleComponent = (route, index, onNavigation) => {
    const routeDesc = routeMapper(route);
    const TitleComponent = routeDesc.titleComponent;

    if (TitleComponent) {
        return <TitleComponent {...routeDesc.passProps} onNavigation={onNavigation} />;
    }

    if (routeDesc.title) {
        return (
            <View style={styles.title}>
                <AppText numberOfLines={1} style={styles.titleText}>{routeDesc.title}</AppText>
            </View>
        );
    }

    return null;
};

const _getLeftComponent = (route, index, onNavigation) => {
    const routeDesc = routeMapper(route);
    const LeftComponent = routeDesc.leftComponent;

    if (LeftComponent) {
        return <LeftComponent {...routeDesc.passProps} onNavigation={onNavigation} />;
    }

    if (index !== 0) {
        return (
            <AppbarTouchable style={styles.button} onPress={() => onNavigation(new NavigationReducer.Actions.Pop())}>
                <AppbarIcon name="arrow-back" />
            </AppbarTouchable>
        );
    }
};

const _getRightComponent = (route, index, onNavigation) => {
    const routeDesc = routeMapper(route);
    const RightComponent = routeDesc.rightComponent;

    if (RightComponent) {
        return <RightComponent {...routeDesc.passProps} onNavigation={onNavigation} />;
    }
};

const renderOverlay = function(navState: Object, onNavigation: Function): Function {
    return props => {
        return (
            <NavigationHeader
                {...props}
                style={styles.header}
                getTitleComponent={(route, index) => _getTitleComponent(route, index, onNavigation)}
                getLeftComponent={(route, index) => _getLeftComponent(route, index, onNavigation)}
                getRightComponent={(route, index) => _getRightComponent(route, index, onNavigation)}
            />
        );
    };
};

export default renderOverlay;

I'm sharing the code I'm using. For simpler apps, it'll be much simpler. Many people may not need a right component, or the title will be a string for many.

Now, the implementation,

/* @flow */

import React from "react-native";

const {
    Animated,
    NavigationContainer,
    NavigationState,
    PixelRatio,
    Platform,
    StyleSheet,
    View
} = React;

const APPBAR_HEIGHT = Platform.OS === "ios" ? 44 : 56;
const STATUSBAR_HEIGHT = Platform.OS === "ios" ? 20 : 0;

const styles = StyleSheet.create({
    appbar: {
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "flex-start",
        backgroundColor: Platform.OS === "ios" ? "rgba(255, 255, 255, .7)" : "rgba(255, 255, 255, 1)",
        borderBottomWidth: Platform.OS === "ios" ? 1 / PixelRatio.get() : 0,
        borderBottomColor: "rgba(0, 0, 0, .16)",
        height: APPBAR_HEIGHT,
        paddingTop: STATUSBAR_HEIGHT,
        marginBottom: 10,
        elevation: 4,
    },

    title: {
        position: "absolute",
        left: APPBAR_HEIGHT,
        right: APPBAR_HEIGHT,
        marginHorizontal: 16,
    },

    left: {
        position: "absolute",
        left: 0
    },

    right: {
        position: "absolute",
        right: 0
    }
});

class NavigationHeader extends React.Component {
    _renderLeftComponent = (route, index, key) => {
        if (this.props.getLeftComponent) {
            return (
                <Animated.View
                    pointerEvents={this.props.navigationState.index === index ? "auto" : "none"}
                    key={key}
                    style={[
                        styles.left,
                        {
                            opacity: this.props.position.interpolate({
                                inputRange: [ index - 1, index, index + 1 ],
                                outputRange: [ 0, 1, 0 ],
                            })
                        }
                    ]}
                >
                    {this.props.getLeftComponent(route, index)}
                </Animated.View>
            );
        }

        return null;
    };

    _renderRightComponent = (route, index, key) => {
        if (this.props.getRightComponent) {
            return (
                <Animated.View
                    pointerEvents={this.props.navigationState.index === index ? "auto" : "none"}
                    key={key}
                    style={[
                        styles.right,
                        {
                            opacity: this.props.position.interpolate({
                                inputRange: [ index - 1, index, index + 1 ],
                                outputRange: [ 0, 1, 0 ],
                            })
                        }
                    ]}
                >
                    {this.props.getRightComponent(route, index)}
                </Animated.View>
            );
        }

        return null;
    };

    _renderTitle = (route, index, key) => {
        if (this.props.getTitleComponent) {
            return (
                <Animated.View
                    pointerEvents={this.props.navigationState.index === index ? "auto" : "none"}
                    key={key}
                    style={[
                        styles.title,
                        {
                            opacity: this.props.position.interpolate({
                                inputRange: [ index - 1, index, index + 1 ],
                                outputRange: [ 0, 1, 0 ],
                            }),
                            transform: [
                                {
                                    translateX: this.props.position.interpolate({
                                        inputRange: [ index - 1, index + 1 ],
                                        outputRange: [ 200, -200 ],
                                    }),
                                }
                            ],
                        }
                    ]}
                >
                    {this.props.getTitleComponent(route, index)}
                </Animated.View>
            );
        }

        return null;
    };

    render() {
        const state = this.props.navigationState;

        return (
            <View style={[ styles.appbar, this.props.style ]}>
                {state.mapToArray(this._renderLeftComponent)}
                {state.mapToArray(this._renderTitle)}
                {state.mapToArray(this._renderRightComponent)}
            </View>
        );
    }
}

NavigationHeader.propTypes = {
    position: React.PropTypes.object.isRequired,
    navigationState: React.PropTypes.instanceOf(NavigationState),
    onNavigation: React.PropTypes.func.isRequired,
    getTitleComponent: React.PropTypes.func,
    getLeftComponent: React.PropTypes.func,
    getRightComponent: React.PropTypes.func
};

export default NavigationContainer.create(NavigationHeader);

Cleanup of old routes

Awesome stuff @ericvicenti. Super excited to see where this goes.

What are your thoughts around cleaning up old routes? For instance...if you go through like, 10 nested routes...is there something we could include here that would unmount old components? If not unmounting...is there something we might be able to do in React Native land that is like "hey, this view isn't on the screen, can we cleanup after ourselves"? I'm not familiar off the top of my head how UINavigationController handles it behind the scenes...but I know it does something. Perhaps we could follow in it's footsteps, in some way.

RFC: Using JSON objects for navigation state and routes

Problem 1: We want to easily be able to save NavigationState to disk, but the (de)serialization is hard

Problem 2: The relationship between route and sub-navigationState is ambiguous. Currently the NavigationReducer demands that the route implement getNavigationStack and setNavigationStack in order to support the OnRouteNavigationState action. This feels complicated and awkward

Proposed solution: We should encourage people to model the navigation state state in dumb JSON-serializable objects rather than class instances. A NavigationState can be a string or an object, so it can be a URL or a deep object. Each child route is another NavigationState, so the deeply-nested navigation structure is simple and easy to serialize.

const NavigationStateUtils = require('NavigationStateUtils'); // this doesn't exist yet
type NavigationState = string | {
  key: ?string,
  index: ?number,
  routes: ?Array<NavigationState>,
};
var navState: NavigationState = { routes: [ 'First Page' ], index: 0, }

// in your reducer:
return NavigationStateUtils.push(navState, 'Second Page');
// output:
{ routes: [ 'First Page', 'Second Page' ], index: 1 }

This becomes handy when we want to do sophisticated actions, like the composition example with independent navStacks inside tabs:

var navState: NavigationState = {
  routes: [
    {key: 'tab0', routes: ['First Page in Tab 0' ], index: 0 },
    {key: 'tab1', routes: ['First Page in Tab 1'], index: 0 },
  ],
  index: 1
};

// in your reducer:
var tabToPushOn = 'tab0';
return NavigationStateUtils.setRoute(
  tabToPushOn,
  NavigationStateUtils.push(
    NavigationStateUtils.getRoute(tabToPushOn),
    'Second Page on Tab 0'
  )
)
// output:
{
  routes: [
    {key: 'tab0', routes: ['First Page in Tab 0', 'Second Page on Tab 0' ], index: 1 },
    {key: 'tab1', routes: ['First Page in Tab 1'], index: 0 },
  ],
  index: 1
};

cc @sahrens @hedgerwang. Thanks @gaearon for chatting last week and planting this idea in my head

NavigationStack

#bikeshedding

A NavigationStack is a list of arbitrary routes and an index

The index is a little confusing -- a stack implies LIFO, and navigation usually (at least on iOS, browsers) is push/pop/popTo. The current route is just what's on top.

Is there a use case we're trying to support with the index and the class? If so, it might be nice to rename this to RouteList or something.

Integration with redux guide?

It is not clear (from docs) how to share state between redux & navigation. I could combine all existing redux reducers with navigation reducers but how to use common state?

AnimatedStackView configScene?

Current react-native navigator has an option configScene to specify different types of animations(PushFromRight, PushFromBottom, ...). can we have similar for AnimatedStackView ?

Error when NavigationActions.Pop through gestures

When I call the Pop() method by a click handler or calling manually in my code works.
But when I try to go back using the gestures ( swiping left to right ) this happens:

I don't need this feature now, but I though it was nice to report it

screen shot 2016-01-21 at 18 06 29

Is there any working example?

I have some pain reading docs - it's hard for me to get all the concepts, reducers idea etc. It's always much easier to understand what does what when you see live example when you see how each part is needed and is working together with other parts.

Is there any example showing as much aspects of this Navigator as possible?

Support for "replace" navigation action?

I implement 'replace' support with my own reducer (just changing state.children[state.index]), it works except I'm getting yellow warning setState can only update a mounted component - i believe i've broken built-in NavigationCard animation stuff (any way to disable it?). Any possible solution to avoid warning?

screen shot 2016-03-14 at 19 50 39

AnimationStackView onNavigation should return a promise for animation completion

onNavigation() could return a promise that would allow to (optionally) track the completion of the navigation action.

For instance, if a button do a navigation push, you might want to make it display differently and disable it until the navigation is completed.
I don't think it is the responsability to the button to "check if navigation is happening and disable it itself" but if the button onPress methods allow a function that (potentially) returns a promise, we could just do: <Button onPress={() => onNavigation(new Navigation.Action.Push('route'))} />

It's great to have a fine level of control over navigation action: on a single navigation action, user could know when the navigation has successfully moved to next screen or if it was aborted (by making the promise failing). Also the fact it's a promise make a great API to the user I think, because people might just ignore this detail and don't use it, but when you need it you can do a then() on the result.

(for context, I asked the same question on the old navigation api: facebook/react-native#4824 )

Dependency of redux

Why the navigator depends on redux?

I dont think it is a good idea of having the navigation store as the same as our application.

It should be transparent for us. The navigator should be aware of his own state, why should I pass my store, doesn't make sense for me, maybe I'm missing something

In my case I don't even have redux installed on my react-native view project, it is in another project that the view can import as a module and then with react-redux connect the store to my components.

StackView and AnimatedStackView APIs

StackView has a renderRoute prop, while AnimatedStackView has a renderScene and renderOverlay prop -- should we try to keep the APIs here the same or is there a reason why we use renderRoute instead of renderScene in StackView?

Switch NavigationCard Card to use Animated.Event

I haven't adopted Animated.Event yet in NavigationCard, and I would love some help switching over to it instead of providing JS handlers to the touch responder events.

This will allow our gestures to be optimized in the future when Animated animations are eventually moved to the main thread.

<Navigator> and "endless swiping"

Hi, I posted the original issue here: facebook/react-native#5100
Then got a tip from brentvatne about this repo which I previously didn't know about.

So I'm wondering, is the goal to support that kind of navigation with the new Navigator? Creating routes dynamically "1 step ahead" of the user so he always has some pre-fetched content to swipe to.

Another question, does a ballpark ETA exist for this project?

Looking forward to try this out asap! :)

Scene Registry

Ive been looking in the documentation for some kind of Route/Scene Registry component.
It would be a component that holds all the known Screens (Home, Registration, etc), a function to retrieve the Screen component lazily, a way to parse a route path (as opposed to a key): (account/profile, account/shoppingcart, account/user/{id}). I havent found it yet in the documentation. Is that something that is part of the concept?

Simple master/detail view with header

Hi,

Does anyone have a simple example of how to create a master view (list) that when selected goes to another view with a header - ie emulating the IOSNavigator

What I am looking for is how to setup the base classes. At the moment I have:

NavigationReducer.StackReducer for state management since we push and pop views
Then in the main component I start off with a NavigationRootContainer which creates a NavigationCardStack and then renders the appropriate scene. I also have a render over lay in the NavigationCardStack which is supposed to create a header but does not work for some reason (undefined is not an object (evaluating 'this.props.navigationProps.scenes'))

const AppStateReducer = NavigationReducer.StackReducer({
    initialState: {
        key: 'root',
        index: 0,
        children: [
            {key: 'list', type: 'root page'}
        ]
    }
});

export default class App extends Component {
    constructor(props, context) {
        super(props, context);
    }
    componentWillMount() {
        this._renderNavigation = this._renderNavigation.bind(this);
        this._renderTitleComponent = this._renderTitleComponent.bind(this);
        this._renderHeader = this._renderHeader.bind(this);
        this._renderScene = this._renderScene.bind(this);
    }
    render() {
        return (
            <NavigationRootContainer
                reducer={AppStateReducer}
                persistenceKey="mykey"
                renderNavigation={this._renderNavigation}
                ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
            />
        );
    }

    _renderNavigation(navigationState, onNavigate) {
        if (!navigationState) {
            return null;
        }

        return (
            <NavigationCardStack
                navigationState={navigationState}
                renderOverlay={this._renderHeader}
                renderScene={this._renderScene}
            />
        );
    }

    _renderScene(props: NavigationSceneRendererProps) {
        switch (props.scene.key) {
            case 'list': {
                return <MyListView />
            }
            break;
            case 'first': {
                return <MyChildView />
            }
            break;
        }

        return null;
    }

    _renderHeader(props) {
        return (
            <NavigationHeader
                {...props}
                renderTitleComponent={this._renderTitleComponent}
            />
        );
    }

    _renderTitleComponent(props) {

        return (
            <NavigationHeader.Title style={{backgroundColor: 'green'}}>
                'MyApp'
            </NavigationHeader.Title>
        );
    }
}

Problem with basic example code

Dear Eric,

Thanks for your work on the NavigationExperimental - we just pulled the 0.21 react-native release and were glad to see you provided an implementation that works well with Redux.

We tried the NavigationBasicExample as a starter - however if we replicate the example we get an undefined is not an object (evaluating initialStates.length) regarding the NavigationStackReducer.js:88 - could you help us fix this error in the example code or hint at if itโ€™s on our end what we have done wrong?

Kind regards,
Thomas

Navigation for pure Flux app?

I'd like to propose the minimum set of APIs that we'd need to build a flux style application with navigation.

Imagine that we're building a TodoList app with the ordinary flux implementation, and all my navigation states are maintained by one singleton flux store called TodoNavigationStore:


const appDispatcher = new Dispatcher();

class TodoNavigationStore extends FluxStore {
  constructor() {
    super(appDispatcher);
    this._tasks = new NavigationStack([new TaskRoute('first task')]);
  }

  getNavigationStack() {
    return this._tasks;
  }
}

module.exports = new TODONavigationStore();

and the navigation state is render by the NavigationAnimatedView

class TodoApp extends React.Component {
  static getStores() {
    return [TodoNavigationStore];
  }

  static calculateState() {
    return {
      navigationStack: AppNavigationStore.getNavigationStack(),
    }
  }

  render() {
    return (
      <NavigationAnimatedView
        navigationStack={this.state.navigationStack}
      />
    );
  }
}

TodoApp = Flux.createContainer(TodoApp);

as you could imagine, we'd be able to map the add task action to navigation changes.


class TodoNavigationStore extends FluxStore {

  // handle dispatcher action.
  __onDispatch(action): void {  
    switch (action.type) {
      case 'add_task':
        this._tasks = this._tasks.push(new TaskRoute(action.text));
        this.__emitChange();
    }
  }
}

of course, we could also map the close task action to navigation changes, too.


class TodoNavigationStore extends FluxStore {

  __onDispatch(action): void {  
    switch (action.type) {
      // .....

      case 'close_task':
        const route = action.route;
        this._tasks = this._tasks.remove(route);
        this.__emitChange();
    }
  }
}

now let's consider a more complicated case. Say that we want to open a modal composer. When the composer is show, the tasks are hidden. So the store may look like this:

class TodoNavigationStore extends FluxStore {
  constructor() {
    // ...

    this._tasks = new NavigationStack([new TaskRoute('hello')]);

    this._showComposer = false;
    this._composer = new NavigationStack([new ComposerRoute]);
  }

  getNavigationStack() {
    return this._showComposer ? this._composer : this._tasks;
  }

and the actions that open or close the compose changes the navigation stack.

class TodoNavigationStore extends FluxStore {
  // ......

  getNavigationStack() {
    return this._showComposer ? this._composer : this._tasks;
  }

  __onDispatch(action): void {

    switch (action.type) {
      // ....
      case 'show_composer':
        this._showComposer = true;
        this.__emitChange();
        break;

      case 'hide_composer':
        this._showComposer = false
        this.__emitChange();
        break;

      case 'add_task':
        this._showComposer = false
        this._tasks = this._tasks.push(new TaskRoute(action.text));
        this.__emitChange();
        break;
    }
}

These code snippets should demonstrate that how could we build reactive flux application with navigation APIs that play nice with flux.

Having said that, here are the minimum set of APIs that I consider useful in flux application.

  • NavigationStack : Immutable data type that represents the list of routes
  • NavigationAnimatedView:: The controlled component that renders the navigation stack and handles gesture and transitions.

Note that I don't mention anything about the NavigationAction or things like navigation reducer.

The idea is that when the application becomes complicated, it'd make sense to separate the navigation states from the views and manage the navigation state at once place.

I came to realize that there is no only one way to define the complete navigation actions and hierarchies of apps so we should not focus on making something that fits all.

Instead, we should provide basic utils to people to build navigation with their own existing architecture such as flux.

Feedback is welcomed.

"key" property for routes

Seems every route needs to provide a Key property which we use as React keys. Any idea on how to handle this when there are multiple instance of the same route in the state? Keys no longer stay unique!

A better explanation of scenes and children

I would love to see a better conceptual explantion of scenes, children. There are various examples that seem to use different structures of scenes but no clear conceptual explanation of the reducer structure.

I'm running into some issues with scenes etc that I don't understand. And I'm not properly able to analyze if those are bugs or implementation issues because I'm not fully understanding the conceptual structure.

Bottom to top transition?

i can't find how to make this transition with NavigationCard. Example shows left-to-right only...

AnimateView Rendering Scenes before they're "Ready"?

I have following state structure:

const navigationState = {
  key: 'Root',
  children: [
    { key: 'Login' },
    { key: 'Dashboard' }
  ]
}

and a render method which looks something like:

<AnimatedView
  navigationState={navigationState}
  renderScene={renderScene}
/>

function renderScene (props) {
  return (
    <Card
      renderScene={({ scene }) => {
        const component = findComponent(scene.navigationState)
        return <component />
      }}
    />
  )
}

The 'Dashboard' (container) component requires some data which is requested on log in, however the AnimatedView renders all it's scenes when it's initialised (even though they're not visible or "active"). In my case this will throw an error.

How can I prevent a scene from rendering before it has the data it requires? It seems a bit daft to have to include conditionals for every component. I'm feel like I'm missing something here :/

AnimatedView incorrectly renders focused scene when index !== children.length-1

In the NavigationCompositionExample, tabs are implemented using a NavigationView. NavigationView renders a single Scene and does so by unmounting any previous Scenes. This is not desirable as component state is lost when switching tabs.

I'm trying to implement tabs using AnimatedView and StackReducer to keep the views mounted when navigated to and from using JumpToIndex or JumpTo.

      +------------+
    +-+            |
  +-+ |            |
  | | |            |
  | | |  Focused   |
  | | |   Card     |
  | | |            |
  +-+ |            |
    +-+            |
      +------------+

AnimatedView renders each Card in the order Scenes are present in ParentState. It assumes the children.length-1 is the focused Card. So setting index in ParentState to anything other than the last Scene has no effect except that the overlay renders correctly since it's passed the Scene at the correct index when render is called:

renderOverlay({
        layout: this._layout,
        navigationState,
        onNavigate,
        position,
        scene: scenes[navigationState.index],
        scenes,
      });

As for the Scenes, the focused Card renders as the one at children.length-1. So the index provided by the reducer is ignored.

One way to fix this is to look at how AnimatedView delegates transition management to StyleInterpolators.

Take forHorizontal for example:

function forHorizontal(props: NavigationSceneRendererProps): Object {
  const {
    layout,
    position,
    scene,
  } = props;

  const index = scene.index;
  const inputRange = [index - 1, index, index + 1];
  const width = layout.initWidth;

  const opacity = position.interpolate({
    inputRange,
    outputRange: [1, 1, 0.3],
  });

  const scale = position.interpolate({
    inputRange,
    outputRange: [1, 1, 0.95],
  });

  const translateY = 0;
  const translateX = position.interpolate({
    inputRange,
    outputRange: [width, 0, -10],
  });

  return {
    opacity,
    transform: [
      { scale },
      { translateX },
      { translateY },
    ],
  };
}

Here the implementation "configures" the focused Card to be the last in the stack. Since, StyleInterpolator is the delegate that coordinates how the actual views transition on the screen, unlike AnimatedView, it's possible to change the implementation to render the focused view correctly and make transition animations work as expected.

The solution ๐Ÿšง I came up with is to push the views to the right of the focused Card off screen:

function forHorizontal(props: NavigationSceneRendererProps): Object {
  const {
    layout,
    position,
    scene,
  } = props;

  const index = scene.index;
  const inputRange = [index - 1, index, index + 1];
  const width = layout.initWidth;
  const translateY = 0;

  if(props.navigationState.index < scene.index && width <= 0) {
   // Move off screen
    const translateX = layout.width.interpolate({
      inputRange: [0, 1],
      outputRange: [0, -1],
    });

    return {
      opacity: 0, // Avoid flicker
      transform: [
        { translateX },
        { translateY },
      ],
    };
  }

  const opacity = position.interpolate({
    inputRange,
    outputRange: [1, 1, 0.3],
  });

  const scale = position.interpolate({
    inputRange,
    outputRange: [1, 1, 0.95],
  });

  const translateX = position.interpolate({
    inputRange,
    outputRange: [width, 0, -10],
  });

  return {
    opacity,
    transform: [
      { scale },
      { translateX },
      { translateY },
    ],
  };
}

Here I'm using width <= 0 ๐Ÿ˜ญ to determine the direction of navigation. A better way would be to include a prevIndex value in the ParentState type so that we can do this:

navigationState.index >= navigationState.prevIndex

Of course, we'd also have to make some changes to jumpToIndex, jumpTo etc in NavigationStateUtils to implicitly keep track of prevIndex.

Alternatively, we could create a custom reducer to achieve pretty much the same thing. But that feels like boilerplate.

{
  ...NavigationStateUtils.jumpToIndex(state, state.index+1),
  prevIndex: state.index
}

For those of you who are looking into implementing tabs using this approach, the transition animations can be "turned off" for tabs by supplying an applyAnimation fn to AnimatedView:

function applyAnimation(
position: NavigationAnimatedValue,  
navigationState: NavigationParentState): void {
  position.setValue(navigationState.index);
}

<NavigationAnimatedView
  applyAnimation={applyAnimation}
  ... />

So far this solution has been working great. But I strongly think some of this can be moved into NavigationExperimental.

I'd be happy to turn some of these ideas into a PR. Or perhaps there is another way to achieve this that I'm missing?

Oh and, thanks for all the work you guys have been doing on these. ๐Ÿš€

๐Ÿบ

Rendering different components

There is a way to push a new scene to the navigation with the scene name and a component to be rendered in that scene?

edit: I'm writing a better example case. I'll udpate this issue in a bit

Doc Improvement: Custom reducers should clearly be encouraged/supported

I'd love to see a documented support for providing this navigation model a custom reducer / onNavigation method which is propagated to build in or library provided Components like NavigationCard or NavigationHeader.

The docs currently make it sound like there is only a bunch of default actions, which could be extended, but the only supported reducer is NavigationReducer. Is that the case?

Error building DependencyGraph: Error: Naming collision detected:

I installed navigation-rfc through npm install https://github.com/ericvicenti/navigation-rfc

After that I created an app.js which is NOT included to index.ios.js yet.
You can find the content of app.js below.

'use strict';

var NavigationContainer = require('navigation-rfc/Navigation/NavigationContainer');
var NavigationReducer = require('navigation-rfc/Navigation/NavigationReducer');
var NavigationState = require('navigation-rfc/Navigation/NavigationState');

var React = require('react-native');

var {
  Text
} = React;

var App = React.createClass({
  render: function() {
    return (
      <NavigationContainer.RootContainer
      initialState={new NavigationState(['first page'], 0)}
      reducer={NavigationReducer}
      renderNavigator={(navState, onNavigation) => (
        <View>
          <Text>Current page: {navState.get(navState.index)}</Text>
          <Text
            onPress={() => {
              onNavigation(new NavigationReducer.Actions.Push('page #' + navState.size));
            }}>
            Push page #{navState.size}
          </Text>
          <Text
            onPress={() => {
              onNavigation(new NavigationReducer.Actions.Pop());
            }}>
            Pop
          </Text>
          <Text onPress={this.props.onExampleExit}>Exit Basic Nav Example</Text>
        </View>
      )}
      />
    );

  }
});

module.exports = App;

Then I tried to build my project but it is failing throwing this exception in the terminal

[1:29:16 PM] <END>   Building (deprecated) Asset Map (115ms)
Error building DependencyGraph:
 Error: Naming collision detected: /Users/myUser/Projects/MyProject/node_modules/navigation-rfc/node_modules/react-native/Libraries/Promise.js collides with /Users/myUser/Projects/MyProject/node_modules/react-native/Libraries/Promise.js
    at HasteMap._updateHasteMap (HasteMap.js:123:13)
    at HasteMap.js:94:28
    at tryCallOne (/Users/myUser/Projects/MyProject/node_modules/promise/lib/core.js:37:12)
    at /Users/myUser/Projects/MyProject/node_modules/promise/lib/core.js:123:15
    at flush (/Users/myUser/Projects/MyProject/node_modules/asap/raw.js:50:29)
    at nextTickCallbackWith0Args (node.js:456:9)
    at process._tickCallback (node.js:385:13)
Thu, 14 Jan 2016 12:29:16 GMT ReactNativePackager:SocketServer exit code: 1

    at terminate (SocketClient.js:59:17)
    at Socket.<anonymous> (SocketClient.js:74:37)
    at emitOne (events.js:77:13)
    at Socket.emit (events.js:169:7)
    at emitErrorNT (net.js:1256:8)
    at nextTickCallbackWith2Args (node.js:478:9)
    at process._tickCallback (node.js:392:17)

I know it is a dependency conflict but it is the first time that I get that so I don't know how to approach this issue.

Undefined is not a constructor when calling NavigationActions.push

I have this component which is already been renderized by the navigator and router.

var First = React.createClass({
  render: function() {
    return (
      <View><Text onPress={ () => {
        this.props.onNavigation(
          new NavigationActions.push({name: 'second', props: {id: 'someId'}})
        );
      }}>GOOOO</Text></View>
    );
  }
});

But when I click on the button to trigger the push of the new route. I get this error below:

screen shot 2016-01-20 at 16 33 55

Position and Layout props

Since you guys changed the API I was trying to make the navigator work again.

Every time that I make it work something is changed in your code and I have to understand what is going on and make the changes. I'm in a infinite loop here hahah.

Now I'm struggling to know from where the position and layout props comes from.

edit: Just debugged this project and realised that position it is a AnimatedValue, but still have no idea were comes from.

I'm using the architeture that @satya164 suggested.
However I'm getting this error: Can't find variable: position

I'll share with you what I have here

  • App.js
var App = React.createClass({
  render: function() {
    return (
      <NavigationContainer.RootContainer
        initialState={{routes: [ 'first' ], index: 0}}
        renderNavigator={renderNavigator}
      />
    );
  }
});
  • renderNavigator.js
const renderNavigator = (navState, onNavigation) => {
  if (!navState) {
    return null;
  }

  return (
    <NavigationAnimatedView
      navigationState={navState}
      style={styles.animatedView}
      renderOverlay={renderOverlay(navState, position, layout, onNavigation)}
      renderScene={renderScene(route, index, navState, position, layout, onNavigation)}
      />
  );
};
  • renderOverlay.js
const renderOverlay = function(navState, position, layout, onNavigation) {
  return props => {
    return (
      <NavigationHeader
        {...props}
        renderLeftComponent={(route, index) => {
          if (index === 0) {
            return null;
          }

          return <NavigationExampleBackButton onNavigation={onNavigation} />;
        }}
        renderTitleComponent={route => <NavigationHeaderTitle>{route}</NavigationHeaderTitle>}
        />
    );
  };
};
  • renderScene.js
const renderScene = function(route, index, navState, position, layout, onNavigation) {
  return props => {
    const route = props; // eslint-disable-line react/prop-types

    const {
      component: RouteComponent
    } = routeMapper(route);

    return (
      <NavigationCard
        {...props}
        route={route}
        index={index}
        navState={navState}
        position={position}
        layout={layout}
        >
        <RouteComponent
          {...route.props}
          style={styles.container}
          onNavigation={onNavigation}
          />
      </NavigationCard>
    );
  };
};
  • routerMap.js
export default function(route) {
  switch (route) {
    case 'first':
      return {
        component: First,
        title: 'First Route'
      };
    case 'second':
      return {
        component: Second,
        title: 'Scond'
      };
    default:
      return null;
  }
}

Questions about the project

Hello there,

I extract my redux logic to a different repo so I can used this shared code with my react application and react-native application.

The thing is that I'm not into use the standard navigator because it not respect the concept of "your state must be a snapshot of your UI over time".
When I say that is I can push a new screen to the navigator but doing that it doesnt change the state.
I thought to create a separated reducer to control my navigation but doing that I'll not respect the one way data flow paradigm.

With that in mind I started to have some brainstorm with some people into the redux channel on discord and some one suggest me this module.

I'm pretty excited about the idea but I need to know how far are you guys on it, what is the roadmap, what is the next steps of implementation.

Since the idea of this project is exactly what I was thinking in build it will be a good idea if instead of creating from scratch I start to contribute to this one, however, to me be able to do that I need to know more about the project because unfortunately I dont have free time to spend in a project that will not be useful for me. ( I hope you understand that )

Maybe we can have a hangout chat or even here in github a discussion the project.

I hope you have a nice day and I'm kinda excited to be part of this !

Memory increasing and delays in transitioning on current Navigator, is it the same in this proposal?

Does this new implementation suffer from the memory problems of the current Navigator?

See this issue:
facebook/react-native#4740

Will we have the same problems?

This is the problem i have with the current Navigator, will I have the same problem with this implementation too?

In our iOS app, memory is increasing even when the app is "idle" (user does nothing and views are settled).

If I use the replace instead of push in the Navigator then this does not happen, so it is clearly a Navigator issue. Is there any fix for this, as this made us use only the replace method which means that we have no back functionality?

Data structure for navigation states transition

Hi

I'd like to talk about state transition.

In most cases, views are not rendering against the navigation state. Instead, it's rendered with the states transition.

Let's assume that the navigation state is just a simple list of routes

for example,

[A, B, C]

and say that we want to replace route C with route D, so that the navigation state shall look like this

[A, B, D]

the question is, when the navigation transition is presented with animation, we'd have to provide both the old state, the new state, and also the progress value that indicates the progressiveness of the transition between the two state.

do you have anything in mind to solve this kind of problem?

Thanks.

NavigationCard doesn't allow custom styles

The NavigationCard doesn't allow custom style. I'm trying to have a transparent background for my navigation but I can't pass any style to do that

  <Animated.View
    {...panHandlers}
    style={[styles.main, animatedStyle]}>
    {this.props.renderScene(sceneProps)}
  </Animated.View>

So I can't pass own style to NavigationCard

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.