I have two React Native Components (Alpha and Beta) that navigate to one another; however, this produces a cyclic dependency and React Native doesn’t seem to handle those.
Requiring Beta in Alpha works fine, but requiring Alpha in Beta returns an empty object. An error is thrown when trying to push a route with an invalid component.
Can cyclic dependencies work in React Native? If not, how do I get around this?
index.ios.js
'use strict';
var React = require('react-native');
var Alpha = require('./Alpha');
var {
AppRegistry,
NavigatorIOS,
StyleSheet,
Text,
View,
} = React;
var ExampleProject = React.createClass({
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: Alpha,
title: Alpha.title,
wrapperStyle: styles.wrapper
}} />
);
},
});
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white'
},
wrapper: {
paddingTop: 64
}
});
AppRegistry.registerComponent('ExampleProject', () => ExampleProject);
Alpha.js
'use strict';
var React = require('react-native');
var Beta = require('./Beta');
var {
StyleSheet,
TouchableHighlight,
View,
Text
} = React;
var Alpha = React.createClass({
statics: {
title: 'Alpha'
},
goToBeta() {
this.props.navigator.push({
component: Beta,
title: Beta.title,
wrapperStyle: styles.wrapper
});
},
render() {
return (
<View>
<TouchableHighlight style={styles.linkWrap}
onPress={this.goToBeta}>
<Text>Go to Beta</Text>
</TouchableHighlight>
</View>
);
}
});
var styles = StyleSheet.create({
linkWrap: {
flex: 1,
alignItems: 'center',
padding: 30
},
wrapper: {
paddingTop: 64
}
});
module.exports = Alpha;
Beta.js
'use strict';
var React = require('react-native');
var Alpha = require('./Alpha');
var {
StyleSheet,
TouchableHighlight,
View,
Text
} = React;
var Beta = React.createClass({
statics: {
title: 'Beta'
},
goToAlpha() {
this.props.navigator.push({
component: Alpha,
title: Alpha.title,
wrapperStyle: styles.wrapper
});
},
render() {
return (
<View>
<TouchableHighlight style={styles.linkWrap}
onPress={this.goToAlpha}>
<Text>Go to Alpha</Text>
</TouchableHighlight>
</View>
);
}
});
var styles = StyleSheet.create({
linkWrap: {
flex: 1,
alignItems: 'center',
padding: 30
},
wrapper: {
paddingTop: 64
}
});
module.exports = Beta;
This is a common problem with routing components. There are a couple of ways to approach this. In general, you want Alpha to avoid requiring Beta before Alpha has defined its exports and vice versa.
Fortunately, JavaScript's import
and export
keywords address this issue by lazily importing values using an intermediate object that acts as a level of indirection. A specific example is a lot easier to understand.
import
and export
Use the export
keyword to export Alpha and Beta from their respective files, and use the import
keyword to import them:
// Alpha.js
import Beta from './Beta';
var Alpha = React.createClass({
/* ... */
});
export default Alpha;
This works because at runtime, the export
keyword creates an intermediate object (equivalent to module.exports
in CommonJS) and assigns a property named default
to it with the value of Alpha
. So, the above export
statement is conceptually similar to this:
module.exports.default = Alpha;
The files that import Alpha then get a reference to the intermediate object, not Alpha itself until Alpha
is directly used. So this code here actually lazily accesses Alpha:
import Alpha from './Alpha';
var ExampleProject = React.createClass({
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: Alpha,
title: Alpha.title,
wrapperStyle: styles.wrapper
}}
/>
);
},
});
The lazy access is implemented by running code conceptually similar to this:
var AlphaExports = require('./Alpha');
var ExampleProject = React.createClass({
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: AlphaExports.default,
title: AlphaExports.default.title,
wrapperStyle: styles.wrapper
}}
/>
);
},
});
require()
Using CommonJS's require
, you have some other options:
Alpha and Beta don't need each other until the user navigates from one scene to the next. For the app to reach this state, Alpha must have defined its exports (that is, module.exports = Alpha
must have run for your app to have rendered an Alpha component). So, when the user is navigating to the scene displaying Beta, it is safe for Beta to require Alpha and therefore it is safe to require Beta at this point in time.
// Alpha.js
var Alpha = React.createClass({
goToBeta() {
// Lazily require Beta, waiting until Alpha has been initialized
var Beta = require('./Beta');
this.props.navigator.push({
component: Beta,
title: Beta.title,
wrapperStyle: styles.wrapper
});
}
});
Although it isn't necessary to do the same for Beta.js in this specific scenario because Alpha is the first component loaded, it's probably a good idea so that your components all handle dependency cycles the same way.
Another solution is to put Alpha and Beta in the same JS file to remove a cycle between modules. You would then export both components from the new mega-module.
// AlphaBeta.js
var Alpha = React.createClass({...});
var Beta = React.createClass({...});
exports.Alpha = Alpha;
exports.Beta = Beta;
To require it:
// index.js
var {Alpha, Beta} = require('./AlphaBeta');
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With