Skip to content

Instantly share code, notes, and snippets.

@ericallam
Last active December 25, 2018 20:41
Show Gist options
  • Save ericallam/51ae9fbd654d8b692bff to your computer and use it in GitHub Desktop.
Save ericallam/51ae9fbd654d8b692bff to your computer and use it in GitHub Desktop.
Getting React Native's Navigator and Relay to work together
import AppComponent from './src/components/AppComponent';
import Relay, {
DefaultNetworkLayer,
} from 'react-relay';
import React, {
Component
} from 'react-native';
Relay.injectNetworkLayer(new DefaultNetworkLayer('http://localhost:3000/graphql'));
export default class App extends Component {
render(): void {
return (
<AppComponent />
);
}
}
import Relay, {
RootContainer,
Route
} from 'react-relay'
class SeasonRoute extends Route {
static paramDefinitions = {};
static queries = {
currentSeason: () => Relay.QL`query { currentSeason }`,
};
static routeName = 'MatchdayRoute';
}
class NodeRoute extends Route {
static paramDefinitions = {
nodeID: { required: true }
};
static queries = {
node: () => Relay.QL`query { node(id: $nodeID) }`,
};
static routeName = 'NodeRoute';
}
const MatchdayList = require('./MatchdayList');
const MatchList = require('./MatchList');
const ROUTES = {
MatchdayList,
MatchList
};
import React, {
View,
Text,
StyleSheet,
Navigator,
Component
} from 'react-native';
export default class AppComponent extends Component {
renderScene(route, navigator){
const props = { route, navigator };
if (route.name == "MatchdayList") {
return <RootContainer
Component={MatchdayList}
route={new SeasonRoute()}
renderFetched={(data) => <MatchdayList {...props} {...data} />}
/>
}else if (route.name == "MatchList") {
return <RootContainer
Component={MatchList}
route={new NodeRoute({nodeID: route.matchday.id})}
renderFetched={(data) => <MatchList {...props} {...data} />}
/>
}
}
render() {
return (
<Navigator
style = { styles.container }
initialRoute = { { name: 'MatchdayList' } }
renderScene = { this.renderScene.bind(this) }
configureScene = { () => { return Navigator.SceneConfigs.FloatFromRight; } } />
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
}
});
import React, {
AppRegistry,
} from 'react-native';
import App from './app';
AppRegistry.registerComponent('App', () => App);
import Relay from 'react-relay';
import React, {
View,
Text,
StyleSheet,
Component,
TouchableHighlight
} from 'react-native';
class MatchdayItem extends Component {
constructor(props, context) {
super(props, context);
this._handleItemPress = this._handleItemPress.bind(this);
}
_handleItemPress() {
this.props.handleItemPress(this.props.matchday)
}
render() {
var { matchday } = this.props;
return (
<TouchableHighlight onPress={this._handleItemPress}>
<View>
<View>
<Text>
Matchday {matchday.number}
</Text>
<Text>
{matchday.start_date}
</Text>
<Text>
{matchday.status}
</Text>
</View>
<View>
<Text>{matchday.matches_count} matches</Text>
</View>
</View>
</TouchableHighlight>
)
}
}
module.exports = Relay.createContainer(MatchdayItem, {
fragments: {
matchday: () => Relay.QL`
fragment on Matchday {
id,
number,
start_date,
matches_count,
status
}
`
},
});
import Relay from 'react-relay';
import MatchdayItem from './MatchdayItem';
import MatchList from './MatchList'
import React, {
View,
Text,
StyleSheet,
ListView,
Component
} from 'react-native';
const _matchdaysDataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1.__dataID__ !== r2.__dataID__,
});
class MatchdayList extends Component {
constructor(props, context) {
super(props, context);
const { edges } = props.currentSeason.matchdays;
this.state = {
initialListSize: edges.length,
listScrollEnabled: true,
dataSource: _matchdaysDataSource.cloneWithRows(edges),
};
this.renderMatchdayEdge = this.renderMatchdayEdge.bind(this);
this.handleItemPress = this.handleItemPress.bind(this);
}
handleItemPress(matchday) {
this.props.navigator.push({name: 'MatchList', matchday: matchday});
}
componentWillReceiveProps(nextProps) {
if (this.props.currentSeason.matchdays.edges !== nextProps.currentSeason.matchdays.edges) {
const {
dataSource,
} = this.state;
this.setState({
dataSource:
dataSource.cloneWithRows(nextProps.currentSeason.matchdays.edges),
});
}
}
renderSeparator(sectionId, rowId) {
return <View key={`sep_${sectionId}_${rowId}`} style={styles.separator} />;
}
renderMatchdayEdge(matchdayEdge, sectionID, rowID) {
return (
<MatchdayItem
key={matchdayEdge.node.id}
matchday={matchdayEdge.node}
handleItemPress={this.handleItemPress}
/>
);
}
render() {
return (
<View>
<View>
<Text>{currentSeason.name}</Text>
</View>
<ListView
dataSource={this.state.dataSource}
initialListSize={this.state.initialListSize}
renderRow={this.renderMatchdayEdge}
renderSeparator={this.renderSeparator}
/>
</View>
);
}
}
module.exports = Relay.createContainer(MatchdayList, {
fragments: {
currentSeason: () => Relay.QL`
fragment on Season {
name,
matchdays(first: 100) {
edges {
node {
id,
number
${MatchdayItem.getFragment('matchday')}
},
},
},
}
`
},
});
import Relay from 'react-relay';
import React, {
View,
Text,
StyleSheet,
Component,
ListView,
TouchableHighlight
} from 'react-native';
const MatchResults = ({
home_team_score,
away_team_score
}) => (
<Text style={styles.score}>
{home_team_score} - {away_team_score}
</Text>
)
import moment from 'moment'
class MatchItem extends Component {
_renderResults() {
var { match } = this.props;
if (match.status === 'FINISHED') {
return <MatchResults
home_team_score={match.home_team_score}
away_team_score={match.away_team_score} />
}
}
render() {
const { match } = this.props;
const scheduledDate = moment(match.scheduled_date);
return (
<TouchableHighlight>
<View style={styles.row}>
<View style={styles.info}>
<Text style={styles.teams}>
{match.home_team.name} vs {match.away_team.name}
</Text>
<Text style={styles.date}>
{scheduledDate.format('DD/MM/YYYY h:mm:ss a')}
</Text>
{this._renderResults()}
</View>
</View>
</TouchableHighlight>
)
}
}
MatchItem = Relay.createContainer(MatchItem, {
fragments: {
match: () => Relay.QL`
fragment on Match {
id,
scheduled_date,
status,
home_team_score,
away_team_score,
home_team { name }
away_team { name }
}
`
},
});
const _matchesDataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1.__dataID__ !== r2.__dataID__,
});
class MatchList extends Component {
constructor(props, context) {
super(props, context);
const { edges } = props.node.matches;
this.state = {
initialListSize: edges.length,
listScrollEnabled: true,
dataSource: _matchesDataSource.cloneWithRows(edges),
};
this.renderMatchEdge = this.renderMatchEdge.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.node.matches.edges !== nextProps.node.matches.edges) {
const {
dataSource,
} = this.state;
this.setState({
dataSource:
dataSource.cloneWithRows(nextProps.node.matches.edges),
});
}
}
renderSeparator(sectionId, rowId) {
return <View key={`sep_${sectionId}_${rowId}`} style={styles.separator} />;
}
renderMatchEdge(matchEdge, sectionID, rowID) {
return (
<MatchItem
key={matchEdge.node.id}
match={matchEdge.node}
/>
);
}
render() {
var { matchday } = this.props.route;
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>Matchday {matchday.number}</Text>
</View>
<ListView
dataSource={this.state.dataSource}
initialListSize={this.state.initialListSize}
renderRow={this.renderMatchEdge}
renderSeparator={this.renderSeparator}
/>
</View>
);
}
}
module.exports = Relay.createContainer(MatchList, {
fragments: {
node: () => Relay.QL`
fragment on Matchday {
number,
matches(first: 10) {
edges {
node {
id
${MatchItem.getFragment("match")}
},
},
},
}
`
},
});
var styles = StyleSheet.create({
container: {
flex: 1
},
separator: {
height: 1,
backgroundColor: '#e0e0e0',
marginLeft: 14
},
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'lightgrey',
flexDirection: 'column',
paddingTop: 25
},
headerText: {
fontWeight: 'normal',
fontSize: 18,
color: 'black'
},
teams: {
fontWeight: 'bold'
},
date: {
color: '#949494'
},
score: {
color: '#585858',
fontSize: 12,
fontWeight: '200'
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 12,
backgroundColor: 'white'
},
info: {
flexDirection: 'column',
justifyContent: 'space-around'
}
});
scalar DateTime
type Match implements Node {
id: ID!
scheduled_date: DateTime
status: String
home_team_score: Int
away_team_score: Int
home_team: Team
away_team: Team
}
type MatchConnection {
pageInfo: PageInfo!
edges: [MatchEdge]
}
type Matchday implements Node {
id: ID!
number: Int!
status: Status!
matches_count: Int
start_date: DateTime
end_date: DateTime
matches(after: String, first: Int, before: String, last: Int): MatchConnection
}
type MatchdayConnection {
pageInfo: PageInfo!
edges: [MatchdayEdge]
}
type MatchdayEdge {
node: Matchday
cursor: String!
}
type MatchEdge {
node: Match
cursor: String!
}
interface Node {
id: ID!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Root {
currentSeason: Season
node(id: ID!): Node
}
type Season implements Node {
id: ID!
name: String!
leagueCode: String!
matchdays(after: String, first: Int, before: String, last: Int): MatchdayConnection
}
enum Status {
UPCOMING
ACCEPTING_UPDATES
IN_PROGRESS
COMPLETED
}
type Team implements Node {
id: ID!
name: String
}
@ericallam
Copy link
Author

I've updated this Gist with the recommendation in this relay issue. Now I use a new Route/RootContainer for each Navigator child.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment