Remove built-in navigation template (#21155)

Summary:
This is an updated (rebased) version of the very old #16579.

I added a [template](https://github.com/facebook/react-native/tree/master/local-cli/templates/HelloNavigation) for react-navigation directly into the React Native repo a long time ago. The feature `react-native init --template foo` supports both:
- Remote templates (`react-native-template-foo` in npm as well as HTTP URLs)
- Local templates that ship with react-native itself

In retrospect, adding the local template wasn't a good idea. Templates should live outside of the React Native repo and be versioned independently. This way templates can be fixed independently of React Native releases, and people can use new templates without having to upgrade React Native.
Pull Request resolved: https://github.com/facebook/react-native/pull/21155

Differential Revision: D9885719

Pulled By: hramos

fbshipit-source-id: d2982f374d3c451c09e348ce5fcdca9d0be5a474
This commit is contained in:
Martin Konicek 2018-09-17 15:11:28 -07:00 committed by Facebook Github Bot
parent 85505fdd3e
commit 52dd7dbbcf
16 changed files with 3 additions and 855 deletions

View File

@ -14,45 +14,6 @@ const execSync = require('child_process').execSync;
const fs = require('fs');
const path = require('path');
/**
* Templates released as part of react-native in local-cli/templates.
*/
const builtInTemplates = {
navigation: 'HelloNavigation',
};
function listTemplatesAndExit(newProjectName, options) {
if (options.template === true) {
// Just listing templates using 'react-native init --template'.
// Not creating a new app.
// Print available templates and exit.
const templateKeys = Object.keys(builtInTemplates);
if (templateKeys.length === 0) {
// Just a guard, should never happen as long builtInTemplates
// above is defined correctly :)
console.log(
'There are no templates available besides ' +
'the default "Hello World" one.',
);
} else {
console.log(
'The available templates are:\n' +
templateKeys.join('\n') +
'\nYou can use these to create an app based on a template, for example: ' +
'you could run: ' +
'react-native init ' +
newProjectName +
' --template ' +
templateKeys[0],
);
}
// Exit 'react-native init'
return true;
}
// Continue 'react-native init'
return false;
}
/**
* @param destPath Create the new project at this path.
* @param newProjectName For example 'AwesomeApp'.
@ -92,41 +53,7 @@ function createProjectFromTemplate(
// This way we don't have to duplicate the native files in every template.
// If we duplicated them we'd make RN larger and risk that people would
// forget to maintain all the copies so they would go out of sync.
const builtInTemplateName = builtInTemplates[template];
if (builtInTemplateName) {
// template is e.g. 'navigation',
// use the built-in local-cli/templates/HelloNavigation folder
createFromBuiltInTemplate(
builtInTemplateName,
destPath,
newProjectName,
yarnVersion,
);
} else {
// template is e.g. 'ignite',
// use the template react-native-template-ignite from npm
createFromRemoteTemplate(template, destPath, newProjectName, yarnVersion);
}
}
// (We might want to get rid of built-in templates in the future -
// publish them to npm and install from there.)
function createFromBuiltInTemplate(
templateName,
destPath,
newProjectName,
yarnVersion,
) {
const templatePath = path.resolve(
'node_modules',
'react-native',
'local-cli',
'templates',
templateName,
);
copyProjectTemplateAndReplace(templatePath, destPath, newProjectName);
installTemplateDependencies(templatePath, yarnVersion);
installTemplateDevDependencies(templatePath, yarnVersion);
createFromRemoteTemplate(template, destPath, newProjectName, yarnVersion);
}
/**
@ -264,6 +191,5 @@ function installTemplateDevDependencies(templatePath, yarnVersion) {
}
module.exports = {
listTemplatesAndExit,
createProjectFromTemplate,
};

View File

@ -10,7 +10,6 @@
'use strict';
const {
listTemplatesAndExit,
createProjectFromTemplate,
} = require('../generator/templates');
const execSync = require('child_process').execSync;
@ -44,14 +43,8 @@ function init(projectDir, argsOrName) {
const newProjectName = args[0];
const options = minimist(args);
if (listTemplatesAndExit(newProjectName, options)) {
// Just listing templates using 'react-native init --template'
// Not creating a new app.
return;
} else {
console.log('Setting up new React Native app in ' + projectDir);
generateProject(projectDir, newProjectName, options);
}
console.log('Setting up new React Native app in ' + projectDir);
generateProject(projectDir, newProjectName, options);
}
/**

View File

@ -1,27 +0,0 @@
/**
* This is an example React Native app demonstrates ListViews, text input and
* navigation between a few screens.
* https://github.com/facebook/react-native
*
* @format
*/
import React, {Component} from 'react';
import {StackNavigator} from 'react-navigation';
import HomeScreenTabNavigator from './views/HomeScreenTabNavigator';
import ChatScreen from './views/chat/ChatScreen';
/**
* Top-level navigator. Renders the application UI.
*/
const App = StackNavigator({
Home: {
screen: HomeScreenTabNavigator,
},
Chat: {
screen: ChatScreen,
},
});
export default App;

View File

@ -1,41 +0,0 @@
# App template for new React Native apps
This is a simple React Native app template which demonstrates a few basics concepts such as navigation between a few screens, FlatLists, and handling text input.
<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"/>
## Purpose
The idea is to make it easier for people to get started with React Native. Currently `react-native init` creates a very simple app that contains one screen with static text. Everyone new to React Native then needs to figure out how to do very basic things such as:
- Rendering a list of items fetched from a server
- Navigating between screens
- Handling text input and the software keyboard
This app serves as a template used by `react-native init` so it is easier for anyone to get up and running quickly by having an app with a few screens and a FlatList ready to go.
### Best practices
Another purpose of this app is to define best practices such as the folder structure of a standalone React Native app and naming conventions.
## Not using Redux
This template intentionally doesn't use Redux. After discussing with a few people who have experience using Redux we concluded that adding Redux to this app targeted at beginners would make the code more confusing, and wouldn't clearly show the benefits of Redux (because the app is too small). There are already a few concepts to grasp - the React component lifecycle, rendering lists, using async / await, handling the software keyboard. We thought that's the maximum amount of things to learn at once. It's better for everyone to see patterns in their codebase as the app grows and decide for themselves whether and when they need Redux. See also the post [You Might Not Need Redux](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367#.f3q7kq4b3) by [Dan Abramov](https://twitter.com/dan_abramov).
## Not using Flow (for now)
Many people are new to React Native, some are new to ES6 and most people will be new to Flow. Therefore we didn't want to introduce all these concepts all at once in a single codebase. However, it might make sense to later introduce a separate version of this template that uses Flow annotations.
## Provide feedback
We need your feedback. Do you have a lot of experience building React Native apps? If so, please carefully read the code of the template and if you think something should be done differently, use issues in the repo [mkonicek/AppTemplateFeedback](https://github.com/mkonicek/AppTemplateFeedback) to discuss what should be done differently.
## How to use the template
```
$ react-native init MyApp --template navigation
$ cd MyApp
$ react-native run-android
$ react-native run-ios
```

View File

@ -1,120 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
/* @flow */
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Platform, View, Keyboard, LayoutAnimation} from 'react-native';
type Props = {
offset?: number,
};
type State = {
keyboardHeight: number,
};
// Consider contributing this to the popular library:
// https://github.com/Andr3wHur5t/react-native-keyboard-spacer
/**
* On iOS, the software keyboard covers the screen by default.
* This is not desirable if there are TextInputs near the bottom of the screen -
* they would be covered by the keyboard and the user cannot see what they
* are typing.
* To get around this problem, place a `<KeyboardSpacer />` at the bottom
* of the screen, after your TextInputs. The keyboard spacer has size 0 and
* when the keyboard is shown it will grow to the same size as the keyboard,
* shifting all views above it and therefore making them visible.
*
* On Android, this component is not needed because resizing the UI when
* the keyboard is shown is supported by the OS.
* Simply set the `android:windowSoftInputMode="adjustResize"` attribute
* on the <activity> element in your AndroidManifest.xml.
*
* How is this different from KeyboardAvoidingView?
* The KeyboardAvoidingView doesn't work when used together with
* a ScrollView/ListView.
*/
const KeyboardSpacer = () =>
Platform.OS === 'ios' ? <KeyboardSpacerIOS /> : null;
class KeyboardSpacerIOS extends Component<Props, State> {
static propTypes = {
offset: PropTypes.number,
};
static defaultProps = {
offset: 0,
};
state: State = {
keyboardHeight: 0,
};
UNSAFE_componentWillMount() {
this._registerEvents();
}
componentWillUnmount() {
this._unRegisterEvents();
}
_keyboardWillShowSubscription: {+remove: Function};
_keyboardWillHideSubscription: {+remove: Function};
_registerEvents = () => {
this._keyboardWillShowSubscription = Keyboard.addListener(
'keyboardWillShow',
this._keyboardWillShow,
);
this._keyboardWillHideSubscription = Keyboard.addListener(
'keyboardWillHide',
this._keyboardWillHide,
);
};
_unRegisterEvents = () => {
this._keyboardWillShowSubscription.remove();
this._keyboardWillHideSubscription.remove();
};
_configureLayoutAnimation = () => {
// Any duration is OK here. The `type: 'keyboard defines the animation.
LayoutAnimation.configureNext({
duration: 100,
update: {
type: 'keyboard',
},
});
};
_keyboardWillShow = (e: any) => {
this._configureLayoutAnimation();
this.setState({
keyboardHeight: e.endCoordinates.height - (this.props.offset || 0),
});
};
_keyboardWillHide = () => {
this._configureLayoutAnimation();
this.setState({
keyboardHeight: 0,
});
};
render() {
return <View style={{height: this.state.keyboardHeight}} />;
}
}
export default KeyboardSpacer;

View File

@ -1,56 +0,0 @@
/** @format */
'use strict';
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
TouchableHighlight,
TouchableNativeFeedback,
View,
} from 'react-native';
/**
* Renders the right type of Touchable for the list item, based on platform.
*/
const Touchable = ({onPress, children}) => {
const child = React.Children.only(children);
if (Platform.OS === 'android') {
return (
<TouchableNativeFeedback onPress={onPress}>
{child}
</TouchableNativeFeedback>
);
} else {
return (
<TouchableHighlight onPress={onPress} underlayColor="#ddd">
{child}
</TouchableHighlight>
);
}
};
const ListItem = ({label, onPress}) => (
<Touchable onPress={onPress}>
<View style={styles.item}>
<Text style={styles.label}>{label}</Text>
</View>
</Touchable>
);
const styles = StyleSheet.create({
item: {
height: 48,
justifyContent: 'center',
paddingLeft: 12,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#ddd',
},
label: {
fontSize: 16,
},
});
export default ListItem;

View File

@ -1,3 +0,0 @@
{
"react-navigation": "1.0.0-beta.11"
}

View File

@ -1,117 +0,0 @@
/** @format */
'use strict';
// 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 chatForName = backendStateForLoggedInPerson.chats.find(
chat => chat.name === name,
);
if (chatForName) {
chatForName.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,20 +0,0 @@
/** @format */
import {TabNavigator} from 'react-navigation';
import ChatListScreen from './chat/ChatListScreen';
import WelcomeScreen from './welcome/WelcomeScreen';
/**
* Screen with tabs shown on app startup.
*/
const HomeScreenTabNavigator = TabNavigator({
Welcome: {
screen: WelcomeScreen,
},
Chats: {
screen: ChatListScreen,
},
});
export default HomeScreenTabNavigator;

View File

@ -1,91 +0,0 @@
/** @format */
import React, {Component} from 'react';
import {
ActivityIndicator,
Image,
FlatList,
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: 'Chats',
header: Platform.OS === 'ios' ? undefined : null,
tabBarIcon: ({tintColor}) => (
<Image
// Using react-native-vector-icons works here too
source={require('./chat-icon.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
};
constructor(props) {
super(props);
this.state = {
isLoading: true,
};
}
async componentDidMount() {
const chatList = await Backend.fetchChatList();
this.setState(prevState => ({
chatList,
isLoading: false,
}));
}
// Binding the function so it can be passed to FlatList below
// and 'this' works properly inside renderItem
renderItem = ({item}) => {
return (
<ListItem
label={item}
onPress={() => {
// Start fetching in parallel with animating
this.props.navigation.navigate('Chat', {
name: item,
});
}}
/>
);
};
render() {
if (this.state.isLoading) {
return (
<View style={styles.loadingScreen}>
<ActivityIndicator />
</View>
);
}
return (
<FlatList
data={this.state.chatList}
renderItem={this.renderItem}
keyExtractor={(item, index) => index}
style={styles.listView}
/>
);
}
}
const styles = StyleSheet.create({
loadingScreen: {
backgroundColor: 'white',
paddingTop: 8,
flex: 1,
},
listView: {
backgroundColor: 'white',
},
icon: {
width: 30,
height: 26,
},
});

View File

@ -1,170 +0,0 @@
/** @format */
import React, {Component} from 'react';
import {
ActivityIndicator,
Button,
FlatList,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import KeyboardSpacer from '../../components/KeyboardSpacer';
import Backend from '../../lib/Backend';
export default class ChatScreen extends Component {
static navigationOptions = ({navigation}) => ({
title: `Chat with ${navigation.state.params.name}`,
});
constructor(props) {
super(props);
this.state = {
messages: [],
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;
}
this.setState(prevState => ({
messages: 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.
}
};
addMessageLocal = () => {
this.setState(prevState => {
if (!prevState.myMessage) {
return prevState;
}
const messages = [
...prevState.messages,
{
name: 'Me',
text: prevState.myMessage,
},
];
return {
messages: messages,
myMessage: '',
};
});
this.textInput.clear();
};
onMyMessageChange = event => {
this.setState({myMessage: event.nativeEvent.text});
};
renderItem = ({item}) => (
<View style={styles.bubble}>
<Text style={styles.name}>{item.name}</Text>
<Text>{item.text}</Text>
</View>
);
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={this.state.messages}
renderItem={this.renderItem}
keyExtractor={(item, index) => index}
style={styles.listView}
/>
<View style={styles.composer}>
<TextInput
ref={textInput => {
this.textInput = textInput;
}}
style={styles.textInput}
placeholder="Type a message..."
text={this.state.myMessage}
onSubmitEditing={this.onAddMessage}
onChange={this.onMyMessageChange}
/>
{this.state.myMessage !== '' && (
<Button title="Send" onPress={this.onAddMessage} />
)}
</View>
<KeyboardSpacer />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 8,
backgroundColor: 'white',
},
listView: {
flex: 1,
alignSelf: 'stretch',
},
bubble: {
alignSelf: 'flex-end',
backgroundColor: '#d6f3fc',
padding: 12,
borderRadius: 4,
marginBottom: 4,
},
name: {
fontWeight: 'bold',
},
composer: {
flexDirection: 'row',
alignItems: 'center',
height: 36,
},
textInput: {
flex: 1,
borderColor: '#ddd',
borderWidth: 1,
padding: 4,
height: 30,
fontSize: 13,
marginRight: 8,
},
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,33 +0,0 @@
/** @format */
import React, {Component} from 'react';
import {Image, Platform, StyleSheet} from 'react-native';
import ListItem from '../../components/ListItem';
import WelcomeText from './WelcomeText';
export default class WelcomeScreen extends Component {
static navigationOptions = {
title: 'Welcome',
// You can now set header: null on any component to hide the header
header: Platform.OS === 'ios' ? undefined : null,
tabBarIcon: ({tintColor}) => (
<Image
// Using react-native-vector-icons works here too
source={require('./welcome-icon.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
};
render() {
return <WelcomeText />;
}
}
const styles = StyleSheet.create({
icon: {
width: 30,
height: 26,
},
});

View File

@ -1,46 +0,0 @@
/** @format */
import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';
export default class WelcomeText extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>
This app shows the basics of navigating between a few screens, working
with ListView and handling text input.
</Text>
<Text style={styles.instructions}>
Modify any files to get started. For example try changing the file
views/welcome/WelcomeText.android.js.
</Text>
<Text style={styles.instructions}>
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu.
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
padding: 20,
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 16,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 12,
},
});

View File

@ -1,47 +0,0 @@
/** @format */
import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';
export default class WelcomeText extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>
This app shows the basics of navigating between a few screens, working
with ListView and handling text input.
</Text>
<Text style={styles.instructions}>
Modify any files to get started. For example try changing the file{
'\n'
}views/welcome/WelcomeText.ios.js.
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu.
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
padding: 20,
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 16,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 12,
},
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB