diff --git a/content/docs/handling-events.md b/content/docs/handling-events.md index 175303c24ef..4d7e0688e91 100644 --- a/content/docs/handling-events.md +++ b/content/docs/handling-events.md @@ -39,20 +39,7 @@ Another difference is that you cannot return `false` to prevent default behavior In React, this could instead be: -```js{2-5,8} -function ActionLink() { - function handleClick(e) { - e.preventDefault(); - console.log('The link was clicked.'); - } - - return ( - - Click me - - ); -} -``` +`embed:handling-events/action-link-example.js` Here, `e` is a synthetic event. React defines these synthetic events according to the [W3C spec](https://www.w3.org/TR/DOM-Level-3-Events/), so you don't need to worry about cross-browser compatibility. See the [`SyntheticEvent`](/docs/events.html) reference guide to learn more. @@ -60,38 +47,9 @@ When using React you should generally not need to call `addEventListener` to add When you define a component using an [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes), a common pattern is for an event handler to be a method on the class. For example, this `Toggle` component renders a button that lets the user toggle between "ON" and "OFF" states: -```js{6,7,10-14,18} -class Toggle extends React.Component { - constructor(props) { - super(props); - this.state = {isToggleOn: true}; - - // This binding is necessary to make `this` work in the callback - this.handleClick = this.handleClick.bind(this); - } - - handleClick() { - this.setState(state => ({ - isToggleOn: !state.isToggleOn - })); - } - - render() { - return ( - - ); - } -} - -ReactDOM.render( - , - document.getElementById('root') -); -``` +`embed:handling-events/toggle-example.js` -[**Try it on CodePen**](https://codepen.io/gaearon/pen/xEmzGg?editors=0010) +**[Try it on CodeSandbox](codesandbox://handling-events/toggle-example,handling-events/toggle-example.css)** You have to be careful about the meaning of `this` in JSX callbacks. In JavaScript, class methods are not [bound](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind) by default. If you forget to bind `this.handleClick` and pass it to `onClick`, `this` will be `undefined` when the function is actually called. @@ -99,44 +57,13 @@ This is not React-specific behavior; it is a part of [how functions work in Java If calling `bind` annoys you, there are two ways you can get around this. If you are using the experimental [public class fields syntax](https://babeljs.io/docs/plugins/transform-class-properties/), you can use class fields to correctly bind callbacks: -```js{2-6} -class LoggingButton extends React.Component { - // This syntax ensures `this` is bound within handleClick. - // Warning: this is *experimental* syntax. - handleClick = () => { - console.log('this is:', this); - } - - render() { - return ( - - ); - } -} -``` +`embed:handling-events/login-button-public-class-field-example.js` This syntax is enabled by default in [Create React App](https://github.com/facebookincubator/create-react-app). If you aren't using class fields syntax, you can use an [arrow function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) in the callback: -```js{7-9} -class LoggingButton extends React.Component { - handleClick() { - console.log('this is:', this); - } - - render() { - // This syntax ensures `this` is bound within handleClick - return ( - - ); - } -} -``` +`embed:handling-events/login-button-arrow-function.js` The problem with this syntax is that a different callback is created each time the `LoggingButton` renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem. diff --git a/examples/handling-events/action-link-example.js b/examples/handling-events/action-link-example.js new file mode 100644 index 00000000000..1635be28427 --- /dev/null +++ b/examples/handling-events/action-link-example.js @@ -0,0 +1,13 @@ +// highlight-range{2-5,8} +function ActionLink() { + function handleClick(e) { + e.preventDefault(); + console.log('The link was clicked.'); + } + + return ( + + Click me + + ); +} diff --git a/examples/handling-events/login-button-arrow-function.js b/examples/handling-events/login-button-arrow-function.js new file mode 100644 index 00000000000..5546af99f34 --- /dev/null +++ b/examples/handling-events/login-button-arrow-function.js @@ -0,0 +1,15 @@ +// highlight-range{7-9} +class LoggingButton extends React.Component { + handleClick() { + console.log('this is:', this); + } + + render() { + // This syntax ensures `this` is bound within handleClick + return ( + + ); + } +} diff --git a/examples/handling-events/login-button-public-class-field-example.js b/examples/handling-events/login-button-public-class-field-example.js new file mode 100644 index 00000000000..28d4dc557bc --- /dev/null +++ b/examples/handling-events/login-button-public-class-field-example.js @@ -0,0 +1,14 @@ +// highlight-range{2-6} +class LoggingButton extends React.Component { + // This syntax ensures `this` is bound within handleClick. + // Warning: this is *experimental* syntax. + handleClick = () => { + console.log('this is:', this); + }; + + render() { + return ( + + ); + } +} diff --git a/examples/handling-events/toggle-example.css b/examples/handling-events/toggle-example.css new file mode 100644 index 00000000000..ce71bb9c972 --- /dev/null +++ b/examples/handling-events/toggle-example.css @@ -0,0 +1,3 @@ +body { + padding: 5px +} \ No newline at end of file diff --git a/examples/handling-events/toggle-example.js b/examples/handling-events/toggle-example.js new file mode 100644 index 00000000000..ea29a20a1ff --- /dev/null +++ b/examples/handling-events/toggle-example.js @@ -0,0 +1,34 @@ +/* hide-range{1-4} */ +// highlight-range{10,11,14-18,22} +import React from 'react'; +import ReactDOM from 'react-dom'; +import './toggle-example.css'; + +class Toggle extends React.Component { + constructor(props) { + super(props); + this.state = {isToggleOn: true}; + + // This binding is necessary to make `this` work in the callback + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.setState(state => ({ + isToggleOn: !state.isToggleOn, + })); + } + + render() { + return ( + + ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/package.json b/package.json index b22d19551fe..8e2e887c468 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,10 @@ "gatsby-plugin-twitter": "^2.0.0", "gatsby-remark-code-repls": "^2.0.0", "gatsby-remark-copy-linked-files": "^2.0.0", - "gatsby-remark-embed-snippet": "^3.0.0", + "gatsby-remark-embed-snippet": "4.1.6", "gatsby-remark-external-links": "^0.0.4", "gatsby-remark-images": "^2.0.0", - "gatsby-remark-prismjs": "^3.0.2", + "gatsby-remark-prismjs": "3.3.12", "gatsby-remark-responsive-iframe": "^2.0.0", "gatsby-remark-smartypants": "^2.0.0", "gatsby-source-filesystem": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 480594945e9..1edd77026d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -732,6 +732,13 @@ dependencies: regenerator-runtime "^0.12.0" +"@babel/runtime@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" + integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@7.0.0-beta.44": version "7.0.0-beta.44" resolved "http://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" @@ -5489,15 +5496,15 @@ gatsby-remark-copy-linked-files@^2.0.0: probe-image-size "^4.0.0" unist-util-visit "^1.3.0" -gatsby-remark-embed-snippet@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/gatsby-remark-embed-snippet/-/gatsby-remark-embed-snippet-3.0.0.tgz#9ed032d51274bbabe7307e6e03879cd49941818f" - integrity sha512-vqxx15/J+AylOvpVAfKhYDr/TGGnJ7DNx3ZdukEaQrT/aGc+/8ztfir61+gsHIFa8qIpmpoTpXvUAEbcK0lzIg== +gatsby-remark-embed-snippet@4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/gatsby-remark-embed-snippet/-/gatsby-remark-embed-snippet-4.1.6.tgz#c7a6a9b6f6724303528303274ef8a9ce19e30bc1" + integrity sha512-37618jCRC7iDpPxEkhieDUWHDAMaNT+unyV7pqUro3+zN6nvUeDzwWkCFQzg1QcAWv/uSqlDkIBrfm5wxcR92w== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.5.5" normalize-path "^2.1.1" parse-numeric-range "^0.0.2" - unist-util-map "^1.0.3" + unist-util-map "^1.0.5" gatsby-remark-external-links@^0.0.4: version "0.0.4" @@ -5521,14 +5528,14 @@ gatsby-remark-images@^2.0.0: unist-util-select "^1.5.0" unist-util-visit-parents "^2.0.1" -gatsby-remark-prismjs@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/gatsby-remark-prismjs/-/gatsby-remark-prismjs-3.0.2.tgz#38a67c4e020ac26102e850ccefbe8b0e696501dc" - integrity sha512-tjgGzDVkDX3EtBgKI8uNxArNjLF97M3ipMzh1PLGtIn1naVrixw36xV7s6BkiZigdpybj9rjF7mvBKPtZekh2w== +gatsby-remark-prismjs@3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/gatsby-remark-prismjs/-/gatsby-remark-prismjs-3.3.12.tgz#4239bb48a5e2eb5fcff5adbbac1a6821012bd9bf" + integrity sha512-PE744uWg0M09hFZSOpQac+oUpmwKHAYJc0unLIszNdXvbihPL8rhi9aahKiz3EvQDbQ35WNHHPOsS/OjtegmIw== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.5.5" parse-numeric-range "^0.0.2" - unist-util-visit "^1.3.0" + unist-util-visit "^1.4.1" gatsby-remark-responsive-iframe@^2.0.0: version "2.0.5" @@ -10491,6 +10498,11 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== +regenerator-runtime@^0.13.2: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" + integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== + regenerator-transform@^0.13.3: version "0.13.3" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" @@ -12487,6 +12499,13 @@ unist-util-map@^1.0.3: dependencies: object-assign "^4.0.1" +unist-util-map@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/unist-util-map/-/unist-util-map-1.0.5.tgz#701069b72e1d1cc02db265502a5e82b77c2eb8b7" + integrity sha512-dFil/AN6vqhnQWNCZk0GF/G3+Q5YwsB+PqjnzvpO2wzdRtUJ1E8PN+XRE/PRr/G3FzKjRTJU0haqE0Ekl+O3Ag== + dependencies: + object-assign "^4.0.1" + unist-util-modify-children@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-1.1.2.tgz#c7f1b91712554ee59c47a05b551ed3e052a4e2d1" @@ -12539,6 +12558,13 @@ unist-util-visit@^1.0.0, unist-util-visit@^1.1.0, unist-util-visit@^1.1.3, unist dependencies: unist-util-visit-parents "^2.0.0" +unist-util-visit@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" + integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw== + dependencies: + unist-util-visit-parents "^2.0.0" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"