Skip to content

Commit 2bc2ae7

Browse files
author
Martin Konicek
committed
CLI: Update Navigation app template
Summary: Update the template that will be used by `react-native init --template navigation`. The goal is to make it easier for people to get started by demonstrating a few very basic concepts such as navigation, lists and text input. **Test plan (required)** <img src="https://cloud.githubusercontent.com/assets/346214/22697898/ced66f52-ed4a-11e6-9b90-df6daef43199.gif" alt="Android Example" height="800" style="float: left"/> <img src="https://cloud.githubusercontent.com/assets/346214/22697901/cfeab3e4-ed4a-11e6-8552-d76585317ac2.gif" alt="iOS Example" height="800"/> Closes #12260 Differential Revision: D4521758 Pulled By: mkonicek fbshipit-source-id: d7d9e481dd3373917ac68ec9169b9ac3267547a9
1 parent 3a6dff4 commit 2bc2ae7

7 files changed

Lines changed: 216 additions & 37 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
2+
// This file just a dummy example of a HTTP API to talk to the backend.
3+
// The state of the "database" that would normally live on the server
4+
// is simply held here in memory.
5+
6+
const backendStateForLoggedInPerson = {
7+
chats: [
8+
{
9+
name: 'Claire',
10+
messages: [
11+
{
12+
name: 'Claire',
13+
text: 'I ❤️ React Native!',
14+
},
15+
],
16+
},
17+
{
18+
name: 'John',
19+
messages: [
20+
{
21+
name: 'John',
22+
text: 'I ❤️ React Native!',
23+
},
24+
],
25+
}
26+
],
27+
};
28+
29+
/**
30+
* Randomly simulate network failures.
31+
* It is useful to enable this during development to make sure our app works
32+
* in real-world conditions.
33+
*/
34+
function isNetworkFailure() {
35+
const chanceOfFailure = 0; // 0..1
36+
return Math.random() < chanceOfFailure;
37+
}
38+
39+
/**
40+
* Helper for the other functions in this file.
41+
* Simulates a short delay and then returns a provided value or failure.
42+
* This is just a dummy example. Normally we'd make a HTTP request,
43+
* see http://facebook.github.io/react-native/docs/network.html
44+
*/
45+
function _makeSimulatedNetworkRequest(getValue) {
46+
const durationMs = 400;
47+
return new Promise(function (resolve, reject) {
48+
setTimeout(function () {
49+
if (isNetworkFailure()) {
50+
reject(new Error('Network failure'));
51+
} else {
52+
getValue(resolve, reject);
53+
}
54+
}, durationMs);
55+
});
56+
}
57+
58+
/**
59+
* Fetch a list of all chats for the logged in person.
60+
*/
61+
async function fetchChatList() {
62+
return _makeSimulatedNetworkRequest((resolve, reject) => {
63+
resolve(backendStateForLoggedInPerson.chats.map(chat => chat.name))
64+
});
65+
}
66+
67+
/**
68+
* Fetch a single chat.
69+
*/
70+
async function fetchChat(name) {
71+
return _makeSimulatedNetworkRequest((resolve, reject) => {
72+
resolve(
73+
backendStateForLoggedInPerson.chats.find(
74+
chat => chat.name === name
75+
)
76+
)
77+
});
78+
}
79+
80+
/**
81+
* Send given message to given person.
82+
*/
83+
async function sendMessage({name, message}) {
84+
return _makeSimulatedNetworkRequest((resolve, reject) => {
85+
const chat = backendStateForLoggedInPerson.chats.find(
86+
chat => chat.name === name
87+
);
88+
if (chat) {
89+
chat.messages.push({
90+
name: 'Me',
91+
text: message,
92+
});
93+
resolve();
94+
} else {
95+
reject(new Error('Uknown person: ' + name));
96+
}
97+
});
98+
}
99+
100+
const Backend = {
101+
fetchChatList,
102+
fetchChat,
103+
sendMessage,
104+
};
105+
106+
export default Backend;
107+
108+
// In case you are looking into using Redux for state management,
109+
// this is how network requests are done in the f8 app which uses Redux:
110+
// - To load some data, a Component fires a Redux action, such as loadSession()
111+
// - That action makes the HTTP requests and then dispatches a redux action
112+
// {type: 'LOADED_SESSIONS', results}
113+
// - Then all reducers get called and one of them updates a part of the application
114+
// state by storing the results
115+
// - Redux re-renders the connected Components
116+
// See https://github.com/fbsamples/f8app/search?utf8=%E2%9C%93&q=loaded_sessions

