简体   繁体   中英

React Native Navigator routes

I would like to insert in my react-native App a navigation with defined routes. I have found the Navigate.js. Unfortunately, it's not functioning in my app. The following error is indicated:

Null is not an object (evaluating 'route.component')

I cannot find any mistakes in my code. I do not understand why the object is not defined. However, in the demo it functions perfectly. Can you please help me?

图片

index.android.js:

import React, { Component } from 'react';
import {
  AppRegistry, Navigator, DrawerLayoutAndroid, ScrollView, View, Text
} from 'react-native';

import Navigate from './src/utils/Navigate';
import Navigation from './src/scenes/Navigation';
import Home from './src/views/Home';

class app extends Component {
  static childContextTypes = {
    drawer: React.PropTypes.object,
    navigator: React.PropTypes.object
  };

  constructor(props) {
    super(props);
    this.state = {
      drawer: null,
      navigator: null
    };
  }

  getChildContext = () => {
    return {
      drawer: this.state.drawer,
      navigator: this.state.navigator
    }
  };

  setDrawer = (drawer) => {
    this.setState({
      drawer
    });
  };

  setNavigator = (navigator) => {
    this.setState({
      navigator: new Navigate(navigator)
    });
  };

  render() {
    const { drawer, navigator } = this.state;
    const navView = React.createElement(Navigation);

    return (
      <DrawerLayoutAndroid
        drawerWidth={300}
        drawerPosition={DrawerLayoutAndroid.positions.Left}
        renderNavigationView={() => {
                    if (drawer && navigator) {
                        return navView;
                    }
                    return null;
                }}
        ref={(drawer) => { !this.state.drawer ? this.setDrawer(drawer) : null }}
      >
        {drawer &&
        <Navigator
          initialRoute={Navigate.getInitialRoute()}
          navigationBar={<Toolbar onIconPress={drawer.openDrawer} />}
          configureScene={() => {
                            return Navigator.SceneConfigs.FadeAndroid;
                        }}
          ref={(navigator) => { !this.state.navigator ? this.setNavigator(navigator) : null }}
          renderScene={(route) => {
                        if (this.state.navigator && route.component) {
                            return (
                                <View
                                    style={styles.scene}
                                    showsVerticalScrollIndicator={false}>
                                  <route.component title={route.title} path={route.path} {...route.props} />
                                </View>
                            );
                        }
                    }}
        />
        }
      </DrawerLayoutAndroid>
    );
  }
}

AppRegistry.registerComponent('app', () => app);

const styles = {
  scene: {
    flex: 1,
    marginTop: 56
  }
};

routes.js:

export default {
    home: {
        initialRoute: true,
        title: 'Home',
        component: require('./views/Home').default,
    },

    hotels: {
        title: 'Hotels',
        component: require('./views/Hotels').default
    },

}

./src/utils/Navigate.js :

import { BackAndroid } from 'react-native';

let routes = null;

try {
  routes = require('../routes').default;
} catch (e) {
}

export default class Navigate {

  /**
  * Gets the initial root props
  * Accepts a parent route name and finds that routes props
  *    OR - Finds the first parent route with 'initialRoute' set to true.
  *    OR FINALLY - Returns the first parent route.
  * @param path
  * @param customRoutes
  * @returns {{path: *}}
  */
  static getInitialRoute = (path, customRoutes) => {
    if (customRoutes) {
      routes = customRoutes;
    }
    if (!routes) {
      console.warn(`[Navigate.getInitialRoute()] No routes found. Add routes to src/routes.js.`);
      return null;
    }
    if (path) {
      return {
        path,
        ...routes[path]
      }
    } else {
      let initial;
      for (const route in routes) {
        if (routes[route].initialRoute) {
          initial = {path: route, ...routes[route]};
          break;
        }
      }
      return initial || {
        path,
        ...routes[Object.keys(routes)[0]]
      }
    }
  };

  constructor(navigator) {
    this.navigator = navigator;
    this.savedInstanceStates = new Map();
    this.currentRoute = null;
    this.previousRoute = null;
    this.isChild = false;
    BackAndroid.addEventListener('hardwareBackPress', this._hardwareBackPress);
  }

  /**
  * Generates a pretty name based off the last item in a route path.
  * Mainly for titles
  * @param path
  * @returns {string}
  * @private
  */
  _getPathPrettyName = (path) => {
    path = path.split('.');
    if (path.length === 1) {
      path = path[0]
    } else {
      path = path[path.length - 1];
    }
    return path.charAt(0).toUpperCase() + path.slice(1);
  };

  /**
  * Handle hardware back press
  * @returns {boolean}
  */
  _hardwareBackPress = () => {
    if (this.navigator.getCurrentRoutes()[0].path == Navigate.getInitialRoute().path) {
      BackAndroid.exitApp();
      return false;
    } else {
      if (!this.isChild) {
        route = Navigate.getInitialRoute();
        this.currentRoute = route;
        this.navigator.replace(route);
        return true;
      } else {
        this.back();
        return true;
      }
    }
  };

