diff --git a/content/blog/2019-02-06-react-v16.8.0.md b/content/blog/2019-02-06-react-v16.8.0.md new file mode 100644 index 000000000..9fe33e8f4 --- /dev/null +++ b/content/blog/2019-02-06-react-v16.8.0.md @@ -0,0 +1,207 @@ +--- +title: "React v16.8: The One With Hooks" +author: [gaearon] +--- + +With React 16.8, [React Hooks](/docs/hooks-intro.html) are available in a stable release! + +## What Are Hooks? + +Hooks let you use state and other React features without writing a class. You can also **build your own Hooks** to share reusable stateful logic between components. + +If you've never heard of Hooks before, you might find these resources interesting: + +* [Introducing Hooks](/docs/hooks-intro.html) explains why we're adding Hooks to React. +* [Hooks at a Glance](/docs/hooks-overview.html) is a fast-paced overview of the built-in Hooks. +* [Building Your Own Hooks](/docs/hooks-custom.html) demonstrates code reuse with custom Hooks. +* [Making Sense of React Hooks](https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889) explores the new possibilities unlocked by Hooks. +* [useHooks.com](https://usehooks.com/) showcases community-maintained Hooks recipes and demos. + +**You don't have to learn Hooks right now.** Hooks have no breaking changes, and we have no plans to remove classes from React. The [Hooks FAQ](/docs/hooks-faq.html) describes the gradual adoption strategy. + +## No Big Rewrites + +We don't recommend rewriting your existing applications to use Hooks overnight. Instead, try using Hooks in some of the new components, and let us know what you think. Code using Hooks will work [side by side](/docs/hooks-intro.html#gradual-adoption-strategy) with existing code using classes. + +## Can I Use Hooks Today? + +Yes! Starting with 16.8.0, React includes a stable implementation of React Hooks for: + +* React DOM +* React DOM Server +* React Test Renderer +* React Shallow Renderer + +Note that **to enable Hooks, all React packages need to be 16.8.0 or higher**. Hooks won't work if you forget to update, for example, React DOM. + +**React Native will support Hooks in the [0.59 release](https://github.com/react-native-community/react-native-releases/issues/79#issuecomment-457735214).** + +## Tooling Support + +React Hooks are now supported by React DevTools. They are also supported in the latest Flow and TypeScript definitions for React. We strongly recommend enabling a new [lint rule called `eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks) to enforce best practices with Hooks. It will soon be included into Create React App by default. + +## What's Next + +We described our plan for the next months in the recently published [React Roadmap](/blog/2018/11/27/react-16-roadmap.html). + +Note that React Hooks don't cover *all* use cases for classes yet but they're [very close](/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes). Currently, only `getSnapshotBeforeUpdate()` and `componentDidCatch()` methods don't have equivalent Hooks APIs, and these lifecycles are relatively uncommon. If you want, you should be able to use Hooks in most of the new code you're writing. + +Even while Hooks were in alpha, the React community created many interesting [examples](https://codesandbox.io/react-hooks) and [recipes](https://usehooks.com) using Hooks for animations, forms, subscriptions, integrating with other libraries, and so on. We're excited about Hooks because they make code reuse easier, helping you write your components in a simpler way and make great user experiences. We can't wait to see what you'll create next! + +## Testing Hooks + +We have added a new API called `ReactTestUtils.act()` in this release. It ensures that the behavior in your tests matches what happens in the browser more closely. We recommend to wrap any code rendering and triggering updates to your components into `act()` calls. Testing libraries can also wrap their APIs with it (for example, [`react-testing-library`](https://github.com/kentcdodds/react-testing-library)'s `render` and `fireEvent` utilities do this). + +For example, the counter example from [this page](/docs/hooks-effect.html) can be tested like this: + +```js{3,20-22,29-31} +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import Counter from './Counter'; + +let container; + +beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container); + container = null; +}); + +it('can render and update a counter', () => { + // Test first render and effect + act(() => { + ReactDOM.render(, container); + }); + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 0 times'); + expect(document.title).toBe('You clicked 0 times'); + + // Test second render and effect + act(() => { + button.dispatchEvent(new MouseEvent('click', {bubbles: true})); + }); + expect(label.textContent).toBe('You clicked 1 times'); + expect(document.title).toBe('You clicked 1 times'); +}); +``` + +The calls to `act()` will also flush the effects inside of them. + +If you need to test a custom Hook, you can do so by creating a component in your test, and using your Hook from it. Then you can test the component you wrote. + +To reduce the boilerplate, we recommend using [`react-testing-library`](https://git.io/react-testing-library) which is designed to encourage writing tests that use your components as the end users do. + +## Thanks + +We'd like to thank everybody who commented on the [Hooks RFC](https://github.com/reactjs/rfcs/pull/68) for sharing their feedback. We've read all of your comments and made some adjustments to the final API based on them. + +## Installation + +### React + +React v16.8.0 is available on the npm registry. + +To install React 16 with Yarn, run: + +```bash +yarn add react@^16.8.0 react-dom@^16.8.0 +``` + +To install React 16 with npm, run: + +```bash +npm install --save react@^16.8.0 react-dom@^16.8.0 +``` + +We also provide UMD builds of React via a CDN: + +```html + + +``` + +Refer to the documentation for [detailed installation instructions](/docs/installation.html). + +### ESLint Plugin for React Hooks + +>Note +> +>As mentioned above, we strongly recommend using the `eslint-plugin-react-hooks` lint rule. +> +>If you're using Create React App, instead of manually configuring ESLint you can wait for the next version of `react-scripts` which will come out shortly and will include this rule. + +Assuming you already have ESLint installed, run: + +```sh +# npm +npm install eslint-plugin-react-hooks@next --save-dev + +# yarn +yarn add eslint-plugin-react-hooks@next --dev +``` + +Then add it to your ESLint configuration: + +```js +{ + "plugins": [ + // ... + "react-hooks" + ], + "rules": { + // ... + "react-hooks/rules-of-hooks": "error" + } +} +``` + +## Changelog + +### React + +* Add [Hooks](https://reactjs.org/docs/hooks-intro.html) — a way to use state and other React features without writing a class. ([@acdlite](https://github.com/acdlite) et al. in [#13968](https://github.com/facebook/react/pull/13968)) +* Improve the `useReducer` Hook lazy initialization API. ([@acdlite](https://github.com/acdlite) in [#14723](https://github.com/facebook/react/pull/14723)) + +### React DOM + +* Bail out of rendering on identical values for `useState` and `useReducer` Hooks. ([@acdlite](https://github.com/acdlite) in [#14569](https://github.com/facebook/react/pull/14569)) +* Don’t compare the first argument passed to `useEffect`/`useMemo`/`useCallback` Hooks. ([@acdlite](https://github.com/acdlite) in [#14594](https://github.com/facebook/react/pull/14594)) +* Use `Object.is` algorithm for comparing `useState` and `useReducer` values. ([@Jessidhia](https://github.com/Jessidhia) in [#14752](https://github.com/facebook/react/pull/14752)) +* Support synchronous thenables passed to `React.lazy()`. ([@gaearon](https://github.com/gaearon) in [#14626](https://github.com/facebook/react/pull/14626)) +* Render components with Hooks twice in Strict Mode (DEV-only) to match class behavior. ([@gaearon](https://github.com/gaearon) in [#14654](https://github.com/facebook/react/pull/14654)) +* Warn about mismatching Hook order in development. ([@threepointone](https://github.com/threepointone) in [#14585](https://github.com/facebook/react/pull/14585) and [@acdlite](https://github.com/acdlite) in [#14591](https://github.com/facebook/react/pull/14591)) +* Effect clean-up functions must return either `undefined` or a function. All other values, including `null`, are not allowed. [@acdlite](https://github.com/acdlite) in [#14119](https://github.com/facebook/react/pull/14119) + +### React Test Renderer + +* Support Hooks in the shallow renderer. ([@trueadm](https://github.com/trueadm) in [#14567](https://github.com/facebook/react/pull/14567)) +* Fix wrong state in `shouldComponentUpdate` in the presence of `getDerivedStateFromProps` for Shallow Renderer. ([@chenesan](https://github.com/chenesan) in [#14613](https://github.com/facebook/react/pull/14613)) +* Add `ReactTestRenderer.act()` and `ReactTestUtils.act()` for batching updates so that tests more closely match real behavior. ([@threepointone](https://github.com/threepointone) in [#14744](https://github.com/facebook/react/pull/14744)) + +### ESLint Plugin: React Hooks + +* Initial [release](https://www.npmjs.com/package/eslint-plugin-react-hooks). ([@calebmer](https://github.com/calebmer) in [#13968](https://github.com/facebook/react/pull/13968)) +* Fix reporting after encountering a loop. ([@calebmer](https://github.com/calebmer) and [@Yurickh](https://github.com/Yurickh) in [#14661](https://github.com/facebook/react/pull/14661)) +* Don't consider throwing to be a rule violation. ([@sophiebits](https://github.com/sophiebits) in [#14040](https://github.com/facebook/react/pull/14040)) + +## Hooks Changelog Since Alpha Versions + +The above changelog contains all notable changes since our last **stable** release (16.7.0). [As with all our minor releases](/docs/faq-versioning.html), none of the changes break backwards compatibility. + +If you're currently using Hooks from an alpha build of React, note that this release does contain some small breaking changes to Hooks. **We don't recommend depending on alphas in production code.** We publish them so we can make changes in response to community feedback before the API is stable. + +Here are all breaking changes to Hooks that have been made since the first alpha release: + +* Remove `useMutationEffect`. ([@sophiebits](https://github.com/sophiebits) in [#14336](https://github.com/facebook/react/pull/14336)) +* Rename `useImperativeMethods` to `useImperativeHandle`. ([@threepointone](https://github.com/threepointone) in [#14565](https://github.com/facebook/react/pull/14565)) +* Bail out of rendering on identical values for `useState` and `useReducer` Hooks. ([@acdlite](https://github.com/acdlite) in [#14569](https://github.com/facebook/react/pull/14569)) +* Don’t compare the first argument passed to `useEffect`/`useMemo`/`useCallback` Hooks. ([@acdlite](https://github.com/acdlite) in [#14594](https://github.com/facebook/react/pull/14594)) +* Use `Object.is` algorithm for comparing `useState` and `useReducer` values. ([@Jessidhia](https://github.com/Jessidhia) in [#14752](https://github.com/facebook/react/pull/14752)) +* Render components with Hooks twice in Strict Mode (DEV-only). ([@gaearon](https://github.com/gaearon) in [#14654](https://github.com/facebook/react/pull/14654)) +* Improve the `useReducer` Hook lazy initialization API. ([@acdlite](https://github.com/acdlite) in [#14723](https://github.com/facebook/react/pull/14723)) diff --git a/content/docs/addons-test-utils.md b/content/docs/addons-test-utils.md index e994ec9a1..244bf7e46 100644 --- a/content/docs/addons-test-utils.md +++ b/content/docs/addons-test-utils.md @@ -19,12 +19,11 @@ var ReactTestUtils = require('react-dom/test-utils'); // ES5 with npm > Nota: > -> Airbnb ha liberado una utilidad para pruebas llamada Enzyme, que hace fácil asegurar, manipular y navegar por el resultado de sus Componentes de React. Si está decidiendo que utilidad para pruebas unitarias utilizar junto con Jest u otra herramienta para pruebas, vale la pena darle un vistazo a: [http://airbnb.io/enzyme/](http://airbnb.io/enzyme/) +> We recommend using [`react-testing-library`](https://git.io/react-testing-library) which is designed to enable and encourage writing tests that use your components as the end users do. > -> Como otra opción, también hay otra utilidad para pruebas llamada react-testing-library diseñada para permitir e incentivar el escribir las pruebas de sus componentes de la misma forma en que los usuarios finales los usarían. De igual forma, funciona con cualquiera de los ejecutores de pruebas: [https://git.io/react-testing-library](https://git.io/react-testing-library) +> Alternatively, Airbnb has released a testing utility called [Enzyme](http://airbnb.io/enzyme/), which makes it easy to assert, manipulate, and traverse your React Components' output. - - [`Simulate`](#simulate) - - [`renderIntoDocument()`](#renderintodocument) + - [`act()`](#act) - [`mockComponent()`](#mockcomponent) - [`isElement()`](#iselement) - [`isElementOfType()`](#iselementoftype) @@ -38,68 +37,92 @@ var ReactTestUtils = require('react-dom/test-utils'); // ES5 with npm - [`findRenderedDOMComponentWithTag()`](#findrendereddomcomponentwithtag) - [`scryRenderedComponentsWithType()`](#scryrenderedcomponentswithtype) - [`findRenderedComponentWithType()`](#findrenderedcomponentwithtype) + - [`renderIntoDocument()`](#renderintodocument) + - [`Simulate`](#simulate) ## Referencia -## Renderizado superficial +### `act()` -Cuando se escriben pruebas de unidad para React, el renderizado superficial puede ser de ayuda. El renderizado superficial permite renderizar el componente "un nivel de profundidad" y asegurar lo que su método de renderizado retorna, sin preocuparse acerca del comportamiento de los componentes hijos, los cuales no son instanciados o renderizados. Esto no requiere de un DOM. +To prepare a component for assertions, wrap the code rendering it and performing updates inside an `act()` call. This makes your test run closer to how React works in the browser. -> Nota: +>Note > -> El renderizado superficial se ha movido a `react-test-renderer/shallow`.
-> [Puede encontrar más información sobre el renderizado superficial en su página de referencia](/docs/shallow-renderer.html) - -## Otras utilidades - -### `Simulate` - -```javascript -Simulate.{eventName}( - element, - [eventData] -) -``` - -Simula la ejecución de un evento en un nodo del DOM con los datos opcionales de evento `eventData`. - -`Simulate` tiene un método para [cada uno de los eventos que React comprende](/docs/events.html#supported-events). - -**Haciendo clic en un elemento** - -```javascript -// -const node = this.button; -ReactTestUtils.Simulate.click(node); +>If you use `react-test-renderer`, it also provides an `act` export that behaves the same way. + +For example, let's say we have this `Counter` component: + +```js +class App extends React.Component { + constructor(props) { + super(props); + this.state = {count: 0}; + this.handleClick = this.handleClick.bind(this); + } + componentDidMount() { + document.title = `You clicked ${this.state.count} times`; + } + componentDidUpdate() { + document.title = `You clicked ${this.state.count} times`; + } + handleClick() { + this.setState(state => ({ + count: state.count + 1, + })); + } + render() { + return ( +
+

