Skip to content

Instantly share code, notes, and snippets.

@fdecampredon
Created December 20, 2016 10:07
Show Gist options
  • Save fdecampredon/74501a07ab9a732f6d42bd1c27607cb2 to your computer and use it in GitHub Desktop.
Save fdecampredon/74501a07ab9a732f6d42bd1c27607cb2 to your computer and use it in GitHub Desktop.
React-router + NavigationExperimental
import React, { PropTypes } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
NavigationExperimental,
} from 'react-native';
import { MemoryRouter as Router } from 'react-router';
import { Stack, StackMatch, Link } from './stack';
const { Header: NavigationHeader } = NavigationExperimental;
const A = () => (
<View style={styles.scene}>
<Text style={styles.welcome}>
A
</Text>
<Link to="/b" label="Go to B" />
</View>
);
const B = () => (
<View style={styles.scene}>
<Text style={styles.welcome}>
B
</Text>
<Link to="/c" label="Go to C" />
</View>
);
const C = () => (
<View style={styles.scene}>
<Text style={styles.welcome}>
C
</Text>
<Link to="/" label="Go to A" />
</View>
);
const Header = ({ pathname, ...props }, { history }) => (
<NavigationHeader
{...props}
renderTitleComponent={() => (
<NavigationHeader.Title>
{pathname}
</NavigationHeader.Title>
)}
onNavigateBack={() => history.goBack()}
/>
);
Header.contextTypes = {
history: PropTypes.object.isRequired,
};
Header.propTypes = {
pathname: PropTypes.string.isRequired,
};
const App = () => (
<Router>
<Stack style={styles.container} enableGestures={false}>
<StackMatch exactly pattern="/" component={A} headerComponent={Header} />
<StackMatch exactly pattern="/b" component={B} headerComponent={Header} />
<StackMatch exactly pattern="/C" component={C} headerComponent={Header} />
</Stack>
</Router>
);
const styles = StyleSheet.create({
container: {
flex: 1,
},
scene: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFF',
},
});
AppRegistry.registerComponent('ReactRouterStack', () => App);
import React, { PropTypes, Children } from 'react';
import { NavigationExperimental, TouchableOpacity, Text } from 'react-native';
import matchPattern from 'react-router/matchPattern';
const { CardStack: NavigationCardStack } = NavigationExperimental;
export function StackMatch() {
throw new Error('Stack Match should not be renderered use `Stack`');
}
StackMatch.propTypes = {
pattern: PropTypes.string.isRequired,
exactly: PropTypes.bool,
renderHeader: PropTypes.func,
headerComponent: PropTypes.func,
render: PropTypes.func,
component: PropTypes.func,
};
export class Stack extends React.Component {
static contextTypes = {
match: PropTypes.object,
history: PropTypes.object.isRequired,
}
static propTypes = {
children: PropTypes.node.isRequired,
}
constructor(props, context) {
super(props, context);
const { history } = context;
this.historyListener = history.listen(() => {
this.setState({
navigationState: this.createNavigationSate(),
});
});
this.state = {
navigationState: this.createNavigationSate(),
};
}
componentWillReceiveProps({ children }) {
if (children !== this.props.children) {
this.setState({
navigationState: this.createNavigationSate(),
});
}
}
componentWillUnmount() {
this.historyListener();
}
createNavigationSate() {
const { history, match: matchContext } = this.context;
const children = Children.toArray(this.props.children);
const parent = matchContext && matchContext.parent;
const routes = history.entries.slice(0, history.index + 1)
.map(location => ({
key: location.key || location.pathname,
location,
child: children.find((child) => {
const { pattern, exactly: matchExactly } = child.props;
return matchPattern(pattern, location, matchExactly, parent);
}),
}))
.reduce((array, item) => {
if (!array.length || array[array.length - 1].child !== item.child) {
array.push(item);
}
return array;
}, []);
return { index: routes.length - 1, routes };
}
onNavigateBack = () => {
this.context.history.goBack();
}
renderHeader = (sceneProps) => {
const { scene: { route: { child, location } } } = sceneProps;
const { match: matchContext } = this.context;
const parent = matchContext && matchContext.parent;
const {
pattern,
exactly,
renderHeader,
headerComponent: HeaderComponent,
} = child.props;
const match = matchPattern(pattern, location, exactly, parent);
const props = { ...sceneProps, ...match };
if (renderHeader) {
return renderHeader(props);
} else if (HeaderComponent) {
return (
<HeaderComponent {...props} />
);
}
return null;
}
renderScene = (sceneProps) => {
const { scene: { route: { child, location } } } = sceneProps;
const { match: matchContext } = this.context;
const parent = matchContext && matchContext.parent;
const {
pattern,
exactly,
render,
component: Component,
} = child.props;
const match = matchPattern(pattern, location, exactly, parent);
const props = { ...sceneProps, ...match };
if (render) {
return render(props);
}
return <Component {...props} />;
}
render() {
const { navigationState } = this.state;
const { children, ...props } = this.props; // eslint-disable-line no-unused-vars
return (
<NavigationCardStack
onNavigateBack={this.onNavigateBack}
{...props}
navigationState={navigationState}
renderScene={this.renderScene}
renderHeader={this.renderHeader}
/>
);
}
}
export const Link = ({ to, label, ...props }, context) => (
<TouchableOpacity {...props} onPress={() => context.history.push(to)}>
<Text>{label}</Text>
</TouchableOpacity>
);
Link.contextTypes = {
history: PropTypes.object.isRequired,
};
Link.propTypes = {
label: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment