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 https://github.com/facebook/react-native/pull/12260

Differential Revision: D4521758

Pulled By: mkonicek

fbshipit-source-id: d7d9e481dd3373917ac68ec9169b9ac3267547a9
This commit is contained in:
Martin Konicek
2017-02-07 09:03:00 -08:00
committed by Facebook Github Bot
parent 016969df49
commit 4551e9347a
7 changed files with 216 additions and 37 deletions

View File

@@ -0,0 +1,116 @@
// This file just a dummy example of a HTTP API to talk to the backend.
// The state of the "database" that would normally live on the server
// is simply held here in memory.
const backendStateForLoggedInPerson = {
chats: [
{
name: 'Claire',
messages: [
{
name: 'Claire',
text: 'I ❤️ React Native!',
},
],
},
{
name: 'John',
messages: [
{
name: 'John',
text: 'I ❤️ React Native!',
},
],
}
],
};
/**
* Randomly simulate network failures.
* It is useful to enable this during development to make sure our app works
* in real-world conditions.
*/
function isNetworkFailure() {
const chanceOfFailure = 0; // 0..1
return Math.random() < chanceOfFailure;
}
/**
* Helper for the other functions in this file.
* Simulates a short delay and then returns a provided value or failure.
* This is just a dummy example. Normally we'd make a HTTP request,
* see http://facebook.github.io/react-native/docs/network.html
*/
function _makeSimulatedNetworkRequest(getValue) {
const durationMs = 400;
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (isNetworkFailure()) {
reject(new Error('Network failure'));
} else {
getValue(resolve, reject);
}
}, durationMs);
});
}
/**
* Fetch a list of all chats for the logged in person.
*/
async function fetchChatList() {
return _makeSimulatedNetworkRequest((resolve, reject) => {
resolve(backendStateForLoggedInPerson.chats.map(chat => chat.name))
});
}
/**
* Fetch a single chat.
*/
async function fetchChat(name) {
return _makeSimulatedNetworkRequest((resolve, reject) => {
resolve(
backendStateForLoggedInPerson.chats.find(
chat => chat.name === name
)
)
});
}
/**
* Send given message to given person.
*/
async function sendMessage({name, message}) {
return _makeSimulatedNetworkRequest((resolve, reject) => {
const chat = backendStateForLoggedInPerson.chats.find(
chat => chat.name === name
);
if (chat) {
chat.messages.push({
name: 'Me',
text: message,
});
resolve();
} else {
reject(new Error('Uknown person: ' + name));
}
});
}
const Backend = {
fetchChatList,
fetchChat,
sendMessage,
};
export default Backend;
// In case you are looking into using Redux for state management,
// this is how network requests are done in the f8 app which uses Redux:
// - To load some data, a Component fires a Redux action, such as loadSession()
// - That action makes the HTTP requests and then dispatches a redux action
// {type: 'LOADED_SESSIONS', results}
// - Then all reducers get called and one of them updates a part of the application
// state by storing the results
// - Redux re-renders the connected Components
// See https://github.com/fbsamples/f8app/search?utf8=%E2%9C%93&q=loaded_sessions

View File

@@ -1,9 +1,4 @@
import React, { Component } from 'react';
import {
ListView,
Platform,
Text,
} from 'react-native';
import { TabNavigator } from 'react-navigation';
import ChatListScreen from './chat/ChatListScreen';

View File