You clicked {this.state.count} times

+ +
+ ); + } +} ``` -**Cambiar el valor en un campo de entrada y presionar ENTER.** - -```javascript -// this.textInput = node} /> -const node = this.textInput; -node.value = 'giraffe'; -ReactTestUtils.Simulate.change(node); -ReactTestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13}); -``` - -> Nota -> -> Se debe proveer cualquiera de las propiedades del evento que se esté usando en tu componente (p.e. keyCode, which, etc...) ya que React no creará ninguna de estas por ti. - -* * * - -### `renderIntoDocument()` - -```javascript -renderIntoDocument(element) +Here is how we can test it: + +```js{3,20-22,29-31} +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import Counter from './Counter'; + +let container; + +beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container); + container = null; +}); + +it('can render and update a counter', () => { + // Test first render and componentDidMount + act(() => { + ReactDOM.render(, container); + }); + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 0 times'); + expect(document.title).toBe('You clicked 0 times'); + + // Test second render and componentDidUpdate + act(() => { + button.dispatchEvent(new MouseEvent('click', {bubbles: true})); + }); + expect(label.textContent).toBe('You clicked 1 times'); + expect(document.title).toBe('You clicked 1 times'); +}); ``` -Renderiza un Elemento de React en un nodo separado del DOM en el documento. **Esta función requiere un DOM** - -> Nota: -> -> Necesitará tener `window`, `window.document` y `window.document.createElement` habilitados de forma global **antes** de importar `React`. De otro modo React pensará que no tiene acceso al DOM y los métodos como `setState` no funcionarán. +Don't forget that dispatching DOM events only works when the DOM container is added to the `document`. You can use a helper like [`react-testing-library`](https://github.com/kentcdodds/react-testing-library) to reduce the boilerplate code. * * * @@ -265,4 +288,62 @@ findRenderedComponentWithType( Igual a [`scryRenderedComponentsWithType()`](#scryrenderedcomponentswithtype) pero espera que sólo haya un resultado y retorna ese único resultado, de lo contrario lanza una excepción si hay algún otro número de coincidencias diferentes a una. +*** + +### `renderIntoDocument()` + +```javascript +renderIntoDocument(element) +``` + +Render a React element into a detached DOM node in the document. **This function requires a DOM.** It is effectively equivalent to: + +```js +const domContainer = document.createElement('div'); +ReactDOM.render(element, domContainer); +``` + +> Note: +> +> You will need to have `window`, `window.document` and `window.document.createElement` globally available **before** you import `React`. Otherwise React will think it can't access the DOM and methods like `setState` won't work. + +* * * + +## Other Utilities + +### `Simulate` + +```javascript +Simulate.{eventName}( + element, + [eventData] +) +``` + +Simulate an event dispatch on a DOM node with optional `eventData` event data. + +`Simulate` has a method for [every event that React understands](/docs/events.html#supported-events). + +**Clicking an element** + +```javascript +// +const node = this.button; +ReactTestUtils.Simulate.click(node); +``` + +**Changing the value of an input field and then pressing ENTER.** + +```javascript +// this.textInput = node} /> +const node = this.textInput; +node.value = 'giraffe'; +ReactTestUtils.Simulate.change(node); +ReactTestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13}); +``` + +> Note +> +> You will have to provide any event property that you're using in your component (e.g. keyCode, which, etc...) as React is not creating any of these for you. + * * * diff --git a/content/docs/hooks-custom.md b/content/docs/hooks-custom.md index 8a0410302..d4320f022 100644 --- a/content/docs/hooks-custom.md +++ b/content/docs/hooks-custom.md @@ -5,14 +5,15 @@ permalink: docs/hooks-custom.html next: hooks-reference.html prev: hooks-rules.html --- -*Hooks* are an upcoming feature that lets you use state and other React features without writing a class. They're currently in React v16.8.0-alpha.1. + +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. Construir tus propios Hooks te permite extraer la lógica del componente en funciones reutilizables. Cuando estábamos aprendiendo a [usar el Hook de Efecto](/docs/hooks-effect.html#example-using-hooks-1), vimos este componente de una aplicación de chat que muestra un mensaje indicando si un amigo está conectado o desconectado: ```js{4-15} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); @@ -38,7 +39,7 @@ function FriendStatus(props) { Supongamos ahora que nuestra aplicación de chat tiene también una lista de contactos y queremos que renderice nombres de usuarios con color verde. Podríamos copiar y pegar la lógica adaptada a nuestro componente `FriendListItem`, pero eso no sería ideal: ```js{4-15} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function FriendListItem(props) { const [isOnline, setIsOnline] = useState(null); @@ -73,7 +74,7 @@ Cuando queremos compartir lógica entre dos funciones de Javascript, lo extraemo **Un Hook personalizado es una función de JavaScript cuyo nombre comienza con "`use`" y que puede llamar a otros Hooks.** Por ejemplo, a continuación `useFriendStatus` es nuestro primer Hook personalizado: ```js{3} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); diff --git a/content/docs/hooks-effect.md b/content/docs/hooks-effect.md index b01045386..b40167ec0 100644 --- a/content/docs/hooks-effect.md +++ b/content/docs/hooks-effect.md @@ -6,12 +6,12 @@ next: hooks-rules.html prev: hooks-intro.html --- -*Hooks* are an upcoming feature that lets you use state and other React features without writing a class. They're currently in React v16.8.0-alpha.1. +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. The *Effect Hook* lets you perform side effects in function components: ```js{1,6-10} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); @@ -94,7 +94,7 @@ Now let's see how we can do the same with the `useEffect` Hook. We've already seen this example at the top of this page, but let's take a closer look at it: ```js{1,6-8} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); @@ -199,7 +199,7 @@ Let's see how we could write this component with Hooks. You might be thinking that we'd need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that `useEffect` is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up: ```js{10-16} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index 74d9227e4..30c5e681c 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -5,7 +5,7 @@ permalink: docs/hooks-faq.html prev: hooks-reference.html --- -*Hooks* are an upcoming feature that lets you use state and other React features without writing a class. They're currently in React v16.8.0-alpha.1. +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. This page answers some of the frequently asked questions about [Hooks](/docs/hooks-overview.html). @@ -19,7 +19,9 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo --> * **[Adoption Strategy](#adoption-strategy)** + * [Which versions of React include Hooks?](#which-versions-of-react-include-hooks) * [Do I need to rewrite all my class components?](#do-i-need-to-rewrite-all-my-class-components) + * [What can I do with Hooks that I couldn't with classes?](#what-can-i-do-with-hooks-that-i-couldnt-with-classes) * [How much of my React knowledge stays relevant?](#how-much-of-my-react-knowledge-stays-relevant) * [Should I use Hooks, classes, or a mix of both?](#should-i-use-hooks-classes-or-a-mix-of-both) * [Do Hooks cover all use cases for classes?](#do-hooks-cover-all-use-cases-for-classes) @@ -35,6 +37,7 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo * [Can I run an effect only on updates?](#can-i-run-an-effect-only-on-updates) * [How to get the previous props or state?](#how-to-get-the-previous-props-or-state) * [How do I implement getDerivedStateFromProps?](#how-do-i-implement-getderivedstatefromprops) + * [Is there something like forceUpdate?](#is-there-something-like-forceupdate) * [Can I make a ref to a function component?](#can-i-make-a-ref-to-a-function-component) * [What does const [thing, setThing] = useState() mean?](#what-does-const-thing-setthing--usestate-mean) * **[Performance Optimizations](#performance-optimizations)** @@ -51,10 +54,27 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo ## Adoption Strategy +### Which versions of React include Hooks? + +Starting with 16.8.0, React includes a stable implementation of React Hooks for: + +* React DOM +* React DOM Server +* React Test Renderer +* React Shallow Renderer + +Note that **to enable Hooks, all React packages need to be 16.8.0 or higher**. Hooks won't work if you forget to update, for example, React DOM. + +React Native will fully support Hooks in its next stable release. + ### Do I need to rewrite all my class components? No. There are [no plans](/docs/hooks-intro.html#gradual-adoption-strategy) to remove classes from React -- we all need to keep shipping products and can't afford rewrites. We recommend trying Hooks in new code. +### What can I do with Hooks that I couldn't with classes? + +Hooks offer a powerful and expressive new way to reuse functionality between components. ["Building Your Own Hooks"](/docs/hooks-custom.html) provides a glimpse of what's possible. [This article](https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889) by a React core team member dives deeper into the new capabilities unlocked by Hooks. + ### How much of my React knowledge stays relevant? Hooks are a more direct way to use the React features you already know -- such as state, lifecycle, context, and refs. They don't fundamentally change how React works, and your knowledge of components, props, and top-down data flow is just as relevant. @@ -71,7 +91,7 @@ You can't use Hooks *inside* of a class component, but you can definitely mix cl Our goal is for Hooks to cover all use cases for classes as soon as possible. There are no Hook equivalents to the uncommon `getSnapshotBeforeUpdate` and `componentDidCatch` lifecycles yet, but we plan to add them soon. -It is a very early time for Hooks, so some integrations like DevTools support or Flow/TypeScript typings may not be ready yet. Some third-party libraries might also not be compatible with Hooks at the moment. +It is an early time for Hooks, and some third-party libraries might not be compatible with Hooks at the moment. ### Do Hooks replace render props and higher-order components? @@ -85,7 +105,7 @@ In the future, new versions of these libraries might also export custom Hooks su ### Do Hooks work with static typing? -Hooks were designed with static typing in mind. Because they're functions, they are easier to type correctly than patterns like higher-order components. We have reached out both to Flow and TypeScript teams in advance, and they plan to include definitions for React Hooks in the future. +Hooks were designed with static typing in mind. Because they're functions, they are easier to type correctly than patterns like higher-order components. The latest Flow and TypeScript React definitions include support for React Hooks. Importantly, custom Hooks give you the power to constrain React API if you'd like to type them more strictly in some way. React gives you the primitives, but you can combine them in different ways than what we provide out of the box. @@ -93,8 +113,70 @@ Importantly, custom Hooks give you the power to constrain React API if you'd lik From React's point of view, a component using Hooks is just a regular component. If your testing solution doesn't rely on React internals, testing components with Hooks shouldn't be different from how you normally test components. +For example, let's say we have this counter component: + +```js +function Example() { + const [count, setCount] = useState(0); + useEffect(() => { + document.title = `You clicked ${count} times`; + }); + return ( +
+

