Coder Social home page Coder Social logo

ex-navigation's Introduction

🚨This library is no longer maintained 🚨

Please use "react-navigation", or other alternatives instead.


ExNavigation

A route-centric, batteries-included navigation library for Expo and React Native that works seamlessly on Android and iOS.

A few of our favorite features

  • Android back button handling (it just works, no need to do anything)
  • Tab bar navigation
  • Drawer navigation
  • Sliding tab navigation
  • Optional blurred translucent backgrounds in navigation and tab bar on iOS
  • Alert bars
  • Declarative configuration co-located with your routes
  • Typed with Flow

Help / Support / Questions

We don't provide any realtime support for ExNavigation questions. If you join the Expo Slack and ask a question there, we will direct you to this section of the README. We suggest the following resources:

Installation

As of version 1.9.0, ExNavigation only supports React Native versions >= 0.36.0 due to changes to the css-layout algorithm in React Native core.

  • npm i @expo/ex-navigation babel-preset-react-native-stage-0 --save
  • Change your .babelrc (if you have one, if not, then create one):
{
  "presets": ["react-native-stage-0/decorator-support"]
}

Note: Comprehensive documentation is coming soon! For now, check out the example project in example/. This lib is very much a work in progress.

How to run the example project

or use this link in your mobile phone: https://expo.io/@community/ex-navigation-example

How is this different from what is built into React Native?

NavigationExperimental ships with React Native, it is powerful and flexible, and that comes at the cost of exposing some internals to the app developer. ExNavigation is built on top of NavigationExperimental with the aim of providing a more feature-rich out of the box experience.

A minimal navigation set up

To give you an idea of what the required pieces of are, the following includes only the minimal code necessary to get ExNavigation working.

import React from 'react';
import {
  AppRegistry,
  Text,
  View,
} from 'react-native';

/**
 * If you're using Expo, uncomment the line below to import Exponent
 * BEFORE importing `@expo/ex-navigation`. This sets the status bar
 * offsets properly.
 */
// import Expo from 'expo';

import {
  createRouter,
  NavigationProvider,
  StackNavigation,
} from '@expo/ex-navigation';

/**
  * This is where we map route names to route components. Any React
  * component can be a route, it only needs to have a static `route`
  * property defined on it, as in HomeScreen below
  */
const Router = createRouter(() => ({
  home: () => HomeScreen,
}));

class App extends React.Component {
  render() {
    /**
      * NavigationProvider is only needed at the top level of the app,
      * similar to react-redux's Provider component. It passes down
      * navigation objects and functions through context to children.
      *
      * StackNavigation represents a single stack of screens, you can
      * think of a stack like a stack of playing cards, and each time
      * you add a screen it slides in on top. Stacks can contain
      * other stacks, for example if you have a tab bar, each of the
      * tabs has its own individual stack. This is where the playing
      * card analogy falls apart, but it's still useful when thinking
      * of individual stacks.
      */
    return (
      <NavigationProvider router={Router}>
        <StackNavigation initialRoute={Router.getRoute('home')} />
      </NavigationProvider>
    );
  }
}

class HomeScreen extends React.Component {
  /**
    * This is where we can define any route configuration for this
    * screen. For example, in addition to the navigationBar title we
    * could add backgroundColor.
    */
  static route = {
    navigationBar: {
      title: 'Home',
    }
  }

  render() {
    return (
      <View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
        <Text>HomeScreen!</Text>
      </View>
    )
  }
}


AppRegistry.registerComponent('main', () => App);

Push and popping routes

 const Router = createRouter(() => ({
   home: () => HomeScreen,
+  about: () => AboutScreen,
 }));

 class HomeScreen extends React.Component {
    render() {
     return (
       <View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
         <Text>HomeScreen!</Text>
+        <Text onPress={this._goToAbout}>
+          Push about route
+        </Text>
       </View>
     )
   }
+
+  _goToAbout = () => {
+    this.props.navigator.push(Router.getRoute('about'));
+  }
 }

+ class AboutScreen extends React.Component {
+  static route = {
+    navigationBar: {
+      title: 'About',
+    }
+  }
+
+  render() {
+    return (
+      <View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
+        <Text>AboutScreen!</Text>
+        <Text onPress={this._goBackHome}>
+          Go back home
+        </Text>
+      </View>
+    )
+  }
+
+  _goBackHome = () => {
+    this.props.navigator.pop();
+  }
+}