@@ -1,16 +1,19 @@
import React, { Component } from 'react';
import {
ActivityIndicator,
Image,
ListView,
Platform,
StyleSheet,
View,
} from 'react-native';
import ListItem from '../../components/ListItem';
import Backend from '../../lib/Backend';
export default class ChatListScreen extends Component {
static navigationOptions = {
title: 'Friends',
title: 'Chats',
header: {
visible: Platform.OS === 'ios',
},
@@ -29,28 +32,48 @@ export default class ChatListScreen extends Component {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows([
'Claire', 'John'
])
isLoading: true,
dataSource: ds,
};
}
async componentDidMount() {
const chatList = await Backend.fetchChatList();
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState((prevState) => ({
dataSource: prevState.dataSource.cloneWithRows(chatList),
isLoading: false,
}));
}
// Binding the function so it can be passed to ListView below
// and 'this' works properly inside _renderRow
_renderRow = (name) => {
// and 'this' works properly inside renderRow
renderRow = (name) => {
return (
<ListItem
label={name}
onPress={() => this.props.navigation.navigate('Chat', {name: name})}
onPress={() => {
// Start fetching in parallel with animating
this.props.navigation.navigate('Chat', {
name: name,
});
}}
/>
)
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.loadingScreen}>
<ActivityIndicator />
</View>
);
}
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderRow={this.renderRow}
style={styles.listView}
/>
);
@@ -58,6 +81,11 @@ export default class ChatListScreen extends Component {
}
const styles = StyleSheet.create({
loadingScreen: {
backgroundColor: 'white',
paddingTop: 8,
flex: 1,
},
listView: {
backgroundColor: 'white',
},

View File

@@ -1,14 +1,15 @@
import React, { Component } from 'react';
import {
ActivityIndicator,
Button,
ListView,
Platform,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import KeyboardSpacer from '../../components/KeyboardSpacer';
import Backend from '../../lib/Backend';
export default class ChatScreen extends Component {
@@ -19,23 +20,59 @@ export default class ChatScreen extends Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
const messages = [
{
name: props.navigation.state.params.name,
name: 'Claire',
text: 'I ❤️ React Native!',
},
];
this.state = {
messages: messages,
dataSource: ds.cloneWithRows(messages),
messages: [],
dataSource: ds,
myMessage: '',
isLoading: true,
};
}
async componentDidMount() {
let chat;
try {
chat = await Backend.fetchChat(this.props.navigation.state.params.name);
} catch (err) {
// Here we would handle the fact the request failed, e.g.
// set state to display "Messages could not be loaded".
// We should also check network connection first before making any
// network requests - maybe we're offline? See React Native's NetInfo
// module.
this.setState({
isLoading: false,
});
return;
}
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState((prevState) => ({
messages: chat.messages,
dataSource: prevState.dataSource.cloneWithRows(chat.messages),
isLoading: false,
}));
}
onAddMessage = async () => {
// Optimistically update the UI
this.addMessageLocal();
// Send the request
try {
await Backend.sendMessage({
name: this.props.navigation.state.params.name,
// TODO Is reading state like this outside of setState OK?
// Can it contain a stale value?
message: this.state.myMessage,
});
} catch (err) {
// Here we would handle the request failure, e.g. call setState
// to display a visual hint showing the message could not be sent.
}
}
addMessage = () => {
addMessageLocal = () => {
this.setState((prevState) => {
if (!prevState.myMessage) return prevState;
if (!prevState.myMessage) {
return prevState;
}
const messages = [
...prevState.messages, {
name: 'Me',
@@ -51,7 +88,7 @@ export default class ChatScreen extends Component {
this.refs.textInput.clear();
}
myMessageChange = (event) => {
onMyMessageChange = (event) => {
this.setState({myMessage: event.nativeEvent.text});
}
@@ -63,6 +100,13 @@ export default class ChatScreen extends Component {
)
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.container}>
<ListView
@@ -78,13 +122,13 @@ export default class ChatScreen extends Component {
style={styles.textInput}
placeholder='Type a message...'
text={this.state.myMessage}
onSubmitEditing={this.addMessage}
onChange={this.myMessageChange}
onSubmitEditing={this.onAddMessage}
onChange={this.onMyMessageChange}
/>
{this.state.myMessage !== '' && (
<Button
title="Send"
onPress={this.addMessage}
onPress={this.onAddMessage}
/>
)}
</View>
@@ -99,7 +143,6 @@ const styles = StyleSheet.create({
flex: 1,
padding: 8,
backgroundColor: 'white',
alignItems: 'flex-end',
},
listView: {
flex: 1,

View File

@@ -4,7 +4,6 @@ import {
Platform,
StyleSheet,
Text,
View,
} from 'react-native';
import ListItem from '../../components/ListItem';

View File

@@ -1,9 +1,8 @@
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
View,
} from 'react-native';
export default class WelcomeText extends Component {
@@ -22,8 +21,8 @@ export default class WelcomeText extends Component {
file views/welcome/WelcomeText.android.js.
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu.
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu.
</Text>
</View>
);

View File

@@ -1,9 +1,8 @@
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
View,
} from 'react-native';
export default class WelcomeText extends Component {