local-cli/templates/HelloNavigation/views/HomeScreenTabNavigator.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
import React, { Component } from 'react';
2-
import {
3-
ListView,
4-
Platform,
5-
Text,
6-
} from 'react-native';
72
import { TabNavigator } from 'react-navigation';
83

94
import ChatListScreen from './chat/ChatListScreen';

local-cli/templates/HelloNavigation/views/chat/ChatListScreen.js

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import React, { Component } from 'react';
22
import {
3+
ActivityIndicator,
34
Image,
45
ListView,
56
Platform,
67
StyleSheet,
8+
View,
79
} from 'react-native';
810
import ListItem from '../../components/ListItem';
11+
import Backend from '../../lib/Backend';
912

1013
export default class ChatListScreen extends Component {
1114

1215
static navigationOptions = {
13-
title: 'Friends',
16+
title: 'Chats',
1417
header: {
1518
visible: Platform.OS === 'ios',
1619
},
@@ -29,35 +32,60 @@ export default class ChatListScreen extends Component {
2932
super(props);
3033
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
3134
this.state = {
32-
dataSource: ds.cloneWithRows([
33-
'Claire', 'John'
34-
])
35+
isLoading: true,
36+
dataSource: ds,
3537
};
3638
}
3739

40+
async componentDidMount() {
41+
const chatList = await Backend.fetchChatList();
42+
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
43+
this.setState((prevState) => ({
44+
dataSource: prevState.dataSource.cloneWithRows(chatList),
45+
isLoading: false,
46+
}));
47+
}
48+
3849
// Binding the function so it can be passed to ListView below
39-
// and 'this' works properly inside _renderRow
40-
_renderRow = (name) => {
50+
// and 'this' works properly inside renderRow
51+
renderRow = (name) => {
4152
return (
4253
<ListItem
4354
label={name}
44-
onPress={() => this.props.navigation.navigate('Chat', {name: name})}
55+
onPress={() => {
56+
// Start fetching in parallel with animating
57+
this.props.navigation.navigate('Chat', {
58+
name: name,
59+
});
60+
}}
4561
/>
4662
)
4763
}
4864

4965
render() {
66+
if (this.state.isLoading) {
67+
return (
68+
<View style={styles.loadingScreen}>
69+
<ActivityIndicator />
70+
</View>
71+
);
72+
}
5073
return (
5174
<ListView
5275
dataSource={this.state.dataSource}
53-
renderRow={this._renderRow}
76+
renderRow={this.renderRow}
5477
style={styles.listView}
5578
/>
5679
);
5780
}
5881
}
5982

6083
const styles = StyleSheet.create({
84+
loadingScreen: {
85+
backgroundColor: 'white',
86+
paddingTop: 8,
87+
flex: 1,
88+
},
6189
listView: {
6290
backgroundColor: 'white',
6391
},

local-cli/templates/HelloNavigation/views/chat/ChatScreen.js

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React, { Component } from 'react';
22
import {
3+
ActivityIndicator,
34
Button,
45
ListView,
5-
Platform,
66
StyleSheet,
77
Text,
88
TextInput,
99
View,
1010
} from 'react-native';
1111
import KeyboardSpacer from '../../components/KeyboardSpacer';
12+
import Backend from '../../lib/Backend';
1213

1314
export default class ChatScreen extends Component {
1415

@@ -19,23 +20,59 @@ export default class ChatScreen extends Component {
1920
constructor(props) {
2021
super(props);
2122
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
22-
const messages = [
23-
{
24-
name: props.navigation.state.params.name,
25-
name: 'Claire',
26-
text: 'I ❤️ React Native!',
27-
},
28-
];
2923
this.state = {
30-
messages: messages,
31-
dataSource: ds.cloneWithRows(messages),
24+
messages: [],
25+
dataSource: ds,
3226
myMessage: '',
27+
isLoading: true,
28+
};
29+
}
30+
31+
async componentDidMount() {
32+
let chat;
33+
try {
34+
chat = await Backend.fetchChat(this.props.navigation.state.params.name);
35+
} catch (err) {
36+
// Here we would handle the fact the request failed, e.g.
37+
// set state to display "Messages could not be loaded".
38+
// We should also check network connection first before making any
39+
// network requests - maybe we're offline? See React Native's NetInfo
40+
// module.
41+
this.setState({
42+
isLoading: false,
43+
});
44+
return;
3345
}
46+
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
47+
this.setState((prevState) => ({
48+
messages: chat.messages,
49+
dataSource: prevState.dataSource.cloneWithRows(chat.messages),
50+
isLoading: false,
51+
}));
3452
}
3553

36-
addMessage = () => {
54+
onAddMessage = async () => {
55+
// Optimistically update the UI
56+
this.addMessageLocal();
57+
// Send the request
58+
try {
59+
await Backend.sendMessage({
60+
name: this.props.navigation.state.params.name,
61+
// TODO Is reading state like this outside of setState OK?
62+
// Can it contain a stale value?
63+
message: this.state.myMessage,
64+
});
65+
} catch (err) {
66+
// Here we would handle the request failure, e.g. call setState
67+
// to display a visual hint showing the message could not be sent.
68+
}
69+
}
70+
71+
addMessageLocal = () => {
3772
this.setState((prevState) => {
38-
if (!prevState.myMessage) return prevState;
73+
if (!prevState.myMessage) {
74+
return prevState;
75+
}
3976
const messages = [
4077
...prevState.messages, {
4178
name: 'Me',
@@ -51,7 +88,7 @@ export default class ChatScreen extends Component {
5188
this.refs.textInput.clear();
5289
}
5390

54-
myMessageChange = (event) => {
91+
onMyMessageChange = (event) => {
5592
this.setState({myMessage: event.nativeEvent.text});
5693
}
5794

@@ -63,6 +100,13 @@ export default class ChatScreen extends Component {
63100
)
64101

65102
render() {
103+
if (this.state.isLoading) {
104+
return (
105+
<View style={styles.container}>
106+
<ActivityIndicator />
107+
</View>
108+
);
109+
}
66110
return (
67111
<View style={styles.container}>
68112
<ListView
@@ -78,13 +122,13 @@ export default class ChatScreen extends Component {
78122
style={styles.textInput}
79123
placeholder='Type a message...'
80124
text={this.state.myMessage}
81-
onSubmitEditing={this.addMessage}
82-
onChange={this.myMessageChange}
125+
onSubmitEditing={this.onAddMessage}
126+
onChange={this.onMyMessageChange}
83127
/>
84128
{this.state.myMessage !== '' && (
85129
<Button
86130
title="Send"
87-
onPress={this.addMessage}
131+
onPress={this.onAddMessage}
88132
/>
89133
)}
90134
</View>
@@ -99,7 +143,6 @@ const styles = StyleSheet.create({
99143
flex: 1,
100144
padding: 8,
101145
backgroundColor: 'white',
102-
alignItems: 'flex-end',
103146
},
104147
listView: {
105148
flex: 1,

local-cli/templates/HelloNavigation/views/welcome/WelcomeScreen.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
Platform,
55
StyleSheet,
66
Text,
7-
View,
87
} from 'react-native';
98

109
import ListItem from '../../components/ListItem';

local-cli/templates/HelloNavigation/views/welcome/WelcomeText.android.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React, { Component } from 'react';
22
import {
3-
AppRegistry,
43
StyleSheet,
54
Text,
6-
View
5+
View,
76
} from 'react-native';
87

98
export default class WelcomeText extends Component {
@@ -22,8 +21,8 @@ export default class WelcomeText extends Component {
2221
file views/welcome/WelcomeText.android.js.
2322
</Text>
2423
<Text style={styles.instructions}>
25-
Press Cmd+R to reload,{'\n'}
26-
Cmd+D or shake for dev menu.
24+
Double tap R on your keyboard to reload,{'\n'}
25+
Shake or press menu button for dev menu.
2726
</Text>
2827
</View>
2928
);

local-cli/templates/HelloNavigation/views/welcome/WelcomeText.ios.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React, { Component } from 'react';
22
import {
3-
AppRegistry,
43
StyleSheet,
54
Text,
6-
View
5+
View,
76
} from 'react-native';
87

98
export default class WelcomeText extends Component {

0 commit comments

Comments
 (0)