In the above example you will see that we push and pop routes to and from the stack by calling those functions on the navigator prop. This is a prop that is passed into all components that you registered with the router. If you need to access the navigator on a component that is not a route, you can either pass it in manually from your route component or use withNavigation as a decorator on the component:

import React from 'react';
import { Text } from 'react-native';
import { withNavigation } from '@expo/ex-navigation';

@withNavigation
class BackButton extends React.Component {
  render() {
    return <Text onPress={this._goBack}>Go back</Text>
  }

  _goBack = () => {
    if (this.props.navigator.getCurrentIndex() > 0) {
      this.props.navigator.pop();
    }
  }
}

Alternatively, rather than importing Router each time, you may pass the route's name directly:

_goToAbout = () => {
-  this.props.navigator.push(Router.getRoute('about'));
+  this.props.navigator.push('about');
}

… bearing in mind you will loose the ability to type check the route (if using Flow).

Passing params to a route

  class HomeScreen extends React.Component {

   _goToAbout = () => {
-    this.props.navigator.push(Router.getRoute('about'));
+    this.props.navigator.push(Router.getRoute('about', {name: 'Brent'}));
   }
 }

 class AboutScreen extends React.Component {
   static route = {
     navigationBar: {
-      title: 'About',
+      title(params) {
+        return `Greeting for ${params.name}`;
+      },
     }
   }

   render() {
     return (
       <View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
-        <Text>AboutScreen!</Text>
+        <Text>AboutScreen! Hello {this.props.route.params.name}</Text>
         <Text onPress={this._goBackHome}>
           Go back home
         </Text>

Updating route params

Sometimes you don't have all of the data that you need to set the navigation bar title when you mount the route - for example, if you navigate to a user profile screen by user id and need to fetch the profile data before you know what the name is. In this case, one solution is to use the updateCurrentRouteParams function available on StackNavigation navigators.

 class AboutScreen extends React.Component {
   static route = {
     navigationBar: {
       title(params) {
-        return `Greeting for ${params.name}`;
+        if (typeof params.isCool === 'undefined') {
+          return '';
+        }
+
+        return params.isCool ? `Hey cool person!` : `zzz`;
       },
     }
   }

+  componentDidMount() {
+    setTimeout(() => {
+      this.props.navigator.updateCurrentRouteParams({
+        isCool: this.props.route.params.name === 'Brent'
+      })
+    }, 1000);
+  }
+

Make navigation bar buttons update based on route or app state

See the following example for details on how to connect your buttons to the navigator or Redux to perform actions: https://github.com/brentvatne/ex-navigation-conditional-buttons-example

StackNavigation actions

As you saw above, you can push and pop routes. The following is a full list of functions that can be called on StackNavigation navigators.

  • push: add a route to the top of the stack
  • pop(n): remove n routes from the top of the stack, defaults to 1
  • popToTop: remove all but the first route from the stack
  • replace: replace the current route with a given route
  • showLocalAlert: show an alert bar with given text and styles
  • hideLocalAlert: hide an active alert bar
  • immediatelyResetStack: reset the current stack to the given stack
  • updateCurrentRouteParams: update route params as in the above example

Working with the navigation bar

The navigation bar configuration exposes a set of useful options that should allow you to do most things that you will want to do with it.

You specify the configuration for the navigationBar on the route component, or on a StackNavigation component.

On a route component

When you configure the navigationBar on a route component, the configuration only applies to that specific component. This is usually useful for specifying the title or components to render on the left or right of the title.

 class HomeScreen extends React.Component {
   _goToAbout = () => {
     this.props.navigator.push(Router.getRoute('about', {name: 'Brent'}));
   }
 }

 class AboutScreen extends React.Component {
   static route = {
     navigationBar: {
       title: 'Title goes here',
       renderRight: (route, props) => <SignOutButton name={route.params.name} />
     }
   }

   // ...
 }

 @connect()
 class SignOutButton extends React.Component {
   render() {
      return (
        <TouchableOpacity onPress={this.props.dispatch(Actions.signOut())}>
          <Text>Sign out {this.props.name}</Text>
        </TouchableOpacity>
      );
   }
 }

On StackNavigation

You can configure the defaultRouteConfig for all routes within a StackNavigation to save you needing to specify properties like the navigationBar backgroundColor and tintColor (color to use for the title and back button or drawer menu hamburger button).

class App extends React.Component {
  render() {
    return (
      <NavigationProvider router={Router}>
        <StackNavigation
          defaultRouteConfig={{
            navigationBar: {
              backgroundColor: '#000',
              tintColor: '#fff',
            }
          }}
          initialRoute={Router.getRoute('home')}
        />
      </NavigationProvider>
    );
  }
}

navigationBar properties

  • title - a string or a function that returns a string. The function is provided with the route params as the first argument.
  • titleStyle - Text.propTypes.style object to use for the title.
  • backgroundColor - the background color to use for the navigationBar.
  • tintColor - the color to use for the title text and back button or drawer button icons.
  • visible - boolean that indicates whether the navigationBar should be visible for this route.
  • translucent - iOS and Expo only, use background blur on the navigationBar, like in the Apple Podcasts app, for example.
  • borderBottomWidth - the width of the bottom border
  • borderBottomColor - the color of the bottom border
  • renderLeft - a function that should return a React component that will be rendered in the left position of the navigationBar.
  • renderTitle - a function that should return a React component that will be rendered in the title position of the navigationBar.
  • renderRight - a function that should return a React component that will be rendered in the right position of the navigationBar.
  • renderBackground - a function that should return a React component that will be rendered in the background of the navigationBar.

TabNavigation

A minimal example using tabs:

import {
  StackNavigation,
  TabNavigation,
  TabNavigationItem as TabItem,
} from '@expo/ex-navigation';


// Treat the TabScreen route like any other route -- you may want to set
// it as the initial route for a top-level StackNavigation
class TabScreen extends React.Component {
  static route = {
    navigationBar: {
      visible: false,
    }
  }

  render() {
    return (
      <TabNavigation
        id="main"
        navigatorUID="main"
        initialTab="home">
        <TabItem
          id="home"
          title="Home"
          selectedStyle={styles.selectedTab}
          renderIcon={(isSelected) => <Image source={require('./assets/images/home.png')} /> }>
          <StackNavigation
            id="home"
            navigatorUID="home"
            initialRoute={Router.getRoute('home')}
          />
        </TabItem>

        <TabItem
          id="posts"
          title="Posts"
          selectedStyle={styles.selectedTab}
          renderIcon={(isSelected) => <Image source={require('./assets/images/posts.png')} /> }>
          <StackNavigation
            id="posts"
            initialRoute={Router.getRoute('posts')}
          />
        </TabItem>

        <TabItem
          id="profile"
          title="Profile"
          selectedStyle={styles.selectedTab}
          renderIcon={(isSelected) => <Image source={require('./assets/images/profile.png')} /> }>
          <StackNavigation
            id="profile"
            initialRoute={Router.getRoute('profile')}
          />
        </TabItem>
      </TabNavigation>
    );
  }
}

See an example of TabNavigation in a real app here.

If you'd like to switch tabs programmatically (eg: a notification arrives and you want to jump to a notifications tab, or you tap on a button to open your profile but within another tab) you can use jumpToTab. For the code below to work, we need the navigatorUID prop to be set on TabNavigator, as with the example above.

<TouchableOpacity
  onPress={() => {
    this.props.navigation.performAction(({ tabs, stacks }) => {
      tabs('main').jumpToTab('profile');
      stacks('home').push(route);
    });
  }}
/>

DrawerNavigation

A minimal example using the DrawerNavigation:

import {
  StackNavigation,
  DrawerNavigation,
  DrawerNavigationItem,
} from '@expo/ex-navigation';

// Treat the DrawerNavigationLayout route like any other route -- you may want to set
// it as the intiial route for a top-level StackNavigation

class DrawerNavigationLayout extends React.Component {
  static route = {
    navigationBar: {
      visible: false,
    }
  };

  render() {
    return (
      <DrawerNavigation
        id='main'
        initialItem='home'
        drawerWidth={300}
        renderHeader={this._renderHeader}
      >
        <DrawerNavigationItem
          id='home'
          selectedStyle={styles.selectedItemStyle}
          renderTitle={isSelected => this._renderTitle('Home', isSelected)}
        >
          <StackNavigation
            id='home'
            initialRoute={Router.getRoute('home')}
          />
        </DrawerNavigationItem>

        <DrawerNavigationItem
          id='about'
          selectedStyle={styles.selectedItemStyle}
          renderTitle={isSelected => this._renderTitle('About', isSelected)}
        >
          <StackNavigation
            id='about'
            initialRoute={Router.getRoute('about')}
          />
        </DrawerNavigationItem>

      </DrawerNavigation>
    );
  }

  _renderHeader = () => {
    return (
      <View style={styles.header}>
      </View>
    );
  };

  _renderTitle(text: string, isSelected: boolean) {
    return (
      <Text style={[styles.titleText, isSelected ? styles.selectedTitleText : {}]}>
        {text}
      </Text>
    );
  };
}

const styles = StyleSheet.create({
  header: {
    height: 20
  },

  selectedItemStyle: {
    backgroundColor: 'blue'
  },

  titleText: {
    fontWeight: 'bold'
  },

  selectedTitleText: {
    color: 'white'
  }
});

Integrate with your existing Redux store

Behind the scenes ExNavigation manages your navigation state using Redux in its own store. If you'd like to store the navigation state on your app's store, you can use the createStoreWithNavigation function when creating the store and then manually provide the NavigationContext, initialized with your app's store.

/* Your store definition, let's say state/Store.js */

import { createNavigationEnabledStore, NavigationReducer } from '@expo/ex-navigation';
import { combineReducers, createStore } from 'redux';

const createStoreWithNavigation = createNavigationEnabledStore({
  createStore,
  navigationStateKey: 'navigation',
});

const store = createStoreWithNavigation(
  /* combineReducers and your normal create store things here! */
  combineReducers({
    navigation: NavigationReducer,
    // other reducers
  })
);

export default store;
/* Your routes, Router.js */

import { createRouter } from '@expo/ex-navigation';
import HomeScreen from './HomeScreen';

export const Router = createRouter(() => ({
  home: () => HomeScreen,
}));
 /* The top level of your app, often in main.js or index.[ios/android].js */

 import {
   NavigationContext,
   NavigationProvider,
   StackNavigation,
 } from '@expo/ex-navigation';

 import Store from './state/Store';
 import Router from './Router';

+const navigationContext = new NavigationContext({
+  router: Router,
+  store: Store,
+})

 return (
   <Provider store={Store}>
+    <NavigationProvider context={navigationContext}>
       <StackNavigation yourUsualPropsHere />
     </NavigationProvider>
   </Provider>
 )

Perform navigation actions from outside of a component

You might be using some Redux middleware like saga, thunk, promise, or effex (we recommend effex because we love async/await). Whatever you're using, you no longer have access to this.props.navigator and the like. What to do? Well as long as you include your navigation state inside of your Redux store, you can dispatch a NavigationAction to it -- after all, this is what this.props.navigator.push etc. do behind the scenes.

In the following example we call getState and dispatch directly on your store -- feel free to change this to whatever the equivalent is for your context (eg: if this was effex, dispatch and getState would be passed in to the goHome function).

import { NavigationActions } from '@expo/ex-navigation'
import Store from '../state/Store';
import Router from './Router'

export default function goHome() {
  let navigatorUID = Store.getState().navigation.currentNavigatorUID;
  Store.dispatch(NavigationActions.push(navigatorUID, Router.getRoute('home')))
}

Screen Tracking / Analytics

You might want to do some screen tracking in your apps. Since the entire navigation state is in redux, screen tracking is as simple as writing a redux middleware. Below is a simple middleware that uses routeName as the screen name for tracking screens.

import SegmentIO from 'react-native-segment-io-analytics';

const navigationStateKey = 'navigation';

// gets the current screen from navigation state
function getCurrentScreen(getStateFn) {
  const navigationState = getStateFn()[navigationStateKey];
  // navigationState can be null when exnav is initializing
  if (!navigationState) return null;

  const { currentNavigatorUID, navigators } = navigationState;
  if (!currentNavigatorUID) return null;

  const { index, routes } = navigators[currentNavigatorUID];
  const { routeName } = routes[index];
  return routeName;
}

const screenTracking = ({ getState }) => next => action => {
  if (!action.type.startsWith('EX_NAVIGATION')) return next(action);
  const currentScreen = getCurrentScreen(getState);
  const result = next(action);
  const nextScreen = getCurrentScreen(getState);
  if (nextScreen !== currentScreen) {
    SegmentIO.screen(nextScreen);
  }
  return result;
}

export default screenTracking;

Android back button handling

React Native includes a global BackHandler module. Rather than using this module directly, include the AndroidBackButtonBehavior component in routes where you'd like to control the back button. AndroidBackButtonBehavior accepts isFocused and onBackButtonPress. If isFocused is true, the onBackButtonPress will fire when the user presses the back button. You need to make sure that onBackButtonPress returns a promise that wraps the function you want to be called. Eg.

<AndroidBackButtonBehavior isFocused={someboolean}
   onBackButtonPress={()=>Promise.resolve(fireMeWhenSomeBooleanIsTrue)}>
   ...
</AndroidBackButtonBehavior>

ex-navigation's People

Contributors

brentvatne avatar chirag04 avatar coderbyheart avatar cyprusglobe avatar elie222 avatar expbot avatar fiberjw avatar fson avatar gblejman avatar grabbou avatar ide avatar isnifer avatar janicduplessis avatar jozan avatar juellez avatar knowbody avatar leocavalcante avatar lucasbento avatar matthargett avatar mnichols avatar philipheinser avatar ptomasroos avatar samuelkraft avatar satya164 avatar sibelius avatar skevy avatar tlvince avatar vonovak avatar wli avatar xxvalhallacoderxx 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

ex-navigation's Issues

Opening ExNavigationExample in XDE gives Error: Problem in exp.json. child "ios" fails because ["permissions" is not allowed]

I'm using a fresh clone of ex-navigation. I ran cd example/ExNavigationExample && npm install. When I open the project in XDE (2.4.0), I see the error:

Error: Problem in exp.json. child "ios" fails because ["permissions" is not allowed]. See https://docs.getexponent.com/.
Couldn't start project. Please fix the above issues and restart the project.

If I delete the entire permissions section under ios in exp.json, the project will open ok, but then shows the following error when I open it in the iOS simulator:

[node-haste] Encountered an error while persisting cache:
SyntaxError: /path/to/ex-navigation/example/ExNavigationExample/node_modules/exponent/src/Exponent.js: Unexpected token (22:9)

20 | import './Logs';
21 | 
22 | export * as Constants from './Constants';
   |          ^
23 | export * as Contacts from './Contacts';
24 | export * as Asset from './Asset';
25 | export * as Font from './Font';

> at Parser.pp.raise (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/location.js:22:13)
> at Parser.pp.unexpected (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/util.js:89:8)
> at Parser.pp.parseExportFrom (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/statement.js:938:12)
> at Parser.pp.parseExport (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/statement.js:861:12)
> at Parser.parseExport (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/plugins/flow.js:107:20)
> at Parser.pp.parseStatement (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/statement.js:141:81)
> at Parser.parseStatement (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/plugins/flow.js:30:22)
> at Parser.pp.parseBlockBody (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/statement.js:529:21)
> at Parser.pp.parseTopLevel (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/statement.js:36:8)
> at Parser.parse (/path/to/ex-navigation/example/ExNavigationExample/node_modules/babylon/lib/parser/index.js:129:19)

Not sure if it's relevant, but I did run a previous version of this example last week without any issues.

Let me know if there's any other information I can provide. Happy to help out however I can, but I'm pretty new to Exponent and ex-navigation so my knowledge is limited at this point.

Don't render the scene twice.

from my conversation with @brentvatne on slack. I will try to post repro code soon. Keeping this as a placeholder here on github so that it's not lost in the slack conversations.

`navigation.push` triggers multiple screens simultaneously

Here's what the log looks like:

1:11:35 PM nav question
1:11:35 PM Analytics push question
1:11:35 PM QuestionScreen willMount
1:11:37 PM pop question
1:11:39 PM nav conversation
1:11:39 PM Analytics push conversation
1:11:39 PM ConversationScreen willMount
1:11:41 PM pop conversation
1:11:45 PM nav question
1:11:45 PM Analytics push question
1:11:45 PM ConversationScreen willMount
1:11:45 PM QuestionScreen willMount
  • nav conversation is logged right before navigator.push is called
  • ConversationScreen willMount is logged inside the componentWillMount function
  • Analytics push question is logged via redux hooks into EX_NAVIGATION.PUSH
  • timestamps confirm when these happen too

Seems pretty reproducible: push A, pop, push B, pop, push A (which triggers A and B simultaneously)

@skevy @brentvatne

Why vendoring NavigationExperimental?

Is this because RN Experimental is continously improving?

Can't we just freeze the version that was expected to work instead?

Whats the reason for doing this?

Missing .babelrc ?

Issue:
When running the example, I got an error due to missing babel configuration
screen shot 2016-08-10 at 11 45 41

@ide give me the solution, creating a .babelrc with this conf:

{
  "presets": ["react-native-stage-0/decorator-support"]
}

And then run npm install -D babel-preset-react-native-stage-0 to get the preset

Controlling Left & Right NavigationHeader Icons

React native's NavigationHeader component has a few props for managing the display of the left and right icon(s): renderLeftComponent and renderRightComponent. Is it possible to use this package to set these props at the scene-level, similar to how react native's renderTitleComponent has been supported?

Going one step further, is it possible to define oncPress handlers for these icons? I.e.:

{
  title: 'Title Component',
  backgroundColor: '#000',
  left: <Text onPress={onPress1}>Left Component</Text>
  right: <Text onPress={onPress2}>Right Component</Text>
}

Store Custom Attributes on each Route + Update them with updateCurrentRouteParams()

It's great that ExNavigator allows you to set the background color for the NavigationHeader component at the scene-level. Similar to how this is done, it would be great to be able to set custom attributes on the route, such as a backgroundColor for the StatusBar.

The use case for this is Android Material Design. Google recommends using a 500-level color for the NavigationHeader, and a 700-level accent color for the status bar. See https://material.google.com/style/color.html

The YouTube app does this. When you go to someone's channel, not only does the NavigationHeader color change based on user preference, so does the StatusBar background accent.

So basically fetching profile data after the component has been invoked and using it to update both the status bar color and navigation bar colors using updateCurrentRouteParams().

I've logged a similar issue here detailing the need for setting the navigationBar backgroundColor with updateCurrentRouteParams(), but I think it's necessary to expand this even further to custom attributes per the above reasoning.

TLDR:

  1. I have set up a StatusBar component in my application that I would like to subscribe to the attributes of the current route (i.e. connected via Redux store), and update when a custom attribute (perhaps named statusBarBackground) on the current route changes.
  2. This custom attribute should be able to be set with updateCurrentRouteParams() since it is data-dependent.

Do I have to import my Router everywhere?

Awsome work!
After struggling with NavigationExperimental for a while ex-navigation feels really simple to use. Thank you!

I like it that I get the navigator "connected" automatically to props. But what is the best practice for importing the Router? Everywhere I'm calling navigator.push I need to feed it with Router.getRoute(). I.e. I need to import Router in every view with navigation capabilities.

I have probably missed something. NavigationProvider gets my Router from start, so it might very well be accessible somewhere in the props. But none of the examples shows this.

Update docs with alternative route config

Hey,

Going through the source code I've discovered this to be an alternative way of declaring route config:

const Router = createRouter(() => ({
  home: () => ({
    render: Router._makeRoute(HomeScene),
    config: {
      navigationBar: {
        title: 'Home',
      },
    },
  }),
}));

In this case, I am calling _makeRoute since when object is passed, it's not called as in other cases. Anyways, I think it would be cool to document it (or add to an examples) since many people might prefer to have config in one place, instead of looking into every scene separately for details.

Happy to PR, just wondering what's your idea on that

Maybe allow renderBackButton in the navigationBar config

renderBackButton would let us customize the back button UI and then i can just pass that to defaultRouteConfig.

Usually an app would have mulitple StackNavigations so i would still need to add renderBackButton to all the stacknavigators defaultRouteConfigs.

One can also override the BackButton component globally like this:

import { withNavigation, NavigationBar } from '@exponent/ex-navigation';

const myBackButton = () => <View />;

NavigationBar.BackButton = BackButton;

It's a trade off and idk what's the best api here. cc @brentvatne @skevy @ide

initialize with a history

Thank you guys for open sourcing ex-navigation

Is there a way to initialize it with a predefined stack of routes ?
Example: At app launch, being able to go back to a previous screen for development purpose

On Android useNativeDriver causes red screen

Native animation offloading is enabled in the Animation of Navigation.
Do we need to add a NativeModule in the MainActivity in order to make this work?

HT: Until its fixed you can disable offloading by setting useNativeDriver: false in ExNavigationStyles.js and it will run just fine on javascript thread instead.

Disable Drawer on pushed route

When I navigate away from a DrawerNavigationItem to a new route (Router.getRoute('routeA')) I get the back button but when I attempt to swipe to go back, it opens the drawer instead of navigating back. Clicking the back button works as expected.

Add navigator.getCurrentRoutes

as discussed on exponent slack, getCurrentRoutes will extract relevant data from the navigationState and export getCurrentRoutes on the navigator object.

I will PR

[bug] pushing a route w/o navigation bar causes red screen

When route A has navigationBar and route B doesn't have it (no static route set), I am getting the following exception:

screen shot 2016-08-03 at 13 19 03

For now, I did what I usually do - one container Navigator and two nested ones, one w/o header for onboarding scenes and 2nd one, called home, with navigation bar.

Replace stack

Hi,

Thank you for this great module!

I currently face a challenge. I want to replace the previous stack-entry programmatically.

        ---routeB---\
      /               > routeC
---routeA-----------/

The user can take multiple "paths" to routeC.
Let's say the user navigates as follows: routeA -> routeB -> routeC.
In routeC he behaves in such a way, that when he presses back, he should return to routeA. So he's stack looks like this: routeA -> routeC.

I've tried using immediatelyResetStack, but so far no luck.

this.props.navigator.immediatelyResetStack([
    Router.getRoute('routeA'), Router.getRoute('routeC', {routeCSpecificProp: this.props.routeCSpecificProp}))
]);

Define Parent/Child Relationship?

Is it possible to define a route as a child of another, similar to react router? I.e. something like:

export const router = createRouter(() => ({
  componentA: () => ComponentA, // normal definition
  componentB: () => <Parent><ComponentB/></Parent>, // render ComponentB as a child of Parent
  componentC: () => <Parent><ComponentC/></Parent>, // render ComponentC as a child of Parent
}));

make showLocalAlert customizable

Right now showLocalAlert takes a message and styles to apply. but it still controls the timeout. There is not way to have a dismiss button.

how about an API like:

this.props.navigator.showLocalAlert({
   renderAlert: () => <AlertComponent />,
   timeout: 500, // if not time them alert won't auto close.
});

idk if its worth supporting any default with local alerts because most apps would have their own styles and timeouts

cc @skevy @brentvatne

Missing an example of how to connect ex-navigation to an existing redux store

Hi,
I have more or less common setup of redux store with couple of reducers, middlewares and redux-persist. Ideally I want to make a branch in this store and give it to be used by ex-navigation, to have 1 store in the app with logging and persistence. But I'm lacking some understanding of ex-navigation and redux.

Small question, is it at all smth that was expected and has support in the library?

Bigger question, can you show an example pls?

Android: Too much empty space in top of navigationbar

When using the component without application bar, the ExNavigationBarcontains extra top margin. The navigation bar should be as close to the status bar as possible.

This margin is determined by the STATUSBAR_HEIGHTin ExNavigationBar.js
Setting this value to 0 fixes the issue.

@brentvatne

Customizable navbar height

Should be possible to specify navbar height in route config, right now the option is there but we aren't using it.

How to make SignOut-button in navigationBar listen to Redux store?

I want a signout-button visible to the right in the navigationBar of my main screen. I didn't manage to get hold of this.props.. from within the static route object in my view.

Ended up writing a connected component which I render from navigationBar.renderRight. Is the below an overly complicated solution, or is it the way to go?

class _SignOutButton extends React.Component {
  render() {
    if(!this.props.isLoggedIn) return null;

    return (
      <TouchableOpacity onPress={this._onPress}>
        <Text>Sign out</Text>
      </TouchableOpacity>
    );
  }
  _onPress = () => this.props.dispatch(AuthenticationState.LogoutAction());
}

const SignOutButton = connect(
  state => ({
    isLoggedIn: AuthenticationSelectors.isLoggedIn(state)
  })
)(_SignOutButton);

Usage outside of exponent

Is it possible to use ex-navigation without using exponent? I really like the look of the API and want to use it within an existing react native app.

Add popN to StackNavigation

popping multiple screens in a multi screen form seems like a standard ux. thoughts on supporting this?

I can PR if you guys agree.

[Feature] support lightbox

Just like showAlert we can support showLightBox. showLightBox api can be similar to push except the animation works out of the box for lightbox?

Took the idea from react-native-navigation:

this.props.navigator.showLightBox({
 screen: "example.LightBoxScreen", // unique ID registered with Navigation.registerScreen
 passProps: {}, // simple serializable object that will pass as props to the lightbox (optional)
 style: {
   backgroundBlur: "dark", // 'dark' / 'light' / 'xlight' / 'none' - the type of blur on the background
   backgroundColor: "#ff000080" // tint color for the background, you can specify alpha here (optional)
 }
});

cc @skevy @brentvatne

@withNavigation decorator only works within StackNavigation tree

When using @withNavigation decorator on a component outside of the StackNavigation tree, this.props.navigator is null. The use case here is for an app with multiple StackNavigators, and to manage them, there are general layout components that sit outside of the StackNavigation trees. i.e. a single StatusBar component.

This StatusBar component needs to be aware of the parameters of the current route of the currently active navigator (color; network activity; etc.), since these values will change on a per-route basis and must change as the user switches between tabs (not exclusive to popping and pushing). How does one achieve this? It doesn't seem possible to subscribe to the current route params across multiple navigators as is done with NavigatorExperimental's reducer.

Related: #34

Tests Fail for Imported Components

When running mocha tests on components that contain StackNavigation components somewhere in the child tree, the test will error out. Here's an example of a valid Enzyme test failing due to inability to process this package:

$ mocha --require imports/ui/helpers/test/setup.js --compilers js:babel-register --recursive imports/**/*.tests.js --reporter spec

/Users/jcursi/Sites/joncursi/redbirdNative/node_modules/@exponent/ex-navigation/src/ExNavigationStyles.js:33
sceneAnimations:CardStackStyleInterpolator.forHorizontal,
                                          ^

TypeError: Cannot read property 'forHorizontal' of undefined
    at Object.<anonymous> (ExNavigationStyles.js:33:20)
    at Module._compile (module.js:541:32)
    at Module.replacementCompile (/Users/jcursi/Sites/joncursi/redbirdNative/node_modules/append-transform/index.js:63:13)
    at loader (/Users/jcursi/Sites/joncursi/redbirdNative/node_modules/babel-register/lib/node.js:148:5)
    at require.extensions.(anonymous function) (/Users/jcursi/Sites/joncursi/redbirdNative/node_modules/babel-register/lib/node.js:158:7)
    at Object.<anonymous> (/Users/jcursi/Sites/joncursi/redbirdNative/node_modules/append-transform/index.js:67:4)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)

DrawerNavigation error handling

If the initialItem is not a valid route name you get a not too helpful error. Maybe if initialItem took a route instead you would get the flow checking plus a better error message.

Disable/enable default android back button behaviour

Sometimes I'd like to capture the android back button with custom handling and not letting ExNavigation use it's default pop() action. For instance, when we open up a modal in a current scene and when pressing the android back button I'd like to close the modal, not popping any routes. I note enable() and disable() methods in ExNavigationBackButtonManager but I'm not sure how to use them? Is this possible?

Cannot Call updateCurrentRouteParams() During Route Transition

Calling updateCurrentRouteParams during a route transition (i.e. before a freshly pushed route finishes sliding across the screen from right to left), results in a broken navigation stack. It seems that route params cannot be modified during transition. This will break:

  static route = {
    navigationBar: {
      title(params) {
        return params.title;
      },
    },
  }

  componentDidMount() {
      this.props.navigator.updateCurrentRouteParams({
        title: 'New Title',
      });
  }

The only workaround seems to be to wrap the call in a setTimeout function, as done in the README example to prevent it from calling before the route transitions, but this feels janky for a number of reasons.

  1. I don't know how long to make the setTimeout because each route takes a different amount of time to transition onto the stack, depending on the phone + component performance
  2. Often my new params come back faster than the time it takes to transition, so I'd rather not wait
  3. Flashing new values to the screen (i.e. new title and new renderRight component), results in sub-optimal UX

This may be a bug in NavigationExperimental core, I've logged the same type of issue on the react-native repo as well: facebook/react-native#9326

Cannot update navigationBar backgroundColor via updateCurrentRouteParams()

The background color for my scenes are determined based on data (i.e. themed based on user preferences). I would like to use updateCurrentRouteParams to update the backgroundColor once the color code has been fetched, however the below throws a warning:

  static route = {
    navigationBar: {
      backgroundColor(params) {
        return params.color;
      },
    },
  }

screen shot 2016-08-16 at 3 24 34 am

Add popToTop

from our conversation on exponent slack channel, keeping this as a placeholder. I will PR

cc @skevy

Rename `renderOverlay` to `renderHeader`

RN moved to renderHeader convention for the navigationCardStack. It's not a breaking change for ex-navigation as we don't use navigationCardStack. but wondering if we want to follow the same naming convention.

@skevy @brentvatne upstream navExp also removed absolute positioning for rendering the header. My concern is that would not allow for translucent navbar(?) do we want to do this?

Example using DrawerNavigation

I'm migrating from using exponentjs/ex-navigator and noticed that ex-navigation has built in functionality for using Drawer navigation.

Are there any examples/pointers of how to use this? (I looked at the checked in ExNavigationExample but it unfortunately does not include a sample DrawerNavigation/DrawerNavigationItem).

cannot run example

I have tried running the example as per the instructions in the readme.

However, I get this error:

ERROR Unexpected token ...
SyntaxError: Unexpected token ...
at exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:374:25)
at Module._extensions..js (module.js:417:10)
at Object.require.extensions.(anonymous function) as .js
at Module.load (module.js:344:32)
at Function.Module._load (module.js:301:12)
at Module.require (module.js:354:17)
at require (internal/module.js:12:17)
at new Bundler (index.js:120:27)
at new Server (index.js:207:21)

Any idea on what is going on?

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.