You clicked {count} times

+ +
+ ); +} +``` + +We'll test it using React DOM. To make sure that the behavior matches what happens in the browser, we'll wrap the code rendering and updating it into [`ReactTestUtils.act()`](/docs/test-utils.html#act) calls: + +```js{3,20-22,29-31} +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import Counter from './Counter'; + +let container; + +beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container); + container = null; +}); + +it('can render and update a counter', () => { + // Test first render and effect + act(() => { + ReactDOM.render(, container); + }); + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 0 times'); + expect(document.title).toBe('You clicked 0 times'); + + // Test second render and effect + act(() => { + button.dispatchEvent(new MouseEvent('click', {bubbles: true})); + }); + expect(label.textContent).toBe('You clicked 1 times'); + expect(document.title).toBe('You clicked 1 times'); +}); +``` + +The calls to `act()` will also flush the effects inside of them. + If you need to test a custom Hook, you can do so by creating a component in your test, and using your Hook from it. Then you can test the component you wrote. +To reduce the boilerplate, we recommend using [`react-testing-library`](https://git.io/react-testing-library) which is designed to encourage writing tests that use your components as the end users do. + ### What exactly do the [lint rules](https://www.npmjs.com/package/eslint-plugin-react-hooks) enforce? We provide an [ESLint plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks) that enforces [rules of Hooks](/docs/hooks-rules.html) to avoid bugs. It assumes that any function starting with "`use`" and a capital letter right after it is a Hook. We recognize this heuristic isn't perfect and there may be some false positives, but without an ecosystem-wide convention there is just no way to make Hooks work well -- and longer names will discourage people from either adopting Hooks or following the convention. @@ -303,6 +385,22 @@ function ScrollView({row}) { This might look strange at first, but an update during rendering is exactly what `getDerivedStateFromProps` has always been like conceptually. +### Is there something like forceUpdate? + +Both `useState` and `useReducer` Hooks [bail out of updates](/docs/hooks-reference.html#bailing-out-of-a-state-update) if the next value is the same as the previous one. Mutating state in place and calling `setState` will not cause a re-render. + +Normally, you shouldn't mutate local state in React. However, as an escape hatch, you can use an incrementing counter to force a re-render even if the state has not changed: + +```js + const [ignored, forceUpdate] = useReducer(x => x + 1, 0); + + function handleClick() { + forceUpdate(); + } +``` + +Try to avoid this pattern if possible. + ### Can I make a ref to a function component? While you shouldn't need this often, you may expose some imperative methods to a parent component with the [`useImperativeHandle`](/docs/hooks-reference.html#useimperativehandle) Hook. diff --git a/content/docs/hooks-intro.md b/content/docs/hooks-intro.md index b6e595372..d0a4ef6ac 100644 --- a/content/docs/hooks-intro.md +++ b/content/docs/hooks-intro.md @@ -5,10 +5,10 @@ permalink: docs/hooks-intro.html next: hooks-overview.html --- -*Hooks* are an upcoming feature that lets you use state and other React features without writing a class. They're currently in React v16.8.0-alpha.1. +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. ```js{4,5} -import { useState } from 'react'; +import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" @@ -29,6 +29,10 @@ This new function `useState` is the first "Hook" we'll learn about, but this exa **You can start learning Hooks [on the next page](/docs/hooks-overview.html).** On this page, we'll continue by explaining why we're adding Hooks to React and how they can help you write great applications. +>Note +> +>React 16.8.0 is the first release to support Hooks. When upgrading, don't forget to update all packages, including React DOM. React Native will support Hooks in the next stable release. + ## Video Introduction At React Conf 2018, Sophie Alpert and Dan Abramov introduced Hooks, followed by Ryan Florence demonstrating how to refactor an application to use them. Watch the video here: @@ -43,7 +47,7 @@ Before we continue, note that Hooks are: * **Completely opt-in.** You can try Hooks in a few components without rewriting any existing code. But you don't have to learn or use Hooks right now if you don't want to. * **100% backwards-compatible.** Hooks don't contain any breaking changes. -* **Available now.** Hooks are currently in an alpha release, and we hope to include them in React 16.x after receiving community feedback. +* **Available now.** Hooks are now available with the release of v16.8.0. **There are no plans to remove classes from React.** You can read more about the gradual adoption strategy for Hooks in the [bottom section](#gradual-adoption-strategy) of this page. @@ -99,6 +103,10 @@ Finally, there is no rush to migrate to Hooks. We recommend avoiding any "big re We intend for Hooks to cover all existing use cases for classes, but **we will keep supporting class components for the foreseeable future.** At Facebook, we have tens of thousands of components written as classes, and we have absolutely no plans to rewrite them. Instead, we are starting to use Hooks in the new code side by side with classes. +## Frequently Asked Questions + +We've prepared a [Hooks FAQ page](/docs/hooks-faq.html) that answers the most common questions about Hooks. + ## Next Steps By the end of this page, you should have a rough idea of what problems Hooks are solving, but many details are probably unclear. Don't worry! **Let's now go to [the next page](/docs/hooks-overview.html) where we start learning about Hooks by example.** diff --git a/content/docs/hooks-overview.md b/content/docs/hooks-overview.md index 62ce2ab9d..013f792ca 100644 --- a/content/docs/hooks-overview.md +++ b/content/docs/hooks-overview.md @@ -6,7 +6,7 @@ next: hooks-state.html prev: hooks-intro.html --- -*Hooks* are an upcoming feature that lets you use state and other React features without writing a class. They're currently in React v16.8.0-alpha.1. +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. Hooks are [backwards-compatible](/docs/hooks-intro.html#no-breaking-changes). This page provides an overview of Hooks for experienced React users. This is a fast-paced overview. If you get confused, look for a yellow box like this: @@ -21,7 +21,7 @@ Hooks are [backwards-compatible](/docs/hooks-intro.html#no-breaking-changes). Th This example renders a counter. When you click the button, it increments the value: ```js{1,4,5} -import { useState } from 'react'; +import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" @@ -77,7 +77,7 @@ The Effect Hook, `useEffect`, adds the ability to perform side effects from a fu For example, this component sets the document title after React updates the DOM: ```js{1,6-10} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); @@ -104,7 +104,7 @@ When you call `useEffect`, you're telling React to run your "effect" function af Effects may also optionally specify how to "clean up" after them by returning a function. For example, this component uses an effect to subscribe to a friend's online status, and cleans up by unsubscribing from it: ```js{10-16} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); @@ -181,7 +181,7 @@ Earlier on this page, we introduced a `FriendStatus` component that calls the `u First, we'll extract this logic into a custom Hook called `useFriendStatus`: ```js{3} -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md index a6da9c754..a2ca4107e 100644 --- a/content/docs/hooks-reference.md +++ b/content/docs/hooks-reference.md @@ -6,7 +6,7 @@ prev: hooks-custom.html next: hooks-faq.html --- -Los *Hooks* son una próxima característica que te permite usar el estado y otras características de React sin escribir una clase. Están actualmente en React v16.8.0-alpha.1. +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. Esta página describe las API para los Hooks incorporados en React. @@ -90,6 +90,10 @@ const [state, setState] = useState(() => { }); ``` +#### Bailing out of a state update + +If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the [`Object.is` comparison algorithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description).) + ### `useEffect` ```js @@ -173,39 +177,34 @@ Los siguientes Hooks son variantes de los básicos de la sección anterior o sol ### `useReducer` ```js -const [state, dispatch] = useReducer(reducer, initialState); +const [state, dispatch] = useReducer(reducer, initialArg, init); ``` Una alternativa a [`useState`](#usestate). Acepta un reducer de tipo `(state, action) => newState` y devuelve el estado actual emparejado con un método` dispatch`. (Si está familiarizado con Redux, ya sabe cómo funciona). -Aquí está el ejemplo de contador de la sección [`useState`] (# usestate), reescrito para usar un reducer: +`useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. `useReducer` also lets you optimize performance for components that trigger deep updates because [you can pass `dispatch` down instead of callbacks](/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down). + +Here's the counter example from the [`useState`](#usestate) section, rewritten to use a reducer: ```js const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { - case 'reset': - return initialState; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: - // Un reducer siempre debe devolver un estado válido. - // Alternativamente, puede lanzar un error si se envía una acción no válida. - return state; + throw new Error(); } } function Counter({initialCount}) { - const [state, dispatch] = useReducer(reducer, {count: initialCount}); + const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} - @@ -213,21 +212,40 @@ function Counter({initialCount}) { } ``` -#### Inicialización diferida +#### Specifying the initial state -`useReducer` acepta un tercer argumento opcional, `initialAction`. Si se proporciona, la acción inicial se aplica durante el render inicial. Esto es útil para calcular un estado inicial que incluye valores pasados a través de props: +There’s two different ways to initialize `useReducer` state. You may choose either one depending on the use case. The simplest way to pass the initial state as a second argument: -```js -const initialState = {count: 0}; +```js{3} + const [state, dispatch] = useReducer( + reducer, + {count: initialCount} + ); +``` + +>Note +> +>React doesn’t use the `state = initialState` argument convention popularized by Redux. The initial value sometimes needs to depend on props and so is specified from the Hook call instead. If you feel strongly about this, you can call `useReducer(reducer, undefined, reducer)` to emulate the Redux behavior, but it's not encouraged. + +#### Lazy initialization + +You can also create the initial state lazily. To do this, you can pass an `init` function as the third argument. The initial state will be set to `init(initialArg)`. + +It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action: + +```js{1-3,11-12,21,26} +function init(initialCount) { + return {count: initialCount}; +} function reducer(state, action) { switch (action.type) { - case 'reset': - return {count: action.payload}; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; + case 'reset': + return init(action.payload); default: // Un reducer siempre debe devolver un estado válido. // Alternativamente, puede lanzar un error si se envía una acción no válida. @@ -236,12 +254,7 @@ function reducer(state, action) { } function Counter({initialCount}) { - const [state, dispatch] = useReducer( - reducer, - initialState, - {type: 'reset', payload: initialCount}, - ); - + const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} @@ -256,7 +269,9 @@ function Counter({initialCount}) { } ``` -`useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because [you can pass `dispatch` down instead of callbacks](/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down). +#### Bailing out of a dispatch + +If you return the same value from a Reducer Hook as the current state, React will bail out without rendering the children or firing effects. (React uses the [`Object.is` comparison algorithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description).) `useReducer` suele ser preferible a `useState` cuando tiene una lógica de estado compleja que involucra múltiples subvalores. También le permite optimizar el rendimiento de los componentes que activan actualizaciones profundas porque [puede pasar `dispatch` en lugar de callbacks](/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down). @@ -370,7 +385,7 @@ useDebugValue(value) `useDebugValue` puede usarse para mostrar una etiqueta para Hooks personalizados en React DevTools. -Por ejemplo, considere el Hook personalizado `useFriendStatus` descrito en ["Construyendo sus propios Hooks"](/docs/hooks-custom.html): +Por ejemplo, considera el Hook personalizado `useFriendStatus` descrito en ["Construyendo sus propios Hooks"](/docs/hooks-custom.html): ```js{6-8} function useFriendStatus(friendID) { diff --git a/content/docs/hooks-rules.md b/content/docs/hooks-rules.md index af935c799..80a447992 100644 --- a/content/docs/hooks-rules.md +++ b/content/docs/hooks-rules.md @@ -6,7 +6,7 @@ next: hooks-custom.html prev: hooks-effect.html --- -*Hooks* are an upcoming feature that lets you use state and other React features without writing a class. They're currently in React v16.8.0-alpha.1. +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. Hooks are JavaScript functions, but you need to follow two rules when using them. We provide a [linter plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks) to enforce these rules automatically: @@ -28,7 +28,7 @@ By following this rule, you ensure that all stateful logic in a component is cle We released an ESLint plugin called [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks) that enforces these two rules. You can add this plugin to your project if you'd like to try it: ```bash -npm install eslint-plugin-react-hooks@next +npm install eslint-plugin-react-hooks ``` ```js diff --git a/content/docs/hooks-state.md b/content/docs/hooks-state.md index c910268ac..2a234af82 100644 --- a/content/docs/hooks-state.md +++ b/content/docs/hooks-state.md @@ -6,12 +6,12 @@ next: hooks-effect.html prev: hooks-overview.html --- -*Hooks* are an upcoming feature that lets you use state and other React features without writing a class. They're currently in React v16.8.0-alpha.1. +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. The [previous page](/docs/hooks-intro.html) introduced Hooks with this example: ```js{4-5} -import { useState } from 'react'; +import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" @@ -91,7 +91,7 @@ Hooks **don't** work inside classes. But you can use them instead of writing cla Our new example starts by importing the `useState` Hook from React: ```js{1} -import { useState } from 'react'; +import React, { useState } from 'react'; function Example() { // ... @@ -123,7 +123,7 @@ class Example extends React.Component { In a function component, we have no `this`, so we can't assign or read `this.state`. Instead, we call the `useState` Hook directly inside our component: ```js{4,5} -import { useState } from 'react'; +import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" @@ -139,7 +139,7 @@ function Example() { Now that we know what the `useState` Hook does, our example should make more sense: ```js{4,5} -import { useState } from 'react'; +import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" @@ -196,7 +196,7 @@ Let's now **recap what we learned line by line** and check our understanding. But if GitHub got away with it for years we can cheat. --> ```js{1,4,9} - 1: import { useState } from 'react'; + 1: import React, { useState } from 'react'; 2: 3: function Example() { 4: const [count, setCount] = useState(0); diff --git a/content/docs/nav.yml b/content/docs/nav.yml index 54e4a507d..d35b7f015 100644 --- a/content/docs/nav.yml +++ b/content/docs/nav.yml @@ -104,7 +104,7 @@ title: JS Environment Requirements - id: glossary title: Glossary -- title: Hooks (Preview) +- title: Hooks (New) isOrdered: true items: - id: hooks-intro diff --git a/content/docs/reference-react.md b/content/docs/reference-react.md index 68a00c4c0..3ac8c607a 100644 --- a/content/docs/reference-react.md +++ b/content/docs/reference-react.md @@ -65,6 +65,23 @@ Suspense lets components "wait" for something before rendering. Today, Suspense - [`React.lazy`](#reactlazy) - [`React.Suspense`](#reactsuspense) +### Hooks + +*Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. Hooks have a [dedicated docs section](/docs/hooks-intro.html) and a separate API reference: + +- [Basic Hooks](/docs/hooks-reference.html#basic-hooks) + - [`useState`](/docs/hooks-reference.html#usestate) + - [`useEffect`](/docs/hooks-reference.html#useeffect) + - [`useContext`](/docs/hooks-reference.html#usecontext) +- [Additional Hooks](/docs/hooks-reference.html#additional-hooks) + - [`useReducer`](/docs/hooks-reference.html#usereducer) + - [`useCallback`](/docs/hooks-reference.html#usecallback) + - [`useMemo`](/docs/hooks-reference.html#usememo) + - [`useRef`](/docs/hooks-reference.html#useref) + - [`useImperativeHandle`](/docs/hooks-reference.html#useimperativehandle) + - [`useLayoutEffect`](/docs/hooks-reference.html#uselayouteffect) + - [`useDebugValue`](/docs/hooks-reference.html#usedebugvalue) + * * * ## Reference diff --git a/content/versions.yml b/content/versions.yml index 896af542b..8f1ca21be 100644 --- a/content/versions.yml +++ b/content/versions.yml @@ -1,5 +1,9 @@ +- title: '16.8' + changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1680-february-6-2019 - title: '16.7' + path: /version/16.7 changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1670-december-19-2018 + url: https://5c54aa429e16c80007af3cd2--reactjs.netlify.com/ - title: '16.6' path: /version/16.6 changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1660-october-23-2018 diff --git a/content/warnings/invalid-hook-call-warning.md b/content/warnings/invalid-hook-call-warning.md index f04ec9013..38808adec 100644 --- a/content/warnings/invalid-hook-call-warning.md +++ b/content/warnings/invalid-hook-call-warning.md @@ -18,7 +18,7 @@ Let's look at each of these cases. ## Mismatching Versions of React and React DOM -You might be using a version of `react-dom` (< 16.8.0) or `react-native` (< 0.60) that doesn't yet support Hooks. You can run `npm ls react-dom` or `npm ls react-native` in your application folder to check which version you're using. If you find more than one of them, this might also create problems (more on that below). +You might be using a version of `react-dom` (< 16.8.0) or `react-native` (< 0.59) that doesn't yet support Hooks. You can run `npm ls react-dom` or `npm ls react-native` in your application folder to check which version you're using. If you find more than one of them, this might also create problems (more on that below). ## Breaking the Rules of Hooks diff --git a/gatsby-config.js b/gatsby-config.js index ed0799f34..456396f92 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -55,7 +55,7 @@ module.exports = { maxWidth: 840, }, }, - 'gatsby-remark-autolink-headers', + 'gatsby-remark-header-custom-ids', { resolve: 'gatsby-remark-code-repls', options: { diff --git a/package.json b/package.json index a9930ccc8..d050abaf5 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "gatsby-plugin-react-helmet": "^3.0.0", "gatsby-plugin-sharp": "^2.0.0", "gatsby-plugin-twitter": "^2.0.0", - "gatsby-remark-autolink-headers": "^2.0.12", "gatsby-remark-code-repls": "^2.0.0", "gatsby-remark-copy-linked-files": "^2.0.0", "gatsby-remark-embed-snippet": "^3.0.0", @@ -40,8 +39,10 @@ "gatsby-source-filesystem": "^2.0.0", "gatsby-transformer-remark": "^2.0.0", "gatsby-transformer-sharp": "^2.0.0", + "github-slugger": "^1.2.1", "glamor": "^2.20.40", "hex2rgba": "^0.0.1", + "mdast-util-to-string": "^1.0.5", "normalize.css": "^8.0.0", "prettier": "^1.7.4", "prismjs": "^1.15.0", diff --git a/plugins/gatsby-remark-header-custom-ids/gatsby-client.js b/plugins/gatsby-remark-header-custom-ids/gatsby-client.js new file mode 100644 index 000000000..6b24d0b13 --- /dev/null +++ b/plugins/gatsby-remark-header-custom-ids/gatsby-client.js @@ -0,0 +1,30 @@ +let offsetY = 0; + +const getTargetOffset = hash => { + const id = window.decodeURI(hash.replace(`#`, ``)); + if (id !== ``) { + const element = document.getElementById(id); + if (element) { + return element.offsetTop - offsetY; + } + } + return null; +}; + +exports.onInitialClientRender = (_, pluginOptions) => { + if (pluginOptions.offsetY) { + offsetY = pluginOptions.offsetY; + } + + requestAnimationFrame(() => { + const offset = getTargetOffset(window.location.hash); + if (offset !== null) { + window.scrollTo(0, offset); + } + }); +}; + +exports.shouldUpdateScroll = ({routerProps: {location}}) => { + const offset = getTargetOffset(location.hash); + return offset !== null ? [0, offset] : true; +}; diff --git a/plugins/gatsby-remark-header-custom-ids/gatsby-ssr.js b/plugins/gatsby-remark-header-custom-ids/gatsby-ssr.js new file mode 100644 index 000000000..c616fc289 --- /dev/null +++ b/plugins/gatsby-remark-header-custom-ids/gatsby-ssr.js @@ -0,0 +1,76 @@ +const React = require(`react`); + +const pluginDefaults = { + className: `anchor`, + icon: true, + offsetY: 0, +}; + +exports.onRenderBody = ({setHeadComponents}, pluginOptions) => { + const {className, icon, offsetY} = Object.assign( + pluginDefaults, + pluginOptions, + ); + + const styles = ` + .${className} { + float: left; + padding-right: 4px; + margin-left: -20px; + } + h1 .${className} svg, + h2 .${className} svg, + h3 .${className} svg, + h4 .${className} svg, + h5 .${className} svg, + h6 .${className} svg { + visibility: hidden; + } + h1:hover .${className} svg, + h2:hover .${className} svg, + h3:hover .${className} svg, + h4:hover .${className} svg, + h5:hover .${className} svg, + h6:hover .${className} svg, + h1 .${className}:focus svg, + h2 .${className}:focus svg, + h3 .${className}:focus svg, + h4 .${className}:focus svg, + h5 .${className}:focus svg, + h6 .${className}:focus svg { + visibility: visible; + } + `; + + const script = ` + document.addEventListener("DOMContentLoaded", function(event) { + var hash = window.decodeURI(location.hash.replace('#', '')) + if (hash !== '') { + var element = document.getElementById(hash) + if (element) { + var offset = element.offsetTop + // Wait for the browser to finish rendering before scrolling. + setTimeout((function() { + window.scrollTo(0, offset - ${offsetY}) + }), 0) + } + } + }) + `; + + const style = icon ? ( + + ) : ( + undefined + ); + + return setHeadComponents([ + style, +