From b8542397cd65a147a5a3ce80beac3203d9b33d8a Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Wed, 5 Apr 2017 18:55:45 -0700 Subject: [PATCH] Move away from rnplay to snack, with embedded examples! Summary: React Native Playground has been sunset, so I've replaced the examples that previously used it with examples using [Snack](http://snack.expo.io/). The examples are directly embedded and can be edited live to see updates. The code itself is also in the docs, so we can easily update the docs in one place and we don't have to actually go to a saved app on Snack and update it there. Run it locally, go to the `Animations` section and the `Direct Manipulation` section. ![screen shot 2017-04-03 at 6 29 51 pm](https://cloud.githubusercontent.com/assets/90494/24638271/ff3ad044-189b-11e7-845d-24b2fb612d95.png) Open it on your phone, notice that it falls back to just showing plain code. - Get rid of the Expo new user experience dialog that you see when you open a Snack -- is this a dealbreaker Closes https://github.com/facebook/react-native/pull/13285 Differential Revision: D4828011 Pulled By: hramos fbshipit-source-id: 684ad24a14deb72abb8587ffbb726d316f126d75 --- docs/Animations.md | 168 +++++++++---------- docs/DirectManipulation.md | 51 +++--- website/core/Marked.js | 7 + website/core/Site.js | 223 +++++++++++++++++++------ website/core/SnackPlayer.js | 115 +++++++++++++ website/src/react-native/js/scripts.js | 44 ++++- 6 files changed, 439 insertions(+), 169 deletions(-) create mode 100644 website/core/SnackPlayer.js diff --git a/docs/Animations.md b/docs/Animations.md index c97fe2b460b..dfcf64ccecc 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -26,34 +26,33 @@ The [`Animated`](docs/animated.html) API is designed to make it very easy to con For example, a container view that fades in when it is mounted may look like this: -```javascript -// FadeInView.js -import React, { Component } from 'react'; -import { - Animated, -} from 'react-native'; +```SnackPlayer +import React from 'react'; +import { Animated, Text, View } from 'react-native'; -class FadeInView extends Component { - constructor(props) { - super(props); - this.state = { - fadeAnim: new Animated.Value(0), // Initial value for opacity: 0 - }; +class FadeInView extends React.Component { + state = { + fadeAnim: new Animated.Value(0), // Initial value for opacity: 0 } + componentDidMount() { - Animated.timing( // Animate over time - this.state.fadeAnim, // The animated value to drive + Animated.timing( // Animate over time + this.state.fadeAnim, // The animated value to drive { - toValue: 1, // Animate to opacity: 1, or fully opaque + toValue: 1, // Animate to opacity: 1 (opaque) + duration: 10000, // Make it take a while } - ).start(); // Starts the animation + ).start(); // Starts the animation } + render() { + let { fadeAnim } = this.state; + return ( - {this.props.children} @@ -62,23 +61,20 @@ class FadeInView extends Component { } } -module.exports = FadeInView; -``` - -You can then use your `FadeInView` in place of a `View` in your components, like so: - -```javascript -render() { - return ( - - Fading in - - ) +// You can then use your `FadeInView` in place of a `View` in your components: +export default class App extends React.Component { + render() { + return ( + + + Fading in + + + ) + } } ``` -![FadeInView](img/AnimatedFadeInView.gif) - Let's break down what's happening here. In the `FadeInView` constructor, a new `Animated.Value` called `fadeAnim` is initialized as part of `state`. The opacity property on the `View` is mapped to this animated value. @@ -393,22 +389,29 @@ Note that in order to get this to work on **Android** you need to set the follow UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); ``` -![](img/LayoutAnimationExample.gif) +```SnackPlayer +import React from 'react'; +import { + NativeModules, + LayoutAnimation, + Text, + TouchableOpacity, + StyleSheet, + View, +} from 'react-native'; -```javascript -class App extends React.Component { - constructor(props) { - super(props); - this.state = { w: 100, h: 100 }; - this._onPress = this._onPress.bind(this); - } +const { UIManager } = NativeModules; - componentWillMount() { - // Animate creation - LayoutAnimation.spring(); - } +UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); - _onPress() { +export default class App extends React.Component { + state = { + w: 100, + h: 100, + }; + + _onPress = () => { // Animate the update LayoutAnimation.spring(); this.setState({w: this.state.w + 15, h: this.state.h + 15}) @@ -427,8 +430,30 @@ class App extends React.Component { ); } } + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + box: { + width: 200, + height: 200, + backgroundColor: 'red', + }, + button: { + backgroundColor: 'black', + paddingHorizontal: 20, + paddingVertical: 15, + marginTop: 15, + }, + buttonText: { + color: '#fff', + fontWeight: 'bold', + }, +}); ``` -[Run this example](https://rnplay.org/apps/uaQrGQ) This example uses a preset value, you can customize the animations as you need, see [LayoutAnimation.js](https://github.com/facebook/react-native/blob/master/Libraries/LayoutAnimation/LayoutAnimation.js) @@ -457,45 +482,12 @@ We could use this in the Rebound example to update the scale - this might be helpful if the component that we are updating is deeply nested and hasn't been optimized with `shouldComponentUpdate`. -```javascript -// Back inside of the App component, replace the scrollSpring listener -// in componentWillMount with this: -this._scrollSpring.addListener({ - onSpringUpdate: () => { - if (!this._photo) { return } - var v = this._scrollSpring.getCurrentValue(); - var newProps = {style: {transform: [{scaleX: v}, {scaleY: v}]}}; - this._photo.setNativeProps(newProps); - }, -}); - -// Lastly, we update the render function to no longer pass in the -// transform via style (avoid clashes when re-rendering) and to set the -// photo ref -render() { - return ( - - - this._photo = component} - source={{uri: "img/ReboundExample.png"}} - style={{width: 250, height: 200}} /> - - - ); -} -``` -[Run this example](https://rnplay.org/apps/fUqjAg) - -It would not make sense to use `setNativeProps` with react-tween-state -because the updated tween values are set on the state automatically by -the library - Rebound on the other hand gives us an updated value for -each frame with the `onSpringUpdate` function. - -If you find your animations with dropping frames (performing below 60 -frames per second), look into using `setNativeProps` or -`shouldComponentUpdate` to optimize them. You may also want to defer any -computationally intensive work until after animations are complete, -using the -[InteractionManager](docs/interactionmanager.html). You -can monitor the frame rate by using the In-App Developer Menu "FPS -Monitor" tool. +If you find your animations with dropping frames (performing below 60 frames +per second), look into using `setNativeProps` or `shouldComponentUpdate` to +optimize them. Or you could run the animations on the UI thread rather than +the JavaScript thread [with the useNativeDriver +option](http://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html). +You may also want to defer any computationally intensive work until after +animations are complete, using the +[InteractionManager](docs/interactionmanager.html). You can monitor the +frame rate by using the In-App Developer Menu "FPS Monitor" tool. \ No newline at end of file diff --git a/docs/DirectManipulation.md b/docs/DirectManipulation.md index 8c692544d57..ae0924cde9f 100644 --- a/docs/DirectManipulation.md +++ b/docs/DirectManipulation.md @@ -94,7 +94,10 @@ ReactNativeBaseComponent.js](https://github.com/facebook/react/blob/master/src/r Composite components are not backed by a native view, so you cannot call `setNativeProps` on them. Consider this example: -```javascript +```SnackPlayer?name=setNativeProps%20with%20Composite%20Components +import React from 'react'; +import { Text, TouchableOpacity, View } from 'react-native'; + class MyButton extends React.Component { render() { return ( @@ -105,7 +108,7 @@ class MyButton extends React.Component { } } -class App extends React.Component { +export default class App extends React.Component { render() { return ( @@ -115,7 +118,6 @@ class App extends React.Component { } } ``` -[Run this example](https://rnplay.org/apps/JXkgmQ) If you run this you will immediately see this error: `Touchable child must either be native or forward setNativeProps to a native component`. @@ -133,9 +135,12 @@ All we need to do is provide a `setNativeProps` method on our component that calls `setNativeProps` on the appropriate child with the given arguments. -```javascript +```SnackPlayer?name=Forwarding%20setNativeProps +import React from 'react'; +import { Text, TouchableOpacity, View } from 'react-native'; + class MyButton extends React.Component { - setNativeProps(nativeProps) { + setNativeProps = (nativeProps) => { this._root.setNativeProps(nativeProps); } @@ -147,8 +152,17 @@ class MyButton extends React.Component { ) } } + +export default class App extends React.Component { + render() { + return ( + + + + ) + } +} ``` -[Run this example](https://rnplay.org/apps/YJxnEQ) You can now use `MyButton` inside of `TouchableOpacity`! A sidenote for clarity: we used the [ref callback](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) syntax here, rather than the traditional string-based ref. @@ -173,22 +187,22 @@ use `setNativeProps` to directly manipulate the TextInput value when necessary. For example, the following code demonstrates clearing the input when you tap a button: -```javascript -class App extends React.Component { - constructor(props) { - super(props); - this.clearText = this.clearText.bind(this); - } +```SnackPlayer?name=Clear%20text +import React from 'react'; +import { TextInput, Text, TouchableOpacity, View } from 'react-native'; - clearText() { +export default class App extends React.Component { + clearText = () => { this._textInput.setNativeProps({text: ''}); } render() { return ( - - this._textInput = component} - style={styles.textInput} /> + + this._textInput = component} + style={{height: 50, flex: 1, marginHorizontal: 20, borderWidth: 1, borderColor: '#ccc'}} + /> Clear text @@ -197,7 +211,6 @@ class App extends React.Component { } } ``` -[Run this example](https://rnplay.org/plays/pOI9bA) ## Avoiding conflicts with the render function @@ -205,9 +218,7 @@ If you update a property that is also managed by the render function, you might end up with some unpredictable and confusing bugs because anytime the component re-renders and that property changes, whatever value was previously set from `setNativeProps` will be completely -ignored and overridden. [See this example](https://rnplay.org/apps/bp1DvQ) -for a demonstration of what can happen if these two collide - notice -the jerky animation each 250ms when `setState` triggers a re-render. +ignored and overridden. ## setNativeProps & shouldComponentUpdate diff --git a/website/core/Marked.js b/website/core/Marked.js index f727315cc06..64bc26feba7 100644 --- a/website/core/Marked.js +++ b/website/core/Marked.js @@ -15,6 +15,7 @@ var Header = require('Header'); var Prism = require('Prism'); var React = require('React'); var WebPlayer = require('WebPlayer'); +var SnackPlayer = require('SnackPlayer'); /** * Block-Level Grammar @@ -842,6 +843,12 @@ Parser.prototype.tok = function() { ); } + if (lang && lang.indexOf('SnackPlayer') === 0) { + return ( + {text} + ); + } + return {text}; } case 'table': { diff --git a/website/core/Site.js b/website/core/Site.js index 736b8a7170c..286aa06b05d 100644 --- a/website/core/Site.js +++ b/website/core/Site.js @@ -20,9 +20,11 @@ var Site = React.createClass({ const version = Metadata.config.RN_VERSION; const algoliaVersion = version === 'next' ? 'master' : version; var basePath = '/react-native/' + (path ? path + '/' : ''); - var currentYear = (new Date()).getFullYear(); + var currentYear = new Date().getFullYear(); - var title = this.props.title ? this.props.title : 'React Native | A framework for building native apps using React'; + var title = this.props.title + ? this.props.title + : 'React Native | A framework for building native apps using React'; var metaTags = [ { charSet: 'utf-8' }, @@ -35,8 +37,8 @@ var Site = React.createClass({ content: 'width=device-width', }, // Facebook - { property: 'fb:app_id', content: '1677033832619985', }, - { property: 'fb:admins', content: '121800083', }, + { property: 'fb:app_id', content: '1677033832619985' }, + { property: 'fb:admins', content: '121800083' }, // Open Graph { property: 'og:site_name', @@ -48,15 +50,20 @@ var Site = React.createClass({ }, { property: 'og:url', - content: 'https://facebook.github.io/react-native/' + (this.props.path ? this.props.path : 'index.html'), + content: 'https://facebook.github.io/react-native/' + + (this.props.path ? this.props.path : 'index.html'), }, { property: 'og:image', - content: this.props.image ? this.props.image : 'https://facebook.github.io/react-native/img/opengraph.png', + content: this.props.image + ? this.props.image + : 'https://facebook.github.io/react-native/img/opengraph.png', }, { property: 'og:description', - content: this.props.description ? this.props.description : 'A framework for building native apps using React', + content: this.props.description + ? this.props.description + : 'A framework for building native apps using React', }, // Twitter Cards { @@ -69,18 +76,23 @@ var Site = React.createClass({ }, ]; - var typeTags = [{ - property: 'og:type', - content: 'website', - }]; - if (this.props.author) { - typeTags = [{ + var typeTags = [ + { property: 'og:type', - content: 'article', - }, { - property: 'article:author', - content: this.props.author, - }]; + content: 'website', + }, + ]; + if (this.props.author) { + typeTags = [ + { + property: 'og:type', + content: 'article', + }, + { + property: 'article:author', + content: this.props.author, + }, + ]; } metaTags.push(...typeTags); @@ -95,27 +107,46 @@ var Site = React.createClass({ {title} - { - metaTags.map((tag, index) => - ) - } + {metaTags.map((tag, index) => )} - + - - + + + -