  /**
  * Deep get an object without passing in the 'children' key
  * @param path
  * @returns object
  * @private
  */
  _getRouteObject = (path) => {
    let obj = routes;
    const properties = path.replace(/\./g, '.children.').split('.');
    if (properties.length === 1) return obj[path];
    properties.forEach(function (key) {
      if (!obj || !hasOwnProperty.call(obj, key)) {
        obj = undefined;
        return;
      }
      obj = obj[key];
    });
    return obj;
  };

  _saveInstanceState = (path, instanceState) => {
    if (instanceState) {
      this.savedInstanceStates.set(path, instanceState);
    }
  };

  _recoverInstanceState = (path) => {
    const instanceState = this.savedInstanceStates.get(path);
    if (instanceState) {
      this.savedInstanceStates.delete(path);
    }
    return instanceState || null;
  };
  /**
  * Jump to a component at a certain path defined in routes
  * @param path
  * @param title
  * @param props
  */
  to = (path, title, props) => {
    if (!path) {
      console.warn(`[Navigate.to(undefined)] A route path is required to navigate to`);
    } else {
      const obj = this._getRouteObject(path);

      if (!obj || !obj.component) {
        console.warn(`[Navigate.to(${path})] No component exists at this path`);
      } else {
        this.isChild = path.split('.').length > 1;
        const route = {
          title: title ? title : (obj.title ? obj.title : path),
          path,
          component: obj.component,
          props
        };
        this.previousRoute = this.currentRoute;
        this.currentRoute = route;
        this.navigator.replace(route);
      }
    }
  };

  /**
  * Go back to the parent of the current component
  * @param title
  * @param props
  */
  back = (title, props) => {
    const current = this.navigator.getCurrentRoutes()[0].path;
    const path = current.substr(0, current.lastIndexOf('.'));
    const obj = this._getRouteObject(path);
    const savedInstance = this._recoverInstanceState(path); 

    if (!obj) {
      console.warn(`[Navigate.back()] No component exists for the parent of ${current}`);
    } else {
      this.isChild = path.split('.').length > 1;
      const route = {
        // title: title ? title : (obj.title || this._getPathPrettyName(path)),
        title: title ? title : (this.previousRoute ? this.previousRoute.title : (obj.title || this._getPathPrettyName(path))),
        path,
        component: obj.component,
        props
      };

      this.currentRoute = route;
      this.navigator.replace(route);
    }
  };

  /**
  * Go forward to a defined child component of the current route or the first child that exists
  * @param {String} child [Optional] Specify the name of the child to go to.
  * @param {String} title [Optional] Override the routes default title.
  * @param {Object} props [Optional] Send additional props that'll get bootstrapped onto the route
  * @param {Object} savedInstanceState [Optional] Send additional props that'll get bootstrapped onto the route
  */
  forward = (child, title, props, savedInstanceState) => {
    const current = this.navigator.getCurrentRoutes()[0].path;
    const currentObject = this._getRouteObject(current);

    if (!currentObject.children || !Object.keys(currentObject.children).length) {
      console.warn(`[Navigate.forward()] No child components exists for ${current}`);
    } else {
      this.isChild = true;
      if (child) {
        const obj = this._getRouteObject(`${current}.${child}`);
        if (!obj) {
          console.warn(`[Navigate.forward(${child})] Child component ${child} does not exist on ${current}`);
        } else {
          const route = {
            title: title ? title : (obj.title || this._getPathPrettyName(`${current}.${child}`)),
            path: `${current}.${child}`,
            component: obj.component,
            props
          };
          this.previousRoute = this.currentRoute;
          this.currentRoute = route;
          this.navigator.replace(route);
        }
      } else {
        const path = `${current}.${Object.keys(currentObject.children)[0]}`;
        const obj = this._getRouteObject(path);
        const route = {
          title: title ? title : (obj.title ? obj.title : this._getPathPrettyName(path)),
          path,
          component: obj.component,
          props
        };
        this.currentRoute = route;
        this.navigator.replace(route);
      }
    }
  };

  /**
  * Returns the current route config.
  * @returns {*|makeAction}
  */
  getRoutes = () => {
    return routes;
  };

  setRoutes = (newRoutes) => {
    routes = newRoutes;
  };

};

/src/views/Home.js:

import React from "react";
import {View, StyleSheet, ScrollView, Text} from "react-native";

export class Home extends React.Component {

  static contextTypes = {
    navigator: React.PropTypes.object.isRequired
  };

    render() {
      const { navigator } = this.context;
      const theme = AppStore.getState().theme;

    return <View style={styles.container}>
      <Text>...</Text>
    </View>;
  }
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

Maybe the problem is in the Navigate file. Your method getInitialRoute needs customRoutes not to be null/undefined, or it will return null.

And I'm assuming that you dont know how to debug. You can read how to debug here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM