diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index b0efeb7bc..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: circleci/node:8 - steps: - - checkout - - restore_cache: - keys: - - dependencies-{{ checksum "yarn.lock" }} - - run: - name: Install - command: yarn install --pure-lockfile - - save_cache: - paths: - - node_modules - key: dependencies-{{ checksum "yarn.lock" }} - - run: - name: Check Prettier, ESLint, Flow - command: yarn ci-check diff --git a/.eslintignore b/.eslintignore index 942541715..ee6604687 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,8 @@ node_modules/* +# Skip beta +beta/* + # Ignore markdown files and examples content/* diff --git a/.flowconfig b/.flowconfig index 836f6ec1e..baf4b0255 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,5 +1,6 @@ [ignore] +<PROJECT_ROOT>/beta/.* <PROJECT_ROOT>/content/.* <PROJECT_ROOT>/node_modules/.* <PROJECT_ROOT>/public/.* diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d3c569401..e34dda4af 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,12 +1,10 @@ - - <!-- Thank you for the PR! Contributors like you keep React awesome! Please see the Contribution Guide for guidelines: -https://github.com/reactjs/reactjs.org/blob/master/CONTRIBUTING.md +https://github.com/reactjs/reactjs.org/blob/main/CONTRIBUTING.md If your PR references an existing issue, please add the issue number below diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..7768da2ba --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,2 @@ +beta: +- beta/**/* diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml new file mode 100644 index 000000000..ea9f6b573 --- /dev/null +++ b/.github/workflows/analyze.yml @@ -0,0 +1,96 @@ +name: Analyze Bundle + +on: + pull_request: + push: + branches: + - main # change this if your default branch is named differently + workflow_dispatch: + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up node + uses: actions/setup-node@v1 + with: + node-version: "14.x" + + - name: Install dependencies + uses: bahmutov/npm-install@v1.7.10 + with: + working-directory: 'beta' + + - name: Restore next build + uses: actions/cache@v2 + id: restore-build-cache + env: + cache-name: cache-next-build + with: + path: beta/.next/cache + # change this if you prefer a more strict cache + key: ${{ runner.os }}-build-${{ env.cache-name }} + + - name: Build next.js app + # change this if your site requires a custom build command + run: ./node_modules/.bin/next build + working-directory: beta + + # Here's the first place where next-bundle-analysis' own script is used + # This step pulls the raw bundle stats for the current bundle + - name: Analyze bundle + run: npx -p nextjs-bundle-analysis report + working-directory: beta + + - name: Upload bundle + uses: actions/upload-artifact@v2 + with: + path: beta/.next/analyze/__bundle_analysis.json + name: bundle_analysis.json + + - name: Download base branch bundle stats + uses: dawidd6/action-download-artifact@v2 + if: success() && github.event.number + with: + workflow: analyze.yml + branch: ${{ github.event.pull_request.base.ref }} + name: bundle_analysis.json + path: beta/.next/analyze/base/bundle + + # And here's the second place - this runs after we have both the current and + # base branch bundle stats, and will compare them to determine what changed. + # There are two configurable arguments that come from package.json: + # + # - budget: optional, set a budget (bytes) against which size changes are measured + # it's set to 350kb here by default, as informed by the following piece: + # https://infrequently.org/2021/03/the-performance-inequality-gap/ + # + # - red-status-percentage: sets the percent size increase where you get a red + # status indicator, defaults to 20% + # + # Either of these arguments can be changed or removed by editing the `nextBundleAnalysis` + # entry in your package.json file. + - name: Compare with base branch bundle + if: success() && github.event.number + run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare + working-directory: beta + + - name: Upload analysis comment + uses: actions/upload-artifact@v2 + with: + name: analysis_comment.txt + path: beta/.next/analyze/__bundle_analysis_comment.txt + + - name: Save PR number + run: echo ${{ github.event.number }} > ./pr_number + + - name: Upload PR number + uses: actions/upload-artifact@v2 + with: + name: pr_number + path: ./pr_number + + # The actual commenting happens in the other action, matching the guidance in + # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ diff --git a/.github/workflows/analyze_comment.yml b/.github/workflows/analyze_comment.yml new file mode 100644 index 000000000..8166089fd --- /dev/null +++ b/.github/workflows/analyze_comment.yml @@ -0,0 +1,72 @@ +name: Analyze Bundle (Comment) + +on: + workflow_run: + workflows: ["Analyze Bundle"] + types: + - completed + +jobs: + comment: + runs-on: ubuntu-latest + if: > + ${{ github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' }} + steps: + - name: Download base branch bundle stats + uses: dawidd6/action-download-artifact@v2 + with: + workflow: analyze.yml + run_id: ${{ github.event.workflow_run.id }} + name: analysis_comment.txt + path: analysis_comment.txt + + - name: Download PR number + uses: dawidd6/action-download-artifact@v2 + with: + workflow: analyze.yml + run_id: ${{ github.event.workflow_run.id }} + name: pr_number + path: pr_number + + - name: Get comment body + id: get-comment-body + if: success() + run: | + pr_number=$(cat pr_number/pr_number) + body=$(cat analysis_comment.txt/__bundle_analysis_comment.txt) + body="## Size Changes + <details> + + ${body} + + </details>" + body="${body//'%'/'%25'}" + body="${body//$'\n'/'%0A'}" + body="${body//$'\r'/'%0D'}" + echo ::set-output name=body::$body + echo ::set-output name=pr-number::$pr_number + + - name: Find Comment + uses: peter-evans/find-comment@v1 + if: success() + id: fc + with: + issue-number: ${{ steps.get-comment-body.outputs.pr-number }} + body-includes: "<!-- __NEXTJS_BUNDLE -->" + + - name: Create Comment + uses: peter-evans/create-or-update-comment@v1.4.4 + if: success() && steps.fc.outputs.comment-id == 0 + with: + issue-number: ${{ steps.get-comment-body.outputs.pr-number }} + body: ${{ steps.get-comment-body.outputs.body }} + + - name: Update Comment + uses: peter-evans/create-or-update-comment@v1.4.4 + if: success() && steps.fc.outputs.comment-id != 0 + with: + issue-number: ${{ steps.get-comment-body.outputs.pr-number }} + body: ${{ steps.get-comment-body.outputs.body }} + comment-id: ${{ steps.fc.outputs.comment-id }} + edit-mode: replace \ No newline at end of file diff --git a/.github/workflows/beta_site_lint.yml b/.github/workflows/beta_site_lint.yml new file mode 100644 index 000000000..a9cacd7f0 --- /dev/null +++ b/.github/workflows/beta_site_lint.yml @@ -0,0 +1,30 @@ +name: Beta Site Lint / Heading ID check + +on: + push: + branches: + - main # change this if your default branch is named differently + pull_request: + types: [opened, synchronize, reopened] + +jobs: + lint: + runs-on: ubuntu-latest + + name: Lint on node 12.x and ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + + - name: Install deps and build (with cache) + uses: bahmutov/npm-install@v1.7.10 + with: + working-directory: 'beta' + + + - name: Lint codebase + run: cd beta && yarn ci-check diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 000000000..90a961d4c --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,22 @@ +# This workflow will triage pull requests and apply a label based on the +# paths that are modified in the pull request. +# +# To use this workflow, you will need to set up a .github/labeler.yml +# file with configuration. For more information, see: +# https://github.com/actions/labeler + +name: Labeler +on: [pull_request_target] + +jobs: + label: + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - uses: actions/labeler@v2 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 000000000..eed7b3d94 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,27 @@ +name: Lint / Flow check + +on: + push: + branches: + - main # change this if your default branch is named differently + pull_request: + types: [opened, synchronize, reopened] + +jobs: + lint: + runs-on: ubuntu-latest + + name: Lint on node 12.x and ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + + - name: Install deps and build (with cache) + uses: bahmutov/npm-install@v1.7.10 + + - name: Lint codebase + run: yarn ci-check diff --git a/.gitignore b/.gitignore index d1bde99ce..e81f1af62 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ .DS_STORE .idea node_modules -public +/public yarn-error.log \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 5debbed21..898e643b0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -lts/carbon +12.22.0 diff --git a/README.md b/README.md index c9118e714..0b91c9aa6 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ This repo contains the source code and documentation powering [reactjs.org](http ### Prerequisites 1. Git -1. Node: any 8.x version starting with 8.4.0 or greater -1. Yarn: See [Yarn website for installation instructions](https://yarnpkg.com/lang/en/docs/install/) +1. Node: any 12.x version starting with v12.0.0 or greater +1. Yarn v1: See [Yarn website for installation instructions](https://yarnpkg.com/lang/en/docs/install/) 1. A fork of the repo (for any contributions) 1. A clone of the [reactjs.org repo](https://github.com/reactjs/reactjs.org) on your local machine @@ -26,12 +26,12 @@ This repo contains the source code and documentation powering [reactjs.org](http ### Guidelines -The documentation is divided into several sections with a different tone and purpose. If you plan to write more than a few sentences, you might find it helpful to get familiar with the [contributing guidelines](https://github.com/reactjs/reactjs.org/blob/master/CONTRIBUTING.md#guidelines-for-text) for the appropriate sections. +The documentation is divided into several sections with a different tone and purpose. If you plan to write more than a few sentences, you might find it helpful to get familiar with the [contributing guidelines](https://github.com/reactjs/reactjs.org/blob/main/CONTRIBUTING.md#guidelines-for-text) for the appropriate sections. ### Create a branch -1. `git checkout master` from any folder in your local `reactjs.org` repository -1. `git pull origin master` to ensure you have the latest main code +1. `git checkout main` from any folder in your local `reactjs.org` repository +1. `git pull origin main` to ensure you have the latest main code 1. `git checkout -b the-name-of-my-branch` (replacing `the-name-of-my-branch` with a suitable name) to create a branch ### Make the change @@ -53,11 +53,11 @@ The documentation is divided into several sections with a different tone and pur 1. `git push my-fork-name the-name-of-my-branch` 1. Go to the [reactjs.org repo](https://github.com/reactjs/reactjs.org) and you should see recently pushed branches. 1. Follow GitHub's instructions. -1. If possible, include screenshots of visual changes. A Netlify build will also be automatically created once you make your PR so other people can see your change. +1. If possible, include screenshots of visual changes. A preview build is triggered after your changes are pushed to GitHub. ## Translation -If you are interested in translating `reactjs.org`, please see the current translation efforts at [isreacttranslatedyet.com](https://www.isreacttranslatedyet.com/). +If you are interested in translating `reactjs.org`, please see the current translation efforts at [translations.reactjs.org](https://translations.reactjs.org/). If your language does not have a translation and you would like to create one, please follow the instructions at [reactjs.org Translations](https://github.com/reactjs/reactjs.org-translation#translating-reactjsorg). @@ -67,4 +67,4 @@ If your language does not have a translation and you would like to create one, p - `yarn reset` to clear the local cache ## License -Content submitted to [reactjs.org](https://reactjs.org/) is CC-BY-4.0 licensed, as found in the [LICENSE-DOCS.md](https://github.com/open-source-explorer/reactjs.org/blob/master/LICENSE-DOCS.md) file. +Content submitted to [reactjs.org](https://reactjs.org/) is CC-BY-4.0 licensed, as found in the [LICENSE-DOCS.md](LICENSE-DOCS.md) file. diff --git a/beta/.env.development b/beta/.env.development new file mode 100644 index 000000000..a692f21c7 --- /dev/null +++ b/beta/.env.development @@ -0,0 +1 @@ +SANDPACK_BARE_COMPONENTS=true \ No newline at end of file diff --git a/beta/.env.production b/beta/.env.production new file mode 100644 index 000000000..445c9c4d0 --- /dev/null +++ b/beta/.env.production @@ -0,0 +1,2 @@ +NEXT_PUBLIC_GA_TRACKING_ID = 'UA-41298772-4' +SANDPACK_BARE_COMPONENTS=true \ No newline at end of file diff --git a/beta/.eslintignore b/beta/.eslintignore new file mode 100644 index 000000000..4738cb697 --- /dev/null +++ b/beta/.eslintignore @@ -0,0 +1,3 @@ +scripts +plugins +next.config.js diff --git a/beta/.eslintrc b/beta/.eslintrc new file mode 100644 index 000000000..147e54607 --- /dev/null +++ b/beta/.eslintrc @@ -0,0 +1,16 @@ +{ + "root": true, + "extends": "next/core-web-vitals", + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "warn" + }, + "env": { + "node": true, + "commonjs": true, + "browser": true, + "es6": true + } +} diff --git a/beta/.gitignore b/beta/.gitignore new file mode 100644 index 000000000..c842b12a3 --- /dev/null +++ b/beta/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem +tsconfig.tsbuildinfo + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# external fonts +public/fonts/Optimistic_Display_W_Lt.woff2 +public/fonts/Optimistic_Display_W_Md.woff2 +public/fonts/Optimistic_Display_W_Bd.woff2 diff --git a/beta/.husky/pre-commit b/beta/.husky/pre-commit new file mode 100755 index 000000000..bbf64071e --- /dev/null +++ b/beta/.husky/pre-commit @@ -0,0 +1,5 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +cd beta +yarn lint-staged \ No newline at end of file diff --git a/beta/.prettierignore b/beta/.prettierignore new file mode 100644 index 000000000..96f1f96d2 --- /dev/null +++ b/beta/.prettierignore @@ -0,0 +1 @@ +src/content/**/*.md diff --git a/beta/.prettierrc b/beta/.prettierrc new file mode 100644 index 000000000..19b54ad05 --- /dev/null +++ b/beta/.prettierrc @@ -0,0 +1,21 @@ +{ + "bracketSpacing": false, + "singleQuote": true, + "bracketSameLine": true, + "trailingComma": "es5", + "printWidth": 80, + "overrides": [ + { + "files": "*.css", + "options": { + "parser": "css" + } + }, + { + "files": "*.md", + "options": { + "parser": "mdx" + } + } + ] +} diff --git a/beta/CONTRIBUTING.md b/beta/CONTRIBUTING.md new file mode 100644 index 000000000..dd81c8546 --- /dev/null +++ b/beta/CONTRIBUTING.md @@ -0,0 +1,135 @@ +# Contributing + +Thank you for your interest in contributing to the React Docs! + +## Code of Conduct + +Facebook has adopted a Code of Conduct that we expect project +participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) +so that you can understand what actions will and will not be tolerated. + +## Technical Writing Tips + +This is a [good summary](https://medium.com/@kvosswinkel/coding-like-a-journalist-ee52360a16bc) for things to keep in mind when writing technical docs. + +## Guidelines for Text + +**Different sections intentionally have different styles.** + +The documentation is divided into sections to cater to different learning styles and use cases. When editing an article, try to match the surrounding text in tone and style. When creating a new article, try to match the tone of the other articles in the same section. Learn about the motivation behind each section below. + +**[Learn React](https://beta.reactjs.org/learn)** is designed to introduce fundamental concepts in a step-by-step way. Each individual article in Learn React builds on the knowledge from the previous ones, so make sure not to add any "cyclical dependencies" between them. It is important that the reader can start with the first article and work their way to the last Learn React article without ever having to "look ahead" for a definition. This explains some ordering choices (e.g. that state is explained before events, or that "thinking in React" doesn't use refs). Learn React also serves as a reference manual for React concepts, so it is important to be very strict about their definitions and relationships between them. + +**[API Reference](https://reactjs.org/apis/react)** is organized by APIs rather than concepts. It is intended to be exhaustive. Any corner cases or recommendations that were skipped for brevity in Learn React should be mentioned in the reference documentation for the corresponding APIs. + +**Try to follow your own instructions.** + +When writing step-by-step instructions (e.g. how to install something), try to forget everything you know about the topic, and actually follow the instructions you wrote, a single step at time. Often you will discover that there is implicit knowledge that you forgot to mention, or that there are missing or out-of-order steps in the instructions. Bonus points for getting *somebody else* to follow the steps and watching what they struggle with. Often it would be something very simple that you have not anticipated. + +## Guidelines for Code Examples + +### Syntax + +#### Prefer JSX to `createElement`. + +Ignore this if you're specifically describing `createElement`. + +#### Use `const` where possible, otherwise `let`. Don't use `var`. + +Ignore this if you're specifically writing about ES5. + +#### Don't use ES6 features when equivalent ES5 features have no downsides. + +Remember that ES6 is still new to a lot of people. While we use it in many places (`const` / `let`, classes, arrow functions), if the equivalent ES5 code is just as straightforward and readable, consider using it. + +In particular, you should prefer named `function` declarations over `const myFunction = () => ...` arrows for top-level functions. However, you *should* use arrow functions where they provide a tangible improvement (such as preserving `this` context inside a component). Consider both sides of the tradeoff when deciding whether to use a new feature. + +#### Don't use features that aren't standardized yet. + +For example, **don't** write this: + +```js +class MyComponent extends React.Component { + state = {value: ''}; + handleChange = (e) => { + this.setState({value: e.target.value}); + }; +} +``` + +Instead, **do** write this: + +```js +class MyComponent extends React.Component { + constructor(props) { + super(props); + this.handleChange = this.handleChange.bind(this); + this.state = {value: ''}; + } + handleChange(e) { + this.setState({value: e.target.value}); + } +} +``` + +Ignore this rule if you're specifically describing an experimental proposal. Make sure to mention its experimental nature in the code and in the surrounding text. + +### Style + +- Use semicolons. +- No space between function names and parens (`method() {}` not `method () {}`). +- When in doubt, use the default style favored by [Prettier](https://prettier.io/playground/). + +### Highlighting + +Use `js` as the highlighting language in Markdown code blocks: + +```` +```js +// code +``` +```` + +Sometimes you'll see blocks with numbers. +They tell the website to highlight specific lines. + +You can highlight a single line: + +```` +```js {2} +function hello() { + // this line will get highlighted +} +``` +```` + +A range of lines: + +```` +```js {2-4} +function hello() { + // these lines + // will get + // highlighted +} +``` +```` + +Or even multiple ranges: + +```` +```js {2-4,6} +function hello() { + // these lines + // will get + // highlighted + console.log('hello'); + // also this one + console.log('there'); +} +``` +```` + +Be mindful that if you move some code in an example with highlighting, you also need to update the highlighting. + +Don't be afraid to often use highlighting! It is very valuable when you need to focus the reader's attention on a particular detail that's easy to miss. diff --git a/beta/README.md b/beta/README.md new file mode 100644 index 000000000..8208938ee --- /dev/null +++ b/beta/README.md @@ -0,0 +1,73 @@ +# reactjs.org + +This repo contains the source code and documentation powering [beta.reactjs.org](https://beta.reactjs.org/). + + +## Getting started + +### Prerequisites + +1. Git +1. Node: any 12.x version starting with v12.0.0 or greater +1. Yarn: See [Yarn website for installation instructions](https://yarnpkg.com/lang/en/docs/install/) +1. A fork of the repo (for any contributions) +1. A clone of the [reactjs.org repo](https://github.com/reactjs/reactjs.org) on your local machine + +### Installation + +1. `cd reactjs.org` to go into the project root +1. `cd beta` to open the beta website +3. `yarn` to install the website's npm dependencies + +### Running locally + +1. Make sure you're in the `beta` folder +1. `yarn dev` to start the development server (powered by [Next.js](https://nextjs.org/)) +1. `open http://localhost:3000` to open the site in your favorite browser + +## Contributing + +### Guidelines + +The documentation is divided into several sections with a different tone and purpose. If you plan to write more than a few sentences, you might find it helpful to get familiar with the [contributing guidelines](https://github.com/reactjs/reactjs.org/blob/main/CONTRIBUTING.md#guidelines-for-text) for the appropriate sections. + +### Create a branch + +1. `git checkout main` from any folder in your local `reactjs.org` repository +1. `git pull origin main` to ensure you have the latest main code +1. `git checkout -b the-name-of-my-branch` (replacing `the-name-of-my-branch` with a suitable name) to create a branch + +### Make the change + +1. Follow the ["Running locally"](#running-locally) instructions +1. Save the files and check in the browser + 1. Changes to React components in `src` will hot-reload + 1. Changes to markdown files in `content` will hot-reload + 1. If working with plugins, you may need to remove the `.cache` directory and restart the server + +### Test the change + +1. If possible, test any visual changes in all latest versions of common browsers, on both desktop and mobile. +2. Run `yarn check-all` from the `beta` folder. (This will run Prettier, ESLint and validate types.) + +### Push it + +1. `git add -A && git commit -m "My message"` (replacing `My message` with a commit message, such as `Fix header logo on Android`) to stage and commit your changes +1. `git push my-fork-name the-name-of-my-branch` +1. Go to the [reactjs.org repo](https://github.com/reactjs/reactjs.org) and you should see recently pushed branches. +1. Follow GitHub's instructions. +1. If possible, include screenshots of visual changes. A preview build is triggered after your changes are pushed to GitHub. + +## Translation + +If you are interested in translating `reactjs.org`, please see the current translation efforts at [translations.reactjs.org](https://translations.reactjs.org/). + + +If your language does not have a translation and you would like to create one, please follow the instructions at [reactjs.org Translations](https://github.com/reactjs/reactjs.org-translation#translating-reactjsorg). + +## Troubleshooting + +- `yarn reset` to clear the local cache + +## License +Content submitted to [reactjs.org](https://reactjs.org/) is CC-BY-4.0 licensed, as found in the [LICENSE-DOCS.md](https://github.com/open-source-explorer/reactjs.org/blob/master/LICENSE-DOCS.md) file. diff --git a/beta/colors.js b/beta/colors.js new file mode 100644 index 000000000..bac74d41e --- /dev/null +++ b/beta/colors.js @@ -0,0 +1,89 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +module.exports = { + // Text colors + primary: '#23272F', // gray-90 + 'primary-dark': '#F6F7F9', // gray-5 + secondary: '#404756', // gray-70 + 'secondary-dark': '#EBECF0', // gray-10 + link: '#087EA4', // blue-50 + 'link-dark': '#149ECA', // blue-40 + syntax: '#EBECF0', // gray-10 + wash: '#FFFFFF', + 'wash-dark': '#23272F', // gray-90 + card: '#F6F7F9', // gray-05 + 'card-dark': '#343A46', // gray-80 + highlight: '#E6F7FF', // blue-10 + 'highlight-dark': 'rgba(88,175,223,.1)', + border: '#EBECF0', // gray-10 + 'border-dark': '#343A46', // gray-80 + 'secondary-button': '#EBECF0', // gray-10 + 'secondary-button-dark': '#404756', // gray-70 + + // Gray + 'gray-95': '#16181D', + 'gray-90': '#23272F', + 'gray-80': '#343A46', + 'gray-70': '#404756', + 'gray-60': '#4E5769', + 'gray-50': '#5E687E', // unused + 'gray-40': '#78839B', + 'gray-30': '#99A1B3', + 'gray-20': '#BCC1CD', + 'gray-10': '#EBECF0', + 'gray-5': '#F6F7F9', + + // Blue + 'blue-60': '#045975', + 'blue-50': '#087EA4', + 'blue-40': '#149ECA', // Brand Blue + 'blue-30': '#58C4DC', // unused + 'blue-20': '#ABE2ED', + 'blue-10': '#E6F7FF', // todo: doesn't match illustrations + 'blue-5': '#E6F6FA', + + // Yellow + 'yellow-60': '#B65700', + 'yellow-50': '#C76A15', + 'yellow-40': '#DB7D27', // unused + 'yellow-30': '#FABD62', // unused + 'yellow-20': '#FCDEB0', // unused + 'yellow-10': '#FDE7C7', + 'yellow-5': '#FEF5E7', + + // Purple + 'purple-60': '#2B3491', // unused + 'purple-50': '#575FB7', + 'purple-40': '#6B75DB', + 'purple-30': '#8891EC', + 'purple-20': '#C3C8F5', // unused + 'purple-10': '#E7E9FB', + 'purple-5': '#F3F4FD', + + // Green + 'green-60': '#2B6E62', + 'green-50': '#388F7F', + 'green-40': '#44AC99', + 'green-30': '#7FCCBF', + 'green-20': '#ABDED5', + 'green-10': '#E5F5F2', + 'green-5': '#F4FBF9', + + // RED + 'red-60': '#712D28', + 'red-50': '#A6423A', // unused + 'red-40': '#C1554D', + 'red-30': '#D07D77', + 'red-20': '#E5B7B3', // unused + 'red-10': '#F2DBD9', // unused + 'red-5': '#FAF1F0', + + // MISC + 'code-block': '#99a1b30f', // gray-30 @ 6% + 'gradient-blue': '#58C4DC', // Only used for the landing gradient for now. + github: { + highlight: '#fffbdd', + }, +}; diff --git a/beta/next-env.d.ts b/beta/next-env.d.ts new file mode 100644 index 000000000..4f11a03dc --- /dev/null +++ b/beta/next-env.d.ts @@ -0,0 +1,5 @@ +/// <reference types="next" /> +/// <reference types="next/image-types/global" /> + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/beta/next.config.js b/beta/next.config.js new file mode 100644 index 000000000..2ea3e916e --- /dev/null +++ b/beta/next.config.js @@ -0,0 +1,70 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/** + * @type {import('next').NextConfig} + **/ +const nextConfig = { + pageExtensions: ['jsx', 'js', 'ts', 'tsx', 'mdx', 'md'], + reactStrictMode: true, + experimental: { + plugins: true, + scrollRestoration: true, + legacyBrowsers: false, + browsersListForSwc: true, + }, + env: { + SANDPACK_BARE_COMPONENTS: process.env.SANDPACK_BARE_COMPONENTS, + }, + webpack: (config, {dev, isServer, ...options}) => { + if (process.env.ANALYZE) { + const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); + config.plugins.push( + new BundleAnalyzerPlugin({ + analyzerMode: 'static', + reportFilename: options.isServer + ? '../analyze/server.html' + : './analyze/client.html', + }) + ); + } + + // Don't bundle the shim unnecessarily. + config.resolve.alias['use-sync-external-store/shim'] = 'react'; + + const {IgnorePlugin, NormalModuleReplacementPlugin} = require('webpack'); + config.plugins.push( + new NormalModuleReplacementPlugin( + /^@stitches\/core$/, + require.resolve('./src/utils/emptyShim.js') + ), + new NormalModuleReplacementPlugin( + /^raf$/, + require.resolve('./src/utils/rafShim.js') + ), + new NormalModuleReplacementPlugin( + /^process$/, + require.resolve('./src/utils/processShim.js') + ), + new IgnorePlugin({ + checkResource(resource, context) { + if ( + /\/eslint\/lib\/rules$/.test(context) && + /\.\/[\w-]+(\.js)?$/.test(resource) + ) { + // Skips imports of built-in rules that ESLint + // tries to carry into the bundle by default. + // We only want the engine and the React rules. + return true; + } + return false; + }, + }) + ); + + return config; + }, +}; + +module.exports = nextConfig; diff --git a/beta/package.json b/beta/package.json new file mode 100644 index 000000000..acef2acfc --- /dev/null +++ b/beta/package.json @@ -0,0 +1,112 @@ +{ + "name": "react-website", + "version": "1.0.0", + "private": true, + "license": "CC", + "scripts": { + "analyze": "ANALYZE=true next build", + "dev": "next-remote-watch ./src/content", + "build": "next build && node ./scripts/downloadFonts.js", + "lint": "next lint", + "lint:fix": "next lint --fix", + "format:source": "prettier --config .prettierrc --write \"{plugins,src}/**/*.{js,ts,jsx,tsx,css}\"", + "nit:source": "prettier --config .prettierrc --list-different \"{plugins,src}/**/*.{js,ts,jsx,tsx,css}\"", + "prettier": "yarn format:source", + "prettier:diff": "yarn nit:source", + "lint-heading-ids": "node scripts/headingIdLinter.js", + "fix-headings": "node scripts/headingIdLinter.js --fix", + "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids", + "tsc": "tsc --noEmit", + "start": "next start", + "postinstall": "patch-package && (is-ci || (cd .. && husky install beta/.husky))", + "check-all": "npm-run-all prettier lint:fix tsc" + }, + "dependencies": { + "@codesandbox/sandpack-react": "1.15.5", + "@docsearch/css": "3.0.0-alpha.41", + "@docsearch/react": "3.0.0-alpha.41", + "@headlessui/react": "^1.7.0", + "body-scroll-lock": "^3.1.3", + "classnames": "^2.2.6", + "date-fns": "^2.16.1", + "debounce": "^1.2.1", + "ga-lite": "^2.1.4", + "github-slugger": "^1.3.0", + "next": "12.3.2-canary.7", + "next-remote-watch": "^1.0.0", + "parse-numeric-range": "^1.2.0", + "react": "0.0.0-experimental-cb5084d1c-20220924", + "react-collapsed": "npm:@gaearon/react-collapsed@3.1.0-forked.1", + "react-dom": "0.0.0-experimental-cb5084d1c-20220924", + "remark-frontmatter": "^4.0.1", + "remark-gfm": "^3.0.1" + }, + "devDependencies": { + "@babel/core": "^7.12.9", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@mdx-js/mdx": "^2.1.3", + "@types/body-scroll-lock": "^2.6.1", + "@types/classnames": "^2.2.10", + "@types/debounce": "^1.2.1", + "@types/github-slugger": "^1.3.0", + "@types/mdx-js__react": "^1.5.2", + "@types/node": "^14.6.4", + "@types/parse-numeric-range": "^0.0.1", + "@types/react": "^18.0.9", + "@types/react-dom": "^18.0.5", + "@typescript-eslint/eslint-plugin": "^5.36.2", + "@typescript-eslint/parser": "^5.36.2", + "asyncro": "^3.0.0", + "autoprefixer": "^10.4.2", + "babel-eslint": "10.x", + "eslint": "7.x", + "eslint-config-next": "12.0.3", + "eslint-config-react-app": "^5.2.1", + "eslint-plugin-flowtype": "4.x", + "eslint-plugin-import": "2.x", + "eslint-plugin-jsx-a11y": "6.x", + "eslint-plugin-react": "7.x", + "eslint-plugin-react-hooks": "^0.0.0-experimental-fabef7a6b-20221215", + "fs-extra": "^9.0.1", + "globby": "^11.0.1", + "gray-matter": "^4.0.2", + "husky": "^7.0.4", + "is-ci": "^3.0.1", + "lint-staged": ">=10", + "mdast-util-to-string": "^1.1.0", + "metro-cache": "0.72.2", + "npm-run-all": "^4.1.5", + "patch-package": "^6.2.2", + "postcss": "^8.4.5", + "postcss-flexbugs-fixes": "4.2.1", + "postcss-preset-env": "^6.7.0", + "prettier": "^2.5.1", + "reading-time": "^1.2.0", + "remark": "^12.0.1", + "remark-external-links": "^7.0.0", + "remark-html": "^12.0.0", + "remark-images": "^2.0.0", + "remark-slug": "^7.0.0", + "remark-unwrap-images": "^2.0.0", + "retext": "^7.0.1", + "retext-smartypants": "^4.0.0", + "rss": "^1.2.2", + "tailwindcss": "^3.0.22", + "typescript": "^4.0.2", + "unist-util-visit": "^2.0.3", + "webpack-bundle-analyzer": "^4.5.0" + }, + "engines": { + "node": ">=12.x" + }, + "nextBundleAnalysis": { + "budget": null, + "budgetPercentIncreaseRed": 10, + "showDetails": true + }, + "lint-staged": { + "*.{js,ts,jsx,tsx,css}": "yarn prettier", + "src/**/*.md": "yarn fix-headings" + } +} diff --git a/beta/patches/@codemirror+lang-javascript+0.19.6.patch b/beta/patches/@codemirror+lang-javascript+0.19.6.patch new file mode 100644 index 000000000..3436b8e37 --- /dev/null +++ b/beta/patches/@codemirror+lang-javascript+0.19.6.patch @@ -0,0 +1,30 @@ +diff --git a/node_modules/@codemirror/lang-javascript/dist/index.cjs b/node_modules/@codemirror/lang-javascript/dist/index.cjs +index 4475e4f..e1255c9 100644 +--- a/node_modules/@codemirror/lang-javascript/dist/index.cjs ++++ b/node_modules/@codemirror/lang-javascript/dist/index.cjs +@@ -135,7 +135,9 @@ const javascriptLanguage = language.LRLanguage.define({ + JSXText: highlight.tags.content, + "JSXStartTag JSXStartCloseTag JSXSelfCloseEndTag JSXEndTag": highlight.tags.angleBracket, + "JSXIdentifier JSXNameSpacedName": highlight.tags.tagName, +- "JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName": highlight.tags.attributeName ++ "JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName": highlight.tags.attributeName, ++ "JSXAttribute/JSXLowerIdentifier JSXAttribute/JSXNameSpacedName": highlight.tags.attributeName, ++ "JSXBuiltin/JSXIdentifier": highlight.tags.standard(highlight.tags.tagName) + }) + ] + }), +diff --git a/node_modules/@codemirror/lang-javascript/dist/index.js b/node_modules/@codemirror/lang-javascript/dist/index.js +index d089f6b..db09ea6 100644 +--- a/node_modules/@codemirror/lang-javascript/dist/index.js ++++ b/node_modules/@codemirror/lang-javascript/dist/index.js +@@ -131,7 +131,9 @@ const javascriptLanguage = /*@__PURE__*/LRLanguage.define({ + JSXText: tags.content, + "JSXStartTag JSXStartCloseTag JSXSelfCloseEndTag JSXEndTag": tags.angleBracket, + "JSXIdentifier JSXNameSpacedName": tags.tagName, +- "JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName": tags.attributeName ++ "JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName": tags.attributeName, ++ "JSXAttribute/JSXLowerIdentifier JSXAttribute/JSXNameSpacedName": tags.attributeName, ++ "JSXBuiltin/JSXIdentifier": tags.standard(tags.tagName), + }) + ] + }), diff --git a/beta/patches/@codesandbox+sandpack-react+1.15.5.patch b/beta/patches/@codesandbox+sandpack-react+1.15.5.patch new file mode 100644 index 000000000..6a9a438dc --- /dev/null +++ b/beta/patches/@codesandbox+sandpack-react+1.15.5.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@codesandbox/sandpack-react/dist/cjs/index.js b/node_modules/@codesandbox/sandpack-react/dist/cjs/index.js +index 6b8518e..86dd663 100644 +--- a/node_modules/@codesandbox/sandpack-react/dist/cjs/index.js ++++ b/node_modules/@codesandbox/sandpack-react/dist/cjs/index.js +@@ -395,7 +395,7 @@ createApp(App).mount('#app') + <!-- built files will be auto injected --> + </body> + </html> +-`},"/package.json":{code:JSON.stringify({dependencies:{"core-js":"^3.6.5",vue:"^3.0.0-0","@vue/cli-plugin-babel":"4.5.0"},main:"/src/main.js"})}},main:"/src/App.vue",environment:"vue-cli"};var We={react:wt,"react-ts":Ft,vue:Ht,vanilla:Ot,"vanilla-ts":Dt,vue3:Bt,angular:Lt,svelte:Pt,solid:At,"test-ts":It};var pt=e=>{var c,p,d,h,y,S;let t=(0,Ee.normalizePath)(e.files),o=bn({template:e.template,customSetup:e.customSetup,files:t}),s=(0,Ee.normalizePath)((p=(c=e.options)==null?void 0:c.visibleFiles)!=null?p:[]),r=((d=e.options)==null?void 0:d.activeFile)?Is((h=e.options)==null?void 0:h.activeFile,t||{}):void 0;s.length===0&&t&&Object.keys(t).forEach(v=>{let g=t[v];if(typeof g=="string"){s.push(v);return}!r&&g.active&&(r=v,g.hidden===!0&&s.push(v)),g.hidden||s.push(v)}),s.length===0&&(s=[o.main]),o.files[o.entry]||(o.entry=Is(o.entry,o.files)),!r&&o.main&&(r=o.main),(!r||!o.files[r])&&(r=s[0]),s.includes(r)||s.push(r);let n=(0,Ee.addPackageJSONIfNeeded)(o.files,(y=o.dependencies)!=null?y:{},(S=o.devDependencies)!=null?S:{},o.entry);return{visibleFiles:s.filter(v=>n[v]),activeFile:r,files:n,environment:o.environment}},Is=(e,t)=>{let o=(0,Ee.normalizePath)(t),s=(0,Ee.normalizePath)(e);if(s in o)return s;if(!e)return null;let r=null,n=0,a=[".js",".jsx",".ts",".tsx"];for(;!r&&n<a.length;){let p=`${s.split(".")[0]}${a[n]}`;o[p]!==void 0&&(r=p),n++}return r},bn=({files:e,template:t,customSetup:o})=>{if(!t){if(!o)return We.vanilla;if(!e||Object.keys(e).length===0)throw new Error("[sandpack-react]: without a template, you must pass at least one file");return M(u({},o),{files:_t(e)})}let s=We[t];if(!s)throw new Error(`[sandpack-react]: invalid template "${t}" provided`);return!o&&!e?s:{files:_t(u(u({},s.files),e)),dependencies:u(u({},s.dependencies),o==null?void 0:o.dependencies),devDependencies:u(u({},s.devDependencies),o==null?void 0:o.devDependencies),entry:(0,Ee.normalizePath)((o==null?void 0:o.entry)||s.entry),main:s.main,environment:(o==null?void 0:o.environment)||s.environment}},_t=e=>e?Object.keys(e).reduce((t,o)=>(typeof e[o]=="string"?t[o]={code:e[o]}:t[o]=e[o],t),{}):{};var mt=re.createContext(null),yn=3e4,$o=class extends re.PureComponent{constructor(t){super(t);this.timeoutHook=null;this.initializeSandpackIframeHook=null;this.handleMessage=t=>{this.timeoutHook&&clearTimeout(this.timeoutHook),t.type==="state"?this.setState({bundlerState:t.state}):t.type==="done"&&!t.compilatonError?this.setState({error:null}):t.type==="action"&&t.action==="show-error"?this.setState({error:(0,Ge.extractErrorDetails)(t)}):t.type==="action"&&t.action==="notification"&&t.notificationType==="error"&&this.setState({error:{message:t.title}})};this.registerReactDevTools=t=>{this.setState({reactDevTools:t})};this.updateCurrentFile=t=>{this.updateFile(this.state.activeFile,t)};this.updateFile=(t,o)=>{var r;let s=this.state.files;if(typeof t=="string"&&o){if(o===((r=this.state.files[t])==null?void 0:r.code))return;s=M(u({},s),{[t]:{code:o}})}else typeof t=="object"&&(s=u(u({},s),_t(t)));this.setState({files:(0,Ge.normalizePath)(s)},this.updateClients)};this.updateClients=()=>{var n,a,c,p;let{files:t,sandpackStatus:o}=this.state,s=(a=(n=this.props.options)==null?void 0:n.recompileMode)!=null?a:"delayed",r=(p=(c=this.props.options)==null?void 0:c.recompileDelay)!=null?p:500;o==="running"&&(s==="immediate"&&Object.values(this.clients).forEach(d=>{d.updatePreview({files:t})}),s==="delayed"&&(window.clearTimeout(this.debounceHook),this.debounceHook=window.setTimeout(()=>{Object.values(this.clients).forEach(d=>{d.updatePreview({files:this.state.files})})},r)))};this.createClient=(t,o)=>{var n,a,c,p,d,h,y,S,v;let s=new Ge.SandpackClient(t,{files:this.state.files,template:this.state.environment},{externalResources:(n=this.props.options)==null?void 0:n.externalResources,bundlerURL:(a=this.props.options)==null?void 0:a.bundlerURL,startRoute:(c=this.props.options)==null?void 0:c.startRoute,fileResolver:(p=this.props.options)==null?void 0:p.fileResolver,skipEval:(h=(d=this.props.options)==null?void 0:d.skipEval)!=null?h:!1,logLevel:(y=this.props.options)==null?void 0:y.logLevel,showOpenInCodeSandbox:!this.openInCSBRegistered.current,showErrorScreen:!this.errorScreenRegistered.current,showLoadingScreen:!this.loadingScreenRegistered.current,reactDevTools:this.state.reactDevTools,customNpmRegistries:(v=(S=this.props.customSetup)==null?void 0:S.npmRegistries)==null?void 0:v.map(g=>{var k;return(k=M(u({},g),{proxyEnabled:!1}))!=null?k:[]})});return typeof this.unsubscribe!="function"&&(this.unsubscribe=s.listen(this.handleMessage),this.timeoutHook=setTimeout(()=>{this.setState({sandpackStatus:"timeout"})},yn)),this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o]&&(Object.keys(this.queuedListeners[o]).forEach(g=>{let k=this.queuedListeners[o][g],$=s.listen(k);this.unsubscribeClientListeners[o][g]=$}),this.queuedListeners[o]={}),Object.entries(this.queuedListeners.global).forEach(([g,k])=>{let $=s.listen(k);this.unsubscribeClientListeners[o][g]=$}),s};this.runSandpack=()=>{Object.keys(this.preregisteredIframes).forEach(t=>{let o=this.preregisteredIframes[t];this.clients[t]=this.createClient(o,t)}),this.setState({sandpackStatus:"running"})};this.registerBundler=(t,o)=>{this.state.sandpackStatus==="running"?this.clients[o]=this.createClient(t,o):this.preregisteredIframes[o]=t};this.unregisterBundler=t=>{var r;let o=this.clients[t];o?(o.cleanup(),(r=o.iframe.contentWindow)==null||r.location.replace("about:blank"),delete this.clients[t]):delete this.preregisteredIframes[t],this.timeoutHook&&clearTimeout(this.timeoutHook),Object.values(this.unsubscribeClientListeners).forEach(n=>{Object.values(n).forEach(c=>c())}),this.setState({sandpackStatus:"idle"})};this.unregisterAllClients=()=>{Object.keys(this.clients).map(this.unregisterBundler),typeof this.unsubscribe=="function"&&(this.unsubscribe(),this.unsubscribe=void 0)};this.setActiveFile=t=>{this.setState({activeFile:t})};this.openFile=t=>{this.setState(({visibleFiles:o})=>{let s=o.includes(t)?o:[...o,t];return{activeFile:t,visibleFiles:s}})};this.closeFile=t=>{this.state.visibleFiles.length!==1&&this.setState(({visibleFiles:o,activeFile:s})=>{let r=o.indexOf(t),n=o.filter(a=>a!==t);return{activeFile:t===s?r===0?o[1]:o[r-1]:s,visibleFiles:n}})};this.deleteFile=t=>{this.setState(({visibleFiles:o,files:s})=>{let r=u({},s);return delete r[t],{visibleFiles:o.filter(n=>n!==t),files:r}},this.updateClients)};this.addFile=this.updateFile;this.dispatchMessage=(t,o)=>{if(this.state.sandpackStatus!=="running"){console.warn("[sandpack-react]: dispatch cannot be called while in idle mode");return}o?this.clients[o].dispatch(t):Object.values(this.clients).forEach(s=>{s.dispatch(t)})};this.addListener=(t,o)=>{if(o){if(this.clients[o])return this.clients[o].listen(t);{let s=it();return this.queuedListeners[o]=this.queuedListeners[o]||{},this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o][s]=t,()=>{this.queuedListeners[o][s]?delete this.queuedListeners[o][s]:this.unsubscribeClientListeners[o][s]&&(this.unsubscribeClientListeners[o][s](),delete this.unsubscribeClientListeners[o][s])}}}else{let s=it();this.queuedListeners.global[s]=t;let n=Object.values(this.clients).map(c=>c.listen(t));return()=>{n.forEach(c=>c())}}};this.resetFile=t=>{let{files:o}=pt({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState(s=>({files:M(u({},s.files),{[t]:o[t]})}),this.updateClients)};this.resetAllFiles=()=>{let{files:t}=pt({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState({files:t},this.updateClients)};this._getSandpackState=()=>{let{files:t,activeFile:o,visibleFiles:s,visibleFilesFromProps:r,startRoute:n,bundlerState:a,editorState:c,error:p,sandpackStatus:d,environment:h,initMode:y}=this.state;return{files:t,environment:h,visibleFiles:s,visibleFilesFromProps:r,activeFile:o,startRoute:n,error:p,bundlerState:a,status:d,editorState:c,initMode:y,clients:this.clients,dispatch:this.dispatchMessage,errorScreenRegisteredRef:this.errorScreenRegistered,lazyAnchorRef:this.lazyAnchorRef,listen:this.addListener,loadingScreenRegisteredRef:this.loadingScreenRegistered,openInCSBRegisteredRef:this.openInCSBRegistered,registerBundler:this.registerBundler,runSandpack:this.runSandpack,unregisterBundler:this.unregisterBundler,registerReactDevTools:this.registerReactDevTools,openFile:this.openFile,resetFile:this.resetFile,resetAllFiles:this.resetAllFiles,setActiveFile:this.setActiveFile,updateCurrentFile:this.updateCurrentFile,updateFile:this.updateFile,addFile:this.addFile,closeFile:this.closeFile,deleteFile:this.deleteFile}};var a,c,p,d;let{activeFile:o,visibleFiles:s,files:r,environment:n}=pt({template:t.template,files:t.files,customSetup:t.customSetup,options:t.options});this.state={files:r,environment:n,visibleFiles:s,visibleFilesFromProps:s,activeFile:o,startRoute:(a=this.props.options)==null?void 0:a.startRoute,bundlerState:void 0,error:null,sandpackStatus:((p=(c=this.props.options)==null?void 0:c.autorun)!=null?p:!0)?"initial":"idle",editorState:"pristine",initMode:((d=this.props.options)==null?void 0:d.initMode)||"lazy",reactDevTools:void 0},this.queuedListeners={global:{}},this.unsubscribeClientListeners={},this.preregisteredIframes={},this.clients={},this.lazyAnchorRef=re.createRef(),this.errorScreenRegistered=re.createRef(),this.openInCSBRegistered=re.createRef(),this.loadingScreenRegistered=re.createRef()}initializeSandpackIframe(){var s,r,n,a,c;if(!((r=(s=this.props.options)==null?void 0:s.autorun)!=null?r:!0))return;let o=(a=(n=this.props.options)==null?void 0:n.initModeObserverOptions)!=null?a:{rootMargin:"1000px 0px"};this.intersectionObserver&&this.lazyAnchorRef.current&&((c=this.intersectionObserver)==null||c.unobserve(this.lazyAnchorRef.current)),this.lazyAnchorRef.current&&this.state.initMode==="lazy"?(this.intersectionObserver=new IntersectionObserver(p=>{var d;p.some(h=>h.isIntersecting)&&(this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50),this.lazyAnchorRef.current&&((d=this.intersectionObserver)==null||d.unobserve(this.lazyAnchorRef.current)))},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.lazyAnchorRef.current&&this.state.initMode==="user-visible"?(this.intersectionObserver=new IntersectionObserver(p=>{p.some(d=>d.isIntersecting)?this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50):(this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),Object.keys(this.clients).map(this.unregisterBundler),this.unregisterAllClients())},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.initializeSandpackIframeHook=setTimeout(()=>this.runSandpack(),50)}componentDidMount(){this.initializeSandpackIframe()}componentDidUpdate(t){var c,p,d,h;((c=t.options)==null?void 0:c.initMode)!==((p=this.props.options)==null?void 0:p.initMode)&&((d=this.props.options)==null?void 0:d.initMode)&&this.setState({initMode:(h=this.props.options)==null?void 0:h.initMode},this.initializeSandpackIframe);let{activeFile:o,visibleFiles:s,files:r,environment:n}=pt({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});if(t.template!==this.props.template||!(0,dt.default)(t.options,this.props.options)||!(0,dt.default)(t.customSetup,this.props.customSetup)||!(0,dt.default)(t.files,this.props.files)){if(this.setState({activeFile:o,visibleFiles:s,visibleFilesFromProps:s,files:r,environment:n}),this.state.sandpackStatus!=="running")return;Object.values(this.clients).forEach(y=>y.updatePreview({files:r,template:n}))}let a=(0,dt.default)(r,this.state.files)?"pristine":"dirty";a!==this.state.editorState&&this.setState({editorState:a})}componentWillUnmount(){typeof this.unsubscribe=="function"&&this.unsubscribe(),this.timeoutHook&&clearTimeout(this.timeoutHook),this.debounceHook&&clearTimeout(this.debounceHook),this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),this.intersectionObserver&&this.intersectionObserver.disconnect()}render(){var n;let{children:t,theme:o,className:s,style:r}=this.props;return re.createElement(mt.Provider,{value:this._getSandpackState()},re.createElement(Os.ClasserProvider,{classes:(n=this.props.options)==null?void 0:n.classes},re.createElement(No,{className:s,style:r,theme:o},t)))}},Mo=$o,Sn=mt.Consumer;function T(){let e=Ds.useContext(mt);if(e===null)throw new Error('[sandpack-react]: "useSandpack" must be wrapped by a "SandpackProvider"');let r=e,{dispatch:t,listen:o}=r,s=N(r,["dispatch","listen"]);return{sandpack:u({},s),dispatch:t,listen:o}}var ut=()=>{var t,o,s;let{sandpack:e}=T();return{code:(t=e.files[e.activeFile])==null?void 0:t.code,readOnly:(s=(o=e.files[e.activeFile])==null?void 0:o.readOnly)!=null?s:!1,updateCode:e.updateCurrentFile}};var Bs=f(require("@code-hike/classer")),Ye=f(require("react"));var me=m({svg:{margin:"auto"}}),F=m({appearance:"none",border:"0",outline:"none",display:"flex",alignItems:"center",fontSize:"inherit",fontFamily:"inherit",backgroundColor:"transparent",transition:"color $default, background $default",cursor:"pointer",color:"$colors$clickable","&:disabled":{color:"$colors$disabled"},"&:hover:not(:disabled,[data-active='true'])":{color:"$colors$hover"},'&[data-active="true"]':{color:"$colors$accent"},svg:{minWidth:"$space$4",width:"$space$4",height:"$space$4"},[`&.${me}`]:{padding:"$space$1",width:"$space$7",height:"$space$7",display:"flex"}}),te=m({backgroundColor:"$colors$surface2",borderRadius:"99999px",'&[data-active="true"]':{color:"$colors$surface1",background:"$colors$accent"},"&:hover:not(:disabled,[data-active='true'])":{backgroundColor:"$colors$surface3"}}),Hs=m({padding:0}),vn=Mt({"0%":{opacity:0,transform:"translateY(4px)"},"100%":{opacity:1,transform:"translateY(0)"}}),ft=m({position:"absolute",bottom:"0",left:"0",right:"0",top:"0",margin:"0",overflow:"auto",height:"100%",zIndex:"$top"}),jt=m({padding:"$space$4",whiteSpace:"pre-wrap",fontFamily:"$font$mono",backgroundColor:"$colors$errorSurface"}),Ze=m({animation:`${vn} 150ms ease`,color:"$colors$error"});var kn=m({borderBottom:"1px solid $colors$surface2",background:"$colors$surface1"}),Cn=m({padding:"0 $space$2",overflow:"auto",display:"flex",flexWrap:"nowrap",alignItems:"stretch",minHeight:"40px",marginBottom:"-1px"}),_s=m({padding:"0 $space$1 0 $space$1",borderRadius:"$border$radius",marginLeft:"$space$1",width:"$space$5",visibility:"hidden",svg:{width:"$space$3",height:"$space$3",display:"block",position:"relative",top:1}}),js=m({padding:"0 $space$2",height:"$layout$headerHeight",whiteSpace:"nowrap","&:focus":{outline:"none"},[`&:hover > .${_s}`]:{visibility:"unset"}}),ht=s=>{var r=s,{closableTabs:e,className:t}=r,o=N(r,["closableTabs","className"]);let{sandpack:n}=T(),a=(0,Bs.useClasser)(b),{activeFile:c,visibleFiles:p,setActiveFile:d}=n,h=S=>{S.stopPropagation();let v=S.target.closest("[data-active]"),g=v==null?void 0:v.getAttribute("title");!g||n.closeFile(g)},y=S=>{let v=Xe(S),g=p.reduce((k,$)=>($===S||Xe($)===v&&k.push($),k),[]);return g.length===0?v:Es(S,g)};return Ye.createElement("div",u({className:l(a("tabs"),kn,t),translate:"no"},o),Ye.createElement("div",{"aria-label":"Select active file",className:l(a("tabs-scrollable-container"),Cn),role:"tablist"},p.map(S=>Ye.createElement("button",{key:S,"aria-selected":S===c,className:l(a("tab-button"),F,js),"data-active":S===c,onClick:()=>d(S),role:"tab",title:S,type:"button"},y(S),e&&p.length>1&&Ye.createElement("span",{className:l(a("close-button"),_s),onClick:h},Ye.createElement(Ro,null))))))};var Us=f(require("@code-hike/classer")),Lo=f(require("react"));var xn=m({position:"absolute",bottom:"$space$2",right:"$space$2",paddingRight:"$space$3"}),gt=s=>{var r=s,{className:e,onClick:t}=r,o=N(r,["className","onClick"]);let n=(0,Us.useClasser)(b),{sandpack:a}=T();return Lo.createElement("button",u({className:l(n("button"),F,te,xn,e),onClick:c=>{a.runSandpack(),t==null||t(c)},type:"button"},o),Lo.createElement(rt,null),"Run")};var zs=f(require("@code-hike/classer")),Vs=f(require("react"));var Te=m({display:"flex",flexDirection:"column",width:"100%",position:"relative",backgroundColor:"$colors$surface1",transition:"flex $transitions$default",gap:1,[`&:has(.${b}-stack)`]:{backgroundColor:"$colors$surface2"}}),ne=o=>{var s=o,{className:e}=s,t=N(s,["className"]);let r=(0,zs.useClasser)(b);return Vs.createElement("div",u({className:l(r("stack"),Te,e)},t))};var ir=f(require("@code-hike/classer")),Xt=f(require("@codemirror/closebrackets")),Ne=f(require("@codemirror/commands")),cr=f(require("@codemirror/comment")),lr=f(require("@codemirror/gutter")),pr=f(require("@codemirror/highlight")),Wt=f(require("@codemirror/history")),dr=f(require("@codemirror/matchbrackets")),$e=f(require("@codemirror/state")),Io=f(require("@codemirror/state")),fe=f(require("@codemirror/view")),mr=f(require("@react-hook/intersection-observer")),L=f(require("react"));var Xs=f(require("react"));var Oe=()=>{let{theme:e,id:t,mode:o}=Xs.useContext(lt);return{theme:e,themeId:t,themeMode:o}};var wo=(e,t)=>{if(e.length!==t.length)return!1;let o=!0;for(let s=0;s<e.length;s++)if(e[s]!==t[s]){o=!1;break}return o};var De=f(require("@codemirror/view"));var P=f(require("@codemirror/highlight")),Ws=f(require("@codemirror/lang-css")),Gs=f(require("@codemirror/lang-html")),Fo=f(require("@codemirror/lang-javascript")),Zs=f(require("@codemirror/view")),Ys=f(require("react"));var bt=(e,{line:t,column:o})=>e.line(t).from+(o!=null?o:0)-1,Js=()=>Zs.EditorView.theme({"&":{backgroundColor:`var(--${b}-colors-surface1)`,color:`var(--${b}-syntax-color-plain)`,height:"100%"},".cm-matchingBracket, .cm-nonmatchingBracket, &.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{color:"inherit",backgroundColor:"rgba(128,128,128,.25)",backgroundBlendMode:"difference"},"&.cm-editor.cm-focused":{outline:"none"},".cm-activeLine":{backgroundColor:`var(--${b}-colors-surface3)`,borderRadius:`var(--${b}-border-radius)`},".cm-errorLine":{backgroundColor:`var(--${b}-colors-errorSurface)`,borderRadius:`var(--${b}-border-radius)`},".cm-content":{caretColor:`var(--${b}-colors-accent)`,padding:`0 var(--${b}-space-4)`},".cm-scroller":{fontFamily:`var(--${b}-font-mono)`,lineHeight:`var(--${b}-font-lineHeight)`},".cm-gutters":{backgroundColor:`var(--${b}-colors-surface1)`,color:`var(--${b}-colors-disabled)`,border:"none",paddingLeft:`var(--${b}-space-1)`},".cm-gutter.cm-lineNumbers":{fontSize:".6em"},".cm-lineNumbers .cm-gutterElement":{lineHeight:`var(--${b}-font-lineHeight)`,minWidth:`var(--${b}-space-5)`},".cm-content .cm-line":{paddingLeft:`var(--${b}-space-1)`},".cm-content.cm-readonly .cm-line":{paddingLeft:0}}),ue=e=>`${b}-syntax-${e}`,qs=()=>["string","plain","comment","keyword","definition","punctuation","property","tag","static"].reduce((t,o)=>M(u({},t),{[`.${ue(o)}`]:{color:`$syntax$color$${o}`,fontStyle:`$syntax$fontStyle$${o}`}}),{}),Ks=e=>P.HighlightStyle.define([{tag:P.tags.link,textDecoration:"underline"},{tag:P.tags.emphasis,fontStyle:"italic"},{tag:P.tags.strong,fontWeight:"bold"},{tag:P.tags.keyword,class:ue("keyword")},{tag:[P.tags.atom,P.tags.number,P.tags.bool],class:ue("static")},{tag:P.tags.tagName,class:ue("tag")},{tag:P.tags.variableName,class:ue("plain")},{tag:P.tags.function(P.tags.variableName),class:ue("definition")},{tag:P.tags.definition(P.tags.function(P.tags.variableName)),class:ue("definition")},{tag:P.tags.propertyName,class:ue("property")},{tag:[P.tags.literal,P.tags.inserted],class:ue(e.syntax.string?"string":"static")},{tag:P.tags.punctuation,class:ue("punctuation")},{tag:[P.tags.comment,P.tags.quote],class:ue("comment")}]),Qs=(e,t,o)=>{if(!e&&!t)return"javascript";let s=t;if(!s&&e){let r=e.lastIndexOf(".");s=e.slice(r+1)}for(let r of o)if(s===r.name||r.extensions.includes(s||""))return r.name;switch(s){case"ts":case"tsx":return"typescript";case"html":case"svelte":case"vue":return"html";case"css":case"less":case"scss":return"css";case"js":case"jsx":case"json":default:return"javascript"}},er=(e,t)=>{let o={javascript:(0,Fo.javascript)({jsx:!0,typescript:!1}),typescript:(0,Fo.javascript)({jsx:!0,typescript:!0}),html:(0,Gs.html)(),css:(0,Ws.css)()};for(let s of t)if(e===s.name)return s.language;return o[e]},Ut=(...e)=>Ys.useCallback(t=>e.forEach(o=>{if(!!o){if(typeof o=="function")return o(t);o.current=t}}),e);function tr(e){return De.ViewPlugin.fromClass(class{constructor(t){this.decorations=this.getDecoration(t)}update(t){}getDecoration(t){if(!e)return De.Decoration.none;let o=e.map(s=>{var c,p,d;let r=De.Decoration.line({attributes:{class:(c=s.className)!=null?c:""}}),n=De.Decoration.mark({class:(p=s.className)!=null?p:"",attributes:(d=s.elementAttributes)!=null?d:void 0}),a=bt(t.state.doc,{line:s.line,column:s.startColumn})+1;if(s.startColumn&&s.endColumn){let h=bt(t.state.doc,{line:s.line,column:s.endColumn})+1;return n.range(a,h)}return r.range(a)});return De.Decoration.set(o)}},{decorations:t=>t.decorations})}var He=f(require("@codemirror/view"));function or(){return En}var Rn=He.Decoration.line({attributes:{class:"cm-errorLine"}}),En=He.ViewPlugin.fromClass(class{constructor(){this.decorations=He.Decoration.none}update(e){e.transactions.forEach(t=>{let o=t.annotation("show-error");if(o!==void 0){let s=bt(e.view.state.doc,{line:o})+1;this.decorations=He.Decoration.set([Rn.range(s)])}else t.annotation("remove-errors")&&(this.decorations=He.Decoration.none)})}},{decorations:e=>e.decorations});var zt=m({margin:"0",display:"block",fontFamily:"$font$mono",fontSize:"$font$size",color:"$syntax$color$plain",lineHeight:"$font$lineHeight"}),Ao=m(qs()),Vt=m({flex:1,position:"relative",overflow:"auto",background:"$colors$surface1",".cm-scroller":{padding:"$space$4 0"},[`.${zt}`]:{padding:"$space$4 0"}}),Po=m({margin:"0",outline:"none",height:"100%"}),sr=m({fontFamily:"$font$mono",fontSize:"0.8em",position:"absolute",right:"$space$2",bottom:"$space$2",zIndex:"$top",color:"$colors$clickable",backgroundColor:"$colors$surface2",borderRadius:"99999px",padding:"calc($space$1 / 2) $space$2",[`& + .${F}`]:{right:"calc($space$11 * 2)"}});var rr=f(require("@codemirror/highlight")),nr=f(require("react")),ar=({langSupport:e,highlightTheme:t,code:o=""})=>{let s=e.language.parser.parse(o),r=0,n=[],a=(c,p)=>{if(c>r){let d=o.slice(r,c);n.push(p?(0,nr.createElement)("span",{children:d,className:p,key:`${c}${r}`}):d),r=c}};return(0,rr.highlightTree)(s,t.match,(c,p,d)=>{a(c,""),a(p,d)}),r<o.length&&n.push(` ++`},"/package.json":{code:JSON.stringify({dependencies:{"core-js":"^3.6.5",vue:"^3.0.0-0","@vue/cli-plugin-babel":"4.5.0"},main:"/src/main.js"})}},main:"/src/App.vue",environment:"vue-cli"};var We={react:wt,"react-ts":Ft,vue:Ht,vanilla:Ot,"vanilla-ts":Dt,vue3:Bt,angular:Lt,svelte:Pt,solid:At,"test-ts":It};var pt=e=>{var c,p,d,h,y,S;let t=(0,Ee.normalizePath)(e.files),o=bn({template:e.template,customSetup:e.customSetup,files:t}),s=(0,Ee.normalizePath)((p=(c=e.options)==null?void 0:c.visibleFiles)!=null?p:[]),r=((d=e.options)==null?void 0:d.activeFile)?Is((h=e.options)==null?void 0:h.activeFile,t||{}):void 0;s.length===0&&t&&Object.keys(t).forEach(v=>{let g=t[v];if(typeof g=="string"){s.push(v);return}!r&&g.active&&(r=v,g.hidden===!0&&s.push(v)),g.hidden||s.push(v)}),s.length===0&&(s=[o.main]),o.files[o.entry]||(o.entry=Is(o.entry,o.files)),!r&&o.main&&(r=o.main),(!r||!o.files[r])&&(r=s[0]),s.includes(r)||s.push(r);let n=(0,Ee.addPackageJSONIfNeeded)(o.files,(y=o.dependencies)!=null?y:{},(S=o.devDependencies)!=null?S:{},o.entry);return{visibleFiles:s.filter(v=>n[v]),activeFile:r,files:n,environment:o.environment}},Is=(e,t)=>{let o=(0,Ee.normalizePath)(t),s=(0,Ee.normalizePath)(e);if(s in o)return s;if(!e)return null;let r=null,n=0,a=[".js",".jsx",".ts",".tsx"];for(;!r&&n<a.length;){let p=`${s.split(".")[0]}${a[n]}`;o[p]!==void 0&&(r=p),n++}return r},bn=({files:e,template:t,customSetup:o})=>{if(!t){if(!o)return We.vanilla;if(!e||Object.keys(e).length===0)throw new Error("[sandpack-react]: without a template, you must pass at least one file");return M(u({},o),{files:_t(e)})}let s=We[t];if(!s)throw new Error(`[sandpack-react]: invalid template "${t}" provided`);return!o&&!e?s:{files:_t(u(u({},s.files),e)),dependencies:u(u({},s.dependencies),o==null?void 0:o.dependencies),devDependencies:u(u({},s.devDependencies),o==null?void 0:o.devDependencies),entry:(0,Ee.normalizePath)((o==null?void 0:o.entry)||s.entry),main:s.main,environment:(o==null?void 0:o.environment)||s.environment}},_t=e=>e?Object.keys(e).reduce((t,o)=>(typeof e[o]=="string"?t[o]={code:e[o]}:t[o]=e[o],t),{}):{};var mt=re.createContext(null),yn=3e4,$o=class extends re.PureComponent{constructor(t){super(t);this.timeoutHook=null;this.initializeSandpackIframeHook=null;this.handleMessage=t=>{this.timeoutHook&&clearTimeout(this.timeoutHook),t.type==="state"?this.setState({bundlerState:t.state}):t.type==="done"&&!t.compilatonError?this.setState({error:null}):t.type==="action"&&t.action==="show-error"?this.setState({error:(0,Ge.extractErrorDetails)(t)}):t.type==="action"&&t.action==="notification"&&t.notificationType==="error"&&this.setState({error:{message:t.title}})};this.registerReactDevTools=t=>{this.setState({reactDevTools:t})};this.updateCurrentFile=t=>{this.updateFile(this.state.activeFile,t)};this.updateFile=(t,o)=>{var r;let s=this.state.files;if(typeof t=="string"&&o){if(o===((r=this.state.files[t])==null?void 0:r.code))return;s=M(u({},s),{[t]:{code:o}})}else typeof t=="object"&&(s=u(u({},s),_t(t)));this.setState({files:(0,Ge.normalizePath)(s)},this.updateClients)};this.updateClients=()=>{var n,a,c,p;let{files:t,sandpackStatus:o}=this.state,s=(a=(n=this.props.options)==null?void 0:n.recompileMode)!=null?a:"delayed",r=(p=(c=this.props.options)==null?void 0:c.recompileDelay)!=null?p:500;o==="running"&&(s==="immediate"&&Object.values(this.clients).forEach(d=>{d.updatePreview({files:t})}),s==="delayed"&&(window.clearTimeout(this.debounceHook),this.debounceHook=window.setTimeout(()=>{Object.values(this.clients).forEach(d=>{d.updatePreview({files:this.state.files})})},r)))};this.createClient=(t,o)=>{var n,a,c,p,d,h,y,S,v;let s=new Ge.SandpackClient(t,{files:this.state.files,template:this.state.environment},{externalResources:(n=this.props.options)==null?void 0:n.externalResources,bundlerURL:(a=this.props.options)==null?void 0:a.bundlerURL,startRoute:(c=this.props.options)==null?void 0:c.startRoute,fileResolver:(p=this.props.options)==null?void 0:p.fileResolver,skipEval:(h=(d=this.props.options)==null?void 0:d.skipEval)!=null?h:!1,logLevel:(y=this.props.options)==null?void 0:y.logLevel,showOpenInCodeSandbox:!this.openInCSBRegistered.current,showErrorScreen:!this.errorScreenRegistered.current,showLoadingScreen:!this.loadingScreenRegistered.current,reactDevTools:this.state.reactDevTools,customNpmRegistries:(v=(S=this.props.customSetup)==null?void 0:S.npmRegistries)==null?void 0:v.map(g=>{var k;return(k=M(u({},g),{proxyEnabled:!1}))!=null?k:[]})});return typeof this.unsubscribe!="function"&&(this.unsubscribe=s.listen(this.handleMessage),this.timeoutHook=setTimeout(()=>{this.setState({sandpackStatus:"timeout"})},yn)),this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o]&&(Object.keys(this.queuedListeners[o]).forEach(g=>{let k=this.queuedListeners[o][g],$=s.listen(k);this.unsubscribeClientListeners[o][g]=$}),this.queuedListeners[o]={}),Object.entries(this.queuedListeners.global).forEach(([g,k])=>{let $=s.listen(k);this.unsubscribeClientListeners[o][g]=$}),s};this.runSandpack=()=>{Object.keys(this.preregisteredIframes).forEach(t=>{let o=this.preregisteredIframes[t];this.clients[t]=this.createClient(o,t)}),this.setState({sandpackStatus:"running"})};this.registerBundler=(t,o)=>{this.state.sandpackStatus==="running"?this.clients[o]=this.createClient(t,o):this.preregisteredIframes[o]=t};this.unregisterBundler=t=>{var r;let o=this.clients[t];o?(o.cleanup(),(r=o.iframe.contentWindow)==null||r.location.replace("about:blank"),delete this.clients[t]):delete this.preregisteredIframes[t],this.timeoutHook&&clearTimeout(this.timeoutHook),Object.values(this.unsubscribeClientListeners).forEach(n=>{Object.values(n).forEach(c=>c())}),this.setState({sandpackStatus:"idle"})};this.unregisterAllClients=()=>{Object.keys(this.clients).map(this.unregisterBundler),typeof this.unsubscribe=="function"&&(this.unsubscribe(),this.unsubscribe=void 0)};this.setActiveFile=t=>{this.setState({activeFile:t})};this.openFile=t=>{this.setState(({visibleFiles:o})=>{let s=o.includes(t)?o:[...o,t];return{activeFile:t,visibleFiles:s}})};this.closeFile=t=>{this.state.visibleFiles.length!==1&&this.setState(({visibleFiles:o,activeFile:s})=>{let r=o.indexOf(t),n=o.filter(a=>a!==t);return{activeFile:t===s?r===0?o[1]:o[r-1]:s,visibleFiles:n}})};this.deleteFile=t=>{this.setState(({visibleFiles:o,files:s})=>{let r=u({},s);return delete r[t],{visibleFiles:o.filter(n=>n!==t),files:r}},this.updateClients)};this.addFile=this.updateFile;this.dispatchMessage=(t,o)=>{if(this.state.sandpackStatus!=="running"){console.warn("[sandpack-react]: dispatch cannot be called while in idle mode");return}o?this.clients[o].dispatch(t):Object.values(this.clients).forEach(s=>{s.dispatch(t)})};this.addListener=(t,o)=>{if(o){if(this.clients[o])return this.clients[o].listen(t);{let s=it();return this.queuedListeners[o]=this.queuedListeners[o]||{},this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o][s]=t,()=>{this.queuedListeners[o][s]?delete this.queuedListeners[o][s]:this.unsubscribeClientListeners[o][s]&&(this.unsubscribeClientListeners[o][s](),delete this.unsubscribeClientListeners[o][s])}}}else{let s=it();this.queuedListeners.global[s]=t;let n=Object.values(this.clients).map(c=>c.listen(t));return()=>{n.forEach(c=>c())}}};this.resetFile=t=>{let{files:o}=pt({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState(s=>({files:M(u({},s.files),{[t]:o[t]})}),this.updateClients)};this.resetAllFiles=()=>{let{files:t}=pt({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState({files:t},this.updateClients)};this._getSandpackState=()=>{let{files:t,activeFile:o,visibleFiles:s,visibleFilesFromProps:r,startRoute:n,bundlerState:a,editorState:c,error:p,sandpackStatus:d,environment:h,initMode:y}=this.state;return{files:t,environment:h,visibleFiles:s,visibleFilesFromProps:r,activeFile:o,startRoute:n,error:p,bundlerState:a,status:d,editorState:c,initMode:y,clients:this.clients,dispatch:this.dispatchMessage,errorScreenRegisteredRef:this.errorScreenRegistered,lazyAnchorRef:this.lazyAnchorRef,listen:this.addListener,loadingScreenRegisteredRef:this.loadingScreenRegistered,openInCSBRegisteredRef:this.openInCSBRegistered,registerBundler:this.registerBundler,runSandpack:this.runSandpack,unregisterBundler:this.unregisterBundler,registerReactDevTools:this.registerReactDevTools,openFile:this.openFile,resetFile:this.resetFile,resetAllFiles:this.resetAllFiles,setActiveFile:this.setActiveFile,updateCurrentFile:this.updateCurrentFile,updateFile:this.updateFile,addFile:this.addFile,closeFile:this.closeFile,deleteFile:this.deleteFile}};var a,c,p,d;let{activeFile:o,visibleFiles:s,files:r,environment:n}=pt({template:t.template,files:t.files,customSetup:t.customSetup,options:t.options});this.state={files:r,environment:n,visibleFiles:s,visibleFilesFromProps:s,activeFile:o,startRoute:(a=this.props.options)==null?void 0:a.startRoute,bundlerState:void 0,error:null,sandpackStatus:((p=(c=this.props.options)==null?void 0:c.autorun)!=null?p:!0)?"initial":"idle",editorState:"pristine",initMode:((d=this.props.options)==null?void 0:d.initMode)||"lazy",reactDevTools:void 0},this.queuedListeners={global:{}},this.unsubscribeClientListeners={},this.preregisteredIframes={},this.clients={},this.lazyAnchorRef=re.createRef(),this.errorScreenRegistered=re.createRef(),this.openInCSBRegistered=re.createRef(),this.loadingScreenRegistered=re.createRef()}initializeSandpackIframe(){var s,r,n,a,c;if(!((r=(s=this.props.options)==null?void 0:s.autorun)!=null?r:!0))return;let o=(a=(n=this.props.options)==null?void 0:n.initModeObserverOptions)!=null?a:{rootMargin:"1000px 0px"};this.intersectionObserver&&this.lazyAnchorRef.current&&((c=this.intersectionObserver)==null||c.unobserve(this.lazyAnchorRef.current)),this.lazyAnchorRef.current&&this.state.initMode==="lazy"?(this.intersectionObserver=new IntersectionObserver(p=>{var d;p.some(h=>h.isIntersecting)&&(this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50),this.lazyAnchorRef.current&&((d=this.intersectionObserver)==null||d.unobserve(this.lazyAnchorRef.current)))},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.lazyAnchorRef.current&&this.state.initMode==="user-visible"?(this.intersectionObserver=new IntersectionObserver(p=>{p.some(d=>d.isIntersecting)?this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50):(this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),Object.keys(this.clients).map(this.unregisterBundler),this.unregisterAllClients())},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.initializeSandpackIframeHook=setTimeout(()=>this.runSandpack(),50)}componentDidMount(){this.initializeSandpackIframe()}componentDidUpdate(t){var c,p,d,h;((c=t.options)==null?void 0:c.initMode)!==((p=this.props.options)==null?void 0:p.initMode)&&((d=this.props.options)==null?void 0:d.initMode)&&this.setState({initMode:(h=this.props.options)==null?void 0:h.initMode},this.initializeSandpackIframe);let{activeFile:o,visibleFiles:s,files:r,environment:n}=pt({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});if(t.template!==this.props.template||!(0,dt.default)(t.options,this.props.options)||!(0,dt.default)(t.customSetup,this.props.customSetup)||!(0,dt.default)(t.files,this.props.files)){if(this.setState({activeFile:o,visibleFiles:s,visibleFilesFromProps:s,files:r,environment:n}),this.state.sandpackStatus!=="running")return;Object.values(this.clients).forEach(y=>y.updatePreview({files:r,template:n}))}let a=(0,dt.default)(r,this.state.files)?"pristine":"dirty";a!==this.state.editorState&&this.setState({editorState:a})}componentWillUnmount(){typeof this.unsubscribe=="function"&&this.unsubscribe(),this.timeoutHook&&clearTimeout(this.timeoutHook),this.debounceHook&&clearTimeout(this.debounceHook),this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),this.intersectionObserver&&this.intersectionObserver.disconnect()}render(){var n;let{children:t,theme:o,className:s,style:r}=this.props;return re.createElement(mt.Provider,{value:this._getSandpackState()},re.createElement(Os.ClasserProvider,{classes:(n=this.props.options)==null?void 0:n.classes},re.createElement(No,{className:s,style:r,theme:o},t)))}},Mo=$o,Sn=mt.Consumer;function T(){let e=Ds.useContext(mt);if(e===null)throw new Error('[sandpack-react]: "useSandpack" must be wrapped by a "SandpackProvider"');let r=e,{dispatch:t,listen:o}=r,s=N(r,["dispatch","listen"]);return{sandpack:u({},s),dispatch:t,listen:o}}var ut=()=>{var t,o,s;let{sandpack:e}=T();return{code:(t=e.files[e.activeFile])==null?void 0:t.code,readOnly:(s=(o=e.files[e.activeFile])==null?void 0:o.readOnly)!=null?s:!1,updateCode:e.updateCurrentFile}};var Bs=f(require("@code-hike/classer")),Ye=f(require("react"));var me=m({svg:{margin:"auto"}}),F=m({appearance:"none",border:"0",outline:"none",display:"flex",alignItems:"center",fontSize:"inherit",fontFamily:"inherit",backgroundColor:"transparent",transition:"color $default, background $default",cursor:"pointer",color:"$colors$clickable","&:disabled":{color:"$colors$disabled"},"&:hover:not(:disabled,[data-active='true'])":{color:"$colors$hover"},'&[data-active="true"]':{color:"$colors$accent"},svg:{minWidth:"$space$4",width:"$space$4",height:"$space$4"},[`&.${me}`]:{padding:"$space$1",width:"$space$7",height:"$space$7",display:"flex"}}),te=m({backgroundColor:"$colors$surface2",borderRadius:"99999px",'&[data-active="true"]':{color:"$colors$surface1",background:"$colors$accent"},"&:hover:not(:disabled,[data-active='true'])":{backgroundColor:"$colors$surface3"}}),Hs=m({padding:0}),vn=Mt({"0%":{opacity:0,transform:"translateY(4px)"},"100%":{opacity:1,transform:"translateY(0)"}}),ft=m({position:"absolute",bottom:"0",left:"0",right:"0",top:"0",margin:"0",overflow:"auto",height:"100%",zIndex:"$top"}),jt=m({padding:"$space$4",whiteSpace:"pre-wrap",fontFamily:"$font$mono",backgroundColor:"$colors$errorSurface"}),Ze=m({animation:`${vn} 150ms ease`,color:"$colors$error"});var kn=m({borderBottom:"1px solid $colors$surface2",background:"$colors$surface1"}),Cn=m({padding:"0 $space$2",overflow:"auto",display:"flex",flexWrap:"nowrap",alignItems:"stretch",minHeight:"40px",marginBottom:"-1px"}),_s=m({padding:"0 $space$1 0 $space$1",borderRadius:"$border$radius",marginLeft:"$space$1",width:"$space$5",visibility:"hidden",svg:{width:"$space$3",height:"$space$3",display:"block",position:"relative",top:1}}),js=m({padding:"0 $space$2",height:"$layout$headerHeight",whiteSpace:"nowrap","&:focus":{outline:"none"},[`&:hover > .${_s}`]:{visibility:"unset"}}),ht=s=>{var r=s,{closableTabs:e,className:t}=r,o=N(r,["closableTabs","className"]);let{sandpack:n}=T(),a=(0,Bs.useClasser)(b),{activeFile:c,visibleFiles:p,setActiveFile:d}=n,h=S=>{S.stopPropagation();let v=S.target.closest("[data-active]"),g=v==null?void 0:v.getAttribute("title");!g||n.closeFile(g)},y=S=>{let v=Xe(S),g=p.reduce((k,$)=>($===S||Xe($)===v&&k.push($),k),[]);return g.length===0?v:Es(S,g)};return Ye.createElement("div",u({className:l(a("tabs"),kn,t),translate:"no"},o),Ye.createElement("div",{"aria-label":"Select active file",className:l(a("tabs-scrollable-container"),Cn),role:"tablist"},p.map(S=>Ye.createElement("button",{key:S,"aria-selected":S===c,className:l(a("tab-button"),F,js),"data-active":S===c,onClick:()=>d(S),role:"tab",title:S,type:"button"},y(S),e&&p.length>1&&Ye.createElement("span",{className:l(a("close-button"),_s),onClick:h},Ye.createElement(Ro,null))))))};var Us=f(require("@code-hike/classer")),Lo=f(require("react"));var xn=m({position:"absolute",bottom:"$space$2",right:"$space$2",paddingRight:"$space$3"}),gt=s=>{var r=s,{className:e,onClick:t}=r,o=N(r,["className","onClick"]);let n=(0,Us.useClasser)(b),{sandpack:a}=T();return Lo.createElement("button",u({className:l(n("button"),F,te,xn,e),onClick:c=>{a.runSandpack(),t==null||t(c)},type:"button"},o),Lo.createElement(rt,null),"Run")};var zs=f(require("@code-hike/classer")),Vs=f(require("react"));var Te=m({display:"flex",flexDirection:"column",width:"100%",position:"relative",backgroundColor:"$colors$surface1",transition:"flex $transitions$default",gap:1,[`&:has(.${b}-stack)`]:{backgroundColor:"$colors$surface2"}}),ne=o=>{var s=o,{className:e}=s,t=N(s,["className"]);let r=(0,zs.useClasser)(b);return Vs.createElement("div",u({className:l(r("stack"),Te,e)},t))};var ir=f(require("@code-hike/classer")),Xt=f(require("@codemirror/closebrackets")),Ne=f(require("@codemirror/commands")),cr=f(require("@codemirror/comment")),lr=f(require("@codemirror/gutter")),pr=f(require("@codemirror/highlight")),Wt=f(require("@codemirror/history")),dr=f(require("@codemirror/matchbrackets")),$e=f(require("@codemirror/state")),Io=f(require("@codemirror/state")),fe=f(require("@codemirror/view")),mr=f(require("@react-hook/intersection-observer")),L=f(require("react"));var Xs=f(require("react"));var Oe=()=>{let{theme:e,id:t,mode:o}=Xs.useContext(lt);return{theme:e,themeId:t,themeMode:o}};var wo=(e,t)=>{if(e.length!==t.length)return!1;let o=!0;for(let s=0;s<e.length;s++)if(e[s]!==t[s]){o=!1;break}return o};var De=f(require("@codemirror/view"));var P=f(require("@codemirror/highlight")),Ws=f(require("@codemirror/lang-css")),Gs=f(require("@codemirror/lang-html")),Fo=f(require("@codemirror/lang-javascript")),Zs=f(require("@codemirror/view")),Ys=f(require("react"));var bt=(e,{line:t,column:o})=>e.line(t).from+(o!=null?o:0)-1,Js=()=>Zs.EditorView.theme({"&":{backgroundColor:`var(--${b}-colors-surface1)`,color:`var(--${b}-syntax-color-plain)`,height:"100%"},".cm-matchingBracket, .cm-nonmatchingBracket, &.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{color:"inherit",backgroundColor:"rgba(128,128,128,.25)",backgroundBlendMode:"difference"},"&.cm-editor.cm-focused":{outline:"none"},".cm-activeLine":{backgroundColor:`var(--${b}-colors-surface3)`,borderRadius:`var(--${b}-border-radius)`},".cm-errorLine":{backgroundColor:`var(--${b}-colors-errorSurface)`,borderRadius:`var(--${b}-border-radius)`},".cm-content":{caretColor:`var(--${b}-colors-accent)`,padding:`0 var(--${b}-space-4)`},".cm-scroller":{fontFamily:`var(--${b}-font-mono)`,lineHeight:`var(--${b}-font-lineHeight)`},".cm-gutters":{backgroundColor:`var(--${b}-colors-surface1)`,color:`var(--${b}-colors-disabled)`,border:"none",paddingLeft:`var(--${b}-space-1)`},".cm-gutter.cm-lineNumbers":{fontSize:".6em"},".cm-lineNumbers .cm-gutterElement":{lineHeight:`var(--${b}-font-lineHeight)`,minWidth:`var(--${b}-space-5)`},".cm-content .cm-line":{paddingLeft:`var(--${b}-space-1)`},".cm-content.cm-readonly .cm-line":{paddingLeft:0}}),ue=e=>`${b}-syntax-${e}`,qs=()=>["string","plain","comment","keyword","definition","punctuation","property","tag","static"].reduce((t,o)=>M(u({},t),{[`.${ue(o)}`]:{color:`$syntax$color$${o}`,fontStyle:`$syntax$fontStyle$${o}`}}),{}),Ks=e=>P.HighlightStyle.define([{tag:P.tags.link,textDecoration:"underline"},{tag:P.tags.emphasis,fontStyle:"italic"},{tag:P.tags.strong,fontWeight:"bold"},{tag:P.tags.keyword,class:ue("keyword")},{tag:[P.tags.atom,P.tags.number,P.tags.bool],class:ue("static")},{tag:P.tags.standard(P.tags.tagName),class:ue("tag")},{tag:P.tags.variableName,class:ue("plain")},{tag:P.tags.function(P.tags.variableName),class:ue("definition")},{tag:[P.tags.definition(P.tags.function(P.tags.variableName)),P.tags.tagName],class:ue("definition")},{tag:P.tags.propertyName,class:ue("property")},{tag:[P.tags.literal,P.tags.inserted],class:ue(e.syntax.string?"string":"static")},{tag:P.tags.punctuation,class:ue("punctuation")},{tag:[P.tags.comment,P.tags.quote],class:ue("comment")}]),Qs=(e,t,o)=>{if(!e&&!t)return"javascript";let s=t;if(!s&&e){let r=e.lastIndexOf(".");s=e.slice(r+1)}for(let r of o)if(s===r.name||r.extensions.includes(s||""))return r.name;switch(s){case"ts":case"tsx":return"typescript";case"html":case"svelte":case"vue":return"html";case"css":case"less":case"scss":return"css";case"js":case"jsx":case"json":default:return"javascript"}},er=(e,t)=>{let o={javascript:(0,Fo.javascript)({jsx:!0,typescript:!1}),typescript:(0,Fo.javascript)({jsx:!0,typescript:!0}),html:(0,Gs.html)(),css:(0,Ws.css)()};for(let s of t)if(e===s.name)return s.language;return o[e]},Ut=(...e)=>Ys.useCallback(t=>e.forEach(o=>{if(!!o){if(typeof o=="function")return o(t);o.current=t}}),e);function tr(e){return De.ViewPlugin.fromClass(class{constructor(t){this.decorations=this.getDecoration(t)}update(t){}getDecoration(t){if(!e)return De.Decoration.none;let o=e.map(s=>{var c,p,d;let r=De.Decoration.line({attributes:{class:(c=s.className)!=null?c:""}}),n=De.Decoration.mark({class:(p=s.className)!=null?p:"",attributes:(d=s.elementAttributes)!=null?d:void 0}),a=bt(t.state.doc,{line:s.line,column:s.startColumn})+1;if(s.startColumn&&s.endColumn){let h=bt(t.state.doc,{line:s.line,column:s.endColumn})+1;return n.range(a,h)}return r.range(a)});return De.Decoration.set(o)}},{decorations:t=>t.decorations})}var He=f(require("@codemirror/view"));function or(){return En}var Rn=He.Decoration.line({attributes:{class:"cm-errorLine"}}),En=He.ViewPlugin.fromClass(class{constructor(){this.decorations=He.Decoration.none}update(e){e.transactions.forEach(t=>{let o=t.annotation("show-error");if(o!==void 0){let s=bt(e.view.state.doc,{line:o})+1;this.decorations=He.Decoration.set([Rn.range(s)])}else t.annotation("remove-errors")&&(this.decorations=He.Decoration.none)})}},{decorations:e=>e.decorations});var zt=m({margin:"0",display:"block",fontFamily:"$font$mono",fontSize:"$font$size",color:"$syntax$color$plain",lineHeight:"$font$lineHeight"}),Ao=m(qs()),Vt=m({flex:1,position:"relative",overflow:"auto",background:"$colors$surface1",".cm-scroller":{padding:"$space$4 0"},[`.${zt}`]:{padding:"$space$4 0"}}),Po=m({margin:"0",outline:"none",height:"100%"}),sr=m({fontFamily:"$font$mono",fontSize:"0.8em",position:"absolute",right:"$space$2",bottom:"$space$2",zIndex:"$top",color:"$colors$clickable",backgroundColor:"$colors$surface2",borderRadius:"99999px",padding:"calc($space$1 / 2) $space$2",[`& + .${F}`]:{right:"calc($space$11 * 2)"}});var rr=f(require("@codemirror/highlight")),nr=f(require("react")),ar=({langSupport:e,highlightTheme:t,code:o=""})=>{let s=e.language.parser.parse(o),r=0,n=[],a=(c,p)=>{if(c>r){let d=o.slice(r,c);n.push(p?(0,nr.createElement)("span",{children:d,className:p,key:`${c}${r}`}):d),r=c}};return(0,rr.highlightTree)(s,t.match,(c,p,d)=>{a(c,""),a(p,d)}),r<o.length&&n.push(` + + `),n};var Be=L.forwardRef(({code:e,filePath:t,fileType:o,onCodeUpdate:s,showLineNumbers:r=!1,showInlineErrors:n=!1,wrapContent:a=!1,editorState:c="pristine",readOnly:p=!1,showReadOnly:d=!0,decorators:h,initMode:y="lazy",id:S,extensions:v=[],extensionsKeymap:g=[],additionalLanguages:k=[]},$)=>{let w=L.useRef(null),X=Ut(w,$),A=L.useRef(),{theme:ae,themeId:W}=Oe(),[Q,G]=L.useState(e),[q,C]=L.useState(y==="immediate"),O=(0,ir.useClasser)(b),{listen:be}=T(),x=L.useRef([]),R=L.useRef([]),{isIntersecting:z}=(0,mr.default)(w,{rootMargin:"600px 0px",threshold:.2});L.useImperativeHandle($,()=>({getCodemirror:()=>A.current})),L.useEffect(()=>{(y==="lazy"||y==="user-visible")&&z&&C(!0)},[y,z]);let D=Qs(t,o,k),V=er(D,k),le=Ks(ae),et=ar({langSupport:V,highlightTheme:le,code:e}),je=L.useMemo(()=>h&&h.sort((_,Z)=>_.line-Z.line),[h]);L.useEffect(()=>{if(!w.current||!q)return;let _=setTimeout(function(){let ee=[{key:"Tab",run:ie=>{var Ce;(0,Ne.indentMore)(ie);let pe=g.find(({key:Ae})=>Ae==="Tab");return(Ce=pe==null?void 0:pe.run(ie))!=null?Ce:!0}},{key:"Shift-Tab",run:({state:ie,dispatch:pe})=>{var Ae;(0,Ne.indentLess)({state:ie,dispatch:pe});let Ce=g.find(({key:Nt})=>Nt==="Shift-Tab");return(Ae=Ce==null?void 0:Ce.run(ye))!=null?Ae:!0}},{key:"Escape",run:()=>(p||w.current&&w.current.focus(),!0)},{key:"mod-Backspace",run:Ne.deleteGroupBackward}],j=[(0,fe.highlightSpecialChars)(),(0,Wt.history)(),(0,Xt.closeBrackets)(),...v,fe.keymap.of([...Xt.closeBracketsKeymap,...Ne.defaultKeymap,...Wt.historyKeymap,...cr.commentKeymap,...ee,...g]),V,pr.defaultHighlightStyle.fallback,Js(),le];p?(j.push($e.EditorState.readOnly.of(!0)),j.push(fe.EditorView.editable.of(!1))):(j.push((0,dr.bracketMatching)()),j.push((0,fe.highlightActiveLine)())),je&&j.push(tr(je)),a&&j.push(fe.EditorView.lineWrapping),r&&j.push((0,lr.lineNumbers)()),n&&j.push(or());let Ue=$e.EditorState.create({doc:e,extensions:j}),ze=w.current,ot=ze.querySelector(".sp-pre-placeholder");ot&&ze.removeChild(ot);let ye=new fe.EditorView({state:Ue,parent:ze,dispatch:ie=>{if(ye.update([ie]),ie.docChanged){let pe=ie.newDoc.sliceString(0,ie.newDoc.length);G(pe),s==null||s(pe)}}});ye.contentDOM.setAttribute("data-gramm","false"),ye.contentDOM.setAttribute("aria-label",t?`Code Editor for ${Xe(t)}`:"Code Editor"),p?ye.contentDOM.classList.add("cm-readonly"):ye.contentDOM.setAttribute("tabIndex","-1"),A.current=ye},0);return()=>{var Z;(Z=A.current)==null||Z.destroy(),clearTimeout(_)}},[q,r,a,W,je,p]),L.useEffect(function(){let Z=A.current,ee=!wo(v,x.current)||!wo(g,R.current);Z&&ee&&(Z.dispatch({effects:$e.StateEffect.appendConfig.of(v)}),Z.dispatch({effects:$e.StateEffect.appendConfig.of(fe.keymap.of([...g]))}),x.current=v,R.current=g)},[v,g]),L.useEffect(()=>{A.current&&c==="dirty"&&window.matchMedia("(min-width: 768px)").matches&&A.current.contentDOM.focus()},[]),L.useEffect(()=>{if(A.current&&e!==Q){let _=A.current,Z=_.state.selection.ranges.some(({to:j,from:Ue})=>j>e.length||Ue>e.length)?$e.EditorSelection.cursor(e.length):_.state.selection,ee={from:0,to:_.state.doc.length,insert:e};_.dispatch({changes:ee,selection:Z})}},[e]),L.useEffect(function(){if(!n)return;let Z=be(ee=>{let j=A.current;ee.type==="success"?j==null||j.dispatch({annotations:[new Io.Annotation("remove-errors",!0)]}):ee.type==="action"&&ee.action==="show-error"&&ee.line&&(j==null||j.dispatch({annotations:[new Io.Annotation("show-error",ee.line)]}))});return()=>Z()},[be,n]);let Tt=_=>{_.key==="Enter"&&A.current&&(_.preventDefault(),A.current.contentDOM.focus())},tt=()=>{let _=4;return r&&(_+=6),p||(_+=1),`var(--${b}-space-${_})`};return p?L.createElement(L.Fragment,null,L.createElement("pre",{ref:X,className:l(O("cm",c,D),Po,Ao),translate:"no"},L.createElement("code",{className:l(O("pre-placeholder"),zt),style:{marginLeft:tt()}},et)),p&&d&&L.createElement("span",u({className:l(O("read-only"),sr)},{}),"Read-only")):L.createElement("div",{ref:X,"aria-autocomplete":"list","aria-label":t?`Code Editor for ${Xe(t)}`:"Code Editor","aria-multiline":"true",className:l(O("cm",c,D),Po,Ao),onKeyDown:Tt,role:"textbox",tabIndex:0,translate:"no",suppressHydrationWarning:!0},L.createElement("pre",{className:l(O("pre-placeholder"),zt),style:{marginLeft:tt()}},et))});var Oo=Me.forwardRef(({style:e,showTabs:t,showLineNumbers:o=!1,showInlineErrors:s=!1,showRunButton:r=!0,wrapContent:n=!1,closableTabs:a=!1,initMode:c,extensions:p,extensionsKeymap:d,id:h,readOnly:y,showReadOnly:S,additionalLanguages:v},g)=>{let{sandpack:k}=T(),{code:$,updateCode:w,readOnly:X}=ut(),{activeFile:A,status:ae,editorState:W}=k,Q=t!=null?t:k.visibleFiles.length>1,G=(0,ur.useClasser)(b),q=C=>{w(C)};return Me.createElement(ne,{className:G("editor"),style:e},Q&&Me.createElement(ht,{closableTabs:a}),Me.createElement("div",{className:l(G("code-editor"),Vt)},Me.createElement(Be,{key:A,ref:g,additionalLanguages:v,code:$,editorState:W,extensions:p,extensionsKeymap:d,filePath:A,id:h,initMode:c||k.initMode,onCodeUpdate:q,readOnly:y||X,showInlineErrors:s,showLineNumbers:o,showReadOnly:S,wrapContent:n}),r&&ae==="idle"?Me.createElement(gt,null):null))});var fr=f(require("@code-hike/classer")),Le=f(require("react"));var Do=Le.forwardRef((p,c)=>{var d=p,{showTabs:e,showLineNumbers:t,decorators:o,code:s,initMode:r,wrapContent:n}=d,a=N(d,["showTabs","showLineNumbers","decorators","code","initMode","wrapContent"]);let{sandpack:h}=T(),{code:y}=ut(),S=(0,fr.useClasser)(b),v=e!=null?e:h.visibleFiles.length>1;return Le.createElement(ne,u({},a),v?Le.createElement(ht,null):null,Le.createElement("div",{className:l(S("code-editor"),Vt)},Le.createElement(Be,{ref:c,code:s!=null?s:y,decorators:o,filePath:h.activeFile,initMode:r||h.initMode,showLineNumbers:t,showReadOnly:!1,wrapContent:n,readOnly:!0})),h.status==="idle"?Le.createElement(gt,null):null)});var Ho=f(require("react"));var Yt=f(require("react"));var qe=f(require("react"));var hr=f(require("@code-hike/classer")),Je=f(require("react"));var Tn=m({borderRadius:"0",width:"100%",padding:0,marginBottom:"$space$2",span:{textOverflow:"ellipsis",whiteSpace:"nowrap",overflow:"hidden"},svg:{marginRight:"$space$1"}}),Gt=({selectFile:e,path:t,active:o,onClick:s,depth:r,isDirOpen:n})=>{let a=(0,hr.useClasser)(b),c=h=>{e&&e(t),s==null||s(h)},p=t.split("/").filter(Boolean).pop(),d=()=>e?Je.createElement(xo,null):n?Je.createElement(ko,null):Je.createElement(Co,null);return Je.createElement("button",{className:l(a("button","explorer"),F,Tn),"data-active":o,onClick:c,style:{paddingLeft:18*r+"px"},title:p,type:"button"},d(),Je.createElement("span",null,p))};var gr=({prefixedPath:e,files:t,selectFile:o,activeFile:s,depth:r,autoHiddenFiles:n,visibleFiles:a})=>{let[c,p]=qe.useState(!0);return qe.createElement("div",{key:e},qe.createElement(Gt,{depth:r,isDirOpen:c,onClick:()=>p(h=>!h),path:e+"/"}),c&&qe.createElement(Zt,{activeFile:s,autoHiddenFiles:n,depth:r+1,files:t,prefixedPath:e,selectFile:o,visibleFiles:a}))};var br=({autoHiddenFiles:e,visibleFiles:t,files:o,prefixedPath:s})=>{let r=t.length>0,n=e&&!r,a=e&&!!r,c=Object.keys(o).filter(h=>{var S;let y=h.startsWith(s);return a?y&&t.includes(h):n?y&&!((S=o[h])==null?void 0:S.hidden):y}).map(h=>h.substring(s.length)),p=new Set(c.filter(h=>h.includes("/")).map(h=>`${s}${h.split("/")[0]}/`)),d=c.filter(h=>!h.includes("/")).map(h=>`${s}${h}`);return{directories:Array.from(p),modules:d}};var Zt=({depth:e=0,activeFile:t,selectFile:o,prefixedPath:s,files:r,autoHiddenFiles:n,visibleFiles:a})=>{let{directories:c,modules:p}=br({visibleFiles:a,autoHiddenFiles:n,prefixedPath:s,files:r});return Yt.createElement("div",null,c.map(d=>Yt.createElement(gr,{key:d,activeFile:t,autoHiddenFiles:n,depth:e,files:r,prefixedPath:d,selectFile:o,visibleFiles:a})),p.map(d=>Yt.createElement(Gt,{key:d,active:t===d,depth:e,path:d,selectFile:o})))};var Nn=m({padding:"$space$3",overflow:"auto",height:"100%"}),$n=s=>{var r=s,{className:e,autoHiddenFiles:t=!1}=r,o=N(r,["className","autoHiddenFiles"]);let{sandpack:n}=T();return Ho.createElement("div",u({className:l(Te,Nn,`${b}-file-explorer`,e)},o),Ho.createElement(Zt,{activeFile:n.activeFile,autoHiddenFiles:t,files:n.files,prefixedPath:"/",selectFile:n.openFile,visibleFiles:n.visibleFilesFromProps}))};var Sr=f(require("@code-hike/classer")),Y=f(require("react"));var yr=e=>{let t=e.match(/(https?:\/\/.*?)\//);return t&&t[1]?[t[1],e.replace(t[1],"")]:[e,"/"]};var Mn=m({display:"flex",alignItems:"center",height:"$layout$headerHeight",borderBottom:"1px solid $colors$surface2",padding:"$space$3 $space$2",background:"$colors$surface1"}),Ln=m({backgroundColor:"$colors$surface2",color:"$colors$clickable",padding:"$space$1 $space$3",borderRadius:"99999px",border:"1px solid $colors$surface2",height:"24px",lineHeight:"24px",fontSize:"inherit",outline:"none",flex:1,marginLeft:"$space$4",width:"0",transition:"background $transitions$default","&:hover":{backgroundColor:"$colors$surface3"},"&:focus":{backgroundColor:"$surface1",border:"1px solid $colors$accent",color:"$colors$base"}}),Bo=r=>{var n=r,{clientId:e,onURLChange:t,className:o}=n,s=N(n,["clientId","onURLChange","className"]);var q;let[a,c]=Y.useState(""),{sandpack:p,dispatch:d,listen:h}=T(),[y,S]=Y.useState((q=p.startRoute)!=null?q:"/"),[v,g]=Y.useState(!1),[k,$]=Y.useState(!1),w=(0,Sr.useClasser)(b);Y.useEffect(()=>{let C=h(O=>{if(O.type==="urlchange"){let{url:be,back:x,forward:R}=O,[z,D]=yr(be);c(z),S(D),g(x),$(R)}},e);return()=>C()},[]);let X=C=>{let O=C.target.value.startsWith("/")?C.target.value:`/${C.target.value}`;S(O)},A=C=>{C.code==="Enter"&&(C.preventDefault(),C.stopPropagation(),typeof t=="function"&&t(a+C.currentTarget.value))},ae=()=>{d({type:"refresh"})},W=()=>{d({type:"urlback"})},Q=()=>{d({type:"urlforward"})},G=l(w("button","icon"),F,Hs,m({minWidth:"$space$6",justifyContent:"center"}));return Y.createElement("div",u({className:l(w("navigator"),Mn,o)},s),Y.createElement("button",{"aria-label":"Go back one page",className:G,disabled:!v,onClick:W,type:"button"},Y.createElement(bo,null)),Y.createElement("button",{"aria-label":"Go forward one page",className:G,disabled:!k,onClick:Q,type:"button"},Y.createElement(yo,null)),Y.createElement("button",{"aria-label":"Refresh page",className:G,onClick:ae,type:"button"},Y.createElement(nt,null)),Y.createElement("input",{"aria-label":"Current Sandpack URL",className:l(w("input"),Ln),name:"Current Sandpack URL",onChange:X,onKeyDown:A,type:"text",value:y}))};var $r=f(require("@code-hike/classer")),U=f(require("react"));var vr=f(require("react"));var _o=()=>{var o;let{sandpack:e}=T(),{error:t}=e;return vr.useEffect(()=>{e.errorScreenRegisteredRef.current=!0},[]),(o=t==null?void 0:t.message)!=null?o:null};var yt=f(require("react"));var Jt=200,jo=(e,t)=>{let{sandpack:o,listen:s}=T(),[r,n]=yt.useState("LOADING");return yt.useEffect(()=>{o.loadingScreenRegisteredRef.current=!0;let a=s(c=>{c.type==="start"&&c.firstLoad===!0&&n("LOADING"),c.type==="done"&&n(p=>p==="LOADING"?"PRE_FADING":"HIDDEN")},e);return()=>{a()}},[e,o.status==="idle"]),yt.useEffect(()=>{let a;return r==="PRE_FADING"&&!t?n("FADING"):r==="FADING"&&(a=setTimeout(()=>n("HIDDEN"),Jt)),()=>{clearTimeout(a)}},[r,t]),o.status==="timeout"?"TIMEOUT":o.status!=="running"?"HIDDEN":r};var Uo=e=>{let{dispatch:t}=T();return{refresh:()=>t({type:"refresh"},e),back:()=>t({type:"urlback"},e),forward:()=>t({type:"urlforward"},e)}};function wn(e){var r,n;let{activeFile:t,bundlerState:o}=e;if(o==null)return null;let s=o.transpiledModules[t+":"];return(n=(r=s==null?void 0:s.source)==null?void 0:r.compiledCode)!=null?n:null}var zo=()=>{let{sandpack:e}=T();return e.status!=="running"?null:wn(e)};var St=f(require("react"));var vt=()=>{let{sandpack:e,listen:t,dispatch:o}=T(),s=St.useRef(null),r=St.useRef(it());return St.useEffect(()=>{let a=s.current,c=r.current;return a!==null&&e.registerBundler(a,c),()=>e.unregisterBundler(c)},[]),{sandpack:e,getClient:()=>e.clients[r.current]||null,clientId:r.current,iframe:s,listen:a=>t(a,r.current),dispatch:a=>o(a,r.current)}};var kr=f(require("@code-hike/classer")),Vo=f(require("react"));var kt=s=>{var r=s,{children:e,className:t}=r,o=N(r,["children","className"]);let n=_o(),a=(0,kr.useClasser)(b);return!n&&!e?null:Vo.createElement("div",u({className:l(a("overlay","error"),ft,jt,t),translate:"no"},o),Vo.createElement("div",{className:l(a("error-message"),Ze)},n||e))};var Tr=f(require("@code-hike/classer")),_e=f(require("react"));var Er=f(require("@code-hike/classer")),he=f(require("react"));var Rr=f(require("@code-hike/classer")),Xo=f(require("react"));var Cr=f(require("lz-string")),ce=f(require("react"));var Fn=e=>Cr.default.compressToBase64(JSON.stringify(e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""),xr="https://codesandbox.io/api/v1/sandboxes/define",An=(e,t)=>{let o=Object.keys(e).reduce((s,r)=>{let n=r.replace("/",""),a={content:e[r].code,isBinary:!1};return M(u({},s),{[n]:a})},{});return Fn(u({files:o},t?{template:t}:null))},qt=o=>{var s=o,{children:e}=s,t=N(s,["children"]);var p,d,h;let{sandpack:r}=T(),n=ce.useRef(null),[a,c]=ce.useState();return ce.useEffect(function(){let S=setTimeout(()=>{let v=An(r.files,r.environment),g=new URLSearchParams({parameters:v,query:new URLSearchParams({file:r.activeFile,utm_medium:"sandpack"}).toString()});c(g)},600);return()=>{clearTimeout(S)}},[r.activeFile,r.environment,r.files]),ce.useEffect(function(){r.openInCSBRegisteredRef.current=!0},[]),((h=(d=(p=a==null?void 0:a.get)==null?void 0:p.call(a,"parameters"))==null?void 0:d.length)!=null?h:0)>1500?ce.createElement("button",u({onClick:()=>{var y;return(y=n.current)==null?void 0:y.submit()},title:"Open in CodeSandbox"},t),ce.createElement("form",{ref:n,action:xr,method:"POST",style:{visibility:"hidden"},target:"_blank"},Array.from(a,([y,S])=>ce.createElement("input",{key:y,name:y,type:"hidden",value:S}))),e):ce.createElement("a",u({href:`${xr}?${a==null?void 0:a.toString()}`,rel:"noreferrer noopener",target:"_blank",title:"Open in CodeSandbox"},t),e)};var Ke=()=>{let e=(0,Rr.useClasser)(b);return Xo.createElement(qt,{className:l(e("button","icon-standalone"),F,me,te)},Xo.createElement(vo,null))};var Wo=m({transform:"translate(-4px, 9px) scale(0.13, 0.13)","*":{position:"absolute",width:"96px",height:"96px"}}),Pn=m({position:"absolute",right:"$space$2",bottom:"$space$2",zIndex:"$top",width:"32px",height:"32px",borderRadius:"$border$radius",[`.${Wo}`]:{display:"flex"},[`.${F}`]:{display:"none"},[`&:hover .${F}`]:{display:"flex"},[`&:hover .${Wo}`]:{display:"none"}}),In=Mt({"0%":{transform:"rotateX(-25.5deg) rotateY(45deg)"},"100%":{transform:"rotateX(-25.5deg) rotateY(405deg)"}}),On=m({animation:`${In} 1s linear infinite`,animationFillMode:"forwards",transformStyle:"preserve-3d",transform:"rotateX(-25.5deg) rotateY(45deg)","*":{border:"10px solid $colors$clickable",borderRadius:"8px",background:"$colors$surface1"},".top":{transform:"rotateX(90deg) translateZ(44px)",transformOrigin:"50% 50%"},".bottom":{transform:"rotateX(-90deg) translateZ(44px)",transformOrigin:"50% 50%"},".front":{transform:"rotateY(0deg) translateZ(44px)",transformOrigin:"50% 50%"},".back":{transform:"rotateY(-180deg) translateZ(44px)",transformOrigin:"50% 50%"},".left":{transform:"rotateY(-90deg) translateZ(44px)",transformOrigin:"50% 50%"},".right":{transform:"rotateY(90deg) translateZ(44px)",transformOrigin:"50% 50%"}}),Kt=s=>{var r=s,{className:e,showOpenInCodeSandbox:t}=r,o=N(r,["className","showOpenInCodeSandbox"]);let n=(0,Er.useClasser)(b);return he.createElement("div",u({className:l(n("cube-wrapper"),Pn,e),title:"Open in CodeSandbox"},o),t&&he.createElement(Ke,null),he.createElement("div",{className:l(n("cube"),Wo)},he.createElement("div",{className:l(n("sides"),On)},he.createElement("div",{className:"top"}),he.createElement("div",{className:"right"}),he.createElement("div",{className:"bottom"}),he.createElement("div",{className:"left"}),he.createElement("div",{className:"front"}),he.createElement("div",{className:"back"}))))};var Dn=m({backgroundColor:"$colors$surface1"}),Ct=a=>{var c=a,{clientId:e,loading:t,className:o,style:s,showOpenInCodeSandbox:r}=c,n=N(c,["clientId","loading","className","style","showOpenInCodeSandbox"]);let p=jo(e,t),d=(0,Tr.useClasser)(b);if(p==="HIDDEN")return null;if(p==="TIMEOUT")return _e.createElement("div",u({className:l(d("overlay","error"),ft,jt,o)},n),_e.createElement("div",{className:l(d("error-message"),Ze)},"Unable to establish connection with the sandpack bundler. Make sure you are online or try again later. If the problem persists, please report it via"," ",_e.createElement("a",{className:l(d("error-message"),Ze),href:"mailto:hello@codesandbox.io?subject=Sandpack Timeout Error"},"email")," ","or submit an issue on"," ",_e.createElement("a",{className:l(d("error-message"),Ze),href:"https://github.com/codesandbox/sandpack/issues",rel:"noreferrer noopener",target:"_blank"},"GitHub.")));let h=p==="LOADING"||p==="PRE_FADING";return _e.createElement("div",u({className:l(d("overlay","loading"),ft,Dn,o),style:M(u({},s),{opacity:h?1:0,transition:`opacity ${Jt}ms ease-out`})},n),_e.createElement(Kt,{showOpenInCodeSandbox:r}))};var Nr=f(require("@code-hike/classer")),Go=f(require("react"));var Zo=({clientId:e})=>{let{refresh:t}=Uo(e),o=(0,Nr.useClasser)(b);return Go.createElement("button",{className:l(o("button","icon-standalone"),F,me,te),onClick:t,title:"Refresh Sandpack",type:"button"},Go.createElement(nt,null))};var Hn=m({flex:1,display:"flex",flexDirection:"column",background:"white",overflow:"auto",position:"relative"}),Bn=m({border:"0",outline:"0",width:"100%",height:"100%",minHeight:"160px",maxHeight:"2000px",flex:1}),_n=m({display:"flex",position:"absolute",bottom:"$space$2",right:"$space$2",zIndex:"$overlay","> *":{marginLeft:"$space$2"}}),Yo=U.forwardRef((d,p)=>{var h=d,{showNavigator:e=!1,showRefreshButton:t=!0,showOpenInCodeSandbox:o=!0,showSandpackErrorOverlay:s=!0,actionsChildren:r=U.createElement(U.Fragment,null),children:n,className:a}=h,c=N(h,["showNavigator","showRefreshButton","showOpenInCodeSandbox","showSandpackErrorOverlay","actionsChildren","children","className"]);let{sandpack:y,listen:S,iframe:v,getClient:g,clientId:k}=vt(),[$,w]=U.useState(null),{status:X,errorScreenRegisteredRef:A,openInCSBRegisteredRef:ae,loadingScreenRegisteredRef:W}=y,Q=(0,$r.useClasser)(b);ae.current=!0,A.current=!0,W.current=!0,U.useEffect(()=>S(C=>{C.type==="resize"&&w(C.height)}),[]),U.useImperativeHandle(p,()=>({clientId:k,getClient:g}),[g,k]);let G=q=>{!v.current||(v.current.src=q)};return U.createElement(ne,u({className:l(`${b}-preview`,a)},c),e&&U.createElement(Bo,{clientId:k,onURLChange:G}),U.createElement("div",{className:l(Q("preview-container"),Hn)},U.createElement("iframe",{ref:v,className:l(Q("preview-iframe"),Bn),style:{height:$||void 0},title:"Sandpack Preview"}),s&&U.createElement(kt,null),U.createElement("div",{className:l(Q("preview-actions"),_n)},r,!e&&t&&X==="running"&&U.createElement(Zo,{clientId:k}),o&&U.createElement(Ke,null)),U.createElement(Ct,{clientId:k,showOpenInCodeSandbox:o}),n))});var Mr=f(require("@code-hike/classer")),Se=f(require("react"));var jn=m({display:"flex",flexDirection:"column",width:"100%",position:"relative",overflow:"auto",minHeight:"160px",flex:1}),Un=o=>{var s=o,{className:e}=s,t=N(s,["className"]);let{sandpack:r}=T(),n=zo(),a=(0,Mr.useClasser)(b),c=Se.useRef(null);return Se.useEffect(()=>{let p=c.current;return p&&r.registerBundler(p,"hidden"),()=>{r.unregisterBundler("hidden")}},[]),Se.createElement("div",u({className:l(a("transpiled-code"),Te,jn,e)},t),Se.createElement(Do,u({code:n!=null?n:"",initMode:r.initMode},t)),Se.createElement("iframe",{ref:c,style:{display:"none"},title:"transpiled sandpack code"}),Se.createElement(kt,null),Se.createElement(Ct,{clientId:"hidden",showOpenInCodeSandbox:!1}))};var Lr=f(require("@code-hike/classer")),ge=f(require("react"));var zn=m({height:"$layout$height",width:"100%"}),Vn=r=>{var n=r,{clientId:e,theme:t,className:o}=n,s=N(n,["clientId","theme","className"]);let{listen:a,sandpack:c}=T(),{themeMode:p}=Oe(),d=(0,Lr.useClasser)(b),h=ge.useRef(),[y,S]=ge.useState(null);return ge.useEffect(()=>{import("react-devtools-inline/frontend").then(v=>{h.current=v})},[]),ge.useEffect(()=>a(g=>{var k;if(g.type==="activate-react-devtools"){let $=e?c.clients[e]:Object.values(c.clients)[0],w=(k=$==null?void 0:$.iframe)==null?void 0:k.contentWindow;h.current&&w&&S(h.current.initialize(w))}}),[h,e,a,c.clients]),ge.useEffect(()=>{c.registerReactDevTools("legacy")},[]),y?ge.createElement("div",u({className:l(d("devtools"),zn,o)},s),ge.createElement(y,{browserTheme:t!=null?t:p})):null};var H=f(require("react"));var wr=f(require("@code-hike/classer")),Qt=f(require("react"));var Fr=m({border:"1px solid $colors$surface2",display:"flex",flexWrap:"wrap",alignItems:"stretch",borderRadius:"$border$radius",overflow:"hidden",position:"relative",backgroundColor:"$colors$surface2",gap:1,[`> .${Te}`]:{flexGrow:1,flexShrink:1,flexBasis:"0",minWidth:"350px",height:"$layout$height","@media print":{height:"auto",display:"block"},"@media screen and (max-width: 768px)":{height:"auto",minWidth:"100% !important;"}},[`> .${b}-file-explorer`]:{flex:.2,minWidth:200}}),Jo=Qt.forwardRef((r,s)=>{var n=r,{children:e,className:t}=n,o=N(n,["children","className"]);let{sandpack:a}=T(),c=(0,wr.useClasser)(b),p=Ut(a.lazyAnchorRef,s);return Qt.createElement("div",u({ref:p,className:l(c("layout"),Fr,t)},o),e)});var Re=f(require("react"));var Xn=m({justifyContent:"space-between",borderBottom:"1px solid $colors$surface2",padding:"$space$3 $space$2",fontFamily:"$font$mono",maxHeight:"$layout$headerHeight",overflowX:"auto",whiteSpace:"nowrap"}),qo=m({display:"flex",flexDirection:"row",alignItems:"center",gap:"$space$2"}),Ar=({status:e,suiteOnly:t,setSuiteOnly:o,setVerbose:s,verbose:r,watchMode:n,setWatchMode:a,showSuitesOnly:c})=>{let p=l(F,te,m({padding:"$space$1 $space$3"}));return Re.createElement("div",{className:l(Xn,qo)},Re.createElement("div",{className:l(qo)},Re.createElement("p",{className:l(m({lineHeight:1,margin:0,color:"$colors$base",fontSize:"$font$size",display:"flex",alignItems:"center",gap:"$space$2"}))},Re.createElement(Pe,null),"Tests")),Re.createElement("div",{className:l(qo)},c&&Re.createElement("button",{className:p,"data-active":t,disabled:e==="initialising",onClick:o},"Suite only"),Re.createElement("button",{className:p,"data-active":r,disabled:e==="initialising",onClick:s},"Verbose"),Re.createElement("button",{className:p,"data-active":n,disabled:e==="initialising",onClick:a},"Watch")))};var Pr=f(require("@code-hike/classer")),Ko=f(require("react"));var Ir=({onClick:e})=>{let t=(0,Pr.useClasser)(b);return Ko.createElement("button",{className:l(t("button","icon-standalone"),F,me,te),onClick:e,title:"Run tests",type:"button"},Ko.createElement(rt,null))};var I=f(require("react"));var Fe=f(require("react"));var we=f(require("react"));var Or=e=>({"--test-pass":e?"#18df16":"#15c213","--test-fail":e?"#df162b":"#c21325","--test-skip":e?"#eace2b":"#c2a813","--test-run":e?"#eace2b":"#c2a813","--test-title":e?"#3fbabe":"#256c6f"}),eo=m({variants:{status:{pass:{color:"var(--test-pass)"},fail:{color:"var(--test-fail)"},skip:{color:"var(--test-skip)"},title:{color:"var(--test-title)"}}}}),ve=eo({status:"pass"}),J=eo({status:"fail"}),to=eo({status:"skip"}),Dr=eo({status:"title"}),Qo=m({variants:{status:{pass:{background:"var(--test-pass)",color:"$colors$surface1"},fail:{background:"var(--test-fail)",color:"$colors$surface1"},run:{background:"var(--test-run)",color:"$colors$surface1"}}}}),Hr=Qo({status:"run"}),Br=Qo({status:"pass"}),es=Qo({status:"fail"});var Wn=m({marginLeft:"$space$4"}),Gn=m({marginBottom:"$space$2",color:"$colors$clickable"}),Zn=m({marginBottom:"$space$2",color:"$colors$hover"}),Yn=m({marginLeft:"$space$2"}),ts=m({marginRight:"$space$2"}),oo=({tests:e,style:t})=>we.default.createElement("div",{className:l(Wn)},e.map(o=>we.default.createElement("div",{key:o.name,className:l(Gn)},o.status==="pass"&&we.default.createElement("span",{className:l(ve,ts)},"\u2713"),o.status==="fail"&&we.default.createElement("span",{className:l(J,ts)},"\u2715"),o.status==="idle"&&we.default.createElement("span",{className:l(to,ts)},"\u25CB"),we.default.createElement("span",{className:l(Zn)},o.name),o.duration!==void 0&&we.default.createElement("span",{className:l(Yn)},"(",o.duration," ms)"))));var _r=f(require("clean-set")),jr=e=>so(e).filter(t=>t.status==="fail"),so=e=>Object.values(e.tests).concat(...Object.values(e.describes).map(so)),Ur=e=>e.map(ro).reduce((t,o)=>({pass:t.pass+o.pass,fail:t.fail+o.fail,skip:t.skip+o.skip,total:t.total+o.total}),{pass:0,skip:0,fail:0,total:0}),ro=e=>so(e).reduce((t,o)=>({pass:o.status==="pass"?t.pass+1:t.pass,fail:o.status==="fail"?t.fail+1:t.fail,skip:o.status==="idle"||o.status==="running"?t.skip+1:t.skip,total:t.total+1}),{pass:0,fail:0,skip:0,total:0}),zr=e=>e.filter(t=>Object.values(t.describes).length>0||Object.values(t.tests).length>0).map(ro).reduce((t,o)=>({pass:t.pass+(o.fail===0?1:0),fail:t.fail+(o.fail>0?1:0),total:t.total+1}),{pass:0,fail:0,total:0}),Vr=e=>Qe(e,so).reduce((t,o)=>t+(o.duration||0),0),no=e=>Object.values(e.describes).length===0&&Object.values(e.tests).length===0,xt=e=>{let t=e.length-1,o=e.slice(0,t),s=e[t];return[o,s]},Qe=(e,t)=>e.map(t).reduce((o,s)=>o.concat(s),[]),ke=(e,t)=>o=>(0,_r.default)(o,e,t);var Jn=m({color:"$colors$hover",marginBottom:"$space$2"}),qn=m({marginLeft:"$space$4"}),os=({describes:e})=>Fe.createElement(Fe.Fragment,null,e.map(t=>{if(no(t))return null;let o=Object.values(t.tests),s=Object.values(t.describes);return Fe.createElement("div",{key:t.name,className:l(qn)},Fe.createElement("div",{className:l(Jn)},t.name),Fe.createElement(oo,{tests:o}),Fe.createElement(os,{describes:s}))}));var Xr=f(require("react"));var Kn=m({color:"$colors$hover",fontSize:"$font$size",padding:"$space$2",whiteSpace:"pre-wrap"}),ss=({error:e,path:t})=>Xr.createElement("div",{className:l(Kn),dangerouslySetInnerHTML:{__html:Qn(e,t)}}),ao=e=>e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),Qn=(e,t)=>{let o="";if(e.matcherResult?o=`<span>${ao(e.message).replace(/(expected)/m,`<span class="${ve}">$1</span>`).replace(/(received)/m,`<span class="${J}">$1</span>`).replace(/(Difference:)/m,"<span>$1</span>").replace(/(Expected:)(.*)/m,`<span>$1</span><span class="${ve}">$2</span>`).replace(/(Received:)(.*)/m,`<span>$1</span><span class="${J}">$2</span>`).replace(/^(-.*)/gm,`<span class="${J}">$1</span>`).replace(/^(\+.*)/gm,`<span class="${ve}">$1</span>`)}</span>`:o=ao(e.message),e.mappedErrors&&e.mappedErrors[0]&&e.mappedErrors[0].fileName.endsWith(t)&&e.mappedErrors[0]._originalScriptCode){let r=e.mappedErrors[0]._originalScriptCode||[],n=Math.max(...r.map(c=>(c.lineNumber+"").length))+2,a=Array.from({length:n}).map(()=>" ");o+="<br />",o+="<br />",o+="<div>",r.filter(c=>c.content.trim()).forEach(c=>{let p=(c.lineNumber+"").length,d=[...a];d.length-=p,c.highlight&&(d.length-=2);let h=c.content.indexOf(".to"),y=Array.from({length:a.length+h-(n-1)},()=>" "),S=ao(c.content).replace(/(describe|test|it)(\()('|"|`)(.*)('|"|`)/m,`<span>$1$2$3</span><span class="${Dr}">$4</span><span>$5</span>`).replace(/(expect\()(.*)(\)\..*)(to[\w\d]*)(\()(.*)(\))/m,`<span>$1</span><span class="${J}">$2</span><span>$3</span><span style="text-decoration: underline; font-weight: 900">$4</span><span>$5</span><span class="${ve}">$6</span><span>$7</span>`);o+=`<div ${c.highlight?'style="font-weight:200;"':""}>`+(c.highlight?`<span class="${J}">></span> `:"")+d.join("")+ao(""+c.lineNumber)+" | "+S+"</div>"+(c.highlight?"<div>"+a.join("")+" | "+y.join("")+`<span class="${J}">^</span></div>`:"")}),o+="</div>"}return o.replace(/(?:\r\n|\r|\n)/g,"<br />")};var ea=m({display:"flex",flexDirection:"row",alignItems:"center",marginBottom:"$space$2"}),rs=m({marginBottom:"$space$2"}),ta=m({fontWeight:"bold"}),io=m({borderRadius:"calc($border$radius / 2)"}),oa=m({padding:"$space$1 $space$2",fontFamily:"$font$mono",textTransform:"uppercase",marginRight:"$space$2"}),sa=m({fontFamily:"$font$mono",cursor:"pointer",display:"inline-block"}),ra=m({color:"$colors$clickable",textDecorationStyle:"dotted",textDecorationLine:"underline"}),na=m({color:"$colors$hover",fontWeight:"bold",textDecorationStyle:"dotted",textDecorationLine:"underline"}),Wr=({specs:e,openSpec:t,status:o,verbose:s})=>I.createElement(I.Fragment,null,e.map(r=>{if(r.error)return I.createElement("div",{key:r.name,className:l(rs)},I.createElement(co,{className:l(io,es)},"Error"),I.createElement(Gr,{onClick:()=>t(r.name),path:r.name}),I.createElement(ss,{error:r.error,path:r.name}));if(no(r))return null;let n=Object.values(r.tests),a=Object.values(r.describes),c=ro(r);return I.createElement("div",{key:r.name,className:l(rs)},I.createElement("div",{className:l(ea)},o==="complete"?c.fail>0?I.createElement(co,{className:l(io,es)},"Fail"):I.createElement(co,{className:l(io,Br)},"Pass"):I.createElement(co,{className:l(io,Hr)},"Run"),I.createElement(Gr,{onClick:()=>t(r.name),path:r.name})),s&&I.createElement(oo,{tests:n}),s&&I.createElement(os,{describes:a}),jr(r).map(p=>I.createElement("div",{key:`failing-${p.name}`,className:l(rs)},I.createElement("div",{className:l(ta,J)},"\u25CF ",p.blocks.join(" \u203A ")," \u203A ",p.name),p.errors.map(d=>I.createElement(ss,{key:`failing-${p.name}-error`,error:d,path:p.path})))))})),co=({children:e,className:t})=>I.createElement("span",{className:l(oa,t)},e),Gr=({onClick:e,path:t})=>{let o=t.split("/"),s=o.slice(0,o.length-1).join("/")+"/",r=o[o.length-1];return I.createElement("button",{className:l(F,sa),onClick:e},I.createElement("span",{className:l(ra)},s),I.createElement("span",{className:l(na)},r))};var oe=f(require("react"));var Zr=m({marginBottom:"$space$2"}),ns=m({fontWeight:"bold",color:"$colors$hover",whiteSpace:"pre-wrap"}),aa=m({fontWeight:"bold",color:"$colors$clickable"}),Yr=({suites:e,tests:t,duration:o})=>{let s="Test suites: ",r=n=>{let a=s.length-n.length,c=Array.from({length:a},()=>" ").join("");return n+c};return oe.createElement("div",{className:l(aa)},oe.createElement("div",{className:l(Zr)},oe.createElement("span",{className:l(ns)},s),e.fail>0&&oe.createElement("span",{className:l(J)},e.fail," failed,"," "),e.pass>0&&oe.createElement("span",{className:l(ve)},e.pass," passed,"," "),oe.createElement("span",null,e.total," total")),oe.createElement("div",{className:l(Zr)},oe.createElement("span",{className:l(ns)},r("Tests:")),t.fail>0&&oe.createElement("span",{className:l(J)},t.fail," failed,"," "),t.skip>0&&oe.createElement("span",{className:l(to)},t.skip," skipped,"," "),t.pass>0&&oe.createElement("span",{className:l(ve)},t.pass," passed,"," "),oe.createElement("span",null,t.total," total")),oe.createElement("div",{className:l(ns)},r("Time:"),o/1e3,"s"))};var ia=m({display:"flex",position:"absolute",bottom:"$space$2",right:"$space$2",zIndex:"$overlay","> *":{marginLeft:"$space$2"}}),ca={specs:{},status:"initialising",verbose:!1,watchMode:!0,suiteOnly:!1,specsCount:0},lo=c=>{var p=c,{verbose:e=!1,watchMode:t=!0,style:o,className:s,onComplete:r,actionsChildren:n}=p,a=N(p,["verbose","watchMode","style","className","onComplete","actionsChildren"]);let d=Oe(),{getClient:h,iframe:y,listen:S,sandpack:v}=vt(),[g,k]=H.useState(M(u({},ca),{verbose:e,watchMode:t}));H.useEffect(()=>{let C=[],O="";return S(x=>{if(!(g.suiteOnly&&("path"in x&&x.path!==v.activeFile||"test"in x&&"path"in x.test&&x.test.path!==v.activeFile))){if(x.type==="action"&&x.action==="clear-errors"&&x.source==="jest"){O=x.path;return}if(x.type==="test"){if(x.event==="initialize_tests")return C=[],O="",g.watchMode?$():k(R=>M(u({},R),{status:"idle",specs:{}}));if(x.event==="test_count")return k(R=>M(u({},R),{specsCount:x.count}));if(x.event==="total_test_start")return C=[],k(R=>M(u({},R),{status:"running"}));if(x.event==="total_test_end")return k(R=>(r!==void 0&&r(R.specs),M(u({},R),{status:"complete"})));if(x.event==="add_file")return k(ke(["specs",x.path],{describes:{},tests:{},name:x.path}));if(x.event==="remove_file")return k(R=>{let z=Object.entries(R.specs).reduce((D,[V,le])=>V===x.path?D:M(u({},D),{[V]:le}),{});return M(u({},R),{specs:z})});if(x.event==="file_error")return k(ke(["specs",x.path,"error"],x.error));if(x.event==="describe_start"){C.push(x.blockName);let[R,z]=xt(C),D=O;return z===void 0?void 0:k(ke(["specs",D,"describes",...Qe(R,V=>[V,"describes"]),z],{name:x.blockName,tests:{},describes:{}}))}if(x.event==="describe_end"){C.pop();return}if(x.event==="add_test"){let[R,z]=xt(C),D={status:"idle",errors:[],name:x.testName,blocks:[...C],path:x.path};return k(z===void 0?ke(["specs",x.path,"tests",x.testName],D):ke(["specs",x.path,"describes",...Qe(R,V=>[V,"describes"]),z,"tests",x.testName],D))}if(x.event==="test_start"){let{test:R}=x,[z,D]=xt(R.blocks),V={status:"running",name:R.name,blocks:R.blocks,path:R.path,errors:[]};return k(D===void 0?ke(["specs",R.path,"tests",R.name],V):ke(["specs",R.path,"describes",...Qe(z,le=>[le,"describes"]),D,"tests",R.name],V))}if(x.event==="test_end"){let{test:R}=x,[z,D]=xt(R.blocks),V={status:R.status,errors:R.errors,duration:R.duration,name:R.name,blocks:R.blocks,path:R.path};return k(D===void 0?ke(["specs",R.path,"tests",R.name],V):ke(["specs",R.path,"describes",...Qe(z,le=>[le,"describes"]),D,"tests",R.name],V))}}}})},[g.suiteOnly,g.watchMode,v.activeFile]);let $=()=>{k(O=>M(u({},O),{status:"running",specs:{}}));let C=h();C&&C.dispatch({type:"run-all-tests"})},w=()=>{k(O=>M(u({},O),{status:"running",specs:{}}));let C=h();C&&C.dispatch({type:"run-tests",path:v.activeFile})},X=/.*\.(test|spec)\.[tj]sx?$/,A=v.activeFile.match(X)!==null;H.useEffect(function(){return S(({type:be})=>{be==="done"&&g.watchMode&&(A?w():$())})},[w,$,g.watchMode,A]);let ae=C=>{v.setActiveFile(C)},W=Object.values(g.specs),Q=Vr(W),G=Ur(W),q=zr(W);return H.createElement(ne,u({className:l(`${b}-tests`,s),style:u(u({},Or(d.themeMode==="dark")),o)},a),H.createElement("iframe",{ref:y,style:{display:"none"},title:"Sandpack Tests"}),H.createElement(Ar,{setSuiteOnly:()=>k(C=>M(u({},C),{suiteOnly:!C.suiteOnly})),setVerbose:()=>k(C=>M(u({},C),{verbose:!C.verbose})),setWatchMode:()=>{k(C=>M(u({},C),{watchMode:!C.watchMode}))},showSuitesOnly:g.specsCount>1,status:g.status,suiteOnly:g.suiteOnly,verbose:g.verbose,watchMode:g.watchMode}),g.status==="running"||g.status==="initialising"?H.createElement(Kt,{showOpenInCodeSandbox:!1}):H.createElement("div",{className:ia.toString()},n,H.createElement(Ir,{onClick:g.suiteOnly?w:$})),H.createElement("div",{className:l(la)},W.length===0&&g.status==="complete"?H.createElement("div",{className:l(pa)},H.createElement("p",null,"No test files found."),H.createElement("p",null,"Test match:"," ",H.createElement("span",{className:l(J)},X.toString()))):H.createElement(H.Fragment,null,H.createElement(Wr,{openSpec:ae,specs:W,status:g.status,verbose:g.verbose}),g.status==="complete"&&G.total>0&&H.createElement(Yr,{duration:Q,suites:q,tests:G}))))},la=m({padding:"$space$4",height:"100%",overflow:"auto",display:"flex",flexDirection:"column",position:"relative",fontFamily:"$font$mono"}),pa=m({fontWeight:"bold",color:"$colors$base"});var se=f(require("react"));var Jr=f(require("@code-hike/classer")),as=f(require("react"));var qr=({onClick:e})=>{let t=(0,Jr.useClasser)("sp");return as.default.createElement("button",{className:l(t("button","icon-standalone"),F,me,te,m({position:"absolute",bottom:"$space$2",right:"$space$2"})),onClick:e},as.default.createElement(So,null))};var po=f(require("react"));var Kr=()=>po.default.createElement("div",{className:l(m({borderBottom:"1px solid $colors$surface2",padding:"$space$3 $space$2",height:"$layout$headerHeight"}))},po.default.createElement("p",{className:l(m({lineHeight:1,margin:0,color:"$colors$base",fontSize:"$font$size",display:"flex",alignItems:"center",gap:"$space$2"}))},po.default.createElement(Pe,null),"Console"));var uo=f(require("react"));var Qr=["SyntaxError: ","Error in sandbox:"],en={id:"random",method:"clear",data:["Console was cleared"]},is="@t",cs="@r",ls=1e4,ps=2,mo=400,ds=mo*2;var fo=e=>{var a,c;let[t,o]=uo.useState([]),{listen:s}=T(),r=(a=e==null?void 0:e.showSyntaxError)!=null?a:!1,n=(c=e==null?void 0:e.maxMessageCount)!=null?c:ds;return uo.useEffect(()=>s(d=>{if(d.type==="console"&&d.codesandbox){if(d.log.find(({method:y})=>y==="clear"))return o([en]);let h=r?d.log:d.log.filter(y=>y.data.filter(v=>typeof v!="string"?!0:Qr.filter(k=>v.startsWith(k)).length===0).length>0);if(!h)return;o(y=>{let S=[...y,...h].filter((v,g,k)=>g===k.findIndex($=>$.id===v.id));for(;S.length>ds;)S.shift();return S})}},e==null?void 0:e.clientId),[s,n,e,r]),{logs:t,reset:()=>o([])}};var ms=function(){return(0,eval)("this")}(),da=typeof ArrayBuffer=="function",ma=typeof Map=="function",ua=typeof Set=="function",Rt;(function(s){s[s.infinity=0]="infinity",s[s.minusInfinity=1]="minusInfinity",s[s.minusZero=2]="minusZero"})(Rt||(Rt={}));var tn={Arithmetic:e=>e===0?1/0:e===1?-1/0:e===2?-0:e,HTMLElement:e=>{let t=document.implementation.createHTMLDocument("sandbox");try{let o=t.createElement(e.tagName);o.innerHTML=e.innerHTML;for(let s of Object.keys(e.attributes))try{o.setAttribute(s,e.attributes[s])}catch(r){}return o}catch(o){return e}},Function:e=>{let t=()=>{};return Object.defineProperty(t,"toString",{value:()=>`function ${e.name}() {${e.body}}`}),t},"[[NaN]]":()=>NaN,"[[undefined]]":()=>{},"[[Date]]":e=>{let t=new Date;return t.setTime(e),t},"[[RegExp]]":e=>new RegExp(e.src,e.flags),"[[Error]]":e=>{let t=ms[e.name]||Error,o=new t(e.message);return o.stack=e.stack,o},"[[ArrayBuffer]]":e=>{if(da){let t=new ArrayBuffer(e.length);return new Int8Array(t).set(e),t}return e},"[[TypedArray]]":e=>typeof ms[e.ctorName]=="function"?new ms[e.ctorName](e.arr):e.arr,"[[Map]]":e=>{if(ma){let o=new Map;for(let s=0;s<e.length;s+=2)o.set(e[s],e[s+1]);return o}let t=[];for(let o=0;o<e.length;o+=2)t.push([e[i],e[i+1]]);return t},"[[Set]]":e=>{if(ua){let t=new Set;for(let o=0;o<e.length;o++)t.add(e[o]);return t}return e}};var on=e=>{if(typeof e=="string"||typeof e=="number"||e===null)return e;if(Array.isArray(e))return e.map(on);if(typeof e=="object"&&is in e){let t=e[is];return tn[t](e.data)}return e},fa=(e,t,o)=>`[${e.reduce((r,n,a)=>`${r}${a?", ":""}${Et(n,t,o)}`,"")}]`,ha=(e,t,o)=>{let s=e.constructor.name!=="Object"?`${e.constructor.name} `:"";if(o>ps)return s;let r=Object.entries(e),n=Object.entries(e).reduce((a,[c,p],d)=>{let h=d===0?"":", ",y=r.length>10?` + `:"",S=Et(p,t,o);return d===mo?a+y+"...":d>mo?a:a+`${h}${y}${c}: `+S},"");return`${s}{ ${n}${r.length>10?` +diff --git a/node_modules/@codesandbox/sandpack-react/dist/esm/index.js b/node_modules/@codesandbox/sandpack-react/dist/esm/index.js +index 985904c..eb4bfe9 100644 +--- a/node_modules/@codesandbox/sandpack-react/dist/esm/index.js ++++ b/node_modules/@codesandbox/sandpack-react/dist/esm/index.js +@@ -395,7 +395,7 @@ createApp(App).mount('#app') + <!-- built files will be auto injected --> + </body> + </html> +-`},"/package.json":{code:JSON.stringify({dependencies:{"core-js":"^3.6.5",vue:"^3.0.0-0","@vue/cli-plugin-babel":"4.5.0"},main:"/src/main.js"})}},main:"/src/App.vue",environment:"vue-cli"};var Ie={react:Dt,"react-ts":Ht,vue:Vt,vanilla:Ut,"vanilla-ts":zt,vue3:Xt,angular:Ot,svelte:_t,solid:Bt,"test-ts":jt};var De=e=>{var a,p,d,u,f,b;let t=Oe(e.files),o=Sr({template:e.template,customSetup:e.customSetup,files:t}),s=Oe((p=(a=e.options)==null?void 0:a.visibleFiles)!=null?p:[]),r=((d=e.options)==null?void 0:d.activeFile)?Yo((u=e.options)==null?void 0:u.activeFile,t||{}):void 0;s.length===0&&t&&Object.keys(t).forEach(g=>{let y=t[g];if(typeof y=="string"){s.push(g);return}!r&&y.active&&(r=g,y.hidden===!0&&s.push(g)),y.hidden||s.push(g)}),s.length===0&&(s=[o.main]),o.files[o.entry]||(o.entry=Yo(o.entry,o.files)),!r&&o.main&&(r=o.main),(!r||!o.files[r])&&(r=s[0]),s.includes(r)||s.push(r);let n=yr(o.files,(f=o.dependencies)!=null?f:{},(b=o.devDependencies)!=null?b:{},o.entry);return{visibleFiles:s.filter(g=>n[g]),activeFile:r,files:n,environment:o.environment}},Yo=(e,t)=>{let o=Oe(t),s=Oe(e);if(s in o)return s;if(!e)return null;let r=null,n=0,c=[".js",".jsx",".ts",".tsx"];for(;!r&&n<c.length;){let p=`${s.split(".")[0]}${c[n]}`;o[p]!==void 0&&(r=p),n++}return r},Sr=({files:e,template:t,customSetup:o})=>{if(!t){if(!o)return Ie.vanilla;if(!e||Object.keys(e).length===0)throw new Error("[sandpack-react]: without a template, you must pass at least one file");return{...o,files:st(e)}}let s=Ie[t];if(!s)throw new Error(`[sandpack-react]: invalid template "${t}" provided`);return!o&&!e?s:{files:st({...s.files,...e}),dependencies:{...s.dependencies,...o==null?void 0:o.dependencies},devDependencies:{...s.devDependencies,...o==null?void 0:o.devDependencies},entry:Oe((o==null?void 0:o.entry)||s.entry),main:s.main,environment:(o==null?void 0:o.environment)||s.environment}},st=e=>e?Object.keys(e).reduce((t,o)=>(typeof e[o]=="string"?t[o]={code:e[o]}:t[o]=e[o],t),{}):{};var nt=Er(null),Rr=3e4,Jo=class extends Tr{constructor(t){super(t);this.timeoutHook=null;this.initializeSandpackIframeHook=null;this.handleMessage=t=>{this.timeoutHook&&clearTimeout(this.timeoutHook),t.type==="state"?this.setState({bundlerState:t.state}):t.type==="done"&&!t.compilatonError?this.setState({error:null}):t.type==="action"&&t.action==="show-error"?this.setState({error:Cr(t)}):t.type==="action"&&t.action==="notification"&&t.notificationType==="error"&&this.setState({error:{message:t.title}})};this.registerReactDevTools=t=>{this.setState({reactDevTools:t})};this.updateCurrentFile=t=>{this.updateFile(this.state.activeFile,t)};this.updateFile=(t,o)=>{var r;let s=this.state.files;if(typeof t=="string"&&o){if(o===((r=this.state.files[t])==null?void 0:r.code))return;s={...s,[t]:{code:o}}}else typeof t=="object"&&(s={...s,...st(t)});this.setState({files:xr(s)},this.updateClients)};this.updateClients=()=>{var n,c,a,p;let{files:t,sandpackStatus:o}=this.state,s=(c=(n=this.props.options)==null?void 0:n.recompileMode)!=null?c:"delayed",r=(p=(a=this.props.options)==null?void 0:a.recompileDelay)!=null?p:500;o==="running"&&(s==="immediate"&&Object.values(this.clients).forEach(d=>{d.updatePreview({files:t})}),s==="delayed"&&(window.clearTimeout(this.debounceHook),this.debounceHook=window.setTimeout(()=>{Object.values(this.clients).forEach(d=>{d.updatePreview({files:this.state.files})})},r)))};this.createClient=(t,o)=>{var n,c,a,p,d,u,f,b,g;let s=new kr(t,{files:this.state.files,template:this.state.environment},{externalResources:(n=this.props.options)==null?void 0:n.externalResources,bundlerURL:(c=this.props.options)==null?void 0:c.bundlerURL,startRoute:(a=this.props.options)==null?void 0:a.startRoute,fileResolver:(p=this.props.options)==null?void 0:p.fileResolver,skipEval:(u=(d=this.props.options)==null?void 0:d.skipEval)!=null?u:!1,logLevel:(f=this.props.options)==null?void 0:f.logLevel,showOpenInCodeSandbox:!this.openInCSBRegistered.current,showErrorScreen:!this.errorScreenRegistered.current,showLoadingScreen:!this.loadingScreenRegistered.current,reactDevTools:this.state.reactDevTools,customNpmRegistries:(g=(b=this.props.customSetup)==null?void 0:b.npmRegistries)==null?void 0:g.map(y=>({...y,proxyEnabled:!1}))});return typeof this.unsubscribe!="function"&&(this.unsubscribe=s.listen(this.handleMessage),this.timeoutHook=setTimeout(()=>{this.setState({sandpackStatus:"timeout"})},Rr)),this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o]&&(Object.keys(this.queuedListeners[o]).forEach(y=>{let R=this.queuedListeners[o][y],N=s.listen(R);this.unsubscribeClientListeners[o][y]=N}),this.queuedListeners[o]={}),Object.entries(this.queuedListeners.global).forEach(([y,R])=>{let N=s.listen(R);this.unsubscribeClientListeners[o][y]=N}),s};this.runSandpack=()=>{Object.keys(this.preregisteredIframes).forEach(t=>{let o=this.preregisteredIframes[t];this.clients[t]=this.createClient(o,t)}),this.setState({sandpackStatus:"running"})};this.registerBundler=(t,o)=>{this.state.sandpackStatus==="running"?this.clients[o]=this.createClient(t,o):this.preregisteredIframes[o]=t};this.unregisterBundler=t=>{var r;let o=this.clients[t];o?(o.cleanup(),(r=o.iframe.contentWindow)==null||r.location.replace("about:blank"),delete this.clients[t]):delete this.preregisteredIframes[t],this.timeoutHook&&clearTimeout(this.timeoutHook),Object.values(this.unsubscribeClientListeners).forEach(n=>{Object.values(n).forEach(a=>a())}),this.setState({sandpackStatus:"idle"})};this.unregisterAllClients=()=>{Object.keys(this.clients).map(this.unregisterBundler),typeof this.unsubscribe=="function"&&(this.unsubscribe(),this.unsubscribe=void 0)};this.setActiveFile=t=>{this.setState({activeFile:t})};this.openFile=t=>{this.setState(({visibleFiles:o})=>{let s=o.includes(t)?o:[...o,t];return{activeFile:t,visibleFiles:s}})};this.closeFile=t=>{this.state.visibleFiles.length!==1&&this.setState(({visibleFiles:o,activeFile:s})=>{let r=o.indexOf(t),n=o.filter(c=>c!==t);return{activeFile:t===s?r===0?o[1]:o[r-1]:s,visibleFiles:n}})};this.deleteFile=t=>{this.setState(({visibleFiles:o,files:s})=>{let r={...s};return delete r[t],{visibleFiles:o.filter(n=>n!==t),files:r}},this.updateClients)};this.addFile=this.updateFile;this.dispatchMessage=(t,o)=>{if(this.state.sandpackStatus!=="running"){console.warn("[sandpack-react]: dispatch cannot be called while in idle mode");return}o?this.clients[o].dispatch(t):Object.values(this.clients).forEach(s=>{s.dispatch(t)})};this.addListener=(t,o)=>{if(o){if(this.clients[o])return this.clients[o].listen(t);{let s=Ae();return this.queuedListeners[o]=this.queuedListeners[o]||{},this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o][s]=t,()=>{this.queuedListeners[o][s]?delete this.queuedListeners[o][s]:this.unsubscribeClientListeners[o][s]&&(this.unsubscribeClientListeners[o][s](),delete this.unsubscribeClientListeners[o][s])}}}else{let s=Ae();this.queuedListeners.global[s]=t;let n=Object.values(this.clients).map(a=>a.listen(t));return()=>{n.forEach(a=>a())}}};this.resetFile=t=>{let{files:o}=De({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState(s=>({files:{...s.files,[t]:o[t]}}),this.updateClients)};this.resetAllFiles=()=>{let{files:t}=De({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState({files:t},this.updateClients)};this._getSandpackState=()=>{let{files:t,activeFile:o,visibleFiles:s,visibleFilesFromProps:r,startRoute:n,bundlerState:c,editorState:a,error:p,sandpackStatus:d,environment:u,initMode:f}=this.state;return{files:t,environment:u,visibleFiles:s,visibleFilesFromProps:r,activeFile:o,startRoute:n,error:p,bundlerState:c,status:d,editorState:a,initMode:f,clients:this.clients,dispatch:this.dispatchMessage,errorScreenRegisteredRef:this.errorScreenRegistered,lazyAnchorRef:this.lazyAnchorRef,listen:this.addListener,loadingScreenRegisteredRef:this.loadingScreenRegistered,openInCSBRegisteredRef:this.openInCSBRegistered,registerBundler:this.registerBundler,runSandpack:this.runSandpack,unregisterBundler:this.unregisterBundler,registerReactDevTools:this.registerReactDevTools,openFile:this.openFile,resetFile:this.resetFile,resetAllFiles:this.resetAllFiles,setActiveFile:this.setActiveFile,updateCurrentFile:this.updateCurrentFile,updateFile:this.updateFile,addFile:this.addFile,closeFile:this.closeFile,deleteFile:this.deleteFile}};var c,a,p,d;let{activeFile:o,visibleFiles:s,files:r,environment:n}=De({template:t.template,files:t.files,customSetup:t.customSetup,options:t.options});this.state={files:r,environment:n,visibleFiles:s,visibleFilesFromProps:s,activeFile:o,startRoute:(c=this.props.options)==null?void 0:c.startRoute,bundlerState:void 0,error:null,sandpackStatus:((p=(a=this.props.options)==null?void 0:a.autorun)!=null?p:!0)?"initial":"idle",editorState:"pristine",initMode:((d=this.props.options)==null?void 0:d.initMode)||"lazy",reactDevTools:void 0},this.queuedListeners={global:{}},this.unsubscribeClientListeners={},this.preregisteredIframes={},this.clients={},this.lazyAnchorRef=at(),this.errorScreenRegistered=at(),this.openInCSBRegistered=at(),this.loadingScreenRegistered=at()}initializeSandpackIframe(){var s,r,n,c,a;if(!((r=(s=this.props.options)==null?void 0:s.autorun)!=null?r:!0))return;let o=(c=(n=this.props.options)==null?void 0:n.initModeObserverOptions)!=null?c:{rootMargin:"1000px 0px"};this.intersectionObserver&&this.lazyAnchorRef.current&&((a=this.intersectionObserver)==null||a.unobserve(this.lazyAnchorRef.current)),this.lazyAnchorRef.current&&this.state.initMode==="lazy"?(this.intersectionObserver=new IntersectionObserver(p=>{var d;p.some(u=>u.isIntersecting)&&(this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50),this.lazyAnchorRef.current&&((d=this.intersectionObserver)==null||d.unobserve(this.lazyAnchorRef.current)))},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.lazyAnchorRef.current&&this.state.initMode==="user-visible"?(this.intersectionObserver=new IntersectionObserver(p=>{p.some(d=>d.isIntersecting)?this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50):(this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),Object.keys(this.clients).map(this.unregisterBundler),this.unregisterAllClients())},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.initializeSandpackIframeHook=setTimeout(()=>this.runSandpack(),50)}componentDidMount(){this.initializeSandpackIframe()}componentDidUpdate(t){var a,p,d,u;((a=t.options)==null?void 0:a.initMode)!==((p=this.props.options)==null?void 0:p.initMode)&&((d=this.props.options)==null?void 0:d.initMode)&&this.setState({initMode:(u=this.props.options)==null?void 0:u.initMode},this.initializeSandpackIframe);let{activeFile:o,visibleFiles:s,files:r,environment:n}=De({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});if(t.template!==this.props.template||!rt(t.options,this.props.options)||!rt(t.customSetup,this.props.customSetup)||!rt(t.files,this.props.files)){if(this.setState({activeFile:o,visibleFiles:s,visibleFilesFromProps:s,files:r,environment:n}),this.state.sandpackStatus!=="running")return;Object.values(this.clients).forEach(f=>f.updatePreview({files:r,template:n}))}let c=rt(r,this.state.files)?"pristine":"dirty";c!==this.state.editorState&&this.setState({editorState:c})}componentWillUnmount(){typeof this.unsubscribe=="function"&&this.unsubscribe(),this.timeoutHook&&clearTimeout(this.timeoutHook),this.debounceHook&&clearTimeout(this.debounceHook),this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),this.intersectionObserver&&this.intersectionObserver.disconnect()}render(){var n;let{children:t,theme:o,className:s,style:r}=this.props;return Wt(nt.Provider,{value:this._getSandpackState()},Wt(vr,{classes:(n=this.props.options)==null?void 0:n.classes},Wt(Go,{className:s,style:r,theme:o},t)))}},qo=Jo,dc=nt.Consumer;function x(){let e=Nr(nt);if(e===null)throw new Error('[sandpack-react]: "useSandpack" must be wrapped by a "SandpackProvider"');let{dispatch:t,listen:o,...s}=e;return{sandpack:{...s},dispatch:t,listen:o}}var it=()=>{var t,o,s;let{sandpack:e}=x();return{code:(t=e.files[e.activeFile])==null?void 0:t.code,readOnly:(s=(o=e.files[e.activeFile])==null?void 0:o.readOnly)!=null?s:!1,updateCode:e.updateCurrentFile}};import{useClasser as Mr}from"@code-hike/classer";import{createElement as Be}from"react";var ee=m({svg:{margin:"auto"}}),T=m({appearance:"none",border:"0",outline:"none",display:"flex",alignItems:"center",fontSize:"inherit",fontFamily:"inherit",backgroundColor:"transparent",transition:"color $default, background $default",cursor:"pointer",color:"$colors$clickable","&:disabled":{color:"$colors$disabled"},"&:hover:not(:disabled,[data-active='true'])":{color:"$colors$hover"},'&[data-active="true"]':{color:"$colors$accent"},svg:{minWidth:"$space$4",width:"$space$4",height:"$space$4"},[`&.${ee}`]:{padding:"$space$1",width:"$space$7",height:"$space$7",display:"flex"}}),z=m({backgroundColor:"$colors$surface2",borderRadius:"99999px",'&[data-active="true"]':{color:"$colors$surface1",background:"$colors$accent"},"&:hover:not(:disabled,[data-active='true'])":{backgroundColor:"$colors$surface3"}}),Ko=m({padding:0}),$r=tt({"0%":{opacity:0,transform:"translateY(4px)"},"100%":{opacity:1,transform:"translateY(0)"}}),He=m({position:"absolute",bottom:"0",left:"0",right:"0",top:"0",margin:"0",overflow:"auto",height:"100%",zIndex:"$top"}),ct=m({padding:"$space$4",whiteSpace:"pre-wrap",fontFamily:"$font$mono",backgroundColor:"$colors$errorSurface"}),ke=m({animation:`${$r} 150ms ease`,color:"$colors$error"});var Lr=m({borderBottom:"1px solid $colors$surface2",background:"$colors$surface1"}),wr=m({padding:"0 $space$2",overflow:"auto",display:"flex",flexWrap:"nowrap",alignItems:"stretch",minHeight:"40px",marginBottom:"-1px"}),Qo=m({padding:"0 $space$1 0 $space$1",borderRadius:"$border$radius",marginLeft:"$space$1",width:"$space$5",visibility:"hidden",svg:{width:"$space$3",height:"$space$3",display:"block",position:"relative",top:1}}),Fr=m({padding:"0 $space$2",height:"$layout$headerHeight",whiteSpace:"nowrap","&:focus":{outline:"none"},[`&:hover > .${Qo}`]:{visibility:"unset"}}),lt=({closableTabs:e,className:t,...o})=>{let{sandpack:s}=x(),r=Mr(h),{activeFile:n,visibleFiles:c,setActiveFile:a}=s,p=u=>{u.stopPropagation();let f=u.target.closest("[data-active]"),b=f==null?void 0:f.getAttribute("title");!b||s.closeFile(b)},d=u=>{let f=ve(u),b=c.reduce((g,y)=>(y===u||ve(y)===f&&g.push(y),g),[]);return b.length===0?f:_o(u,b)};return Be("div",{className:l(r("tabs"),Lr,t),translate:"no",...o},Be("div",{"aria-label":"Select active file",className:l(r("tabs-scrollable-container"),wr),role:"tablist"},c.map(u=>Be("button",{key:u,"aria-selected":u===n,className:l(r("tab-button"),T,Fr),"data-active":u===n,onClick:()=>a(u),role:"tab",title:u,type:"button"},d(u),e&&c.length>1&&Be("span",{className:l(r("close-button"),Qo),onClick:p},Be(Do,null))))))};import{useClasser as Ar}from"@code-hike/classer";import{createElement as es}from"react";var Pr=m({position:"absolute",bottom:"$space$2",right:"$space$2",paddingRight:"$space$3"}),pt=({className:e,onClick:t,...o})=>{let s=Ar(h),{sandpack:r}=x();return es("button",{className:l(s("button"),T,z,Pr,e),onClick:n=>{r.runSandpack(),t==null||t(n)},type:"button",...o},es(Ke,null),"Run")};import{useClasser as Ir}from"@code-hike/classer";import{createElement as Or}from"react";var me=m({display:"flex",flexDirection:"column",width:"100%",position:"relative",backgroundColor:"$colors$surface1",transition:"flex $transitions$default",gap:1,[`&:has(.${h}-stack)`]:{backgroundColor:"$colors$surface2"}}),Y=({className:e,...t})=>{let o=Ir(h);return Or("div",{className:l(o("stack"),me,e),...t})};import{useClasser as Yr}from"@code-hike/classer";import{closeBrackets as Jr,closeBracketsKeymap as qr}from"@codemirror/closebrackets";import{defaultKeymap as Kr,indentLess as Qr,indentMore as en,deleteGroupBackward as tn}from"@codemirror/commands";import{commentKeymap as on}from"@codemirror/comment";import{lineNumbers as sn}from"@codemirror/gutter";import{defaultHighlightStyle as rn}from"@codemirror/highlight";import{history as nn,historyKeymap as an}from"@codemirror/history";import{bracketMatching as cn}from"@codemirror/matchbrackets";import{EditorState as ds,EditorSelection as ln,StateEffect as ms}from"@codemirror/state";import{Annotation as us}from"@codemirror/state";import{highlightSpecialChars as pn,highlightActiveLine as dn,keymap as fs,EditorView as Jt}from"@codemirror/view";import mn from"@react-hook/intersection-observer";import{Fragment as gn,createElement as Ee,forwardRef as un,useEffect as Re,useImperativeHandle as fn,useMemo as hn,useRef as gt,useState as hs}from"react";import{useContext as Dr}from"react";var Ce=()=>{let{theme:e,id:t,mode:o}=Dr(ot);return{theme:e,themeId:t,themeMode:o}};var Gt=(e,t)=>{if(e.length!==t.length)return!1;let o=!0;for(let s=0;s<e.length;s++)if(e[s]!==t[s]){o=!1;break}return o};import{Decoration as mt,ViewPlugin as zr}from"@codemirror/view";import{HighlightStyle as Hr,tags as A}from"@codemirror/highlight";import{css as Br}from"@codemirror/lang-css";import{html as _r}from"@codemirror/lang-html";import{javascript as ts}from"@codemirror/lang-javascript";import{EditorView as jr}from"@codemirror/view";import{useCallback as Ur}from"react";var _e=(e,{line:t,column:o})=>e.line(t).from+(o!=null?o:0)-1,os=()=>jr.theme({"&":{backgroundColor:`var(--${h}-colors-surface1)`,color:`var(--${h}-syntax-color-plain)`,height:"100%"},".cm-matchingBracket, .cm-nonmatchingBracket, &.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{color:"inherit",backgroundColor:"rgba(128,128,128,.25)",backgroundBlendMode:"difference"},"&.cm-editor.cm-focused":{outline:"none"},".cm-activeLine":{backgroundColor:`var(--${h}-colors-surface3)`,borderRadius:`var(--${h}-border-radius)`},".cm-errorLine":{backgroundColor:`var(--${h}-colors-errorSurface)`,borderRadius:`var(--${h}-border-radius)`},".cm-content":{caretColor:`var(--${h}-colors-accent)`,padding:`0 var(--${h}-space-4)`},".cm-scroller":{fontFamily:`var(--${h}-font-mono)`,lineHeight:`var(--${h}-font-lineHeight)`},".cm-gutters":{backgroundColor:`var(--${h}-colors-surface1)`,color:`var(--${h}-colors-disabled)`,border:"none",paddingLeft:`var(--${h}-space-1)`},".cm-gutter.cm-lineNumbers":{fontSize:".6em"},".cm-lineNumbers .cm-gutterElement":{lineHeight:`var(--${h}-font-lineHeight)`,minWidth:`var(--${h}-space-5)`},".cm-content .cm-line":{paddingLeft:`var(--${h}-space-1)`},".cm-content.cm-readonly .cm-line":{paddingLeft:0}}),te=e=>`${h}-syntax-${e}`,ss=()=>["string","plain","comment","keyword","definition","punctuation","property","tag","static"].reduce((t,o)=>({...t,[`.${te(o)}`]:{color:`$syntax$color$${o}`,fontStyle:`$syntax$fontStyle$${o}`}}),{}),rs=e=>Hr.define([{tag:A.link,textDecoration:"underline"},{tag:A.emphasis,fontStyle:"italic"},{tag:A.strong,fontWeight:"bold"},{tag:A.keyword,class:te("keyword")},{tag:[A.atom,A.number,A.bool],class:te("static")},{tag:A.tagName,class:te("tag")},{tag:A.variableName,class:te("plain")},{tag:A.function(A.variableName),class:te("definition")},{tag:A.definition(A.function(A.variableName)),class:te("definition")},{tag:A.propertyName,class:te("property")},{tag:[A.literal,A.inserted],class:te(e.syntax.string?"string":"static")},{tag:A.punctuation,class:te("punctuation")},{tag:[A.comment,A.quote],class:te("comment")}]),ns=(e,t,o)=>{if(!e&&!t)return"javascript";let s=t;if(!s&&e){let r=e.lastIndexOf(".");s=e.slice(r+1)}for(let r of o)if(s===r.name||r.extensions.includes(s||""))return r.name;switch(s){case"ts":case"tsx":return"typescript";case"html":case"svelte":case"vue":return"html";case"css":case"less":case"scss":return"css";case"js":case"jsx":case"json":default:return"javascript"}},as=(e,t)=>{let o={javascript:ts({jsx:!0,typescript:!1}),typescript:ts({jsx:!0,typescript:!0}),html:_r(),css:Br()};for(let s of t)if(e===s.name)return s.language;return o[e]},dt=(...e)=>Ur(t=>e.forEach(o=>{if(!!o){if(typeof o=="function")return o(t);o.current=t}}),e);function is(e){return zr.fromClass(class{constructor(t){this.decorations=this.getDecoration(t)}update(t){}getDecoration(t){if(!e)return mt.none;let o=e.map(s=>{var a,p,d;let r=mt.line({attributes:{class:(a=s.className)!=null?a:""}}),n=mt.mark({class:(p=s.className)!=null?p:"",attributes:(d=s.elementAttributes)!=null?d:void 0}),c=_e(t.state.doc,{line:s.line,column:s.startColumn})+1;if(s.startColumn&&s.endColumn){let u=_e(t.state.doc,{line:s.line,column:s.endColumn})+1;return n.range(c,u)}return r.range(c)});return mt.set(o)}},{decorations:t=>t.decorations})}import{Decoration as ut,ViewPlugin as Vr}from"@codemirror/view";function cs(){return Wr}var Xr=ut.line({attributes:{class:"cm-errorLine"}}),Wr=Vr.fromClass(class{constructor(){this.decorations=ut.none}update(e){e.transactions.forEach(t=>{let o=t.annotation("show-error");if(o!==void 0){let s=_e(e.view.state.doc,{line:o})+1;this.decorations=ut.set([Xr.range(s)])}else t.annotation("remove-errors")&&(this.decorations=ut.none)})}},{decorations:e=>e.decorations});var ft=m({margin:"0",display:"block",fontFamily:"$font$mono",fontSize:"$font$size",color:"$syntax$color$plain",lineHeight:"$font$lineHeight"}),Zt=m(ss()),ht=m({flex:1,position:"relative",overflow:"auto",background:"$colors$surface1",".cm-scroller":{padding:"$space$4 0"},[`.${ft}`]:{padding:"$space$4 0"}}),Yt=m({margin:"0",outline:"none",height:"100%"}),ls=m({fontFamily:"$font$mono",fontSize:"0.8em",position:"absolute",right:"$space$2",bottom:"$space$2",zIndex:"$top",color:"$colors$clickable",backgroundColor:"$colors$surface2",borderRadius:"99999px",padding:"calc($space$1 / 2) $space$2",[`& + .${T}`]:{right:"calc($space$11 * 2)"}});import{highlightTree as Gr}from"@codemirror/highlight";import{createElement as Zr}from"react";var ps=({langSupport:e,highlightTheme:t,code:o=""})=>{let s=e.language.parser.parse(o),r=0,n=[],c=(a,p)=>{if(a>r){let d=o.slice(r,a);n.push(p?Zr("span",{children:d,className:p,key:`${a}${r}`}):d),r=a}};return Gr(s,t.match,(a,p,d)=>{c(a,""),c(p,d)}),r<o.length&&n.push(` ++`},"/package.json":{code:JSON.stringify({dependencies:{"core-js":"^3.6.5",vue:"^3.0.0-0","@vue/cli-plugin-babel":"4.5.0"},main:"/src/main.js"})}},main:"/src/App.vue",environment:"vue-cli"};var Ie={react:Dt,"react-ts":Ht,vue:Vt,vanilla:Ut,"vanilla-ts":zt,vue3:Xt,angular:Ot,svelte:_t,solid:Bt,"test-ts":jt};var De=e=>{var a,p,d,u,f,b;let t=Oe(e.files),o=Sr({template:e.template,customSetup:e.customSetup,files:t}),s=Oe((p=(a=e.options)==null?void 0:a.visibleFiles)!=null?p:[]),r=((d=e.options)==null?void 0:d.activeFile)?Yo((u=e.options)==null?void 0:u.activeFile,t||{}):void 0;s.length===0&&t&&Object.keys(t).forEach(g=>{let y=t[g];if(typeof y=="string"){s.push(g);return}!r&&y.active&&(r=g,y.hidden===!0&&s.push(g)),y.hidden||s.push(g)}),s.length===0&&(s=[o.main]),o.files[o.entry]||(o.entry=Yo(o.entry,o.files)),!r&&o.main&&(r=o.main),(!r||!o.files[r])&&(r=s[0]),s.includes(r)||s.push(r);let n=yr(o.files,(f=o.dependencies)!=null?f:{},(b=o.devDependencies)!=null?b:{},o.entry);return{visibleFiles:s.filter(g=>n[g]),activeFile:r,files:n,environment:o.environment}},Yo=(e,t)=>{let o=Oe(t),s=Oe(e);if(s in o)return s;if(!e)return null;let r=null,n=0,c=[".js",".jsx",".ts",".tsx"];for(;!r&&n<c.length;){let p=`${s.split(".")[0]}${c[n]}`;o[p]!==void 0&&(r=p),n++}return r},Sr=({files:e,template:t,customSetup:o})=>{if(!t){if(!o)return Ie.vanilla;if(!e||Object.keys(e).length===0)throw new Error("[sandpack-react]: without a template, you must pass at least one file");return{...o,files:st(e)}}let s=Ie[t];if(!s)throw new Error(`[sandpack-react]: invalid template "${t}" provided`);return!o&&!e?s:{files:st({...s.files,...e}),dependencies:{...s.dependencies,...o==null?void 0:o.dependencies},devDependencies:{...s.devDependencies,...o==null?void 0:o.devDependencies},entry:Oe((o==null?void 0:o.entry)||s.entry),main:s.main,environment:(o==null?void 0:o.environment)||s.environment}},st=e=>e?Object.keys(e).reduce((t,o)=>(typeof e[o]=="string"?t[o]={code:e[o]}:t[o]=e[o],t),{}):{};var nt=Er(null),Rr=3e4,Jo=class extends Tr{constructor(t){super(t);this.timeoutHook=null;this.initializeSandpackIframeHook=null;this.handleMessage=t=>{this.timeoutHook&&clearTimeout(this.timeoutHook),t.type==="state"?this.setState({bundlerState:t.state}):t.type==="done"&&!t.compilatonError?this.setState({error:null}):t.type==="action"&&t.action==="show-error"?this.setState({error:Cr(t)}):t.type==="action"&&t.action==="notification"&&t.notificationType==="error"&&this.setState({error:{message:t.title}})};this.registerReactDevTools=t=>{this.setState({reactDevTools:t})};this.updateCurrentFile=t=>{this.updateFile(this.state.activeFile,t)};this.updateFile=(t,o)=>{var r;let s=this.state.files;if(typeof t=="string"&&o){if(o===((r=this.state.files[t])==null?void 0:r.code))return;s={...s,[t]:{code:o}}}else typeof t=="object"&&(s={...s,...st(t)});this.setState({files:xr(s)},this.updateClients)};this.updateClients=()=>{var n,c,a,p;let{files:t,sandpackStatus:o}=this.state,s=(c=(n=this.props.options)==null?void 0:n.recompileMode)!=null?c:"delayed",r=(p=(a=this.props.options)==null?void 0:a.recompileDelay)!=null?p:500;o==="running"&&(s==="immediate"&&Object.values(this.clients).forEach(d=>{d.updatePreview({files:t})}),s==="delayed"&&(window.clearTimeout(this.debounceHook),this.debounceHook=window.setTimeout(()=>{Object.values(this.clients).forEach(d=>{d.updatePreview({files:this.state.files})})},r)))};this.createClient=(t,o)=>{var n,c,a,p,d,u,f,b,g;let s=new kr(t,{files:this.state.files,template:this.state.environment},{externalResources:(n=this.props.options)==null?void 0:n.externalResources,bundlerURL:(c=this.props.options)==null?void 0:c.bundlerURL,startRoute:(a=this.props.options)==null?void 0:a.startRoute,fileResolver:(p=this.props.options)==null?void 0:p.fileResolver,skipEval:(u=(d=this.props.options)==null?void 0:d.skipEval)!=null?u:!1,logLevel:(f=this.props.options)==null?void 0:f.logLevel,showOpenInCodeSandbox:!this.openInCSBRegistered.current,showErrorScreen:!this.errorScreenRegistered.current,showLoadingScreen:!this.loadingScreenRegistered.current,reactDevTools:this.state.reactDevTools,customNpmRegistries:(g=(b=this.props.customSetup)==null?void 0:b.npmRegistries)==null?void 0:g.map(y=>({...y,proxyEnabled:!1}))});return typeof this.unsubscribe!="function"&&(this.unsubscribe=s.listen(this.handleMessage),this.timeoutHook=setTimeout(()=>{this.setState({sandpackStatus:"timeout"})},Rr)),this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o]&&(Object.keys(this.queuedListeners[o]).forEach(y=>{let R=this.queuedListeners[o][y],N=s.listen(R);this.unsubscribeClientListeners[o][y]=N}),this.queuedListeners[o]={}),Object.entries(this.queuedListeners.global).forEach(([y,R])=>{let N=s.listen(R);this.unsubscribeClientListeners[o][y]=N}),s};this.runSandpack=()=>{Object.keys(this.preregisteredIframes).forEach(t=>{let o=this.preregisteredIframes[t];this.clients[t]=this.createClient(o,t)}),this.setState({sandpackStatus:"running"})};this.registerBundler=(t,o)=>{this.state.sandpackStatus==="running"?this.clients[o]=this.createClient(t,o):this.preregisteredIframes[o]=t};this.unregisterBundler=t=>{var r;let o=this.clients[t];o?(o.cleanup(),(r=o.iframe.contentWindow)==null||r.location.replace("about:blank"),delete this.clients[t]):delete this.preregisteredIframes[t],this.timeoutHook&&clearTimeout(this.timeoutHook),Object.values(this.unsubscribeClientListeners).forEach(n=>{Object.values(n).forEach(a=>a())}),this.setState({sandpackStatus:"idle"})};this.unregisterAllClients=()=>{Object.keys(this.clients).map(this.unregisterBundler),typeof this.unsubscribe=="function"&&(this.unsubscribe(),this.unsubscribe=void 0)};this.setActiveFile=t=>{this.setState({activeFile:t})};this.openFile=t=>{this.setState(({visibleFiles:o})=>{let s=o.includes(t)?o:[...o,t];return{activeFile:t,visibleFiles:s}})};this.closeFile=t=>{this.state.visibleFiles.length!==1&&this.setState(({visibleFiles:o,activeFile:s})=>{let r=o.indexOf(t),n=o.filter(c=>c!==t);return{activeFile:t===s?r===0?o[1]:o[r-1]:s,visibleFiles:n}})};this.deleteFile=t=>{this.setState(({visibleFiles:o,files:s})=>{let r={...s};return delete r[t],{visibleFiles:o.filter(n=>n!==t),files:r}},this.updateClients)};this.addFile=this.updateFile;this.dispatchMessage=(t,o)=>{if(this.state.sandpackStatus!=="running"){console.warn("[sandpack-react]: dispatch cannot be called while in idle mode");return}o?this.clients[o].dispatch(t):Object.values(this.clients).forEach(s=>{s.dispatch(t)})};this.addListener=(t,o)=>{if(o){if(this.clients[o])return this.clients[o].listen(t);{let s=Ae();return this.queuedListeners[o]=this.queuedListeners[o]||{},this.unsubscribeClientListeners[o]=this.unsubscribeClientListeners[o]||{},this.queuedListeners[o][s]=t,()=>{this.queuedListeners[o][s]?delete this.queuedListeners[o][s]:this.unsubscribeClientListeners[o][s]&&(this.unsubscribeClientListeners[o][s](),delete this.unsubscribeClientListeners[o][s])}}}else{let s=Ae();this.queuedListeners.global[s]=t;let n=Object.values(this.clients).map(a=>a.listen(t));return()=>{n.forEach(a=>a())}}};this.resetFile=t=>{let{files:o}=De({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState(s=>({files:{...s.files,[t]:o[t]}}),this.updateClients)};this.resetAllFiles=()=>{let{files:t}=De({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});this.setState({files:t},this.updateClients)};this._getSandpackState=()=>{let{files:t,activeFile:o,visibleFiles:s,visibleFilesFromProps:r,startRoute:n,bundlerState:c,editorState:a,error:p,sandpackStatus:d,environment:u,initMode:f}=this.state;return{files:t,environment:u,visibleFiles:s,visibleFilesFromProps:r,activeFile:o,startRoute:n,error:p,bundlerState:c,status:d,editorState:a,initMode:f,clients:this.clients,dispatch:this.dispatchMessage,errorScreenRegisteredRef:this.errorScreenRegistered,lazyAnchorRef:this.lazyAnchorRef,listen:this.addListener,loadingScreenRegisteredRef:this.loadingScreenRegistered,openInCSBRegisteredRef:this.openInCSBRegistered,registerBundler:this.registerBundler,runSandpack:this.runSandpack,unregisterBundler:this.unregisterBundler,registerReactDevTools:this.registerReactDevTools,openFile:this.openFile,resetFile:this.resetFile,resetAllFiles:this.resetAllFiles,setActiveFile:this.setActiveFile,updateCurrentFile:this.updateCurrentFile,updateFile:this.updateFile,addFile:this.addFile,closeFile:this.closeFile,deleteFile:this.deleteFile}};var c,a,p,d;let{activeFile:o,visibleFiles:s,files:r,environment:n}=De({template:t.template,files:t.files,customSetup:t.customSetup,options:t.options});this.state={files:r,environment:n,visibleFiles:s,visibleFilesFromProps:s,activeFile:o,startRoute:(c=this.props.options)==null?void 0:c.startRoute,bundlerState:void 0,error:null,sandpackStatus:((p=(a=this.props.options)==null?void 0:a.autorun)!=null?p:!0)?"initial":"idle",editorState:"pristine",initMode:((d=this.props.options)==null?void 0:d.initMode)||"lazy",reactDevTools:void 0},this.queuedListeners={global:{}},this.unsubscribeClientListeners={},this.preregisteredIframes={},this.clients={},this.lazyAnchorRef=at(),this.errorScreenRegistered=at(),this.openInCSBRegistered=at(),this.loadingScreenRegistered=at()}initializeSandpackIframe(){var s,r,n,c,a;if(!((r=(s=this.props.options)==null?void 0:s.autorun)!=null?r:!0))return;let o=(c=(n=this.props.options)==null?void 0:n.initModeObserverOptions)!=null?c:{rootMargin:"1000px 0px"};this.intersectionObserver&&this.lazyAnchorRef.current&&((a=this.intersectionObserver)==null||a.unobserve(this.lazyAnchorRef.current)),this.lazyAnchorRef.current&&this.state.initMode==="lazy"?(this.intersectionObserver=new IntersectionObserver(p=>{var d;p.some(u=>u.isIntersecting)&&(this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50),this.lazyAnchorRef.current&&((d=this.intersectionObserver)==null||d.unobserve(this.lazyAnchorRef.current)))},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.lazyAnchorRef.current&&this.state.initMode==="user-visible"?(this.intersectionObserver=new IntersectionObserver(p=>{p.some(d=>d.isIntersecting)?this.initializeSandpackIframeHook=setTimeout(()=>{this.runSandpack()},50):(this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),Object.keys(this.clients).map(this.unregisterBundler),this.unregisterAllClients())},o),this.intersectionObserver.observe(this.lazyAnchorRef.current)):this.initializeSandpackIframeHook=setTimeout(()=>this.runSandpack(),50)}componentDidMount(){this.initializeSandpackIframe()}componentDidUpdate(t){var a,p,d,u;((a=t.options)==null?void 0:a.initMode)!==((p=this.props.options)==null?void 0:p.initMode)&&((d=this.props.options)==null?void 0:d.initMode)&&this.setState({initMode:(u=this.props.options)==null?void 0:u.initMode},this.initializeSandpackIframe);let{activeFile:o,visibleFiles:s,files:r,environment:n}=De({template:this.props.template,files:this.props.files,customSetup:this.props.customSetup,options:this.props.options});if(t.template!==this.props.template||!rt(t.options,this.props.options)||!rt(t.customSetup,this.props.customSetup)||!rt(t.files,this.props.files)){if(this.setState({activeFile:o,visibleFiles:s,visibleFilesFromProps:s,files:r,environment:n}),this.state.sandpackStatus!=="running")return;Object.values(this.clients).forEach(f=>f.updatePreview({files:r,template:n}))}let c=rt(r,this.state.files)?"pristine":"dirty";c!==this.state.editorState&&this.setState({editorState:c})}componentWillUnmount(){typeof this.unsubscribe=="function"&&this.unsubscribe(),this.timeoutHook&&clearTimeout(this.timeoutHook),this.debounceHook&&clearTimeout(this.debounceHook),this.initializeSandpackIframeHook&&clearTimeout(this.initializeSandpackIframeHook),this.intersectionObserver&&this.intersectionObserver.disconnect()}render(){var n;let{children:t,theme:o,className:s,style:r}=this.props;return Wt(nt.Provider,{value:this._getSandpackState()},Wt(vr,{classes:(n=this.props.options)==null?void 0:n.classes},Wt(Go,{className:s,style:r,theme:o},t)))}},qo=Jo,dc=nt.Consumer;function x(){let e=Nr(nt);if(e===null)throw new Error('[sandpack-react]: "useSandpack" must be wrapped by a "SandpackProvider"');let{dispatch:t,listen:o,...s}=e;return{sandpack:{...s},dispatch:t,listen:o}}var it=()=>{var t,o,s;let{sandpack:e}=x();return{code:(t=e.files[e.activeFile])==null?void 0:t.code,readOnly:(s=(o=e.files[e.activeFile])==null?void 0:o.readOnly)!=null?s:!1,updateCode:e.updateCurrentFile}};import{useClasser as Mr}from"@code-hike/classer";import{createElement as Be}from"react";var ee=m({svg:{margin:"auto"}}),T=m({appearance:"none",border:"0",outline:"none",display:"flex",alignItems:"center",fontSize:"inherit",fontFamily:"inherit",backgroundColor:"transparent",transition:"color $default, background $default",cursor:"pointer",color:"$colors$clickable","&:disabled":{color:"$colors$disabled"},"&:hover:not(:disabled,[data-active='true'])":{color:"$colors$hover"},'&[data-active="true"]':{color:"$colors$accent"},svg:{minWidth:"$space$4",width:"$space$4",height:"$space$4"},[`&.${ee}`]:{padding:"$space$1",width:"$space$7",height:"$space$7",display:"flex"}}),z=m({backgroundColor:"$colors$surface2",borderRadius:"99999px",'&[data-active="true"]':{color:"$colors$surface1",background:"$colors$accent"},"&:hover:not(:disabled,[data-active='true'])":{backgroundColor:"$colors$surface3"}}),Ko=m({padding:0}),$r=tt({"0%":{opacity:0,transform:"translateY(4px)"},"100%":{opacity:1,transform:"translateY(0)"}}),He=m({position:"absolute",bottom:"0",left:"0",right:"0",top:"0",margin:"0",overflow:"auto",height:"100%",zIndex:"$top"}),ct=m({padding:"$space$4",whiteSpace:"pre-wrap",fontFamily:"$font$mono",backgroundColor:"$colors$errorSurface"}),ke=m({animation:`${$r} 150ms ease`,color:"$colors$error"});var Lr=m({borderBottom:"1px solid $colors$surface2",background:"$colors$surface1"}),wr=m({padding:"0 $space$2",overflow:"auto",display:"flex",flexWrap:"nowrap",alignItems:"stretch",minHeight:"40px",marginBottom:"-1px"}),Qo=m({padding:"0 $space$1 0 $space$1",borderRadius:"$border$radius",marginLeft:"$space$1",width:"$space$5",visibility:"hidden",svg:{width:"$space$3",height:"$space$3",display:"block",position:"relative",top:1}}),Fr=m({padding:"0 $space$2",height:"$layout$headerHeight",whiteSpace:"nowrap","&:focus":{outline:"none"},[`&:hover > .${Qo}`]:{visibility:"unset"}}),lt=({closableTabs:e,className:t,...o})=>{let{sandpack:s}=x(),r=Mr(h),{activeFile:n,visibleFiles:c,setActiveFile:a}=s,p=u=>{u.stopPropagation();let f=u.target.closest("[data-active]"),b=f==null?void 0:f.getAttribute("title");!b||s.closeFile(b)},d=u=>{let f=ve(u),b=c.reduce((g,y)=>(y===u||ve(y)===f&&g.push(y),g),[]);return b.length===0?f:_o(u,b)};return Be("div",{className:l(r("tabs"),Lr,t),translate:"no",...o},Be("div",{"aria-label":"Select active file",className:l(r("tabs-scrollable-container"),wr),role:"tablist"},c.map(u=>Be("button",{key:u,"aria-selected":u===n,className:l(r("tab-button"),T,Fr),"data-active":u===n,onClick:()=>a(u),role:"tab",title:u,type:"button"},d(u),e&&c.length>1&&Be("span",{className:l(r("close-button"),Qo),onClick:p},Be(Do,null))))))};import{useClasser as Ar}from"@code-hike/classer";import{createElement as es}from"react";var Pr=m({position:"absolute",bottom:"$space$2",right:"$space$2",paddingRight:"$space$3"}),pt=({className:e,onClick:t,...o})=>{let s=Ar(h),{sandpack:r}=x();return es("button",{className:l(s("button"),T,z,Pr,e),onClick:n=>{r.runSandpack(),t==null||t(n)},type:"button",...o},es(Ke,null),"Run")};import{useClasser as Ir}from"@code-hike/classer";import{createElement as Or}from"react";var me=m({display:"flex",flexDirection:"column",width:"100%",position:"relative",backgroundColor:"$colors$surface1",transition:"flex $transitions$default",gap:1,[`&:has(.${h}-stack)`]:{backgroundColor:"$colors$surface2"}}),Y=({className:e,...t})=>{let o=Ir(h);return Or("div",{className:l(o("stack"),me,e),...t})};import{useClasser as Yr}from"@code-hike/classer";import{closeBrackets as Jr,closeBracketsKeymap as qr}from"@codemirror/closebrackets";import{defaultKeymap as Kr,indentLess as Qr,indentMore as en,deleteGroupBackward as tn}from"@codemirror/commands";import{commentKeymap as on}from"@codemirror/comment";import{lineNumbers as sn}from"@codemirror/gutter";import{defaultHighlightStyle as rn}from"@codemirror/highlight";import{history as nn,historyKeymap as an}from"@codemirror/history";import{bracketMatching as cn}from"@codemirror/matchbrackets";import{EditorState as ds,EditorSelection as ln,StateEffect as ms}from"@codemirror/state";import{Annotation as us}from"@codemirror/state";import{highlightSpecialChars as pn,highlightActiveLine as dn,keymap as fs,EditorView as Jt}from"@codemirror/view";import mn from"@react-hook/intersection-observer";import{Fragment as gn,createElement as Ee,forwardRef as un,useEffect as Re,useImperativeHandle as fn,useMemo as hn,useRef as gt,useState as hs}from"react";import{useContext as Dr}from"react";var Ce=()=>{let{theme:e,id:t,mode:o}=Dr(ot);return{theme:e,themeId:t,themeMode:o}};var Gt=(e,t)=>{if(e.length!==t.length)return!1;let o=!0;for(let s=0;s<e.length;s++)if(e[s]!==t[s]){o=!1;break}return o};import{Decoration as mt,ViewPlugin as zr}from"@codemirror/view";import{HighlightStyle as Hr,tags as A}from"@codemirror/highlight";import{css as Br}from"@codemirror/lang-css";import{html as _r}from"@codemirror/lang-html";import{javascript as ts}from"@codemirror/lang-javascript";import{EditorView as jr}from"@codemirror/view";import{useCallback as Ur}from"react";var _e=(e,{line:t,column:o})=>e.line(t).from+(o!=null?o:0)-1,os=()=>jr.theme({"&":{backgroundColor:`var(--${h}-colors-surface1)`,color:`var(--${h}-syntax-color-plain)`,height:"100%"},".cm-matchingBracket, .cm-nonmatchingBracket, &.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{color:"inherit",backgroundColor:"rgba(128,128,128,.25)",backgroundBlendMode:"difference"},"&.cm-editor.cm-focused":{outline:"none"},".cm-activeLine":{backgroundColor:`var(--${h}-colors-surface3)`,borderRadius:`var(--${h}-border-radius)`},".cm-errorLine":{backgroundColor:`var(--${h}-colors-errorSurface)`,borderRadius:`var(--${h}-border-radius)`},".cm-content":{caretColor:`var(--${h}-colors-accent)`,padding:`0 var(--${h}-space-4)`},".cm-scroller":{fontFamily:`var(--${h}-font-mono)`,lineHeight:`var(--${h}-font-lineHeight)`},".cm-gutters":{backgroundColor:`var(--${h}-colors-surface1)`,color:`var(--${h}-colors-disabled)`,border:"none",paddingLeft:`var(--${h}-space-1)`},".cm-gutter.cm-lineNumbers":{fontSize:".6em"},".cm-lineNumbers .cm-gutterElement":{lineHeight:`var(--${h}-font-lineHeight)`,minWidth:`var(--${h}-space-5)`},".cm-content .cm-line":{paddingLeft:`var(--${h}-space-1)`},".cm-content.cm-readonly .cm-line":{paddingLeft:0}}),te=e=>`${h}-syntax-${e}`,ss=()=>["string","plain","comment","keyword","definition","punctuation","property","tag","static"].reduce((t,o)=>({...t,[`.${te(o)}`]:{color:`$syntax$color$${o}`,fontStyle:`$syntax$fontStyle$${o}`}}),{}),rs=e=>Hr.define([{tag:A.link,textDecoration:"underline"},{tag:A.emphasis,fontStyle:"italic"},{tag:A.strong,fontWeight:"bold"},{tag:A.keyword,class:te("keyword")},{tag:[A.atom,A.number,A.bool],class:te("static")},{tag:A.standard(A.tagName),class:te("tag")},{tag:A.variableName,class:te("plain")},{tag:A.function(A.variableName),class:te("definition")},{tag:[A.definition(A.function(A.variableName)),A.tagName],class:te("definition")},{tag:A.propertyName,class:te("property")},{tag:[A.literal,A.inserted],class:te(e.syntax.string?"string":"static")},{tag:A.punctuation,class:te("punctuation")},{tag:[A.comment,A.quote],class:te("comment")}]),ns=(e,t,o)=>{if(!e&&!t)return"javascript";let s=t;if(!s&&e){let r=e.lastIndexOf(".");s=e.slice(r+1)}for(let r of o)if(s===r.name||r.extensions.includes(s||""))return r.name;switch(s){case"ts":case"tsx":return"typescript";case"html":case"svelte":case"vue":return"html";case"css":case"less":case"scss":return"css";case"js":case"jsx":case"json":default:return"javascript"}},as=(e,t)=>{let o={javascript:ts({jsx:!0,typescript:!1}),typescript:ts({jsx:!0,typescript:!0}),html:_r(),css:Br()};for(let s of t)if(e===s.name)return s.language;return o[e]},dt=(...e)=>Ur(t=>e.forEach(o=>{if(!!o){if(typeof o=="function")return o(t);o.current=t}}),e);function is(e){return zr.fromClass(class{constructor(t){this.decorations=this.getDecoration(t)}update(t){}getDecoration(t){if(!e)return mt.none;let o=e.map(s=>{var a,p,d;let r=mt.line({attributes:{class:(a=s.className)!=null?a:""}}),n=mt.mark({class:(p=s.className)!=null?p:"",attributes:(d=s.elementAttributes)!=null?d:void 0}),c=_e(t.state.doc,{line:s.line,column:s.startColumn})+1;if(s.startColumn&&s.endColumn){let u=_e(t.state.doc,{line:s.line,column:s.endColumn})+1;return n.range(c,u)}return r.range(c)});return mt.set(o)}},{decorations:t=>t.decorations})}import{Decoration as ut,ViewPlugin as Vr}from"@codemirror/view";function cs(){return Wr}var Xr=ut.line({attributes:{class:"cm-errorLine"}}),Wr=Vr.fromClass(class{constructor(){this.decorations=ut.none}update(e){e.transactions.forEach(t=>{let o=t.annotation("show-error");if(o!==void 0){let s=_e(e.view.state.doc,{line:o})+1;this.decorations=ut.set([Xr.range(s)])}else t.annotation("remove-errors")&&(this.decorations=ut.none)})}},{decorations:e=>e.decorations});var ft=m({margin:"0",display:"block",fontFamily:"$font$mono",fontSize:"$font$size",color:"$syntax$color$plain",lineHeight:"$font$lineHeight"}),Zt=m(ss()),ht=m({flex:1,position:"relative",overflow:"auto",background:"$colors$surface1",".cm-scroller":{padding:"$space$4 0"},[`.${ft}`]:{padding:"$space$4 0"}}),Yt=m({margin:"0",outline:"none",height:"100%"}),ls=m({fontFamily:"$font$mono",fontSize:"0.8em",position:"absolute",right:"$space$2",bottom:"$space$2",zIndex:"$top",color:"$colors$clickable",backgroundColor:"$colors$surface2",borderRadius:"99999px",padding:"calc($space$1 / 2) $space$2",[`& + .${T}`]:{right:"calc($space$11 * 2)"}});import{highlightTree as Gr}from"@codemirror/highlight";import{createElement as Zr}from"react";var ps=({langSupport:e,highlightTheme:t,code:o=""})=>{let s=e.language.parser.parse(o),r=0,n=[],c=(a,p)=>{if(a>r){let d=o.slice(r,a);n.push(p?Zr("span",{children:d,className:p,key:`${a}${r}`}):d),r=a}};return Gr(s,t.match,(a,p,d)=>{c(a,""),c(p,d)}),r<o.length&&n.push(` + + `),n};var xe=un(({code:e,filePath:t,fileType:o,onCodeUpdate:s,showLineNumbers:r=!1,showInlineErrors:n=!1,wrapContent:c=!1,editorState:a="pristine",readOnly:p=!1,showReadOnly:d=!0,decorators:u,initMode:f="lazy",id:b,extensions:g=[],extensionsKeymap:y=[],additionalLanguages:R=[]},N)=>{let F=gt(null),X=dt(F,N),E=gt(),{theme:_,themeId:B}=Ce(),[j,v]=hs(e),[$,q]=hs(f==="immediate"),S=Yr(h),{listen:k}=x(),I=gt([]),M=gt([]),{isIntersecting:O}=mn(F,{rootMargin:"600px 0px",threshold:.2});fn(N,()=>({getCodemirror:()=>E.current})),Re(()=>{(f==="lazy"||f==="user-visible")&&O&&q(!0)},[f,O]);let G=ns(t,o,R),$e=as(G,R),Me=rs(_),Le=ps({langSupport:$e,highlightTheme:Me,code:e}),he=hn(()=>u&&u.sort((L,D)=>L.line-D.line),[u]);Re(()=>{if(!F.current||!$)return;let L=setTimeout(function(){let U=[{key:"Tab",run:Z=>{var ie;en(Z);let K=y.find(({key:de})=>de==="Tab");return(ie=K==null?void 0:K.run(Z))!=null?ie:!0}},{key:"Shift-Tab",run:({state:Z,dispatch:K})=>{var de;Qr({state:Z,dispatch:K});let ie=y.find(({key:qe})=>qe==="Shift-Tab");return(de=ie==null?void 0:ie.run(oe))!=null?de:!0}},{key:"Escape",run:()=>(p||F.current&&F.current.focus(),!0)},{key:"mod-Backspace",run:tn}],w=[pn(),nn(),Jr(),...g,fs.of([...qr,...Kr,...an,...on,...U,...y]),$e,rn.fallback,os(),Me];p?(w.push(ds.readOnly.of(!0)),w.push(Jt.editable.of(!1))):(w.push(cn()),w.push(dn())),he&&w.push(is(he)),c&&w.push(Jt.lineWrapping),r&&w.push(sn()),n&&w.push(cs());let ge=ds.create({doc:e,extensions:w}),be=F.current,Fe=be.querySelector(".sp-pre-placeholder");Fe&&be.removeChild(Fe);let oe=new Jt({state:ge,parent:be,dispatch:Z=>{if(oe.update([Z]),Z.docChanged){let K=Z.newDoc.sliceString(0,Z.newDoc.length);v(K),s==null||s(K)}}});oe.contentDOM.setAttribute("data-gramm","false"),oe.contentDOM.setAttribute("aria-label",t?`Code Editor for ${ve(t)}`:"Code Editor"),p?oe.contentDOM.classList.add("cm-readonly"):oe.contentDOM.setAttribute("tabIndex","-1"),E.current=oe},0);return()=>{var D;(D=E.current)==null||D.destroy(),clearTimeout(L)}},[$,r,c,B,he,p]),Re(function(){let D=E.current,U=!Gt(g,I.current)||!Gt(y,M.current);D&&U&&(D.dispatch({effects:ms.appendConfig.of(g)}),D.dispatch({effects:ms.appendConfig.of(fs.of([...y]))}),I.current=g,M.current=y)},[g,y]),Re(()=>{E.current&&a==="dirty"&&window.matchMedia("(min-width: 768px)").matches&&E.current.contentDOM.focus()},[]),Re(()=>{if(E.current&&e!==j){let L=E.current,D=L.state.selection.ranges.some(({to:w,from:ge})=>w>e.length||ge>e.length)?ln.cursor(e.length):L.state.selection,U={from:0,to:L.state.doc.length,insert:e};L.dispatch({changes:U,selection:D})}},[e]),Re(function(){if(!n)return;let D=k(U=>{let w=E.current;U.type==="success"?w==null||w.dispatch({annotations:[new us("remove-errors",!0)]}):U.type==="action"&&U.action==="show-error"&&U.line&&(w==null||w.dispatch({annotations:[new us("show-error",U.line)]}))});return()=>D()},[k,n]);let Je=L=>{L.key==="Enter"&&E.current&&(L.preventDefault(),E.current.contentDOM.focus())},we=()=>{let L=4;return r&&(L+=6),p||(L+=1),`var(--${h}-space-${L})`};return p?Ee(gn,null,Ee("pre",{ref:X,className:l(S("cm",a,G),Yt,Zt),translate:"no"},Ee("code",{className:l(S("pre-placeholder"),ft),style:{marginLeft:we()}},Le)),p&&d&&Ee("span",{className:l(S("read-only"),ls),...{}},"Read-only")):Ee("div",{ref:X,"aria-autocomplete":"list","aria-label":t?`Code Editor for ${ve(t)}`:"Code Editor","aria-multiline":"true",className:l(S("cm",a,G),Yt,Zt),onKeyDown:Je,role:"textbox",tabIndex:0,translate:"no",suppressHydrationWarning:!0},Ee("pre",{className:l(S("pre-placeholder"),ft),style:{marginLeft:we()}},Le))});var gs=yn(({style:e,showTabs:t,showLineNumbers:o=!1,showInlineErrors:s=!1,showRunButton:r=!0,wrapContent:n=!1,closableTabs:c=!1,initMode:a,extensions:p,extensionsKeymap:d,id:u,readOnly:f,showReadOnly:b,additionalLanguages:g},y)=>{let{sandpack:R}=x(),{code:N,updateCode:F,readOnly:X}=it(),{activeFile:E,status:_,editorState:B}=R,j=t!=null?t:R.visibleFiles.length>1,v=bn(h),$=q=>{F(q)};return je(Y,{className:v("editor"),style:e},j&&je(lt,{closableTabs:c}),je("div",{className:l(v("code-editor"),ht)},je(xe,{key:E,ref:y,additionalLanguages:g,code:N,editorState:B,extensions:p,extensionsKeymap:d,filePath:E,id:u,initMode:a||R.initMode,onCodeUpdate:$,readOnly:f||X,showInlineErrors:s,showLineNumbers:o,showReadOnly:b,wrapContent:n}),r&&_==="idle"?je(pt,null):null))});import{useClasser as Sn}from"@code-hike/classer";import{createElement as Ue,forwardRef as vn}from"react";var bs=vn(({showTabs:e,showLineNumbers:t,decorators:o,code:s,initMode:r,wrapContent:n,...c},a)=>{let{sandpack:p}=x(),{code:d}=it(),u=Sn(h),f=e!=null?e:p.visibleFiles.length>1;return Ue(Y,{...c},f?Ue(lt,null):null,Ue("div",{className:l(u("code-editor"),ht)},Ue(xe,{ref:a,code:s!=null?s:d,decorators:o,filePath:p.activeFile,initMode:r||p.initMode,showLineNumbers:t,showReadOnly:!1,wrapContent:n,readOnly:!0})),p.status==="idle"?Ue(pt,null):null)});import{createElement as vs}from"react";import{createElement as Kt}from"react";import{createElement as qt,useState as xn}from"react";import{useClasser as kn}from"@code-hike/classer";import{createElement as ze}from"react";var Cn=m({borderRadius:"0",width:"100%",padding:0,marginBottom:"$space$2",span:{textOverflow:"ellipsis",whiteSpace:"nowrap",overflow:"hidden"},svg:{marginRight:"$space$1"}}),bt=({selectFile:e,path:t,active:o,onClick:s,depth:r,isDirOpen:n})=>{let c=kn(h),a=u=>{e&&e(t),s==null||s(u)},p=t.split("/").filter(Boolean).pop(),d=()=>e?ze(Oo,null):n?ze(Po,null):ze(Io,null);return ze("button",{className:l(c("button","explorer"),T,Cn),"data-active":o,onClick:a,style:{paddingLeft:18*r+"px"},title:p,type:"button"},d(),ze("span",null,p))};var ys=({prefixedPath:e,files:t,selectFile:o,activeFile:s,depth:r,autoHiddenFiles:n,visibleFiles:c})=>{let[a,p]=xn(!0);return qt("div",{key:e},qt(bt,{depth:r,isDirOpen:a,onClick:()=>p(u=>!u),path:e+"/"}),a&&qt(yt,{activeFile:s,autoHiddenFiles:n,depth:r+1,files:t,prefixedPath:e,selectFile:o,visibleFiles:c}))};var Ss=({autoHiddenFiles:e,visibleFiles:t,files:o,prefixedPath:s})=>{let r=t.length>0,n=e&&!r,c=e&&!!r,a=Object.keys(o).filter(u=>{var b;let f=u.startsWith(s);return c?f&&t.includes(u):n?f&&!((b=o[u])==null?void 0:b.hidden):f}).map(u=>u.substring(s.length)),p=new Set(a.filter(u=>u.includes("/")).map(u=>`${s}${u.split("/")[0]}/`)),d=a.filter(u=>!u.includes("/")).map(u=>`${s}${u}`);return{directories:Array.from(p),modules:d}};var yt=({depth:e=0,activeFile:t,selectFile:o,prefixedPath:s,files:r,autoHiddenFiles:n,visibleFiles:c})=>{let{directories:a,modules:p}=Ss({visibleFiles:c,autoHiddenFiles:n,prefixedPath:s,files:r});return Kt("div",null,a.map(d=>Kt(ys,{key:d,activeFile:t,autoHiddenFiles:n,depth:e,files:r,prefixedPath:d,selectFile:o,visibleFiles:c})),p.map(d=>Kt(bt,{key:d,active:t===d,depth:e,path:d,selectFile:o})))};var Rn=m({padding:"$space$3",overflow:"auto",height:"100%"}),wp=({className:e,autoHiddenFiles:t=!1,...o})=>{let{sandpack:s}=x();return vs("div",{className:l(me,Rn,`${h}-file-explorer`,e),...o},vs(yt,{activeFile:s.activeFile,autoHiddenFiles:t,files:s.files,prefixedPath:"/",selectFile:s.openFile,visibleFiles:s.visibleFilesFromProps}))};import{useClasser as En}from"@code-hike/classer";import{createElement as le,useEffect as $n,useState as St}from"react";var ks=e=>{let t=e.match(/(https?:\/\/.*?)\//);return t&&t[1]?[t[1],e.replace(t[1],"")]:[e,"/"]};var Tn=m({display:"flex",alignItems:"center",height:"$layout$headerHeight",borderBottom:"1px solid $colors$surface2",padding:"$space$3 $space$2",background:"$colors$surface1"}),Nn=m({backgroundColor:"$colors$surface2",color:"$colors$clickable",padding:"$space$1 $space$3",borderRadius:"99999px",border:"1px solid $colors$surface2",height:"24px",lineHeight:"24px",fontSize:"inherit",outline:"none",flex:1,marginLeft:"$space$4",width:"0",transition:"background $transitions$default","&:hover":{backgroundColor:"$colors$surface3"},"&:focus":{backgroundColor:"$surface1",border:"1px solid $colors$accent",color:"$colors$base"}}),Cs=({clientId:e,onURLChange:t,className:o,...s})=>{var j;let[r,n]=St(""),{sandpack:c,dispatch:a,listen:p}=x(),[d,u]=St((j=c.startRoute)!=null?j:"/"),[f,b]=St(!1),[g,y]=St(!1),R=En(h);$n(()=>{let v=p($=>{if($.type==="urlchange"){let{url:q,back:S,forward:k}=$,[I,M]=ks(q);n(I),u(M),b(S),y(k)}},e);return()=>v()},[]);let N=v=>{let $=v.target.value.startsWith("/")?v.target.value:`/${v.target.value}`;u($)},F=v=>{v.code==="Enter"&&(v.preventDefault(),v.stopPropagation(),typeof t=="function"&&t(r+v.currentTarget.value))},X=()=>{a({type:"refresh"})},E=()=>{a({type:"urlback"})},_=()=>{a({type:"urlforward"})},B=l(R("button","icon"),T,Ko,m({minWidth:"$space$6",justifyContent:"center"}));return le("div",{className:l(R("navigator"),Tn,o),...s},le("button",{"aria-label":"Go back one page",className:B,disabled:!f,onClick:E,type:"button"},le(Lo,null)),le("button",{"aria-label":"Go forward one page",className:B,disabled:!g,onClick:_,type:"button"},le(wo,null)),le("button",{"aria-label":"Refresh page",className:B,onClick:X,type:"button"},le(Qe,null)),le("input",{"aria-label":"Current Sandpack URL",className:l(R("input"),Nn),name:"Current Sandpack URL",onChange:N,onKeyDown:F,type:"text",value:d}))};import{useClasser as Gn}from"@code-hike/classer";import{Fragment as Kn,createElement as re,forwardRef as qn,useEffect as ea,useImperativeHandle as ta,useState as Qn}from"react";import{useEffect as Mn}from"react";var xs=()=>{var o;let{sandpack:e}=x(),{error:t}=e;return Mn(()=>{e.errorScreenRegisteredRef.current=!0},[]),(o=t==null?void 0:t.message)!=null?o:null};import{useEffect as Es,useState as Ln}from"react";var Qt=200,Rs=(e,t)=>{let{sandpack:o,listen:s}=x(),[r,n]=Ln("LOADING");return Es(()=>{o.loadingScreenRegisteredRef.current=!0;let c=s(a=>{a.type==="start"&&a.firstLoad===!0&&n("LOADING"),a.type==="done"&&n(p=>p==="LOADING"?"PRE_FADING":"HIDDEN")},e);return()=>{c()}},[e,o.status==="idle"]),Es(()=>{let c;return r==="PRE_FADING"&&!t?n("FADING"):r==="FADING"&&(c=setTimeout(()=>n("HIDDEN"),Qt)),()=>{clearTimeout(c)}},[r,t]),o.status==="timeout"?"TIMEOUT":o.status!=="running"?"HIDDEN":r};var Ts=e=>{let{dispatch:t}=x();return{refresh:()=>t({type:"refresh"},e),back:()=>t({type:"urlback"},e),forward:()=>t({type:"urlforward"},e)}};function wn(e){var r,n;let{activeFile:t,bundlerState:o}=e;if(o==null)return null;let s=o.transpiledModules[t+":"];return(n=(r=s==null?void 0:s.source)==null?void 0:r.compiledCode)!=null?n:null}var Ns=()=>{let{sandpack:e}=x();return e.status!=="running"?null:wn(e)};import{useEffect as Fn,useRef as $s}from"react";var vt=()=>{let{sandpack:e,listen:t,dispatch:o}=x(),s=$s(null),r=$s(Ae());return Fn(()=>{let c=s.current,a=r.current;return c!==null&&e.registerBundler(c,a),()=>e.unregisterBundler(a)},[]),{sandpack:e,getClient:()=>e.clients[r.current]||null,clientId:r.current,iframe:s,listen:c=>t(c,r.current),dispatch:c=>o(c,r.current)}};import{useClasser as An}from"@code-hike/classer";import{createElement as Ms}from"react";var kt=({children:e,className:t,...o})=>{let s=xs(),r=An(h);return!s&&!e?null:Ms("div",{className:l(r("overlay","error"),He,ct,t),translate:"no",...o},Ms("div",{className:l(r("error-message"),ke)},s||e))};import{useClasser as Vn}from"@code-hike/classer";import{createElement as Te}from"react";import{useClasser as _n}from"@code-hike/classer";import{createElement as se}from"react";import{useClasser as Bn}from"@code-hike/classer";import{createElement as Fs}from"react";import Pn from"lz-string";import{createElement as Ct,useEffect as ws,useRef as Dn,useState as Hn}from"react";var In=e=>Pn.compressToBase64(JSON.stringify(e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""),Ls="https://codesandbox.io/api/v1/sandboxes/define",On=(e,t)=>{let o=Object.keys(e).reduce((s,r)=>{let n=r.replace("/",""),c={content:e[r].code,isBinary:!1};return{...s,[n]:c}},{});return In({files:o,...t?{template:t}:null})},eo=({children:e,...t})=>{var c,a,p;let{sandpack:o}=x(),s=Dn(null),[r,n]=Hn();return ws(function(){let u=setTimeout(()=>{let f=On(o.files,o.environment),b=new URLSearchParams({parameters:f,query:new URLSearchParams({file:o.activeFile,utm_medium:"sandpack"}).toString()});n(b)},600);return()=>{clearTimeout(u)}},[o.activeFile,o.environment,o.files]),ws(function(){o.openInCSBRegisteredRef.current=!0},[]),((p=(a=(c=r==null?void 0:r.get)==null?void 0:c.call(r,"parameters"))==null?void 0:a.length)!=null?p:0)>1500?Ct("button",{onClick:()=>{var d;return(d=s.current)==null?void 0:d.submit()},title:"Open in CodeSandbox",...t},Ct("form",{ref:s,action:Ls,method:"POST",style:{visibility:"hidden"},target:"_blank"},Array.from(r,([d,u])=>Ct("input",{key:d,name:d,type:"hidden",value:u}))),e):Ct("a",{href:`${Ls}?${r==null?void 0:r.toString()}`,rel:"noreferrer noopener",target:"_blank",title:"Open in CodeSandbox",...t},e)};var Ve=()=>{let e=Bn(h);return Fs(eo,{className:l(e("button","icon-standalone"),T,ee,z)},Fs(Ao,null))};var to=m({transform:"translate(-4px, 9px) scale(0.13, 0.13)","*":{position:"absolute",width:"96px",height:"96px"}}),jn=m({position:"absolute",right:"$space$2",bottom:"$space$2",zIndex:"$top",width:"32px",height:"32px",borderRadius:"$border$radius",[`.${to}`]:{display:"flex"},[`.${T}`]:{display:"none"},[`&:hover .${T}`]:{display:"flex"},[`&:hover .${to}`]:{display:"none"}}),Un=tt({"0%":{transform:"rotateX(-25.5deg) rotateY(45deg)"},"100%":{transform:"rotateX(-25.5deg) rotateY(405deg)"}}),zn=m({animation:`${Un} 1s linear infinite`,animationFillMode:"forwards",transformStyle:"preserve-3d",transform:"rotateX(-25.5deg) rotateY(45deg)","*":{border:"10px solid $colors$clickable",borderRadius:"8px",background:"$colors$surface1"},".top":{transform:"rotateX(90deg) translateZ(44px)",transformOrigin:"50% 50%"},".bottom":{transform:"rotateX(-90deg) translateZ(44px)",transformOrigin:"50% 50%"},".front":{transform:"rotateY(0deg) translateZ(44px)",transformOrigin:"50% 50%"},".back":{transform:"rotateY(-180deg) translateZ(44px)",transformOrigin:"50% 50%"},".left":{transform:"rotateY(-90deg) translateZ(44px)",transformOrigin:"50% 50%"},".right":{transform:"rotateY(90deg) translateZ(44px)",transformOrigin:"50% 50%"}}),xt=({className:e,showOpenInCodeSandbox:t,...o})=>{let s=_n(h);return se("div",{className:l(s("cube-wrapper"),jn,e),title:"Open in CodeSandbox",...o},t&&se(Ve,null),se("div",{className:l(s("cube"),to)},se("div",{className:l(s("sides"),zn)},se("div",{className:"top"}),se("div",{className:"right"}),se("div",{className:"bottom"}),se("div",{className:"left"}),se("div",{className:"front"}),se("div",{className:"back"}))))};var Xn=m({backgroundColor:"$colors$surface1"}),Rt=({clientId:e,loading:t,className:o,style:s,showOpenInCodeSandbox:r,...n})=>{let c=Rs(e,t),a=Vn(h);if(c==="HIDDEN")return null;if(c==="TIMEOUT")return Te("div",{className:l(a("overlay","error"),He,ct,o),...n},Te("div",{className:l(a("error-message"),ke)},"Unable to establish connection with the sandpack bundler. Make sure you are online or try again later. If the problem persists, please report it via"," ",Te("a",{className:l(a("error-message"),ke),href:"mailto:hello@codesandbox.io?subject=Sandpack Timeout Error"},"email")," ","or submit an issue on"," ",Te("a",{className:l(a("error-message"),ke),href:"https://github.com/codesandbox/sandpack/issues",rel:"noreferrer noopener",target:"_blank"},"GitHub.")));let p=c==="LOADING"||c==="PRE_FADING";return Te("div",{className:l(a("overlay","loading"),He,Xn,o),style:{...s,opacity:p?1:0,transition:`opacity ${Qt}ms ease-out`},...n},Te(xt,{showOpenInCodeSandbox:r}))};import{useClasser as Wn}from"@code-hike/classer";import{createElement as Ps}from"react";var As=({clientId:e})=>{let{refresh:t}=Ts(e),o=Wn(h);return Ps("button",{className:l(o("button","icon-standalone"),T,ee,z),onClick:t,title:"Refresh Sandpack",type:"button"},Ps(Qe,null))};var Zn=m({flex:1,display:"flex",flexDirection:"column",background:"white",overflow:"auto",position:"relative"}),Yn=m({border:"0",outline:"0",width:"100%",height:"100%",minHeight:"160px",maxHeight:"2000px",flex:1}),Jn=m({display:"flex",position:"absolute",bottom:"$space$2",right:"$space$2",zIndex:"$overlay","> *":{marginLeft:"$space$2"}}),Is=qn(({showNavigator:e=!1,showRefreshButton:t=!0,showOpenInCodeSandbox:o=!0,showSandpackErrorOverlay:s=!0,actionsChildren:r=re(Kn,null),children:n,className:c,...a},p)=>{let{sandpack:d,listen:u,iframe:f,getClient:b,clientId:g}=vt(),[y,R]=Qn(null),{status:N,errorScreenRegisteredRef:F,openInCSBRegisteredRef:X,loadingScreenRegisteredRef:E}=d,_=Gn(h);X.current=!0,F.current=!0,E.current=!0,ea(()=>u(v=>{v.type==="resize"&&R(v.height)}),[]),ta(p,()=>({clientId:g,getClient:b}),[b,g]);let B=j=>{!f.current||(f.current.src=j)};return re(Y,{className:l(`${h}-preview`,c),...a},e&&re(Cs,{clientId:g,onURLChange:B}),re("div",{className:l(_("preview-container"),Zn)},re("iframe",{ref:f,className:l(_("preview-iframe"),Yn),style:{height:y||void 0},title:"Sandpack Preview"}),s&&re(kt,null),re("div",{className:l(_("preview-actions"),Jn)},r,!e&&t&&N==="running"&&re(As,{clientId:g}),o&&re(Ve,null)),re(Rt,{clientId:g,showOpenInCodeSandbox:o}),n))});import{useClasser as oa}from"@code-hike/classer";import{createElement as Xe,useEffect as na,useRef as ra}from"react";var sa=m({display:"flex",flexDirection:"column",width:"100%",position:"relative",overflow:"auto",minHeight:"160px",flex:1}),bm=({className:e,...t})=>{let{sandpack:o}=x(),s=Ns(),r=oa(h),n=ra(null);return na(()=>{let c=n.current;return c&&o.registerBundler(c,"hidden"),()=>{o.unregisterBundler("hidden")}},[]),Xe("div",{className:l(r("transpiled-code"),me,sa,e),...t},Xe(bs,{code:s!=null?s:"",initMode:o.initMode,...t}),Xe("iframe",{ref:n,style:{display:"none"},title:"transpiled sandpack code"}),Xe(kt,null),Xe(Rt,{clientId:"hidden",showOpenInCodeSandbox:!1}))};import{useClasser as aa}from"@code-hike/classer";import{createElement as Os,useEffect as oo,useRef as ca,useState as la}from"react";var ia=m({height:"$layout$height",width:"100%"}),Em=({clientId:e,theme:t,className:o,...s})=>{let{listen:r,sandpack:n}=x(),{themeMode:c}=Ce(),a=aa(h),p=ca(),[d,u]=la(null);return oo(()=>{import("react-devtools-inline/frontend").then(f=>{p.current=f})},[]),oo(()=>r(b=>{var g;if(b.type==="activate-react-devtools"){let y=e?n.clients[e]:Object.values(n.clients)[0],R=(g=y==null?void 0:y.iframe)==null?void 0:g.contentWindow;p.current&&R&&u(p.current.initialize(R))}}),[p,e,r,n.clients]),oo(()=>{n.registerReactDevTools("legacy")},[]),d?Os("div",{className:l(a("devtools"),ia,o),...s},Os(d,{browserTheme:t!=null?t:c})):null};import{Fragment as _a,createElement as V,useEffect as Qs,useState as Ba}from"react";import{useClasser as pa}from"@code-hike/classer";import{createElement as ua,forwardRef as ma}from"react";var da=m({border:"1px solid $colors$surface2",display:"flex",flexWrap:"wrap",alignItems:"stretch",borderRadius:"$border$radius",overflow:"hidden",position:"relative",backgroundColor:"$colors$surface2",gap:1,[`> .${me}`]:{flexGrow:1,flexShrink:1,flexBasis:"0",minWidth:"350px",height:"$layout$height","@media print":{height:"auto",display:"block"},"@media screen and (max-width: 768px)":{height:"auto",minWidth:"100% !important;"}},[`> .${h}-file-explorer`]:{flex:.2,minWidth:200}}),Ds=ma(({children:e,className:t,...o},s)=>{let{sandpack:r}=x(),n=pa(h),c=dt(r.lazyAnchorRef,s);return ua("div",{ref:c,className:l(n("layout"),da,t),...o},e)});import{createElement as pe}from"react";var fa=m({justifyContent:"space-between",borderBottom:"1px solid $colors$surface2",padding:"$space$3 $space$2",fontFamily:"$font$mono",maxHeight:"$layout$headerHeight",overflowX:"auto",whiteSpace:"nowrap"}),so=m({display:"flex",flexDirection:"row",alignItems:"center",gap:"$space$2"}),Hs=({status:e,suiteOnly:t,setSuiteOnly:o,setVerbose:s,verbose:r,watchMode:n,setWatchMode:c,showSuitesOnly:a})=>{let p=l(T,z,m({padding:"$space$1 $space$3"}));return pe("div",{className:l(fa,so)},pe("div",{className:l(so)},pe("p",{className:l(m({lineHeight:1,margin:0,color:"$colors$base",fontSize:"$font$size",display:"flex",alignItems:"center",gap:"$space$2"}))},pe(Se,null),"Tests")),pe("div",{className:l(so)},a&&pe("button",{className:p,"data-active":t,disabled:e==="initialising",onClick:o},"Suite only"),pe("button",{className:p,"data-active":r,disabled:e==="initialising",onClick:s},"Verbose"),pe("button",{className:p,"data-active":n,disabled:e==="initialising",onClick:c},"Watch")))};import{useClasser as ha}from"@code-hike/classer";import{createElement as _s}from"react";var Bs=({onClick:e})=>{let t=ha(h);return _s("button",{className:l(t("button","icon-standalone"),T,ee,z),onClick:e,title:"Run tests",type:"button"},_s(Ke,null))};import{Fragment as Aa,createElement as P}from"react";import{Fragment as xa,createElement as Ge}from"react";import ue from"react";var js=e=>({"--test-pass":e?"#18df16":"#15c213","--test-fail":e?"#df162b":"#c21325","--test-skip":e?"#eace2b":"#c2a813","--test-run":e?"#eace2b":"#c2a813","--test-title":e?"#3fbabe":"#256c6f"}),Et=m({variants:{status:{pass:{color:"var(--test-pass)"},fail:{color:"var(--test-fail)"},skip:{color:"var(--test-skip)"},title:{color:"var(--test-title)"}}}}),ne=Et({status:"pass"}),H=Et({status:"fail"}),Tt=Et({status:"skip"}),Us=Et({status:"title"}),ro=m({variants:{status:{pass:{background:"var(--test-pass)",color:"$colors$surface1"},fail:{background:"var(--test-fail)",color:"$colors$surface1"},run:{background:"var(--test-run)",color:"$colors$surface1"}}}}),zs=ro({status:"run"}),Vs=ro({status:"pass"}),no=ro({status:"fail"});var ga=m({marginLeft:"$space$4"}),ba=m({marginBottom:"$space$2",color:"$colors$clickable"}),ya=m({marginBottom:"$space$2",color:"$colors$hover"}),Sa=m({marginLeft:"$space$2"}),ao=m({marginRight:"$space$2"}),Nt=({tests:e,style:t})=>ue.createElement("div",{className:l(ga)},e.map(o=>ue.createElement("div",{key:o.name,className:l(ba)},o.status==="pass"&&ue.createElement("span",{className:l(ne,ao)},"\u2713"),o.status==="fail"&&ue.createElement("span",{className:l(H,ao)},"\u2715"),o.status==="idle"&&ue.createElement("span",{className:l(Tt,ao)},"\u25CB"),ue.createElement("span",{className:l(ya)},o.name),o.duration!==void 0&&ue.createElement("span",{className:l(Sa)},"(",o.duration," ms)"))));import va from"clean-set";var Xs=e=>$t(e).filter(t=>t.status==="fail"),$t=e=>Object.values(e.tests).concat(...Object.values(e.describes).map($t)),Ws=e=>e.map(Mt).reduce((t,o)=>({pass:t.pass+o.pass,fail:t.fail+o.fail,skip:t.skip+o.skip,total:t.total+o.total}),{pass:0,skip:0,fail:0,total:0}),Mt=e=>$t(e).reduce((t,o)=>({pass:o.status==="pass"?t.pass+1:t.pass,fail:o.status==="fail"?t.fail+1:t.fail,skip:o.status==="idle"||o.status==="running"?t.skip+1:t.skip,total:t.total+1}),{pass:0,fail:0,skip:0,total:0}),Gs=e=>e.filter(t=>Object.values(t.describes).length>0||Object.values(t.tests).length>0).map(Mt).reduce((t,o)=>({pass:t.pass+(o.fail===0?1:0),fail:t.fail+(o.fail>0?1:0),total:t.total+1}),{pass:0,fail:0,total:0}),Zs=e=>Ne(e,$t).reduce((t,o)=>t+(o.duration||0),0),Lt=e=>Object.values(e.describes).length===0&&Object.values(e.tests).length===0,We=e=>{let t=e.length-1,o=e.slice(0,t),s=e[t];return[o,s]},Ne=(e,t)=>e.map(t).reduce((o,s)=>o.concat(s),[]),ae=(e,t)=>o=>va(o,e,t);var ka=m({color:"$colors$hover",marginBottom:"$space$2"}),Ca=m({marginLeft:"$space$4"}),io=({describes:e})=>Ge(xa,null,e.map(t=>{if(Lt(t))return null;let o=Object.values(t.tests),s=Object.values(t.describes);return Ge("div",{key:t.name,className:l(Ca)},Ge("div",{className:l(ka)},t.name),Ge(Nt,{tests:o}),Ge(io,{describes:s}))}));import{createElement as Ta}from"react";var Ra=m({color:"$colors$hover",fontSize:"$font$size",padding:"$space$2",whiteSpace:"pre-wrap"}),co=({error:e,path:t})=>Ta("div",{className:l(Ra),dangerouslySetInnerHTML:{__html:Ea(e,t)}}),wt=e=>e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),Ea=(e,t)=>{let o="";if(e.matcherResult?o=`<span>${wt(e.message).replace(/(expected)/m,`<span class="${ne}">$1</span>`).replace(/(received)/m,`<span class="${H}">$1</span>`).replace(/(Difference:)/m,"<span>$1</span>").replace(/(Expected:)(.*)/m,`<span>$1</span><span class="${ne}">$2</span>`).replace(/(Received:)(.*)/m,`<span>$1</span><span class="${H}">$2</span>`).replace(/^(-.*)/gm,`<span class="${H}">$1</span>`).replace(/^(\+.*)/gm,`<span class="${ne}">$1</span>`)}</span>`:o=wt(e.message),e.mappedErrors&&e.mappedErrors[0]&&e.mappedErrors[0].fileName.endsWith(t)&&e.mappedErrors[0]._originalScriptCode){let r=e.mappedErrors[0]._originalScriptCode||[],n=Math.max(...r.map(a=>(a.lineNumber+"").length))+2,c=Array.from({length:n}).map(()=>" ");o+="<br />",o+="<br />",o+="<div>",r.filter(a=>a.content.trim()).forEach(a=>{let p=(a.lineNumber+"").length,d=[...c];d.length-=p,a.highlight&&(d.length-=2);let u=a.content.indexOf(".to"),f=Array.from({length:c.length+u-(n-1)},()=>" "),b=wt(a.content).replace(/(describe|test|it)(\()('|"|`)(.*)('|"|`)/m,`<span>$1$2$3</span><span class="${Us}">$4</span><span>$5</span>`).replace(/(expect\()(.*)(\)\..*)(to[\w\d]*)(\()(.*)(\))/m,`<span>$1</span><span class="${H}">$2</span><span>$3</span><span style="text-decoration: underline; font-weight: 900">$4</span><span>$5</span><span class="${ne}">$6</span><span>$7</span>`);o+=`<div ${a.highlight?'style="font-weight:200;"':""}>`+(a.highlight?`<span class="${H}">></span> `:"")+d.join("")+wt(""+a.lineNumber)+" | "+b+"</div>"+(a.highlight?"<div>"+c.join("")+" | "+f.join("")+`<span class="${H}">^</span></div>`:"")}),o+="</div>"}return o.replace(/(?:\r\n|\r|\n)/g,"<br />")};var Na=m({display:"flex",flexDirection:"row",alignItems:"center",marginBottom:"$space$2"}),lo=m({marginBottom:"$space$2"}),$a=m({fontWeight:"bold"}),Ft=m({borderRadius:"calc($border$radius / 2)"}),Ma=m({padding:"$space$1 $space$2",fontFamily:"$font$mono",textTransform:"uppercase",marginRight:"$space$2"}),La=m({fontFamily:"$font$mono",cursor:"pointer",display:"inline-block"}),wa=m({color:"$colors$clickable",textDecorationStyle:"dotted",textDecorationLine:"underline"}),Fa=m({color:"$colors$hover",fontWeight:"bold",textDecorationStyle:"dotted",textDecorationLine:"underline"}),Ys=({specs:e,openSpec:t,status:o,verbose:s})=>P(Aa,null,e.map(r=>{if(r.error)return P("div",{key:r.name,className:l(lo)},P(At,{className:l(Ft,no)},"Error"),P(Js,{onClick:()=>t(r.name),path:r.name}),P(co,{error:r.error,path:r.name}));if(Lt(r))return null;let n=Object.values(r.tests),c=Object.values(r.describes),a=Mt(r);return P("div",{key:r.name,className:l(lo)},P("div",{className:l(Na)},o==="complete"?a.fail>0?P(At,{className:l(Ft,no)},"Fail"):P(At,{className:l(Ft,Vs)},"Pass"):P(At,{className:l(Ft,zs)},"Run"),P(Js,{onClick:()=>t(r.name),path:r.name})),s&&P(Nt,{tests:n}),s&&P(io,{describes:c}),Xs(r).map(p=>P("div",{key:`failing-${p.name}`,className:l(lo)},P("div",{className:l($a,H)},"\u25CF ",p.blocks.join(" \u203A ")," \u203A ",p.name),p.errors.map(d=>P(co,{key:`failing-${p.name}-error`,error:d,path:p.path})))))})),At=({children:e,className:t})=>P("span",{className:l(Ma,t)},e),Js=({onClick:e,path:t})=>{let o=t.split("/"),s=o.slice(0,o.length-1).join("/")+"/",r=o[o.length-1];return P("button",{className:l(T,La),onClick:e},P("span",{className:l(wa)},s),P("span",{className:l(Fa)},r))};import{createElement as W}from"react";var qs=m({marginBottom:"$space$2"}),po=m({fontWeight:"bold",color:"$colors$hover",whiteSpace:"pre-wrap"}),Pa=m({fontWeight:"bold",color:"$colors$clickable"}),Ks=({suites:e,tests:t,duration:o})=>{let s="Test suites: ",r=n=>{let c=s.length-n.length,a=Array.from({length:c},()=>" ").join("");return n+a};return W("div",{className:l(Pa)},W("div",{className:l(qs)},W("span",{className:l(po)},s),e.fail>0&&W("span",{className:l(H)},e.fail," failed,"," "),e.pass>0&&W("span",{className:l(ne)},e.pass," passed,"," "),W("span",null,e.total," total")),W("div",{className:l(qs)},W("span",{className:l(po)},r("Tests:")),t.fail>0&&W("span",{className:l(H)},t.fail," failed,"," "),t.skip>0&&W("span",{className:l(Tt)},t.skip," skipped,"," "),t.pass>0&&W("span",{className:l(ne)},t.pass," passed,"," "),W("span",null,t.total," total")),W("div",{className:l(po)},r("Time:"),o/1e3,"s"))};var Ia=m({display:"flex",position:"absolute",bottom:"$space$2",right:"$space$2",zIndex:"$overlay","> *":{marginLeft:"$space$2"}}),Oa={specs:{},status:"initialising",verbose:!1,watchMode:!0,suiteOnly:!1,specsCount:0},mo=({verbose:e=!1,watchMode:t=!0,style:o,className:s,onComplete:r,actionsChildren:n,...c})=>{let a=Ce(),{getClient:p,iframe:d,listen:u,sandpack:f}=vt(),[b,g]=Ba({...Oa,verbose:e,watchMode:t});Qs(()=>{let v=[],$="";return u(S=>{if(!(b.suiteOnly&&("path"in S&&S.path!==f.activeFile||"test"in S&&"path"in S.test&&S.test.path!==f.activeFile))){if(S.type==="action"&&S.action==="clear-errors"&&S.source==="jest"){$=S.path;return}if(S.type==="test"){if(S.event==="initialize_tests")return v=[],$="",b.watchMode?y():g(k=>({...k,status:"idle",specs:{}}));if(S.event==="test_count")return g(k=>({...k,specsCount:S.count}));if(S.event==="total_test_start")return v=[],g(k=>({...k,status:"running"}));if(S.event==="total_test_end")return g(k=>(r!==void 0&&r(k.specs),{...k,status:"complete"}));if(S.event==="add_file")return g(ae(["specs",S.path],{describes:{},tests:{},name:S.path}));if(S.event==="remove_file")return g(k=>{let I=Object.entries(k.specs).reduce((M,[O,G])=>O===S.path?M:{...M,[O]:G},{});return{...k,specs:I}});if(S.event==="file_error")return g(ae(["specs",S.path,"error"],S.error));if(S.event==="describe_start"){v.push(S.blockName);let[k,I]=We(v),M=$;return I===void 0?void 0:g(ae(["specs",M,"describes",...Ne(k,O=>[O,"describes"]),I],{name:S.blockName,tests:{},describes:{}}))}if(S.event==="describe_end"){v.pop();return}if(S.event==="add_test"){let[k,I]=We(v),M={status:"idle",errors:[],name:S.testName,blocks:[...v],path:S.path};return g(I===void 0?ae(["specs",S.path,"tests",S.testName],M):ae(["specs",S.path,"describes",...Ne(k,O=>[O,"describes"]),I,"tests",S.testName],M))}if(S.event==="test_start"){let{test:k}=S,[I,M]=We(k.blocks),O={status:"running",name:k.name,blocks:k.blocks,path:k.path,errors:[]};return g(M===void 0?ae(["specs",k.path,"tests",k.name],O):ae(["specs",k.path,"describes",...Ne(I,G=>[G,"describes"]),M,"tests",k.name],O))}if(S.event==="test_end"){let{test:k}=S,[I,M]=We(k.blocks),O={status:k.status,errors:k.errors,duration:k.duration,name:k.name,blocks:k.blocks,path:k.path};return g(M===void 0?ae(["specs",k.path,"tests",k.name],O):ae(["specs",k.path,"describes",...Ne(I,G=>[G,"describes"]),M,"tests",k.name],O))}}}})},[b.suiteOnly,b.watchMode,f.activeFile]);let y=()=>{g($=>({...$,status:"running",specs:{}}));let v=p();v&&v.dispatch({type:"run-all-tests"})},R=()=>{g($=>({...$,status:"running",specs:{}}));let v=p();v&&v.dispatch({type:"run-tests",path:f.activeFile})},N=/.*\.(test|spec)\.[tj]sx?$/,F=f.activeFile.match(N)!==null;Qs(function(){return u(({type:q})=>{q==="done"&&b.watchMode&&(F?R():y())})},[R,y,b.watchMode,F]);let X=v=>{f.setActiveFile(v)},E=Object.values(b.specs),_=Zs(E),B=Ws(E),j=Gs(E);return V(Y,{className:l(`${h}-tests`,s),style:{...js(a.themeMode==="dark"),...o},...c},V("iframe",{ref:d,style:{display:"none"},title:"Sandpack Tests"}),V(Hs,{setSuiteOnly:()=>g(v=>({...v,suiteOnly:!v.suiteOnly})),setVerbose:()=>g(v=>({...v,verbose:!v.verbose})),setWatchMode:()=>{g(v=>({...v,watchMode:!v.watchMode}))},showSuitesOnly:b.specsCount>1,status:b.status,suiteOnly:b.suiteOnly,verbose:b.verbose,watchMode:b.watchMode}),b.status==="running"||b.status==="initialising"?V(xt,{showOpenInCodeSandbox:!1}):V("div",{className:Ia.toString()},n,V(Bs,{onClick:b.suiteOnly?R:y})),V("div",{className:l(Da)},E.length===0&&b.status==="complete"?V("div",{className:l(Ha)},V("p",null,"No test files found."),V("p",null,"Test match:"," ",V("span",{className:l(H)},N.toString()))):V(_a,null,V(Ys,{openSpec:X,specs:E,status:b.status,verbose:b.verbose}),b.status==="complete"&&B.total>0&&V(Ks,{duration:_,suites:j,tests:B}))))},Da=m({padding:"$space$4",height:"100%",overflow:"auto",display:"flex",flexDirection:"column",position:"relative",fontFamily:"$font$mono"}),Ha=m({fontWeight:"bold",color:"$colors$base"});import{Fragment as Ka,createElement as fe,useEffect as qa,useRef as Ja}from"react";import{useClasser as ja}from"@code-hike/classer";import er from"react";var tr=({onClick:e})=>{let t=ja("sp");return er.createElement("button",{className:l(t("button","icon-standalone"),T,ee,z,m({position:"absolute",bottom:"$space$2",right:"$space$2"})),onClick:e},er.createElement(Fo,null))};import uo from"react";var or=()=>uo.createElement("div",{className:l(m({borderBottom:"1px solid $colors$surface2",padding:"$space$3 $space$2",height:"$layout$headerHeight"}))},uo.createElement("p",{className:l(m({lineHeight:1,margin:0,color:"$colors$base",fontSize:"$font$size",display:"flex",alignItems:"center",gap:"$space$2"}))},uo.createElement(Se,null),"Console"));import{useEffect as za,useState as Ua}from"react";var sr=["SyntaxError: ","Error in sandbox:"],rr={id:"random",method:"clear",data:["Console was cleared"]},fo="@t",ho="@r",go=1e4,bo=2,Pt=400,yo=Pt*2;var So=e=>{var c,a;let[t,o]=Ua([]),{listen:s}=x(),r=(c=e==null?void 0:e.showSyntaxError)!=null?c:!1,n=(a=e==null?void 0:e.maxMessageCount)!=null?a:yo;return za(()=>s(d=>{if(d.type==="console"&&d.codesandbox){if(d.log.find(({method:f})=>f==="clear"))return o([rr]);let u=r?d.log:d.log.filter(f=>f.data.filter(g=>typeof g!="string"?!0:sr.filter(R=>g.startsWith(R)).length===0).length>0);if(!u)return;o(f=>{let b=[...f,...u].filter((g,y,R)=>y===R.findIndex(N=>N.id===g.id));for(;b.length>yo;)b.shift();return b})}},e==null?void 0:e.clientId),[s,n,e,r]),{logs:t,reset:()=>o([])}};var vo=function(){return(0,eval)("this")}(),Va=typeof ArrayBuffer=="function",Xa=typeof Map=="function",Wa=typeof Set=="function",Ze;(function(s){s[s.infinity=0]="infinity",s[s.minusInfinity=1]="minusInfinity",s[s.minusZero=2]="minusZero"})(Ze||(Ze={}));var nr={Arithmetic:e=>e===0?1/0:e===1?-1/0:e===2?-0:e,HTMLElement:e=>{let t=document.implementation.createHTMLDocument("sandbox");try{let o=t.createElement(e.tagName);o.innerHTML=e.innerHTML;for(let s of Object.keys(e.attributes))try{o.setAttribute(s,e.attributes[s])}catch{}return o}catch(o){return e}},Function:e=>{let t=()=>{};return Object.defineProperty(t,"toString",{value:()=>`function ${e.name}() {${e.body}}`}),t},"[[NaN]]":()=>NaN,"[[undefined]]":()=>{},"[[Date]]":e=>{let t=new Date;return t.setTime(e),t},"[[RegExp]]":e=>new RegExp(e.src,e.flags),"[[Error]]":e=>{let t=vo[e.name]||Error,o=new t(e.message);return o.stack=e.stack,o},"[[ArrayBuffer]]":e=>{if(Va){let t=new ArrayBuffer(e.length);return new Int8Array(t).set(e),t}return e},"[[TypedArray]]":e=>typeof vo[e.ctorName]=="function"?new vo[e.ctorName](e.arr):e.arr,"[[Map]]":e=>{if(Xa){let o=new Map;for(let s=0;s<e.length;s+=2)o.set(e[s],e[s+1]);return o}let t=[];for(let o=0;o<e.length;o+=2)t.push([e[i],e[i+1]]);return t},"[[Set]]":e=>{if(Wa){let t=new Set;for(let o=0;o<e.length;o++)t.add(e[o]);return t}return e}};var ar=e=>{if(typeof e=="string"||typeof e=="number"||e===null)return e;if(Array.isArray(e))return e.map(ar);if(typeof e=="object"&&fo in e){let t=e[fo];return nr[t](e.data)}return e},Ga=(e,t,o)=>`[${e.reduce((r,n,c)=>`${r}${c?", ":""}${Ye(n,t,o)}`,"")}]`,Za=(e,t,o)=>{let s=e.constructor.name!=="Object"?`${e.constructor.name} `:"";if(o>bo)return s;let r=Object.entries(e),n=Object.entries(e).reduce((c,[a,p],d)=>{let u=d===0?"":", ",f=r.length>10?` + `:"",b=Ye(p,t,o);return d===Pt?c+f+"...":d>Pt?c:c+`${u}${f}${a}: `+b},"");return`${s}{ ${n}${r.length>10?` diff --git a/beta/patches/@lezer+javascript+0.15.2.patch b/beta/patches/@lezer+javascript+0.15.2.patch new file mode 100644 index 000000000..c7ecd94c4 --- /dev/null +++ b/beta/patches/@lezer+javascript+0.15.2.patch @@ -0,0 +1,345 @@ +diff --git a/node_modules/@lezer/javascript/dist/index.cjs b/node_modules/@lezer/javascript/dist/index.cjs +index 2d4ede8..622851f 100644 +--- a/node_modules/@lezer/javascript/dist/index.cjs ++++ b/node_modules/@lezer/javascript/dist/index.cjs +@@ -6,16 +6,16 @@ var lr = require('@lezer/lr'); + var common = require('@lezer/common'); + + // This file was generated by lezer-generator. You probably shouldn't edit it. +-const noSemi = 275, ++const noSemi = 277, + incdec = 1, + incdecPrefix = 2, +- templateContent = 276, +- templateDollarBrace = 277, +- templateEnd = 278, +- insertSemi = 279, ++ templateContent = 278, ++ templateDollarBrace = 279, ++ templateEnd = 280, ++ insertSemi = 281, + TSExtends = 3, +- spaces = 281, +- newline = 282, ++ spaces = 283, ++ newline = 284, + LineComment = 4, + BlockComment = 5, + Dialect_ts = 1; +@@ -95,31 +95,31 @@ function tsExtends(value, stack) { + } + + // This file was generated by lezer-generator. You probably shouldn't edit it. +-const spec_identifier = {__proto__:null,export:16, as:21, from:25, default:30, async:35, function:36, this:46, true:54, false:54, void:60, typeof:64, null:78, super:80, new:114, await:131, yield:133, delete:134, class:144, extends:146, public:189, private:189, protected:189, readonly:191, instanceof:212, in:214, const:216, import:248, keyof:299, unique:303, infer:309, is:343, abstract:363, implements:365, type:367, let:370, var:372, interface:379, enum:383, namespace:389, module:391, declare:395, global:399, for:420, of:429, while:432, with:436, do:440, if:444, else:446, switch:450, case:456, try:462, catch:464, finally:466, return:470, throw:474, break:478, continue:482, debugger:486}; +-const spec_word = {__proto__:null,async:101, get:103, set:105, public:153, private:153, protected:153, static:155, abstract:157, override:159, readonly:165, new:347}; ++const spec_identifier = {__proto__:null,export:16, as:21, from:25, default:30, async:35, function:36, this:46, true:54, false:54, void:60, typeof:64, null:78, super:80, new:114, await:131, yield:133, delete:134, class:144, extends:146, public:189, private:189, protected:189, readonly:191, instanceof:212, in:214, const:216, import:248, keyof:303, unique:307, infer:313, is:347, abstract:367, implements:369, type:371, let:374, var:376, interface:383, enum:387, namespace:393, module:395, declare:399, global:403, for:424, of:433, while:436, with:440, do:444, if:448, else:450, switch:454, case:460, try:466, catch:468, finally:470, return:474, throw:478, break:482, continue:486, debugger:490}; ++const spec_word = {__proto__:null,async:101, get:103, set:105, public:153, private:153, protected:153, static:155, abstract:157, override:159, readonly:165, new:351}; + const spec_LessThan = {__proto__:null,"<":121}; + const parser = lr.LRParser.deserialize({ + version: 13, +- states: "$1WO`QYOOO'QQ!LdO'#CgO'XOSO'#DSO)dQYO'#DXO)tQYO'#DdO){QYO'#DnO-xQYO'#DtOOQO'#EX'#EXO.]QWO'#EWO.bQWO'#EWOOQ!LS'#Eb'#EbO0aQ!LdO'#IqO2wQ!LdO'#IrO3eQWO'#EvO3jQpO'#F]OOQ!LS'#FO'#FOO3rO!bO'#FOO4QQWO'#FdO5_QWO'#FcOOQ!LS'#Ir'#IrOOQ!LQ'#Iq'#IqOOQQ'#J['#J[O5dQWO'#HjO5iQ!LYO'#HkOOQQ'#Ic'#IcOOQQ'#Hl'#HlQ`QYOOO){QYO'#DfO5qQWO'#GWO5vQ#tO'#ClO6UQWO'#EVO6aQWO'#EcO6fQ#tO'#E}O7QQWO'#GWO7VQWO'#G[O7bQWO'#G[O7pQWO'#G_O7pQWO'#G`O7pQWO'#GbO5qQWO'#GeO8aQWO'#GhO9oQWO'#CcO:PQWO'#GuO:XQWO'#G{O:XQWO'#G}O`QYO'#HPO:XQWO'#HRO:XQWO'#HUO:^QWO'#H[O:cQ!LZO'#H`O){QYO'#HbO:nQ!LZO'#HdO:yQ!LZO'#HfO5iQ!LYO'#HhO){QYO'#IsOOOS'#Hn'#HnO;UOSO,59nOOQ!LS,59n,59nO=gQbO'#CgO=qQYO'#HoO>OQWO'#ItO?}QbO'#ItO'dQYO'#ItO@UQWO,59sO@lQ&jO'#D^OAeQWO'#EXOArQWO'#JPOA}QWO'#JOOBVQWO,5:uOB[QWO'#I}OBcQWO'#DuO5vQ#tO'#EVOBqQWO'#EVOB|Q`O'#E}OOQ!LS,5:O,5:OOCUQYO,5:OOESQ!LdO,5:YOEpQWO,5:`OFZQ!LYO'#I|O7VQWO'#I{OFbQWO'#I{OFjQWO,5:tOFoQWO'#I{OF}QYO,5:rOH}QWO'#ESOJXQWO,5:rOKhQWO'#DhOKoQYO'#DmOKyQ&jO,5:{O){QYO,5:{OOQQ'#En'#EnOOQQ'#Ep'#EpO){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}OOQQ'#Et'#EtOLRQYO,5;_OOQ!LS,5;d,5;dOOQ!LS,5;e,5;eONRQWO,5;eOOQ!LS,5;f,5;fO){QYO'#HyONWQ!LYO,5<PONrQWO,5:}O){QYO,5;bO! [QpO'#JTONyQpO'#JTO! cQpO'#JTO! tQpO,5;mOOOO,5;w,5;wO!!SQYO'#F_OOOO'#Hx'#HxO3rO!bO,5;jO!!ZQpO'#FaOOQ!LS,5;j,5;jO!!wQ,UO'#CqOOQ!LS'#Ct'#CtO!#[QWO'#CtO!#aOSO'#CxO!#}Q#tO,5;|O!$UQWO,5<OO!%bQWO'#FnO!%oQWO'#FoO!%tQWO'#FsO!&vQ&jO'#FwO!'iQ,UO'#IlOOQ!LS'#Il'#IlO!'sQWO'#IkO!(RQWO'#IjOOQ!LS'#Cr'#CrOOQ!LS'#Cy'#CyO!(ZQWO'#C{OJ^QWO'#FfOJ^QWO'#FhO!(`QWO'#FjO!(eQWO'#FkO!(jQWO'#FqOJ^QWO'#FvO!(oQWO'#EYO!)WQWO,5;}O`QYO,5>UOOQQ'#If'#IfOOQQ,5>V,5>VOOQQ-E;j-E;jO!+SQ!LdO,5:QOOQ!LQ'#Co'#CoO!+sQ#tO,5<rOOQO'#Ce'#CeO!,UQWO'#CpO!,^Q!LYO'#IgO5_QWO'#IgO:^QWO,59WO!,lQpO,59WO!,tQ#tO,59WO5vQ#tO,59WO!-PQWO,5:rO!-XQWO'#GtO!-dQWO'#J`O){QYO,5;gO!-lQ&jO,5;iO!-qQWO,5=_O!-vQWO,5=_O!-{QWO,5=_O5iQ!LYO,5=_O5qQWO,5<rO!.ZQWO'#EZO!.lQ&jO'#E[OOQ!LQ'#I}'#I}O!.}Q!LYO'#J]O5iQ!LYO,5<vO7pQWO,5<|OOQO'#Cq'#CqO!/YQpO,5<yO!/bQ#tO,5<zO!/mQWO,5<|O!/rQ`O,5=PO:^QWO'#GjO5qQWO'#GlO!/zQWO'#GlO5vQ#tO'#GoO!0PQWO'#GoOOQQ,5=S,5=SO!0UQWO'#GpO!0^QWO'#ClO!0cQWO,58}O!0mQWO,58}O!2oQYO,58}OOQQ,58},58}O!2|Q!LYO,58}O){QYO,58}O!3XQYO'#GwOOQQ'#Gx'#GxOOQQ'#Gy'#GyO`QYO,5=aO!3iQWO,5=aO){QYO'#DtO`QYO,5=gO`QYO,5=iO!3nQWO,5=kO`QYO,5=mO!3sQWO,5=pO!3xQYO,5=vOOQQ,5=z,5=zO){QYO,5=zO5iQ!LYO,5=|OOQQ,5>O,5>OO!7yQWO,5>OOOQQ,5>Q,5>QO!7yQWO,5>QOOQQ,5>S,5>SO!8OQ`O,5?_OOOS-E;l-E;lOOQ!LS1G/Y1G/YO!8TQbO,5>ZO){QYO,5>ZOOQO-E;m-E;mO!8_QWO,5?`O!8gQbO,5?`O!8nQWO,5?jOOQ!LS1G/_1G/_O!8vQpO'#DQOOQO'#Iv'#IvO){QYO'#IvO!9eQpO'#IvO!:SQpO'#D_O!:eQ&jO'#D_O!<pQYO'#D_O!<wQWO'#IuO!=PQWO,59xO!=UQWO'#E]O!=dQWO'#JQO!=lQWO,5:vO!>SQ&jO'#D_O){QYO,5?kO!>^QWO'#HtO!8nQWO,5?jOOQ!LQ1G0a1G0aO!?jQ&jO'#DxOOQ!LS,5:a,5:aO){QYO,5:aOH}QWO,5:aO!?qQWO,5:aO:^QWO,5:qO!,lQpO,5:qO!,tQ#tO,5:qO5vQ#tO,5:qOOQ!LS1G/j1G/jOOQ!LS1G/z1G/zOOQ!LQ'#ER'#ERO){QYO,5?hO!?|Q!LYO,5?hO!@_Q!LYO,5?hO!@fQWO,5?gO!@nQWO'#HvO!@fQWO,5?gOOQ!LQ1G0`1G0`O7VQWO,5?gOOQ!LS1G0^1G0^O!AYQ!LdO1G0^O!AyQ!LbO,5:nOOQ!LS'#Fm'#FmO!BgQ!LdO'#IlOF}QYO1G0^O!DfQ#tO'#IwO!DpQWO,5:SO!DuQbO'#IxO){QYO'#IxO!EPQWO,5:XOOQ!LS'#DQ'#DQOOQ!LS1G0g1G0gO!EUQWO1G0gO!GgQ!LdO1G0iO!GnQ!LdO1G0iO!JRQ!LdO1G0iO!JYQ!LdO1G0iO!LaQ!LdO1G0iO!LtQ!LdO1G0iO# eQ!LdO1G0iO# lQ!LdO1G0iO#$PQ!LdO1G0iO#$WQ!LdO1G0iO#%{Q!LdO1G0iO#(uQ7^O'#CgO#*pQ7^O1G0yO#,kQ7^O'#IrOOQ!LS1G1P1G1PO#-OQ!LdO,5>eOOQ!LQ-E;w-E;wO#-oQ!LdO1G0iOOQ!LS1G0i1G0iO#/qQ!LdO1G0|O#0bQpO,5;oO#0gQpO,5;pO#0lQpO'#FWO#1QQWO'#FVOOQO'#JU'#JUOOQO'#Hw'#HwO#1VQpO1G1XOOQ!LS1G1X1G1XOOOO1G1b1G1bO#1eQ7^O'#IqO#1oQWO,5;yOLRQYO,5;yOOOO-E;v-E;vOOQ!LS1G1U1G1UOOQ!LS,5;{,5;{O#1tQpO,5;{OOQ!LS,59`,59`OH}QWO'#InOOOS'#Hm'#HmO#1yOSO,59dOOQ!LS,59d,59dO){QYO1G1hO!(eQWO'#H{O#2UQWO,5<aOOQ!LS,5<^,5<^OOQO'#GR'#GROJ^QWO,5<lOOQO'#GT'#GTOJ^QWO,5<nOJ^QWO,5<pOOQO1G1j1G1jO#2aQ`O'#CoO#2tQ`O,5<YO#2{QWO'#JXO5qQWO'#JXO#3ZQWO,5<[OJ^QWO,5<ZO#3`Q`O'#FmO#3mQ`O'#JYO#3wQWO'#JYOH}QWO'#JYO#3|QWO,5<_OOQ!LQ'#Dc'#DcO#4RQWO'#FpO#4^QpO'#FxO!&qQ&jO'#FxO!&qQ&jO'#FzO#4oQWO'#F{O!(jQWO'#GOOOQO'#H}'#H}O#4tQ&jO,5<cOOQ!LS,5<c,5<cO#4{Q&jO'#FxO#5ZQ&jO'#FyO#5cQ&jO'#FyOOQ!LS,5<q,5<qOJ^QWO,5?VOJ^QWO,5?VO#5hQWO'#IOO#5sQWO,5?UOOQ!LS'#Cg'#CgO#6gQ#tO,59gOOQ!LS,59g,59gO#7YQ#tO,5<QO#7{Q#tO,5<SO#8VQWO,5<UOOQ!LS,5<V,5<VO#8[QWO,5<]O#8aQ#tO,5<bOF}QYO1G1iO#8qQWO1G1iOOQQ1G3p1G3pOOQ!LS1G/l1G/lONRQWO1G/lOOQQ1G2^1G2^OH}QWO1G2^O){QYO1G2^OH}QWO1G2^O#8vQWO1G2^O#9UQWO,59[O#:_QWO'#ESOOQ!LQ,5?R,5?RO#:iQ!LYO,5?ROOQQ1G.r1G.rO:^QWO1G.rO!,lQpO1G.rO!,tQ#tO1G.rO#:wQWO1G0^O#:|QWO'#CgO#;XQWO'#JaO#;aQWO,5=`O#;fQWO'#JaO#;kQWO'#JaO#;pQWO'#IWO#<OQWO,5?zO#<WQbO1G1ROOQ!LS1G1T1G1TO5qQWO1G2yO#<_QWO1G2yO#<dQWO1G2yO#<iQWO1G2yOOQQ1G2y1G2yO#<nQ#tO1G2^O7VQWO'#JOO7VQWO'#E]O7VQWO'#IQO#=PQ!LYO,5?wOOQQ1G2b1G2bO!/mQWO1G2hOH}QWO1G2eO#=[QWO1G2eOOQQ1G2f1G2fOH}QWO1G2fO#=aQWO1G2fO#=iQ&jO'#GdOOQQ1G2h1G2hO!&qQ&jO'#ISO!/rQ`O1G2kOOQQ1G2k1G2kOOQQ,5=U,5=UO#=qQ#tO,5=WO5qQWO,5=WO#4oQWO,5=ZO5_QWO,5=ZO!,lQpO,5=ZO!,tQ#tO,5=ZO5vQ#tO,5=ZO#>SQWO'#J_O#>_QWO,5=[OOQQ1G.i1G.iO#>dQ!LYO1G.iO#>oQWO1G.iO!(ZQWO1G.iO5iQ!LYO1G.iO#>tQbO,5?|O#?OQWO,5?|O#?ZQYO,5=cO#?bQWO,5=cO7VQWO,5?|OOQQ1G2{1G2{O`QYO1G2{OOQQ1G3R1G3ROOQQ1G3T1G3TO:XQWO1G3VO#?gQYO1G3XO#CbQYO'#HWOOQQ1G3[1G3[O:^QWO1G3bO#CoQWO1G3bO5iQ!LYO1G3fOOQQ1G3h1G3hOOQ!LQ'#Ft'#FtO5iQ!LYO1G3jO5iQ!LYO1G3lOOOS1G4y1G4yO#CwQ`O,5<PO#DPQbO1G3uO#DZQWO1G4zO#DcQWO1G5UO#DkQWO,5?bOLRQYO,5:wO7VQWO,5:wO:^QWO,59yOLRQYO,59yO!,lQpO,59yO#DpQ7^O,59yOOQO,5:w,5:wO#DzQ&jO'#HpO#EbQWO,5?aOOQ!LS1G/d1G/dO#EjQ&jO'#HuO#FOQWO,5?lOOQ!LQ1G0b1G0bO!:eQ&jO,59yO#FWQbO1G5VOOQO,5>`,5>`O7VQWO,5>`OOQO-E;r-E;rOOQ!LQ'#EO'#EOO#FbQ!LrO'#EPO!?bQ&jO'#DyOOQO'#Hs'#HsO#F|Q&jO,5:dOOQ!LS,5:d,5:dO#GTQ&jO'#DyO#GfQ&jO'#DyO#GmQ&jO'#EUO#GpQ&jO'#EPO#G}Q&jO'#EPO!?bQ&jO'#EPO#HbQWO1G/{O#HgQ`O1G/{OOQ!LS1G/{1G/{O){QYO1G/{OH}QWO1G/{OOQ!LS1G0]1G0]O:^QWO1G0]O!,lQpO1G0]O!,tQ#tO1G0]O#HnQ!LdO1G5SO){QYO1G5SO#IOQ!LYO1G5SO#IaQWO1G5RO7VQWO,5>bOOQO,5>b,5>bO#IiQWO,5>bOOQO-E;t-E;tO#IaQWO1G5RO#IwQ!LdO,59gO#KvQ!LdO,5<QO#MxQ!LdO,5<SO$ zQ!LdO,5<bOOQ!LS7+%x7+%xO$$SQ!LdO7+%xO$$sQWO'#HqO$$}QWO,5?cOOQ!LS1G/n1G/nO$%VQYO'#HrO$%dQWO,5?dO$%lQbO,5?dOOQ!LS1G/s1G/sOOQ!LS7+&R7+&RO$%vQ7^O,5:YO){QYO7+&eO$&QQ7^O,5:QOOQO1G1Z1G1ZOOQO1G1[1G1[O$&_QMhO,5;rOLRQYO,5;qOOQO-E;u-E;uOOQ!LS7+&s7+&sOOOO7+&|7+&|OOOO1G1e1G1eO$&jQWO1G1eOOQ!LS1G1g1G1gO$&oQ`O,5?YOOOS-E;k-E;kOOQ!LS1G/O1G/OO$&vQ!LdO7+'SOOQ!LS,5>g,5>gO$'gQWO,5>gOOQ!LS1G1{1G1{P$'lQWO'#H{POQ!LS-E;y-E;yO$(]Q#tO1G2WO$)OQ#tO1G2YO$)YQ#tO1G2[OOQ!LS1G1t1G1tO$)aQWO'#HzO$)oQWO,5?sO$)oQWO,5?sO$)wQWO,5?sO$*SQWO,5?sOOQO1G1v1G1vO$*bQ#tO1G1uO$*rQWO'#H|O$+SQWO,5?tOH}QWO,5?tO$+[Q`O,5?tOOQ!LS1G1y1G1yO5iQ!LYO,5<dO5iQ!LYO,5<eO$+fQWO,5<eO#4jQWO,5<eO!,lQpO,5<dO$+kQWO,5<fO5iQ!LYO,5<gO$+fQWO,5<jOOQO-E;{-E;{OOQ!LS1G1}1G1}O!&qQ&jO,5<dO$+sQWO,5<eO!&qQ&jO,5<fO!&qQ&jO,5<eO$,OQ#tO1G4qO$,YQ#tO1G4qOOQO,5>j,5>jOOQO-E;|-E;|O!-lQ&jO,59iO){QYO,59iO$,gQWO1G1pOJ^QWO1G1wO$,lQ!LdO7+'TOOQ!LS7+'T7+'TOF}QYO7+'TOOQ!LS7+%W7+%WO$-]Q`O'#JZO#HbQWO7+'xO$-gQWO7+'xO$-oQ`O7+'xOOQQ7+'x7+'xOH}QWO7+'xO){QYO7+'xOH}QWO7+'xOOQO1G.v1G.vO$-yQ!LbO'#CgO$.ZQ!LbO,5<hO$.xQWO,5<hOOQ!LQ1G4m1G4mOOQQ7+$^7+$^O:^QWO7+$^O!,lQpO7+$^OF}QYO7+%xO$.}QWO'#IVO$/]QWO,5?{OOQO1G2z1G2zO5qQWO,5?{O$/]QWO,5?{O$/eQWO,5?{OOQO,5>r,5>rOOQO-E<U-E<UOOQ!LS7+&m7+&mO$/jQWO7+(eO5iQ!LYO7+(eO5qQWO7+(eO$/oQWO7+(eO$/tQWO7+'xOOQ!LQ,5>l,5>lOOQ!LQ-E<O-E<OOOQQ7+(S7+(SO$0SQ!LbO7+(POH}QWO7+(PO$0^Q`O7+(QOOQQ7+(Q7+(QOH}QWO7+(QO$0eQWO'#J^O$0pQWO,5=OOOQO,5>n,5>nOOQO-E<Q-E<QOOQQ7+(V7+(VO$1jQ&jO'#GmOOQQ1G2r1G2rOH}QWO1G2rO){QYO1G2rOH}QWO1G2rO$1qQWO1G2rO$2PQ#tO1G2rO5iQ!LYO1G2uO#4oQWO1G2uO5_QWO1G2uO!,lQpO1G2uO!,tQ#tO1G2uO$2bQWO'#IUO$2mQWO,5?yO$2uQ&jO,5?yOOQ!LQ1G2v1G2vOOQQ7+$T7+$TO$2zQWO7+$TO5iQ!LYO7+$TO$3PQWO7+$TO){QYO1G5hO){QYO1G5iO$3UQYO1G2}O$3]QWO1G2}O$3bQYO1G2}O$3iQ!LYO1G5hOOQQ7+(g7+(gO5iQ!LYO7+(qO`QYO7+(sOOQQ'#Jd'#JdOOQQ'#IX'#IXO$3sQYO,5=rOOQQ,5=r,5=rO){QYO'#HXO$4QQWO'#HZOOQQ7+(|7+(|O$4VQYO7+(|O7VQWO7+(|OOQQ7+)Q7+)QOOQQ7+)U7+)UOOQQ7+)W7+)WOOQO1G4|1G4|O$8TQ7^O1G0cO$8_QWO1G0cOOQO1G/e1G/eO$8jQ7^O1G/eO:^QWO1G/eOLRQYO'#D_OOQO,5>[,5>[OOQO-E;n-E;nOOQO,5>a,5>aOOQO-E;s-E;sO!,lQpO1G/eOOQO1G3z1G3zO:^QWO,5:eOOQO,5:k,5:kO){QYO,5:kO$8tQ!LYO,5:kO$9PQ!LYO,5:kO!,lQpO,5:eOOQO-E;q-E;qOOQ!LS1G0O1G0OO!?bQ&jO,5:eO$9_Q&jO,5:eO$9pQ!LrO,5:kO$:[Q&jO,5:eO!?bQ&jO,5:kOOQO,5:p,5:pO$:cQ&jO,5:kO$:pQ!LYO,5:kOOQ!LS7+%g7+%gO#HbQWO7+%gO#HgQ`O7+%gOOQ!LS7+%w7+%wO:^QWO7+%wO!,lQpO7+%wO$;UQ!LdO7+*nO){QYO7+*nOOQO1G3|1G3|O7VQWO1G3|O$;fQWO7+*mO$;nQ!LdO1G2WO$=pQ!LdO1G2YO$?rQ!LdO1G1uO$AzQ#tO,5>]OOQO-E;o-E;oO$BUQbO,5>^O){QYO,5>^OOQO-E;p-E;pO$B`QWO1G5OO$BhQ7^O1G0^O$DoQ7^O1G0iO$DvQ7^O1G0iO$FwQ7^O1G0iO$GOQ7^O1G0iO$HsQ7^O1G0iO$IWQ7^O1G0iO$KeQ7^O1G0iO$KlQ7^O1G0iO$MmQ7^O1G0iO$MtQ7^O1G0iO% iQ7^O1G0iO% |Q!LdO<<JPO%!mQ7^O1G0iO%$]Q7^O'#IlO%&YQ7^O1G0|OLRQYO'#FYOOQO'#JV'#JVOOQO1G1^1G1^O%&gQWO1G1]O%&lQ7^O,5>eOOOO7+'P7+'POOOS1G4t1G4tOOQ!LS1G4R1G4ROJ^QWO7+'vO%&vQWO,5>fO5qQWO,5>fOOQO-E;x-E;xO%'UQWO1G5_O%'UQWO1G5_O%'^QWO1G5_O%'iQ`O,5>hO%'sQWO,5>hOH}QWO,5>hOOQO-E;z-E;zO%'xQ`O1G5`O%(SQWO1G5`OOQO1G2O1G2OOOQO1G2P1G2PO5iQ!LYO1G2PO$+fQWO1G2PO5iQ!LYO1G2OO%([QWO1G2QOH}QWO1G2QOOQO1G2R1G2RO5iQ!LYO1G2UO!,lQpO1G2OO#4jQWO1G2PO%(aQWO1G2QO%(iQWO1G2POJ^QWO7+*]OOQ!LS1G/T1G/TO%(tQWO1G/TOOQ!LS7+'[7+'[O%(yQ#tO7+'cO%)ZQ!LdO<<JoOOQ!LS<<Jo<<JoOH}QWO'#IPO%)zQWO,5?uOOQQ<<Kd<<KdOH}QWO<<KdO#HbQWO<<KdO%*SQWO<<KdO%*[Q`O<<KdOH}QWO1G2SOOQQ<<Gx<<GxO:^QWO<<GxO%*fQ!LdO<<IdOOQ!LS<<Id<<IdOOQO,5>q,5>qO%+VQWO,5>qO#;kQWO,5>qOOQO-E<T-E<TO%+[QWO1G5gO%+[QWO1G5gO5qQWO1G5gO%+dQWO<<LPOOQQ<<LP<<LPO%+iQWO<<LPO5iQ!LYO<<LPO){QYO<<KdOH}QWO<<KdOOQQ<<Kk<<KkO$0SQ!LbO<<KkOOQQ<<Kl<<KlO$0^Q`O<<KlO%+nQ&jO'#IRO%+yQWO,5?xOLRQYO,5?xOOQQ1G2j1G2jO#FbQ!LrO'#EPO!?bQ&jO'#GnOOQO'#IT'#ITO%,RQ&jO,5=XOOQQ,5=X,5=XO%,YQ&jO'#EPO%,eQ&jO'#EPO%,|Q&jO'#EPO%-WQ&jO'#GnO%-iQWO7+(^O%-nQWO7+(^O%-vQ`O7+(^OOQQ7+(^7+(^OH}QWO7+(^O){QYO7+(^OH}QWO7+(^O%.QQWO7+(^OOQQ7+(a7+(aO5iQ!LYO7+(aO#4oQWO7+(aO5_QWO7+(aO!,lQpO7+(aO%.`QWO,5>pOOQO-E<S-E<SOOQO'#Gq'#GqO%.kQWO1G5eO5iQ!LYO<<GoOOQQ<<Go<<GoO%.sQWO<<GoO%.xQWO7++SO%.}QWO7++TOOQQ7+(i7+(iO%/SQWO7+(iO%/XQYO7+(iO%/`QWO7+(iO){QYO7++SO){QYO7++TOOQQ<<L]<<L]OOQQ<<L_<<L_OOQQ-E<V-E<VOOQQ1G3^1G3^O%/eQWO,5=sOOQQ,5=u,5=uO:^QWO<<LhO%/jQWO<<LhOLRQYO7+%}OOQO7+%P7+%PO%/oQ7^O1G5VO:^QWO7+%POOQO1G0P1G0PO%/yQ!LdO1G0VOOQO1G0V1G0VO){QYO1G0VO%0TQ!LYO1G0VO:^QWO1G0PO!,lQpO1G0PO!?bQ&jO1G0PO%0`Q!LYO1G0VO%0nQ&jO1G0PO%1PQ!LYO1G0VO%1eQ!LrO1G0VO%1oQ&jO1G0PO!?bQ&jO1G0VOOQ!LS<<IR<<IROOQ!LS<<Ic<<IcO:^QWO<<IcO%1vQ!LdO<<NYOOQO7+)h7+)hO%2WQ!LdO7+'cO%4`QbO1G3xO%4jQ7^O7+%xO%4wQ7^O,59gO%6tQ7^O,5<QO%8qQ7^O,5<SO%:nQ7^O,5<bO%<^Q7^O7+'SO%<kQ7^O7+'TO%<xQWO,5;tOOQO7+&w7+&wO%<}Q#tO<<KbOOQO1G4Q1G4QO%=_QWO1G4QO%=jQWO1G4QO%=xQWO7+*yO%=xQWO7+*yOH}QWO1G4SO%>QQ`O1G4SO%>[QWO7+*zOOQO7+'k7+'kO5iQ!LYO7+'kOOQO7+'j7+'jO$+fQWO7+'lO%>dQ`O7+'lOOQO7+'p7+'pO5iQ!LYO7+'jO$+fQWO7+'kO%>kQWO7+'lOH}QWO7+'lO#4jQWO7+'kO%>pQ#tO<<MwOOQ!LS7+$o7+$oO%>zQ`O,5>kOOQO-E;}-E;}O#HbQWOANAOOOQQANAOANAOOH}QWOANAOO%?UQ!LbO7+'nOOQQAN=dAN=dO5qQWO1G4]OOQO1G4]1G4]O%?cQWO1G4]O%?hQWO7++RO%?hQWO7++RO5iQ!LYOANAkO%?pQWOANAkOOQQANAkANAkO%?uQWOANAOO%?}Q`OANAOOOQQANAVANAVOOQQANAWANAWO%@XQWO,5>mOOQO-E<P-E<PO%@dQ7^O1G5dO#4oQWO,5=YO5_QWO,5=YO!,lQpO,5=YOOQO-E<R-E<ROOQQ1G2s1G2sO$9pQ!LrO,5:kO!?bQ&jO,5=YO%@nQ&jO,5=YO%APQ&jO,5:kOOQQ<<Kx<<KxOH}QWO<<KxO%-iQWO<<KxO%AZQWO<<KxO%AcQ`O<<KxO){QYO<<KxOH}QWO<<KxOOQQ<<K{<<K{O5iQ!LYO<<K{O#4oQWO<<K{O5_QWO<<K{O%AmQ&jO1G4[O%ArQWO7++POOQQAN=ZAN=ZO5iQ!LYOAN=ZOOQQ<<Nn<<NnOOQQ<<No<<NoOOQQ<<LT<<LTO%AzQWO<<LTO%BPQYO<<LTO%BWQWO<<NnO%B]QWO<<NoOOQQ1G3_1G3_OOQQANBSANBSO:^QWOANBSO%BbQ7^O<<IiOOQO<<Hk<<HkOOQO7+%q7+%qO%/yQ!LdO7+%qO){QYO7+%qOOQO7+%k7+%kO:^QWO7+%kO!,lQpO7+%kO%BlQ!LYO7+%qO!?bQ&jO7+%kO%BwQ!LYO7+%qO%CVQ&jO7+%kO%ChQ!LYO7+%qOOQ!LSAN>}AN>}O%C|Q!LdO<<KbO%FUQ7^O<<JPO%FcQ7^O1G1uO%HRQ7^O1G2WO%JOQ7^O1G2YO%K{Q7^O<<JoO%LYQ7^O<<IdOOQO1G1`1G1`OOQO7+)l7+)lO%LgQWO7+)lO%LrQWO<<NeO%LzQ`O7+)nOOQO<<KV<<KVO5iQ!LYO<<KWO$+fQWO<<KWOOQO<<KU<<KUO5iQ!LYO<<KVO%MUQ`O<<KWO$+fQWO<<KVOOQQG26jG26jO#HbQWOG26jOOQO7+)w7+)wO5qQWO7+)wO%M]QWO<<NmOOQQG27VG27VO5iQ!LYOG27VOH}QWOG26jOLRQYO1G4XO%MeQWO7++OO5iQ!LYO1G2tO#4oQWO1G2tO5_QWO1G2tO!,lQpO1G2tO!?bQ&jO1G2tO%1eQ!LrO1G0VO%MmQ&jO1G2tO%-iQWOANAdOOQQANAdANAdOH}QWOANAdO%NOQWOANAdO%NWQ`OANAdOOQQANAgANAgO5iQ!LYOANAgO#4oQWOANAgOOQO'#Gr'#GrOOQO7+)v7+)vOOQQG22uG22uOOQQANAoANAoO%NbQWOANAoOOQQANDYANDYOOQQANDZANDZO%NgQYOG27nOOQO<<I]<<I]O%/yQ!LdO<<I]OOQO<<IV<<IVO:^QWO<<IVO){QYO<<I]O!,lQpO<<IVO&$eQ!LYO<<I]O!?bQ&jO<<IVO&$pQ!LYO<<I]O&%OQ7^O7+'cOOQO<<MW<<MWOOQOAN@rAN@rO5iQ!LYOAN@rOOQOAN@qAN@qO$+fQWOAN@rO5iQ!LYOAN@qOOQQLD,ULD,UOOQO<<Mc<<McOOQQLD,qLD,qO#HbQWOLD,UO&&nQ7^O7+)sOOQO7+(`7+(`O5iQ!LYO7+(`O#4oQWO7+(`O5_QWO7+(`O!,lQpO7+(`O!?bQ&jO7+(`OOQQG27OG27OO%-iQWOG27OOH}QWOG27OOOQQG27RG27RO5iQ!LYOG27ROOQQG27ZG27ZO:^QWOLD-YOOQOAN>wAN>wOOQOAN>qAN>qO%/yQ!LdOAN>wO:^QWOAN>qO){QYOAN>wO!,lQpOAN>qO&&xQ!LYOAN>wO&'TQ7^O<<KbOOQOG26^G26^O5iQ!LYOG26^OOQOG26]G26]OOQQ!$( p!$( pOOQO<<Kz<<KzO5iQ!LYO<<KzO#4oQWO<<KzO5_QWO<<KzO!,lQpO<<KzOOQQLD,jLD,jO%-iQWOLD,jOOQQLD,mLD,mOOQQ!$(!t!$(!tOOQOG24cG24cOOQOG24]G24]O%/yQ!LdOG24cO:^QWOG24]O){QYOG24cOOQOLD+xLD+xOOQOANAfANAfO5iQ!LYOANAfO#4oQWOANAfO5_QWOANAfOOQQ!$(!U!$(!UOOQOLD)}LD)}OOQOLD)wLD)wO%/yQ!LdOLD)}OOQOG27QG27QO5iQ!LYOG27QO#4oQWOG27QOOQO!$'Mi!$'MiOOQOLD,lLD,lO5iQ!LYOLD,lOOQO!$(!W!$(!WOLRQYO'#DnO&(sQbO'#IqOLRQYO'#DfO&(zQ!LdO'#CgO&)eQbO'#CgO&)uQYO,5:rOLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO'#HyO&+uQWO,5<PO&-XQWO,5:}OLRQYO,5;bO!(ZQWO'#C{O!(ZQWO'#C{OH}QWO'#FfO&+}QWO'#FfOH}QWO'#FhO&+}QWO'#FhOH}QWO'#FvO&+}QWO'#FvOLRQYO,5?kO&)uQYO1G0^O&-`Q7^O'#CgOLRQYO1G1hOH}QWO,5<lO&+}QWO,5<lOH}QWO,5<nO&+}QWO,5<nOH}QWO,5<ZO&+}QWO,5<ZO&)uQYO1G1iOLRQYO7+&eOH}QWO1G1wO&+}QWO1G1wO&)uQYO7+'TO&)uQYO7+%xOH}QWO7+'vO&+}QWO7+'vO&-jQWO'#EWO&-oQWO'#EWO&-wQWO'#EvO&-|QWO'#EcO&.RQWO'#JPO&.^QWO'#I}O&.iQWO,5:rO&.nQ#tO,5;|O&.uQWO'#FoO&.zQWO'#FoO&/PQWO,5;}O&/XQWO,5:rO&/aQ7^O1G0yO&/hQWO,5<]O&/mQWO,5<]O&/rQWO1G1iO&/wQWO1G0^O&/|Q#tO1G2[O&0TQ#tO1G2[O4QQWO'#FdO5_QWO'#FcOBqQWO'#EVOLRQYO,5;_O!(jQWO'#FqO!(jQWO'#FqOJ^QWO,5<pOJ^QWO,5<p", +- stateData: "&1Q~O'TOS'UOSSOSTOS~OPTOQTOWyO]cO^hOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#`sO#ppO#t^O${qO$}tO%PrO%QrO%TuO%VvO%YwO%ZwO%]xO%jzO%p{O%r|O%t}O%v!OO%y!PO&P!QO&T!RO&V!SO&X!TO&Z!UO&]!VO'WPO'aQO'mYO'zaO~OPZXYZX^ZXiZXrZXsZXuZX}ZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'RZX'aZX'nZX'uZX'vZX~O!X$hX~P$zO'O!XO'P!WO'Q!ZO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W![O'aQO'mYO'zaO~O|!`O}!]Oz'hPz'rP~P'dO!O!lO~P`OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W9VO'aQO'mYO'zaO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'aQO'mYO'zaO~O|!qO#Q!tO#R!qO'W9WO!_'oP~P+{O#S!uO~O!X!vO#S!uO~OP#]OY#cOi#QOr!zOs!zOu!{O}#aO!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~O^'eX'R'eX!_'eXz'eX!P'eX$|'eX!X'eX~P.jO!w#dO#k#dOP'fXY'fX^'fXi'fXr'fXs'fXu'fX}'fX!]'fX!^'fX!`'fX!f'fX#W'fX#X'fX#Y'fX#Z'fX#['fX#]'fX#^'fX#a'fX#c'fX#e'fX#f'fX'a'fX'n'fX'u'fX'v'fX~O#_'fX'R'fXz'fX!_'fX'c'fX!P'fX$|'fX!X'fX~P0zO!w#dO~O#v#eO#}#iO~O!P#jO#t^O$Q#kO$S#mO~O]#pOg#}Oi#qOj#pOk#pOn$OOp$POu#wO!P#xO!Z$UO!`#uO#R$VO#p$SO$Z$QO$]$RO$`$TO'W#oO'a#rO'['^P~O!`$WO~O!X$YO~O^$ZO'R$ZO~O'W$_O~O!`$WO'W$_O'X$aO']$bO~Ob$hO!`$WO'W$_O~O#_#SO~O]$qOr$mO!P$jO!`$lO$}$pO'W$_O'X$aO[(SP~O!j$rO~Ou$sO!P$tO'W$_O~Ou$sO!P$tO%V$xO'W$_O~O'W$yO~O#`sO$}tO%PrO%QrO%TuO%VvO%YwO%ZwO~Oa%SOb%RO!j%PO${%QO%_%OO~P7uOa%VObmO!P%UO!jlO#`sO${qO%PrO%QrO%TuO%VvO%YwO%ZwO%]xO~O_%YO!w%]O$}%WO'X$aO~P8tO!`%^O!c%bO~O!`%cO~O!PSO~O^$ZO&}%kO'R$ZO~O^$ZO&}%nO'R$ZO~O^$ZO&}%pO'R$ZO~O'O!XO'P!WO'Q%tO~OPZXYZXiZXrZXsZXuZX}ZX}cX!]ZX!^ZX!`ZX!fZX!wZX!wcX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'aZX'nZX'uZX'vZX~OzZXzcX~P;aO|%vOz&cX}&cX~P){O}!]Oz'hX~OP#]OY#cOi#QOr!zOs!zOu!{O}!]O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~Oz'hX~P>WOz%{O~Ou&OO!S&YO!T&RO!U&RO'X$aO~O]&POj&PO|&SO'd%|O!O'iP!O'tP~P@ZOz'qX}'qX!X'qX!_'qX'n'qX~O!w'qX#S!{X!O'qX~PASO!w&ZOz'sX}'sX~O}&[Oz'rX~Oz&^O~O!w#dO~PASOR&bO!P&_O!k&aO'W$_O~Ob&gO!`$WO'W$_O~Or$mO!`$lO~O!O&hO~P`Or!zOs!zOu!{O!^!xO!`!yO'aQOP!baY!bai!ba}!ba!]!ba!f!ba#W!ba#X!ba#Y!ba#Z!ba#[!ba#]!ba#^!ba#_!ba#a!ba#c!ba#e!ba#f!ba'n!ba'u!ba'v!ba~O^!ba'R!baz!ba!_!ba'c!ba!P!ba$|!ba!X!ba~PC]O!_&iO~O!X!vO!w&kO'n&jO}'pX^'pX'R'pX~O!_'pX~PEuO}&oO!_'oX~O!_&qO~Ou$sO!P$tO#R&rO'W$_O~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W9VO'aQO'mYO'zaO~O]#pOg#}Oi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'W&vO'a#rO~O#S&xO~O]#pOg#}Oi#qOj#pOk#pOn$OOp$POu#wO!P#xO!Z$UO!`#uO#R$VO#p$SO$Z$QO$]$RO$`$TO'W&vO'a#rO~O'['kP~PJ^O|&|O!_'lP~P){O'd'OO'mYO~OP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!P!bO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'W'^O'aQO'mYO'z:hO~O!`!yO~O}#aO^$Xa'R$Xa!_$Xaz$Xa!P$Xa$|$Xa!X$Xa~O#`'eO~PH}O!X'gO!P'wX#s'wX#v'wX#}'wX~Or'hO~PNyOr'hO!P'wX#s'wX#v'wX#}'wX~O!P'jO#s'nO#v'iO#}'oO~O|'rO~PLRO#v#eO#}'uO~Or$aXu$aX!^$aX'n$aX'u$aX'v$aX~OReX}eX!weX'[eX'[$aX~P!!cOj'wO~O'O'yO'P'xO'Q'{O~Or'}Ou(OO'n#ZO'u(QO'v(SO~O'['|O~P!#lO'[(VO~O]#pOg#}Oi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'a#rO~O|(ZO'W(WO!_'{P~P!$ZO#S(]O~O|(aO'W(^Oz'|P~P!$ZO^(jOi(oOu(gO!S(mO!T(fO!U(fO!`(dO!t(nO$s(iO'X$aO'd(cO~O!O(lO~P!&RO!^!xOr'`Xu'`X'n'`X'u'`X'v'`X}'`X!w'`X~O'['`X#i'`X~P!&}OR(rO!w(qO}'_X'['_X~O}(sO'['^X~O'W(uO~O!`(zO~O'W&vO~O!`(dO~Ou$sO|!qO!P$tO#Q!tO#R!qO'W$_O!_'oP~O!X!vO#S)OO~OP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~O^!Ya}!Ya'R!Yaz!Ya!_!Ya'c!Ya!P!Ya$|!Ya!X!Ya~P!)`OR)WO!P&_O!k)VO$|)UO']$bO~O'W$yO'['^P~O!X)ZO!P'ZX^'ZX'R'ZX~O!`$WO']$bO~O!`$WO'W$_O']$bO~O!X!vO#S&xO~O$})gO'W)cO!O(TP~O})hO[(SX~O'd'OO~OY)lO~O[)mO~O!P$jO'W$_O'X$aO[(SP~Ou$sO|)rO!P$tO'W$_Oz'rP~O]&VOj&VO|)sO'd'OO!O'tP~O})tO^(PX'R(PX~O!w)xO']$bO~OR){O!P#xO']$bO~O!P)}O~Or*PO!PSO~O!j*UO~Ob*ZO~O'W(uO!O(RP~Ob$hO~O$}tO'W$yO~P8tOY*aO[*`O~OPTOQTO]cOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#t^O${qO'aQO'mYO'zaO~O!P!bO#p!kO'W9VO~P!0uO[*`O^$ZO'R$ZO~O^*eO#`*gO%P*gO%Q*gO~P){O!`%^O~O%p*lO~O!P*nO~O&Q*qO&R*pOP&OaQ&OaW&Oa]&Oa^&Oaa&Oab&Oag&Oai&Oaj&Oak&Oan&Oap&Oau&Oaw&Oax&Oay&Oa!P&Oa!Z&Oa!`&Oa!c&Oa!d&Oa!e&Oa!f&Oa!g&Oa!j&Oa#`&Oa#p&Oa#t&Oa${&Oa$}&Oa%P&Oa%Q&Oa%T&Oa%V&Oa%Y&Oa%Z&Oa%]&Oa%j&Oa%p&Oa%r&Oa%t&Oa%v&Oa%y&Oa&P&Oa&T&Oa&V&Oa&X&Oa&Z&Oa&]&Oa&|&Oa'W&Oa'a&Oa'm&Oa'z&Oa!O&Oa%w&Oa_&Oa%|&Oa~O'W*tO~O'c*wO~Oz&ca}&ca~P!)`O}!]Oz'ha~Oz'ha~P>WO}&[Oz'ra~O}tX}!VX!OtX!O!VX!XtX!X!VX!`!VX!wtX']!VX~O!X+OO!w*}O}#PX}'jX!O#PX!O'jX!X'jX!`'jX']'jX~O!X+QO!`$WO']$bO}!RX!O!RX~O]%}Oj%}Ou&OO'd(cO~OP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!P!bO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'aQO'mYO'z:hO~O'W9sO~P!:sO}+UO!O'iX~O!O+WO~O!X+OO!w*}O}#PX!O#PX~O}+XO!O'tX~O!O+ZO~O]%}Oj%}Ou&OO'X$aO'd(cO~O!T+[O!U+[O~P!=qOu$sO|+_O!P$tO'W$_Oz&hX}&hX~O^+dO!S+gO!T+cO!U+cO!n+kO!o+iO!p+jO!q+hO!t+lO'X$aO'd(cO'm+aO~O!O+fO~P!>rOR+qO!P&_O!k+pO~O!w+wO}'pa!_'pa^'pa'R'pa~O!X!vO~P!?|O}&oO!_'oa~Ou$sO|+zO!P$tO#Q+|O#R+zO'W$_O}&jX!_&jX~O^!zi}!zi'R!ziz!zi!_!zi'c!zi!P!zi$|!zi!X!zi~P!)`O#S!va}!va!_!va!w!va!P!va^!va'R!vaz!va~P!#lO#S'`XP'`XY'`X^'`Xi'`Xs'`X!]'`X!`'`X!f'`X#W'`X#X'`X#Y'`X#Z'`X#['`X#]'`X#^'`X#_'`X#a'`X#c'`X#e'`X#f'`X'R'`X'a'`X!_'`Xz'`X!P'`X'c'`X$|'`X!X'`X~P!&}O},VO'['kX~P!#lO'[,XO~O},YO!_'lX~P!)`O!_,]O~Oz,^O~OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'aQOY#Vi^#Vii#Vi}#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O#W#Vi~P!EZO#W#OO~P!EZOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO'aQOY#Vi^#Vi}#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~Oi#Vi~P!GuOi#QO~P!GuOP#]Oi#QOr!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO'aQO^#Vi}#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P!JaOY#cO!]#SO#]#SO#^#SO#_#SO~P!JaOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO'aQO^#Vi}#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O'u#Vi~P!MXO'u!|O~P!MXOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO'aQO'u!|O^#Vi}#Vi#e#Vi#f#Vi'R#Vi'n#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O'v#Vi~P# sO'v!}O~P# sOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO'aQO'u!|O'v!}O~O^#Vi}#Vi#f#Vi'R#Vi'n#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~P#$_OPZXYZXiZXrZXsZXuZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'aZX'nZX'uZX'vZX}ZX!OZX~O#iZX~P#&rOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO#e9cO#f9dO'aQO'n#ZO'u!|O'v!}O~O#i,`O~P#(|OP'fXY'fXi'fXr'fXs'fXu'fX!]'fX!^'fX!`'fX!f'fX#W'fX#X'fX#Y'fX#Z'fX#['fX#]'fX#^'fX#a'fX#c'fX#e'fX#f'fX'a'fX'n'fX'u'fX'v'fX}'fX~O!w9hO#k9hO#_'fX#i'fX!O'fX~P#*wO^&ma}&ma'R&ma!_&ma'c&maz&ma!P&ma$|&ma!X&ma~P!)`OP#ViY#Vi^#Vii#Vis#Vi}#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'a#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~P!#lO^#ji}#ji'R#jiz#ji!_#ji'c#ji!P#ji$|#ji!X#ji~P!)`O#v,bO~O#v,cO~O!X'gO!w,dO!P#zX#s#zX#v#zX#}#zX~O|,eO~O!P'jO#s,gO#v'iO#},hO~O}9eO!O'eX~P#(|O!O,iO~O#},kO~O'O'yO'P'xO'Q,nO~O],qOj,qOz,rO~O}cX!XcX!_cX!_$aX'ncX~P!!cO!_,xO~P!#lO},yO!X!vO'n&jO!_'{X~O!_-OO~Oz$aX}$aX!X$hX~P!!cO}-QOz'|X~P!#lO!X-SO~Oz-UO~O|(ZO'W$_O!_'{P~Oi-YO!X!vO!`$WO']$bO'n&jO~O!X)ZO~O!O-`O~P!&RO!T-aO!U-aO'X$aO'd(cO~Ou-cO'd(cO~O!t-dO~O'W$yO}&rX'[&rX~O}(sO'['^a~Or-iOs-iOu-jO'noa'uoa'voa}oa!woa~O'[oa#ioa~P#5{Or'}Ou(OO'n$Ya'u$Ya'v$Ya}$Ya!w$Ya~O'[$Ya#i$Ya~P#6qOr'}Ou(OO'n$[a'u$[a'v$[a}$[a!w$[a~O'[$[a#i$[a~P#7dO]-kO~O#S-lO~O'[$ja}$ja#i$ja!w$ja~P!#lO#S-oO~OR-xO!P&_O!k-wO$|-vO~O'[-yO~O]#pOi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'a#rO~Og-{O'W-zO~P#9ZO!X)ZO!P'Za^'Za'R'Za~O#S.RO~OYZX}cX!OcX~O}.SO!O(TX~O!O.UO~OY.VO~O'W)cO~O!P$jO'W$_O[&zX}&zX~O})hO[(Sa~O!_.[O~P!)`O].^O~OY._O~O[.`O~OR-xO!P&_O!k-wO$|-vO']$bO~O})tO^(Pa'R(Pa~O!w.fO~OR.iO!P#xO~O'd'OO!O(QP~OR.sO!P.oO!k.rO$|.qO']$bO~OY.}O}.{O!O(RX~O!O/OO~O[/QO^$ZO'R$ZO~O]/RO~O#_/TO%n/UO~P0zO!w#dO#_/TO%n/UO~O^/VO~P){O^/XO~O%w/]OP%uiQ%uiW%ui]%ui^%uia%uib%uig%uii%uij%uik%uin%uip%uiu%uiw%uix%uiy%ui!P%ui!Z%ui!`%ui!c%ui!d%ui!e%ui!f%ui!g%ui!j%ui#`%ui#p%ui#t%ui${%ui$}%ui%P%ui%Q%ui%T%ui%V%ui%Y%ui%Z%ui%]%ui%j%ui%p%ui%r%ui%t%ui%v%ui%y%ui&P%ui&T%ui&V%ui&X%ui&Z%ui&]%ui&|%ui'W%ui'a%ui'm%ui'z%ui!O%ui_%ui%|%ui~O_/cO!O/aO%|/bO~P`O!PSO!`/fO~O}#aO'c$Xa~Oz&ci}&ci~P!)`O}!]Oz'hi~O}&[Oz'ri~Oz/jO~O}!Ra!O!Ra~P#(|O]%}Oj%}O|/pO'd(cO}&dX!O&dX~P@ZO}+UO!O'ia~O]&VOj&VO|)sO'd'OO}&iX!O&iX~O}+XO!O'ta~Oz'si}'si~P!)`O^$ZO!X!vO!`$WO!f/{O!w/yO'R$ZO']$bO'n&jO~O!O0OO~P!>rO!T0PO!U0PO'X$aO'd(cO'm+aO~O!S0QO~P#GTO!PSO!S0QO!q0SO!t0TO~P#GTO!S0QO!o0VO!p0VO!q0SO!t0TO~P#GTO!P&_O~O!P&_O~P!#lO}'pi!_'pi^'pi'R'pi~P!)`O!w0`O}'pi!_'pi^'pi'R'pi~O}&oO!_'oi~Ou$sO!P$tO#R0bO'W$_O~O#SoaPoaYoa^oaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa'Roa'aoa!_oazoa!Poa'coa$|oa!Xoa~P#5{O#S$YaP$YaY$Ya^$Yai$Yas$Ya!]$Ya!^$Ya!`$Ya!f$Ya#W$Ya#X$Ya#Y$Ya#Z$Ya#[$Ya#]$Ya#^$Ya#_$Ya#a$Ya#c$Ya#e$Ya#f$Ya'R$Ya'a$Ya!_$Yaz$Ya!P$Ya'c$Ya$|$Ya!X$Ya~P#6qO#S$[aP$[aY$[a^$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a'R$[a'a$[a!_$[az$[a!P$[a'c$[a$|$[a!X$[a~P#7dO#S$jaP$jaY$ja^$jai$jas$ja}$ja!]$ja!^$ja!`$ja!f$ja#W$ja#X$ja#Y$ja#Z$ja#[$ja#]$ja#^$ja#_$ja#a$ja#c$ja#e$ja#f$ja'R$ja'a$ja!_$jaz$ja!P$ja!w$ja'c$ja$|$ja!X$ja~P!#lO^!zq}!zq'R!zqz!zq!_!zq'c!zq!P!zq$|!zq!X!zq~P!)`O}&eX'[&eX~PJ^O},VO'['ka~O|0jO}&fX!_&fX~P){O},YO!_'la~O},YO!_'la~P!)`O#i!ba!O!ba~PC]O#i!Ya}!Ya!O!Ya~P#(|O!P0}O#t^O#{1OO~O!O1SO~O'c1TO~P!#lO^$Uq}$Uq'R$Uqz$Uq!_$Uq'c$Uq!P$Uq$|$Uq!X$Uq~P!)`Oz1UO~O],qOj,qO~Or'}Ou(OO'v(SO'n$ti'u$ti}$ti!w$ti~O'[$ti#i$ti~P$'tOr'}Ou(OO'n$vi'u$vi'v$vi}$vi!w$vi~O'[$vi#i$vi~P$(gO#i1VO~P!#lO|1XO'W$_O}&nX!_&nX~O},yO!_'{a~O},yO!X!vO!_'{a~O},yO!X!vO'n&jO!_'{a~O'[$ci}$ci#i$ci!w$ci~P!#lO|1`O'W(^Oz&pX}&pX~P!$ZO}-QOz'|a~O}-QOz'|a~P!#lO!X!vO~O!X!vO#_1jO~Oi1nO!X!vO'n&jO~O}'_i'['_i~P!#lO!w1qO}'_i'['_i~P!#lO!_1tO~O^$Vq}$Vq'R$Vqz$Vq!_$Vq'c$Vq!P$Vq$|$Vq!X$Vq~P!)`O}1xO!P'}X~P!#lO!P&_O$|1{O~O!P&_O$|1{O~P!#lO!P$aX$qZX^$aX'R$aX~P!!cO$q2POrfXufX!PfX'nfX'ufX'vfX^fX'RfX~O$q2PO~O$}2WO'W)cO}&yX!O&yX~O}.SO!O(Ta~OY2[O~O[2]O~O]2`O~OR2bO!P&_O!k2aO$|1{O~O^$ZO'R$ZO~P!#lO!P#xO~P!#lO}2gO!w2iO!O(QX~O!O2jO~Ou(gO!S2sO!T2lO!U2lO!n2rO!o2qO!p2qO!t2pO'X$aO'd(cO'm+aO~O!O2oO~P$0uOR2zO!P.oO!k2yO$|2xO~OR2zO!P.oO!k2yO$|2xO']$bO~O'W(uO}&xX!O&xX~O}.{O!O(Ra~O'd3TO~O]3VO~O[3XO~O!_3[O~P){O^3^O~O^3^O~P){O#_3`O%n3aO~PEuO_/cO!O3eO%|/bO~P`O!X3gO~O&R3hOP&OqQ&OqW&Oq]&Oq^&Oqa&Oqb&Oqg&Oqi&Oqj&Oqk&Oqn&Oqp&Oqu&Oqw&Oqx&Oqy&Oq!P&Oq!Z&Oq!`&Oq!c&Oq!d&Oq!e&Oq!f&Oq!g&Oq!j&Oq#`&Oq#p&Oq#t&Oq${&Oq$}&Oq%P&Oq%Q&Oq%T&Oq%V&Oq%Y&Oq%Z&Oq%]&Oq%j&Oq%p&Oq%r&Oq%t&Oq%v&Oq%y&Oq&P&Oq&T&Oq&V&Oq&X&Oq&Z&Oq&]&Oq&|&Oq'W&Oq'a&Oq'm&Oq'z&Oq!O&Oq%w&Oq_&Oq%|&Oq~O}#Pi!O#Pi~P#(|O!w3jO}#Pi!O#Pi~O}!Ri!O!Ri~P#(|O^$ZO!w3qO'R$ZO~O^$ZO!X!vO!w3qO'R$ZO~O!T3uO!U3uO'X$aO'd(cO'm+aO~O^$ZO!X!vO!`$WO!f3vO!w3qO'R$ZO']$bO'n&jO~O!S3wO~P$9_O!S3wO!q3zO!t3{O~P$9_O^$ZO!X!vO!f3vO!w3qO'R$ZO'n&jO~O}'pq!_'pq^'pq'R'pq~P!)`O}&oO!_'oq~O#S$tiP$tiY$ti^$tii$tis$ti!]$ti!^$ti!`$ti!f$ti#W$ti#X$ti#Y$ti#Z$ti#[$ti#]$ti#^$ti#_$ti#a$ti#c$ti#e$ti#f$ti'R$ti'a$ti!_$tiz$ti!P$ti'c$ti$|$ti!X$ti~P$'tO#S$viP$viY$vi^$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi'R$vi'a$vi!_$viz$vi!P$vi'c$vi$|$vi!X$vi~P$(gO#S$ciP$ciY$ci^$cii$cis$ci}$ci!]$ci!^$ci!`$ci!f$ci#W$ci#X$ci#Y$ci#Z$ci#[$ci#]$ci#^$ci#_$ci#a$ci#c$ci#e$ci#f$ci'R$ci'a$ci!_$ciz$ci!P$ci!w$ci'c$ci$|$ci!X$ci~P!#lO}&ea'[&ea~P!#lO}&fa!_&fa~P!)`O},YO!_'li~O#i!zi}!zi!O!zi~P#(|OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'aQOY#Vii#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~O#W#Vi~P$BuO#W9YO~P$BuOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO'aQOY#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~Oi#Vi~P$D}Oi9[O~P$D}OP#]Oi9[Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O'aQO#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P$GVOY9gO!]9^O#]9^O#^9^O#_9^O~P$GVOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O'aQO#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'v#Vi}#Vi!O#Vi~O'u#Vi~P$IkO'u!|O~P$IkOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO'aQO'u!|O#e#Vi#f#Vi#i#Vi'n#Vi}#Vi!O#Vi~O'v#Vi~P$KsO'v!}O~P$KsOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO#e9cO'aQO'u!|O'v!}O~O#f#Vi#i#Vi'n#Vi}#Vi!O#Vi~P$M{O^#gy}#gy'R#gyz#gy!_#gy'c#gy!P#gy$|#gy!X#gy~P!)`OP#ViY#Vii#Vis#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'a#Vi}#Vi!O#Vi~P!#lO!^!xOP'`XY'`Xi'`Xr'`Xs'`Xu'`X!]'`X!`'`X!f'`X#W'`X#X'`X#Y'`X#Z'`X#['`X#]'`X#^'`X#_'`X#a'`X#c'`X#e'`X#f'`X#i'`X'a'`X'n'`X'u'`X'v'`X}'`X!O'`X~O#i#ji}#ji!O#ji~P#(|O!O4]O~O}&ma!O&ma~P#(|O!X!vO'n&jO}&na!_&na~O},yO!_'{i~O},yO!X!vO!_'{i~Oz&pa}&pa~P!#lO!X4dO~O}-QOz'|i~P!#lO}-QOz'|i~Oz4jO~O!X!vO#_4pO~Oi4qO!X!vO'n&jO~Oz4sO~O'[$eq}$eq#i$eq!w$eq~P!#lO^$Vy}$Vy'R$Vyz$Vy!_$Vy'c$Vy!P$Vy$|$Vy!X$Vy~P!)`O}1xO!P'}a~O!P&_O$|4xO~O!P&_O$|4xO~P!#lO^!zy}!zy'R!zyz!zy!_!zy'c!zy!P!zy$|!zy!X!zy~P!)`OY4{O~O}.SO!O(Ti~O]5QO~O[5RO~O'd'OO}&uX!O&uX~O}2gO!O(Qa~O!O5`O~P$0uOu-cO'd(cO'm+aO~O!S5cO!T5bO!U5bO!t0TO'X$aO'd(cO'm+aO~O!o5dO!p5dO~P%,eO!T5bO!U5bO'X$aO'd(cO'm+aO~O!P.oO~O!P.oO$|5fO~O!P.oO$|5fO~P!#lOR5kO!P.oO!k5jO$|5fO~OY5pO}&xa!O&xa~O}.{O!O(Ri~O]5sO~O!_5tO~O!_5uO~O!_5vO~O!_5vO~P){O^5xO~O!X5{O~O!_5}O~O}'si!O'si~P#(|O^$ZO'R$ZO~P!)`O^$ZO!w6SO'R$ZO~O^$ZO!X!vO!w6SO'R$ZO~O!T6XO!U6XO'X$aO'd(cO'm+aO~O^$ZO!X!vO!f6YO!w6SO'R$ZO'n&jO~O!`$WO']$bO~P%1PO!S6ZO~P%0nO}'py!_'py^'py'R'py~P!)`O#S$eqP$eqY$eq^$eqi$eqs$eq}$eq!]$eq!^$eq!`$eq!f$eq#W$eq#X$eq#Y$eq#Z$eq#[$eq#]$eq#^$eq#_$eq#a$eq#c$eq#e$eq#f$eq'R$eq'a$eq!_$eqz$eq!P$eq!w$eq'c$eq$|$eq!X$eq~P!#lO}&fi!_&fi~P!)`O#i!zq}!zq!O!zq~P#(|Or-iOs-iOu-jOPoaYoaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa#ioa'aoa'noa'uoa'voa}oa!Ooa~Or'}Ou(OOP$YaY$Yai$Yas$Ya!]$Ya!^$Ya!`$Ya!f$Ya#W$Ya#X$Ya#Y$Ya#Z$Ya#[$Ya#]$Ya#^$Ya#_$Ya#a$Ya#c$Ya#e$Ya#f$Ya#i$Ya'a$Ya'n$Ya'u$Ya'v$Ya}$Ya!O$Ya~Or'}Ou(OOP$[aY$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a#i$[a'a$[a'n$[a'u$[a'v$[a}$[a!O$[a~OP$jaY$jai$jas$ja!]$ja!^$ja!`$ja!f$ja#W$ja#X$ja#Y$ja#Z$ja#[$ja#]$ja#^$ja#_$ja#a$ja#c$ja#e$ja#f$ja#i$ja'a$ja}$ja!O$ja~P!#lO#i$Uq}$Uq!O$Uq~P#(|O#i$Vq}$Vq!O$Vq~P#(|O!O6eO~O'[$xy}$xy#i$xy!w$xy~P!#lO!X!vO}&ni!_&ni~O!X!vO'n&jO}&ni!_&ni~O},yO!_'{q~Oz&pi}&pi~P!#lO}-QOz'|q~Oz6lO~P!#lOz6lO~O}'_y'['_y~P!#lO}&sa!P&sa~P!#lO!P$pq^$pq'R$pq~P!#lOY6tO~O}.SO!O(Tq~O]6wO~O!P&_O$|6xO~O!P&_O$|6xO~P!#lO!w6yO}&ua!O&ua~O}2gO!O(Qi~P#(|O!T7PO!U7PO'X$aO'd(cO'm+aO~O!S7RO!t3{O~P%@nO!P.oO$|7UO~O!P.oO$|7UO~P!#lO'd7[O~O}.{O!O(Rq~O!_7_O~O!_7_O~P){O!_7aO~O!_7bO~O}#Py!O#Py~P#(|O^$ZO!w7hO'R$ZO~O^$ZO!X!vO!w7hO'R$ZO~O!T7kO!U7kO'X$aO'd(cO'm+aO~O^$ZO!X!vO!f7lO!w7hO'R$ZO'n&jO~O#S$xyP$xyY$xy^$xyi$xys$xy}$xy!]$xy!^$xy!`$xy!f$xy#W$xy#X$xy#Y$xy#Z$xy#[$xy#]$xy#^$xy#_$xy#a$xy#c$xy#e$xy#f$xy'R$xy'a$xy!_$xyz$xy!P$xy!w$xy'c$xy$|$xy!X$xy~P!#lO#i#gy}#gy!O#gy~P#(|OP$ciY$cii$cis$ci!]$ci!^$ci!`$ci!f$ci#W$ci#X$ci#Y$ci#Z$ci#[$ci#]$ci#^$ci#_$ci#a$ci#c$ci#e$ci#f$ci#i$ci'a$ci}$ci!O$ci~P!#lOr'}Ou(OO'v(SOP$tiY$tii$tis$ti!]$ti!^$ti!`$ti!f$ti#W$ti#X$ti#Y$ti#Z$ti#[$ti#]$ti#^$ti#_$ti#a$ti#c$ti#e$ti#f$ti#i$ti'a$ti'n$ti'u$ti}$ti!O$ti~Or'}Ou(OOP$viY$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi#i$vi'a$vi'n$vi'u$vi'v$vi}$vi!O$vi~O#i$Vy}$Vy!O$Vy~P#(|O#i!zy}!zy!O!zy~P#(|O!X!vO}&nq!_&nq~O},yO!_'{y~Oz&pq}&pq~P!#lOz7rO~P!#lO}.SO!O(Ty~O}2gO!O(Qq~O!T8OO!U8OO'X$aO'd(cO'm+aO~O!P.oO$|8RO~O!P.oO$|8RO~P!#lO!_8UO~O&R8VOP&O!ZQ&O!ZW&O!Z]&O!Z^&O!Za&O!Zb&O!Zg&O!Zi&O!Zj&O!Zk&O!Zn&O!Zp&O!Zu&O!Zw&O!Zx&O!Zy&O!Z!P&O!Z!Z&O!Z!`&O!Z!c&O!Z!d&O!Z!e&O!Z!f&O!Z!g&O!Z!j&O!Z#`&O!Z#p&O!Z#t&O!Z${&O!Z$}&O!Z%P&O!Z%Q&O!Z%T&O!Z%V&O!Z%Y&O!Z%Z&O!Z%]&O!Z%j&O!Z%p&O!Z%r&O!Z%t&O!Z%v&O!Z%y&O!Z&P&O!Z&T&O!Z&V&O!Z&X&O!Z&Z&O!Z&]&O!Z&|&O!Z'W&O!Z'a&O!Z'm&O!Z'z&O!Z!O&O!Z%w&O!Z_&O!Z%|&O!Z~O^$ZO!w8[O'R$ZO~O^$ZO!X!vO!w8[O'R$ZO~OP$eqY$eqi$eqs$eq!]$eq!^$eq!`$eq!f$eq#W$eq#X$eq#Y$eq#Z$eq#[$eq#]$eq#^$eq#_$eq#a$eq#c$eq#e$eq#f$eq#i$eq'a$eq}$eq!O$eq~P!#lO}&uq!O&uq~P#(|O^$ZO!w8qO'R$ZO~OP$xyY$xyi$xys$xy!]$xy!^$xy!`$xy!f$xy#W$xy#X$xy#Y$xy#Z$xy#[$xy#]$xy#^$xy#_$xy#a$xy#c$xy#e$xy#f$xy#i$xy'a$xy}$xy!O$xy~P!#lO'c'eX~P.jO'cZXzZX!_ZX%nZX!PZX$|ZX!XZX~P$zO!XcX!_ZX!_cX'ncX~P;aOP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!PSO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'W'^O'aQO'mYO'z:hO~O}9eO!O$Xa~O]#pOg#}Oi#qOj#pOk#pOn$OOp9jOu#wO!P#xO!Z:mO!`#uO#R9pO#p$SO$Z9lO$]9nO$`$TO'W&vO'a#rO~O#`'eO~P&+}O!OZX!OcX~P;aO#S9XO~O!X!vO#S9XO~O!w9hO~O#_9^O~O!w9qO}'sX!O'sX~O!w9hO}'qX!O'qX~O#S9rO~O'[9tO~P!#lO#S9yO~O#S9zO~O!X!vO#S9{O~O!X!vO#S9rO~O#i9|O~P#(|O#S9}O~O#S:OO~O#S:PO~O#S:QO~O#i:RO~P!#lO#i:SO~P!#lO#t~!^!n!p!q#Q#R'z$Z$]$`$q${$|$}%T%V%Y%Z%]%_~TS#t'z#Xy'T'U#v'T'W'd~", +- goto: "#Dk(XPPPPPPP(YP(jP*^PPPP-sPP.Y3j5^5qP5qPPP5q5qP5qP7_PP7dP7xPPPP<XPPPP<X>wPPP>}AYP<XPCsPPPPEk<XPPPPPGd<XPPJcK`PPPPKdL|PMUNVPK`<X<X!#^!&V!*v!*v!.TPPP!.[!1O<XPPPPPPPPPP!3sP!5UPP<X!6cP<XP<X<X<X<XP<X!8vPP!;mP!>`!>h!>l!>lP!;jP!>p!>pP!AcP!Ag<X<X!Am!D_5qP5qP5q5qP!Eb5q5q!GY5q!I[5q!J|5q5q!Kj!Md!Md!Mh!Md!MpP!MdP5q!Nl5q# v5q5q-sPPP##TPP##m##mP##mP#$S##mPP#$YP#$PP#$P#$lMQ#$P#%Z#%a#%d(Y#%g(YP#%n#%n#%nP(YP(YP(YP(YPP(YP#%t#%wP#%w(YPPP(YP(YP(YP(YP(YP(Y(Y#%{#&V#&]#&c#&q#&w#&}#'X#'_#'i#'o#'}#(T#(Z#(i#)O#*b#*p#*v#*|#+S#+Y#+d#+j#+p#+z#,^#,dPPPPPPPPP#,jPP#-^#1[PP#2r#2y#3RP#7_PP#7c#9v#?p#?t#?w#?z#@V#@YPP#@]#@a#AO#As#Aw#BZPP#B_#Be#BiP#Bl#Bp#Bs#Cc#Cy#DO#DR#DU#D[#D_#Dc#DgmhOSj}!m$Y%a%d%e%g*i*n/]/`Q$gmQ$npQ%XyS&R!b+UQ&f!iS(f#x(kQ)a$hQ)n$pQ*Y%RQ+[&YS+c&_+eQ+u&gQ-a(mQ.z*ZY0P+g+h+i+j+kS2l.o2nU3u0Q0S0VU5b2q2r2sS6X3w3zS7P5c5dQ7k6ZR8O7R$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!j'`#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ(v$PQ)f$jQ*[%UQ*c%^Q,P9iQ-|)ZQ.X)gQ/S*aQ2V.SQ3R.{Q4U9jR4}2WpeOSjy}!m$Y%W%a%d%e%g*i*n/]/`R*^%Y&WVOSTjkn}!S!W!]!j!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:j:kW!cRU!`&SQ$`lQ$fmS$kp$pv$urs!q!t$W$s&[&o&r)r)s)t*g+O+_+z+|/f0bQ$}wQ&c!hQ&e!iS(Y#u(dS)`$g$hQ)d$jQ)q$rQ*T%PQ*X%RS+t&f&gQ,}(ZQ.Q)aQ.W)gQ.Y)hQ.])lQ.u*US.y*Y*ZQ0^+uQ1W,yQ2U.SQ2Y.VQ2_._Q3Q.zQ4a1XQ4|2WQ5P2[Q6s4{R7u6t!Y$dm!i$f$g$h&Q&e&f&g(e)`)a+R+b+t+u-Z.Q/u/|0R0^1m3t3y6V7i8]Q)X$`Q)y$zQ)|${Q*W%RQ.a)qQ.t*TU.x*X*Y*ZQ2{.uS3P.y.zQ5]2kQ5o3QS6}5^5aS7|7O7QQ8g7}R8v8hW#{a$b(s:hS$zt%WQ${uQ$|vR)w$x$V#za!v!x#c#u#w$Q$R$V&b'x(R(T(U(](a(q(r)U)W)Z)x){+q,V-Q-S-l-v-x.f.i.q.s1V1`1j1q1x1{2P2b2x2z4d4p4x5f5k6x7U8R9g9k9l9m9n9o9p9u9v9w9x9y9z9}:O:R:S:h:n:oV(w$P9i9jU&V!b$t+XQ'P!zQ)k$mQ.j)}Q1r-iR5X2g&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k$]#`Z!_!n$^%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,a,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:a&ZcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ&T!bR/q+UY%}!b&R&Y+U+[S(e#x(kS+b&_+eS-Z(f(mQ-[(gQ-b(nQ.l*PU/|+c+g+hU0R+i+j+kS0W+l2pQ1m-aQ1o-cQ1p-dS2k.o2nU3t0P0Q0SQ3x0TQ3y0VS5^2l2sS5a2q2rU6V3u3w3zQ6[3{S7O5b5cQ7Q5dS7i6X6ZS7}7P7RQ8]7kR8h8OlhOSj}!m$Y%a%d%e%g*i*n/]/`Q%i!QS&s!u9XQ)^$eQ*R$}Q*S%OQ+r&dS,T&x9rS-n)O9{Q.O)_Q.n*QQ/d*pQ/e*qQ/m+PQ0U+iQ0[+sS1w-o:PQ2Q.PS2T.R:QQ3k/oQ3n/wQ3}0]Q4z2RQ5|3hQ6P3mQ6T3sQ6]4OQ7c5}Q7f6UQ8X7gQ8l8VQ8n8ZR8y8p$W#_Z!_!n%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:aU(p#y&w0{T)S$^,a$W#^Z!_!n%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:aQ'a#_S)R$^,aR-p)S&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ%d{Q%e|Q%g!OQ%h!PR/[*lQ&`!hQ)T$`Q+o&cS-u)X)qS0X+m+nW1z-r-s-t.aS3|0Y0ZU4w1|1}2OU6q4v5T5UQ7t6rR8c7wT+d&_+eS+b&_+eU/|+c+g+hU0R+i+j+kS0W+l2pS2k.o2nU3t0P0Q0SQ3x0TQ3y0VS5^2l2sS5a2q2rU6V3u3w3zQ6[3{S7O5b5cQ7Q5dS7i6X6ZS7}7P7RQ8]7kR8h8OS+d&_+eT2m.o2nS&m!p/YQ,|(YQ-X(eS/{+b2kQ1],}S1g-Y-bU3v0R0W5aQ4`1WS4n1n1pU6Y3x3y7QQ6g4aQ6p4qR7l6[Q!wXS&l!p/YQ)P$XQ)[$cQ)b$iQ+x&mQ,{(YQ-W(eQ-](hQ-})]Q.v*VS/z+b2kS1[,|,}S1f-X-bQ1i-[Q1l-^Q2}.wW3r/{0R0W5aQ4_1WQ4c1]S4h1g1pQ4o1oQ5m3OW6W3v3x3y7QS6f4`4aQ6k4jQ6n4nQ6{5[Q7Y5nS7j6Y6[Q7n6gQ7p6lQ7s6pQ7z6|Q8T7ZQ8^7lQ8a7rQ8e7{Q8t8fQ8|8uQ9Q8}Q:Z:UQ:d:_R:e:`$nWORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qS!wn!j!j:T#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR:Z:j$nXORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qQ$Xb!Y$cm!i$f$g$h&Q&e&f&g(e)`)a+R+b+t+u-Z.Q/u/|0R0^1m3t3y6V7i8]S$in!jQ)]$dQ*V%RW.w*W*X*Y*ZU3O.x.y.zQ5[2kS5n3P3QU6|5]5^5aQ7Z5oU7{6}7O7QS8f7|7}S8u8g8hQ8}8v!j:U#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ:_:iR:`:j$f]OSTjk}!S!W!]!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qU!gRU!`v$urs!q!t$W$s&[&o&r)r)s)t*g+O+_+z+|/f0bQ*d%^!h:V#[#j'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR:Y&SS&W!b$tR/s+X$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!j'`#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR*c%^$noORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qQ'P!z!k:W#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k!h#UZ!_$^%u%y&t&{'Y'Z'[']'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9T!R9`'_'p+S,a/k/n0m0u0v0w0x0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!d#WZ!_$^%u%y&t&{'[']'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9T}9b'_'p+S,a/k/n0m0w0x0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!`#[Z!_$^%u%y&t&{'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9Tl(U#s&y(},w-P-e-f0g1u4^4r:[:f:gx:k'_'p+S,a/k/n0m0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!`:n&u'd(X(_+n,S,l-T-q-t.e.g0Z0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7WZ:o0z4X6`7m8_&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kS#k`#lR1O,d&a_ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j#l$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,d,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kS#f^#mT'i#h'mT#g^#mT'k#h'm&a`ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j#l$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,d,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kT#k`#lQ#n`R't#l$nbORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!k:i#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k#RdOSUj}!S!W!m!{#j$Y%Y%]%^%a%c%d%e%g%k&O&a'r)V*e*i*n+p,e-j-w.r/T/U/V/X/]/`/b0}2a2y3^3`3a5j5xt#ya!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:o!|&w!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:RQ({$TQ,p'}c0{9g9l9n9p9v9x9z:O:St#va!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:oS(h#x(kQ(|$UQ-^(i!|:]!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:Rb:^9g9l9n9p9v9x9z:O:SQ:b:lR:c:mt#ya!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:o!|&w!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:Rc0{9g9l9n9p9v9x9z:O:SlfOSj}!m$Y%a%d%e%g*i*n/]/`Q(`#wQ*u%nQ*v%pR1_-Q$U#za!v!x#c#u#w$Q$R$V&b'x(R(T(U(](a(q(r)U)W)Z)x){+q,V-Q-S-l-v-x.f.i.q.s1V1`1j1q1x1{2P2b2x2z4d4p4x5f5k6x7U8R9g9k9l9m9n9o9p9u9v9w9x9y9z9}:O:R:S:h:n:oQ)z${Q.h)|Q2e.gR5W2fT(j#x(kS(j#x(kT2m.o2nQ)[$cQ-](hQ-})]Q.v*VQ2}.wQ5m3OQ6{5[Q7Y5nQ7z6|Q8T7ZQ8e7{Q8t8fQ8|8uR9Q8}l(R#s&y(},w-P-e-f0g1u4^4r:[:f:g!`9u&u'd(X(_+n,S,l-T-q-t.e.g0Z0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7WZ9v0z4X6`7m8_n(T#s&y(},u,w-P-e-f0g1u4^4r:[:f:g!b9w&u'd(X(_+n,S,l-T-q-t.e.g0Z0d0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7W]9x0z4X6`6a7m8_peOSjy}!m$Y%W%a%d%e%g*i*n/]/`Q%TxR*e%^peOSjy}!m$Y%W%a%d%e%g*i*n/]/`R%TxQ*O$|R.d)wqeOSjy}!m$Y%W%a%d%e%g*i*n/]/`Q.p*TS2w.t.uW5e2t2u2v2{U7T5g5h5iU8P7S7V7WQ8i8QR8w8jQ%[yR*_%WR3U.}R7]5pS$kp$pR.Y)hQ%azR*i%bR*o%hT/^*n/`QjOQ!mST$]j!mQ'z#rR,m'zQ!YQR%s!YQ!^RU%w!^%x*zQ%x!_R*z%yQ+V&TR/r+VQ,W&yR0h,WQ,Z&{S0k,Z0lR0l,[Q+e&_R/}+eQ&]!eQ*{%zT+`&]*{Q+Y&WR/t+YQ&p!rQ+y&nU+}&p+y0cR0c,OQ'm#hR,f'mQ#l`R's#lQ#bZU'c#b*x9fQ*x9TR9f'pQ,z(YW1Y,z1Z4b6hU1Z,{,|,}S4b1[1]R6h4c#q(P#s&u&y'd(X(_(x(y(}+n,Q,R,S,l,u,v,w-P-T-e-f-q-t.e.g0Z0d0e0f0g0z1^1b1u2O2d2f2v4R4V4W4X4^4e4k4r4t4y5U5i6^6`6a6b6i6o7W7m8_:[:f:gQ-R(_U1a-R1c4fQ1c-TR4f1bQ(k#xR-_(kQ(t#|R-h(tQ1y-qR4u1yQ)u$vR.c)uQ2h.jS5Y2h6zR6z5ZQ*Q$}R.m*QQ2n.oR5_2nQ.|*[S3S.|5qR5q3UQ.T)dW2X.T2Z5O6uQ2Z.WQ5O2YR6u5PQ)i$kR.Z)iQ/`*nR3d/`WiOSj!mQ%f}Q)Q$YQ*h%aQ*j%dQ*k%eQ*m%gQ/Z*iS/^*n/`R3c/]Q$[gQ%j!RQ%m!TQ%o!UQ%q!VQ)p$qQ)v$wQ*^%[Q*s%lS/P*_*bQ/g*rQ/h*uQ/i*vS/x+b2kQ1d-VQ1e-WQ1k-]Q2^.^Q2c.eQ2|.vQ3W/RQ3b/[Y3p/z/{0R0W5aQ4g1fQ4i1hQ4l1lQ5S2`Q5V2dQ5l2}Q5r3V[6Q3o3r3v3x3y7QQ6j4hQ6m4mQ6v5QQ7X5mQ7^5sW7d6R6W6Y6[Q7o6kQ7q6nQ7v6wQ7y6{Q8S7YU8W7e7j7lQ8`7pQ8b7sQ8d7zQ8k8TS8m8Y8^Q8r8aQ8s8eQ8x8oQ8{8tQ9O8zQ9P8|R9R9QQ$emQ&d!iU)_$f$g$hQ+P&QU+s&e&f&gQ-V(eS.P)`)aQ/o+RQ/w+bS0]+t+uQ1h-ZQ2R.QQ3m/uS3s/|0RQ4O0^Q4m1mS6U3t3yQ7g6VQ8Z7iR8p8]S#ta:hR)Y$bU#|a$b:hR-g(sQ#saS&u!v)ZQ&y!xQ'd#cQ(X#uQ(_#wQ(x$QQ(y$RQ(}$VQ+n&bQ,Q9kQ,R9mQ,S9oQ,l'xQ,u(RQ,v(TQ,w(UQ-P(]Q-T(aQ-e(qQ-f(rd-q)U-v.q1{2x4x5f6x7U8RQ-t)WQ.e)xQ.g){Q0Z+qQ0d9uQ0e9wQ0f9yQ0g,VQ0z9gQ1^-QQ1b-SQ1u-lQ2O-xQ2d.fQ2f.iQ2v.sQ4R9}Q4V9lQ4W9nQ4X9pQ4^1VQ4e1`Q4k1jQ4r1qQ4t1xQ4y2PQ5U2bQ5i2zQ6^:RQ6`9zQ6a9vQ6b9xQ6i4dQ6o4pQ7W5kQ7m:OQ8_:SQ:[:hQ:f:nR:g:oT'y#r'zlgOSj}!m$Y%a%d%e%g*i*n/]/`S!oU%cQ%l!SQ%r!WQ'Q!{Q'q#jS*b%Y%]Q*f%^Q*r%kQ*|&OQ+m&aQ,j'rQ-s)VQ/W*eQ0Y+pQ1Q,eQ1s-jQ1}-wQ2u.rQ3Y/TQ3Z/UQ3]/VQ3_/XQ3f/bQ4[0}Q5T2aQ5h2yQ5w3^Q5y3`Q5z3aQ7V5jR7`5x!vZOSUj}!S!m!{$Y%Y%]%^%a%c%d%e%g%k&O&a)V*e*i*n+p-j-w.r/T/U/V/X/]/`/b2a2y3^3`3a5j5xQ!_RQ!nTQ$^kQ%u!]Q%y!`Q&t!uQ&{!yQ'R#OQ'S#PQ'T#QQ'U#RQ'V#SQ'W#TQ'X#UQ'Y#VQ'Z#WQ'[#XQ']#YQ'_#[Q'b#aQ'f#dW'p#j'r,e0}Q)j$lQ*y%vS+S&S/pQ+]&ZQ+v&kQ,U&xQ,[&|Q,_9SQ,a9UQ,o'|Q-m)OQ/k*}Q/n+QQ0_+wQ0i,YQ0m9XQ0n9YQ0o9ZQ0p9[Q0q9]Q0r9^Q0s9_Q0t9`Q0u9aQ0v9bQ0w9cQ0x9dQ0y,`Q0|9hQ1R9eQ1v-oQ2S.RQ3l9qQ3o/yQ4P0`Q4S0jQ4T9rQ4Y9tQ4Z9{Q5Z2iQ6O3jQ6R3qQ6_9|Q6c:PQ6d:QQ7e6SQ7x6yQ8Y7hQ8o8[Q8z8qQ9T!WR:a:kT!XQ!YR!aRR&U!bS&Q!b+US+R&R&YR/u+[R&z!xR&}!yT!sU$WS!rU$WU$vrs*gS&n!q!tQ+{&oQ,O&rQ.b)tS0a+z+|R4Q0b[!dR!`$s&[)r+_h!pUrs!q!t$W&o&r)t+z+|0bQ/Y*gQ/l+OQ3i/fT:X&S)sT!fR$sS!eR$sS%z!`)rS+T&S)sQ+^&[R/v+_T&X!b$tQ#h^R'v#mT'l#h'mR1P,dT([#u(dR(b#wQ-r)UQ1|-vQ2t.qQ4v1{Q5g2xQ6r4xQ7S5fQ7w6xQ8Q7UR8j8RlhOSj}!m$Y%a%d%e%g*i*n/]/`Q%ZyR*^%WV$wrs*gR.k)}R*]%UQ$opR)o$pR)e$jT%_z%bT%`z%bT/_*n/`", +- nodeNames: "⚠ ArithOp ArithOp extends LineComment BlockComment Script ExportDeclaration export Star as VariableName from String ; default FunctionDeclaration async function VariableDefinition TypeParamList TypeDefinition ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType VoidType void TypeofType typeof MemberExpression . ?. PropertyName [ TemplateString null super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewExpression new TypeArgList CompareOp < ) ( ArgList UnaryExpression await yield delete LogicOp BitOp ParenthesizedExpression ClassExpression class extends ClassBody MethodDeclaration Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression PrivatePropertyName BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof in const CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXStartTag JSXSelfClosingTag JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast ArrowFunction TypeParamList SequenceExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature CallSignature TypePredicate is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody MethodDeclaration AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try catch finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement", +- maxTerm: 330, ++ states: "$1dO`QYOOO'QQ!LdO'#CgO'XOSO'#DSO)dQYO'#DXO)tQYO'#DdO){QYO'#DnO-xQYO'#DtOOQO'#EX'#EXO.]QWO'#EWO.bQWO'#EWOOQ!LS'#Eb'#EbO0aQ!LdO'#IsO2wQ!LdO'#ItO3eQWO'#EvO3jQpO'#F_OOQ!LS'#FO'#FOO3uO!bO'#FOO4TQWO'#FfO5bQWO'#FeOOQ!LS'#It'#ItOOQ!LQ'#Is'#IsOOQQ'#J^'#J^O5gQWO'#HlO5lQ!LYO'#HmOOQQ'#Ie'#IeOOQQ'#Hn'#HnQ`QYOOO){QYO'#DfO5tQWO'#GYO5yQ#tO'#ClO6XQWO'#EVO6dQWO'#EcO6iQ#tO'#E}O7TQWO'#GYO7YQWO'#G^O7eQWO'#G^O7sQWO'#GaO7sQWO'#GbO7sQWO'#GdO5tQWO'#GgO8dQWO'#GjO9rQWO'#CcO:SQWO'#GwO:[QWO'#G}O:[QWO'#HPO`QYO'#HRO:[QWO'#HTO:[QWO'#HWO:aQWO'#H^O:fQ!LZO'#HbO){QYO'#HdO:qQ!LZO'#HfO:|Q!LZO'#HhO5lQ!LYO'#HjO){QYO'#IuOOOS'#Hp'#HpO;XOSO,59nOOQ!LS,59n,59nO=jQbO'#CgO=tQYO'#HqO>RQWO'#IvO@QQbO'#IvO'dQYO'#IvO@XQWO,59sO@oQ&jO'#D^OAhQWO'#EXOAuQWO'#JROBQQWO'#JQOBYQWO,5:uOB_QWO'#JPOBfQWO'#DuO5yQ#tO'#EVOBtQWO'#EVOCPQ`O'#E}OOQ!LS,5:O,5:OOCXQYO,5:OOEVQ!LdO,5:YOEsQWO,5:`OF^Q!LYO'#JOO7YQWO'#I}OFeQWO'#I}OFmQWO,5:tOFrQWO'#I}OGQQYO,5:rOIQQWO'#ESOJ[QWO,5:rOKkQWO'#DhOKrQYO'#DmOK|Q&jO,5:{O){QYO,5:{OOQQ'#En'#EnOOQQ'#Ep'#EpO){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}OOQQ'#Et'#EtOLUQYO,5;_OOQ!LS,5;d,5;dOOQ!LS,5;e,5;eONUQWO,5;eOOQ!LS,5;f,5;fO){QYO'#H{ONZQ!LYO,5<RONuQWO,5:}O){QYO,5;bON|QpO'#FTO! yQpO'#JVO! eQpO'#JVO!!QQpO'#JVOOQO'#JV'#JVO!!fQpO,5;mOOOO,5;y,5;yO!!wQYO'#FaOOOO'#Hz'#HzO3uO!bO,5;jO!#OQpO'#FcOOQ!LS,5;j,5;jO!#oQ,UO'#CqOOQ!LS'#Ct'#CtO!$SQWO'#CtO!$XOSO'#CxO!$uQ#tO,5<OO!$|QWO,5<QO!&YQWO'#FpO!&gQWO'#FqO!&lQWO'#FuO!'nQ&jO'#FyO!(aQ,UO'#InOOQ!LS'#In'#InO!(kQWO'#ImO!(yQWO'#IlOOQ!LS'#Cr'#CrOOQ!LS'#Cy'#CyO!)RQWO'#C{OJaQWO'#FhOJaQWO'#FjO!)WQWO'#FlO!)]QWO'#FmO!)bQWO'#FsOJaQWO'#FxO!)gQWO'#EYO!*OQWO,5<PO`QYO,5>WOOQQ'#Ih'#IhOOQQ,5>X,5>XOOQQ-E;l-E;lO!+zQ!LdO,5:QOOQ!LQ'#Co'#CoO!,kQ#tO,5<tOOQO'#Ce'#CeO!,|QWO'#CpO!-UQ!LYO'#IiO5bQWO'#IiO:aQWO,59WO!-dQpO,59WO!-lQ#tO,59WO5yQ#tO,59WO!-wQWO,5:rO!.PQWO'#GvO!.[QWO'#JbO){QYO,5;gO!.dQ&jO,5;iO!.iQWO,5=aO!.nQWO,5=aO!.sQWO,5=aO5lQ!LYO,5=aO5tQWO,5<tO!/RQWO'#EZO!/dQ&jO'#E[OOQ!LQ'#JP'#JPO!/uQ!LYO'#J_O5lQ!LYO,5<xO7sQWO,5=OOOQO'#Cq'#CqO!0QQpO,5<{O!0YQ#tO,5<|O!0eQWO,5=OO!0jQ`O,5=RO:aQWO'#GlO5tQWO'#GnO!0rQWO'#GnO5yQ#tO'#GqO!0wQWO'#GqOOQQ,5=U,5=UO!0|QWO'#GrO!1UQWO'#ClO!1ZQWO,58}O!1eQWO,58}O!3gQYO,58}OOQQ,58},58}O!3tQ!LYO,58}O){QYO,58}O!4PQYO'#GyOOQQ'#Gz'#GzOOQQ'#G{'#G{O`QYO,5=cO!4aQWO,5=cO){QYO'#DtO`QYO,5=iO`QYO,5=kO!4fQWO,5=mO`QYO,5=oO!4kQWO,5=rO!4pQYO,5=xOOQQ,5=|,5=|O){QYO,5=|O5lQ!LYO,5>OOOQQ,5>Q,5>QO!8qQWO,5>QOOQQ,5>S,5>SO!8qQWO,5>SOOQQ,5>U,5>UO!8vQ`O,5?aOOOS-E;n-E;nOOQ!LS1G/Y1G/YO!8{QbO,5>]O){QYO,5>]OOQO-E;o-E;oO!9VQWO,5?bO!9_QbO,5?bO!9fQWO,5?lOOQ!LS1G/_1G/_O!9nQpO'#DQOOQO'#Ix'#IxO){QYO'#IxO!:]QpO'#IxO!:zQpO'#D_O!;]Q&jO'#D_O!=hQYO'#D_O!=oQWO'#IwO!=wQWO,59xO!=|QWO'#E]O!>[QWO'#JSO!>dQWO,5:vO!>zQ&jO'#D_O){QYO,5?mO!?UQWO'#HvO!9fQWO,5?lOOQ!LQ1G0a1G0aO!@bQ&jO'#DxOOQ!LS,5:a,5:aO){QYO,5:aOIQQWO,5:aO!@iQWO,5:aO:aQWO,5:qO!-dQpO,5:qO!-lQ#tO,5:qO5yQ#tO,5:qOOQ!LS1G/j1G/jOOQ!LS1G/z1G/zOOQ!LQ'#ER'#ERO){QYO,5?jO!@tQ!LYO,5?jO!AVQ!LYO,5?jO!A^QWO,5?iO!AfQWO'#HxO!A^QWO,5?iOOQ!LQ1G0`1G0`O7YQWO,5?iOOQ!LS1G0^1G0^O!BQQ!LdO1G0^O!BqQ!LbO,5:nOOQ!LS'#Fo'#FoO!C_Q!LdO'#InOGQQYO1G0^O!E^Q#tO'#IyO!EhQWO,5:SO!EmQbO'#IzO){QYO'#IzO!EwQWO,5:XOOQ!LS'#DQ'#DQOOQ!LS1G0g1G0gO!E|QWO1G0gO!H_Q!LdO1G0iO!HfQ!LdO1G0iO!JyQ!LdO1G0iO!KQQ!LdO1G0iO!MXQ!LdO1G0iO!MlQ!LdO1G0iO#!]Q!LdO1G0iO#!dQ!LdO1G0iO#$wQ!LdO1G0iO#%OQ!LdO1G0iO#&sQ!LdO1G0iO#)mQ7^O'#CgO#+hQ7^O1G0yO#-cQ7^O'#ItOOQ!LS1G1P1G1PO#-vQ!LdO,5>gOOQ!LQ-E;y-E;yO#.gQ!LdO1G0iOOQ!LS1G0i1G0iO#0iQ!LdO1G0|O#1YQpO,5;qO#1bQpO,5;rO#1jQpO'#FYO#2RQWO'#FXOOQO'#JW'#JWOOQO'#Hy'#HyO#2WQpO1G1XOOQ!LS1G1X1G1XOOOO1G1d1G1dO#2iQ7^O'#IsO#2sQWO,5;{OLUQYO,5;{OOOO-E;x-E;xOOQ!LS1G1U1G1UOOQ!LS,5;},5;}O#2xQpO,5;}OOQ!LS,59`,59`OIQQWO'#IpOOOS'#Ho'#HoO#2}OSO,59dOOQ!LS,59d,59dO){QYO1G1jO!)]QWO'#H}O#3YQWO,5<cOOQ!LS,5<`,5<`OOQO'#GT'#GTOJaQWO,5<nOOQO'#GV'#GVOJaQWO,5<pOJaQWO,5<rOOQO1G1l1G1lO#3eQ`O'#CoO#3xQ`O,5<[O#4PQWO'#JZO5tQWO'#JZO#4_QWO,5<^OJaQWO,5<]O#4dQ`O'#FoO#4qQ`O'#J[O#4{QWO'#J[OIQQWO'#J[O#5QQWO,5<aOOQ!LQ'#Dc'#DcO#5VQWO'#FrO#5bQpO'#FzO!'iQ&jO'#FzO!'iQ&jO'#F|O#5sQWO'#F}O!)bQWO'#GQOOQO'#IP'#IPO#5xQ&jO,5<eOOQ!LS,5<e,5<eO#6PQ&jO'#FzO#6_Q&jO'#F{O#6gQ&jO'#F{OOQ!LS,5<s,5<sOJaQWO,5?XOJaQWO,5?XO#6lQWO'#IQO#6wQWO,5?WOOQ!LS'#Cg'#CgO#7kQ#tO,59gOOQ!LS,59g,59gO#8^Q#tO,5<SO#9PQ#tO,5<UO#9ZQWO,5<WOOQ!LS,5<X,5<XO#9`QWO,5<_O#9eQ#tO,5<dOGQQYO1G1kO#9uQWO1G1kOOQQ1G3r1G3rOOQ!LS1G/l1G/lONUQWO1G/lOOQQ1G2`1G2`OIQQWO1G2`O){QYO1G2`OIQQWO1G2`O#9zQWO1G2`O#:YQWO,59[O#;cQWO'#ESOOQ!LQ,5?T,5?TO#;mQ!LYO,5?TOOQQ1G.r1G.rO:aQWO1G.rO!-dQpO1G.rO!-lQ#tO1G.rO#;{QWO1G0^O#<QQWO'#CgO#<]QWO'#JcO#<eQWO,5=bO#<jQWO'#JcO#<oQWO'#JcO#<tQWO'#IYO#=SQWO,5?|O#=[QbO1G1ROOQ!LS1G1T1G1TO5tQWO1G2{O#=cQWO1G2{O#=hQWO1G2{O#=mQWO1G2{OOQQ1G2{1G2{O#=rQ#tO1G2`O7YQWO'#JQO7YQWO'#E]O7YQWO'#ISO#>TQ!LYO,5?yOOQQ1G2d1G2dO!0eQWO1G2jOIQQWO1G2gO#>`QWO1G2gOOQQ1G2h1G2hOIQQWO1G2hO#>eQWO1G2hO#>mQ&jO'#GfOOQQ1G2j1G2jO!'iQ&jO'#IUO!0jQ`O1G2mOOQQ1G2m1G2mOOQQ,5=W,5=WO#>uQ#tO,5=YO5tQWO,5=YO#5sQWO,5=]O5bQWO,5=]O!-dQpO,5=]O!-lQ#tO,5=]O5yQ#tO,5=]O#?WQWO'#JaO#?cQWO,5=^OOQQ1G.i1G.iO#?hQ!LYO1G.iO#?sQWO1G.iO!)RQWO1G.iO5lQ!LYO1G.iO#?xQbO,5@OO#@SQWO,5@OO#@_QYO,5=eO#@fQWO,5=eO7YQWO,5@OOOQQ1G2}1G2}O`QYO1G2}OOQQ1G3T1G3TOOQQ1G3V1G3VO:[QWO1G3XO#@kQYO1G3ZO#DfQYO'#HYOOQQ1G3^1G3^O:aQWO1G3dO#DsQWO1G3dO5lQ!LYO1G3hOOQQ1G3j1G3jOOQ!LQ'#Fv'#FvO5lQ!LYO1G3lO5lQ!LYO1G3nOOOS1G4{1G4{O#D{Q`O,5<RO#ETQbO1G3wO#E_QWO1G4|O#EgQWO1G5WO#EoQWO,5?dOLUQYO,5:wO7YQWO,5:wO:aQWO,59yOLUQYO,59yO!-dQpO,59yO#EtQ7^O,59yOOQO,5:w,5:wO#FOQ&jO'#HrO#FfQWO,5?cOOQ!LS1G/d1G/dO#FnQ&jO'#HwO#GSQWO,5?nOOQ!LQ1G0b1G0bO!;]Q&jO,59yO#G[QbO1G5XOOQO,5>b,5>bO7YQWO,5>bOOQO-E;t-E;tOOQ!LQ'#EO'#EOO#GfQ!LrO'#EPO!@YQ&jO'#DyOOQO'#Hu'#HuO#HQQ&jO,5:dOOQ!LS,5:d,5:dO#HXQ&jO'#DyO#HjQ&jO'#DyO#HqQ&jO'#EUO#HtQ&jO'#EPO#IRQ&jO'#EPO!@YQ&jO'#EPO#IfQWO1G/{O#IkQ`O1G/{OOQ!LS1G/{1G/{O){QYO1G/{OIQQWO1G/{OOQ!LS1G0]1G0]O:aQWO1G0]O!-dQpO1G0]O!-lQ#tO1G0]O#IrQ!LdO1G5UO){QYO1G5UO#JSQ!LYO1G5UO#JeQWO1G5TO7YQWO,5>dOOQO,5>d,5>dO#JmQWO,5>dOOQO-E;v-E;vO#JeQWO1G5TO#J{Q!LdO,59gO#LzQ!LdO,5<SO#N|Q!LdO,5<UO$#OQ!LdO,5<dOOQ!LS7+%x7+%xO$%WQ!LdO7+%xO$%wQWO'#HsO$&RQWO,5?eOOQ!LS1G/n1G/nO$&ZQYO'#HtO$&hQWO,5?fO$&pQbO,5?fOOQ!LS1G/s1G/sOOQ!LS7+&R7+&RO$&zQ7^O,5:YO){QYO7+&eO$'UQ7^O,5:QOOQO1G1]1G1]OOQO1G1^1G1^O$'cQMhO,5;tOLUQYO,5;sOOQO-E;w-E;wOOQ!LS7+&s7+&sOOOO7+'O7+'OOOOO1G1g1G1gO$'nQWO1G1gOOQ!LS1G1i1G1iO$'sQ`O,5?[OOOS-E;m-E;mOOQ!LS1G/O1G/OO$'zQ!LdO7+'UOOQ!LS,5>i,5>iO$(kQWO,5>iOOQ!LS1G1}1G1}P$(pQWO'#H}POQ!LS-E;{-E;{O$)aQ#tO1G2YO$*SQ#tO1G2[O$*^Q#tO1G2^OOQ!LS1G1v1G1vO$*eQWO'#H|O$*sQWO,5?uO$*sQWO,5?uO$*{QWO,5?uO$+WQWO,5?uOOQO1G1x1G1xO$+fQ#tO1G1wO$+vQWO'#IOO$,WQWO,5?vOIQQWO,5?vO$,`Q`O,5?vOOQ!LS1G1{1G1{O5lQ!LYO,5<fO5lQ!LYO,5<gO$,jQWO,5<gO#5nQWO,5<gO!-dQpO,5<fO$,oQWO,5<hO5lQ!LYO,5<iO$,jQWO,5<lOOQO-E;}-E;}OOQ!LS1G2P1G2PO!'iQ&jO,5<fO$,wQWO,5<gO!'iQ&jO,5<hO!'iQ&jO,5<gO$-SQ#tO1G4sO$-^Q#tO1G4sOOQO,5>l,5>lOOQO-E<O-E<OO!.dQ&jO,59iO){QYO,59iO$-kQWO1G1rOJaQWO1G1yO$-pQ!LdO7+'VOOQ!LS7+'V7+'VOGQQYO7+'VOOQ!LS7+%W7+%WO$.aQ`O'#J]O#IfQWO7+'zO$.kQWO7+'zO$.sQ`O7+'zOOQQ7+'z7+'zOIQQWO7+'zO){QYO7+'zOIQQWO7+'zOOQO1G.v1G.vO$.}Q!LbO'#CgO$/_Q!LbO,5<jO$/|QWO,5<jOOQ!LQ1G4o1G4oOOQQ7+$^7+$^O:aQWO7+$^O!-dQpO7+$^OGQQYO7+%xO$0RQWO'#IXO$0aQWO,5?}OOQO1G2|1G2|O5tQWO,5?}O$0aQWO,5?}O$0iQWO,5?}OOQO,5>t,5>tOOQO-E<W-E<WOOQ!LS7+&m7+&mO$0nQWO7+(gO5lQ!LYO7+(gO5tQWO7+(gO$0sQWO7+(gO$0xQWO7+'zOOQ!LQ,5>n,5>nOOQ!LQ-E<Q-E<QOOQQ7+(U7+(UO$1WQ!LbO7+(ROIQQWO7+(RO$1bQ`O7+(SOOQQ7+(S7+(SOIQQWO7+(SO$1iQWO'#J`O$1tQWO,5=QOOQO,5>p,5>pOOQO-E<S-E<SOOQQ7+(X7+(XO$2nQ&jO'#GoOOQQ1G2t1G2tOIQQWO1G2tO){QYO1G2tOIQQWO1G2tO$2uQWO1G2tO$3TQ#tO1G2tO5lQ!LYO1G2wO#5sQWO1G2wO5bQWO1G2wO!-dQpO1G2wO!-lQ#tO1G2wO$3fQWO'#IWO$3qQWO,5?{O$3yQ&jO,5?{OOQ!LQ1G2x1G2xOOQQ7+$T7+$TO$4OQWO7+$TO5lQ!LYO7+$TO$4TQWO7+$TO){QYO1G5jO){QYO1G5kO$4YQYO1G3PO$4aQWO1G3PO$4fQYO1G3PO$4mQ!LYO1G5jOOQQ7+(i7+(iO5lQ!LYO7+(sO`QYO7+(uOOQQ'#Jf'#JfOOQQ'#IZ'#IZO$4wQYO,5=tOOQQ,5=t,5=tO){QYO'#HZO$5UQWO'#H]OOQQ7+)O7+)OO$5ZQYO7+)OO7YQWO7+)OOOQQ7+)S7+)SOOQQ7+)W7+)WOOQQ7+)Y7+)YOOQO1G5O1G5OO$9XQ7^O1G0cO$9cQWO1G0cOOQO1G/e1G/eO$9nQ7^O1G/eO:aQWO1G/eOLUQYO'#D_OOQO,5>^,5>^OOQO-E;p-E;pOOQO,5>c,5>cOOQO-E;u-E;uO!-dQpO1G/eOOQO1G3|1G3|O:aQWO,5:eOOQO,5:k,5:kO){QYO,5:kO$9xQ!LYO,5:kO$:TQ!LYO,5:kO!-dQpO,5:eOOQO-E;s-E;sOOQ!LS1G0O1G0OO!@YQ&jO,5:eO$:cQ&jO,5:eO$:tQ!LrO,5:kO$;`Q&jO,5:eO!@YQ&jO,5:kOOQO,5:p,5:pO$;gQ&jO,5:kO$;tQ!LYO,5:kOOQ!LS7+%g7+%gO#IfQWO7+%gO#IkQ`O7+%gOOQ!LS7+%w7+%wO:aQWO7+%wO!-dQpO7+%wO$<YQ!LdO7+*pO){QYO7+*pOOQO1G4O1G4OO7YQWO1G4OO$<jQWO7+*oO$<rQ!LdO1G2YO$>tQ!LdO1G2[O$@vQ!LdO1G1wO$COQ#tO,5>_OOQO-E;q-E;qO$CYQbO,5>`O){QYO,5>`OOQO-E;r-E;rO$CdQWO1G5QO$ClQ7^O1G0^O$EsQ7^O1G0iO$EzQ7^O1G0iO$G{Q7^O1G0iO$HSQ7^O1G0iO$IwQ7^O1G0iO$J[Q7^O1G0iO$LiQ7^O1G0iO$LpQ7^O1G0iO$NqQ7^O1G0iO$NxQ7^O1G0iO%!mQ7^O1G0iO%#QQ!LdO<<JPO%#qQ7^O1G0iO%%aQ7^O'#InO%'^Q7^O1G0|OLUQYO'#F[OOQO'#JX'#JXOOQO1G1`1G1`O%'kQWO1G1_O%'pQ7^O,5>gOOOO7+'R7+'ROOOS1G4v1G4vOOQ!LS1G4T1G4TOJaQWO7+'xO%'zQWO,5>hO5tQWO,5>hOOQO-E;z-E;zO%(YQWO1G5aO%(YQWO1G5aO%(bQWO1G5aO%(mQ`O,5>jO%(wQWO,5>jOIQQWO,5>jOOQO-E;|-E;|O%(|Q`O1G5bO%)WQWO1G5bOOQO1G2Q1G2QOOQO1G2R1G2RO5lQ!LYO1G2RO$,jQWO1G2RO5lQ!LYO1G2QO%)`QWO1G2SOIQQWO1G2SOOQO1G2T1G2TO5lQ!LYO1G2WO!-dQpO1G2QO#5nQWO1G2RO%)eQWO1G2SO%)mQWO1G2ROJaQWO7+*_OOQ!LS1G/T1G/TO%)xQWO1G/TOOQ!LS7+'^7+'^O%)}Q#tO7+'eO%*_Q!LdO<<JqOOQ!LS<<Jq<<JqOIQQWO'#IRO%+OQWO,5?wOOQQ<<Kf<<KfOIQQWO<<KfO#IfQWO<<KfO%+WQWO<<KfO%+`Q`O<<KfOIQQWO1G2UOOQQ<<Gx<<GxO:aQWO<<GxO%+jQ!LdO<<IdOOQ!LS<<Id<<IdOOQO,5>s,5>sO%,ZQWO,5>sO#<oQWO,5>sOOQO-E<V-E<VO%,`QWO1G5iO%,`QWO1G5iO5tQWO1G5iO%,hQWO<<LROOQQ<<LR<<LRO%,mQWO<<LRO5lQ!LYO<<LRO){QYO<<KfOIQQWO<<KfOOQQ<<Km<<KmO$1WQ!LbO<<KmOOQQ<<Kn<<KnO$1bQ`O<<KnO%,rQ&jO'#ITO%,}QWO,5?zOLUQYO,5?zOOQQ1G2l1G2lO#GfQ!LrO'#EPO!@YQ&jO'#GpOOQO'#IV'#IVO%-VQ&jO,5=ZOOQQ,5=Z,5=ZO%-^Q&jO'#EPO%-iQ&jO'#EPO%.QQ&jO'#EPO%.[Q&jO'#GpO%.mQWO7+(`O%.rQWO7+(`O%.zQ`O7+(`OOQQ7+(`7+(`OIQQWO7+(`O){QYO7+(`OIQQWO7+(`O%/UQWO7+(`OOQQ7+(c7+(cO5lQ!LYO7+(cO#5sQWO7+(cO5bQWO7+(cO!-dQpO7+(cO%/dQWO,5>rOOQO-E<U-E<UOOQO'#Gs'#GsO%/oQWO1G5gO5lQ!LYO<<GoOOQQ<<Go<<GoO%/wQWO<<GoO%/|QWO7++UO%0RQWO7++VOOQQ7+(k7+(kO%0WQWO7+(kO%0]QYO7+(kO%0dQWO7+(kO){QYO7++UO){QYO7++VOOQQ<<L_<<L_OOQQ<<La<<LaOOQQ-E<X-E<XOOQQ1G3`1G3`O%0iQWO,5=uOOQQ,5=w,5=wO:aQWO<<LjO%0nQWO<<LjOLUQYO7+%}OOQO7+%P7+%PO%0sQ7^O1G5XO:aQWO7+%POOQO1G0P1G0PO%0}Q!LdO1G0VOOQO1G0V1G0VO){QYO1G0VO%1XQ!LYO1G0VO:aQWO1G0PO!-dQpO1G0PO!@YQ&jO1G0PO%1dQ!LYO1G0VO%1rQ&jO1G0PO%2TQ!LYO1G0VO%2iQ!LrO1G0VO%2sQ&jO1G0PO!@YQ&jO1G0VOOQ!LS<<IR<<IROOQ!LS<<Ic<<IcO:aQWO<<IcO%2zQ!LdO<<N[OOQO7+)j7+)jO%3[Q!LdO7+'eO%5dQbO1G3zO%5nQ7^O7+%xO%5{Q7^O,59gO%7xQ7^O,5<SO%9uQ7^O,5<UO%;rQ7^O,5<dO%=bQ7^O7+'UO%=oQ7^O7+'VO%=|QWO,5;vOOQO7+&y7+&yO%>RQ#tO<<KdOOQO1G4S1G4SO%>cQWO1G4SO%>nQWO1G4SO%>|QWO7+*{O%>|QWO7+*{OIQQWO1G4UO%?UQ`O1G4UO%?`QWO7+*|OOQO7+'m7+'mO5lQ!LYO7+'mOOQO7+'l7+'lO$,jQWO7+'nO%?hQ`O7+'nOOQO7+'r7+'rO5lQ!LYO7+'lO$,jQWO7+'mO%?oQWO7+'nOIQQWO7+'nO#5nQWO7+'mO%?tQ#tO<<MyOOQ!LS7+$o7+$oO%@OQ`O,5>mOOQO-E<P-E<PO#IfQWOANAQOOQQANAQANAQOIQQWOANAQO%@YQ!LbO7+'pOOQQAN=dAN=dO5tQWO1G4_OOQO1G4_1G4_O%@gQWO1G4_O%@lQWO7++TO%@lQWO7++TO5lQ!LYOANAmO%@tQWOANAmOOQQANAmANAmO%@yQWOANAQO%ARQ`OANAQOOQQANAXANAXOOQQANAYANAYO%A]QWO,5>oOOQO-E<R-E<RO%AhQ7^O1G5fO#5sQWO,5=[O5bQWO,5=[O!-dQpO,5=[OOQO-E<T-E<TOOQQ1G2u1G2uO$:tQ!LrO,5:kO!@YQ&jO,5=[O%ArQ&jO,5=[O%BTQ&jO,5:kOOQQ<<Kz<<KzOIQQWO<<KzO%.mQWO<<KzO%B_QWO<<KzO%BgQ`O<<KzO){QYO<<KzOIQQWO<<KzOOQQ<<K}<<K}O5lQ!LYO<<K}O#5sQWO<<K}O5bQWO<<K}O%BqQ&jO1G4^O%BvQWO7++ROOQQAN=ZAN=ZO5lQ!LYOAN=ZOOQQ<<Np<<NpOOQQ<<Nq<<NqOOQQ<<LV<<LVO%COQWO<<LVO%CTQYO<<LVO%C[QWO<<NpO%CaQWO<<NqOOQQ1G3a1G3aOOQQANBUANBUO:aQWOANBUO%CfQ7^O<<IiOOQO<<Hk<<HkOOQO7+%q7+%qO%0}Q!LdO7+%qO){QYO7+%qOOQO7+%k7+%kO:aQWO7+%kO!-dQpO7+%kO%CpQ!LYO7+%qO!@YQ&jO7+%kO%C{Q!LYO7+%qO%DZQ&jO7+%kO%DlQ!LYO7+%qOOQ!LSAN>}AN>}O%EQQ!LdO<<KdO%GYQ7^O<<JPO%GgQ7^O1G1wO%IVQ7^O1G2YO%KSQ7^O1G2[O%MPQ7^O<<JqO%M^Q7^O<<IdOOQO1G1b1G1bOOQO7+)n7+)nO%MkQWO7+)nO%MvQWO<<NgO%NOQ`O7+)pOOQO<<KX<<KXO5lQ!LYO<<KYO$,jQWO<<KYOOQO<<KW<<KWO5lQ!LYO<<KXO%NYQ`O<<KYO$,jQWO<<KXOOQQG26lG26lO#IfQWOG26lOOQO7+)y7+)yO5tQWO7+)yO%NaQWO<<NoOOQQG27XG27XO5lQ!LYOG27XOIQQWOG26lOLUQYO1G4ZO%NiQWO7++QO5lQ!LYO1G2vO#5sQWO1G2vO5bQWO1G2vO!-dQpO1G2vO!@YQ&jO1G2vO%2iQ!LrO1G0VO%NqQ&jO1G2vO%.mQWOANAfOOQQANAfANAfOIQQWOANAfO& SQWOANAfO& [Q`OANAfOOQQANAiANAiO5lQ!LYOANAiO#5sQWOANAiOOQO'#Gt'#GtOOQO7+)x7+)xOOQQG22uG22uOOQQANAqANAqO& fQWOANAqOOQQAND[AND[OOQQAND]AND]O& kQYOG27pOOQO<<I]<<I]O%0}Q!LdO<<I]OOQO<<IV<<IVO:aQWO<<IVO){QYO<<I]O!-dQpO<<IVO&%iQ!LYO<<I]O!@YQ&jO<<IVO&%tQ!LYO<<I]O&&SQ7^O7+'eOOQO<<MY<<MYOOQOAN@tAN@tO5lQ!LYOAN@tOOQOAN@sAN@sO$,jQWOAN@tO5lQ!LYOAN@sOOQQLD,WLD,WOOQO<<Me<<MeOOQQLD,sLD,sO#IfQWOLD,WO&'rQ7^O7+)uOOQO7+(b7+(bO5lQ!LYO7+(bO#5sQWO7+(bO5bQWO7+(bO!-dQpO7+(bO!@YQ&jO7+(bOOQQG27QG27QO%.mQWOG27QOIQQWOG27QOOQQG27TG27TO5lQ!LYOG27TOOQQG27]G27]O:aQWOLD-[OOQOAN>wAN>wOOQOAN>qAN>qO%0}Q!LdOAN>wO:aQWOAN>qO){QYOAN>wO!-dQpOAN>qO&'|Q!LYOAN>wO&(XQ7^O<<KdOOQOG26`G26`O5lQ!LYOG26`OOQOG26_G26_OOQQ!$( r!$( rOOQO<<K|<<K|O5lQ!LYO<<K|O#5sQWO<<K|O5bQWO<<K|O!-dQpO<<K|OOQQLD,lLD,lO%.mQWOLD,lOOQQLD,oLD,oOOQQ!$(!v!$(!vOOQOG24cG24cOOQOG24]G24]O%0}Q!LdOG24cO:aQWOG24]O){QYOG24cOOQOLD+zLD+zOOQOANAhANAhO5lQ!LYOANAhO#5sQWOANAhO5bQWOANAhOOQQ!$(!W!$(!WOOQOLD)}LD)}OOQOLD)wLD)wO%0}Q!LdOLD)}OOQOG27SG27SO5lQ!LYOG27SO#5sQWOG27SOOQO!$'Mi!$'MiOOQOLD,nLD,nO5lQ!LYOLD,nOOQO!$(!Y!$(!YOLUQYO'#DnO&)wQbO'#IsOLUQYO'#DfO&*OQ!LdO'#CgO&*iQbO'#CgO&*yQYO,5:rOLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO'#H{O&,yQWO,5<RO&.]QWO,5:}OLUQYO,5;bO!)RQWO'#C{O!)RQWO'#C{OIQQWO'#FhO&-RQWO'#FhOIQQWO'#FjO&-RQWO'#FjOIQQWO'#FxO&-RQWO'#FxOLUQYO,5?mO&*yQYO1G0^O&.dQ7^O'#CgOLUQYO1G1jOIQQWO,5<nO&-RQWO,5<nOIQQWO,5<pO&-RQWO,5<pOIQQWO,5<]O&-RQWO,5<]O&*yQYO1G1kOLUQYO7+&eOIQQWO1G1yO&-RQWO1G1yO&*yQYO7+'VO&*yQYO7+%xOIQQWO7+'xO&-RQWO7+'xO&.nQWO'#EWO&.sQWO'#EWO&.{QWO'#EvO&/QQWO'#EcO&/VQWO'#JRO&/bQWO'#JPO&/mQWO,5:rO&/rQ#tO,5<OO&/yQWO'#FqO&0OQWO'#FqO&0TQWO,5<PO&0]QWO,5:rO&0eQ7^O1G0yO&0lQWO,5<_O&0qQWO,5<_O&0vQWO1G1kO&0{QWO1G0^O&1QQ#tO1G2^O&1XQ#tO1G2^O4TQWO'#FfO5bQWO'#FeOBtQWO'#EVOLUQYO,5;_O!)bQWO'#FsO!)bQWO'#FsOJaQWO,5<rOJaQWO,5<r", ++ stateData: "&2W~O'VOS'WOSSOSTOS~OPTOQTOWyO]cO^hOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#`sO#ppO#t^O$}qO%PtO%RrO%SrO%VuO%XvO%[wO%]wO%_xO%lzO%r{O%t|O%v}O%x!OO%{!PO&R!QO&V!RO&X!SO&Z!TO&]!UO&_!VO'YPO'cQO'oYO'|aO~OPZXYZX^ZXiZXrZXsZXuZX}ZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'TZX'cZX'pZX'wZX'xZX~O!X$jX~P$zO'Q!XO'R!WO'S!ZO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y![O'cQO'oYO'|aO~O|!`O}!]Oz'jPz'tP~P'dO!O!lO~P`OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y9XO'cQO'oYO'|aO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'cQO'oYO'|aO~O|!qO#Q!tO#R!qO'Y9YO!_'qP~P+{O#S!uO~O!X!vO#S!uO~OP#]OY#cOi#QOr!zOs!zOu!{O}#aO!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~O^'gX'T'gX!_'gXz'gX!P'gX%O'gX!X'gX~P.jO!w#dO#k#dOP'hXY'hX^'hXi'hXr'hXs'hXu'hX}'hX!]'hX!^'hX!`'hX!f'hX#W'hX#X'hX#Y'hX#Z'hX#['hX#]'hX#^'hX#a'hX#c'hX#e'hX#f'hX'c'hX'p'hX'w'hX'x'hX~O#_'hX'T'hXz'hX!_'hX'e'hX!P'hX%O'hX!X'hX~P0zO!w#dO~O#v#fO#x#eO$P#kO~O!P#lO#t^O$S#mO$U#oO~O]#rOg$POi#sOj#rOk#rOn$QOp$ROu#yO!P#zO!Z$WO!`#wO#R$XO#p$UO$]$SO$_$TO$b$VO'Y#qO'c#tO'^'`P~O!`$YO~O!X$[O~O^$]O'T$]O~O'Y$aO~O!`$YO'Y$aO'Z$cO'_$dO~Ob$jO!`$YO'Y$aO~O#_#SO~O]$sOr$oO!P$lO!`$nO%P$rO'Y$aO'Z$cO[(UP~O!j$tO~Ou$uO!P$vO'Y$aO~Ou$uO!P$vO%X$zO'Y$aO~O'Y${O~O#`sO%PtO%RrO%SrO%VuO%XvO%[wO%]wO~Oa%UOb%TO!j%RO$}%SO%a%QO~P7xOa%XObmO!P%WO!jlO#`sO$}qO%RrO%SrO%VuO%XvO%[wO%]wO%_xO~O_%[O!w%_O%P%YO'Z$cO~P8wO!`%`O!c%dO~O!`%eO~O!PSO~O^$]O'P%mO'T$]O~O^$]O'P%pO'T$]O~O^$]O'P%rO'T$]O~O'Q!XO'R!WO'S%vO~OPZXYZXiZXrZXsZXuZX}ZX}cX!]ZX!^ZX!`ZX!fZX!wZX!wcX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'cZX'pZX'wZX'xZX~OzZXzcX~P;dO|%xOz&eX}&eX~P){O}!]Oz'jX~OP#]OY#cOi#QOr!zOs!zOu!{O}!]O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~Oz'jX~P>ZOz%}O~Ou&QO!S&[O!T&TO!U&TO'Z$cO~O]&ROj&RO|&UO'f&OO!O'kP!O'vP~P@^Oz'sX}'sX!X'sX!_'sX'p'sX~O!w'sX#S!{X!O'sX~PAVO!w&]Oz'uX}'uX~O}&^Oz'tX~Oz&`O~O!w#dO~PAVOR&dO!P&aO!k&cO'Y$aO~Ob&iO!`$YO'Y$aO~Or$oO!`$nO~O!O&jO~P`Or!zOs!zOu!{O!^!xO!`!yO'cQOP!baY!bai!ba}!ba!]!ba!f!ba#W!ba#X!ba#Y!ba#Z!ba#[!ba#]!ba#^!ba#_!ba#a!ba#c!ba#e!ba#f!ba'p!ba'w!ba'x!ba~O^!ba'T!baz!ba!_!ba'e!ba!P!ba%O!ba!X!ba~PC`O!_&kO~O!X!vO!w&mO'p&lO}'rX^'rX'T'rX~O!_'rX~PExO}&qO!_'qX~O!_&sO~Ou$uO!P$vO#R&tO'Y$aO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y9XO'cQO'oYO'|aO~O]#rOg$POi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'Y&xO'c#tO~O#S&zO~O]#rOg$POi#sOj#rOk#rOn$QOp$ROu#yO!P#zO!Z$WO!`#wO#R$XO#p$UO$]$SO$_$TO$b$VO'Y&xO'c#tO~O'^'mP~PJaO|'OO!_'nP~P){O'f'QO'oYO~OP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!P!bO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'Y'`O'cQO'oYO'|:jO~O!`!yO~O}#aO^$Za'T$Za!_$Zaz$Za!P$Za%O$Za!X$Za~O#`'gO~PIQOr'jO!X'iO!P#wX#s#wX#v#wX#x#wX$P#wX~O!X'iO!P'yX#s'yX#v'yX#x'yX$P'yX~Or'jO~P! eOr'jO!P'yX#s'yX#v'yX#x'yX$P'yX~O!P'lO#s'pO#v'kO#x'kO$P'qO~O|'tO~PLUO#v#fO#x#eO$P'wO~Or$cXu$cX!^$cX'p$cX'w$cX'x$cX~OReX}eX!weX'^eX'^$cX~P!#ZOj'yO~O'Q'{O'R'zO'S'}O~Or(POu(QO'p#ZO'w(SO'x(UO~O'^(OO~P!$dO'^(XO~O]#rOg$POi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'c#tO~O|(]O'Y(YO!_'}P~P!%RO#S(_O~O|(cO'Y(`Oz(OP~P!%RO^(lOi(qOu(iO!S(oO!T(hO!U(hO!`(fO!t(pO$u(kO'Z$cO'f(eO~O!O(nO~P!&yO!^!xOr'bXu'bX'p'bX'w'bX'x'bX}'bX!w'bX~O'^'bX#i'bX~P!'uOR(tO!w(sO}'aX'^'aX~O}(uO'^'`X~O'Y(wO~O!`(|O~O'Y&xO~O!`(fO~Ou$uO|!qO!P$vO#Q!tO#R!qO'Y$aO!_'qP~O!X!vO#S)QO~OP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~O^!Ya}!Ya'T!Yaz!Ya!_!Ya'e!Ya!P!Ya%O!Ya!X!Ya~P!*WOR)YO!P&aO!k)XO%O)WO'_$dO~O'Y${O'^'`P~O!X)]O!P']X^']X'T']X~O!`$YO'_$dO~O!`$YO'Y$aO'_$dO~O!X!vO#S&zO~O%P)iO'Y)eO!O(VP~O})jO[(UX~O'f'QO~OY)nO~O[)oO~O!P$lO'Y$aO'Z$cO[(UP~Ou$uO|)tO!P$vO'Y$aOz'tP~O]&XOj&XO|)uO'f'QO!O'vP~O})vO^(RX'T(RX~O!w)zO'_$dO~OR)}O!P#zO'_$dO~O!P*PO~Or*RO!PSO~O!j*WO~Ob*]O~O'Y(wO!O(TP~Ob$jO~O%PtO'Y${O~P8wOY*cO[*bO~OPTOQTO]cOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#t^O$}qO'cQO'oYO'|aO~O!P!bO#p!kO'Y9XO~P!1mO[*bO^$]O'T$]O~O^*gO#`*iO%R*iO%S*iO~P){O!`%`O~O%r*nO~O!P*pO~O&S*sO&T*rOP&QaQ&QaW&Qa]&Qa^&Qaa&Qab&Qag&Qai&Qaj&Qak&Qan&Qap&Qau&Qaw&Qax&Qay&Qa!P&Qa!Z&Qa!`&Qa!c&Qa!d&Qa!e&Qa!f&Qa!g&Qa!j&Qa#`&Qa#p&Qa#t&Qa$}&Qa%P&Qa%R&Qa%S&Qa%V&Qa%X&Qa%[&Qa%]&Qa%_&Qa%l&Qa%r&Qa%t&Qa%v&Qa%x&Qa%{&Qa&R&Qa&V&Qa&X&Qa&Z&Qa&]&Qa&_&Qa'O&Qa'Y&Qa'c&Qa'o&Qa'|&Qa!O&Qa%y&Qa_&Qa&O&Qa~O'Y*vO~O'e*yO~Oz&ea}&ea~P!*WO}!]Oz'ja~Oz'ja~P>ZO}&^Oz'ta~O}tX}!VX!OtX!O!VX!XtX!X!VX!`!VX!wtX'_!VX~O!X+QO!w+PO}#PX}'lX!O#PX!O'lX!X'lX!`'lX'_'lX~O!X+SO!`$YO'_$dO}!RX!O!RX~O]&POj&POu&QO'f(eO~OP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!P!bO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'cQO'oYO'|:jO~O'Y9uO~P!;kO}+WO!O'kX~O!O+YO~O!X+QO!w+PO}#PX!O#PX~O}+ZO!O'vX~O!O+]O~O]&POj&POu&QO'Z$cO'f(eO~O!T+^O!U+^O~P!>iOu$uO|+aO!P$vO'Y$aOz&jX}&jX~O^+fO!S+iO!T+eO!U+eO!n+mO!o+kO!p+lO!q+jO!t+nO'Z$cO'f(eO'o+cO~O!O+hO~P!?jOR+sO!P&aO!k+rO~O!w+yO}'ra!_'ra^'ra'T'ra~O!X!vO~P!@tO}&qO!_'qa~Ou$uO|+|O!P$vO#Q,OO#R+|O'Y$aO}&lX!_&lX~O^!zi}!zi'T!ziz!zi!_!zi'e!zi!P!zi%O!zi!X!zi~P!*WO#S!va}!va!_!va!w!va!P!va^!va'T!vaz!va~P!$dO#S'bXP'bXY'bX^'bXi'bXs'bX!]'bX!`'bX!f'bX#W'bX#X'bX#Y'bX#Z'bX#['bX#]'bX#^'bX#_'bX#a'bX#c'bX#e'bX#f'bX'T'bX'c'bX!_'bXz'bX!P'bX'e'bX%O'bX!X'bX~P!'uO},XO'^'mX~P!$dO'^,ZO~O},[O!_'nX~P!*WO!_,_O~Oz,`O~OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'cQOY#Vi^#Vii#Vi}#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O#W#Vi~P!FRO#W#OO~P!FROP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO'cQOY#Vi^#Vi}#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~Oi#Vi~P!HmOi#QO~P!HmOP#]Oi#QOr!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO'cQO^#Vi}#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P!KXOY#cO!]#SO#]#SO#^#SO#_#SO~P!KXOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO'cQO^#Vi}#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O'w#Vi~P!NPO'w!|O~P!NPOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO'cQO'w!|O^#Vi}#Vi#e#Vi#f#Vi'T#Vi'p#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O'x#Vi~P#!kO'x!}O~P#!kOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO'cQO'w!|O'x!}O~O^#Vi}#Vi#f#Vi'T#Vi'p#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~P#%VOPZXYZXiZXrZXsZXuZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'cZX'pZX'wZX'xZX}ZX!OZX~O#iZX~P#'jOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO#e9eO#f9fO'cQO'p#ZO'w!|O'x!}O~O#i,bO~P#)tOP'hXY'hXi'hXr'hXs'hXu'hX!]'hX!^'hX!`'hX!f'hX#W'hX#X'hX#Y'hX#Z'hX#['hX#]'hX#^'hX#a'hX#c'hX#e'hX#f'hX'c'hX'p'hX'w'hX'x'hX}'hX~O!w9jO#k9jO#_'hX#i'hX!O'hX~P#+oO^&oa}&oa'T&oa!_&oa'e&oaz&oa!P&oa%O&oa!X&oa~P!*WOP#ViY#Vi^#Vii#Vis#Vi}#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'c#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~P!$dO^#ji}#ji'T#jiz#ji!_#ji'e#ji!P#ji%O#ji!X#ji~P!*WO#v,dO#x,dO~O#v,eO#x,eO~O!X'iO!w,fO!P#|X#s#|X#v#|X#x#|X$P#|X~O|,gO~O!P'lO#s,iO#v'kO#x'kO$P,jO~O}9gO!O'gX~P#)tO!O,kO~O$P,mO~O'Q'{O'R'zO'S,pO~O],sOj,sOz,tO~O}cX!XcX!_cX!_$cX'pcX~P!#ZO!_,zO~P!$dO},{O!X!vO'p&lO!_'}X~O!_-QO~Oz$cX}$cX!X$jX~P!#ZO}-SOz(OX~P!$dO!X-UO~Oz-WO~O|(]O'Y$aO!_'}P~Oi-[O!X!vO!`$YO'_$dO'p&lO~O!X)]O~O!O-bO~P!&yO!T-cO!U-cO'Z$cO'f(eO~Ou-eO'f(eO~O!t-fO~O'Y${O}&tX'^&tX~O}(uO'^'`a~Or-kOs-kOu-lO'poa'woa'xoa}oa!woa~O'^oa#ioa~P#7POr(POu(QO'p$[a'w$[a'x$[a}$[a!w$[a~O'^$[a#i$[a~P#7uOr(POu(QO'p$^a'w$^a'x$^a}$^a!w$^a~O'^$^a#i$^a~P#8hO]-mO~O#S-nO~O'^$la}$la#i$la!w$la~P!$dO#S-qO~OR-zO!P&aO!k-yO%O-xO~O'^-{O~O]#rOi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'c#tO~Og-}O'Y-|O~P#:_O!X)]O!P']a^']a'T']a~O#S.TO~OYZX}cX!OcX~O}.UO!O(VX~O!O.WO~OY.XO~O'Y)eO~O!P$lO'Y$aO[&|X}&|X~O})jO[(Ua~O!_.^O~P!*WO].`O~OY.aO~O[.bO~OR-zO!P&aO!k-yO%O-xO'_$dO~O})vO^(Ra'T(Ra~O!w.hO~OR.kO!P#zO~O'f'QO!O(SP~OR.uO!P.qO!k.tO%O.sO'_$dO~OY/PO}.}O!O(TX~O!O/QO~O[/SO^$]O'T$]O~O]/TO~O#_/VO%p/WO~P0zO!w#dO#_/VO%p/WO~O^/XO~P){O^/ZO~O%y/_OP%wiQ%wiW%wi]%wi^%wia%wib%wig%wii%wij%wik%win%wip%wiu%wiw%wix%wiy%wi!P%wi!Z%wi!`%wi!c%wi!d%wi!e%wi!f%wi!g%wi!j%wi#`%wi#p%wi#t%wi$}%wi%P%wi%R%wi%S%wi%V%wi%X%wi%[%wi%]%wi%_%wi%l%wi%r%wi%t%wi%v%wi%x%wi%{%wi&R%wi&V%wi&X%wi&Z%wi&]%wi&_%wi'O%wi'Y%wi'c%wi'o%wi'|%wi!O%wi_%wi&O%wi~O_/eO!O/cO&O/dO~P`O!PSO!`/hO~O}#aO'e$Za~Oz&ei}&ei~P!*WO}!]Oz'ji~O}&^Oz'ti~Oz/lO~O}!Ra!O!Ra~P#)tO]&POj&PO|/rO'f(eO}&fX!O&fX~P@^O}+WO!O'ka~O]&XOj&XO|)uO'f'QO}&kX!O&kX~O}+ZO!O'va~Oz'ui}'ui~P!*WO^$]O!X!vO!`$YO!f/}O!w/{O'T$]O'_$dO'p&lO~O!O0QO~P!?jO!T0RO!U0RO'Z$cO'f(eO'o+cO~O!S0SO~P#HXO!PSO!S0SO!q0UO!t0VO~P#HXO!S0SO!o0XO!p0XO!q0UO!t0VO~P#HXO!P&aO~O!P&aO~P!$dO}'ri!_'ri^'ri'T'ri~P!*WO!w0bO}'ri!_'ri^'ri'T'ri~O}&qO!_'qi~Ou$uO!P$vO#R0dO'Y$aO~O#SoaPoaYoa^oaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa'Toa'coa!_oazoa!Poa'eoa%Ooa!Xoa~P#7PO#S$[aP$[aY$[a^$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a'T$[a'c$[a!_$[az$[a!P$[a'e$[a%O$[a!X$[a~P#7uO#S$^aP$^aY$^a^$^ai$^as$^a!]$^a!^$^a!`$^a!f$^a#W$^a#X$^a#Y$^a#Z$^a#[$^a#]$^a#^$^a#_$^a#a$^a#c$^a#e$^a#f$^a'T$^a'c$^a!_$^az$^a!P$^a'e$^a%O$^a!X$^a~P#8hO#S$laP$laY$la^$lai$las$la}$la!]$la!^$la!`$la!f$la#W$la#X$la#Y$la#Z$la#[$la#]$la#^$la#_$la#a$la#c$la#e$la#f$la'T$la'c$la!_$laz$la!P$la!w$la'e$la%O$la!X$la~P!$dO^!zq}!zq'T!zqz!zq!_!zq'e!zq!P!zq%O!zq!X!zq~P!*WO}&gX'^&gX~PJaO},XO'^'ma~O|0lO}&hX!_&hX~P){O},[O!_'na~O},[O!_'na~P!*WO#i!ba!O!ba~PC`O#i!Ya}!Ya!O!Ya~P#)tO!P1PO#t^O#}1QO~O!O1UO~O'e1VO~P!$dO^$Wq}$Wq'T$Wqz$Wq!_$Wq'e$Wq!P$Wq%O$Wq!X$Wq~P!*WOz1WO~O],sOj,sO~Or(POu(QO'x(UO'p$vi'w$vi}$vi!w$vi~O'^$vi#i$vi~P$(xOr(POu(QO'p$xi'w$xi'x$xi}$xi!w$xi~O'^$xi#i$xi~P$)kO#i1XO~P!$dO|1ZO'Y$aO}&pX!_&pX~O},{O!_'}a~O},{O!X!vO!_'}a~O},{O!X!vO'p&lO!_'}a~O'^$ei}$ei#i$ei!w$ei~P!$dO|1bO'Y(`Oz&rX}&rX~P!%RO}-SOz(Oa~O}-SOz(Oa~P!$dO!X!vO~O!X!vO#_1lO~Oi1pO!X!vO'p&lO~O}'ai'^'ai~P!$dO!w1sO}'ai'^'ai~P!$dO!_1vO~O^$Xq}$Xq'T$Xqz$Xq!_$Xq'e$Xq!P$Xq%O$Xq!X$Xq~P!*WO}1zO!P(PX~P!$dO!P&aO%O1}O~O!P&aO%O1}O~P!$dO!P$cX$sZX^$cX'T$cX~P!#ZO$s2ROrfXufX!PfX'pfX'wfX'xfX^fX'TfX~O$s2RO~O%P2YO'Y)eO}&{X!O&{X~O}.UO!O(Va~OY2^O~O[2_O~O]2bO~OR2dO!P&aO!k2cO%O1}O~O^$]O'T$]O~P!$dO!P#zO~P!$dO}2iO!w2kO!O(SX~O!O2lO~Ou(iO!S2uO!T2nO!U2nO!n2tO!o2sO!p2sO!t2rO'Z$cO'f(eO'o+cO~O!O2qO~P$1yOR2|O!P.qO!k2{O%O2zO~OR2|O!P.qO!k2{O%O2zO'_$dO~O'Y(wO}&zX!O&zX~O}.}O!O(Ta~O'f3VO~O]3XO~O[3ZO~O!_3^O~P){O^3`O~O^3`O~P){O#_3bO%p3cO~PExO_/eO!O3gO&O/dO~P`O!X3iO~O&T3jOP&QqQ&QqW&Qq]&Qq^&Qqa&Qqb&Qqg&Qqi&Qqj&Qqk&Qqn&Qqp&Qqu&Qqw&Qqx&Qqy&Qq!P&Qq!Z&Qq!`&Qq!c&Qq!d&Qq!e&Qq!f&Qq!g&Qq!j&Qq#`&Qq#p&Qq#t&Qq$}&Qq%P&Qq%R&Qq%S&Qq%V&Qq%X&Qq%[&Qq%]&Qq%_&Qq%l&Qq%r&Qq%t&Qq%v&Qq%x&Qq%{&Qq&R&Qq&V&Qq&X&Qq&Z&Qq&]&Qq&_&Qq'O&Qq'Y&Qq'c&Qq'o&Qq'|&Qq!O&Qq%y&Qq_&Qq&O&Qq~O}#Pi!O#Pi~P#)tO!w3lO}#Pi!O#Pi~O}!Ri!O!Ri~P#)tO^$]O!w3sO'T$]O~O^$]O!X!vO!w3sO'T$]O~O!T3wO!U3wO'Z$cO'f(eO'o+cO~O^$]O!X!vO!`$YO!f3xO!w3sO'T$]O'_$dO'p&lO~O!S3yO~P$:cO!S3yO!q3|O!t3}O~P$:cO^$]O!X!vO!f3xO!w3sO'T$]O'p&lO~O}'rq!_'rq^'rq'T'rq~P!*WO}&qO!_'qq~O#S$viP$viY$vi^$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi'T$vi'c$vi!_$viz$vi!P$vi'e$vi%O$vi!X$vi~P$(xO#S$xiP$xiY$xi^$xii$xis$xi!]$xi!^$xi!`$xi!f$xi#W$xi#X$xi#Y$xi#Z$xi#[$xi#]$xi#^$xi#_$xi#a$xi#c$xi#e$xi#f$xi'T$xi'c$xi!_$xiz$xi!P$xi'e$xi%O$xi!X$xi~P$)kO#S$eiP$eiY$ei^$eii$eis$ei}$ei!]$ei!^$ei!`$ei!f$ei#W$ei#X$ei#Y$ei#Z$ei#[$ei#]$ei#^$ei#_$ei#a$ei#c$ei#e$ei#f$ei'T$ei'c$ei!_$eiz$ei!P$ei!w$ei'e$ei%O$ei!X$ei~P!$dO}&ga'^&ga~P!$dO}&ha!_&ha~P!*WO},[O!_'ni~O#i!zi}!zi!O!zi~P#)tOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'cQOY#Vii#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~O#W#Vi~P$CyO#W9[O~P$CyOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O'cQOY#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~Oi#Vi~P$FROi9^O~P$FROP#]Oi9^Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O'cQO#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P$HZOY9iO!]9`O#]9`O#^9`O#_9`O~P$HZOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO'cQO#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'x#Vi}#Vi!O#Vi~O'w#Vi~P$JoO'w!|O~P$JoOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO'cQO'w!|O#e#Vi#f#Vi#i#Vi'p#Vi}#Vi!O#Vi~O'x#Vi~P$LwO'x!}O~P$LwOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO#e9eO'cQO'w!|O'x!}O~O#f#Vi#i#Vi'p#Vi}#Vi!O#Vi~P% PO^#gy}#gy'T#gyz#gy!_#gy'e#gy!P#gy%O#gy!X#gy~P!*WOP#ViY#Vii#Vis#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'c#Vi}#Vi!O#Vi~P!$dO!^!xOP'bXY'bXi'bXr'bXs'bXu'bX!]'bX!`'bX!f'bX#W'bX#X'bX#Y'bX#Z'bX#['bX#]'bX#^'bX#_'bX#a'bX#c'bX#e'bX#f'bX#i'bX'c'bX'p'bX'w'bX'x'bX}'bX!O'bX~O#i#ji}#ji!O#ji~P#)tO!O4_O~O}&oa!O&oa~P#)tO!X!vO'p&lO}&pa!_&pa~O},{O!_'}i~O},{O!X!vO!_'}i~Oz&ra}&ra~P!$dO!X4fO~O}-SOz(Oi~P!$dO}-SOz(Oi~Oz4lO~O!X!vO#_4rO~Oi4sO!X!vO'p&lO~Oz4uO~O'^$gq}$gq#i$gq!w$gq~P!$dO^$Xy}$Xy'T$Xyz$Xy!_$Xy'e$Xy!P$Xy%O$Xy!X$Xy~P!*WO}1zO!P(Pa~O!P&aO%O4zO~O!P&aO%O4zO~P!$dO^!zy}!zy'T!zyz!zy!_!zy'e!zy!P!zy%O!zy!X!zy~P!*WOY4}O~O}.UO!O(Vi~O]5SO~O[5TO~O'f'QO}&wX!O&wX~O}2iO!O(Sa~O!O5bO~P$1yOu-eO'f(eO'o+cO~O!S5eO!T5dO!U5dO!t0VO'Z$cO'f(eO'o+cO~O!o5fO!p5fO~P%-iO!T5dO!U5dO'Z$cO'f(eO'o+cO~O!P.qO~O!P.qO%O5hO~O!P.qO%O5hO~P!$dOR5mO!P.qO!k5lO%O5hO~OY5rO}&za!O&za~O}.}O!O(Ti~O]5uO~O!_5vO~O!_5wO~O!_5xO~O!_5xO~P){O^5zO~O!X5}O~O!_6PO~O}'ui!O'ui~P#)tO^$]O'T$]O~P!*WO^$]O!w6UO'T$]O~O^$]O!X!vO!w6UO'T$]O~O!T6ZO!U6ZO'Z$cO'f(eO'o+cO~O^$]O!X!vO!f6[O!w6UO'T$]O'p&lO~O!`$YO'_$dO~P%2TO!S6]O~P%1rO}'ry!_'ry^'ry'T'ry~P!*WO#S$gqP$gqY$gq^$gqi$gqs$gq}$gq!]$gq!^$gq!`$gq!f$gq#W$gq#X$gq#Y$gq#Z$gq#[$gq#]$gq#^$gq#_$gq#a$gq#c$gq#e$gq#f$gq'T$gq'c$gq!_$gqz$gq!P$gq!w$gq'e$gq%O$gq!X$gq~P!$dO}&hi!_&hi~P!*WO#i!zq}!zq!O!zq~P#)tOr-kOs-kOu-lOPoaYoaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa#ioa'coa'poa'woa'xoa}oa!Ooa~Or(POu(QOP$[aY$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a#i$[a'c$[a'p$[a'w$[a'x$[a}$[a!O$[a~Or(POu(QOP$^aY$^ai$^as$^a!]$^a!^$^a!`$^a!f$^a#W$^a#X$^a#Y$^a#Z$^a#[$^a#]$^a#^$^a#_$^a#a$^a#c$^a#e$^a#f$^a#i$^a'c$^a'p$^a'w$^a'x$^a}$^a!O$^a~OP$laY$lai$las$la!]$la!^$la!`$la!f$la#W$la#X$la#Y$la#Z$la#[$la#]$la#^$la#_$la#a$la#c$la#e$la#f$la#i$la'c$la}$la!O$la~P!$dO#i$Wq}$Wq!O$Wq~P#)tO#i$Xq}$Xq!O$Xq~P#)tO!O6gO~O'^$zy}$zy#i$zy!w$zy~P!$dO!X!vO}&pi!_&pi~O!X!vO'p&lO}&pi!_&pi~O},{O!_'}q~Oz&ri}&ri~P!$dO}-SOz(Oq~Oz6nO~P!$dOz6nO~O}'ay'^'ay~P!$dO}&ua!P&ua~P!$dO!P$rq^$rq'T$rq~P!$dOY6vO~O}.UO!O(Vq~O]6yO~O!P&aO%O6zO~O!P&aO%O6zO~P!$dO!w6{O}&wa!O&wa~O}2iO!O(Si~P#)tO!T7RO!U7RO'Z$cO'f(eO'o+cO~O!S7TO!t3}O~P%ArO!P.qO%O7WO~O!P.qO%O7WO~P!$dO'f7^O~O}.}O!O(Tq~O!_7aO~O!_7aO~P){O!_7cO~O!_7dO~O}#Py!O#Py~P#)tO^$]O!w7jO'T$]O~O^$]O!X!vO!w7jO'T$]O~O!T7mO!U7mO'Z$cO'f(eO'o+cO~O^$]O!X!vO!f7nO!w7jO'T$]O'p&lO~O#S$zyP$zyY$zy^$zyi$zys$zy}$zy!]$zy!^$zy!`$zy!f$zy#W$zy#X$zy#Y$zy#Z$zy#[$zy#]$zy#^$zy#_$zy#a$zy#c$zy#e$zy#f$zy'T$zy'c$zy!_$zyz$zy!P$zy!w$zy'e$zy%O$zy!X$zy~P!$dO#i#gy}#gy!O#gy~P#)tOP$eiY$eii$eis$ei!]$ei!^$ei!`$ei!f$ei#W$ei#X$ei#Y$ei#Z$ei#[$ei#]$ei#^$ei#_$ei#a$ei#c$ei#e$ei#f$ei#i$ei'c$ei}$ei!O$ei~P!$dOr(POu(QO'x(UOP$viY$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi#i$vi'c$vi'p$vi'w$vi}$vi!O$vi~Or(POu(QOP$xiY$xii$xis$xi!]$xi!^$xi!`$xi!f$xi#W$xi#X$xi#Y$xi#Z$xi#[$xi#]$xi#^$xi#_$xi#a$xi#c$xi#e$xi#f$xi#i$xi'c$xi'p$xi'w$xi'x$xi}$xi!O$xi~O#i$Xy}$Xy!O$Xy~P#)tO#i!zy}!zy!O!zy~P#)tO!X!vO}&pq!_&pq~O},{O!_'}y~Oz&rq}&rq~P!$dOz7tO~P!$dO}.UO!O(Vy~O}2iO!O(Sq~O!T8QO!U8QO'Z$cO'f(eO'o+cO~O!P.qO%O8TO~O!P.qO%O8TO~P!$dO!_8WO~O&T8XOP&Q!ZQ&Q!ZW&Q!Z]&Q!Z^&Q!Za&Q!Zb&Q!Zg&Q!Zi&Q!Zj&Q!Zk&Q!Zn&Q!Zp&Q!Zu&Q!Zw&Q!Zx&Q!Zy&Q!Z!P&Q!Z!Z&Q!Z!`&Q!Z!c&Q!Z!d&Q!Z!e&Q!Z!f&Q!Z!g&Q!Z!j&Q!Z#`&Q!Z#p&Q!Z#t&Q!Z$}&Q!Z%P&Q!Z%R&Q!Z%S&Q!Z%V&Q!Z%X&Q!Z%[&Q!Z%]&Q!Z%_&Q!Z%l&Q!Z%r&Q!Z%t&Q!Z%v&Q!Z%x&Q!Z%{&Q!Z&R&Q!Z&V&Q!Z&X&Q!Z&Z&Q!Z&]&Q!Z&_&Q!Z'O&Q!Z'Y&Q!Z'c&Q!Z'o&Q!Z'|&Q!Z!O&Q!Z%y&Q!Z_&Q!Z&O&Q!Z~O^$]O!w8^O'T$]O~O^$]O!X!vO!w8^O'T$]O~OP$gqY$gqi$gqs$gq!]$gq!^$gq!`$gq!f$gq#W$gq#X$gq#Y$gq#Z$gq#[$gq#]$gq#^$gq#_$gq#a$gq#c$gq#e$gq#f$gq#i$gq'c$gq}$gq!O$gq~P!$dO}&wq!O&wq~P#)tO^$]O!w8sO'T$]O~OP$zyY$zyi$zys$zy!]$zy!^$zy!`$zy!f$zy#W$zy#X$zy#Y$zy#Z$zy#[$zy#]$zy#^$zy#_$zy#a$zy#c$zy#e$zy#f$zy#i$zy'c$zy}$zy!O$zy~P!$dO'e'gX~P.jO'eZXzZX!_ZX%pZX!PZX%OZX!XZX~P$zO!XcX!_ZX!_cX'pcX~P;dOP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!PSO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'Y'`O'cQO'oYO'|:jO~O}9gO!O$Za~O]#rOg$POi#sOj#rOk#rOn$QOp9lOu#yO!P#zO!Z:oO!`#wO#R9rO#p$UO$]9nO$_9pO$b$VO'Y&xO'c#tO~O#`'gO~P&-RO!OZX!OcX~P;dO#S9ZO~O!X!vO#S9ZO~O!w9jO~O#_9`O~O!w9sO}'uX!O'uX~O!w9jO}'sX!O'sX~O#S9tO~O'^9vO~P!$dO#S9{O~O#S9|O~O!X!vO#S9}O~O!X!vO#S9tO~O#i:OO~P#)tO#S:PO~O#S:QO~O#S:RO~O#S:SO~O#i:TO~P!$dO#i:UO~P!$dO#t~!^!n!p!q#Q#R'|$]$_$b$s$}%O%P%V%X%[%]%_%a~TS#t'|#Xy'V'W'f'W'Y#v#x#v~", ++ goto: "#Dq(ZPPPPPPP([P(lP*`PPPP-uPP.[3l5`5sP5sPPP5s5sP5sP7aPP7fP7zPPPP<ZPPPP<Z>yPPP?PA[P<ZPCuPPPPEm<ZPPPPPGf<ZPPJeKbPPPPKfMOPMWNXPKb<Z<Z!#`!&X!*x!*x!.VPPP!.^!1Q<ZPPPPPPPPPP!3uP!5WPP<Z!6eP<ZP<Z<Z<Z<ZP<Z!8xPP!;oP!>bP!>f!>n!>r!>rP!;lP!>v!>vP!AiP!Am<Z<Z!As!De5sP5sP5s5sP!Eh5s5s!G`5s!Ib5s!KS5s5s!Kp!Mj!Mj!Mn!Mj!MvP!MjP5s!Nr5s# |5s5s-uPPP##ZPP##s##sP##sP#$Y##sPP#$`P#$VP#$V#$rMS#$V#%a#%g#%j([#%m([P#%t#%t#%tP([P([P([P([PP([P#%z#%}P#%}([PPP([P([P([P([P([P([([#&R#&]#&c#&i#&w#&}#'T#'_#'e#'o#'u#(T#(Z#(a#(o#)U#*h#*v#*|#+S#+Y#+`#+j#+p#+v#,Q#,d#,jPPPPPPPPP#,pPP#-d#1bPP#2x#3P#3XP#7ePP#7i#9|#?v#?z#?}#@Q#@]#@`PP#@c#@g#AU#Ay#A}#BaPP#Be#Bk#BoP#Br#Bv#By#Ci#DP#DU#DX#D[#Db#De#Di#DmmhOSj}!m$[%c%f%g%i*k*p/_/bQ$imQ$ppQ%ZyS&T!b+WQ&h!iS(h#z(mQ)c$jQ)p$rQ*[%TQ+^&[S+e&a+gQ+w&iQ-c(oQ.|*]Y0R+i+j+k+l+mS2n.q2pU3w0S0U0XU5d2s2t2uS6Z3y3|S7R5e5fQ7m6]R8Q7T$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!j'b#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ(x$RQ)h$lQ*^%WQ*e%`Q,R9kQ.O)]Q.Z)iQ/U*cQ2X.UQ3T.}Q4W9lR5P2YpeOSjy}!m$[%Y%c%f%g%i*k*p/_/bR*`%[&WVOSTjkn}!S!W!]!j!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:l:mW!cRU!`&UQ$blQ$hmS$mp$rv$wrs!q!t$Y$u&^&q&t)t)u)v*i+Q+a+|,O/h0dQ%PwQ&e!hQ&g!iS([#w(fS)b$i$jQ)f$lQ)s$tQ*V%RQ*Z%TS+v&h&iQ-P(]Q.S)cQ.Y)iQ.[)jQ._)nQ.w*WS.{*[*]Q0`+wQ1Y,{Q2W.UQ2[.XQ2a.aQ3S.|Q4c1ZQ5O2YQ5R2^Q6u4}R7w6v!Y$fm!i$h$i$j&S&g&h&i(g)b)c+T+d+v+w-].S/w0O0T0`1o3v3{6X7k8_Q)Z$bQ){$|Q*O$}Q*Y%TQ.c)sQ.v*VU.z*Z*[*]Q2}.wS3R.{.|Q5_2mQ5q3SS7P5`5cS8O7Q7SQ8i8PR8x8jW#}a$d(u:jS$|t%YQ$}uQ%OvR)y$z$V#|a!v!x#c#w#y$S$T$X&d'z(T(V(W(_(c(s(t)W)Y)])z)}+s,X-S-U-n-x-z.h.k.s.u1X1b1l1s1z1}2R2d2z2|4f4r4z5h5m6z7W8T9i9m9n9o9p9q9r9w9x9y9z9{9|:P:Q:T:U:j:p:qV(y$R9k9lU&X!b$v+ZQ'R!zQ)m$oQ.l*PQ1t-kR5Z2i&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m$]#`Z!_!n$`%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,c,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:c&ZcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ&V!bR/s+WY&P!b&T&[+W+^S(g#z(mS+d&a+gS-](h(oQ-^(iQ-d(pQ.n*RU0O+e+i+jU0T+k+l+mS0Y+n2rQ1o-cQ1q-eQ1r-fS2m.q2pU3v0R0S0UQ3z0VQ3{0XS5`2n2uS5c2s2tU6X3w3y3|Q6^3}S7Q5d5eQ7S5fS7k6Z6]S8P7R7TQ8_7mR8j8QlhOSj}!m$[%c%f%g%i*k*p/_/bQ%k!QS&u!u9ZQ)`$gQ*T%PQ*U%QQ+t&fS,V&z9tS-p)Q9}Q.Q)aQ.p*SQ/f*rQ/g*sQ/o+RQ0W+kQ0^+uS1y-q:RQ2S.RS2V.T:SQ3m/qQ3p/yQ4P0_Q4|2TQ6O3jQ6R3oQ6V3uQ6_4QQ7e6PQ7h6WQ8Z7iQ8n8XQ8p8]R8{8r$W#_Z!_!n%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:cU(r#{&y0}T)U$`,c$W#^Z!_!n%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:cQ'c#_S)T$`,cR-r)U&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ%f{Q%g|Q%i!OQ%j!PR/^*nQ&b!hQ)V$bQ+q&eS-w)Z)sS0Z+o+pW1|-t-u-v.cS4O0[0]U4y2O2P2QU6s4x5V5WQ7v6tR8e7yT+f&a+gS+d&a+gU0O+e+i+jU0T+k+l+mS0Y+n2rS2m.q2pU3v0R0S0UQ3z0VQ3{0XS5`2n2uS5c2s2tU6X3w3y3|Q6^3}S7Q5d5eQ7S5fS7k6Z6]S8P7R7TQ8_7mR8j8QS+f&a+gT2o.q2pS&o!p/[Q-O([Q-Z(gS/}+d2mQ1_-PS1i-[-dU3x0T0Y5cQ4b1YS4p1p1rU6[3z3{7SQ6i4cQ6r4sR7n6^Q!wXS&n!p/[Q)R$ZQ)^$eQ)d$kQ+z&oQ,}([Q-Y(gQ-_(jQ.P)_Q.x*XS/|+d2mS1^-O-PS1h-Z-dQ1k-^Q1n-`Q3P.yW3t/}0T0Y5cQ4a1YQ4e1_S4j1i1rQ4q1qQ5o3QW6Y3x3z3{7SS6h4b4cQ6m4lQ6p4pQ6}5^Q7[5pS7l6[6^Q7p6iQ7r6nQ7u6rQ7|7OQ8V7]Q8`7nQ8c7tQ8g7}Q8v8hQ9O8wQ9S9PQ:]:WQ:f:aR:g:b$nWORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sS!wn!j!j:V#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR:]:l$nXORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sQ$Zb!Y$em!i$h$i$j&S&g&h&i(g)b)c+T+d+v+w-].S/w0O0T0`1o3v3{6X7k8_S$kn!jQ)_$fQ*X%TW.y*Y*Z*[*]U3Q.z.{.|Q5^2mS5p3R3SU7O5_5`5cQ7]5qU7}7P7Q7SS8h8O8PS8w8i8jQ9P8x!j:W#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ:a:kR:b:l$f]OSTjk}!S!W!]!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sU!gRU!`v$wrs!q!t$Y$u&^&q&t)t)u)v*i+Q+a+|,O/h0dQ*f%`!h:X#[#l't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR:[&US&Y!b$vR/u+Z$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!j'b#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR*e%`$noORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sQ'R!z!k:Y#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m!h#UZ!_$`%w%{&v&}'[']'^'_'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9V!R9b'a'r+U,c/m/p0o0w0x0y0z1O1T3n4V4[4]5]6Q6a6e6f7z:c!d#WZ!_$`%w%{&v&}'^'_'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9V}9d'a'r+U,c/m/p0o0y0z1O1T3n4V4[4]5]6Q6a6e6f7z:c!`#[Z!_$`%w%{&v&}'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9Vl(W#u&{)P,y-R-g-h0i1w4`4t:^:h:ix:m'a'r+U,c/m/p0o1O1T3n4V4[4]5]6Q6a6e6f7z:c!`:p&w'f(Z(a+p,U,n-V-s-v.g.i0]0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7YZ:q0|4Z6b7o8a&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mS#m`#nR1Q,f&a_ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l#n$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,f,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mT#i^#oS#g^#oT'k#j'oT#h^#oT'm#j'o&a`ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l#n$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,f,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mT#m`#nQ#p`R'v#n$nbORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!k:k#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m#RdOSUj}!S!W!m!{#l$[%[%_%`%c%e%f%g%i%m&Q&c't)X*g*k*p+r,g-l-y.t/V/W/X/Z/_/b/d1P2c2{3`3b3c5l5zt#{a!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:q!|&y!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:TQ(}$VQ,r(Pc0}9i9n9p9r9x9z9|:Q:Ut#xa!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:qS(j#z(mQ)O$WQ-`(k!|:_!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:Tb:`9i9n9p9r9x9z9|:Q:UQ:d:nR:e:ot#{a!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:q!|&y!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:Tc0}9i9n9p9r9x9z9|:Q:UlfOSj}!m$[%c%f%g%i*k*p/_/bQ(b#yQ*w%pQ*x%rR1a-S$U#|a!v!x#c#w#y$S$T$X&d'z(T(V(W(_(c(s(t)W)Y)])z)}+s,X-S-U-n-x-z.h.k.s.u1X1b1l1s1z1}2R2d2z2|4f4r4z5h5m6z7W8T9i9m9n9o9p9q9r9w9x9y9z9{9|:P:Q:T:U:j:p:qQ)|$}Q.j*OQ2g.iR5Y2hT(l#z(mS(l#z(mT2o.q2pQ)^$eQ-_(jQ.P)_Q.x*XQ3P.yQ5o3QQ6}5^Q7[5pQ7|7OQ8V7]Q8g7}Q8v8hQ9O8wR9S9Pl(T#u&{)P,y-R-g-h0i1w4`4t:^:h:i!`9w&w'f(Z(a+p,U,n-V-s-v.g.i0]0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7YZ9x0|4Z6b7o8an(V#u&{)P,w,y-R-g-h0i1w4`4t:^:h:i!b9y&w'f(Z(a+p,U,n-V-s-v.g.i0]0f0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7Y]9z0|4Z6b6c7o8apeOSjy}!m$[%Y%c%f%g%i*k*p/_/bQ%VxR*g%`peOSjy}!m$[%Y%c%f%g%i*k*p/_/bR%VxQ*Q%OR.f)yqeOSjy}!m$[%Y%c%f%g%i*k*p/_/bQ.r*VS2y.v.wW5g2v2w2x2}U7V5i5j5kU8R7U7X7YQ8k8SR8y8lQ%^yR*a%YR3W/PR7_5rS$mp$rR.[)jQ%czR*k%dR*q%jT/`*p/bQjOQ!mST$_j!mQ'|#tR,o'|Q!YQR%u!YQ!^RU%y!^%z*|Q%z!_R*|%{Q+X&VR/t+XQ,Y&{R0j,YQ,]&}S0m,]0nR0n,^Q+g&aR0P+gQ&_!eQ*}%|T+b&_*}Q+[&YR/v+[Q&r!rQ+{&pU,P&r+{0eR0e,QQ'o#jR,h'oQ#n`R'u#nQ#bZU'e#b*z9hQ*z9VR9h'rQ,|([W1[,|1]4d6jU1],}-O-PS4d1^1_R6j4e#q(R#u&w&{'f(Z(a(z({)P+p,S,T,U,n,w,x,y-R-V-g-h-s-v.g.i0]0f0g0h0i0|1`1d1w2Q2f2h2x4T4X4Y4Z4`4g4m4t4v4{5W5k6`6b6c6d6k6q7Y7o8a:^:h:iQ-T(aU1c-T1e4hQ1e-VR4h1dQ(m#zR-a(mQ(v$OR-j(vQ1{-sR4w1{Q)w$xR.e)wQ2j.lS5[2j6|R6|5]Q*S%PR.o*SQ2p.qR5a2pQ/O*^S3U/O5sR5s3WQ.V)fW2Z.V2]5Q6wQ2].YQ5Q2[R6w5RQ)k$mR.])kQ/b*pR3f/bWiOSj!mQ%h}Q)S$[Q*j%cQ*l%fQ*m%gQ*o%iQ/]*kS/`*p/bR3e/_Q$^gQ%l!RQ%o!TQ%q!UQ%s!VQ)r$sQ)x$yQ*`%^Q*u%nS/R*a*dQ/i*tQ/j*wQ/k*xS/z+d2mQ1f-XQ1g-YQ1m-_Q2`.`Q2e.gQ3O.xQ3Y/TQ3d/^Y3r/|/}0T0Y5cQ4i1hQ4k1jQ4n1nQ5U2bQ5X2fQ5n3PQ5t3X[6S3q3t3x3z3{7SQ6l4jQ6o4oQ6x5SQ7Z5oQ7`5uW7f6T6Y6[6^Q7q6mQ7s6pQ7x6yQ7{6}Q8U7[U8Y7g7l7nQ8b7rQ8d7uQ8f7|Q8m8VS8o8[8`Q8t8cQ8u8gQ8z8qQ8}8vQ9Q8|Q9R9OR9T9SQ$gmQ&f!iU)a$h$i$jQ+R&SU+u&g&h&iQ-X(gS.R)b)cQ/q+TQ/y+dS0_+v+wQ1j-]Q2T.SQ3o/wS3u0O0TQ4Q0`Q4o1oS6W3v3{Q7i6XQ8]7kR8r8_S#va:jR)[$dU$Oa$d:jR-i(uQ#uaS&w!v)]Q&{!xQ'f#cQ(Z#wQ(a#yQ(z$SQ({$TQ)P$XQ+p&dQ,S9mQ,T9oQ,U9qQ,n'zQ,w(TQ,x(VQ,y(WQ-R(_Q-V(cQ-g(sQ-h(td-s)W-x.s1}2z4z5h6z7W8TQ-v)YQ.g)zQ.i)}Q0]+sQ0f9wQ0g9yQ0h9{Q0i,XQ0|9iQ1`-SQ1d-UQ1w-nQ2Q-zQ2f.hQ2h.kQ2x.uQ4T:PQ4X9nQ4Y9pQ4Z9rQ4`1XQ4g1bQ4m1lQ4t1sQ4v1zQ4{2RQ5W2dQ5k2|Q6`:TQ6b9|Q6c9xQ6d9zQ6k4fQ6q4rQ7Y5mQ7o:QQ8a:UQ:^:jQ:h:pR:i:qT'{#t'|lgOSj}!m$[%c%f%g%i*k*p/_/bS!oU%eQ%n!SQ%t!WQ'S!{Q's#lS*d%[%_Q*h%`Q*t%mQ+O&QQ+o&cQ,l'tQ-u)XQ/Y*gQ0[+rQ1S,gQ1u-lQ2P-yQ2w.tQ3[/VQ3]/WQ3_/XQ3a/ZQ3h/dQ4^1PQ5V2cQ5j2{Q5y3`Q5{3bQ5|3cQ7X5lR7b5z!vZOSUj}!S!m!{$[%[%_%`%c%e%f%g%i%m&Q&c)X*g*k*p+r-l-y.t/V/W/X/Z/_/b/d2c2{3`3b3c5l5zQ!_RQ!nTQ$`kQ%w!]Q%{!`Q&v!uQ&}!yQ'T#OQ'U#PQ'V#QQ'W#RQ'X#SQ'Y#TQ'Z#UQ'[#VQ']#WQ'^#XQ'_#YQ'a#[Q'd#aQ'h#dW'r#l't,g1PQ)l$nQ*{%xS+U&U/rQ+_&]Q+x&mQ,W&zQ,^'OQ,a9UQ,c9WQ,q(OQ-o)QQ/m+PQ/p+SQ0a+yQ0k,[Q0o9ZQ0p9[Q0q9]Q0r9^Q0s9_Q0t9`Q0u9aQ0v9bQ0w9cQ0x9dQ0y9eQ0z9fQ0{,bQ1O9jQ1T9gQ1x-qQ2U.TQ3n9sQ3q/{Q4R0bQ4U0lQ4V9tQ4[9vQ4]9}Q5]2kQ6Q3lQ6T3sQ6a:OQ6e:RQ6f:SQ7g6UQ7z6{Q8[7jQ8q8^Q8|8sQ9V!WR:c:mT!XQ!YR!aRR&W!bS&S!b+WS+T&T&[R/w+^R&|!xR'P!yT!sU$YS!rU$YU$xrs*iS&p!q!tQ+}&qQ,Q&tQ.d)vS0c+|,OR4S0d[!dR!`$u&^)t+ah!pUrs!q!t$Y&q&t)v+|,O0dQ/[*iQ/n+QQ3k/hT:Z&U)uT!fR$uS!eR$uS%|!`)tS+V&U)uQ+`&^R/x+aT&Z!b$vQ#j^R'x#oT'n#j'oR1R,fT(^#w(fR(d#yQ-t)WQ2O-xQ2v.sQ4x1}Q5i2zQ6t4zQ7U5hQ7y6zQ8S7WR8l8TlhOSj}!m$[%c%f%g%i*k*p/_/bQ%]yR*`%YV$yrs*iR.m*PR*_%WQ$qpR)q$rR)g$lT%az%dT%bz%dT/a*p/b", ++ nodeNames: "⚠ ArithOp ArithOp extends LineComment BlockComment Script ExportDeclaration export Star as VariableName from String ; default FunctionDeclaration async function VariableDefinition TypeParamList TypeDefinition ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType VoidType void TypeofType typeof MemberExpression . ?. PropertyName [ TemplateString null super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewExpression new TypeArgList CompareOp < ) ( ArgList UnaryExpression await yield delete LogicOp BitOp ParenthesizedExpression ClassExpression class extends ClassBody MethodDeclaration Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression PrivatePropertyName BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof in const CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXStartTag JSXSelfClosingTag JSXIdentifier JSXBuiltin JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast ArrowFunction TypeParamList SequenceExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature CallSignature TypePredicate is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody MethodDeclaration AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try catch finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement", ++ maxTerm: 332, + context: trackNewline, + nodeProps: [ +- [common.NodeProp.group, -26,7,14,16,54,180,184,187,188,190,193,196,207,209,215,217,219,221,224,230,234,236,238,240,242,244,245,"Statement",-30,11,13,23,26,27,38,39,40,41,43,48,56,64,70,71,87,88,97,99,115,118,120,121,122,123,125,126,144,145,147,"Expression",-22,22,24,28,29,31,148,150,152,153,155,156,157,159,160,161,163,164,165,174,176,178,179,"Type",-3,75,81,86,"ClassItem"], +- [common.NodeProp.closedBy, 37,"]",47,"}",62,")",128,"JSXSelfCloseEndTag JSXEndTag",142,"JSXEndTag"], +- [common.NodeProp.openedBy, 42,"[",46,"{",61,"(",127,"JSXStartTag",137,"JSXStartTag JSXStartCloseTag"] ++ [common.NodeProp.group, -26,7,14,16,54,182,186,189,190,192,195,198,209,211,217,219,221,223,226,232,236,238,240,242,244,246,247,"Statement",-30,11,13,23,26,27,38,39,40,41,43,48,56,64,70,71,87,88,97,99,115,118,120,121,122,123,125,126,146,147,149,"Expression",-22,22,24,28,29,31,150,152,154,155,157,158,159,161,162,163,165,166,167,176,178,180,181,"Type",-3,75,81,86,"ClassItem"], ++ [common.NodeProp.closedBy, 37,"]",47,"}",62,")",128,"JSXSelfCloseEndTag JSXEndTag",144,"JSXEndTag"], ++ [common.NodeProp.openedBy, 42,"[",46,"{",61,"(",127,"JSXStartTag",139,"JSXStartTag JSXStartCloseTag"] + ], + skippedNodes: [0,4,5], + repeatNodeCount: 28, +- tokenData: "!C}~R!`OX%TXY%cYZ'RZ[%c[]%T]^'R^p%Tpq%cqr'crs(kst0htu2`uv4pvw5ewx6cxy<yyz=Zz{=k{|>k|}?O}!O>k!O!P?`!P!QCl!Q!R!0[!R![!1q![!]!7s!]!^!8V!^!_!8g!_!`!9d!`!a!:[!a!b!<R!b!c%T!c!}2`!}#O!=d#O#P%T#P#Q!=t#Q#R!>U#R#S2`#S#T!>i#T#o2`#o#p!>y#p#q!?O#q#r!?f#r#s!?x#s$f%T$f$g%c$g#BY2`#BY#BZ!@Y#BZ$IS2`$IS$I_!@Y$I_$I|2`$I|$I}!Bq$I}$JO!Bq$JO$JT2`$JT$JU!@Y$JU$KV2`$KV$KW!@Y$KW&FU2`&FU&FV!@Y&FV?HT2`?HT?HU!@Y?HU~2`W%YR$QWO!^%T!_#o%T#p~%T,T%jg$QW'T+{OX%TXY%cYZ%TZ[%c[p%Tpq%cq!^%T!_#o%T#p$f%T$f$g%c$g#BY%T#BY#BZ%c#BZ$IS%T$IS$I_%c$I_$JT%T$JT$JU%c$JU$KV%T$KV$KW%c$KW&FU%T&FU&FV%c&FV?HT%T?HT?HU%c?HU~%T,T'YR$QW'U+{O!^%T!_#o%T#p~%T$T'jS$QW!f#{O!^%T!_!`'v!`#o%T#p~%T$O'}S#a#v$QWO!^%T!_!`(Z!`#o%T#p~%T$O(bR#a#v$QWO!^%T!_#o%T#p~%T'u(rZ$QW]!ROY(kYZ)eZr(krs*rs!^(k!^!_+U!_#O(k#O#P-b#P#o(k#o#p+U#p~(k&r)jV$QWOr)ers*Ps!^)e!^!_*a!_#o)e#o#p*a#p~)e&r*WR#{&j$QWO!^%T!_#o%T#p~%T&j*dROr*ars*ms~*a&j*rO#{&j'u*{R#{&j$QW]!RO!^%T!_#o%T#p~%T'm+ZV]!ROY+UYZ*aZr+Urs+ps#O+U#O#P+w#P~+U'm+wO#{&j]!R'm+zROr+Urs,Ts~+U'm,[U#{&j]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R,sU]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R-[O]!R!R-_PO~,n'u-gV$QWOr(krs-|s!^(k!^!_+U!_#o(k#o#p+U#p~(k'u.VZ#{&j$QW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/PZ$QW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/yR$QW]!RO!^%T!_#o%T#p~%T!Z0XT$QWO!^.x!^!_,n!_#o.x#o#p,n#p~.xy0mZ$QWOt%Ttu1`u!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`y1g]$QW'mqOt%Ttu1`u!Q%T!Q![1`![!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`&i2k_$QW#vS'W%k'dpOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`[3q_$QW#vSOt%Ttu3ju}%T}!O3j!O!Q%T!Q![3j![!^%T!_!c%T!c!}3j!}#R%T#R#S3j#S#T%T#T#o3j#p$g%T$g~3j$O4wS#Y#v$QWO!^%T!_!`5T!`#o%T#p~%T$O5[R$QW#k#vO!^%T!_#o%T#p~%T%r5lU'v%j$QWOv%Tvw6Ow!^%T!_!`5T!`#o%T#p~%T$O6VS$QW#e#vO!^%T!_!`5T!`#o%T#p~%T'u6jZ$QW]!ROY6cYZ7]Zw6cwx*rx!^6c!^!_8T!_#O6c#O#P:T#P#o6c#o#p8T#p~6c&r7bV$QWOw7]wx*Px!^7]!^!_7w!_#o7]#o#p7w#p~7]&j7zROw7wwx*mx~7w'm8YV]!ROY8TYZ7wZw8Twx+px#O8T#O#P8o#P~8T'm8rROw8Twx8{x~8T'm9SU#{&j]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R9kU]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R:QPO~9f'u:YV$QWOw6cwx:ox!^6c!^!_8T!_#o6c#o#p8T#p~6c'u:xZ#{&j$QW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z;rZ$QW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z<jT$QWO!^;k!^!_9f!_#o;k#o#p9f#p~;k%V=QR!`$}$QWO!^%T!_#o%T#p~%TZ=bR!_R$QWO!^%T!_#o%T#p~%T%R=tU'X!R#Z#v$QWOz%Tz{>W{!^%T!_!`5T!`#o%T#p~%T$O>_S#W#v$QWO!^%T!_!`5T!`#o%T#p~%T$u>rSi$m$QWO!^%T!_!`5T!`#o%T#p~%T&i?VR}&a$QWO!^%T!_#o%T#p~%T&i?gVr%n$QWO!O%T!O!P?|!P!Q%T!Q![@r![!^%T!_#o%T#p~%Ty@RT$QWO!O%T!O!P@b!P!^%T!_#o%T#p~%Ty@iR|q$QWO!^%T!_#o%T#p~%Ty@yZ$QWjqO!Q%T!Q![@r![!^%T!_!g%T!g!hAl!h#R%T#R#S@r#S#X%T#X#YAl#Y#o%T#p~%TyAqZ$QWO{%T{|Bd|}%T}!OBd!O!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyBiV$QWO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyCVV$QWjqO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%T,TCs`$QW#X#vOYDuYZ%TZzDuz{Jl{!PDu!P!Q!-e!Q!^Du!^!_Fx!_!`!.^!`!a!/]!a!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXD|[$QWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXEy_$QWyPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%TPF}VyPOYFxZ!PFx!P!QGd!Q!}Fx!}#OG{#O#PHh#P~FxPGiUyP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGdPHOTOYG{Z#OG{#O#PH_#P#QFx#Q~G{PHbQOYG{Z~G{PHkQOYFxZ~FxXHvY$QWOYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~HqXIkV$QWOYHqYZ%TZ!^Hq!^!_G{!_#oHq#o#pG{#p~HqXJVV$QWOYDuYZ%TZ!^Du!^!_Fx!_#oDu#o#pFx#p~Du,TJs^$QWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q!,R!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,TKtV$QWOzKoz{LZ{!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TL`X$QWOzKoz{LZ{!PKo!P!QL{!Q!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TMSR$QWT+{O!^%T!_#o%T#p~%T+{M`ROzM]z{Mi{~M]+{MlTOzM]z{Mi{!PM]!P!QM{!Q~M]+{NQOT+{,TNX^$QWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q! T!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,T! ^_$QWT+{yPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%T+{!!bYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!&x!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#VYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!#u!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#|UT+{yP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGd+{!$cWOY!$`YZM]Zz!$`z{!${{#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%OYOY!$`YZM]Zz!$`z{!${{!P!$`!P!Q!%n!Q#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%sTT+{OYG{Z#OG{#O#PH_#P#QFx#Q~G{+{!&VTOY!$`YZM]Zz!$`z{!${{~!$`+{!&iTOY!!]YZM]Zz!!]z{!#Q{~!!]+{!&}_yPOzM]z{Mi{#ZM]#Z#[!&x#[#]M]#]#^!&x#^#aM]#a#b!&x#b#gM]#g#h!&x#h#iM]#i#j!&x#j#mM]#m#n!&x#n~M],T!(R[$QWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!(|^$QWOY!'|YZKoZz!'|z{!(w{!P!'|!P!Q!)x!Q!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!*PY$QWT+{OYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~Hq,T!*tX$QWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#o!'|#o#p!$`#p~!'|,T!+fX$QWOYJlYZKoZzJlz{NQ{!^Jl!^!_!!]!_#oJl#o#p!!]#p~Jl,T!,Yc$QWyPOzKoz{LZ{!^Ko!^!_M]!_#ZKo#Z#[!,R#[#]Ko#]#^!,R#^#aKo#a#b!,R#b#gKo#g#h!,R#h#iKo#i#j!,R#j#mKo#m#n!,R#n#oKo#o#pM]#p~Ko,T!-lV$QWS+{OY!-eYZ%TZ!^!-e!^!_!.R!_#o!-e#o#p!.R#p~!-e+{!.WQS+{OY!.RZ~!.R$P!.g[$QW#k#vyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Du]!/f[#sS$QWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Duy!0cd$QWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#U%T#U#V!3X#V#X%T#X#YAl#Y#b%T#b#c!2w#c#d!4m#d#l%T#l#m!5{#m#o%T#p~%Ty!1x_$QWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#X%T#X#YAl#Y#b%T#b#c!2w#c#o%T#p~%Ty!3OR$QWjqO!^%T!_#o%T#p~%Ty!3^W$QWO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#o%T#p~%Ty!3}Y$QWjqO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#b%T#b#c!2w#c#o%T#p~%Ty!4rV$QWO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#o%T#p~%Ty!5`X$QWjqO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#b%T#b#c!2w#c#o%T#p~%Ty!6QZ$QWO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#o%T#p~%Ty!6z]$QWjqO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#b%T#b#c!2w#c#o%T#p~%T%w!7|R!XV$QW#i%hO!^%T!_#o%T#p~%T!P!8^R^w$QWO!^%T!_#o%T#p~%T+c!8rR']d!]%Y#t&s'zP!P!Q!8{!^!_!9Q!_!`!9_W!9QO$SW#v!9VP#[#v!_!`!9Y#v!9_O#k#v#v!9dO#]#v%w!9kT!w%o$QWO!^%T!_!`'v!`!a!9z!a#o%T#p~%T$P!:RR#S#w$QWO!^%T!_#o%T#p~%T%w!:gT'[!s#]#v#}S$QWO!^%T!_!`!:v!`!a!;W!a#o%T#p~%T$O!:}R#]#v$QWO!^%T!_#o%T#p~%T$O!;_T#[#v$QWO!^%T!_!`5T!`!a!;n!a#o%T#p~%T$O!;uS#[#v$QWO!^%T!_!`5T!`#o%T#p~%T%w!<YV'n%o$QWO!O%T!O!P!<o!P!^%T!_!a%T!a!b!=P!b#o%T#p~%T$`!<vRs$W$QWO!^%T!_#o%T#p~%T$O!=WS$QW#f#vO!^%T!_!`5T!`#o%T#p~%T&e!=kRu&]$QWO!^%T!_#o%T#p~%TZ!={RzR$QWO!^%T!_#o%T#p~%T$O!>]S#c#v$QWO!^%T!_!`5T!`#o%T#p~%T$P!>pR$QW'a#wO!^%T!_#o%T#p~%T~!?OO!P~%r!?VT'u%j$QWO!^%T!_!`5T!`#o%T#p#q!=P#q~%T$u!?oR!O$k$QW'cQO!^%T!_#o%T#p~%TX!@PR!gP$QWO!^%T!_#o%T#p~%T,T!@gr$QW'T+{#vS'W%k'dpOX%TXY%cYZ%TZ[%c[p%Tpq%cqt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$f%T$f$g%c$g#BY2`#BY#BZ!@Y#BZ$IS2`$IS$I_!@Y$I_$JT2`$JT$JU!@Y$JU$KV2`$KV$KW!@Y$KW&FU2`&FU&FV!@Y&FV?HT2`?HT?HU!@Y?HU~2`,T!CO_$QW'U+{#vS'W%k'dpOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`", ++ tokenData: "!F_~R!`OX%TXY%cYZ'RZ[%c[]%T]^'R^p%Tpq%cqr'crs(kst0htu2`uv4pvw5ewx6cxy<yyz=Zz{=k{|>k|}?O}!O>k!O!P?`!P!QCl!Q!R!0[!R![!1q![!]!7s!]!^!8V!^!_!8g!_!`!9d!`!a!:[!a!b!<R!b!c%T!c!}2`!}#O!=d#O#P%T#P#Q!=t#Q#R!>U#R#S2`#S#T!>i#T#o!>y#o#p!AZ#p#q!A`#q#r!Av#r#s!BY#s$f%T$f$g%c$g#BY2`#BY#BZ!Bj#BZ$IS2`$IS$I_!Bj$I_$I|2`$I|$I}!ER$I}$JO!ER$JO$JT2`$JT$JU!Bj$JU$KV2`$KV$KW!Bj$KW&FU2`&FU&FV!Bj&FV?HT2`?HT?HU!Bj?HU~2`W%YR$SWO!^%T!_#o%T#p~%T,T%jg$SW'V+{OX%TXY%cYZ%TZ[%c[p%Tpq%cq!^%T!_#o%T#p$f%T$f$g%c$g#BY%T#BY#BZ%c#BZ$IS%T$IS$I_%c$I_$JT%T$JT$JU%c$JU$KV%T$KV$KW%c$KW&FU%T&FU&FV%c&FV?HT%T?HT?HU%c?HU~%T,T'YR$SW'W+{O!^%T!_#o%T#p~%T$T'jS$SW!f#{O!^%T!_!`'v!`#o%T#p~%T$O'}S#a#v$SWO!^%T!_!`(Z!`#o%T#p~%T$O(bR#a#v$SWO!^%T!_#o%T#p~%T'u(rZ$SW]!ROY(kYZ)eZr(krs*rs!^(k!^!_+U!_#O(k#O#P-b#P#o(k#o#p+U#p~(k&r)jV$SWOr)ers*Ps!^)e!^!_*a!_#o)e#o#p*a#p~)e&r*WR#}&j$SWO!^%T!_#o%T#p~%T&j*dROr*ars*ms~*a&j*rO#}&j'u*{R#}&j$SW]!RO!^%T!_#o%T#p~%T'm+ZV]!ROY+UYZ*aZr+Urs+ps#O+U#O#P+w#P~+U'm+wO#}&j]!R'm+zROr+Urs,Ts~+U'm,[U#}&j]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R,sU]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R-[O]!R!R-_PO~,n'u-gV$SWOr(krs-|s!^(k!^!_+U!_#o(k#o#p+U#p~(k'u.VZ#}&j$SW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/PZ$SW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/yR$SW]!RO!^%T!_#o%T#p~%T!Z0XT$SWO!^.x!^!_,n!_#o.x#o#p,n#p~.xy0mZ$SWOt%Ttu1`u!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`y1g]$SW'oqOt%Ttu1`u!Q%T!Q![1`![!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`&i2k_$SW'fp'Y%k#vSOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`[3q_$SW#vSOt%Ttu3ju}%T}!O3j!O!Q%T!Q![3j![!^%T!_!c%T!c!}3j!}#R%T#R#S3j#S#T%T#T#o3j#p$g%T$g~3j$O4wS#Y#v$SWO!^%T!_!`5T!`#o%T#p~%T$O5[R$SW#k#vO!^%T!_#o%T#p~%T%r5lU'x%j$SWOv%Tvw6Ow!^%T!_!`5T!`#o%T#p~%T$O6VS$SW#e#vO!^%T!_!`5T!`#o%T#p~%T'u6jZ$SW]!ROY6cYZ7]Zw6cwx*rx!^6c!^!_8T!_#O6c#O#P:T#P#o6c#o#p8T#p~6c&r7bV$SWOw7]wx*Px!^7]!^!_7w!_#o7]#o#p7w#p~7]&j7zROw7wwx*mx~7w'm8YV]!ROY8TYZ7wZw8Twx+px#O8T#O#P8o#P~8T'm8rROw8Twx8{x~8T'm9SU#}&j]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R9kU]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R:QPO~9f'u:YV$SWOw6cwx:ox!^6c!^!_8T!_#o6c#o#p8T#p~6c'u:xZ#}&j$SW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z;rZ$SW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z<jT$SWO!^;k!^!_9f!_#o;k#o#p9f#p~;k%V=QR!`$}$SWO!^%T!_#o%T#p~%TZ=bR!_R$SWO!^%T!_#o%T#p~%T%R=tU'Z!R#Z#v$SWOz%Tz{>W{!^%T!_!`5T!`#o%T#p~%T$O>_S#W#v$SWO!^%T!_!`5T!`#o%T#p~%T$u>rSi$m$SWO!^%T!_!`5T!`#o%T#p~%T&i?VR}&a$SWO!^%T!_#o%T#p~%T&i?gVr%n$SWO!O%T!O!P?|!P!Q%T!Q![@r![!^%T!_#o%T#p~%Ty@RT$SWO!O%T!O!P@b!P!^%T!_#o%T#p~%Ty@iR|q$SWO!^%T!_#o%T#p~%Ty@yZ$SWjqO!Q%T!Q![@r![!^%T!_!g%T!g!hAl!h#R%T#R#S@r#S#X%T#X#YAl#Y#o%T#p~%TyAqZ$SWO{%T{|Bd|}%T}!OBd!O!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyBiV$SWO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyCVV$SWjqO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%T,TCs`$SW#X#vOYDuYZ%TZzDuz{Jl{!PDu!P!Q!-e!Q!^Du!^!_Fx!_!`!.^!`!a!/]!a!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXD|[$SWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXEy_$SWyPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%TPF}VyPOYFxZ!PFx!P!QGd!Q!}Fx!}#OG{#O#PHh#P~FxPGiUyP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGdPHOTOYG{Z#OG{#O#PH_#P#QFx#Q~G{PHbQOYG{Z~G{PHkQOYFxZ~FxXHvY$SWOYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~HqXIkV$SWOYHqYZ%TZ!^Hq!^!_G{!_#oHq#o#pG{#p~HqXJVV$SWOYDuYZ%TZ!^Du!^!_Fx!_#oDu#o#pFx#p~Du,TJs^$SWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q!,R!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,TKtV$SWOzKoz{LZ{!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TL`X$SWOzKoz{LZ{!PKo!P!QL{!Q!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TMSR$SWT+{O!^%T!_#o%T#p~%T+{M`ROzM]z{Mi{~M]+{MlTOzM]z{Mi{!PM]!P!QM{!Q~M]+{NQOT+{,TNX^$SWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q! T!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,T! ^_$SWT+{yPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%T+{!!bYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!&x!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#VYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!#u!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#|UT+{yP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGd+{!$cWOY!$`YZM]Zz!$`z{!${{#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%OYOY!$`YZM]Zz!$`z{!${{!P!$`!P!Q!%n!Q#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%sTT+{OYG{Z#OG{#O#PH_#P#QFx#Q~G{+{!&VTOY!$`YZM]Zz!$`z{!${{~!$`+{!&iTOY!!]YZM]Zz!!]z{!#Q{~!!]+{!&}_yPOzM]z{Mi{#ZM]#Z#[!&x#[#]M]#]#^!&x#^#aM]#a#b!&x#b#gM]#g#h!&x#h#iM]#i#j!&x#j#mM]#m#n!&x#n~M],T!(R[$SWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!(|^$SWOY!'|YZKoZz!'|z{!(w{!P!'|!P!Q!)x!Q!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!*PY$SWT+{OYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~Hq,T!*tX$SWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#o!'|#o#p!$`#p~!'|,T!+fX$SWOYJlYZKoZzJlz{NQ{!^Jl!^!_!!]!_#oJl#o#p!!]#p~Jl,T!,Yc$SWyPOzKoz{LZ{!^Ko!^!_M]!_#ZKo#Z#[!,R#[#]Ko#]#^!,R#^#aKo#a#b!,R#b#gKo#g#h!,R#h#iKo#i#j!,R#j#mKo#m#n!,R#n#oKo#o#pM]#p~Ko,T!-lV$SWS+{OY!-eYZ%TZ!^!-e!^!_!.R!_#o!-e#o#p!.R#p~!-e+{!.WQS+{OY!.RZ~!.R$P!.g[$SW#k#vyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Du]!/f[#sS$SWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Duy!0cd$SWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#U%T#U#V!3X#V#X%T#X#YAl#Y#b%T#b#c!2w#c#d!4m#d#l%T#l#m!5{#m#o%T#p~%Ty!1x_$SWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#X%T#X#YAl#Y#b%T#b#c!2w#c#o%T#p~%Ty!3OR$SWjqO!^%T!_#o%T#p~%Ty!3^W$SWO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#o%T#p~%Ty!3}Y$SWjqO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#b%T#b#c!2w#c#o%T#p~%Ty!4rV$SWO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#o%T#p~%Ty!5`X$SWjqO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#b%T#b#c!2w#c#o%T#p~%Ty!6QZ$SWO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#o%T#p~%Ty!6z]$SWjqO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#b%T#b#c!2w#c#o%T#p~%T%w!7|R!XV$SW#i%hO!^%T!_#o%T#p~%T!P!8^R^w$SWO!^%T!_#o%T#p~%T+c!8rR'_d!]%Y#t&s'|P!P!Q!8{!^!_!9Q!_!`!9_W!9QO$UW#v!9VP#[#v!_!`!9Y#v!9_O#k#v#v!9dO#]#v%w!9kT!w%o$SWO!^%T!_!`'v!`!a!9z!a#o%T#p~%T$P!:RR#S#w$SWO!^%T!_#o%T#p~%T%w!:gT'^!s#]#v$PS$SWO!^%T!_!`!:v!`!a!;W!a#o%T#p~%T$O!:}R#]#v$SWO!^%T!_#o%T#p~%T$O!;_T#[#v$SWO!^%T!_!`5T!`!a!;n!a#o%T#p~%T$O!;uS#[#v$SWO!^%T!_!`5T!`#o%T#p~%T%w!<YV'p%o$SWO!O%T!O!P!<o!P!^%T!_!a%T!a!b!=P!b#o%T#p~%T$`!<vRs$W$SWO!^%T!_#o%T#p~%T$O!=WS$SW#f#vO!^%T!_!`5T!`#o%T#p~%T&e!=kRu&]$SWO!^%T!_#o%T#p~%TZ!={RzR$SWO!^%T!_#o%T#p~%T$O!>]S#c#v$SWO!^%T!_!`5T!`#o%T#p~%T$P!>pR$SW'c#wO!^%T!_#o%T#p~%T&i!?U_$SW'fp'Y%k#xSOt%Ttu!>yu}%T}!O!@T!O!Q%T!Q![!>y![!^%T!_!c%T!c!}!>y!}#R%T#R#S!>y#S#T%T#T#o!>y#p$g%T$g~!>y[!@[_$SW#xSOt%Ttu!@Tu}%T}!O!@T!O!Q%T!Q![!@T![!^%T!_!c%T!c!}!@T!}#R%T#R#S!@T#S#T%T#T#o!@T#p$g%T$g~!@T~!A`O!P~%r!AgT'w%j$SWO!^%T!_!`5T!`#o%T#p#q!=P#q~%T$u!BPR!O$k$SW'eQO!^%T!_#o%T#p~%TX!BaR!gP$SWO!^%T!_#o%T#p~%T,T!Bwr$SW'V+{'fp'Y%k#vSOX%TXY%cYZ%TZ[%c[p%Tpq%cqt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$f%T$f$g%c$g#BY2`#BY#BZ!Bj#BZ$IS2`$IS$I_!Bj$I_$JT2`$JT$JU!Bj$JU$KV2`$KV$KW!Bj$KW&FU2`&FU&FV!Bj&FV?HT2`?HT?HU!Bj?HU~2`,T!E`_$SW'W+{'fp'Y%k#vSOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`", + tokenizers: [noSemicolon, incdecToken, template, 0, 1, 2, 3, 4, 5, 6, 7, 8, insertSemicolon], + topRules: {"Script":[0,6]}, +- dialects: {jsx: 11282, ts: 11284}, +- dynamicPrecedences: {"145":1,"172":1}, +- specialized: [{term: 284, get: (value, stack) => (tsExtends(value, stack) << 1)},{term: 284, get: value => spec_identifier[value] || -1},{term: 296, get: value => spec_word[value] || -1},{term: 59, get: value => spec_LessThan[value] || -1}], +- tokenPrec: 11305 ++ dialects: {jsx: 11332, ts: 11334}, ++ dynamicPrecedences: {"147":1,"174":1}, ++ specialized: [{term: 286, get: (value, stack) => (tsExtends(value, stack) << 1)},{term: 286, get: value => spec_identifier[value] || -1},{term: 298, get: value => spec_word[value] || -1},{term: 59, get: value => spec_LessThan[value] || -1}], ++ tokenPrec: 11355 + }); + + exports.parser = parser; +diff --git a/node_modules/@lezer/javascript/dist/index.es.js b/node_modules/@lezer/javascript/dist/index.es.js +index 94d1df0..489e965 100644 +--- a/node_modules/@lezer/javascript/dist/index.es.js ++++ b/node_modules/@lezer/javascript/dist/index.es.js +@@ -2,16 +2,16 @@ import { ContextTracker, ExternalTokenizer, LRParser } from '@lezer/lr'; + import { NodeProp } from '@lezer/common'; + + // This file was generated by lezer-generator. You probably shouldn't edit it. +-const noSemi = 275, ++const noSemi = 277, + incdec = 1, + incdecPrefix = 2, +- templateContent = 276, +- templateDollarBrace = 277, +- templateEnd = 278, +- insertSemi = 279, ++ templateContent = 278, ++ templateDollarBrace = 279, ++ templateEnd = 280, ++ insertSemi = 281, + TSExtends = 3, +- spaces = 281, +- newline = 282, ++ spaces = 283, ++ newline = 284, + LineComment = 4, + BlockComment = 5, + Dialect_ts = 1; +@@ -91,31 +91,31 @@ function tsExtends(value, stack) { + } + + // This file was generated by lezer-generator. You probably shouldn't edit it. +-const spec_identifier = {__proto__:null,export:16, as:21, from:25, default:30, async:35, function:36, this:46, true:54, false:54, void:60, typeof:64, null:78, super:80, new:114, await:131, yield:133, delete:134, class:144, extends:146, public:189, private:189, protected:189, readonly:191, instanceof:212, in:214, const:216, import:248, keyof:299, unique:303, infer:309, is:343, abstract:363, implements:365, type:367, let:370, var:372, interface:379, enum:383, namespace:389, module:391, declare:395, global:399, for:420, of:429, while:432, with:436, do:440, if:444, else:446, switch:450, case:456, try:462, catch:464, finally:466, return:470, throw:474, break:478, continue:482, debugger:486}; +-const spec_word = {__proto__:null,async:101, get:103, set:105, public:153, private:153, protected:153, static:155, abstract:157, override:159, readonly:165, new:347}; ++const spec_identifier = {__proto__:null,export:16, as:21, from:25, default:30, async:35, function:36, this:46, true:54, false:54, void:60, typeof:64, null:78, super:80, new:114, await:131, yield:133, delete:134, class:144, extends:146, public:189, private:189, protected:189, readonly:191, instanceof:212, in:214, const:216, import:248, keyof:303, unique:307, infer:313, is:347, abstract:367, implements:369, type:371, let:374, var:376, interface:383, enum:387, namespace:393, module:395, declare:399, global:403, for:424, of:433, while:436, with:440, do:444, if:448, else:450, switch:454, case:460, try:466, catch:468, finally:470, return:474, throw:478, break:482, continue:486, debugger:490}; ++const spec_word = {__proto__:null,async:101, get:103, set:105, public:153, private:153, protected:153, static:155, abstract:157, override:159, readonly:165, new:351}; + const spec_LessThan = {__proto__:null,"<":121}; + const parser = LRParser.deserialize({ + version: 13, +- states: "$1WO`QYOOO'QQ!LdO'#CgO'XOSO'#DSO)dQYO'#DXO)tQYO'#DdO){QYO'#DnO-xQYO'#DtOOQO'#EX'#EXO.]QWO'#EWO.bQWO'#EWOOQ!LS'#Eb'#EbO0aQ!LdO'#IqO2wQ!LdO'#IrO3eQWO'#EvO3jQpO'#F]OOQ!LS'#FO'#FOO3rO!bO'#FOO4QQWO'#FdO5_QWO'#FcOOQ!LS'#Ir'#IrOOQ!LQ'#Iq'#IqOOQQ'#J['#J[O5dQWO'#HjO5iQ!LYO'#HkOOQQ'#Ic'#IcOOQQ'#Hl'#HlQ`QYOOO){QYO'#DfO5qQWO'#GWO5vQ#tO'#ClO6UQWO'#EVO6aQWO'#EcO6fQ#tO'#E}O7QQWO'#GWO7VQWO'#G[O7bQWO'#G[O7pQWO'#G_O7pQWO'#G`O7pQWO'#GbO5qQWO'#GeO8aQWO'#GhO9oQWO'#CcO:PQWO'#GuO:XQWO'#G{O:XQWO'#G}O`QYO'#HPO:XQWO'#HRO:XQWO'#HUO:^QWO'#H[O:cQ!LZO'#H`O){QYO'#HbO:nQ!LZO'#HdO:yQ!LZO'#HfO5iQ!LYO'#HhO){QYO'#IsOOOS'#Hn'#HnO;UOSO,59nOOQ!LS,59n,59nO=gQbO'#CgO=qQYO'#HoO>OQWO'#ItO?}QbO'#ItO'dQYO'#ItO@UQWO,59sO@lQ&jO'#D^OAeQWO'#EXOArQWO'#JPOA}QWO'#JOOBVQWO,5:uOB[QWO'#I}OBcQWO'#DuO5vQ#tO'#EVOBqQWO'#EVOB|Q`O'#E}OOQ!LS,5:O,5:OOCUQYO,5:OOESQ!LdO,5:YOEpQWO,5:`OFZQ!LYO'#I|O7VQWO'#I{OFbQWO'#I{OFjQWO,5:tOFoQWO'#I{OF}QYO,5:rOH}QWO'#ESOJXQWO,5:rOKhQWO'#DhOKoQYO'#DmOKyQ&jO,5:{O){QYO,5:{OOQQ'#En'#EnOOQQ'#Ep'#EpO){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}OOQQ'#Et'#EtOLRQYO,5;_OOQ!LS,5;d,5;dOOQ!LS,5;e,5;eONRQWO,5;eOOQ!LS,5;f,5;fO){QYO'#HyONWQ!LYO,5<PONrQWO,5:}O){QYO,5;bO! [QpO'#JTONyQpO'#JTO! cQpO'#JTO! tQpO,5;mOOOO,5;w,5;wO!!SQYO'#F_OOOO'#Hx'#HxO3rO!bO,5;jO!!ZQpO'#FaOOQ!LS,5;j,5;jO!!wQ,UO'#CqOOQ!LS'#Ct'#CtO!#[QWO'#CtO!#aOSO'#CxO!#}Q#tO,5;|O!$UQWO,5<OO!%bQWO'#FnO!%oQWO'#FoO!%tQWO'#FsO!&vQ&jO'#FwO!'iQ,UO'#IlOOQ!LS'#Il'#IlO!'sQWO'#IkO!(RQWO'#IjOOQ!LS'#Cr'#CrOOQ!LS'#Cy'#CyO!(ZQWO'#C{OJ^QWO'#FfOJ^QWO'#FhO!(`QWO'#FjO!(eQWO'#FkO!(jQWO'#FqOJ^QWO'#FvO!(oQWO'#EYO!)WQWO,5;}O`QYO,5>UOOQQ'#If'#IfOOQQ,5>V,5>VOOQQ-E;j-E;jO!+SQ!LdO,5:QOOQ!LQ'#Co'#CoO!+sQ#tO,5<rOOQO'#Ce'#CeO!,UQWO'#CpO!,^Q!LYO'#IgO5_QWO'#IgO:^QWO,59WO!,lQpO,59WO!,tQ#tO,59WO5vQ#tO,59WO!-PQWO,5:rO!-XQWO'#GtO!-dQWO'#J`O){QYO,5;gO!-lQ&jO,5;iO!-qQWO,5=_O!-vQWO,5=_O!-{QWO,5=_O5iQ!LYO,5=_O5qQWO,5<rO!.ZQWO'#EZO!.lQ&jO'#E[OOQ!LQ'#I}'#I}O!.}Q!LYO'#J]O5iQ!LYO,5<vO7pQWO,5<|OOQO'#Cq'#CqO!/YQpO,5<yO!/bQ#tO,5<zO!/mQWO,5<|O!/rQ`O,5=PO:^QWO'#GjO5qQWO'#GlO!/zQWO'#GlO5vQ#tO'#GoO!0PQWO'#GoOOQQ,5=S,5=SO!0UQWO'#GpO!0^QWO'#ClO!0cQWO,58}O!0mQWO,58}O!2oQYO,58}OOQQ,58},58}O!2|Q!LYO,58}O){QYO,58}O!3XQYO'#GwOOQQ'#Gx'#GxOOQQ'#Gy'#GyO`QYO,5=aO!3iQWO,5=aO){QYO'#DtO`QYO,5=gO`QYO,5=iO!3nQWO,5=kO`QYO,5=mO!3sQWO,5=pO!3xQYO,5=vOOQQ,5=z,5=zO){QYO,5=zO5iQ!LYO,5=|OOQQ,5>O,5>OO!7yQWO,5>OOOQQ,5>Q,5>QO!7yQWO,5>QOOQQ,5>S,5>SO!8OQ`O,5?_OOOS-E;l-E;lOOQ!LS1G/Y1G/YO!8TQbO,5>ZO){QYO,5>ZOOQO-E;m-E;mO!8_QWO,5?`O!8gQbO,5?`O!8nQWO,5?jOOQ!LS1G/_1G/_O!8vQpO'#DQOOQO'#Iv'#IvO){QYO'#IvO!9eQpO'#IvO!:SQpO'#D_O!:eQ&jO'#D_O!<pQYO'#D_O!<wQWO'#IuO!=PQWO,59xO!=UQWO'#E]O!=dQWO'#JQO!=lQWO,5:vO!>SQ&jO'#D_O){QYO,5?kO!>^QWO'#HtO!8nQWO,5?jOOQ!LQ1G0a1G0aO!?jQ&jO'#DxOOQ!LS,5:a,5:aO){QYO,5:aOH}QWO,5:aO!?qQWO,5:aO:^QWO,5:qO!,lQpO,5:qO!,tQ#tO,5:qO5vQ#tO,5:qOOQ!LS1G/j1G/jOOQ!LS1G/z1G/zOOQ!LQ'#ER'#ERO){QYO,5?hO!?|Q!LYO,5?hO!@_Q!LYO,5?hO!@fQWO,5?gO!@nQWO'#HvO!@fQWO,5?gOOQ!LQ1G0`1G0`O7VQWO,5?gOOQ!LS1G0^1G0^O!AYQ!LdO1G0^O!AyQ!LbO,5:nOOQ!LS'#Fm'#FmO!BgQ!LdO'#IlOF}QYO1G0^O!DfQ#tO'#IwO!DpQWO,5:SO!DuQbO'#IxO){QYO'#IxO!EPQWO,5:XOOQ!LS'#DQ'#DQOOQ!LS1G0g1G0gO!EUQWO1G0gO!GgQ!LdO1G0iO!GnQ!LdO1G0iO!JRQ!LdO1G0iO!JYQ!LdO1G0iO!LaQ!LdO1G0iO!LtQ!LdO1G0iO# eQ!LdO1G0iO# lQ!LdO1G0iO#$PQ!LdO1G0iO#$WQ!LdO1G0iO#%{Q!LdO1G0iO#(uQ7^O'#CgO#*pQ7^O1G0yO#,kQ7^O'#IrOOQ!LS1G1P1G1PO#-OQ!LdO,5>eOOQ!LQ-E;w-E;wO#-oQ!LdO1G0iOOQ!LS1G0i1G0iO#/qQ!LdO1G0|O#0bQpO,5;oO#0gQpO,5;pO#0lQpO'#FWO#1QQWO'#FVOOQO'#JU'#JUOOQO'#Hw'#HwO#1VQpO1G1XOOQ!LS1G1X1G1XOOOO1G1b1G1bO#1eQ7^O'#IqO#1oQWO,5;yOLRQYO,5;yOOOO-E;v-E;vOOQ!LS1G1U1G1UOOQ!LS,5;{,5;{O#1tQpO,5;{OOQ!LS,59`,59`OH}QWO'#InOOOS'#Hm'#HmO#1yOSO,59dOOQ!LS,59d,59dO){QYO1G1hO!(eQWO'#H{O#2UQWO,5<aOOQ!LS,5<^,5<^OOQO'#GR'#GROJ^QWO,5<lOOQO'#GT'#GTOJ^QWO,5<nOJ^QWO,5<pOOQO1G1j1G1jO#2aQ`O'#CoO#2tQ`O,5<YO#2{QWO'#JXO5qQWO'#JXO#3ZQWO,5<[OJ^QWO,5<ZO#3`Q`O'#FmO#3mQ`O'#JYO#3wQWO'#JYOH}QWO'#JYO#3|QWO,5<_OOQ!LQ'#Dc'#DcO#4RQWO'#FpO#4^QpO'#FxO!&qQ&jO'#FxO!&qQ&jO'#FzO#4oQWO'#F{O!(jQWO'#GOOOQO'#H}'#H}O#4tQ&jO,5<cOOQ!LS,5<c,5<cO#4{Q&jO'#FxO#5ZQ&jO'#FyO#5cQ&jO'#FyOOQ!LS,5<q,5<qOJ^QWO,5?VOJ^QWO,5?VO#5hQWO'#IOO#5sQWO,5?UOOQ!LS'#Cg'#CgO#6gQ#tO,59gOOQ!LS,59g,59gO#7YQ#tO,5<QO#7{Q#tO,5<SO#8VQWO,5<UOOQ!LS,5<V,5<VO#8[QWO,5<]O#8aQ#tO,5<bOF}QYO1G1iO#8qQWO1G1iOOQQ1G3p1G3pOOQ!LS1G/l1G/lONRQWO1G/lOOQQ1G2^1G2^OH}QWO1G2^O){QYO1G2^OH}QWO1G2^O#8vQWO1G2^O#9UQWO,59[O#:_QWO'#ESOOQ!LQ,5?R,5?RO#:iQ!LYO,5?ROOQQ1G.r1G.rO:^QWO1G.rO!,lQpO1G.rO!,tQ#tO1G.rO#:wQWO1G0^O#:|QWO'#CgO#;XQWO'#JaO#;aQWO,5=`O#;fQWO'#JaO#;kQWO'#JaO#;pQWO'#IWO#<OQWO,5?zO#<WQbO1G1ROOQ!LS1G1T1G1TO5qQWO1G2yO#<_QWO1G2yO#<dQWO1G2yO#<iQWO1G2yOOQQ1G2y1G2yO#<nQ#tO1G2^O7VQWO'#JOO7VQWO'#E]O7VQWO'#IQO#=PQ!LYO,5?wOOQQ1G2b1G2bO!/mQWO1G2hOH}QWO1G2eO#=[QWO1G2eOOQQ1G2f1G2fOH}QWO1G2fO#=aQWO1G2fO#=iQ&jO'#GdOOQQ1G2h1G2hO!&qQ&jO'#ISO!/rQ`O1G2kOOQQ1G2k1G2kOOQQ,5=U,5=UO#=qQ#tO,5=WO5qQWO,5=WO#4oQWO,5=ZO5_QWO,5=ZO!,lQpO,5=ZO!,tQ#tO,5=ZO5vQ#tO,5=ZO#>SQWO'#J_O#>_QWO,5=[OOQQ1G.i1G.iO#>dQ!LYO1G.iO#>oQWO1G.iO!(ZQWO1G.iO5iQ!LYO1G.iO#>tQbO,5?|O#?OQWO,5?|O#?ZQYO,5=cO#?bQWO,5=cO7VQWO,5?|OOQQ1G2{1G2{O`QYO1G2{OOQQ1G3R1G3ROOQQ1G3T1G3TO:XQWO1G3VO#?gQYO1G3XO#CbQYO'#HWOOQQ1G3[1G3[O:^QWO1G3bO#CoQWO1G3bO5iQ!LYO1G3fOOQQ1G3h1G3hOOQ!LQ'#Ft'#FtO5iQ!LYO1G3jO5iQ!LYO1G3lOOOS1G4y1G4yO#CwQ`O,5<PO#DPQbO1G3uO#DZQWO1G4zO#DcQWO1G5UO#DkQWO,5?bOLRQYO,5:wO7VQWO,5:wO:^QWO,59yOLRQYO,59yO!,lQpO,59yO#DpQ7^O,59yOOQO,5:w,5:wO#DzQ&jO'#HpO#EbQWO,5?aOOQ!LS1G/d1G/dO#EjQ&jO'#HuO#FOQWO,5?lOOQ!LQ1G0b1G0bO!:eQ&jO,59yO#FWQbO1G5VOOQO,5>`,5>`O7VQWO,5>`OOQO-E;r-E;rOOQ!LQ'#EO'#EOO#FbQ!LrO'#EPO!?bQ&jO'#DyOOQO'#Hs'#HsO#F|Q&jO,5:dOOQ!LS,5:d,5:dO#GTQ&jO'#DyO#GfQ&jO'#DyO#GmQ&jO'#EUO#GpQ&jO'#EPO#G}Q&jO'#EPO!?bQ&jO'#EPO#HbQWO1G/{O#HgQ`O1G/{OOQ!LS1G/{1G/{O){QYO1G/{OH}QWO1G/{OOQ!LS1G0]1G0]O:^QWO1G0]O!,lQpO1G0]O!,tQ#tO1G0]O#HnQ!LdO1G5SO){QYO1G5SO#IOQ!LYO1G5SO#IaQWO1G5RO7VQWO,5>bOOQO,5>b,5>bO#IiQWO,5>bOOQO-E;t-E;tO#IaQWO1G5RO#IwQ!LdO,59gO#KvQ!LdO,5<QO#MxQ!LdO,5<SO$ zQ!LdO,5<bOOQ!LS7+%x7+%xO$$SQ!LdO7+%xO$$sQWO'#HqO$$}QWO,5?cOOQ!LS1G/n1G/nO$%VQYO'#HrO$%dQWO,5?dO$%lQbO,5?dOOQ!LS1G/s1G/sOOQ!LS7+&R7+&RO$%vQ7^O,5:YO){QYO7+&eO$&QQ7^O,5:QOOQO1G1Z1G1ZOOQO1G1[1G1[O$&_QMhO,5;rOLRQYO,5;qOOQO-E;u-E;uOOQ!LS7+&s7+&sOOOO7+&|7+&|OOOO1G1e1G1eO$&jQWO1G1eOOQ!LS1G1g1G1gO$&oQ`O,5?YOOOS-E;k-E;kOOQ!LS1G/O1G/OO$&vQ!LdO7+'SOOQ!LS,5>g,5>gO$'gQWO,5>gOOQ!LS1G1{1G1{P$'lQWO'#H{POQ!LS-E;y-E;yO$(]Q#tO1G2WO$)OQ#tO1G2YO$)YQ#tO1G2[OOQ!LS1G1t1G1tO$)aQWO'#HzO$)oQWO,5?sO$)oQWO,5?sO$)wQWO,5?sO$*SQWO,5?sOOQO1G1v1G1vO$*bQ#tO1G1uO$*rQWO'#H|O$+SQWO,5?tOH}QWO,5?tO$+[Q`O,5?tOOQ!LS1G1y1G1yO5iQ!LYO,5<dO5iQ!LYO,5<eO$+fQWO,5<eO#4jQWO,5<eO!,lQpO,5<dO$+kQWO,5<fO5iQ!LYO,5<gO$+fQWO,5<jOOQO-E;{-E;{OOQ!LS1G1}1G1}O!&qQ&jO,5<dO$+sQWO,5<eO!&qQ&jO,5<fO!&qQ&jO,5<eO$,OQ#tO1G4qO$,YQ#tO1G4qOOQO,5>j,5>jOOQO-E;|-E;|O!-lQ&jO,59iO){QYO,59iO$,gQWO1G1pOJ^QWO1G1wO$,lQ!LdO7+'TOOQ!LS7+'T7+'TOF}QYO7+'TOOQ!LS7+%W7+%WO$-]Q`O'#JZO#HbQWO7+'xO$-gQWO7+'xO$-oQ`O7+'xOOQQ7+'x7+'xOH}QWO7+'xO){QYO7+'xOH}QWO7+'xOOQO1G.v1G.vO$-yQ!LbO'#CgO$.ZQ!LbO,5<hO$.xQWO,5<hOOQ!LQ1G4m1G4mOOQQ7+$^7+$^O:^QWO7+$^O!,lQpO7+$^OF}QYO7+%xO$.}QWO'#IVO$/]QWO,5?{OOQO1G2z1G2zO5qQWO,5?{O$/]QWO,5?{O$/eQWO,5?{OOQO,5>r,5>rOOQO-E<U-E<UOOQ!LS7+&m7+&mO$/jQWO7+(eO5iQ!LYO7+(eO5qQWO7+(eO$/oQWO7+(eO$/tQWO7+'xOOQ!LQ,5>l,5>lOOQ!LQ-E<O-E<OOOQQ7+(S7+(SO$0SQ!LbO7+(POH}QWO7+(PO$0^Q`O7+(QOOQQ7+(Q7+(QOH}QWO7+(QO$0eQWO'#J^O$0pQWO,5=OOOQO,5>n,5>nOOQO-E<Q-E<QOOQQ7+(V7+(VO$1jQ&jO'#GmOOQQ1G2r1G2rOH}QWO1G2rO){QYO1G2rOH}QWO1G2rO$1qQWO1G2rO$2PQ#tO1G2rO5iQ!LYO1G2uO#4oQWO1G2uO5_QWO1G2uO!,lQpO1G2uO!,tQ#tO1G2uO$2bQWO'#IUO$2mQWO,5?yO$2uQ&jO,5?yOOQ!LQ1G2v1G2vOOQQ7+$T7+$TO$2zQWO7+$TO5iQ!LYO7+$TO$3PQWO7+$TO){QYO1G5hO){QYO1G5iO$3UQYO1G2}O$3]QWO1G2}O$3bQYO1G2}O$3iQ!LYO1G5hOOQQ7+(g7+(gO5iQ!LYO7+(qO`QYO7+(sOOQQ'#Jd'#JdOOQQ'#IX'#IXO$3sQYO,5=rOOQQ,5=r,5=rO){QYO'#HXO$4QQWO'#HZOOQQ7+(|7+(|O$4VQYO7+(|O7VQWO7+(|OOQQ7+)Q7+)QOOQQ7+)U7+)UOOQQ7+)W7+)WOOQO1G4|1G4|O$8TQ7^O1G0cO$8_QWO1G0cOOQO1G/e1G/eO$8jQ7^O1G/eO:^QWO1G/eOLRQYO'#D_OOQO,5>[,5>[OOQO-E;n-E;nOOQO,5>a,5>aOOQO-E;s-E;sO!,lQpO1G/eOOQO1G3z1G3zO:^QWO,5:eOOQO,5:k,5:kO){QYO,5:kO$8tQ!LYO,5:kO$9PQ!LYO,5:kO!,lQpO,5:eOOQO-E;q-E;qOOQ!LS1G0O1G0OO!?bQ&jO,5:eO$9_Q&jO,5:eO$9pQ!LrO,5:kO$:[Q&jO,5:eO!?bQ&jO,5:kOOQO,5:p,5:pO$:cQ&jO,5:kO$:pQ!LYO,5:kOOQ!LS7+%g7+%gO#HbQWO7+%gO#HgQ`O7+%gOOQ!LS7+%w7+%wO:^QWO7+%wO!,lQpO7+%wO$;UQ!LdO7+*nO){QYO7+*nOOQO1G3|1G3|O7VQWO1G3|O$;fQWO7+*mO$;nQ!LdO1G2WO$=pQ!LdO1G2YO$?rQ!LdO1G1uO$AzQ#tO,5>]OOQO-E;o-E;oO$BUQbO,5>^O){QYO,5>^OOQO-E;p-E;pO$B`QWO1G5OO$BhQ7^O1G0^O$DoQ7^O1G0iO$DvQ7^O1G0iO$FwQ7^O1G0iO$GOQ7^O1G0iO$HsQ7^O1G0iO$IWQ7^O1G0iO$KeQ7^O1G0iO$KlQ7^O1G0iO$MmQ7^O1G0iO$MtQ7^O1G0iO% iQ7^O1G0iO% |Q!LdO<<JPO%!mQ7^O1G0iO%$]Q7^O'#IlO%&YQ7^O1G0|OLRQYO'#FYOOQO'#JV'#JVOOQO1G1^1G1^O%&gQWO1G1]O%&lQ7^O,5>eOOOO7+'P7+'POOOS1G4t1G4tOOQ!LS1G4R1G4ROJ^QWO7+'vO%&vQWO,5>fO5qQWO,5>fOOQO-E;x-E;xO%'UQWO1G5_O%'UQWO1G5_O%'^QWO1G5_O%'iQ`O,5>hO%'sQWO,5>hOH}QWO,5>hOOQO-E;z-E;zO%'xQ`O1G5`O%(SQWO1G5`OOQO1G2O1G2OOOQO1G2P1G2PO5iQ!LYO1G2PO$+fQWO1G2PO5iQ!LYO1G2OO%([QWO1G2QOH}QWO1G2QOOQO1G2R1G2RO5iQ!LYO1G2UO!,lQpO1G2OO#4jQWO1G2PO%(aQWO1G2QO%(iQWO1G2POJ^QWO7+*]OOQ!LS1G/T1G/TO%(tQWO1G/TOOQ!LS7+'[7+'[O%(yQ#tO7+'cO%)ZQ!LdO<<JoOOQ!LS<<Jo<<JoOH}QWO'#IPO%)zQWO,5?uOOQQ<<Kd<<KdOH}QWO<<KdO#HbQWO<<KdO%*SQWO<<KdO%*[Q`O<<KdOH}QWO1G2SOOQQ<<Gx<<GxO:^QWO<<GxO%*fQ!LdO<<IdOOQ!LS<<Id<<IdOOQO,5>q,5>qO%+VQWO,5>qO#;kQWO,5>qOOQO-E<T-E<TO%+[QWO1G5gO%+[QWO1G5gO5qQWO1G5gO%+dQWO<<LPOOQQ<<LP<<LPO%+iQWO<<LPO5iQ!LYO<<LPO){QYO<<KdOH}QWO<<KdOOQQ<<Kk<<KkO$0SQ!LbO<<KkOOQQ<<Kl<<KlO$0^Q`O<<KlO%+nQ&jO'#IRO%+yQWO,5?xOLRQYO,5?xOOQQ1G2j1G2jO#FbQ!LrO'#EPO!?bQ&jO'#GnOOQO'#IT'#ITO%,RQ&jO,5=XOOQQ,5=X,5=XO%,YQ&jO'#EPO%,eQ&jO'#EPO%,|Q&jO'#EPO%-WQ&jO'#GnO%-iQWO7+(^O%-nQWO7+(^O%-vQ`O7+(^OOQQ7+(^7+(^OH}QWO7+(^O){QYO7+(^OH}QWO7+(^O%.QQWO7+(^OOQQ7+(a7+(aO5iQ!LYO7+(aO#4oQWO7+(aO5_QWO7+(aO!,lQpO7+(aO%.`QWO,5>pOOQO-E<S-E<SOOQO'#Gq'#GqO%.kQWO1G5eO5iQ!LYO<<GoOOQQ<<Go<<GoO%.sQWO<<GoO%.xQWO7++SO%.}QWO7++TOOQQ7+(i7+(iO%/SQWO7+(iO%/XQYO7+(iO%/`QWO7+(iO){QYO7++SO){QYO7++TOOQQ<<L]<<L]OOQQ<<L_<<L_OOQQ-E<V-E<VOOQQ1G3^1G3^O%/eQWO,5=sOOQQ,5=u,5=uO:^QWO<<LhO%/jQWO<<LhOLRQYO7+%}OOQO7+%P7+%PO%/oQ7^O1G5VO:^QWO7+%POOQO1G0P1G0PO%/yQ!LdO1G0VOOQO1G0V1G0VO){QYO1G0VO%0TQ!LYO1G0VO:^QWO1G0PO!,lQpO1G0PO!?bQ&jO1G0PO%0`Q!LYO1G0VO%0nQ&jO1G0PO%1PQ!LYO1G0VO%1eQ!LrO1G0VO%1oQ&jO1G0PO!?bQ&jO1G0VOOQ!LS<<IR<<IROOQ!LS<<Ic<<IcO:^QWO<<IcO%1vQ!LdO<<NYOOQO7+)h7+)hO%2WQ!LdO7+'cO%4`QbO1G3xO%4jQ7^O7+%xO%4wQ7^O,59gO%6tQ7^O,5<QO%8qQ7^O,5<SO%:nQ7^O,5<bO%<^Q7^O7+'SO%<kQ7^O7+'TO%<xQWO,5;tOOQO7+&w7+&wO%<}Q#tO<<KbOOQO1G4Q1G4QO%=_QWO1G4QO%=jQWO1G4QO%=xQWO7+*yO%=xQWO7+*yOH}QWO1G4SO%>QQ`O1G4SO%>[QWO7+*zOOQO7+'k7+'kO5iQ!LYO7+'kOOQO7+'j7+'jO$+fQWO7+'lO%>dQ`O7+'lOOQO7+'p7+'pO5iQ!LYO7+'jO$+fQWO7+'kO%>kQWO7+'lOH}QWO7+'lO#4jQWO7+'kO%>pQ#tO<<MwOOQ!LS7+$o7+$oO%>zQ`O,5>kOOQO-E;}-E;}O#HbQWOANAOOOQQANAOANAOOH}QWOANAOO%?UQ!LbO7+'nOOQQAN=dAN=dO5qQWO1G4]OOQO1G4]1G4]O%?cQWO1G4]O%?hQWO7++RO%?hQWO7++RO5iQ!LYOANAkO%?pQWOANAkOOQQANAkANAkO%?uQWOANAOO%?}Q`OANAOOOQQANAVANAVOOQQANAWANAWO%@XQWO,5>mOOQO-E<P-E<PO%@dQ7^O1G5dO#4oQWO,5=YO5_QWO,5=YO!,lQpO,5=YOOQO-E<R-E<ROOQQ1G2s1G2sO$9pQ!LrO,5:kO!?bQ&jO,5=YO%@nQ&jO,5=YO%APQ&jO,5:kOOQQ<<Kx<<KxOH}QWO<<KxO%-iQWO<<KxO%AZQWO<<KxO%AcQ`O<<KxO){QYO<<KxOH}QWO<<KxOOQQ<<K{<<K{O5iQ!LYO<<K{O#4oQWO<<K{O5_QWO<<K{O%AmQ&jO1G4[O%ArQWO7++POOQQAN=ZAN=ZO5iQ!LYOAN=ZOOQQ<<Nn<<NnOOQQ<<No<<NoOOQQ<<LT<<LTO%AzQWO<<LTO%BPQYO<<LTO%BWQWO<<NnO%B]QWO<<NoOOQQ1G3_1G3_OOQQANBSANBSO:^QWOANBSO%BbQ7^O<<IiOOQO<<Hk<<HkOOQO7+%q7+%qO%/yQ!LdO7+%qO){QYO7+%qOOQO7+%k7+%kO:^QWO7+%kO!,lQpO7+%kO%BlQ!LYO7+%qO!?bQ&jO7+%kO%BwQ!LYO7+%qO%CVQ&jO7+%kO%ChQ!LYO7+%qOOQ!LSAN>}AN>}O%C|Q!LdO<<KbO%FUQ7^O<<JPO%FcQ7^O1G1uO%HRQ7^O1G2WO%JOQ7^O1G2YO%K{Q7^O<<JoO%LYQ7^O<<IdOOQO1G1`1G1`OOQO7+)l7+)lO%LgQWO7+)lO%LrQWO<<NeO%LzQ`O7+)nOOQO<<KV<<KVO5iQ!LYO<<KWO$+fQWO<<KWOOQO<<KU<<KUO5iQ!LYO<<KVO%MUQ`O<<KWO$+fQWO<<KVOOQQG26jG26jO#HbQWOG26jOOQO7+)w7+)wO5qQWO7+)wO%M]QWO<<NmOOQQG27VG27VO5iQ!LYOG27VOH}QWOG26jOLRQYO1G4XO%MeQWO7++OO5iQ!LYO1G2tO#4oQWO1G2tO5_QWO1G2tO!,lQpO1G2tO!?bQ&jO1G2tO%1eQ!LrO1G0VO%MmQ&jO1G2tO%-iQWOANAdOOQQANAdANAdOH}QWOANAdO%NOQWOANAdO%NWQ`OANAdOOQQANAgANAgO5iQ!LYOANAgO#4oQWOANAgOOQO'#Gr'#GrOOQO7+)v7+)vOOQQG22uG22uOOQQANAoANAoO%NbQWOANAoOOQQANDYANDYOOQQANDZANDZO%NgQYOG27nOOQO<<I]<<I]O%/yQ!LdO<<I]OOQO<<IV<<IVO:^QWO<<IVO){QYO<<I]O!,lQpO<<IVO&$eQ!LYO<<I]O!?bQ&jO<<IVO&$pQ!LYO<<I]O&%OQ7^O7+'cOOQO<<MW<<MWOOQOAN@rAN@rO5iQ!LYOAN@rOOQOAN@qAN@qO$+fQWOAN@rO5iQ!LYOAN@qOOQQLD,ULD,UOOQO<<Mc<<McOOQQLD,qLD,qO#HbQWOLD,UO&&nQ7^O7+)sOOQO7+(`7+(`O5iQ!LYO7+(`O#4oQWO7+(`O5_QWO7+(`O!,lQpO7+(`O!?bQ&jO7+(`OOQQG27OG27OO%-iQWOG27OOH}QWOG27OOOQQG27RG27RO5iQ!LYOG27ROOQQG27ZG27ZO:^QWOLD-YOOQOAN>wAN>wOOQOAN>qAN>qO%/yQ!LdOAN>wO:^QWOAN>qO){QYOAN>wO!,lQpOAN>qO&&xQ!LYOAN>wO&'TQ7^O<<KbOOQOG26^G26^O5iQ!LYOG26^OOQOG26]G26]OOQQ!$( p!$( pOOQO<<Kz<<KzO5iQ!LYO<<KzO#4oQWO<<KzO5_QWO<<KzO!,lQpO<<KzOOQQLD,jLD,jO%-iQWOLD,jOOQQLD,mLD,mOOQQ!$(!t!$(!tOOQOG24cG24cOOQOG24]G24]O%/yQ!LdOG24cO:^QWOG24]O){QYOG24cOOQOLD+xLD+xOOQOANAfANAfO5iQ!LYOANAfO#4oQWOANAfO5_QWOANAfOOQQ!$(!U!$(!UOOQOLD)}LD)}OOQOLD)wLD)wO%/yQ!LdOLD)}OOQOG27QG27QO5iQ!LYOG27QO#4oQWOG27QOOQO!$'Mi!$'MiOOQOLD,lLD,lO5iQ!LYOLD,lOOQO!$(!W!$(!WOLRQYO'#DnO&(sQbO'#IqOLRQYO'#DfO&(zQ!LdO'#CgO&)eQbO'#CgO&)uQYO,5:rOLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO'#HyO&+uQWO,5<PO&-XQWO,5:}OLRQYO,5;bO!(ZQWO'#C{O!(ZQWO'#C{OH}QWO'#FfO&+}QWO'#FfOH}QWO'#FhO&+}QWO'#FhOH}QWO'#FvO&+}QWO'#FvOLRQYO,5?kO&)uQYO1G0^O&-`Q7^O'#CgOLRQYO1G1hOH}QWO,5<lO&+}QWO,5<lOH}QWO,5<nO&+}QWO,5<nOH}QWO,5<ZO&+}QWO,5<ZO&)uQYO1G1iOLRQYO7+&eOH}QWO1G1wO&+}QWO1G1wO&)uQYO7+'TO&)uQYO7+%xOH}QWO7+'vO&+}QWO7+'vO&-jQWO'#EWO&-oQWO'#EWO&-wQWO'#EvO&-|QWO'#EcO&.RQWO'#JPO&.^QWO'#I}O&.iQWO,5:rO&.nQ#tO,5;|O&.uQWO'#FoO&.zQWO'#FoO&/PQWO,5;}O&/XQWO,5:rO&/aQ7^O1G0yO&/hQWO,5<]O&/mQWO,5<]O&/rQWO1G1iO&/wQWO1G0^O&/|Q#tO1G2[O&0TQ#tO1G2[O4QQWO'#FdO5_QWO'#FcOBqQWO'#EVOLRQYO,5;_O!(jQWO'#FqO!(jQWO'#FqOJ^QWO,5<pOJ^QWO,5<p", +- stateData: "&1Q~O'TOS'UOSSOSTOS~OPTOQTOWyO]cO^hOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#`sO#ppO#t^O${qO$}tO%PrO%QrO%TuO%VvO%YwO%ZwO%]xO%jzO%p{O%r|O%t}O%v!OO%y!PO&P!QO&T!RO&V!SO&X!TO&Z!UO&]!VO'WPO'aQO'mYO'zaO~OPZXYZX^ZXiZXrZXsZXuZX}ZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'RZX'aZX'nZX'uZX'vZX~O!X$hX~P$zO'O!XO'P!WO'Q!ZO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W![O'aQO'mYO'zaO~O|!`O}!]Oz'hPz'rP~P'dO!O!lO~P`OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W9VO'aQO'mYO'zaO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'aQO'mYO'zaO~O|!qO#Q!tO#R!qO'W9WO!_'oP~P+{O#S!uO~O!X!vO#S!uO~OP#]OY#cOi#QOr!zOs!zOu!{O}#aO!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~O^'eX'R'eX!_'eXz'eX!P'eX$|'eX!X'eX~P.jO!w#dO#k#dOP'fXY'fX^'fXi'fXr'fXs'fXu'fX}'fX!]'fX!^'fX!`'fX!f'fX#W'fX#X'fX#Y'fX#Z'fX#['fX#]'fX#^'fX#a'fX#c'fX#e'fX#f'fX'a'fX'n'fX'u'fX'v'fX~O#_'fX'R'fXz'fX!_'fX'c'fX!P'fX$|'fX!X'fX~P0zO!w#dO~O#v#eO#}#iO~O!P#jO#t^O$Q#kO$S#mO~O]#pOg#}Oi#qOj#pOk#pOn$OOp$POu#wO!P#xO!Z$UO!`#uO#R$VO#p$SO$Z$QO$]$RO$`$TO'W#oO'a#rO'['^P~O!`$WO~O!X$YO~O^$ZO'R$ZO~O'W$_O~O!`$WO'W$_O'X$aO']$bO~Ob$hO!`$WO'W$_O~O#_#SO~O]$qOr$mO!P$jO!`$lO$}$pO'W$_O'X$aO[(SP~O!j$rO~Ou$sO!P$tO'W$_O~Ou$sO!P$tO%V$xO'W$_O~O'W$yO~O#`sO$}tO%PrO%QrO%TuO%VvO%YwO%ZwO~Oa%SOb%RO!j%PO${%QO%_%OO~P7uOa%VObmO!P%UO!jlO#`sO${qO%PrO%QrO%TuO%VvO%YwO%ZwO%]xO~O_%YO!w%]O$}%WO'X$aO~P8tO!`%^O!c%bO~O!`%cO~O!PSO~O^$ZO&}%kO'R$ZO~O^$ZO&}%nO'R$ZO~O^$ZO&}%pO'R$ZO~O'O!XO'P!WO'Q%tO~OPZXYZXiZXrZXsZXuZX}ZX}cX!]ZX!^ZX!`ZX!fZX!wZX!wcX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'aZX'nZX'uZX'vZX~OzZXzcX~P;aO|%vOz&cX}&cX~P){O}!]Oz'hX~OP#]OY#cOi#QOr!zOs!zOu!{O}!]O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~Oz'hX~P>WOz%{O~Ou&OO!S&YO!T&RO!U&RO'X$aO~O]&POj&PO|&SO'd%|O!O'iP!O'tP~P@ZOz'qX}'qX!X'qX!_'qX'n'qX~O!w'qX#S!{X!O'qX~PASO!w&ZOz'sX}'sX~O}&[Oz'rX~Oz&^O~O!w#dO~PASOR&bO!P&_O!k&aO'W$_O~Ob&gO!`$WO'W$_O~Or$mO!`$lO~O!O&hO~P`Or!zOs!zOu!{O!^!xO!`!yO'aQOP!baY!bai!ba}!ba!]!ba!f!ba#W!ba#X!ba#Y!ba#Z!ba#[!ba#]!ba#^!ba#_!ba#a!ba#c!ba#e!ba#f!ba'n!ba'u!ba'v!ba~O^!ba'R!baz!ba!_!ba'c!ba!P!ba$|!ba!X!ba~PC]O!_&iO~O!X!vO!w&kO'n&jO}'pX^'pX'R'pX~O!_'pX~PEuO}&oO!_'oX~O!_&qO~Ou$sO!P$tO#R&rO'W$_O~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W9VO'aQO'mYO'zaO~O]#pOg#}Oi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'W&vO'a#rO~O#S&xO~O]#pOg#}Oi#qOj#pOk#pOn$OOp$POu#wO!P#xO!Z$UO!`#uO#R$VO#p$SO$Z$QO$]$RO$`$TO'W&vO'a#rO~O'['kP~PJ^O|&|O!_'lP~P){O'd'OO'mYO~OP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!P!bO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'W'^O'aQO'mYO'z:hO~O!`!yO~O}#aO^$Xa'R$Xa!_$Xaz$Xa!P$Xa$|$Xa!X$Xa~O#`'eO~PH}O!X'gO!P'wX#s'wX#v'wX#}'wX~Or'hO~PNyOr'hO!P'wX#s'wX#v'wX#}'wX~O!P'jO#s'nO#v'iO#}'oO~O|'rO~PLRO#v#eO#}'uO~Or$aXu$aX!^$aX'n$aX'u$aX'v$aX~OReX}eX!weX'[eX'[$aX~P!!cOj'wO~O'O'yO'P'xO'Q'{O~Or'}Ou(OO'n#ZO'u(QO'v(SO~O'['|O~P!#lO'[(VO~O]#pOg#}Oi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'a#rO~O|(ZO'W(WO!_'{P~P!$ZO#S(]O~O|(aO'W(^Oz'|P~P!$ZO^(jOi(oOu(gO!S(mO!T(fO!U(fO!`(dO!t(nO$s(iO'X$aO'd(cO~O!O(lO~P!&RO!^!xOr'`Xu'`X'n'`X'u'`X'v'`X}'`X!w'`X~O'['`X#i'`X~P!&}OR(rO!w(qO}'_X'['_X~O}(sO'['^X~O'W(uO~O!`(zO~O'W&vO~O!`(dO~Ou$sO|!qO!P$tO#Q!tO#R!qO'W$_O!_'oP~O!X!vO#S)OO~OP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~O^!Ya}!Ya'R!Yaz!Ya!_!Ya'c!Ya!P!Ya$|!Ya!X!Ya~P!)`OR)WO!P&_O!k)VO$|)UO']$bO~O'W$yO'['^P~O!X)ZO!P'ZX^'ZX'R'ZX~O!`$WO']$bO~O!`$WO'W$_O']$bO~O!X!vO#S&xO~O$})gO'W)cO!O(TP~O})hO[(SX~O'd'OO~OY)lO~O[)mO~O!P$jO'W$_O'X$aO[(SP~Ou$sO|)rO!P$tO'W$_Oz'rP~O]&VOj&VO|)sO'd'OO!O'tP~O})tO^(PX'R(PX~O!w)xO']$bO~OR){O!P#xO']$bO~O!P)}O~Or*PO!PSO~O!j*UO~Ob*ZO~O'W(uO!O(RP~Ob$hO~O$}tO'W$yO~P8tOY*aO[*`O~OPTOQTO]cOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#t^O${qO'aQO'mYO'zaO~O!P!bO#p!kO'W9VO~P!0uO[*`O^$ZO'R$ZO~O^*eO#`*gO%P*gO%Q*gO~P){O!`%^O~O%p*lO~O!P*nO~O&Q*qO&R*pOP&OaQ&OaW&Oa]&Oa^&Oaa&Oab&Oag&Oai&Oaj&Oak&Oan&Oap&Oau&Oaw&Oax&Oay&Oa!P&Oa!Z&Oa!`&Oa!c&Oa!d&Oa!e&Oa!f&Oa!g&Oa!j&Oa#`&Oa#p&Oa#t&Oa${&Oa$}&Oa%P&Oa%Q&Oa%T&Oa%V&Oa%Y&Oa%Z&Oa%]&Oa%j&Oa%p&Oa%r&Oa%t&Oa%v&Oa%y&Oa&P&Oa&T&Oa&V&Oa&X&Oa&Z&Oa&]&Oa&|&Oa'W&Oa'a&Oa'm&Oa'z&Oa!O&Oa%w&Oa_&Oa%|&Oa~O'W*tO~O'c*wO~Oz&ca}&ca~P!)`O}!]Oz'ha~Oz'ha~P>WO}&[Oz'ra~O}tX}!VX!OtX!O!VX!XtX!X!VX!`!VX!wtX']!VX~O!X+OO!w*}O}#PX}'jX!O#PX!O'jX!X'jX!`'jX']'jX~O!X+QO!`$WO']$bO}!RX!O!RX~O]%}Oj%}Ou&OO'd(cO~OP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!P!bO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'aQO'mYO'z:hO~O'W9sO~P!:sO}+UO!O'iX~O!O+WO~O!X+OO!w*}O}#PX!O#PX~O}+XO!O'tX~O!O+ZO~O]%}Oj%}Ou&OO'X$aO'd(cO~O!T+[O!U+[O~P!=qOu$sO|+_O!P$tO'W$_Oz&hX}&hX~O^+dO!S+gO!T+cO!U+cO!n+kO!o+iO!p+jO!q+hO!t+lO'X$aO'd(cO'm+aO~O!O+fO~P!>rOR+qO!P&_O!k+pO~O!w+wO}'pa!_'pa^'pa'R'pa~O!X!vO~P!?|O}&oO!_'oa~Ou$sO|+zO!P$tO#Q+|O#R+zO'W$_O}&jX!_&jX~O^!zi}!zi'R!ziz!zi!_!zi'c!zi!P!zi$|!zi!X!zi~P!)`O#S!va}!va!_!va!w!va!P!va^!va'R!vaz!va~P!#lO#S'`XP'`XY'`X^'`Xi'`Xs'`X!]'`X!`'`X!f'`X#W'`X#X'`X#Y'`X#Z'`X#['`X#]'`X#^'`X#_'`X#a'`X#c'`X#e'`X#f'`X'R'`X'a'`X!_'`Xz'`X!P'`X'c'`X$|'`X!X'`X~P!&}O},VO'['kX~P!#lO'[,XO~O},YO!_'lX~P!)`O!_,]O~Oz,^O~OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'aQOY#Vi^#Vii#Vi}#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O#W#Vi~P!EZO#W#OO~P!EZOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO'aQOY#Vi^#Vi}#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~Oi#Vi~P!GuOi#QO~P!GuOP#]Oi#QOr!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO'aQO^#Vi}#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P!JaOY#cO!]#SO#]#SO#^#SO#_#SO~P!JaOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO'aQO^#Vi}#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O'u#Vi~P!MXO'u!|O~P!MXOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO'aQO'u!|O^#Vi}#Vi#e#Vi#f#Vi'R#Vi'n#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O'v#Vi~P# sO'v!}O~P# sOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO'aQO'u!|O'v!}O~O^#Vi}#Vi#f#Vi'R#Vi'n#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~P#$_OPZXYZXiZXrZXsZXuZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'aZX'nZX'uZX'vZX}ZX!OZX~O#iZX~P#&rOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO#e9cO#f9dO'aQO'n#ZO'u!|O'v!}O~O#i,`O~P#(|OP'fXY'fXi'fXr'fXs'fXu'fX!]'fX!^'fX!`'fX!f'fX#W'fX#X'fX#Y'fX#Z'fX#['fX#]'fX#^'fX#a'fX#c'fX#e'fX#f'fX'a'fX'n'fX'u'fX'v'fX}'fX~O!w9hO#k9hO#_'fX#i'fX!O'fX~P#*wO^&ma}&ma'R&ma!_&ma'c&maz&ma!P&ma$|&ma!X&ma~P!)`OP#ViY#Vi^#Vii#Vis#Vi}#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'a#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~P!#lO^#ji}#ji'R#jiz#ji!_#ji'c#ji!P#ji$|#ji!X#ji~P!)`O#v,bO~O#v,cO~O!X'gO!w,dO!P#zX#s#zX#v#zX#}#zX~O|,eO~O!P'jO#s,gO#v'iO#},hO~O}9eO!O'eX~P#(|O!O,iO~O#},kO~O'O'yO'P'xO'Q,nO~O],qOj,qOz,rO~O}cX!XcX!_cX!_$aX'ncX~P!!cO!_,xO~P!#lO},yO!X!vO'n&jO!_'{X~O!_-OO~Oz$aX}$aX!X$hX~P!!cO}-QOz'|X~P!#lO!X-SO~Oz-UO~O|(ZO'W$_O!_'{P~Oi-YO!X!vO!`$WO']$bO'n&jO~O!X)ZO~O!O-`O~P!&RO!T-aO!U-aO'X$aO'd(cO~Ou-cO'd(cO~O!t-dO~O'W$yO}&rX'[&rX~O}(sO'['^a~Or-iOs-iOu-jO'noa'uoa'voa}oa!woa~O'[oa#ioa~P#5{Or'}Ou(OO'n$Ya'u$Ya'v$Ya}$Ya!w$Ya~O'[$Ya#i$Ya~P#6qOr'}Ou(OO'n$[a'u$[a'v$[a}$[a!w$[a~O'[$[a#i$[a~P#7dO]-kO~O#S-lO~O'[$ja}$ja#i$ja!w$ja~P!#lO#S-oO~OR-xO!P&_O!k-wO$|-vO~O'[-yO~O]#pOi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'a#rO~Og-{O'W-zO~P#9ZO!X)ZO!P'Za^'Za'R'Za~O#S.RO~OYZX}cX!OcX~O}.SO!O(TX~O!O.UO~OY.VO~O'W)cO~O!P$jO'W$_O[&zX}&zX~O})hO[(Sa~O!_.[O~P!)`O].^O~OY._O~O[.`O~OR-xO!P&_O!k-wO$|-vO']$bO~O})tO^(Pa'R(Pa~O!w.fO~OR.iO!P#xO~O'd'OO!O(QP~OR.sO!P.oO!k.rO$|.qO']$bO~OY.}O}.{O!O(RX~O!O/OO~O[/QO^$ZO'R$ZO~O]/RO~O#_/TO%n/UO~P0zO!w#dO#_/TO%n/UO~O^/VO~P){O^/XO~O%w/]OP%uiQ%uiW%ui]%ui^%uia%uib%uig%uii%uij%uik%uin%uip%uiu%uiw%uix%uiy%ui!P%ui!Z%ui!`%ui!c%ui!d%ui!e%ui!f%ui!g%ui!j%ui#`%ui#p%ui#t%ui${%ui$}%ui%P%ui%Q%ui%T%ui%V%ui%Y%ui%Z%ui%]%ui%j%ui%p%ui%r%ui%t%ui%v%ui%y%ui&P%ui&T%ui&V%ui&X%ui&Z%ui&]%ui&|%ui'W%ui'a%ui'm%ui'z%ui!O%ui_%ui%|%ui~O_/cO!O/aO%|/bO~P`O!PSO!`/fO~O}#aO'c$Xa~Oz&ci}&ci~P!)`O}!]Oz'hi~O}&[Oz'ri~Oz/jO~O}!Ra!O!Ra~P#(|O]%}Oj%}O|/pO'd(cO}&dX!O&dX~P@ZO}+UO!O'ia~O]&VOj&VO|)sO'd'OO}&iX!O&iX~O}+XO!O'ta~Oz'si}'si~P!)`O^$ZO!X!vO!`$WO!f/{O!w/yO'R$ZO']$bO'n&jO~O!O0OO~P!>rO!T0PO!U0PO'X$aO'd(cO'm+aO~O!S0QO~P#GTO!PSO!S0QO!q0SO!t0TO~P#GTO!S0QO!o0VO!p0VO!q0SO!t0TO~P#GTO!P&_O~O!P&_O~P!#lO}'pi!_'pi^'pi'R'pi~P!)`O!w0`O}'pi!_'pi^'pi'R'pi~O}&oO!_'oi~Ou$sO!P$tO#R0bO'W$_O~O#SoaPoaYoa^oaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa'Roa'aoa!_oazoa!Poa'coa$|oa!Xoa~P#5{O#S$YaP$YaY$Ya^$Yai$Yas$Ya!]$Ya!^$Ya!`$Ya!f$Ya#W$Ya#X$Ya#Y$Ya#Z$Ya#[$Ya#]$Ya#^$Ya#_$Ya#a$Ya#c$Ya#e$Ya#f$Ya'R$Ya'a$Ya!_$Yaz$Ya!P$Ya'c$Ya$|$Ya!X$Ya~P#6qO#S$[aP$[aY$[a^$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a'R$[a'a$[a!_$[az$[a!P$[a'c$[a$|$[a!X$[a~P#7dO#S$jaP$jaY$ja^$jai$jas$ja}$ja!]$ja!^$ja!`$ja!f$ja#W$ja#X$ja#Y$ja#Z$ja#[$ja#]$ja#^$ja#_$ja#a$ja#c$ja#e$ja#f$ja'R$ja'a$ja!_$jaz$ja!P$ja!w$ja'c$ja$|$ja!X$ja~P!#lO^!zq}!zq'R!zqz!zq!_!zq'c!zq!P!zq$|!zq!X!zq~P!)`O}&eX'[&eX~PJ^O},VO'['ka~O|0jO}&fX!_&fX~P){O},YO!_'la~O},YO!_'la~P!)`O#i!ba!O!ba~PC]O#i!Ya}!Ya!O!Ya~P#(|O!P0}O#t^O#{1OO~O!O1SO~O'c1TO~P!#lO^$Uq}$Uq'R$Uqz$Uq!_$Uq'c$Uq!P$Uq$|$Uq!X$Uq~P!)`Oz1UO~O],qOj,qO~Or'}Ou(OO'v(SO'n$ti'u$ti}$ti!w$ti~O'[$ti#i$ti~P$'tOr'}Ou(OO'n$vi'u$vi'v$vi}$vi!w$vi~O'[$vi#i$vi~P$(gO#i1VO~P!#lO|1XO'W$_O}&nX!_&nX~O},yO!_'{a~O},yO!X!vO!_'{a~O},yO!X!vO'n&jO!_'{a~O'[$ci}$ci#i$ci!w$ci~P!#lO|1`O'W(^Oz&pX}&pX~P!$ZO}-QOz'|a~O}-QOz'|a~P!#lO!X!vO~O!X!vO#_1jO~Oi1nO!X!vO'n&jO~O}'_i'['_i~P!#lO!w1qO}'_i'['_i~P!#lO!_1tO~O^$Vq}$Vq'R$Vqz$Vq!_$Vq'c$Vq!P$Vq$|$Vq!X$Vq~P!)`O}1xO!P'}X~P!#lO!P&_O$|1{O~O!P&_O$|1{O~P!#lO!P$aX$qZX^$aX'R$aX~P!!cO$q2POrfXufX!PfX'nfX'ufX'vfX^fX'RfX~O$q2PO~O$}2WO'W)cO}&yX!O&yX~O}.SO!O(Ta~OY2[O~O[2]O~O]2`O~OR2bO!P&_O!k2aO$|1{O~O^$ZO'R$ZO~P!#lO!P#xO~P!#lO}2gO!w2iO!O(QX~O!O2jO~Ou(gO!S2sO!T2lO!U2lO!n2rO!o2qO!p2qO!t2pO'X$aO'd(cO'm+aO~O!O2oO~P$0uOR2zO!P.oO!k2yO$|2xO~OR2zO!P.oO!k2yO$|2xO']$bO~O'W(uO}&xX!O&xX~O}.{O!O(Ra~O'd3TO~O]3VO~O[3XO~O!_3[O~P){O^3^O~O^3^O~P){O#_3`O%n3aO~PEuO_/cO!O3eO%|/bO~P`O!X3gO~O&R3hOP&OqQ&OqW&Oq]&Oq^&Oqa&Oqb&Oqg&Oqi&Oqj&Oqk&Oqn&Oqp&Oqu&Oqw&Oqx&Oqy&Oq!P&Oq!Z&Oq!`&Oq!c&Oq!d&Oq!e&Oq!f&Oq!g&Oq!j&Oq#`&Oq#p&Oq#t&Oq${&Oq$}&Oq%P&Oq%Q&Oq%T&Oq%V&Oq%Y&Oq%Z&Oq%]&Oq%j&Oq%p&Oq%r&Oq%t&Oq%v&Oq%y&Oq&P&Oq&T&Oq&V&Oq&X&Oq&Z&Oq&]&Oq&|&Oq'W&Oq'a&Oq'm&Oq'z&Oq!O&Oq%w&Oq_&Oq%|&Oq~O}#Pi!O#Pi~P#(|O!w3jO}#Pi!O#Pi~O}!Ri!O!Ri~P#(|O^$ZO!w3qO'R$ZO~O^$ZO!X!vO!w3qO'R$ZO~O!T3uO!U3uO'X$aO'd(cO'm+aO~O^$ZO!X!vO!`$WO!f3vO!w3qO'R$ZO']$bO'n&jO~O!S3wO~P$9_O!S3wO!q3zO!t3{O~P$9_O^$ZO!X!vO!f3vO!w3qO'R$ZO'n&jO~O}'pq!_'pq^'pq'R'pq~P!)`O}&oO!_'oq~O#S$tiP$tiY$ti^$tii$tis$ti!]$ti!^$ti!`$ti!f$ti#W$ti#X$ti#Y$ti#Z$ti#[$ti#]$ti#^$ti#_$ti#a$ti#c$ti#e$ti#f$ti'R$ti'a$ti!_$tiz$ti!P$ti'c$ti$|$ti!X$ti~P$'tO#S$viP$viY$vi^$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi'R$vi'a$vi!_$viz$vi!P$vi'c$vi$|$vi!X$vi~P$(gO#S$ciP$ciY$ci^$cii$cis$ci}$ci!]$ci!^$ci!`$ci!f$ci#W$ci#X$ci#Y$ci#Z$ci#[$ci#]$ci#^$ci#_$ci#a$ci#c$ci#e$ci#f$ci'R$ci'a$ci!_$ciz$ci!P$ci!w$ci'c$ci$|$ci!X$ci~P!#lO}&ea'[&ea~P!#lO}&fa!_&fa~P!)`O},YO!_'li~O#i!zi}!zi!O!zi~P#(|OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'aQOY#Vii#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~O#W#Vi~P$BuO#W9YO~P$BuOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO'aQOY#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~Oi#Vi~P$D}Oi9[O~P$D}OP#]Oi9[Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O'aQO#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P$GVOY9gO!]9^O#]9^O#^9^O#_9^O~P$GVOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O'aQO#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'v#Vi}#Vi!O#Vi~O'u#Vi~P$IkO'u!|O~P$IkOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO'aQO'u!|O#e#Vi#f#Vi#i#Vi'n#Vi}#Vi!O#Vi~O'v#Vi~P$KsO'v!}O~P$KsOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO#e9cO'aQO'u!|O'v!}O~O#f#Vi#i#Vi'n#Vi}#Vi!O#Vi~P$M{O^#gy}#gy'R#gyz#gy!_#gy'c#gy!P#gy$|#gy!X#gy~P!)`OP#ViY#Vii#Vis#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'a#Vi}#Vi!O#Vi~P!#lO!^!xOP'`XY'`Xi'`Xr'`Xs'`Xu'`X!]'`X!`'`X!f'`X#W'`X#X'`X#Y'`X#Z'`X#['`X#]'`X#^'`X#_'`X#a'`X#c'`X#e'`X#f'`X#i'`X'a'`X'n'`X'u'`X'v'`X}'`X!O'`X~O#i#ji}#ji!O#ji~P#(|O!O4]O~O}&ma!O&ma~P#(|O!X!vO'n&jO}&na!_&na~O},yO!_'{i~O},yO!X!vO!_'{i~Oz&pa}&pa~P!#lO!X4dO~O}-QOz'|i~P!#lO}-QOz'|i~Oz4jO~O!X!vO#_4pO~Oi4qO!X!vO'n&jO~Oz4sO~O'[$eq}$eq#i$eq!w$eq~P!#lO^$Vy}$Vy'R$Vyz$Vy!_$Vy'c$Vy!P$Vy$|$Vy!X$Vy~P!)`O}1xO!P'}a~O!P&_O$|4xO~O!P&_O$|4xO~P!#lO^!zy}!zy'R!zyz!zy!_!zy'c!zy!P!zy$|!zy!X!zy~P!)`OY4{O~O}.SO!O(Ti~O]5QO~O[5RO~O'd'OO}&uX!O&uX~O}2gO!O(Qa~O!O5`O~P$0uOu-cO'd(cO'm+aO~O!S5cO!T5bO!U5bO!t0TO'X$aO'd(cO'm+aO~O!o5dO!p5dO~P%,eO!T5bO!U5bO'X$aO'd(cO'm+aO~O!P.oO~O!P.oO$|5fO~O!P.oO$|5fO~P!#lOR5kO!P.oO!k5jO$|5fO~OY5pO}&xa!O&xa~O}.{O!O(Ri~O]5sO~O!_5tO~O!_5uO~O!_5vO~O!_5vO~P){O^5xO~O!X5{O~O!_5}O~O}'si!O'si~P#(|O^$ZO'R$ZO~P!)`O^$ZO!w6SO'R$ZO~O^$ZO!X!vO!w6SO'R$ZO~O!T6XO!U6XO'X$aO'd(cO'm+aO~O^$ZO!X!vO!f6YO!w6SO'R$ZO'n&jO~O!`$WO']$bO~P%1PO!S6ZO~P%0nO}'py!_'py^'py'R'py~P!)`O#S$eqP$eqY$eq^$eqi$eqs$eq}$eq!]$eq!^$eq!`$eq!f$eq#W$eq#X$eq#Y$eq#Z$eq#[$eq#]$eq#^$eq#_$eq#a$eq#c$eq#e$eq#f$eq'R$eq'a$eq!_$eqz$eq!P$eq!w$eq'c$eq$|$eq!X$eq~P!#lO}&fi!_&fi~P!)`O#i!zq}!zq!O!zq~P#(|Or-iOs-iOu-jOPoaYoaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa#ioa'aoa'noa'uoa'voa}oa!Ooa~Or'}Ou(OOP$YaY$Yai$Yas$Ya!]$Ya!^$Ya!`$Ya!f$Ya#W$Ya#X$Ya#Y$Ya#Z$Ya#[$Ya#]$Ya#^$Ya#_$Ya#a$Ya#c$Ya#e$Ya#f$Ya#i$Ya'a$Ya'n$Ya'u$Ya'v$Ya}$Ya!O$Ya~Or'}Ou(OOP$[aY$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a#i$[a'a$[a'n$[a'u$[a'v$[a}$[a!O$[a~OP$jaY$jai$jas$ja!]$ja!^$ja!`$ja!f$ja#W$ja#X$ja#Y$ja#Z$ja#[$ja#]$ja#^$ja#_$ja#a$ja#c$ja#e$ja#f$ja#i$ja'a$ja}$ja!O$ja~P!#lO#i$Uq}$Uq!O$Uq~P#(|O#i$Vq}$Vq!O$Vq~P#(|O!O6eO~O'[$xy}$xy#i$xy!w$xy~P!#lO!X!vO}&ni!_&ni~O!X!vO'n&jO}&ni!_&ni~O},yO!_'{q~Oz&pi}&pi~P!#lO}-QOz'|q~Oz6lO~P!#lOz6lO~O}'_y'['_y~P!#lO}&sa!P&sa~P!#lO!P$pq^$pq'R$pq~P!#lOY6tO~O}.SO!O(Tq~O]6wO~O!P&_O$|6xO~O!P&_O$|6xO~P!#lO!w6yO}&ua!O&ua~O}2gO!O(Qi~P#(|O!T7PO!U7PO'X$aO'd(cO'm+aO~O!S7RO!t3{O~P%@nO!P.oO$|7UO~O!P.oO$|7UO~P!#lO'd7[O~O}.{O!O(Rq~O!_7_O~O!_7_O~P){O!_7aO~O!_7bO~O}#Py!O#Py~P#(|O^$ZO!w7hO'R$ZO~O^$ZO!X!vO!w7hO'R$ZO~O!T7kO!U7kO'X$aO'd(cO'm+aO~O^$ZO!X!vO!f7lO!w7hO'R$ZO'n&jO~O#S$xyP$xyY$xy^$xyi$xys$xy}$xy!]$xy!^$xy!`$xy!f$xy#W$xy#X$xy#Y$xy#Z$xy#[$xy#]$xy#^$xy#_$xy#a$xy#c$xy#e$xy#f$xy'R$xy'a$xy!_$xyz$xy!P$xy!w$xy'c$xy$|$xy!X$xy~P!#lO#i#gy}#gy!O#gy~P#(|OP$ciY$cii$cis$ci!]$ci!^$ci!`$ci!f$ci#W$ci#X$ci#Y$ci#Z$ci#[$ci#]$ci#^$ci#_$ci#a$ci#c$ci#e$ci#f$ci#i$ci'a$ci}$ci!O$ci~P!#lOr'}Ou(OO'v(SOP$tiY$tii$tis$ti!]$ti!^$ti!`$ti!f$ti#W$ti#X$ti#Y$ti#Z$ti#[$ti#]$ti#^$ti#_$ti#a$ti#c$ti#e$ti#f$ti#i$ti'a$ti'n$ti'u$ti}$ti!O$ti~Or'}Ou(OOP$viY$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi#i$vi'a$vi'n$vi'u$vi'v$vi}$vi!O$vi~O#i$Vy}$Vy!O$Vy~P#(|O#i!zy}!zy!O!zy~P#(|O!X!vO}&nq!_&nq~O},yO!_'{y~Oz&pq}&pq~P!#lOz7rO~P!#lO}.SO!O(Ty~O}2gO!O(Qq~O!T8OO!U8OO'X$aO'd(cO'm+aO~O!P.oO$|8RO~O!P.oO$|8RO~P!#lO!_8UO~O&R8VOP&O!ZQ&O!ZW&O!Z]&O!Z^&O!Za&O!Zb&O!Zg&O!Zi&O!Zj&O!Zk&O!Zn&O!Zp&O!Zu&O!Zw&O!Zx&O!Zy&O!Z!P&O!Z!Z&O!Z!`&O!Z!c&O!Z!d&O!Z!e&O!Z!f&O!Z!g&O!Z!j&O!Z#`&O!Z#p&O!Z#t&O!Z${&O!Z$}&O!Z%P&O!Z%Q&O!Z%T&O!Z%V&O!Z%Y&O!Z%Z&O!Z%]&O!Z%j&O!Z%p&O!Z%r&O!Z%t&O!Z%v&O!Z%y&O!Z&P&O!Z&T&O!Z&V&O!Z&X&O!Z&Z&O!Z&]&O!Z&|&O!Z'W&O!Z'a&O!Z'm&O!Z'z&O!Z!O&O!Z%w&O!Z_&O!Z%|&O!Z~O^$ZO!w8[O'R$ZO~O^$ZO!X!vO!w8[O'R$ZO~OP$eqY$eqi$eqs$eq!]$eq!^$eq!`$eq!f$eq#W$eq#X$eq#Y$eq#Z$eq#[$eq#]$eq#^$eq#_$eq#a$eq#c$eq#e$eq#f$eq#i$eq'a$eq}$eq!O$eq~P!#lO}&uq!O&uq~P#(|O^$ZO!w8qO'R$ZO~OP$xyY$xyi$xys$xy!]$xy!^$xy!`$xy!f$xy#W$xy#X$xy#Y$xy#Z$xy#[$xy#]$xy#^$xy#_$xy#a$xy#c$xy#e$xy#f$xy#i$xy'a$xy}$xy!O$xy~P!#lO'c'eX~P.jO'cZXzZX!_ZX%nZX!PZX$|ZX!XZX~P$zO!XcX!_ZX!_cX'ncX~P;aOP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!PSO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'W'^O'aQO'mYO'z:hO~O}9eO!O$Xa~O]#pOg#}Oi#qOj#pOk#pOn$OOp9jOu#wO!P#xO!Z:mO!`#uO#R9pO#p$SO$Z9lO$]9nO$`$TO'W&vO'a#rO~O#`'eO~P&+}O!OZX!OcX~P;aO#S9XO~O!X!vO#S9XO~O!w9hO~O#_9^O~O!w9qO}'sX!O'sX~O!w9hO}'qX!O'qX~O#S9rO~O'[9tO~P!#lO#S9yO~O#S9zO~O!X!vO#S9{O~O!X!vO#S9rO~O#i9|O~P#(|O#S9}O~O#S:OO~O#S:PO~O#S:QO~O#i:RO~P!#lO#i:SO~P!#lO#t~!^!n!p!q#Q#R'z$Z$]$`$q${$|$}%T%V%Y%Z%]%_~TS#t'z#Xy'T'U#v'T'W'd~", +- goto: "#Dk(XPPPPPPP(YP(jP*^PPPP-sPP.Y3j5^5qP5qPPP5q5qP5qP7_PP7dP7xPPPP<XPPPP<X>wPPP>}AYP<XPCsPPPPEk<XPPPPPGd<XPPJcK`PPPPKdL|PMUNVPK`<X<X!#^!&V!*v!*v!.TPPP!.[!1O<XPPPPPPPPPP!3sP!5UPP<X!6cP<XP<X<X<X<XP<X!8vPP!;mP!>`!>h!>l!>lP!;jP!>p!>pP!AcP!Ag<X<X!Am!D_5qP5qP5q5qP!Eb5q5q!GY5q!I[5q!J|5q5q!Kj!Md!Md!Mh!Md!MpP!MdP5q!Nl5q# v5q5q-sPPP##TPP##m##mP##mP#$S##mPP#$YP#$PP#$P#$lMQ#$P#%Z#%a#%d(Y#%g(YP#%n#%n#%nP(YP(YP(YP(YPP(YP#%t#%wP#%w(YPPP(YP(YP(YP(YP(YP(Y(Y#%{#&V#&]#&c#&q#&w#&}#'X#'_#'i#'o#'}#(T#(Z#(i#)O#*b#*p#*v#*|#+S#+Y#+d#+j#+p#+z#,^#,dPPPPPPPPP#,jPP#-^#1[PP#2r#2y#3RP#7_PP#7c#9v#?p#?t#?w#?z#@V#@YPP#@]#@a#AO#As#Aw#BZPP#B_#Be#BiP#Bl#Bp#Bs#Cc#Cy#DO#DR#DU#D[#D_#Dc#DgmhOSj}!m$Y%a%d%e%g*i*n/]/`Q$gmQ$npQ%XyS&R!b+UQ&f!iS(f#x(kQ)a$hQ)n$pQ*Y%RQ+[&YS+c&_+eQ+u&gQ-a(mQ.z*ZY0P+g+h+i+j+kS2l.o2nU3u0Q0S0VU5b2q2r2sS6X3w3zS7P5c5dQ7k6ZR8O7R$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!j'`#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ(v$PQ)f$jQ*[%UQ*c%^Q,P9iQ-|)ZQ.X)gQ/S*aQ2V.SQ3R.{Q4U9jR4}2WpeOSjy}!m$Y%W%a%d%e%g*i*n/]/`R*^%Y&WVOSTjkn}!S!W!]!j!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:j:kW!cRU!`&SQ$`lQ$fmS$kp$pv$urs!q!t$W$s&[&o&r)r)s)t*g+O+_+z+|/f0bQ$}wQ&c!hQ&e!iS(Y#u(dS)`$g$hQ)d$jQ)q$rQ*T%PQ*X%RS+t&f&gQ,}(ZQ.Q)aQ.W)gQ.Y)hQ.])lQ.u*US.y*Y*ZQ0^+uQ1W,yQ2U.SQ2Y.VQ2_._Q3Q.zQ4a1XQ4|2WQ5P2[Q6s4{R7u6t!Y$dm!i$f$g$h&Q&e&f&g(e)`)a+R+b+t+u-Z.Q/u/|0R0^1m3t3y6V7i8]Q)X$`Q)y$zQ)|${Q*W%RQ.a)qQ.t*TU.x*X*Y*ZQ2{.uS3P.y.zQ5]2kQ5o3QS6}5^5aS7|7O7QQ8g7}R8v8hW#{a$b(s:hS$zt%WQ${uQ$|vR)w$x$V#za!v!x#c#u#w$Q$R$V&b'x(R(T(U(](a(q(r)U)W)Z)x){+q,V-Q-S-l-v-x.f.i.q.s1V1`1j1q1x1{2P2b2x2z4d4p4x5f5k6x7U8R9g9k9l9m9n9o9p9u9v9w9x9y9z9}:O:R:S:h:n:oV(w$P9i9jU&V!b$t+XQ'P!zQ)k$mQ.j)}Q1r-iR5X2g&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k$]#`Z!_!n$^%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,a,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:a&ZcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ&T!bR/q+UY%}!b&R&Y+U+[S(e#x(kS+b&_+eS-Z(f(mQ-[(gQ-b(nQ.l*PU/|+c+g+hU0R+i+j+kS0W+l2pQ1m-aQ1o-cQ1p-dS2k.o2nU3t0P0Q0SQ3x0TQ3y0VS5^2l2sS5a2q2rU6V3u3w3zQ6[3{S7O5b5cQ7Q5dS7i6X6ZS7}7P7RQ8]7kR8h8OlhOSj}!m$Y%a%d%e%g*i*n/]/`Q%i!QS&s!u9XQ)^$eQ*R$}Q*S%OQ+r&dS,T&x9rS-n)O9{Q.O)_Q.n*QQ/d*pQ/e*qQ/m+PQ0U+iQ0[+sS1w-o:PQ2Q.PS2T.R:QQ3k/oQ3n/wQ3}0]Q4z2RQ5|3hQ6P3mQ6T3sQ6]4OQ7c5}Q7f6UQ8X7gQ8l8VQ8n8ZR8y8p$W#_Z!_!n%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:aU(p#y&w0{T)S$^,a$W#^Z!_!n%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:aQ'a#_S)R$^,aR-p)S&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ%d{Q%e|Q%g!OQ%h!PR/[*lQ&`!hQ)T$`Q+o&cS-u)X)qS0X+m+nW1z-r-s-t.aS3|0Y0ZU4w1|1}2OU6q4v5T5UQ7t6rR8c7wT+d&_+eS+b&_+eU/|+c+g+hU0R+i+j+kS0W+l2pS2k.o2nU3t0P0Q0SQ3x0TQ3y0VS5^2l2sS5a2q2rU6V3u3w3zQ6[3{S7O5b5cQ7Q5dS7i6X6ZS7}7P7RQ8]7kR8h8OS+d&_+eT2m.o2nS&m!p/YQ,|(YQ-X(eS/{+b2kQ1],}S1g-Y-bU3v0R0W5aQ4`1WS4n1n1pU6Y3x3y7QQ6g4aQ6p4qR7l6[Q!wXS&l!p/YQ)P$XQ)[$cQ)b$iQ+x&mQ,{(YQ-W(eQ-](hQ-})]Q.v*VS/z+b2kS1[,|,}S1f-X-bQ1i-[Q1l-^Q2}.wW3r/{0R0W5aQ4_1WQ4c1]S4h1g1pQ4o1oQ5m3OW6W3v3x3y7QS6f4`4aQ6k4jQ6n4nQ6{5[Q7Y5nS7j6Y6[Q7n6gQ7p6lQ7s6pQ7z6|Q8T7ZQ8^7lQ8a7rQ8e7{Q8t8fQ8|8uQ9Q8}Q:Z:UQ:d:_R:e:`$nWORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qS!wn!j!j:T#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR:Z:j$nXORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qQ$Xb!Y$cm!i$f$g$h&Q&e&f&g(e)`)a+R+b+t+u-Z.Q/u/|0R0^1m3t3y6V7i8]S$in!jQ)]$dQ*V%RW.w*W*X*Y*ZU3O.x.y.zQ5[2kS5n3P3QU6|5]5^5aQ7Z5oU7{6}7O7QS8f7|7}S8u8g8hQ8}8v!j:U#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ:_:iR:`:j$f]OSTjk}!S!W!]!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qU!gRU!`v$urs!q!t$W$s&[&o&r)r)s)t*g+O+_+z+|/f0bQ*d%^!h:V#[#j'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR:Y&SS&W!b$tR/s+X$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!j'`#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR*c%^$noORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qQ'P!z!k:W#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k!h#UZ!_$^%u%y&t&{'Y'Z'[']'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9T!R9`'_'p+S,a/k/n0m0u0v0w0x0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!d#WZ!_$^%u%y&t&{'[']'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9T}9b'_'p+S,a/k/n0m0w0x0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!`#[Z!_$^%u%y&t&{'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9Tl(U#s&y(},w-P-e-f0g1u4^4r:[:f:gx:k'_'p+S,a/k/n0m0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!`:n&u'd(X(_+n,S,l-T-q-t.e.g0Z0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7WZ:o0z4X6`7m8_&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kS#k`#lR1O,d&a_ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j#l$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,d,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kS#f^#mT'i#h'mT#g^#mT'k#h'm&a`ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j#l$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,d,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kT#k`#lQ#n`R't#l$nbORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!k:i#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k#RdOSUj}!S!W!m!{#j$Y%Y%]%^%a%c%d%e%g%k&O&a'r)V*e*i*n+p,e-j-w.r/T/U/V/X/]/`/b0}2a2y3^3`3a5j5xt#ya!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:o!|&w!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:RQ({$TQ,p'}c0{9g9l9n9p9v9x9z:O:St#va!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:oS(h#x(kQ(|$UQ-^(i!|:]!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:Rb:^9g9l9n9p9v9x9z:O:SQ:b:lR:c:mt#ya!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:o!|&w!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:Rc0{9g9l9n9p9v9x9z:O:SlfOSj}!m$Y%a%d%e%g*i*n/]/`Q(`#wQ*u%nQ*v%pR1_-Q$U#za!v!x#c#u#w$Q$R$V&b'x(R(T(U(](a(q(r)U)W)Z)x){+q,V-Q-S-l-v-x.f.i.q.s1V1`1j1q1x1{2P2b2x2z4d4p4x5f5k6x7U8R9g9k9l9m9n9o9p9u9v9w9x9y9z9}:O:R:S:h:n:oQ)z${Q.h)|Q2e.gR5W2fT(j#x(kS(j#x(kT2m.o2nQ)[$cQ-](hQ-})]Q.v*VQ2}.wQ5m3OQ6{5[Q7Y5nQ7z6|Q8T7ZQ8e7{Q8t8fQ8|8uR9Q8}l(R#s&y(},w-P-e-f0g1u4^4r:[:f:g!`9u&u'd(X(_+n,S,l-T-q-t.e.g0Z0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7WZ9v0z4X6`7m8_n(T#s&y(},u,w-P-e-f0g1u4^4r:[:f:g!b9w&u'd(X(_+n,S,l-T-q-t.e.g0Z0d0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7W]9x0z4X6`6a7m8_peOSjy}!m$Y%W%a%d%e%g*i*n/]/`Q%TxR*e%^peOSjy}!m$Y%W%a%d%e%g*i*n/]/`R%TxQ*O$|R.d)wqeOSjy}!m$Y%W%a%d%e%g*i*n/]/`Q.p*TS2w.t.uW5e2t2u2v2{U7T5g5h5iU8P7S7V7WQ8i8QR8w8jQ%[yR*_%WR3U.}R7]5pS$kp$pR.Y)hQ%azR*i%bR*o%hT/^*n/`QjOQ!mST$]j!mQ'z#rR,m'zQ!YQR%s!YQ!^RU%w!^%x*zQ%x!_R*z%yQ+V&TR/r+VQ,W&yR0h,WQ,Z&{S0k,Z0lR0l,[Q+e&_R/}+eQ&]!eQ*{%zT+`&]*{Q+Y&WR/t+YQ&p!rQ+y&nU+}&p+y0cR0c,OQ'm#hR,f'mQ#l`R's#lQ#bZU'c#b*x9fQ*x9TR9f'pQ,z(YW1Y,z1Z4b6hU1Z,{,|,}S4b1[1]R6h4c#q(P#s&u&y'd(X(_(x(y(}+n,Q,R,S,l,u,v,w-P-T-e-f-q-t.e.g0Z0d0e0f0g0z1^1b1u2O2d2f2v4R4V4W4X4^4e4k4r4t4y5U5i6^6`6a6b6i6o7W7m8_:[:f:gQ-R(_U1a-R1c4fQ1c-TR4f1bQ(k#xR-_(kQ(t#|R-h(tQ1y-qR4u1yQ)u$vR.c)uQ2h.jS5Y2h6zR6z5ZQ*Q$}R.m*QQ2n.oR5_2nQ.|*[S3S.|5qR5q3UQ.T)dW2X.T2Z5O6uQ2Z.WQ5O2YR6u5PQ)i$kR.Z)iQ/`*nR3d/`WiOSj!mQ%f}Q)Q$YQ*h%aQ*j%dQ*k%eQ*m%gQ/Z*iS/^*n/`R3c/]Q$[gQ%j!RQ%m!TQ%o!UQ%q!VQ)p$qQ)v$wQ*^%[Q*s%lS/P*_*bQ/g*rQ/h*uQ/i*vS/x+b2kQ1d-VQ1e-WQ1k-]Q2^.^Q2c.eQ2|.vQ3W/RQ3b/[Y3p/z/{0R0W5aQ4g1fQ4i1hQ4l1lQ5S2`Q5V2dQ5l2}Q5r3V[6Q3o3r3v3x3y7QQ6j4hQ6m4mQ6v5QQ7X5mQ7^5sW7d6R6W6Y6[Q7o6kQ7q6nQ7v6wQ7y6{Q8S7YU8W7e7j7lQ8`7pQ8b7sQ8d7zQ8k8TS8m8Y8^Q8r8aQ8s8eQ8x8oQ8{8tQ9O8zQ9P8|R9R9QQ$emQ&d!iU)_$f$g$hQ+P&QU+s&e&f&gQ-V(eS.P)`)aQ/o+RQ/w+bS0]+t+uQ1h-ZQ2R.QQ3m/uS3s/|0RQ4O0^Q4m1mS6U3t3yQ7g6VQ8Z7iR8p8]S#ta:hR)Y$bU#|a$b:hR-g(sQ#saS&u!v)ZQ&y!xQ'd#cQ(X#uQ(_#wQ(x$QQ(y$RQ(}$VQ+n&bQ,Q9kQ,R9mQ,S9oQ,l'xQ,u(RQ,v(TQ,w(UQ-P(]Q-T(aQ-e(qQ-f(rd-q)U-v.q1{2x4x5f6x7U8RQ-t)WQ.e)xQ.g){Q0Z+qQ0d9uQ0e9wQ0f9yQ0g,VQ0z9gQ1^-QQ1b-SQ1u-lQ2O-xQ2d.fQ2f.iQ2v.sQ4R9}Q4V9lQ4W9nQ4X9pQ4^1VQ4e1`Q4k1jQ4r1qQ4t1xQ4y2PQ5U2bQ5i2zQ6^:RQ6`9zQ6a9vQ6b9xQ6i4dQ6o4pQ7W5kQ7m:OQ8_:SQ:[:hQ:f:nR:g:oT'y#r'zlgOSj}!m$Y%a%d%e%g*i*n/]/`S!oU%cQ%l!SQ%r!WQ'Q!{Q'q#jS*b%Y%]Q*f%^Q*r%kQ*|&OQ+m&aQ,j'rQ-s)VQ/W*eQ0Y+pQ1Q,eQ1s-jQ1}-wQ2u.rQ3Y/TQ3Z/UQ3]/VQ3_/XQ3f/bQ4[0}Q5T2aQ5h2yQ5w3^Q5y3`Q5z3aQ7V5jR7`5x!vZOSUj}!S!m!{$Y%Y%]%^%a%c%d%e%g%k&O&a)V*e*i*n+p-j-w.r/T/U/V/X/]/`/b2a2y3^3`3a5j5xQ!_RQ!nTQ$^kQ%u!]Q%y!`Q&t!uQ&{!yQ'R#OQ'S#PQ'T#QQ'U#RQ'V#SQ'W#TQ'X#UQ'Y#VQ'Z#WQ'[#XQ']#YQ'_#[Q'b#aQ'f#dW'p#j'r,e0}Q)j$lQ*y%vS+S&S/pQ+]&ZQ+v&kQ,U&xQ,[&|Q,_9SQ,a9UQ,o'|Q-m)OQ/k*}Q/n+QQ0_+wQ0i,YQ0m9XQ0n9YQ0o9ZQ0p9[Q0q9]Q0r9^Q0s9_Q0t9`Q0u9aQ0v9bQ0w9cQ0x9dQ0y,`Q0|9hQ1R9eQ1v-oQ2S.RQ3l9qQ3o/yQ4P0`Q4S0jQ4T9rQ4Y9tQ4Z9{Q5Z2iQ6O3jQ6R3qQ6_9|Q6c:PQ6d:QQ7e6SQ7x6yQ8Y7hQ8o8[Q8z8qQ9T!WR:a:kT!XQ!YR!aRR&U!bS&Q!b+US+R&R&YR/u+[R&z!xR&}!yT!sU$WS!rU$WU$vrs*gS&n!q!tQ+{&oQ,O&rQ.b)tS0a+z+|R4Q0b[!dR!`$s&[)r+_h!pUrs!q!t$W&o&r)t+z+|0bQ/Y*gQ/l+OQ3i/fT:X&S)sT!fR$sS!eR$sS%z!`)rS+T&S)sQ+^&[R/v+_T&X!b$tQ#h^R'v#mT'l#h'mR1P,dT([#u(dR(b#wQ-r)UQ1|-vQ2t.qQ4v1{Q5g2xQ6r4xQ7S5fQ7w6xQ8Q7UR8j8RlhOSj}!m$Y%a%d%e%g*i*n/]/`Q%ZyR*^%WV$wrs*gR.k)}R*]%UQ$opR)o$pR)e$jT%_z%bT%`z%bT/_*n/`", +- nodeNames: "⚠ ArithOp ArithOp extends LineComment BlockComment Script ExportDeclaration export Star as VariableName from String ; default FunctionDeclaration async function VariableDefinition TypeParamList TypeDefinition ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType VoidType void TypeofType typeof MemberExpression . ?. PropertyName [ TemplateString null super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewExpression new TypeArgList CompareOp < ) ( ArgList UnaryExpression await yield delete LogicOp BitOp ParenthesizedExpression ClassExpression class extends ClassBody MethodDeclaration Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression PrivatePropertyName BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof in const CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXStartTag JSXSelfClosingTag JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast ArrowFunction TypeParamList SequenceExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature CallSignature TypePredicate is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody MethodDeclaration AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try catch finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement", +- maxTerm: 330, ++ states: "$1dO`QYOOO'QQ!LdO'#CgO'XOSO'#DSO)dQYO'#DXO)tQYO'#DdO){QYO'#DnO-xQYO'#DtOOQO'#EX'#EXO.]QWO'#EWO.bQWO'#EWOOQ!LS'#Eb'#EbO0aQ!LdO'#IsO2wQ!LdO'#ItO3eQWO'#EvO3jQpO'#F_OOQ!LS'#FO'#FOO3uO!bO'#FOO4TQWO'#FfO5bQWO'#FeOOQ!LS'#It'#ItOOQ!LQ'#Is'#IsOOQQ'#J^'#J^O5gQWO'#HlO5lQ!LYO'#HmOOQQ'#Ie'#IeOOQQ'#Hn'#HnQ`QYOOO){QYO'#DfO5tQWO'#GYO5yQ#tO'#ClO6XQWO'#EVO6dQWO'#EcO6iQ#tO'#E}O7TQWO'#GYO7YQWO'#G^O7eQWO'#G^O7sQWO'#GaO7sQWO'#GbO7sQWO'#GdO5tQWO'#GgO8dQWO'#GjO9rQWO'#CcO:SQWO'#GwO:[QWO'#G}O:[QWO'#HPO`QYO'#HRO:[QWO'#HTO:[QWO'#HWO:aQWO'#H^O:fQ!LZO'#HbO){QYO'#HdO:qQ!LZO'#HfO:|Q!LZO'#HhO5lQ!LYO'#HjO){QYO'#IuOOOS'#Hp'#HpO;XOSO,59nOOQ!LS,59n,59nO=jQbO'#CgO=tQYO'#HqO>RQWO'#IvO@QQbO'#IvO'dQYO'#IvO@XQWO,59sO@oQ&jO'#D^OAhQWO'#EXOAuQWO'#JROBQQWO'#JQOBYQWO,5:uOB_QWO'#JPOBfQWO'#DuO5yQ#tO'#EVOBtQWO'#EVOCPQ`O'#E}OOQ!LS,5:O,5:OOCXQYO,5:OOEVQ!LdO,5:YOEsQWO,5:`OF^Q!LYO'#JOO7YQWO'#I}OFeQWO'#I}OFmQWO,5:tOFrQWO'#I}OGQQYO,5:rOIQQWO'#ESOJ[QWO,5:rOKkQWO'#DhOKrQYO'#DmOK|Q&jO,5:{O){QYO,5:{OOQQ'#En'#EnOOQQ'#Ep'#EpO){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}OOQQ'#Et'#EtOLUQYO,5;_OOQ!LS,5;d,5;dOOQ!LS,5;e,5;eONUQWO,5;eOOQ!LS,5;f,5;fO){QYO'#H{ONZQ!LYO,5<RONuQWO,5:}O){QYO,5;bON|QpO'#FTO! yQpO'#JVO! eQpO'#JVO!!QQpO'#JVOOQO'#JV'#JVO!!fQpO,5;mOOOO,5;y,5;yO!!wQYO'#FaOOOO'#Hz'#HzO3uO!bO,5;jO!#OQpO'#FcOOQ!LS,5;j,5;jO!#oQ,UO'#CqOOQ!LS'#Ct'#CtO!$SQWO'#CtO!$XOSO'#CxO!$uQ#tO,5<OO!$|QWO,5<QO!&YQWO'#FpO!&gQWO'#FqO!&lQWO'#FuO!'nQ&jO'#FyO!(aQ,UO'#InOOQ!LS'#In'#InO!(kQWO'#ImO!(yQWO'#IlOOQ!LS'#Cr'#CrOOQ!LS'#Cy'#CyO!)RQWO'#C{OJaQWO'#FhOJaQWO'#FjO!)WQWO'#FlO!)]QWO'#FmO!)bQWO'#FsOJaQWO'#FxO!)gQWO'#EYO!*OQWO,5<PO`QYO,5>WOOQQ'#Ih'#IhOOQQ,5>X,5>XOOQQ-E;l-E;lO!+zQ!LdO,5:QOOQ!LQ'#Co'#CoO!,kQ#tO,5<tOOQO'#Ce'#CeO!,|QWO'#CpO!-UQ!LYO'#IiO5bQWO'#IiO:aQWO,59WO!-dQpO,59WO!-lQ#tO,59WO5yQ#tO,59WO!-wQWO,5:rO!.PQWO'#GvO!.[QWO'#JbO){QYO,5;gO!.dQ&jO,5;iO!.iQWO,5=aO!.nQWO,5=aO!.sQWO,5=aO5lQ!LYO,5=aO5tQWO,5<tO!/RQWO'#EZO!/dQ&jO'#E[OOQ!LQ'#JP'#JPO!/uQ!LYO'#J_O5lQ!LYO,5<xO7sQWO,5=OOOQO'#Cq'#CqO!0QQpO,5<{O!0YQ#tO,5<|O!0eQWO,5=OO!0jQ`O,5=RO:aQWO'#GlO5tQWO'#GnO!0rQWO'#GnO5yQ#tO'#GqO!0wQWO'#GqOOQQ,5=U,5=UO!0|QWO'#GrO!1UQWO'#ClO!1ZQWO,58}O!1eQWO,58}O!3gQYO,58}OOQQ,58},58}O!3tQ!LYO,58}O){QYO,58}O!4PQYO'#GyOOQQ'#Gz'#GzOOQQ'#G{'#G{O`QYO,5=cO!4aQWO,5=cO){QYO'#DtO`QYO,5=iO`QYO,5=kO!4fQWO,5=mO`QYO,5=oO!4kQWO,5=rO!4pQYO,5=xOOQQ,5=|,5=|O){QYO,5=|O5lQ!LYO,5>OOOQQ,5>Q,5>QO!8qQWO,5>QOOQQ,5>S,5>SO!8qQWO,5>SOOQQ,5>U,5>UO!8vQ`O,5?aOOOS-E;n-E;nOOQ!LS1G/Y1G/YO!8{QbO,5>]O){QYO,5>]OOQO-E;o-E;oO!9VQWO,5?bO!9_QbO,5?bO!9fQWO,5?lOOQ!LS1G/_1G/_O!9nQpO'#DQOOQO'#Ix'#IxO){QYO'#IxO!:]QpO'#IxO!:zQpO'#D_O!;]Q&jO'#D_O!=hQYO'#D_O!=oQWO'#IwO!=wQWO,59xO!=|QWO'#E]O!>[QWO'#JSO!>dQWO,5:vO!>zQ&jO'#D_O){QYO,5?mO!?UQWO'#HvO!9fQWO,5?lOOQ!LQ1G0a1G0aO!@bQ&jO'#DxOOQ!LS,5:a,5:aO){QYO,5:aOIQQWO,5:aO!@iQWO,5:aO:aQWO,5:qO!-dQpO,5:qO!-lQ#tO,5:qO5yQ#tO,5:qOOQ!LS1G/j1G/jOOQ!LS1G/z1G/zOOQ!LQ'#ER'#ERO){QYO,5?jO!@tQ!LYO,5?jO!AVQ!LYO,5?jO!A^QWO,5?iO!AfQWO'#HxO!A^QWO,5?iOOQ!LQ1G0`1G0`O7YQWO,5?iOOQ!LS1G0^1G0^O!BQQ!LdO1G0^O!BqQ!LbO,5:nOOQ!LS'#Fo'#FoO!C_Q!LdO'#InOGQQYO1G0^O!E^Q#tO'#IyO!EhQWO,5:SO!EmQbO'#IzO){QYO'#IzO!EwQWO,5:XOOQ!LS'#DQ'#DQOOQ!LS1G0g1G0gO!E|QWO1G0gO!H_Q!LdO1G0iO!HfQ!LdO1G0iO!JyQ!LdO1G0iO!KQQ!LdO1G0iO!MXQ!LdO1G0iO!MlQ!LdO1G0iO#!]Q!LdO1G0iO#!dQ!LdO1G0iO#$wQ!LdO1G0iO#%OQ!LdO1G0iO#&sQ!LdO1G0iO#)mQ7^O'#CgO#+hQ7^O1G0yO#-cQ7^O'#ItOOQ!LS1G1P1G1PO#-vQ!LdO,5>gOOQ!LQ-E;y-E;yO#.gQ!LdO1G0iOOQ!LS1G0i1G0iO#0iQ!LdO1G0|O#1YQpO,5;qO#1bQpO,5;rO#1jQpO'#FYO#2RQWO'#FXOOQO'#JW'#JWOOQO'#Hy'#HyO#2WQpO1G1XOOQ!LS1G1X1G1XOOOO1G1d1G1dO#2iQ7^O'#IsO#2sQWO,5;{OLUQYO,5;{OOOO-E;x-E;xOOQ!LS1G1U1G1UOOQ!LS,5;},5;}O#2xQpO,5;}OOQ!LS,59`,59`OIQQWO'#IpOOOS'#Ho'#HoO#2}OSO,59dOOQ!LS,59d,59dO){QYO1G1jO!)]QWO'#H}O#3YQWO,5<cOOQ!LS,5<`,5<`OOQO'#GT'#GTOJaQWO,5<nOOQO'#GV'#GVOJaQWO,5<pOJaQWO,5<rOOQO1G1l1G1lO#3eQ`O'#CoO#3xQ`O,5<[O#4PQWO'#JZO5tQWO'#JZO#4_QWO,5<^OJaQWO,5<]O#4dQ`O'#FoO#4qQ`O'#J[O#4{QWO'#J[OIQQWO'#J[O#5QQWO,5<aOOQ!LQ'#Dc'#DcO#5VQWO'#FrO#5bQpO'#FzO!'iQ&jO'#FzO!'iQ&jO'#F|O#5sQWO'#F}O!)bQWO'#GQOOQO'#IP'#IPO#5xQ&jO,5<eOOQ!LS,5<e,5<eO#6PQ&jO'#FzO#6_Q&jO'#F{O#6gQ&jO'#F{OOQ!LS,5<s,5<sOJaQWO,5?XOJaQWO,5?XO#6lQWO'#IQO#6wQWO,5?WOOQ!LS'#Cg'#CgO#7kQ#tO,59gOOQ!LS,59g,59gO#8^Q#tO,5<SO#9PQ#tO,5<UO#9ZQWO,5<WOOQ!LS,5<X,5<XO#9`QWO,5<_O#9eQ#tO,5<dOGQQYO1G1kO#9uQWO1G1kOOQQ1G3r1G3rOOQ!LS1G/l1G/lONUQWO1G/lOOQQ1G2`1G2`OIQQWO1G2`O){QYO1G2`OIQQWO1G2`O#9zQWO1G2`O#:YQWO,59[O#;cQWO'#ESOOQ!LQ,5?T,5?TO#;mQ!LYO,5?TOOQQ1G.r1G.rO:aQWO1G.rO!-dQpO1G.rO!-lQ#tO1G.rO#;{QWO1G0^O#<QQWO'#CgO#<]QWO'#JcO#<eQWO,5=bO#<jQWO'#JcO#<oQWO'#JcO#<tQWO'#IYO#=SQWO,5?|O#=[QbO1G1ROOQ!LS1G1T1G1TO5tQWO1G2{O#=cQWO1G2{O#=hQWO1G2{O#=mQWO1G2{OOQQ1G2{1G2{O#=rQ#tO1G2`O7YQWO'#JQO7YQWO'#E]O7YQWO'#ISO#>TQ!LYO,5?yOOQQ1G2d1G2dO!0eQWO1G2jOIQQWO1G2gO#>`QWO1G2gOOQQ1G2h1G2hOIQQWO1G2hO#>eQWO1G2hO#>mQ&jO'#GfOOQQ1G2j1G2jO!'iQ&jO'#IUO!0jQ`O1G2mOOQQ1G2m1G2mOOQQ,5=W,5=WO#>uQ#tO,5=YO5tQWO,5=YO#5sQWO,5=]O5bQWO,5=]O!-dQpO,5=]O!-lQ#tO,5=]O5yQ#tO,5=]O#?WQWO'#JaO#?cQWO,5=^OOQQ1G.i1G.iO#?hQ!LYO1G.iO#?sQWO1G.iO!)RQWO1G.iO5lQ!LYO1G.iO#?xQbO,5@OO#@SQWO,5@OO#@_QYO,5=eO#@fQWO,5=eO7YQWO,5@OOOQQ1G2}1G2}O`QYO1G2}OOQQ1G3T1G3TOOQQ1G3V1G3VO:[QWO1G3XO#@kQYO1G3ZO#DfQYO'#HYOOQQ1G3^1G3^O:aQWO1G3dO#DsQWO1G3dO5lQ!LYO1G3hOOQQ1G3j1G3jOOQ!LQ'#Fv'#FvO5lQ!LYO1G3lO5lQ!LYO1G3nOOOS1G4{1G4{O#D{Q`O,5<RO#ETQbO1G3wO#E_QWO1G4|O#EgQWO1G5WO#EoQWO,5?dOLUQYO,5:wO7YQWO,5:wO:aQWO,59yOLUQYO,59yO!-dQpO,59yO#EtQ7^O,59yOOQO,5:w,5:wO#FOQ&jO'#HrO#FfQWO,5?cOOQ!LS1G/d1G/dO#FnQ&jO'#HwO#GSQWO,5?nOOQ!LQ1G0b1G0bO!;]Q&jO,59yO#G[QbO1G5XOOQO,5>b,5>bO7YQWO,5>bOOQO-E;t-E;tOOQ!LQ'#EO'#EOO#GfQ!LrO'#EPO!@YQ&jO'#DyOOQO'#Hu'#HuO#HQQ&jO,5:dOOQ!LS,5:d,5:dO#HXQ&jO'#DyO#HjQ&jO'#DyO#HqQ&jO'#EUO#HtQ&jO'#EPO#IRQ&jO'#EPO!@YQ&jO'#EPO#IfQWO1G/{O#IkQ`O1G/{OOQ!LS1G/{1G/{O){QYO1G/{OIQQWO1G/{OOQ!LS1G0]1G0]O:aQWO1G0]O!-dQpO1G0]O!-lQ#tO1G0]O#IrQ!LdO1G5UO){QYO1G5UO#JSQ!LYO1G5UO#JeQWO1G5TO7YQWO,5>dOOQO,5>d,5>dO#JmQWO,5>dOOQO-E;v-E;vO#JeQWO1G5TO#J{Q!LdO,59gO#LzQ!LdO,5<SO#N|Q!LdO,5<UO$#OQ!LdO,5<dOOQ!LS7+%x7+%xO$%WQ!LdO7+%xO$%wQWO'#HsO$&RQWO,5?eOOQ!LS1G/n1G/nO$&ZQYO'#HtO$&hQWO,5?fO$&pQbO,5?fOOQ!LS1G/s1G/sOOQ!LS7+&R7+&RO$&zQ7^O,5:YO){QYO7+&eO$'UQ7^O,5:QOOQO1G1]1G1]OOQO1G1^1G1^O$'cQMhO,5;tOLUQYO,5;sOOQO-E;w-E;wOOQ!LS7+&s7+&sOOOO7+'O7+'OOOOO1G1g1G1gO$'nQWO1G1gOOQ!LS1G1i1G1iO$'sQ`O,5?[OOOS-E;m-E;mOOQ!LS1G/O1G/OO$'zQ!LdO7+'UOOQ!LS,5>i,5>iO$(kQWO,5>iOOQ!LS1G1}1G1}P$(pQWO'#H}POQ!LS-E;{-E;{O$)aQ#tO1G2YO$*SQ#tO1G2[O$*^Q#tO1G2^OOQ!LS1G1v1G1vO$*eQWO'#H|O$*sQWO,5?uO$*sQWO,5?uO$*{QWO,5?uO$+WQWO,5?uOOQO1G1x1G1xO$+fQ#tO1G1wO$+vQWO'#IOO$,WQWO,5?vOIQQWO,5?vO$,`Q`O,5?vOOQ!LS1G1{1G1{O5lQ!LYO,5<fO5lQ!LYO,5<gO$,jQWO,5<gO#5nQWO,5<gO!-dQpO,5<fO$,oQWO,5<hO5lQ!LYO,5<iO$,jQWO,5<lOOQO-E;}-E;}OOQ!LS1G2P1G2PO!'iQ&jO,5<fO$,wQWO,5<gO!'iQ&jO,5<hO!'iQ&jO,5<gO$-SQ#tO1G4sO$-^Q#tO1G4sOOQO,5>l,5>lOOQO-E<O-E<OO!.dQ&jO,59iO){QYO,59iO$-kQWO1G1rOJaQWO1G1yO$-pQ!LdO7+'VOOQ!LS7+'V7+'VOGQQYO7+'VOOQ!LS7+%W7+%WO$.aQ`O'#J]O#IfQWO7+'zO$.kQWO7+'zO$.sQ`O7+'zOOQQ7+'z7+'zOIQQWO7+'zO){QYO7+'zOIQQWO7+'zOOQO1G.v1G.vO$.}Q!LbO'#CgO$/_Q!LbO,5<jO$/|QWO,5<jOOQ!LQ1G4o1G4oOOQQ7+$^7+$^O:aQWO7+$^O!-dQpO7+$^OGQQYO7+%xO$0RQWO'#IXO$0aQWO,5?}OOQO1G2|1G2|O5tQWO,5?}O$0aQWO,5?}O$0iQWO,5?}OOQO,5>t,5>tOOQO-E<W-E<WOOQ!LS7+&m7+&mO$0nQWO7+(gO5lQ!LYO7+(gO5tQWO7+(gO$0sQWO7+(gO$0xQWO7+'zOOQ!LQ,5>n,5>nOOQ!LQ-E<Q-E<QOOQQ7+(U7+(UO$1WQ!LbO7+(ROIQQWO7+(RO$1bQ`O7+(SOOQQ7+(S7+(SOIQQWO7+(SO$1iQWO'#J`O$1tQWO,5=QOOQO,5>p,5>pOOQO-E<S-E<SOOQQ7+(X7+(XO$2nQ&jO'#GoOOQQ1G2t1G2tOIQQWO1G2tO){QYO1G2tOIQQWO1G2tO$2uQWO1G2tO$3TQ#tO1G2tO5lQ!LYO1G2wO#5sQWO1G2wO5bQWO1G2wO!-dQpO1G2wO!-lQ#tO1G2wO$3fQWO'#IWO$3qQWO,5?{O$3yQ&jO,5?{OOQ!LQ1G2x1G2xOOQQ7+$T7+$TO$4OQWO7+$TO5lQ!LYO7+$TO$4TQWO7+$TO){QYO1G5jO){QYO1G5kO$4YQYO1G3PO$4aQWO1G3PO$4fQYO1G3PO$4mQ!LYO1G5jOOQQ7+(i7+(iO5lQ!LYO7+(sO`QYO7+(uOOQQ'#Jf'#JfOOQQ'#IZ'#IZO$4wQYO,5=tOOQQ,5=t,5=tO){QYO'#HZO$5UQWO'#H]OOQQ7+)O7+)OO$5ZQYO7+)OO7YQWO7+)OOOQQ7+)S7+)SOOQQ7+)W7+)WOOQQ7+)Y7+)YOOQO1G5O1G5OO$9XQ7^O1G0cO$9cQWO1G0cOOQO1G/e1G/eO$9nQ7^O1G/eO:aQWO1G/eOLUQYO'#D_OOQO,5>^,5>^OOQO-E;p-E;pOOQO,5>c,5>cOOQO-E;u-E;uO!-dQpO1G/eOOQO1G3|1G3|O:aQWO,5:eOOQO,5:k,5:kO){QYO,5:kO$9xQ!LYO,5:kO$:TQ!LYO,5:kO!-dQpO,5:eOOQO-E;s-E;sOOQ!LS1G0O1G0OO!@YQ&jO,5:eO$:cQ&jO,5:eO$:tQ!LrO,5:kO$;`Q&jO,5:eO!@YQ&jO,5:kOOQO,5:p,5:pO$;gQ&jO,5:kO$;tQ!LYO,5:kOOQ!LS7+%g7+%gO#IfQWO7+%gO#IkQ`O7+%gOOQ!LS7+%w7+%wO:aQWO7+%wO!-dQpO7+%wO$<YQ!LdO7+*pO){QYO7+*pOOQO1G4O1G4OO7YQWO1G4OO$<jQWO7+*oO$<rQ!LdO1G2YO$>tQ!LdO1G2[O$@vQ!LdO1G1wO$COQ#tO,5>_OOQO-E;q-E;qO$CYQbO,5>`O){QYO,5>`OOQO-E;r-E;rO$CdQWO1G5QO$ClQ7^O1G0^O$EsQ7^O1G0iO$EzQ7^O1G0iO$G{Q7^O1G0iO$HSQ7^O1G0iO$IwQ7^O1G0iO$J[Q7^O1G0iO$LiQ7^O1G0iO$LpQ7^O1G0iO$NqQ7^O1G0iO$NxQ7^O1G0iO%!mQ7^O1G0iO%#QQ!LdO<<JPO%#qQ7^O1G0iO%%aQ7^O'#InO%'^Q7^O1G0|OLUQYO'#F[OOQO'#JX'#JXOOQO1G1`1G1`O%'kQWO1G1_O%'pQ7^O,5>gOOOO7+'R7+'ROOOS1G4v1G4vOOQ!LS1G4T1G4TOJaQWO7+'xO%'zQWO,5>hO5tQWO,5>hOOQO-E;z-E;zO%(YQWO1G5aO%(YQWO1G5aO%(bQWO1G5aO%(mQ`O,5>jO%(wQWO,5>jOIQQWO,5>jOOQO-E;|-E;|O%(|Q`O1G5bO%)WQWO1G5bOOQO1G2Q1G2QOOQO1G2R1G2RO5lQ!LYO1G2RO$,jQWO1G2RO5lQ!LYO1G2QO%)`QWO1G2SOIQQWO1G2SOOQO1G2T1G2TO5lQ!LYO1G2WO!-dQpO1G2QO#5nQWO1G2RO%)eQWO1G2SO%)mQWO1G2ROJaQWO7+*_OOQ!LS1G/T1G/TO%)xQWO1G/TOOQ!LS7+'^7+'^O%)}Q#tO7+'eO%*_Q!LdO<<JqOOQ!LS<<Jq<<JqOIQQWO'#IRO%+OQWO,5?wOOQQ<<Kf<<KfOIQQWO<<KfO#IfQWO<<KfO%+WQWO<<KfO%+`Q`O<<KfOIQQWO1G2UOOQQ<<Gx<<GxO:aQWO<<GxO%+jQ!LdO<<IdOOQ!LS<<Id<<IdOOQO,5>s,5>sO%,ZQWO,5>sO#<oQWO,5>sOOQO-E<V-E<VO%,`QWO1G5iO%,`QWO1G5iO5tQWO1G5iO%,hQWO<<LROOQQ<<LR<<LRO%,mQWO<<LRO5lQ!LYO<<LRO){QYO<<KfOIQQWO<<KfOOQQ<<Km<<KmO$1WQ!LbO<<KmOOQQ<<Kn<<KnO$1bQ`O<<KnO%,rQ&jO'#ITO%,}QWO,5?zOLUQYO,5?zOOQQ1G2l1G2lO#GfQ!LrO'#EPO!@YQ&jO'#GpOOQO'#IV'#IVO%-VQ&jO,5=ZOOQQ,5=Z,5=ZO%-^Q&jO'#EPO%-iQ&jO'#EPO%.QQ&jO'#EPO%.[Q&jO'#GpO%.mQWO7+(`O%.rQWO7+(`O%.zQ`O7+(`OOQQ7+(`7+(`OIQQWO7+(`O){QYO7+(`OIQQWO7+(`O%/UQWO7+(`OOQQ7+(c7+(cO5lQ!LYO7+(cO#5sQWO7+(cO5bQWO7+(cO!-dQpO7+(cO%/dQWO,5>rOOQO-E<U-E<UOOQO'#Gs'#GsO%/oQWO1G5gO5lQ!LYO<<GoOOQQ<<Go<<GoO%/wQWO<<GoO%/|QWO7++UO%0RQWO7++VOOQQ7+(k7+(kO%0WQWO7+(kO%0]QYO7+(kO%0dQWO7+(kO){QYO7++UO){QYO7++VOOQQ<<L_<<L_OOQQ<<La<<LaOOQQ-E<X-E<XOOQQ1G3`1G3`O%0iQWO,5=uOOQQ,5=w,5=wO:aQWO<<LjO%0nQWO<<LjOLUQYO7+%}OOQO7+%P7+%PO%0sQ7^O1G5XO:aQWO7+%POOQO1G0P1G0PO%0}Q!LdO1G0VOOQO1G0V1G0VO){QYO1G0VO%1XQ!LYO1G0VO:aQWO1G0PO!-dQpO1G0PO!@YQ&jO1G0PO%1dQ!LYO1G0VO%1rQ&jO1G0PO%2TQ!LYO1G0VO%2iQ!LrO1G0VO%2sQ&jO1G0PO!@YQ&jO1G0VOOQ!LS<<IR<<IROOQ!LS<<Ic<<IcO:aQWO<<IcO%2zQ!LdO<<N[OOQO7+)j7+)jO%3[Q!LdO7+'eO%5dQbO1G3zO%5nQ7^O7+%xO%5{Q7^O,59gO%7xQ7^O,5<SO%9uQ7^O,5<UO%;rQ7^O,5<dO%=bQ7^O7+'UO%=oQ7^O7+'VO%=|QWO,5;vOOQO7+&y7+&yO%>RQ#tO<<KdOOQO1G4S1G4SO%>cQWO1G4SO%>nQWO1G4SO%>|QWO7+*{O%>|QWO7+*{OIQQWO1G4UO%?UQ`O1G4UO%?`QWO7+*|OOQO7+'m7+'mO5lQ!LYO7+'mOOQO7+'l7+'lO$,jQWO7+'nO%?hQ`O7+'nOOQO7+'r7+'rO5lQ!LYO7+'lO$,jQWO7+'mO%?oQWO7+'nOIQQWO7+'nO#5nQWO7+'mO%?tQ#tO<<MyOOQ!LS7+$o7+$oO%@OQ`O,5>mOOQO-E<P-E<PO#IfQWOANAQOOQQANAQANAQOIQQWOANAQO%@YQ!LbO7+'pOOQQAN=dAN=dO5tQWO1G4_OOQO1G4_1G4_O%@gQWO1G4_O%@lQWO7++TO%@lQWO7++TO5lQ!LYOANAmO%@tQWOANAmOOQQANAmANAmO%@yQWOANAQO%ARQ`OANAQOOQQANAXANAXOOQQANAYANAYO%A]QWO,5>oOOQO-E<R-E<RO%AhQ7^O1G5fO#5sQWO,5=[O5bQWO,5=[O!-dQpO,5=[OOQO-E<T-E<TOOQQ1G2u1G2uO$:tQ!LrO,5:kO!@YQ&jO,5=[O%ArQ&jO,5=[O%BTQ&jO,5:kOOQQ<<Kz<<KzOIQQWO<<KzO%.mQWO<<KzO%B_QWO<<KzO%BgQ`O<<KzO){QYO<<KzOIQQWO<<KzOOQQ<<K}<<K}O5lQ!LYO<<K}O#5sQWO<<K}O5bQWO<<K}O%BqQ&jO1G4^O%BvQWO7++ROOQQAN=ZAN=ZO5lQ!LYOAN=ZOOQQ<<Np<<NpOOQQ<<Nq<<NqOOQQ<<LV<<LVO%COQWO<<LVO%CTQYO<<LVO%C[QWO<<NpO%CaQWO<<NqOOQQ1G3a1G3aOOQQANBUANBUO:aQWOANBUO%CfQ7^O<<IiOOQO<<Hk<<HkOOQO7+%q7+%qO%0}Q!LdO7+%qO){QYO7+%qOOQO7+%k7+%kO:aQWO7+%kO!-dQpO7+%kO%CpQ!LYO7+%qO!@YQ&jO7+%kO%C{Q!LYO7+%qO%DZQ&jO7+%kO%DlQ!LYO7+%qOOQ!LSAN>}AN>}O%EQQ!LdO<<KdO%GYQ7^O<<JPO%GgQ7^O1G1wO%IVQ7^O1G2YO%KSQ7^O1G2[O%MPQ7^O<<JqO%M^Q7^O<<IdOOQO1G1b1G1bOOQO7+)n7+)nO%MkQWO7+)nO%MvQWO<<NgO%NOQ`O7+)pOOQO<<KX<<KXO5lQ!LYO<<KYO$,jQWO<<KYOOQO<<KW<<KWO5lQ!LYO<<KXO%NYQ`O<<KYO$,jQWO<<KXOOQQG26lG26lO#IfQWOG26lOOQO7+)y7+)yO5tQWO7+)yO%NaQWO<<NoOOQQG27XG27XO5lQ!LYOG27XOIQQWOG26lOLUQYO1G4ZO%NiQWO7++QO5lQ!LYO1G2vO#5sQWO1G2vO5bQWO1G2vO!-dQpO1G2vO!@YQ&jO1G2vO%2iQ!LrO1G0VO%NqQ&jO1G2vO%.mQWOANAfOOQQANAfANAfOIQQWOANAfO& SQWOANAfO& [Q`OANAfOOQQANAiANAiO5lQ!LYOANAiO#5sQWOANAiOOQO'#Gt'#GtOOQO7+)x7+)xOOQQG22uG22uOOQQANAqANAqO& fQWOANAqOOQQAND[AND[OOQQAND]AND]O& kQYOG27pOOQO<<I]<<I]O%0}Q!LdO<<I]OOQO<<IV<<IVO:aQWO<<IVO){QYO<<I]O!-dQpO<<IVO&%iQ!LYO<<I]O!@YQ&jO<<IVO&%tQ!LYO<<I]O&&SQ7^O7+'eOOQO<<MY<<MYOOQOAN@tAN@tO5lQ!LYOAN@tOOQOAN@sAN@sO$,jQWOAN@tO5lQ!LYOAN@sOOQQLD,WLD,WOOQO<<Me<<MeOOQQLD,sLD,sO#IfQWOLD,WO&'rQ7^O7+)uOOQO7+(b7+(bO5lQ!LYO7+(bO#5sQWO7+(bO5bQWO7+(bO!-dQpO7+(bO!@YQ&jO7+(bOOQQG27QG27QO%.mQWOG27QOIQQWOG27QOOQQG27TG27TO5lQ!LYOG27TOOQQG27]G27]O:aQWOLD-[OOQOAN>wAN>wOOQOAN>qAN>qO%0}Q!LdOAN>wO:aQWOAN>qO){QYOAN>wO!-dQpOAN>qO&'|Q!LYOAN>wO&(XQ7^O<<KdOOQOG26`G26`O5lQ!LYOG26`OOQOG26_G26_OOQQ!$( r!$( rOOQO<<K|<<K|O5lQ!LYO<<K|O#5sQWO<<K|O5bQWO<<K|O!-dQpO<<K|OOQQLD,lLD,lO%.mQWOLD,lOOQQLD,oLD,oOOQQ!$(!v!$(!vOOQOG24cG24cOOQOG24]G24]O%0}Q!LdOG24cO:aQWOG24]O){QYOG24cOOQOLD+zLD+zOOQOANAhANAhO5lQ!LYOANAhO#5sQWOANAhO5bQWOANAhOOQQ!$(!W!$(!WOOQOLD)}LD)}OOQOLD)wLD)wO%0}Q!LdOLD)}OOQOG27SG27SO5lQ!LYOG27SO#5sQWOG27SOOQO!$'Mi!$'MiOOQOLD,nLD,nO5lQ!LYOLD,nOOQO!$(!Y!$(!YOLUQYO'#DnO&)wQbO'#IsOLUQYO'#DfO&*OQ!LdO'#CgO&*iQbO'#CgO&*yQYO,5:rOLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO'#H{O&,yQWO,5<RO&.]QWO,5:}OLUQYO,5;bO!)RQWO'#C{O!)RQWO'#C{OIQQWO'#FhO&-RQWO'#FhOIQQWO'#FjO&-RQWO'#FjOIQQWO'#FxO&-RQWO'#FxOLUQYO,5?mO&*yQYO1G0^O&.dQ7^O'#CgOLUQYO1G1jOIQQWO,5<nO&-RQWO,5<nOIQQWO,5<pO&-RQWO,5<pOIQQWO,5<]O&-RQWO,5<]O&*yQYO1G1kOLUQYO7+&eOIQQWO1G1yO&-RQWO1G1yO&*yQYO7+'VO&*yQYO7+%xOIQQWO7+'xO&-RQWO7+'xO&.nQWO'#EWO&.sQWO'#EWO&.{QWO'#EvO&/QQWO'#EcO&/VQWO'#JRO&/bQWO'#JPO&/mQWO,5:rO&/rQ#tO,5<OO&/yQWO'#FqO&0OQWO'#FqO&0TQWO,5<PO&0]QWO,5:rO&0eQ7^O1G0yO&0lQWO,5<_O&0qQWO,5<_O&0vQWO1G1kO&0{QWO1G0^O&1QQ#tO1G2^O&1XQ#tO1G2^O4TQWO'#FfO5bQWO'#FeOBtQWO'#EVOLUQYO,5;_O!)bQWO'#FsO!)bQWO'#FsOJaQWO,5<rOJaQWO,5<r", ++ stateData: "&2W~O'VOS'WOSSOSTOS~OPTOQTOWyO]cO^hOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#`sO#ppO#t^O$}qO%PtO%RrO%SrO%VuO%XvO%[wO%]wO%_xO%lzO%r{O%t|O%v}O%x!OO%{!PO&R!QO&V!RO&X!SO&Z!TO&]!UO&_!VO'YPO'cQO'oYO'|aO~OPZXYZX^ZXiZXrZXsZXuZX}ZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'TZX'cZX'pZX'wZX'xZX~O!X$jX~P$zO'Q!XO'R!WO'S!ZO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y![O'cQO'oYO'|aO~O|!`O}!]Oz'jPz'tP~P'dO!O!lO~P`OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y9XO'cQO'oYO'|aO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'cQO'oYO'|aO~O|!qO#Q!tO#R!qO'Y9YO!_'qP~P+{O#S!uO~O!X!vO#S!uO~OP#]OY#cOi#QOr!zOs!zOu!{O}#aO!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~O^'gX'T'gX!_'gXz'gX!P'gX%O'gX!X'gX~P.jO!w#dO#k#dOP'hXY'hX^'hXi'hXr'hXs'hXu'hX}'hX!]'hX!^'hX!`'hX!f'hX#W'hX#X'hX#Y'hX#Z'hX#['hX#]'hX#^'hX#a'hX#c'hX#e'hX#f'hX'c'hX'p'hX'w'hX'x'hX~O#_'hX'T'hXz'hX!_'hX'e'hX!P'hX%O'hX!X'hX~P0zO!w#dO~O#v#fO#x#eO$P#kO~O!P#lO#t^O$S#mO$U#oO~O]#rOg$POi#sOj#rOk#rOn$QOp$ROu#yO!P#zO!Z$WO!`#wO#R$XO#p$UO$]$SO$_$TO$b$VO'Y#qO'c#tO'^'`P~O!`$YO~O!X$[O~O^$]O'T$]O~O'Y$aO~O!`$YO'Y$aO'Z$cO'_$dO~Ob$jO!`$YO'Y$aO~O#_#SO~O]$sOr$oO!P$lO!`$nO%P$rO'Y$aO'Z$cO[(UP~O!j$tO~Ou$uO!P$vO'Y$aO~Ou$uO!P$vO%X$zO'Y$aO~O'Y${O~O#`sO%PtO%RrO%SrO%VuO%XvO%[wO%]wO~Oa%UOb%TO!j%RO$}%SO%a%QO~P7xOa%XObmO!P%WO!jlO#`sO$}qO%RrO%SrO%VuO%XvO%[wO%]wO%_xO~O_%[O!w%_O%P%YO'Z$cO~P8wO!`%`O!c%dO~O!`%eO~O!PSO~O^$]O'P%mO'T$]O~O^$]O'P%pO'T$]O~O^$]O'P%rO'T$]O~O'Q!XO'R!WO'S%vO~OPZXYZXiZXrZXsZXuZX}ZX}cX!]ZX!^ZX!`ZX!fZX!wZX!wcX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'cZX'pZX'wZX'xZX~OzZXzcX~P;dO|%xOz&eX}&eX~P){O}!]Oz'jX~OP#]OY#cOi#QOr!zOs!zOu!{O}!]O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~Oz'jX~P>ZOz%}O~Ou&QO!S&[O!T&TO!U&TO'Z$cO~O]&ROj&RO|&UO'f&OO!O'kP!O'vP~P@^Oz'sX}'sX!X'sX!_'sX'p'sX~O!w'sX#S!{X!O'sX~PAVO!w&]Oz'uX}'uX~O}&^Oz'tX~Oz&`O~O!w#dO~PAVOR&dO!P&aO!k&cO'Y$aO~Ob&iO!`$YO'Y$aO~Or$oO!`$nO~O!O&jO~P`Or!zOs!zOu!{O!^!xO!`!yO'cQOP!baY!bai!ba}!ba!]!ba!f!ba#W!ba#X!ba#Y!ba#Z!ba#[!ba#]!ba#^!ba#_!ba#a!ba#c!ba#e!ba#f!ba'p!ba'w!ba'x!ba~O^!ba'T!baz!ba!_!ba'e!ba!P!ba%O!ba!X!ba~PC`O!_&kO~O!X!vO!w&mO'p&lO}'rX^'rX'T'rX~O!_'rX~PExO}&qO!_'qX~O!_&sO~Ou$uO!P$vO#R&tO'Y$aO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y9XO'cQO'oYO'|aO~O]#rOg$POi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'Y&xO'c#tO~O#S&zO~O]#rOg$POi#sOj#rOk#rOn$QOp$ROu#yO!P#zO!Z$WO!`#wO#R$XO#p$UO$]$SO$_$TO$b$VO'Y&xO'c#tO~O'^'mP~PJaO|'OO!_'nP~P){O'f'QO'oYO~OP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!P!bO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'Y'`O'cQO'oYO'|:jO~O!`!yO~O}#aO^$Za'T$Za!_$Zaz$Za!P$Za%O$Za!X$Za~O#`'gO~PIQOr'jO!X'iO!P#wX#s#wX#v#wX#x#wX$P#wX~O!X'iO!P'yX#s'yX#v'yX#x'yX$P'yX~Or'jO~P! eOr'jO!P'yX#s'yX#v'yX#x'yX$P'yX~O!P'lO#s'pO#v'kO#x'kO$P'qO~O|'tO~PLUO#v#fO#x#eO$P'wO~Or$cXu$cX!^$cX'p$cX'w$cX'x$cX~OReX}eX!weX'^eX'^$cX~P!#ZOj'yO~O'Q'{O'R'zO'S'}O~Or(POu(QO'p#ZO'w(SO'x(UO~O'^(OO~P!$dO'^(XO~O]#rOg$POi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'c#tO~O|(]O'Y(YO!_'}P~P!%RO#S(_O~O|(cO'Y(`Oz(OP~P!%RO^(lOi(qOu(iO!S(oO!T(hO!U(hO!`(fO!t(pO$u(kO'Z$cO'f(eO~O!O(nO~P!&yO!^!xOr'bXu'bX'p'bX'w'bX'x'bX}'bX!w'bX~O'^'bX#i'bX~P!'uOR(tO!w(sO}'aX'^'aX~O}(uO'^'`X~O'Y(wO~O!`(|O~O'Y&xO~O!`(fO~Ou$uO|!qO!P$vO#Q!tO#R!qO'Y$aO!_'qP~O!X!vO#S)QO~OP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~O^!Ya}!Ya'T!Yaz!Ya!_!Ya'e!Ya!P!Ya%O!Ya!X!Ya~P!*WOR)YO!P&aO!k)XO%O)WO'_$dO~O'Y${O'^'`P~O!X)]O!P']X^']X'T']X~O!`$YO'_$dO~O!`$YO'Y$aO'_$dO~O!X!vO#S&zO~O%P)iO'Y)eO!O(VP~O})jO[(UX~O'f'QO~OY)nO~O[)oO~O!P$lO'Y$aO'Z$cO[(UP~Ou$uO|)tO!P$vO'Y$aOz'tP~O]&XOj&XO|)uO'f'QO!O'vP~O})vO^(RX'T(RX~O!w)zO'_$dO~OR)}O!P#zO'_$dO~O!P*PO~Or*RO!PSO~O!j*WO~Ob*]O~O'Y(wO!O(TP~Ob$jO~O%PtO'Y${O~P8wOY*cO[*bO~OPTOQTO]cOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#t^O$}qO'cQO'oYO'|aO~O!P!bO#p!kO'Y9XO~P!1mO[*bO^$]O'T$]O~O^*gO#`*iO%R*iO%S*iO~P){O!`%`O~O%r*nO~O!P*pO~O&S*sO&T*rOP&QaQ&QaW&Qa]&Qa^&Qaa&Qab&Qag&Qai&Qaj&Qak&Qan&Qap&Qau&Qaw&Qax&Qay&Qa!P&Qa!Z&Qa!`&Qa!c&Qa!d&Qa!e&Qa!f&Qa!g&Qa!j&Qa#`&Qa#p&Qa#t&Qa$}&Qa%P&Qa%R&Qa%S&Qa%V&Qa%X&Qa%[&Qa%]&Qa%_&Qa%l&Qa%r&Qa%t&Qa%v&Qa%x&Qa%{&Qa&R&Qa&V&Qa&X&Qa&Z&Qa&]&Qa&_&Qa'O&Qa'Y&Qa'c&Qa'o&Qa'|&Qa!O&Qa%y&Qa_&Qa&O&Qa~O'Y*vO~O'e*yO~Oz&ea}&ea~P!*WO}!]Oz'ja~Oz'ja~P>ZO}&^Oz'ta~O}tX}!VX!OtX!O!VX!XtX!X!VX!`!VX!wtX'_!VX~O!X+QO!w+PO}#PX}'lX!O#PX!O'lX!X'lX!`'lX'_'lX~O!X+SO!`$YO'_$dO}!RX!O!RX~O]&POj&POu&QO'f(eO~OP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!P!bO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'cQO'oYO'|:jO~O'Y9uO~P!;kO}+WO!O'kX~O!O+YO~O!X+QO!w+PO}#PX!O#PX~O}+ZO!O'vX~O!O+]O~O]&POj&POu&QO'Z$cO'f(eO~O!T+^O!U+^O~P!>iOu$uO|+aO!P$vO'Y$aOz&jX}&jX~O^+fO!S+iO!T+eO!U+eO!n+mO!o+kO!p+lO!q+jO!t+nO'Z$cO'f(eO'o+cO~O!O+hO~P!?jOR+sO!P&aO!k+rO~O!w+yO}'ra!_'ra^'ra'T'ra~O!X!vO~P!@tO}&qO!_'qa~Ou$uO|+|O!P$vO#Q,OO#R+|O'Y$aO}&lX!_&lX~O^!zi}!zi'T!ziz!zi!_!zi'e!zi!P!zi%O!zi!X!zi~P!*WO#S!va}!va!_!va!w!va!P!va^!va'T!vaz!va~P!$dO#S'bXP'bXY'bX^'bXi'bXs'bX!]'bX!`'bX!f'bX#W'bX#X'bX#Y'bX#Z'bX#['bX#]'bX#^'bX#_'bX#a'bX#c'bX#e'bX#f'bX'T'bX'c'bX!_'bXz'bX!P'bX'e'bX%O'bX!X'bX~P!'uO},XO'^'mX~P!$dO'^,ZO~O},[O!_'nX~P!*WO!_,_O~Oz,`O~OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'cQOY#Vi^#Vii#Vi}#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O#W#Vi~P!FRO#W#OO~P!FROP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO'cQOY#Vi^#Vi}#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~Oi#Vi~P!HmOi#QO~P!HmOP#]Oi#QOr!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO'cQO^#Vi}#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P!KXOY#cO!]#SO#]#SO#^#SO#_#SO~P!KXOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO'cQO^#Vi}#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O'w#Vi~P!NPO'w!|O~P!NPOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO'cQO'w!|O^#Vi}#Vi#e#Vi#f#Vi'T#Vi'p#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O'x#Vi~P#!kO'x!}O~P#!kOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO'cQO'w!|O'x!}O~O^#Vi}#Vi#f#Vi'T#Vi'p#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~P#%VOPZXYZXiZXrZXsZXuZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'cZX'pZX'wZX'xZX}ZX!OZX~O#iZX~P#'jOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO#e9eO#f9fO'cQO'p#ZO'w!|O'x!}O~O#i,bO~P#)tOP'hXY'hXi'hXr'hXs'hXu'hX!]'hX!^'hX!`'hX!f'hX#W'hX#X'hX#Y'hX#Z'hX#['hX#]'hX#^'hX#a'hX#c'hX#e'hX#f'hX'c'hX'p'hX'w'hX'x'hX}'hX~O!w9jO#k9jO#_'hX#i'hX!O'hX~P#+oO^&oa}&oa'T&oa!_&oa'e&oaz&oa!P&oa%O&oa!X&oa~P!*WOP#ViY#Vi^#Vii#Vis#Vi}#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'c#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~P!$dO^#ji}#ji'T#jiz#ji!_#ji'e#ji!P#ji%O#ji!X#ji~P!*WO#v,dO#x,dO~O#v,eO#x,eO~O!X'iO!w,fO!P#|X#s#|X#v#|X#x#|X$P#|X~O|,gO~O!P'lO#s,iO#v'kO#x'kO$P,jO~O}9gO!O'gX~P#)tO!O,kO~O$P,mO~O'Q'{O'R'zO'S,pO~O],sOj,sOz,tO~O}cX!XcX!_cX!_$cX'pcX~P!#ZO!_,zO~P!$dO},{O!X!vO'p&lO!_'}X~O!_-QO~Oz$cX}$cX!X$jX~P!#ZO}-SOz(OX~P!$dO!X-UO~Oz-WO~O|(]O'Y$aO!_'}P~Oi-[O!X!vO!`$YO'_$dO'p&lO~O!X)]O~O!O-bO~P!&yO!T-cO!U-cO'Z$cO'f(eO~Ou-eO'f(eO~O!t-fO~O'Y${O}&tX'^&tX~O}(uO'^'`a~Or-kOs-kOu-lO'poa'woa'xoa}oa!woa~O'^oa#ioa~P#7POr(POu(QO'p$[a'w$[a'x$[a}$[a!w$[a~O'^$[a#i$[a~P#7uOr(POu(QO'p$^a'w$^a'x$^a}$^a!w$^a~O'^$^a#i$^a~P#8hO]-mO~O#S-nO~O'^$la}$la#i$la!w$la~P!$dO#S-qO~OR-zO!P&aO!k-yO%O-xO~O'^-{O~O]#rOi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'c#tO~Og-}O'Y-|O~P#:_O!X)]O!P']a^']a'T']a~O#S.TO~OYZX}cX!OcX~O}.UO!O(VX~O!O.WO~OY.XO~O'Y)eO~O!P$lO'Y$aO[&|X}&|X~O})jO[(Ua~O!_.^O~P!*WO].`O~OY.aO~O[.bO~OR-zO!P&aO!k-yO%O-xO'_$dO~O})vO^(Ra'T(Ra~O!w.hO~OR.kO!P#zO~O'f'QO!O(SP~OR.uO!P.qO!k.tO%O.sO'_$dO~OY/PO}.}O!O(TX~O!O/QO~O[/SO^$]O'T$]O~O]/TO~O#_/VO%p/WO~P0zO!w#dO#_/VO%p/WO~O^/XO~P){O^/ZO~O%y/_OP%wiQ%wiW%wi]%wi^%wia%wib%wig%wii%wij%wik%win%wip%wiu%wiw%wix%wiy%wi!P%wi!Z%wi!`%wi!c%wi!d%wi!e%wi!f%wi!g%wi!j%wi#`%wi#p%wi#t%wi$}%wi%P%wi%R%wi%S%wi%V%wi%X%wi%[%wi%]%wi%_%wi%l%wi%r%wi%t%wi%v%wi%x%wi%{%wi&R%wi&V%wi&X%wi&Z%wi&]%wi&_%wi'O%wi'Y%wi'c%wi'o%wi'|%wi!O%wi_%wi&O%wi~O_/eO!O/cO&O/dO~P`O!PSO!`/hO~O}#aO'e$Za~Oz&ei}&ei~P!*WO}!]Oz'ji~O}&^Oz'ti~Oz/lO~O}!Ra!O!Ra~P#)tO]&POj&PO|/rO'f(eO}&fX!O&fX~P@^O}+WO!O'ka~O]&XOj&XO|)uO'f'QO}&kX!O&kX~O}+ZO!O'va~Oz'ui}'ui~P!*WO^$]O!X!vO!`$YO!f/}O!w/{O'T$]O'_$dO'p&lO~O!O0QO~P!?jO!T0RO!U0RO'Z$cO'f(eO'o+cO~O!S0SO~P#HXO!PSO!S0SO!q0UO!t0VO~P#HXO!S0SO!o0XO!p0XO!q0UO!t0VO~P#HXO!P&aO~O!P&aO~P!$dO}'ri!_'ri^'ri'T'ri~P!*WO!w0bO}'ri!_'ri^'ri'T'ri~O}&qO!_'qi~Ou$uO!P$vO#R0dO'Y$aO~O#SoaPoaYoa^oaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa'Toa'coa!_oazoa!Poa'eoa%Ooa!Xoa~P#7PO#S$[aP$[aY$[a^$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a'T$[a'c$[a!_$[az$[a!P$[a'e$[a%O$[a!X$[a~P#7uO#S$^aP$^aY$^a^$^ai$^as$^a!]$^a!^$^a!`$^a!f$^a#W$^a#X$^a#Y$^a#Z$^a#[$^a#]$^a#^$^a#_$^a#a$^a#c$^a#e$^a#f$^a'T$^a'c$^a!_$^az$^a!P$^a'e$^a%O$^a!X$^a~P#8hO#S$laP$laY$la^$lai$las$la}$la!]$la!^$la!`$la!f$la#W$la#X$la#Y$la#Z$la#[$la#]$la#^$la#_$la#a$la#c$la#e$la#f$la'T$la'c$la!_$laz$la!P$la!w$la'e$la%O$la!X$la~P!$dO^!zq}!zq'T!zqz!zq!_!zq'e!zq!P!zq%O!zq!X!zq~P!*WO}&gX'^&gX~PJaO},XO'^'ma~O|0lO}&hX!_&hX~P){O},[O!_'na~O},[O!_'na~P!*WO#i!ba!O!ba~PC`O#i!Ya}!Ya!O!Ya~P#)tO!P1PO#t^O#}1QO~O!O1UO~O'e1VO~P!$dO^$Wq}$Wq'T$Wqz$Wq!_$Wq'e$Wq!P$Wq%O$Wq!X$Wq~P!*WOz1WO~O],sOj,sO~Or(POu(QO'x(UO'p$vi'w$vi}$vi!w$vi~O'^$vi#i$vi~P$(xOr(POu(QO'p$xi'w$xi'x$xi}$xi!w$xi~O'^$xi#i$xi~P$)kO#i1XO~P!$dO|1ZO'Y$aO}&pX!_&pX~O},{O!_'}a~O},{O!X!vO!_'}a~O},{O!X!vO'p&lO!_'}a~O'^$ei}$ei#i$ei!w$ei~P!$dO|1bO'Y(`Oz&rX}&rX~P!%RO}-SOz(Oa~O}-SOz(Oa~P!$dO!X!vO~O!X!vO#_1lO~Oi1pO!X!vO'p&lO~O}'ai'^'ai~P!$dO!w1sO}'ai'^'ai~P!$dO!_1vO~O^$Xq}$Xq'T$Xqz$Xq!_$Xq'e$Xq!P$Xq%O$Xq!X$Xq~P!*WO}1zO!P(PX~P!$dO!P&aO%O1}O~O!P&aO%O1}O~P!$dO!P$cX$sZX^$cX'T$cX~P!#ZO$s2ROrfXufX!PfX'pfX'wfX'xfX^fX'TfX~O$s2RO~O%P2YO'Y)eO}&{X!O&{X~O}.UO!O(Va~OY2^O~O[2_O~O]2bO~OR2dO!P&aO!k2cO%O1}O~O^$]O'T$]O~P!$dO!P#zO~P!$dO}2iO!w2kO!O(SX~O!O2lO~Ou(iO!S2uO!T2nO!U2nO!n2tO!o2sO!p2sO!t2rO'Z$cO'f(eO'o+cO~O!O2qO~P$1yOR2|O!P.qO!k2{O%O2zO~OR2|O!P.qO!k2{O%O2zO'_$dO~O'Y(wO}&zX!O&zX~O}.}O!O(Ta~O'f3VO~O]3XO~O[3ZO~O!_3^O~P){O^3`O~O^3`O~P){O#_3bO%p3cO~PExO_/eO!O3gO&O/dO~P`O!X3iO~O&T3jOP&QqQ&QqW&Qq]&Qq^&Qqa&Qqb&Qqg&Qqi&Qqj&Qqk&Qqn&Qqp&Qqu&Qqw&Qqx&Qqy&Qq!P&Qq!Z&Qq!`&Qq!c&Qq!d&Qq!e&Qq!f&Qq!g&Qq!j&Qq#`&Qq#p&Qq#t&Qq$}&Qq%P&Qq%R&Qq%S&Qq%V&Qq%X&Qq%[&Qq%]&Qq%_&Qq%l&Qq%r&Qq%t&Qq%v&Qq%x&Qq%{&Qq&R&Qq&V&Qq&X&Qq&Z&Qq&]&Qq&_&Qq'O&Qq'Y&Qq'c&Qq'o&Qq'|&Qq!O&Qq%y&Qq_&Qq&O&Qq~O}#Pi!O#Pi~P#)tO!w3lO}#Pi!O#Pi~O}!Ri!O!Ri~P#)tO^$]O!w3sO'T$]O~O^$]O!X!vO!w3sO'T$]O~O!T3wO!U3wO'Z$cO'f(eO'o+cO~O^$]O!X!vO!`$YO!f3xO!w3sO'T$]O'_$dO'p&lO~O!S3yO~P$:cO!S3yO!q3|O!t3}O~P$:cO^$]O!X!vO!f3xO!w3sO'T$]O'p&lO~O}'rq!_'rq^'rq'T'rq~P!*WO}&qO!_'qq~O#S$viP$viY$vi^$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi'T$vi'c$vi!_$viz$vi!P$vi'e$vi%O$vi!X$vi~P$(xO#S$xiP$xiY$xi^$xii$xis$xi!]$xi!^$xi!`$xi!f$xi#W$xi#X$xi#Y$xi#Z$xi#[$xi#]$xi#^$xi#_$xi#a$xi#c$xi#e$xi#f$xi'T$xi'c$xi!_$xiz$xi!P$xi'e$xi%O$xi!X$xi~P$)kO#S$eiP$eiY$ei^$eii$eis$ei}$ei!]$ei!^$ei!`$ei!f$ei#W$ei#X$ei#Y$ei#Z$ei#[$ei#]$ei#^$ei#_$ei#a$ei#c$ei#e$ei#f$ei'T$ei'c$ei!_$eiz$ei!P$ei!w$ei'e$ei%O$ei!X$ei~P!$dO}&ga'^&ga~P!$dO}&ha!_&ha~P!*WO},[O!_'ni~O#i!zi}!zi!O!zi~P#)tOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'cQOY#Vii#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~O#W#Vi~P$CyO#W9[O~P$CyOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O'cQOY#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~Oi#Vi~P$FROi9^O~P$FROP#]Oi9^Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O'cQO#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P$HZOY9iO!]9`O#]9`O#^9`O#_9`O~P$HZOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO'cQO#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'x#Vi}#Vi!O#Vi~O'w#Vi~P$JoO'w!|O~P$JoOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO'cQO'w!|O#e#Vi#f#Vi#i#Vi'p#Vi}#Vi!O#Vi~O'x#Vi~P$LwO'x!}O~P$LwOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO#e9eO'cQO'w!|O'x!}O~O#f#Vi#i#Vi'p#Vi}#Vi!O#Vi~P% PO^#gy}#gy'T#gyz#gy!_#gy'e#gy!P#gy%O#gy!X#gy~P!*WOP#ViY#Vii#Vis#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'c#Vi}#Vi!O#Vi~P!$dO!^!xOP'bXY'bXi'bXr'bXs'bXu'bX!]'bX!`'bX!f'bX#W'bX#X'bX#Y'bX#Z'bX#['bX#]'bX#^'bX#_'bX#a'bX#c'bX#e'bX#f'bX#i'bX'c'bX'p'bX'w'bX'x'bX}'bX!O'bX~O#i#ji}#ji!O#ji~P#)tO!O4_O~O}&oa!O&oa~P#)tO!X!vO'p&lO}&pa!_&pa~O},{O!_'}i~O},{O!X!vO!_'}i~Oz&ra}&ra~P!$dO!X4fO~O}-SOz(Oi~P!$dO}-SOz(Oi~Oz4lO~O!X!vO#_4rO~Oi4sO!X!vO'p&lO~Oz4uO~O'^$gq}$gq#i$gq!w$gq~P!$dO^$Xy}$Xy'T$Xyz$Xy!_$Xy'e$Xy!P$Xy%O$Xy!X$Xy~P!*WO}1zO!P(Pa~O!P&aO%O4zO~O!P&aO%O4zO~P!$dO^!zy}!zy'T!zyz!zy!_!zy'e!zy!P!zy%O!zy!X!zy~P!*WOY4}O~O}.UO!O(Vi~O]5SO~O[5TO~O'f'QO}&wX!O&wX~O}2iO!O(Sa~O!O5bO~P$1yOu-eO'f(eO'o+cO~O!S5eO!T5dO!U5dO!t0VO'Z$cO'f(eO'o+cO~O!o5fO!p5fO~P%-iO!T5dO!U5dO'Z$cO'f(eO'o+cO~O!P.qO~O!P.qO%O5hO~O!P.qO%O5hO~P!$dOR5mO!P.qO!k5lO%O5hO~OY5rO}&za!O&za~O}.}O!O(Ti~O]5uO~O!_5vO~O!_5wO~O!_5xO~O!_5xO~P){O^5zO~O!X5}O~O!_6PO~O}'ui!O'ui~P#)tO^$]O'T$]O~P!*WO^$]O!w6UO'T$]O~O^$]O!X!vO!w6UO'T$]O~O!T6ZO!U6ZO'Z$cO'f(eO'o+cO~O^$]O!X!vO!f6[O!w6UO'T$]O'p&lO~O!`$YO'_$dO~P%2TO!S6]O~P%1rO}'ry!_'ry^'ry'T'ry~P!*WO#S$gqP$gqY$gq^$gqi$gqs$gq}$gq!]$gq!^$gq!`$gq!f$gq#W$gq#X$gq#Y$gq#Z$gq#[$gq#]$gq#^$gq#_$gq#a$gq#c$gq#e$gq#f$gq'T$gq'c$gq!_$gqz$gq!P$gq!w$gq'e$gq%O$gq!X$gq~P!$dO}&hi!_&hi~P!*WO#i!zq}!zq!O!zq~P#)tOr-kOs-kOu-lOPoaYoaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa#ioa'coa'poa'woa'xoa}oa!Ooa~Or(POu(QOP$[aY$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a#i$[a'c$[a'p$[a'w$[a'x$[a}$[a!O$[a~Or(POu(QOP$^aY$^ai$^as$^a!]$^a!^$^a!`$^a!f$^a#W$^a#X$^a#Y$^a#Z$^a#[$^a#]$^a#^$^a#_$^a#a$^a#c$^a#e$^a#f$^a#i$^a'c$^a'p$^a'w$^a'x$^a}$^a!O$^a~OP$laY$lai$las$la!]$la!^$la!`$la!f$la#W$la#X$la#Y$la#Z$la#[$la#]$la#^$la#_$la#a$la#c$la#e$la#f$la#i$la'c$la}$la!O$la~P!$dO#i$Wq}$Wq!O$Wq~P#)tO#i$Xq}$Xq!O$Xq~P#)tO!O6gO~O'^$zy}$zy#i$zy!w$zy~P!$dO!X!vO}&pi!_&pi~O!X!vO'p&lO}&pi!_&pi~O},{O!_'}q~Oz&ri}&ri~P!$dO}-SOz(Oq~Oz6nO~P!$dOz6nO~O}'ay'^'ay~P!$dO}&ua!P&ua~P!$dO!P$rq^$rq'T$rq~P!$dOY6vO~O}.UO!O(Vq~O]6yO~O!P&aO%O6zO~O!P&aO%O6zO~P!$dO!w6{O}&wa!O&wa~O}2iO!O(Si~P#)tO!T7RO!U7RO'Z$cO'f(eO'o+cO~O!S7TO!t3}O~P%ArO!P.qO%O7WO~O!P.qO%O7WO~P!$dO'f7^O~O}.}O!O(Tq~O!_7aO~O!_7aO~P){O!_7cO~O!_7dO~O}#Py!O#Py~P#)tO^$]O!w7jO'T$]O~O^$]O!X!vO!w7jO'T$]O~O!T7mO!U7mO'Z$cO'f(eO'o+cO~O^$]O!X!vO!f7nO!w7jO'T$]O'p&lO~O#S$zyP$zyY$zy^$zyi$zys$zy}$zy!]$zy!^$zy!`$zy!f$zy#W$zy#X$zy#Y$zy#Z$zy#[$zy#]$zy#^$zy#_$zy#a$zy#c$zy#e$zy#f$zy'T$zy'c$zy!_$zyz$zy!P$zy!w$zy'e$zy%O$zy!X$zy~P!$dO#i#gy}#gy!O#gy~P#)tOP$eiY$eii$eis$ei!]$ei!^$ei!`$ei!f$ei#W$ei#X$ei#Y$ei#Z$ei#[$ei#]$ei#^$ei#_$ei#a$ei#c$ei#e$ei#f$ei#i$ei'c$ei}$ei!O$ei~P!$dOr(POu(QO'x(UOP$viY$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi#i$vi'c$vi'p$vi'w$vi}$vi!O$vi~Or(POu(QOP$xiY$xii$xis$xi!]$xi!^$xi!`$xi!f$xi#W$xi#X$xi#Y$xi#Z$xi#[$xi#]$xi#^$xi#_$xi#a$xi#c$xi#e$xi#f$xi#i$xi'c$xi'p$xi'w$xi'x$xi}$xi!O$xi~O#i$Xy}$Xy!O$Xy~P#)tO#i!zy}!zy!O!zy~P#)tO!X!vO}&pq!_&pq~O},{O!_'}y~Oz&rq}&rq~P!$dOz7tO~P!$dO}.UO!O(Vy~O}2iO!O(Sq~O!T8QO!U8QO'Z$cO'f(eO'o+cO~O!P.qO%O8TO~O!P.qO%O8TO~P!$dO!_8WO~O&T8XOP&Q!ZQ&Q!ZW&Q!Z]&Q!Z^&Q!Za&Q!Zb&Q!Zg&Q!Zi&Q!Zj&Q!Zk&Q!Zn&Q!Zp&Q!Zu&Q!Zw&Q!Zx&Q!Zy&Q!Z!P&Q!Z!Z&Q!Z!`&Q!Z!c&Q!Z!d&Q!Z!e&Q!Z!f&Q!Z!g&Q!Z!j&Q!Z#`&Q!Z#p&Q!Z#t&Q!Z$}&Q!Z%P&Q!Z%R&Q!Z%S&Q!Z%V&Q!Z%X&Q!Z%[&Q!Z%]&Q!Z%_&Q!Z%l&Q!Z%r&Q!Z%t&Q!Z%v&Q!Z%x&Q!Z%{&Q!Z&R&Q!Z&V&Q!Z&X&Q!Z&Z&Q!Z&]&Q!Z&_&Q!Z'O&Q!Z'Y&Q!Z'c&Q!Z'o&Q!Z'|&Q!Z!O&Q!Z%y&Q!Z_&Q!Z&O&Q!Z~O^$]O!w8^O'T$]O~O^$]O!X!vO!w8^O'T$]O~OP$gqY$gqi$gqs$gq!]$gq!^$gq!`$gq!f$gq#W$gq#X$gq#Y$gq#Z$gq#[$gq#]$gq#^$gq#_$gq#a$gq#c$gq#e$gq#f$gq#i$gq'c$gq}$gq!O$gq~P!$dO}&wq!O&wq~P#)tO^$]O!w8sO'T$]O~OP$zyY$zyi$zys$zy!]$zy!^$zy!`$zy!f$zy#W$zy#X$zy#Y$zy#Z$zy#[$zy#]$zy#^$zy#_$zy#a$zy#c$zy#e$zy#f$zy#i$zy'c$zy}$zy!O$zy~P!$dO'e'gX~P.jO'eZXzZX!_ZX%pZX!PZX%OZX!XZX~P$zO!XcX!_ZX!_cX'pcX~P;dOP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!PSO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'Y'`O'cQO'oYO'|:jO~O}9gO!O$Za~O]#rOg$POi#sOj#rOk#rOn$QOp9lOu#yO!P#zO!Z:oO!`#wO#R9rO#p$UO$]9nO$_9pO$b$VO'Y&xO'c#tO~O#`'gO~P&-RO!OZX!OcX~P;dO#S9ZO~O!X!vO#S9ZO~O!w9jO~O#_9`O~O!w9sO}'uX!O'uX~O!w9jO}'sX!O'sX~O#S9tO~O'^9vO~P!$dO#S9{O~O#S9|O~O!X!vO#S9}O~O!X!vO#S9tO~O#i:OO~P#)tO#S:PO~O#S:QO~O#S:RO~O#S:SO~O#i:TO~P!$dO#i:UO~P!$dO#t~!^!n!p!q#Q#R'|$]$_$b$s$}%O%P%V%X%[%]%_%a~TS#t'|#Xy'V'W'f'W'Y#v#x#v~", ++ goto: "#Dq(ZPPPPPPP([P(lP*`PPPP-uPP.[3l5`5sP5sPPP5s5sP5sP7aPP7fP7zPPPP<ZPPPP<Z>yPPP?PA[P<ZPCuPPPPEm<ZPPPPPGf<ZPPJeKbPPPPKfMOPMWNXPKb<Z<Z!#`!&X!*x!*x!.VPPP!.^!1Q<ZPPPPPPPPPP!3uP!5WPP<Z!6eP<ZP<Z<Z<Z<ZP<Z!8xPP!;oP!>bP!>f!>n!>r!>rP!;lP!>v!>vP!AiP!Am<Z<Z!As!De5sP5sP5s5sP!Eh5s5s!G`5s!Ib5s!KS5s5s!Kp!Mj!Mj!Mn!Mj!MvP!MjP5s!Nr5s# |5s5s-uPPP##ZPP##s##sP##sP#$Y##sPP#$`P#$VP#$V#$rMS#$V#%a#%g#%j([#%m([P#%t#%t#%tP([P([P([P([PP([P#%z#%}P#%}([PPP([P([P([P([P([P([([#&R#&]#&c#&i#&w#&}#'T#'_#'e#'o#'u#(T#(Z#(a#(o#)U#*h#*v#*|#+S#+Y#+`#+j#+p#+v#,Q#,d#,jPPPPPPPPP#,pPP#-d#1bPP#2x#3P#3XP#7ePP#7i#9|#?v#?z#?}#@Q#@]#@`PP#@c#@g#AU#Ay#A}#BaPP#Be#Bk#BoP#Br#Bv#By#Ci#DP#DU#DX#D[#Db#De#Di#DmmhOSj}!m$[%c%f%g%i*k*p/_/bQ$imQ$ppQ%ZyS&T!b+WQ&h!iS(h#z(mQ)c$jQ)p$rQ*[%TQ+^&[S+e&a+gQ+w&iQ-c(oQ.|*]Y0R+i+j+k+l+mS2n.q2pU3w0S0U0XU5d2s2t2uS6Z3y3|S7R5e5fQ7m6]R8Q7T$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!j'b#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ(x$RQ)h$lQ*^%WQ*e%`Q,R9kQ.O)]Q.Z)iQ/U*cQ2X.UQ3T.}Q4W9lR5P2YpeOSjy}!m$[%Y%c%f%g%i*k*p/_/bR*`%[&WVOSTjkn}!S!W!]!j!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:l:mW!cRU!`&UQ$blQ$hmS$mp$rv$wrs!q!t$Y$u&^&q&t)t)u)v*i+Q+a+|,O/h0dQ%PwQ&e!hQ&g!iS([#w(fS)b$i$jQ)f$lQ)s$tQ*V%RQ*Z%TS+v&h&iQ-P(]Q.S)cQ.Y)iQ.[)jQ._)nQ.w*WS.{*[*]Q0`+wQ1Y,{Q2W.UQ2[.XQ2a.aQ3S.|Q4c1ZQ5O2YQ5R2^Q6u4}R7w6v!Y$fm!i$h$i$j&S&g&h&i(g)b)c+T+d+v+w-].S/w0O0T0`1o3v3{6X7k8_Q)Z$bQ){$|Q*O$}Q*Y%TQ.c)sQ.v*VU.z*Z*[*]Q2}.wS3R.{.|Q5_2mQ5q3SS7P5`5cS8O7Q7SQ8i8PR8x8jW#}a$d(u:jS$|t%YQ$}uQ%OvR)y$z$V#|a!v!x#c#w#y$S$T$X&d'z(T(V(W(_(c(s(t)W)Y)])z)}+s,X-S-U-n-x-z.h.k.s.u1X1b1l1s1z1}2R2d2z2|4f4r4z5h5m6z7W8T9i9m9n9o9p9q9r9w9x9y9z9{9|:P:Q:T:U:j:p:qV(y$R9k9lU&X!b$v+ZQ'R!zQ)m$oQ.l*PQ1t-kR5Z2i&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m$]#`Z!_!n$`%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,c,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:c&ZcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ&V!bR/s+WY&P!b&T&[+W+^S(g#z(mS+d&a+gS-](h(oQ-^(iQ-d(pQ.n*RU0O+e+i+jU0T+k+l+mS0Y+n2rQ1o-cQ1q-eQ1r-fS2m.q2pU3v0R0S0UQ3z0VQ3{0XS5`2n2uS5c2s2tU6X3w3y3|Q6^3}S7Q5d5eQ7S5fS7k6Z6]S8P7R7TQ8_7mR8j8QlhOSj}!m$[%c%f%g%i*k*p/_/bQ%k!QS&u!u9ZQ)`$gQ*T%PQ*U%QQ+t&fS,V&z9tS-p)Q9}Q.Q)aQ.p*SQ/f*rQ/g*sQ/o+RQ0W+kQ0^+uS1y-q:RQ2S.RS2V.T:SQ3m/qQ3p/yQ4P0_Q4|2TQ6O3jQ6R3oQ6V3uQ6_4QQ7e6PQ7h6WQ8Z7iQ8n8XQ8p8]R8{8r$W#_Z!_!n%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:cU(r#{&y0}T)U$`,c$W#^Z!_!n%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:cQ'c#_S)T$`,cR-r)U&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ%f{Q%g|Q%i!OQ%j!PR/^*nQ&b!hQ)V$bQ+q&eS-w)Z)sS0Z+o+pW1|-t-u-v.cS4O0[0]U4y2O2P2QU6s4x5V5WQ7v6tR8e7yT+f&a+gS+d&a+gU0O+e+i+jU0T+k+l+mS0Y+n2rS2m.q2pU3v0R0S0UQ3z0VQ3{0XS5`2n2uS5c2s2tU6X3w3y3|Q6^3}S7Q5d5eQ7S5fS7k6Z6]S8P7R7TQ8_7mR8j8QS+f&a+gT2o.q2pS&o!p/[Q-O([Q-Z(gS/}+d2mQ1_-PS1i-[-dU3x0T0Y5cQ4b1YS4p1p1rU6[3z3{7SQ6i4cQ6r4sR7n6^Q!wXS&n!p/[Q)R$ZQ)^$eQ)d$kQ+z&oQ,}([Q-Y(gQ-_(jQ.P)_Q.x*XS/|+d2mS1^-O-PS1h-Z-dQ1k-^Q1n-`Q3P.yW3t/}0T0Y5cQ4a1YQ4e1_S4j1i1rQ4q1qQ5o3QW6Y3x3z3{7SS6h4b4cQ6m4lQ6p4pQ6}5^Q7[5pS7l6[6^Q7p6iQ7r6nQ7u6rQ7|7OQ8V7]Q8`7nQ8c7tQ8g7}Q8v8hQ9O8wQ9S9PQ:]:WQ:f:aR:g:b$nWORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sS!wn!j!j:V#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR:]:l$nXORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sQ$Zb!Y$em!i$h$i$j&S&g&h&i(g)b)c+T+d+v+w-].S/w0O0T0`1o3v3{6X7k8_S$kn!jQ)_$fQ*X%TW.y*Y*Z*[*]U3Q.z.{.|Q5^2mS5p3R3SU7O5_5`5cQ7]5qU7}7P7Q7SS8h8O8PS8w8i8jQ9P8x!j:W#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ:a:kR:b:l$f]OSTjk}!S!W!]!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sU!gRU!`v$wrs!q!t$Y$u&^&q&t)t)u)v*i+Q+a+|,O/h0dQ*f%`!h:X#[#l't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR:[&US&Y!b$vR/u+Z$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!j'b#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR*e%`$noORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sQ'R!z!k:Y#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m!h#UZ!_$`%w%{&v&}'[']'^'_'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9V!R9b'a'r+U,c/m/p0o0w0x0y0z1O1T3n4V4[4]5]6Q6a6e6f7z:c!d#WZ!_$`%w%{&v&}'^'_'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9V}9d'a'r+U,c/m/p0o0y0z1O1T3n4V4[4]5]6Q6a6e6f7z:c!`#[Z!_$`%w%{&v&}'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9Vl(W#u&{)P,y-R-g-h0i1w4`4t:^:h:ix:m'a'r+U,c/m/p0o1O1T3n4V4[4]5]6Q6a6e6f7z:c!`:p&w'f(Z(a+p,U,n-V-s-v.g.i0]0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7YZ:q0|4Z6b7o8a&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mS#m`#nR1Q,f&a_ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l#n$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,f,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mT#i^#oS#g^#oT'k#j'oT#h^#oT'm#j'o&a`ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l#n$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,f,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mT#m`#nQ#p`R'v#n$nbORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!k:k#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m#RdOSUj}!S!W!m!{#l$[%[%_%`%c%e%f%g%i%m&Q&c't)X*g*k*p+r,g-l-y.t/V/W/X/Z/_/b/d1P2c2{3`3b3c5l5zt#{a!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:q!|&y!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:TQ(}$VQ,r(Pc0}9i9n9p9r9x9z9|:Q:Ut#xa!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:qS(j#z(mQ)O$WQ-`(k!|:_!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:Tb:`9i9n9p9r9x9z9|:Q:UQ:d:nR:e:ot#{a!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:q!|&y!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:Tc0}9i9n9p9r9x9z9|:Q:UlfOSj}!m$[%c%f%g%i*k*p/_/bQ(b#yQ*w%pQ*x%rR1a-S$U#|a!v!x#c#w#y$S$T$X&d'z(T(V(W(_(c(s(t)W)Y)])z)}+s,X-S-U-n-x-z.h.k.s.u1X1b1l1s1z1}2R2d2z2|4f4r4z5h5m6z7W8T9i9m9n9o9p9q9r9w9x9y9z9{9|:P:Q:T:U:j:p:qQ)|$}Q.j*OQ2g.iR5Y2hT(l#z(mS(l#z(mT2o.q2pQ)^$eQ-_(jQ.P)_Q.x*XQ3P.yQ5o3QQ6}5^Q7[5pQ7|7OQ8V7]Q8g7}Q8v8hQ9O8wR9S9Pl(T#u&{)P,y-R-g-h0i1w4`4t:^:h:i!`9w&w'f(Z(a+p,U,n-V-s-v.g.i0]0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7YZ9x0|4Z6b7o8an(V#u&{)P,w,y-R-g-h0i1w4`4t:^:h:i!b9y&w'f(Z(a+p,U,n-V-s-v.g.i0]0f0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7Y]9z0|4Z6b6c7o8apeOSjy}!m$[%Y%c%f%g%i*k*p/_/bQ%VxR*g%`peOSjy}!m$[%Y%c%f%g%i*k*p/_/bR%VxQ*Q%OR.f)yqeOSjy}!m$[%Y%c%f%g%i*k*p/_/bQ.r*VS2y.v.wW5g2v2w2x2}U7V5i5j5kU8R7U7X7YQ8k8SR8y8lQ%^yR*a%YR3W/PR7_5rS$mp$rR.[)jQ%czR*k%dR*q%jT/`*p/bQjOQ!mST$_j!mQ'|#tR,o'|Q!YQR%u!YQ!^RU%y!^%z*|Q%z!_R*|%{Q+X&VR/t+XQ,Y&{R0j,YQ,]&}S0m,]0nR0n,^Q+g&aR0P+gQ&_!eQ*}%|T+b&_*}Q+[&YR/v+[Q&r!rQ+{&pU,P&r+{0eR0e,QQ'o#jR,h'oQ#n`R'u#nQ#bZU'e#b*z9hQ*z9VR9h'rQ,|([W1[,|1]4d6jU1],}-O-PS4d1^1_R6j4e#q(R#u&w&{'f(Z(a(z({)P+p,S,T,U,n,w,x,y-R-V-g-h-s-v.g.i0]0f0g0h0i0|1`1d1w2Q2f2h2x4T4X4Y4Z4`4g4m4t4v4{5W5k6`6b6c6d6k6q7Y7o8a:^:h:iQ-T(aU1c-T1e4hQ1e-VR4h1dQ(m#zR-a(mQ(v$OR-j(vQ1{-sR4w1{Q)w$xR.e)wQ2j.lS5[2j6|R6|5]Q*S%PR.o*SQ2p.qR5a2pQ/O*^S3U/O5sR5s3WQ.V)fW2Z.V2]5Q6wQ2].YQ5Q2[R6w5RQ)k$mR.])kQ/b*pR3f/bWiOSj!mQ%h}Q)S$[Q*j%cQ*l%fQ*m%gQ*o%iQ/]*kS/`*p/bR3e/_Q$^gQ%l!RQ%o!TQ%q!UQ%s!VQ)r$sQ)x$yQ*`%^Q*u%nS/R*a*dQ/i*tQ/j*wQ/k*xS/z+d2mQ1f-XQ1g-YQ1m-_Q2`.`Q2e.gQ3O.xQ3Y/TQ3d/^Y3r/|/}0T0Y5cQ4i1hQ4k1jQ4n1nQ5U2bQ5X2fQ5n3PQ5t3X[6S3q3t3x3z3{7SQ6l4jQ6o4oQ6x5SQ7Z5oQ7`5uW7f6T6Y6[6^Q7q6mQ7s6pQ7x6yQ7{6}Q8U7[U8Y7g7l7nQ8b7rQ8d7uQ8f7|Q8m8VS8o8[8`Q8t8cQ8u8gQ8z8qQ8}8vQ9Q8|Q9R9OR9T9SQ$gmQ&f!iU)a$h$i$jQ+R&SU+u&g&h&iQ-X(gS.R)b)cQ/q+TQ/y+dS0_+v+wQ1j-]Q2T.SQ3o/wS3u0O0TQ4Q0`Q4o1oS6W3v3{Q7i6XQ8]7kR8r8_S#va:jR)[$dU$Oa$d:jR-i(uQ#uaS&w!v)]Q&{!xQ'f#cQ(Z#wQ(a#yQ(z$SQ({$TQ)P$XQ+p&dQ,S9mQ,T9oQ,U9qQ,n'zQ,w(TQ,x(VQ,y(WQ-R(_Q-V(cQ-g(sQ-h(td-s)W-x.s1}2z4z5h6z7W8TQ-v)YQ.g)zQ.i)}Q0]+sQ0f9wQ0g9yQ0h9{Q0i,XQ0|9iQ1`-SQ1d-UQ1w-nQ2Q-zQ2f.hQ2h.kQ2x.uQ4T:PQ4X9nQ4Y9pQ4Z9rQ4`1XQ4g1bQ4m1lQ4t1sQ4v1zQ4{2RQ5W2dQ5k2|Q6`:TQ6b9|Q6c9xQ6d9zQ6k4fQ6q4rQ7Y5mQ7o:QQ8a:UQ:^:jQ:h:pR:i:qT'{#t'|lgOSj}!m$[%c%f%g%i*k*p/_/bS!oU%eQ%n!SQ%t!WQ'S!{Q's#lS*d%[%_Q*h%`Q*t%mQ+O&QQ+o&cQ,l'tQ-u)XQ/Y*gQ0[+rQ1S,gQ1u-lQ2P-yQ2w.tQ3[/VQ3]/WQ3_/XQ3a/ZQ3h/dQ4^1PQ5V2cQ5j2{Q5y3`Q5{3bQ5|3cQ7X5lR7b5z!vZOSUj}!S!m!{$[%[%_%`%c%e%f%g%i%m&Q&c)X*g*k*p+r-l-y.t/V/W/X/Z/_/b/d2c2{3`3b3c5l5zQ!_RQ!nTQ$`kQ%w!]Q%{!`Q&v!uQ&}!yQ'T#OQ'U#PQ'V#QQ'W#RQ'X#SQ'Y#TQ'Z#UQ'[#VQ']#WQ'^#XQ'_#YQ'a#[Q'd#aQ'h#dW'r#l't,g1PQ)l$nQ*{%xS+U&U/rQ+_&]Q+x&mQ,W&zQ,^'OQ,a9UQ,c9WQ,q(OQ-o)QQ/m+PQ/p+SQ0a+yQ0k,[Q0o9ZQ0p9[Q0q9]Q0r9^Q0s9_Q0t9`Q0u9aQ0v9bQ0w9cQ0x9dQ0y9eQ0z9fQ0{,bQ1O9jQ1T9gQ1x-qQ2U.TQ3n9sQ3q/{Q4R0bQ4U0lQ4V9tQ4[9vQ4]9}Q5]2kQ6Q3lQ6T3sQ6a:OQ6e:RQ6f:SQ7g6UQ7z6{Q8[7jQ8q8^Q8|8sQ9V!WR:c:mT!XQ!YR!aRR&W!bS&S!b+WS+T&T&[R/w+^R&|!xR'P!yT!sU$YS!rU$YU$xrs*iS&p!q!tQ+}&qQ,Q&tQ.d)vS0c+|,OR4S0d[!dR!`$u&^)t+ah!pUrs!q!t$Y&q&t)v+|,O0dQ/[*iQ/n+QQ3k/hT:Z&U)uT!fR$uS!eR$uS%|!`)tS+V&U)uQ+`&^R/x+aT&Z!b$vQ#j^R'x#oT'n#j'oR1R,fT(^#w(fR(d#yQ-t)WQ2O-xQ2v.sQ4x1}Q5i2zQ6t4zQ7U5hQ7y6zQ8S7WR8l8TlhOSj}!m$[%c%f%g%i*k*p/_/bQ%]yR*`%YV$yrs*iR.m*PR*_%WQ$qpR)q$rR)g$lT%az%dT%bz%dT/a*p/b", ++ nodeNames: "⚠ ArithOp ArithOp extends LineComment BlockComment Script ExportDeclaration export Star as VariableName from String ; default FunctionDeclaration async function VariableDefinition TypeParamList TypeDefinition ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType VoidType void TypeofType typeof MemberExpression . ?. PropertyName [ TemplateString null super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewExpression new TypeArgList CompareOp < ) ( ArgList UnaryExpression await yield delete LogicOp BitOp ParenthesizedExpression ClassExpression class extends ClassBody MethodDeclaration Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression PrivatePropertyName BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof in const CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXStartTag JSXSelfClosingTag JSXIdentifier JSXBuiltin JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast ArrowFunction TypeParamList SequenceExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature CallSignature TypePredicate is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody MethodDeclaration AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try catch finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement", ++ maxTerm: 332, + context: trackNewline, + nodeProps: [ +- [NodeProp.group, -26,7,14,16,54,180,184,187,188,190,193,196,207,209,215,217,219,221,224,230,234,236,238,240,242,244,245,"Statement",-30,11,13,23,26,27,38,39,40,41,43,48,56,64,70,71,87,88,97,99,115,118,120,121,122,123,125,126,144,145,147,"Expression",-22,22,24,28,29,31,148,150,152,153,155,156,157,159,160,161,163,164,165,174,176,178,179,"Type",-3,75,81,86,"ClassItem"], +- [NodeProp.closedBy, 37,"]",47,"}",62,")",128,"JSXSelfCloseEndTag JSXEndTag",142,"JSXEndTag"], +- [NodeProp.openedBy, 42,"[",46,"{",61,"(",127,"JSXStartTag",137,"JSXStartTag JSXStartCloseTag"] ++ [NodeProp.group, -26,7,14,16,54,182,186,189,190,192,195,198,209,211,217,219,221,223,226,232,236,238,240,242,244,246,247,"Statement",-30,11,13,23,26,27,38,39,40,41,43,48,56,64,70,71,87,88,97,99,115,118,120,121,122,123,125,126,146,147,149,"Expression",-22,22,24,28,29,31,150,152,154,155,157,158,159,161,162,163,165,166,167,176,178,180,181,"Type",-3,75,81,86,"ClassItem"], ++ [NodeProp.closedBy, 37,"]",47,"}",62,")",128,"JSXSelfCloseEndTag JSXEndTag",144,"JSXEndTag"], ++ [NodeProp.openedBy, 42,"[",46,"{",61,"(",127,"JSXStartTag",139,"JSXStartTag JSXStartCloseTag"] + ], + skippedNodes: [0,4,5], + repeatNodeCount: 28, +- tokenData: "!C}~R!`OX%TXY%cYZ'RZ[%c[]%T]^'R^p%Tpq%cqr'crs(kst0htu2`uv4pvw5ewx6cxy<yyz=Zz{=k{|>k|}?O}!O>k!O!P?`!P!QCl!Q!R!0[!R![!1q![!]!7s!]!^!8V!^!_!8g!_!`!9d!`!a!:[!a!b!<R!b!c%T!c!}2`!}#O!=d#O#P%T#P#Q!=t#Q#R!>U#R#S2`#S#T!>i#T#o2`#o#p!>y#p#q!?O#q#r!?f#r#s!?x#s$f%T$f$g%c$g#BY2`#BY#BZ!@Y#BZ$IS2`$IS$I_!@Y$I_$I|2`$I|$I}!Bq$I}$JO!Bq$JO$JT2`$JT$JU!@Y$JU$KV2`$KV$KW!@Y$KW&FU2`&FU&FV!@Y&FV?HT2`?HT?HU!@Y?HU~2`W%YR$QWO!^%T!_#o%T#p~%T,T%jg$QW'T+{OX%TXY%cYZ%TZ[%c[p%Tpq%cq!^%T!_#o%T#p$f%T$f$g%c$g#BY%T#BY#BZ%c#BZ$IS%T$IS$I_%c$I_$JT%T$JT$JU%c$JU$KV%T$KV$KW%c$KW&FU%T&FU&FV%c&FV?HT%T?HT?HU%c?HU~%T,T'YR$QW'U+{O!^%T!_#o%T#p~%T$T'jS$QW!f#{O!^%T!_!`'v!`#o%T#p~%T$O'}S#a#v$QWO!^%T!_!`(Z!`#o%T#p~%T$O(bR#a#v$QWO!^%T!_#o%T#p~%T'u(rZ$QW]!ROY(kYZ)eZr(krs*rs!^(k!^!_+U!_#O(k#O#P-b#P#o(k#o#p+U#p~(k&r)jV$QWOr)ers*Ps!^)e!^!_*a!_#o)e#o#p*a#p~)e&r*WR#{&j$QWO!^%T!_#o%T#p~%T&j*dROr*ars*ms~*a&j*rO#{&j'u*{R#{&j$QW]!RO!^%T!_#o%T#p~%T'm+ZV]!ROY+UYZ*aZr+Urs+ps#O+U#O#P+w#P~+U'm+wO#{&j]!R'm+zROr+Urs,Ts~+U'm,[U#{&j]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R,sU]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R-[O]!R!R-_PO~,n'u-gV$QWOr(krs-|s!^(k!^!_+U!_#o(k#o#p+U#p~(k'u.VZ#{&j$QW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/PZ$QW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/yR$QW]!RO!^%T!_#o%T#p~%T!Z0XT$QWO!^.x!^!_,n!_#o.x#o#p,n#p~.xy0mZ$QWOt%Ttu1`u!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`y1g]$QW'mqOt%Ttu1`u!Q%T!Q![1`![!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`&i2k_$QW#vS'W%k'dpOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`[3q_$QW#vSOt%Ttu3ju}%T}!O3j!O!Q%T!Q![3j![!^%T!_!c%T!c!}3j!}#R%T#R#S3j#S#T%T#T#o3j#p$g%T$g~3j$O4wS#Y#v$QWO!^%T!_!`5T!`#o%T#p~%T$O5[R$QW#k#vO!^%T!_#o%T#p~%T%r5lU'v%j$QWOv%Tvw6Ow!^%T!_!`5T!`#o%T#p~%T$O6VS$QW#e#vO!^%T!_!`5T!`#o%T#p~%T'u6jZ$QW]!ROY6cYZ7]Zw6cwx*rx!^6c!^!_8T!_#O6c#O#P:T#P#o6c#o#p8T#p~6c&r7bV$QWOw7]wx*Px!^7]!^!_7w!_#o7]#o#p7w#p~7]&j7zROw7wwx*mx~7w'm8YV]!ROY8TYZ7wZw8Twx+px#O8T#O#P8o#P~8T'm8rROw8Twx8{x~8T'm9SU#{&j]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R9kU]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R:QPO~9f'u:YV$QWOw6cwx:ox!^6c!^!_8T!_#o6c#o#p8T#p~6c'u:xZ#{&j$QW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z;rZ$QW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z<jT$QWO!^;k!^!_9f!_#o;k#o#p9f#p~;k%V=QR!`$}$QWO!^%T!_#o%T#p~%TZ=bR!_R$QWO!^%T!_#o%T#p~%T%R=tU'X!R#Z#v$QWOz%Tz{>W{!^%T!_!`5T!`#o%T#p~%T$O>_S#W#v$QWO!^%T!_!`5T!`#o%T#p~%T$u>rSi$m$QWO!^%T!_!`5T!`#o%T#p~%T&i?VR}&a$QWO!^%T!_#o%T#p~%T&i?gVr%n$QWO!O%T!O!P?|!P!Q%T!Q![@r![!^%T!_#o%T#p~%Ty@RT$QWO!O%T!O!P@b!P!^%T!_#o%T#p~%Ty@iR|q$QWO!^%T!_#o%T#p~%Ty@yZ$QWjqO!Q%T!Q![@r![!^%T!_!g%T!g!hAl!h#R%T#R#S@r#S#X%T#X#YAl#Y#o%T#p~%TyAqZ$QWO{%T{|Bd|}%T}!OBd!O!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyBiV$QWO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyCVV$QWjqO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%T,TCs`$QW#X#vOYDuYZ%TZzDuz{Jl{!PDu!P!Q!-e!Q!^Du!^!_Fx!_!`!.^!`!a!/]!a!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXD|[$QWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXEy_$QWyPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%TPF}VyPOYFxZ!PFx!P!QGd!Q!}Fx!}#OG{#O#PHh#P~FxPGiUyP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGdPHOTOYG{Z#OG{#O#PH_#P#QFx#Q~G{PHbQOYG{Z~G{PHkQOYFxZ~FxXHvY$QWOYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~HqXIkV$QWOYHqYZ%TZ!^Hq!^!_G{!_#oHq#o#pG{#p~HqXJVV$QWOYDuYZ%TZ!^Du!^!_Fx!_#oDu#o#pFx#p~Du,TJs^$QWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q!,R!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,TKtV$QWOzKoz{LZ{!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TL`X$QWOzKoz{LZ{!PKo!P!QL{!Q!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TMSR$QWT+{O!^%T!_#o%T#p~%T+{M`ROzM]z{Mi{~M]+{MlTOzM]z{Mi{!PM]!P!QM{!Q~M]+{NQOT+{,TNX^$QWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q! T!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,T! ^_$QWT+{yPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%T+{!!bYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!&x!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#VYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!#u!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#|UT+{yP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGd+{!$cWOY!$`YZM]Zz!$`z{!${{#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%OYOY!$`YZM]Zz!$`z{!${{!P!$`!P!Q!%n!Q#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%sTT+{OYG{Z#OG{#O#PH_#P#QFx#Q~G{+{!&VTOY!$`YZM]Zz!$`z{!${{~!$`+{!&iTOY!!]YZM]Zz!!]z{!#Q{~!!]+{!&}_yPOzM]z{Mi{#ZM]#Z#[!&x#[#]M]#]#^!&x#^#aM]#a#b!&x#b#gM]#g#h!&x#h#iM]#i#j!&x#j#mM]#m#n!&x#n~M],T!(R[$QWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!(|^$QWOY!'|YZKoZz!'|z{!(w{!P!'|!P!Q!)x!Q!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!*PY$QWT+{OYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~Hq,T!*tX$QWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#o!'|#o#p!$`#p~!'|,T!+fX$QWOYJlYZKoZzJlz{NQ{!^Jl!^!_!!]!_#oJl#o#p!!]#p~Jl,T!,Yc$QWyPOzKoz{LZ{!^Ko!^!_M]!_#ZKo#Z#[!,R#[#]Ko#]#^!,R#^#aKo#a#b!,R#b#gKo#g#h!,R#h#iKo#i#j!,R#j#mKo#m#n!,R#n#oKo#o#pM]#p~Ko,T!-lV$QWS+{OY!-eYZ%TZ!^!-e!^!_!.R!_#o!-e#o#p!.R#p~!-e+{!.WQS+{OY!.RZ~!.R$P!.g[$QW#k#vyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Du]!/f[#sS$QWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Duy!0cd$QWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#U%T#U#V!3X#V#X%T#X#YAl#Y#b%T#b#c!2w#c#d!4m#d#l%T#l#m!5{#m#o%T#p~%Ty!1x_$QWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#X%T#X#YAl#Y#b%T#b#c!2w#c#o%T#p~%Ty!3OR$QWjqO!^%T!_#o%T#p~%Ty!3^W$QWO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#o%T#p~%Ty!3}Y$QWjqO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#b%T#b#c!2w#c#o%T#p~%Ty!4rV$QWO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#o%T#p~%Ty!5`X$QWjqO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#b%T#b#c!2w#c#o%T#p~%Ty!6QZ$QWO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#o%T#p~%Ty!6z]$QWjqO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#b%T#b#c!2w#c#o%T#p~%T%w!7|R!XV$QW#i%hO!^%T!_#o%T#p~%T!P!8^R^w$QWO!^%T!_#o%T#p~%T+c!8rR']d!]%Y#t&s'zP!P!Q!8{!^!_!9Q!_!`!9_W!9QO$SW#v!9VP#[#v!_!`!9Y#v!9_O#k#v#v!9dO#]#v%w!9kT!w%o$QWO!^%T!_!`'v!`!a!9z!a#o%T#p~%T$P!:RR#S#w$QWO!^%T!_#o%T#p~%T%w!:gT'[!s#]#v#}S$QWO!^%T!_!`!:v!`!a!;W!a#o%T#p~%T$O!:}R#]#v$QWO!^%T!_#o%T#p~%T$O!;_T#[#v$QWO!^%T!_!`5T!`!a!;n!a#o%T#p~%T$O!;uS#[#v$QWO!^%T!_!`5T!`#o%T#p~%T%w!<YV'n%o$QWO!O%T!O!P!<o!P!^%T!_!a%T!a!b!=P!b#o%T#p~%T$`!<vRs$W$QWO!^%T!_#o%T#p~%T$O!=WS$QW#f#vO!^%T!_!`5T!`#o%T#p~%T&e!=kRu&]$QWO!^%T!_#o%T#p~%TZ!={RzR$QWO!^%T!_#o%T#p~%T$O!>]S#c#v$QWO!^%T!_!`5T!`#o%T#p~%T$P!>pR$QW'a#wO!^%T!_#o%T#p~%T~!?OO!P~%r!?VT'u%j$QWO!^%T!_!`5T!`#o%T#p#q!=P#q~%T$u!?oR!O$k$QW'cQO!^%T!_#o%T#p~%TX!@PR!gP$QWO!^%T!_#o%T#p~%T,T!@gr$QW'T+{#vS'W%k'dpOX%TXY%cYZ%TZ[%c[p%Tpq%cqt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$f%T$f$g%c$g#BY2`#BY#BZ!@Y#BZ$IS2`$IS$I_!@Y$I_$JT2`$JT$JU!@Y$JU$KV2`$KV$KW!@Y$KW&FU2`&FU&FV!@Y&FV?HT2`?HT?HU!@Y?HU~2`,T!CO_$QW'U+{#vS'W%k'dpOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`", ++ tokenData: "!F_~R!`OX%TXY%cYZ'RZ[%c[]%T]^'R^p%Tpq%cqr'crs(kst0htu2`uv4pvw5ewx6cxy<yyz=Zz{=k{|>k|}?O}!O>k!O!P?`!P!QCl!Q!R!0[!R![!1q![!]!7s!]!^!8V!^!_!8g!_!`!9d!`!a!:[!a!b!<R!b!c%T!c!}2`!}#O!=d#O#P%T#P#Q!=t#Q#R!>U#R#S2`#S#T!>i#T#o!>y#o#p!AZ#p#q!A`#q#r!Av#r#s!BY#s$f%T$f$g%c$g#BY2`#BY#BZ!Bj#BZ$IS2`$IS$I_!Bj$I_$I|2`$I|$I}!ER$I}$JO!ER$JO$JT2`$JT$JU!Bj$JU$KV2`$KV$KW!Bj$KW&FU2`&FU&FV!Bj&FV?HT2`?HT?HU!Bj?HU~2`W%YR$SWO!^%T!_#o%T#p~%T,T%jg$SW'V+{OX%TXY%cYZ%TZ[%c[p%Tpq%cq!^%T!_#o%T#p$f%T$f$g%c$g#BY%T#BY#BZ%c#BZ$IS%T$IS$I_%c$I_$JT%T$JT$JU%c$JU$KV%T$KV$KW%c$KW&FU%T&FU&FV%c&FV?HT%T?HT?HU%c?HU~%T,T'YR$SW'W+{O!^%T!_#o%T#p~%T$T'jS$SW!f#{O!^%T!_!`'v!`#o%T#p~%T$O'}S#a#v$SWO!^%T!_!`(Z!`#o%T#p~%T$O(bR#a#v$SWO!^%T!_#o%T#p~%T'u(rZ$SW]!ROY(kYZ)eZr(krs*rs!^(k!^!_+U!_#O(k#O#P-b#P#o(k#o#p+U#p~(k&r)jV$SWOr)ers*Ps!^)e!^!_*a!_#o)e#o#p*a#p~)e&r*WR#}&j$SWO!^%T!_#o%T#p~%T&j*dROr*ars*ms~*a&j*rO#}&j'u*{R#}&j$SW]!RO!^%T!_#o%T#p~%T'm+ZV]!ROY+UYZ*aZr+Urs+ps#O+U#O#P+w#P~+U'm+wO#}&j]!R'm+zROr+Urs,Ts~+U'm,[U#}&j]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R,sU]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R-[O]!R!R-_PO~,n'u-gV$SWOr(krs-|s!^(k!^!_+U!_#o(k#o#p+U#p~(k'u.VZ#}&j$SW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/PZ$SW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/yR$SW]!RO!^%T!_#o%T#p~%T!Z0XT$SWO!^.x!^!_,n!_#o.x#o#p,n#p~.xy0mZ$SWOt%Ttu1`u!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`y1g]$SW'oqOt%Ttu1`u!Q%T!Q![1`![!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`&i2k_$SW'fp'Y%k#vSOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`[3q_$SW#vSOt%Ttu3ju}%T}!O3j!O!Q%T!Q![3j![!^%T!_!c%T!c!}3j!}#R%T#R#S3j#S#T%T#T#o3j#p$g%T$g~3j$O4wS#Y#v$SWO!^%T!_!`5T!`#o%T#p~%T$O5[R$SW#k#vO!^%T!_#o%T#p~%T%r5lU'x%j$SWOv%Tvw6Ow!^%T!_!`5T!`#o%T#p~%T$O6VS$SW#e#vO!^%T!_!`5T!`#o%T#p~%T'u6jZ$SW]!ROY6cYZ7]Zw6cwx*rx!^6c!^!_8T!_#O6c#O#P:T#P#o6c#o#p8T#p~6c&r7bV$SWOw7]wx*Px!^7]!^!_7w!_#o7]#o#p7w#p~7]&j7zROw7wwx*mx~7w'm8YV]!ROY8TYZ7wZw8Twx+px#O8T#O#P8o#P~8T'm8rROw8Twx8{x~8T'm9SU#}&j]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R9kU]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R:QPO~9f'u:YV$SWOw6cwx:ox!^6c!^!_8T!_#o6c#o#p8T#p~6c'u:xZ#}&j$SW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z;rZ$SW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z<jT$SWO!^;k!^!_9f!_#o;k#o#p9f#p~;k%V=QR!`$}$SWO!^%T!_#o%T#p~%TZ=bR!_R$SWO!^%T!_#o%T#p~%T%R=tU'Z!R#Z#v$SWOz%Tz{>W{!^%T!_!`5T!`#o%T#p~%T$O>_S#W#v$SWO!^%T!_!`5T!`#o%T#p~%T$u>rSi$m$SWO!^%T!_!`5T!`#o%T#p~%T&i?VR}&a$SWO!^%T!_#o%T#p~%T&i?gVr%n$SWO!O%T!O!P?|!P!Q%T!Q![@r![!^%T!_#o%T#p~%Ty@RT$SWO!O%T!O!P@b!P!^%T!_#o%T#p~%Ty@iR|q$SWO!^%T!_#o%T#p~%Ty@yZ$SWjqO!Q%T!Q![@r![!^%T!_!g%T!g!hAl!h#R%T#R#S@r#S#X%T#X#YAl#Y#o%T#p~%TyAqZ$SWO{%T{|Bd|}%T}!OBd!O!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyBiV$SWO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyCVV$SWjqO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%T,TCs`$SW#X#vOYDuYZ%TZzDuz{Jl{!PDu!P!Q!-e!Q!^Du!^!_Fx!_!`!.^!`!a!/]!a!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXD|[$SWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXEy_$SWyPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%TPF}VyPOYFxZ!PFx!P!QGd!Q!}Fx!}#OG{#O#PHh#P~FxPGiUyP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGdPHOTOYG{Z#OG{#O#PH_#P#QFx#Q~G{PHbQOYG{Z~G{PHkQOYFxZ~FxXHvY$SWOYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~HqXIkV$SWOYHqYZ%TZ!^Hq!^!_G{!_#oHq#o#pG{#p~HqXJVV$SWOYDuYZ%TZ!^Du!^!_Fx!_#oDu#o#pFx#p~Du,TJs^$SWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q!,R!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,TKtV$SWOzKoz{LZ{!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TL`X$SWOzKoz{LZ{!PKo!P!QL{!Q!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TMSR$SWT+{O!^%T!_#o%T#p~%T+{M`ROzM]z{Mi{~M]+{MlTOzM]z{Mi{!PM]!P!QM{!Q~M]+{NQOT+{,TNX^$SWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q! T!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,T! ^_$SWT+{yPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%T+{!!bYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!&x!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#VYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!#u!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#|UT+{yP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGd+{!$cWOY!$`YZM]Zz!$`z{!${{#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%OYOY!$`YZM]Zz!$`z{!${{!P!$`!P!Q!%n!Q#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%sTT+{OYG{Z#OG{#O#PH_#P#QFx#Q~G{+{!&VTOY!$`YZM]Zz!$`z{!${{~!$`+{!&iTOY!!]YZM]Zz!!]z{!#Q{~!!]+{!&}_yPOzM]z{Mi{#ZM]#Z#[!&x#[#]M]#]#^!&x#^#aM]#a#b!&x#b#gM]#g#h!&x#h#iM]#i#j!&x#j#mM]#m#n!&x#n~M],T!(R[$SWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!(|^$SWOY!'|YZKoZz!'|z{!(w{!P!'|!P!Q!)x!Q!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!*PY$SWT+{OYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~Hq,T!*tX$SWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#o!'|#o#p!$`#p~!'|,T!+fX$SWOYJlYZKoZzJlz{NQ{!^Jl!^!_!!]!_#oJl#o#p!!]#p~Jl,T!,Yc$SWyPOzKoz{LZ{!^Ko!^!_M]!_#ZKo#Z#[!,R#[#]Ko#]#^!,R#^#aKo#a#b!,R#b#gKo#g#h!,R#h#iKo#i#j!,R#j#mKo#m#n!,R#n#oKo#o#pM]#p~Ko,T!-lV$SWS+{OY!-eYZ%TZ!^!-e!^!_!.R!_#o!-e#o#p!.R#p~!-e+{!.WQS+{OY!.RZ~!.R$P!.g[$SW#k#vyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Du]!/f[#sS$SWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Duy!0cd$SWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#U%T#U#V!3X#V#X%T#X#YAl#Y#b%T#b#c!2w#c#d!4m#d#l%T#l#m!5{#m#o%T#p~%Ty!1x_$SWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#X%T#X#YAl#Y#b%T#b#c!2w#c#o%T#p~%Ty!3OR$SWjqO!^%T!_#o%T#p~%Ty!3^W$SWO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#o%T#p~%Ty!3}Y$SWjqO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#b%T#b#c!2w#c#o%T#p~%Ty!4rV$SWO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#o%T#p~%Ty!5`X$SWjqO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#b%T#b#c!2w#c#o%T#p~%Ty!6QZ$SWO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#o%T#p~%Ty!6z]$SWjqO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#b%T#b#c!2w#c#o%T#p~%T%w!7|R!XV$SW#i%hO!^%T!_#o%T#p~%T!P!8^R^w$SWO!^%T!_#o%T#p~%T+c!8rR'_d!]%Y#t&s'|P!P!Q!8{!^!_!9Q!_!`!9_W!9QO$UW#v!9VP#[#v!_!`!9Y#v!9_O#k#v#v!9dO#]#v%w!9kT!w%o$SWO!^%T!_!`'v!`!a!9z!a#o%T#p~%T$P!:RR#S#w$SWO!^%T!_#o%T#p~%T%w!:gT'^!s#]#v$PS$SWO!^%T!_!`!:v!`!a!;W!a#o%T#p~%T$O!:}R#]#v$SWO!^%T!_#o%T#p~%T$O!;_T#[#v$SWO!^%T!_!`5T!`!a!;n!a#o%T#p~%T$O!;uS#[#v$SWO!^%T!_!`5T!`#o%T#p~%T%w!<YV'p%o$SWO!O%T!O!P!<o!P!^%T!_!a%T!a!b!=P!b#o%T#p~%T$`!<vRs$W$SWO!^%T!_#o%T#p~%T$O!=WS$SW#f#vO!^%T!_!`5T!`#o%T#p~%T&e!=kRu&]$SWO!^%T!_#o%T#p~%TZ!={RzR$SWO!^%T!_#o%T#p~%T$O!>]S#c#v$SWO!^%T!_!`5T!`#o%T#p~%T$P!>pR$SW'c#wO!^%T!_#o%T#p~%T&i!?U_$SW'fp'Y%k#xSOt%Ttu!>yu}%T}!O!@T!O!Q%T!Q![!>y![!^%T!_!c%T!c!}!>y!}#R%T#R#S!>y#S#T%T#T#o!>y#p$g%T$g~!>y[!@[_$SW#xSOt%Ttu!@Tu}%T}!O!@T!O!Q%T!Q![!@T![!^%T!_!c%T!c!}!@T!}#R%T#R#S!@T#S#T%T#T#o!@T#p$g%T$g~!@T~!A`O!P~%r!AgT'w%j$SWO!^%T!_!`5T!`#o%T#p#q!=P#q~%T$u!BPR!O$k$SW'eQO!^%T!_#o%T#p~%TX!BaR!gP$SWO!^%T!_#o%T#p~%T,T!Bwr$SW'V+{'fp'Y%k#vSOX%TXY%cYZ%TZ[%c[p%Tpq%cqt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$f%T$f$g%c$g#BY2`#BY#BZ!Bj#BZ$IS2`$IS$I_!Bj$I_$JT2`$JT$JU!Bj$JU$KV2`$KV$KW!Bj$KW&FU2`&FU&FV!Bj&FV?HT2`?HT?HU!Bj?HU~2`,T!E`_$SW'W+{'fp'Y%k#vSOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`", + tokenizers: [noSemicolon, incdecToken, template, 0, 1, 2, 3, 4, 5, 6, 7, 8, insertSemicolon], + topRules: {"Script":[0,6]}, +- dialects: {jsx: 11282, ts: 11284}, +- dynamicPrecedences: {"145":1,"172":1}, +- specialized: [{term: 284, get: (value, stack) => (tsExtends(value, stack) << 1)},{term: 284, get: value => spec_identifier[value] || -1},{term: 296, get: value => spec_word[value] || -1},{term: 59, get: value => spec_LessThan[value] || -1}], +- tokenPrec: 11305 ++ dialects: {jsx: 11332, ts: 11334}, ++ dynamicPrecedences: {"147":1,"174":1}, ++ specialized: [{term: 286, get: (value, stack) => (tsExtends(value, stack) << 1)},{term: 286, get: value => spec_identifier[value] || -1},{term: 298, get: value => spec_word[value] || -1},{term: 59, get: value => spec_LessThan[value] || -1}], ++ tokenPrec: 11355 + }); + + export { parser }; +diff --git a/node_modules/@lezer/javascript/src/javascript.grammar b/node_modules/@lezer/javascript/src/javascript.grammar +index a45c690..1870aa7 100644 +--- a/node_modules/@lezer/javascript/src/javascript.grammar ++++ b/node_modules/@lezer/javascript/src/javascript.grammar +@@ -460,17 +460,18 @@ JSXCloseTag { JSXStartCloseTag jsxElementName? JSXEndTag } + + jsxElementName { + JSXIdentifier | ++ JSXBuiltin { JSXLowerIdentifier } | + JSXNamespacedName | + JSXMemberExpression + } + +-JSXMemberExpression { (JSXMemberExpression | JSXIdentifier) "." JSXIdentifier } ++JSXMemberExpression { (JSXMemberExpression | JSXIdentifier | JSXLowerIdentifier) "." (JSXIdentifier | JSXLowerIdentifier) } + +-JSXNamespacedName { (JSXIdentifier | JSXNamespacedName) ":" JSXIdentifier } ++JSXNamespacedName { (JSXIdentifier | JSXNamespacedName | JSXLowerIdentifier) ":" (JSXIdentifier | JSXLowerIdentifier) } + + jsxAttribute { + JSXSpreadAttribute { "{" "..." expression "}" } | +- JSXAttribute { (JSXIdentifier | JSXNamespacedName) ("=" jsxAttributeValue)? } ++ JSXAttribute { (JSXIdentifier | JSXNamespacedName | JSXLowerIdentifier) ("=" jsxAttributeValue)? } + } + + jsxAttributeValue { +@@ -583,7 +584,7 @@ questionOp[@name=LogicOp] { "?" } + + @precedence { spaces, newline, identifier } + +- @precedence { spaces, newline, JSXIdentifier } ++ @precedence { spaces, newline, JSXIdentifier, JSXLowerIdentifier } + + @precedence { spaces, newline, word } + +@@ -623,7 +624,9 @@ questionOp[@name=LogicOp] { "?" } + + "?." "." "," ";" ":" + +- JSXIdentifier { identifierChar (identifierChar | std.digit | "-")* } ++ JSXIdentifier { $[A-Z_$\u{a1}-\u{10ffff}] (identifierChar | std.digit | "-")* } ++ ++ JSXLowerIdentifier[@name=JSXIdentifier] { $[a-z] (identifierChar | std.digit | "-")* } + + JSXAttributeValue { '"' !["]* '"' | "'" ![']* "'" } + +diff --git a/node_modules/@lezer/javascript/src/parser.js b/node_modules/@lezer/javascript/src/parser.js +index 53f4486..0ee4c3f 100644 +--- a/node_modules/@lezer/javascript/src/parser.js ++++ b/node_modules/@lezer/javascript/src/parser.js +@@ -3,29 +3,29 @@ import {LRParser} from "@lezer/lr" + import {noSemicolon, incdecToken, template, insertSemicolon, tsExtends} from "./tokens" + import {trackNewline} from "./tokens.js" + import {NodeProp} from "@lezer/common" +-const spec_identifier = {__proto__:null,export:16, as:21, from:25, default:30, async:35, function:36, this:46, true:54, false:54, void:60, typeof:64, null:78, super:80, new:114, await:131, yield:133, delete:134, class:144, extends:146, public:189, private:189, protected:189, readonly:191, instanceof:212, in:214, const:216, import:248, keyof:299, unique:303, infer:309, is:343, abstract:363, implements:365, type:367, let:370, var:372, interface:379, enum:383, namespace:389, module:391, declare:395, global:399, for:420, of:429, while:432, with:436, do:440, if:444, else:446, switch:450, case:456, try:462, catch:464, finally:466, return:470, throw:474, break:478, continue:482, debugger:486} +-const spec_word = {__proto__:null,async:101, get:103, set:105, public:153, private:153, protected:153, static:155, abstract:157, override:159, readonly:165, new:347} ++const spec_identifier = {__proto__:null,export:16, as:21, from:25, default:30, async:35, function:36, this:46, true:54, false:54, void:60, typeof:64, null:78, super:80, new:114, await:131, yield:133, delete:134, class:144, extends:146, public:189, private:189, protected:189, readonly:191, instanceof:212, in:214, const:216, import:248, keyof:303, unique:307, infer:313, is:347, abstract:367, implements:369, type:371, let:374, var:376, interface:383, enum:387, namespace:393, module:395, declare:399, global:403, for:424, of:433, while:436, with:440, do:444, if:448, else:450, switch:454, case:460, try:466, catch:468, finally:470, return:474, throw:478, break:482, continue:486, debugger:490} ++const spec_word = {__proto__:null,async:101, get:103, set:105, public:153, private:153, protected:153, static:155, abstract:157, override:159, readonly:165, new:351} + const spec_LessThan = {__proto__:null,"<":121} + export const parser = LRParser.deserialize({ + version: 13, +- states: "$1WO`QYOOO'QQ!LdO'#CgO'XOSO'#DSO)dQYO'#DXO)tQYO'#DdO){QYO'#DnO-xQYO'#DtOOQO'#EX'#EXO.]QWO'#EWO.bQWO'#EWOOQ!LS'#Eb'#EbO0aQ!LdO'#IqO2wQ!LdO'#IrO3eQWO'#EvO3jQpO'#F]OOQ!LS'#FO'#FOO3rO!bO'#FOO4QQWO'#FdO5_QWO'#FcOOQ!LS'#Ir'#IrOOQ!LQ'#Iq'#IqOOQQ'#J['#J[O5dQWO'#HjO5iQ!LYO'#HkOOQQ'#Ic'#IcOOQQ'#Hl'#HlQ`QYOOO){QYO'#DfO5qQWO'#GWO5vQ#tO'#ClO6UQWO'#EVO6aQWO'#EcO6fQ#tO'#E}O7QQWO'#GWO7VQWO'#G[O7bQWO'#G[O7pQWO'#G_O7pQWO'#G`O7pQWO'#GbO5qQWO'#GeO8aQWO'#GhO9oQWO'#CcO:PQWO'#GuO:XQWO'#G{O:XQWO'#G}O`QYO'#HPO:XQWO'#HRO:XQWO'#HUO:^QWO'#H[O:cQ!LZO'#H`O){QYO'#HbO:nQ!LZO'#HdO:yQ!LZO'#HfO5iQ!LYO'#HhO){QYO'#IsOOOS'#Hn'#HnO;UOSO,59nOOQ!LS,59n,59nO=gQbO'#CgO=qQYO'#HoO>OQWO'#ItO?}QbO'#ItO'dQYO'#ItO@UQWO,59sO@lQ&jO'#D^OAeQWO'#EXOArQWO'#JPOA}QWO'#JOOBVQWO,5:uOB[QWO'#I}OBcQWO'#DuO5vQ#tO'#EVOBqQWO'#EVOB|Q`O'#E}OOQ!LS,5:O,5:OOCUQYO,5:OOESQ!LdO,5:YOEpQWO,5:`OFZQ!LYO'#I|O7VQWO'#I{OFbQWO'#I{OFjQWO,5:tOFoQWO'#I{OF}QYO,5:rOH}QWO'#ESOJXQWO,5:rOKhQWO'#DhOKoQYO'#DmOKyQ&jO,5:{O){QYO,5:{OOQQ'#En'#EnOOQQ'#Ep'#EpO){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}OOQQ'#Et'#EtOLRQYO,5;_OOQ!LS,5;d,5;dOOQ!LS,5;e,5;eONRQWO,5;eOOQ!LS,5;f,5;fO){QYO'#HyONWQ!LYO,5<PONrQWO,5:}O){QYO,5;bO! [QpO'#JTONyQpO'#JTO! cQpO'#JTO! tQpO,5;mOOOO,5;w,5;wO!!SQYO'#F_OOOO'#Hx'#HxO3rO!bO,5;jO!!ZQpO'#FaOOQ!LS,5;j,5;jO!!wQ,UO'#CqOOQ!LS'#Ct'#CtO!#[QWO'#CtO!#aOSO'#CxO!#}Q#tO,5;|O!$UQWO,5<OO!%bQWO'#FnO!%oQWO'#FoO!%tQWO'#FsO!&vQ&jO'#FwO!'iQ,UO'#IlOOQ!LS'#Il'#IlO!'sQWO'#IkO!(RQWO'#IjOOQ!LS'#Cr'#CrOOQ!LS'#Cy'#CyO!(ZQWO'#C{OJ^QWO'#FfOJ^QWO'#FhO!(`QWO'#FjO!(eQWO'#FkO!(jQWO'#FqOJ^QWO'#FvO!(oQWO'#EYO!)WQWO,5;}O`QYO,5>UOOQQ'#If'#IfOOQQ,5>V,5>VOOQQ-E;j-E;jO!+SQ!LdO,5:QOOQ!LQ'#Co'#CoO!+sQ#tO,5<rOOQO'#Ce'#CeO!,UQWO'#CpO!,^Q!LYO'#IgO5_QWO'#IgO:^QWO,59WO!,lQpO,59WO!,tQ#tO,59WO5vQ#tO,59WO!-PQWO,5:rO!-XQWO'#GtO!-dQWO'#J`O){QYO,5;gO!-lQ&jO,5;iO!-qQWO,5=_O!-vQWO,5=_O!-{QWO,5=_O5iQ!LYO,5=_O5qQWO,5<rO!.ZQWO'#EZO!.lQ&jO'#E[OOQ!LQ'#I}'#I}O!.}Q!LYO'#J]O5iQ!LYO,5<vO7pQWO,5<|OOQO'#Cq'#CqO!/YQpO,5<yO!/bQ#tO,5<zO!/mQWO,5<|O!/rQ`O,5=PO:^QWO'#GjO5qQWO'#GlO!/zQWO'#GlO5vQ#tO'#GoO!0PQWO'#GoOOQQ,5=S,5=SO!0UQWO'#GpO!0^QWO'#ClO!0cQWO,58}O!0mQWO,58}O!2oQYO,58}OOQQ,58},58}O!2|Q!LYO,58}O){QYO,58}O!3XQYO'#GwOOQQ'#Gx'#GxOOQQ'#Gy'#GyO`QYO,5=aO!3iQWO,5=aO){QYO'#DtO`QYO,5=gO`QYO,5=iO!3nQWO,5=kO`QYO,5=mO!3sQWO,5=pO!3xQYO,5=vOOQQ,5=z,5=zO){QYO,5=zO5iQ!LYO,5=|OOQQ,5>O,5>OO!7yQWO,5>OOOQQ,5>Q,5>QO!7yQWO,5>QOOQQ,5>S,5>SO!8OQ`O,5?_OOOS-E;l-E;lOOQ!LS1G/Y1G/YO!8TQbO,5>ZO){QYO,5>ZOOQO-E;m-E;mO!8_QWO,5?`O!8gQbO,5?`O!8nQWO,5?jOOQ!LS1G/_1G/_O!8vQpO'#DQOOQO'#Iv'#IvO){QYO'#IvO!9eQpO'#IvO!:SQpO'#D_O!:eQ&jO'#D_O!<pQYO'#D_O!<wQWO'#IuO!=PQWO,59xO!=UQWO'#E]O!=dQWO'#JQO!=lQWO,5:vO!>SQ&jO'#D_O){QYO,5?kO!>^QWO'#HtO!8nQWO,5?jOOQ!LQ1G0a1G0aO!?jQ&jO'#DxOOQ!LS,5:a,5:aO){QYO,5:aOH}QWO,5:aO!?qQWO,5:aO:^QWO,5:qO!,lQpO,5:qO!,tQ#tO,5:qO5vQ#tO,5:qOOQ!LS1G/j1G/jOOQ!LS1G/z1G/zOOQ!LQ'#ER'#ERO){QYO,5?hO!?|Q!LYO,5?hO!@_Q!LYO,5?hO!@fQWO,5?gO!@nQWO'#HvO!@fQWO,5?gOOQ!LQ1G0`1G0`O7VQWO,5?gOOQ!LS1G0^1G0^O!AYQ!LdO1G0^O!AyQ!LbO,5:nOOQ!LS'#Fm'#FmO!BgQ!LdO'#IlOF}QYO1G0^O!DfQ#tO'#IwO!DpQWO,5:SO!DuQbO'#IxO){QYO'#IxO!EPQWO,5:XOOQ!LS'#DQ'#DQOOQ!LS1G0g1G0gO!EUQWO1G0gO!GgQ!LdO1G0iO!GnQ!LdO1G0iO!JRQ!LdO1G0iO!JYQ!LdO1G0iO!LaQ!LdO1G0iO!LtQ!LdO1G0iO# eQ!LdO1G0iO# lQ!LdO1G0iO#$PQ!LdO1G0iO#$WQ!LdO1G0iO#%{Q!LdO1G0iO#(uQ7^O'#CgO#*pQ7^O1G0yO#,kQ7^O'#IrOOQ!LS1G1P1G1PO#-OQ!LdO,5>eOOQ!LQ-E;w-E;wO#-oQ!LdO1G0iOOQ!LS1G0i1G0iO#/qQ!LdO1G0|O#0bQpO,5;oO#0gQpO,5;pO#0lQpO'#FWO#1QQWO'#FVOOQO'#JU'#JUOOQO'#Hw'#HwO#1VQpO1G1XOOQ!LS1G1X1G1XOOOO1G1b1G1bO#1eQ7^O'#IqO#1oQWO,5;yOLRQYO,5;yOOOO-E;v-E;vOOQ!LS1G1U1G1UOOQ!LS,5;{,5;{O#1tQpO,5;{OOQ!LS,59`,59`OH}QWO'#InOOOS'#Hm'#HmO#1yOSO,59dOOQ!LS,59d,59dO){QYO1G1hO!(eQWO'#H{O#2UQWO,5<aOOQ!LS,5<^,5<^OOQO'#GR'#GROJ^QWO,5<lOOQO'#GT'#GTOJ^QWO,5<nOJ^QWO,5<pOOQO1G1j1G1jO#2aQ`O'#CoO#2tQ`O,5<YO#2{QWO'#JXO5qQWO'#JXO#3ZQWO,5<[OJ^QWO,5<ZO#3`Q`O'#FmO#3mQ`O'#JYO#3wQWO'#JYOH}QWO'#JYO#3|QWO,5<_OOQ!LQ'#Dc'#DcO#4RQWO'#FpO#4^QpO'#FxO!&qQ&jO'#FxO!&qQ&jO'#FzO#4oQWO'#F{O!(jQWO'#GOOOQO'#H}'#H}O#4tQ&jO,5<cOOQ!LS,5<c,5<cO#4{Q&jO'#FxO#5ZQ&jO'#FyO#5cQ&jO'#FyOOQ!LS,5<q,5<qOJ^QWO,5?VOJ^QWO,5?VO#5hQWO'#IOO#5sQWO,5?UOOQ!LS'#Cg'#CgO#6gQ#tO,59gOOQ!LS,59g,59gO#7YQ#tO,5<QO#7{Q#tO,5<SO#8VQWO,5<UOOQ!LS,5<V,5<VO#8[QWO,5<]O#8aQ#tO,5<bOF}QYO1G1iO#8qQWO1G1iOOQQ1G3p1G3pOOQ!LS1G/l1G/lONRQWO1G/lOOQQ1G2^1G2^OH}QWO1G2^O){QYO1G2^OH}QWO1G2^O#8vQWO1G2^O#9UQWO,59[O#:_QWO'#ESOOQ!LQ,5?R,5?RO#:iQ!LYO,5?ROOQQ1G.r1G.rO:^QWO1G.rO!,lQpO1G.rO!,tQ#tO1G.rO#:wQWO1G0^O#:|QWO'#CgO#;XQWO'#JaO#;aQWO,5=`O#;fQWO'#JaO#;kQWO'#JaO#;pQWO'#IWO#<OQWO,5?zO#<WQbO1G1ROOQ!LS1G1T1G1TO5qQWO1G2yO#<_QWO1G2yO#<dQWO1G2yO#<iQWO1G2yOOQQ1G2y1G2yO#<nQ#tO1G2^O7VQWO'#JOO7VQWO'#E]O7VQWO'#IQO#=PQ!LYO,5?wOOQQ1G2b1G2bO!/mQWO1G2hOH}QWO1G2eO#=[QWO1G2eOOQQ1G2f1G2fOH}QWO1G2fO#=aQWO1G2fO#=iQ&jO'#GdOOQQ1G2h1G2hO!&qQ&jO'#ISO!/rQ`O1G2kOOQQ1G2k1G2kOOQQ,5=U,5=UO#=qQ#tO,5=WO5qQWO,5=WO#4oQWO,5=ZO5_QWO,5=ZO!,lQpO,5=ZO!,tQ#tO,5=ZO5vQ#tO,5=ZO#>SQWO'#J_O#>_QWO,5=[OOQQ1G.i1G.iO#>dQ!LYO1G.iO#>oQWO1G.iO!(ZQWO1G.iO5iQ!LYO1G.iO#>tQbO,5?|O#?OQWO,5?|O#?ZQYO,5=cO#?bQWO,5=cO7VQWO,5?|OOQQ1G2{1G2{O`QYO1G2{OOQQ1G3R1G3ROOQQ1G3T1G3TO:XQWO1G3VO#?gQYO1G3XO#CbQYO'#HWOOQQ1G3[1G3[O:^QWO1G3bO#CoQWO1G3bO5iQ!LYO1G3fOOQQ1G3h1G3hOOQ!LQ'#Ft'#FtO5iQ!LYO1G3jO5iQ!LYO1G3lOOOS1G4y1G4yO#CwQ`O,5<PO#DPQbO1G3uO#DZQWO1G4zO#DcQWO1G5UO#DkQWO,5?bOLRQYO,5:wO7VQWO,5:wO:^QWO,59yOLRQYO,59yO!,lQpO,59yO#DpQ7^O,59yOOQO,5:w,5:wO#DzQ&jO'#HpO#EbQWO,5?aOOQ!LS1G/d1G/dO#EjQ&jO'#HuO#FOQWO,5?lOOQ!LQ1G0b1G0bO!:eQ&jO,59yO#FWQbO1G5VOOQO,5>`,5>`O7VQWO,5>`OOQO-E;r-E;rOOQ!LQ'#EO'#EOO#FbQ!LrO'#EPO!?bQ&jO'#DyOOQO'#Hs'#HsO#F|Q&jO,5:dOOQ!LS,5:d,5:dO#GTQ&jO'#DyO#GfQ&jO'#DyO#GmQ&jO'#EUO#GpQ&jO'#EPO#G}Q&jO'#EPO!?bQ&jO'#EPO#HbQWO1G/{O#HgQ`O1G/{OOQ!LS1G/{1G/{O){QYO1G/{OH}QWO1G/{OOQ!LS1G0]1G0]O:^QWO1G0]O!,lQpO1G0]O!,tQ#tO1G0]O#HnQ!LdO1G5SO){QYO1G5SO#IOQ!LYO1G5SO#IaQWO1G5RO7VQWO,5>bOOQO,5>b,5>bO#IiQWO,5>bOOQO-E;t-E;tO#IaQWO1G5RO#IwQ!LdO,59gO#KvQ!LdO,5<QO#MxQ!LdO,5<SO$ zQ!LdO,5<bOOQ!LS7+%x7+%xO$$SQ!LdO7+%xO$$sQWO'#HqO$$}QWO,5?cOOQ!LS1G/n1G/nO$%VQYO'#HrO$%dQWO,5?dO$%lQbO,5?dOOQ!LS1G/s1G/sOOQ!LS7+&R7+&RO$%vQ7^O,5:YO){QYO7+&eO$&QQ7^O,5:QOOQO1G1Z1G1ZOOQO1G1[1G1[O$&_QMhO,5;rOLRQYO,5;qOOQO-E;u-E;uOOQ!LS7+&s7+&sOOOO7+&|7+&|OOOO1G1e1G1eO$&jQWO1G1eOOQ!LS1G1g1G1gO$&oQ`O,5?YOOOS-E;k-E;kOOQ!LS1G/O1G/OO$&vQ!LdO7+'SOOQ!LS,5>g,5>gO$'gQWO,5>gOOQ!LS1G1{1G1{P$'lQWO'#H{POQ!LS-E;y-E;yO$(]Q#tO1G2WO$)OQ#tO1G2YO$)YQ#tO1G2[OOQ!LS1G1t1G1tO$)aQWO'#HzO$)oQWO,5?sO$)oQWO,5?sO$)wQWO,5?sO$*SQWO,5?sOOQO1G1v1G1vO$*bQ#tO1G1uO$*rQWO'#H|O$+SQWO,5?tOH}QWO,5?tO$+[Q`O,5?tOOQ!LS1G1y1G1yO5iQ!LYO,5<dO5iQ!LYO,5<eO$+fQWO,5<eO#4jQWO,5<eO!,lQpO,5<dO$+kQWO,5<fO5iQ!LYO,5<gO$+fQWO,5<jOOQO-E;{-E;{OOQ!LS1G1}1G1}O!&qQ&jO,5<dO$+sQWO,5<eO!&qQ&jO,5<fO!&qQ&jO,5<eO$,OQ#tO1G4qO$,YQ#tO1G4qOOQO,5>j,5>jOOQO-E;|-E;|O!-lQ&jO,59iO){QYO,59iO$,gQWO1G1pOJ^QWO1G1wO$,lQ!LdO7+'TOOQ!LS7+'T7+'TOF}QYO7+'TOOQ!LS7+%W7+%WO$-]Q`O'#JZO#HbQWO7+'xO$-gQWO7+'xO$-oQ`O7+'xOOQQ7+'x7+'xOH}QWO7+'xO){QYO7+'xOH}QWO7+'xOOQO1G.v1G.vO$-yQ!LbO'#CgO$.ZQ!LbO,5<hO$.xQWO,5<hOOQ!LQ1G4m1G4mOOQQ7+$^7+$^O:^QWO7+$^O!,lQpO7+$^OF}QYO7+%xO$.}QWO'#IVO$/]QWO,5?{OOQO1G2z1G2zO5qQWO,5?{O$/]QWO,5?{O$/eQWO,5?{OOQO,5>r,5>rOOQO-E<U-E<UOOQ!LS7+&m7+&mO$/jQWO7+(eO5iQ!LYO7+(eO5qQWO7+(eO$/oQWO7+(eO$/tQWO7+'xOOQ!LQ,5>l,5>lOOQ!LQ-E<O-E<OOOQQ7+(S7+(SO$0SQ!LbO7+(POH}QWO7+(PO$0^Q`O7+(QOOQQ7+(Q7+(QOH}QWO7+(QO$0eQWO'#J^O$0pQWO,5=OOOQO,5>n,5>nOOQO-E<Q-E<QOOQQ7+(V7+(VO$1jQ&jO'#GmOOQQ1G2r1G2rOH}QWO1G2rO){QYO1G2rOH}QWO1G2rO$1qQWO1G2rO$2PQ#tO1G2rO5iQ!LYO1G2uO#4oQWO1G2uO5_QWO1G2uO!,lQpO1G2uO!,tQ#tO1G2uO$2bQWO'#IUO$2mQWO,5?yO$2uQ&jO,5?yOOQ!LQ1G2v1G2vOOQQ7+$T7+$TO$2zQWO7+$TO5iQ!LYO7+$TO$3PQWO7+$TO){QYO1G5hO){QYO1G5iO$3UQYO1G2}O$3]QWO1G2}O$3bQYO1G2}O$3iQ!LYO1G5hOOQQ7+(g7+(gO5iQ!LYO7+(qO`QYO7+(sOOQQ'#Jd'#JdOOQQ'#IX'#IXO$3sQYO,5=rOOQQ,5=r,5=rO){QYO'#HXO$4QQWO'#HZOOQQ7+(|7+(|O$4VQYO7+(|O7VQWO7+(|OOQQ7+)Q7+)QOOQQ7+)U7+)UOOQQ7+)W7+)WOOQO1G4|1G4|O$8TQ7^O1G0cO$8_QWO1G0cOOQO1G/e1G/eO$8jQ7^O1G/eO:^QWO1G/eOLRQYO'#D_OOQO,5>[,5>[OOQO-E;n-E;nOOQO,5>a,5>aOOQO-E;s-E;sO!,lQpO1G/eOOQO1G3z1G3zO:^QWO,5:eOOQO,5:k,5:kO){QYO,5:kO$8tQ!LYO,5:kO$9PQ!LYO,5:kO!,lQpO,5:eOOQO-E;q-E;qOOQ!LS1G0O1G0OO!?bQ&jO,5:eO$9_Q&jO,5:eO$9pQ!LrO,5:kO$:[Q&jO,5:eO!?bQ&jO,5:kOOQO,5:p,5:pO$:cQ&jO,5:kO$:pQ!LYO,5:kOOQ!LS7+%g7+%gO#HbQWO7+%gO#HgQ`O7+%gOOQ!LS7+%w7+%wO:^QWO7+%wO!,lQpO7+%wO$;UQ!LdO7+*nO){QYO7+*nOOQO1G3|1G3|O7VQWO1G3|O$;fQWO7+*mO$;nQ!LdO1G2WO$=pQ!LdO1G2YO$?rQ!LdO1G1uO$AzQ#tO,5>]OOQO-E;o-E;oO$BUQbO,5>^O){QYO,5>^OOQO-E;p-E;pO$B`QWO1G5OO$BhQ7^O1G0^O$DoQ7^O1G0iO$DvQ7^O1G0iO$FwQ7^O1G0iO$GOQ7^O1G0iO$HsQ7^O1G0iO$IWQ7^O1G0iO$KeQ7^O1G0iO$KlQ7^O1G0iO$MmQ7^O1G0iO$MtQ7^O1G0iO% iQ7^O1G0iO% |Q!LdO<<JPO%!mQ7^O1G0iO%$]Q7^O'#IlO%&YQ7^O1G0|OLRQYO'#FYOOQO'#JV'#JVOOQO1G1^1G1^O%&gQWO1G1]O%&lQ7^O,5>eOOOO7+'P7+'POOOS1G4t1G4tOOQ!LS1G4R1G4ROJ^QWO7+'vO%&vQWO,5>fO5qQWO,5>fOOQO-E;x-E;xO%'UQWO1G5_O%'UQWO1G5_O%'^QWO1G5_O%'iQ`O,5>hO%'sQWO,5>hOH}QWO,5>hOOQO-E;z-E;zO%'xQ`O1G5`O%(SQWO1G5`OOQO1G2O1G2OOOQO1G2P1G2PO5iQ!LYO1G2PO$+fQWO1G2PO5iQ!LYO1G2OO%([QWO1G2QOH}QWO1G2QOOQO1G2R1G2RO5iQ!LYO1G2UO!,lQpO1G2OO#4jQWO1G2PO%(aQWO1G2QO%(iQWO1G2POJ^QWO7+*]OOQ!LS1G/T1G/TO%(tQWO1G/TOOQ!LS7+'[7+'[O%(yQ#tO7+'cO%)ZQ!LdO<<JoOOQ!LS<<Jo<<JoOH}QWO'#IPO%)zQWO,5?uOOQQ<<Kd<<KdOH}QWO<<KdO#HbQWO<<KdO%*SQWO<<KdO%*[Q`O<<KdOH}QWO1G2SOOQQ<<Gx<<GxO:^QWO<<GxO%*fQ!LdO<<IdOOQ!LS<<Id<<IdOOQO,5>q,5>qO%+VQWO,5>qO#;kQWO,5>qOOQO-E<T-E<TO%+[QWO1G5gO%+[QWO1G5gO5qQWO1G5gO%+dQWO<<LPOOQQ<<LP<<LPO%+iQWO<<LPO5iQ!LYO<<LPO){QYO<<KdOH}QWO<<KdOOQQ<<Kk<<KkO$0SQ!LbO<<KkOOQQ<<Kl<<KlO$0^Q`O<<KlO%+nQ&jO'#IRO%+yQWO,5?xOLRQYO,5?xOOQQ1G2j1G2jO#FbQ!LrO'#EPO!?bQ&jO'#GnOOQO'#IT'#ITO%,RQ&jO,5=XOOQQ,5=X,5=XO%,YQ&jO'#EPO%,eQ&jO'#EPO%,|Q&jO'#EPO%-WQ&jO'#GnO%-iQWO7+(^O%-nQWO7+(^O%-vQ`O7+(^OOQQ7+(^7+(^OH}QWO7+(^O){QYO7+(^OH}QWO7+(^O%.QQWO7+(^OOQQ7+(a7+(aO5iQ!LYO7+(aO#4oQWO7+(aO5_QWO7+(aO!,lQpO7+(aO%.`QWO,5>pOOQO-E<S-E<SOOQO'#Gq'#GqO%.kQWO1G5eO5iQ!LYO<<GoOOQQ<<Go<<GoO%.sQWO<<GoO%.xQWO7++SO%.}QWO7++TOOQQ7+(i7+(iO%/SQWO7+(iO%/XQYO7+(iO%/`QWO7+(iO){QYO7++SO){QYO7++TOOQQ<<L]<<L]OOQQ<<L_<<L_OOQQ-E<V-E<VOOQQ1G3^1G3^O%/eQWO,5=sOOQQ,5=u,5=uO:^QWO<<LhO%/jQWO<<LhOLRQYO7+%}OOQO7+%P7+%PO%/oQ7^O1G5VO:^QWO7+%POOQO1G0P1G0PO%/yQ!LdO1G0VOOQO1G0V1G0VO){QYO1G0VO%0TQ!LYO1G0VO:^QWO1G0PO!,lQpO1G0PO!?bQ&jO1G0PO%0`Q!LYO1G0VO%0nQ&jO1G0PO%1PQ!LYO1G0VO%1eQ!LrO1G0VO%1oQ&jO1G0PO!?bQ&jO1G0VOOQ!LS<<IR<<IROOQ!LS<<Ic<<IcO:^QWO<<IcO%1vQ!LdO<<NYOOQO7+)h7+)hO%2WQ!LdO7+'cO%4`QbO1G3xO%4jQ7^O7+%xO%4wQ7^O,59gO%6tQ7^O,5<QO%8qQ7^O,5<SO%:nQ7^O,5<bO%<^Q7^O7+'SO%<kQ7^O7+'TO%<xQWO,5;tOOQO7+&w7+&wO%<}Q#tO<<KbOOQO1G4Q1G4QO%=_QWO1G4QO%=jQWO1G4QO%=xQWO7+*yO%=xQWO7+*yOH}QWO1G4SO%>QQ`O1G4SO%>[QWO7+*zOOQO7+'k7+'kO5iQ!LYO7+'kOOQO7+'j7+'jO$+fQWO7+'lO%>dQ`O7+'lOOQO7+'p7+'pO5iQ!LYO7+'jO$+fQWO7+'kO%>kQWO7+'lOH}QWO7+'lO#4jQWO7+'kO%>pQ#tO<<MwOOQ!LS7+$o7+$oO%>zQ`O,5>kOOQO-E;}-E;}O#HbQWOANAOOOQQANAOANAOOH}QWOANAOO%?UQ!LbO7+'nOOQQAN=dAN=dO5qQWO1G4]OOQO1G4]1G4]O%?cQWO1G4]O%?hQWO7++RO%?hQWO7++RO5iQ!LYOANAkO%?pQWOANAkOOQQANAkANAkO%?uQWOANAOO%?}Q`OANAOOOQQANAVANAVOOQQANAWANAWO%@XQWO,5>mOOQO-E<P-E<PO%@dQ7^O1G5dO#4oQWO,5=YO5_QWO,5=YO!,lQpO,5=YOOQO-E<R-E<ROOQQ1G2s1G2sO$9pQ!LrO,5:kO!?bQ&jO,5=YO%@nQ&jO,5=YO%APQ&jO,5:kOOQQ<<Kx<<KxOH}QWO<<KxO%-iQWO<<KxO%AZQWO<<KxO%AcQ`O<<KxO){QYO<<KxOH}QWO<<KxOOQQ<<K{<<K{O5iQ!LYO<<K{O#4oQWO<<K{O5_QWO<<K{O%AmQ&jO1G4[O%ArQWO7++POOQQAN=ZAN=ZO5iQ!LYOAN=ZOOQQ<<Nn<<NnOOQQ<<No<<NoOOQQ<<LT<<LTO%AzQWO<<LTO%BPQYO<<LTO%BWQWO<<NnO%B]QWO<<NoOOQQ1G3_1G3_OOQQANBSANBSO:^QWOANBSO%BbQ7^O<<IiOOQO<<Hk<<HkOOQO7+%q7+%qO%/yQ!LdO7+%qO){QYO7+%qOOQO7+%k7+%kO:^QWO7+%kO!,lQpO7+%kO%BlQ!LYO7+%qO!?bQ&jO7+%kO%BwQ!LYO7+%qO%CVQ&jO7+%kO%ChQ!LYO7+%qOOQ!LSAN>}AN>}O%C|Q!LdO<<KbO%FUQ7^O<<JPO%FcQ7^O1G1uO%HRQ7^O1G2WO%JOQ7^O1G2YO%K{Q7^O<<JoO%LYQ7^O<<IdOOQO1G1`1G1`OOQO7+)l7+)lO%LgQWO7+)lO%LrQWO<<NeO%LzQ`O7+)nOOQO<<KV<<KVO5iQ!LYO<<KWO$+fQWO<<KWOOQO<<KU<<KUO5iQ!LYO<<KVO%MUQ`O<<KWO$+fQWO<<KVOOQQG26jG26jO#HbQWOG26jOOQO7+)w7+)wO5qQWO7+)wO%M]QWO<<NmOOQQG27VG27VO5iQ!LYOG27VOH}QWOG26jOLRQYO1G4XO%MeQWO7++OO5iQ!LYO1G2tO#4oQWO1G2tO5_QWO1G2tO!,lQpO1G2tO!?bQ&jO1G2tO%1eQ!LrO1G0VO%MmQ&jO1G2tO%-iQWOANAdOOQQANAdANAdOH}QWOANAdO%NOQWOANAdO%NWQ`OANAdOOQQANAgANAgO5iQ!LYOANAgO#4oQWOANAgOOQO'#Gr'#GrOOQO7+)v7+)vOOQQG22uG22uOOQQANAoANAoO%NbQWOANAoOOQQANDYANDYOOQQANDZANDZO%NgQYOG27nOOQO<<I]<<I]O%/yQ!LdO<<I]OOQO<<IV<<IVO:^QWO<<IVO){QYO<<I]O!,lQpO<<IVO&$eQ!LYO<<I]O!?bQ&jO<<IVO&$pQ!LYO<<I]O&%OQ7^O7+'cOOQO<<MW<<MWOOQOAN@rAN@rO5iQ!LYOAN@rOOQOAN@qAN@qO$+fQWOAN@rO5iQ!LYOAN@qOOQQLD,ULD,UOOQO<<Mc<<McOOQQLD,qLD,qO#HbQWOLD,UO&&nQ7^O7+)sOOQO7+(`7+(`O5iQ!LYO7+(`O#4oQWO7+(`O5_QWO7+(`O!,lQpO7+(`O!?bQ&jO7+(`OOQQG27OG27OO%-iQWOG27OOH}QWOG27OOOQQG27RG27RO5iQ!LYOG27ROOQQG27ZG27ZO:^QWOLD-YOOQOAN>wAN>wOOQOAN>qAN>qO%/yQ!LdOAN>wO:^QWOAN>qO){QYOAN>wO!,lQpOAN>qO&&xQ!LYOAN>wO&'TQ7^O<<KbOOQOG26^G26^O5iQ!LYOG26^OOQOG26]G26]OOQQ!$( p!$( pOOQO<<Kz<<KzO5iQ!LYO<<KzO#4oQWO<<KzO5_QWO<<KzO!,lQpO<<KzOOQQLD,jLD,jO%-iQWOLD,jOOQQLD,mLD,mOOQQ!$(!t!$(!tOOQOG24cG24cOOQOG24]G24]O%/yQ!LdOG24cO:^QWOG24]O){QYOG24cOOQOLD+xLD+xOOQOANAfANAfO5iQ!LYOANAfO#4oQWOANAfO5_QWOANAfOOQQ!$(!U!$(!UOOQOLD)}LD)}OOQOLD)wLD)wO%/yQ!LdOLD)}OOQOG27QG27QO5iQ!LYOG27QO#4oQWOG27QOOQO!$'Mi!$'MiOOQOLD,lLD,lO5iQ!LYOLD,lOOQO!$(!W!$(!WOLRQYO'#DnO&(sQbO'#IqOLRQYO'#DfO&(zQ!LdO'#CgO&)eQbO'#CgO&)uQYO,5:rOLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO,5:}OLRQYO'#HyO&+uQWO,5<PO&-XQWO,5:}OLRQYO,5;bO!(ZQWO'#C{O!(ZQWO'#C{OH}QWO'#FfO&+}QWO'#FfOH}QWO'#FhO&+}QWO'#FhOH}QWO'#FvO&+}QWO'#FvOLRQYO,5?kO&)uQYO1G0^O&-`Q7^O'#CgOLRQYO1G1hOH}QWO,5<lO&+}QWO,5<lOH}QWO,5<nO&+}QWO,5<nOH}QWO,5<ZO&+}QWO,5<ZO&)uQYO1G1iOLRQYO7+&eOH}QWO1G1wO&+}QWO1G1wO&)uQYO7+'TO&)uQYO7+%xOH}QWO7+'vO&+}QWO7+'vO&-jQWO'#EWO&-oQWO'#EWO&-wQWO'#EvO&-|QWO'#EcO&.RQWO'#JPO&.^QWO'#I}O&.iQWO,5:rO&.nQ#tO,5;|O&.uQWO'#FoO&.zQWO'#FoO&/PQWO,5;}O&/XQWO,5:rO&/aQ7^O1G0yO&/hQWO,5<]O&/mQWO,5<]O&/rQWO1G1iO&/wQWO1G0^O&/|Q#tO1G2[O&0TQ#tO1G2[O4QQWO'#FdO5_QWO'#FcOBqQWO'#EVOLRQYO,5;_O!(jQWO'#FqO!(jQWO'#FqOJ^QWO,5<pOJ^QWO,5<p", +- stateData: "&1Q~O'TOS'UOSSOSTOS~OPTOQTOWyO]cO^hOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#`sO#ppO#t^O${qO$}tO%PrO%QrO%TuO%VvO%YwO%ZwO%]xO%jzO%p{O%r|O%t}O%v!OO%y!PO&P!QO&T!RO&V!SO&X!TO&Z!UO&]!VO'WPO'aQO'mYO'zaO~OPZXYZX^ZXiZXrZXsZXuZX}ZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'RZX'aZX'nZX'uZX'vZX~O!X$hX~P$zO'O!XO'P!WO'Q!ZO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W![O'aQO'mYO'zaO~O|!`O}!]Oz'hPz'rP~P'dO!O!lO~P`OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W9VO'aQO'mYO'zaO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'aQO'mYO'zaO~O|!qO#Q!tO#R!qO'W9WO!_'oP~P+{O#S!uO~O!X!vO#S!uO~OP#]OY#cOi#QOr!zOs!zOu!{O}#aO!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~O^'eX'R'eX!_'eXz'eX!P'eX$|'eX!X'eX~P.jO!w#dO#k#dOP'fXY'fX^'fXi'fXr'fXs'fXu'fX}'fX!]'fX!^'fX!`'fX!f'fX#W'fX#X'fX#Y'fX#Z'fX#['fX#]'fX#^'fX#a'fX#c'fX#e'fX#f'fX'a'fX'n'fX'u'fX'v'fX~O#_'fX'R'fXz'fX!_'fX'c'fX!P'fX$|'fX!X'fX~P0zO!w#dO~O#v#eO#}#iO~O!P#jO#t^O$Q#kO$S#mO~O]#pOg#}Oi#qOj#pOk#pOn$OOp$POu#wO!P#xO!Z$UO!`#uO#R$VO#p$SO$Z$QO$]$RO$`$TO'W#oO'a#rO'['^P~O!`$WO~O!X$YO~O^$ZO'R$ZO~O'W$_O~O!`$WO'W$_O'X$aO']$bO~Ob$hO!`$WO'W$_O~O#_#SO~O]$qOr$mO!P$jO!`$lO$}$pO'W$_O'X$aO[(SP~O!j$rO~Ou$sO!P$tO'W$_O~Ou$sO!P$tO%V$xO'W$_O~O'W$yO~O#`sO$}tO%PrO%QrO%TuO%VvO%YwO%ZwO~Oa%SOb%RO!j%PO${%QO%_%OO~P7uOa%VObmO!P%UO!jlO#`sO${qO%PrO%QrO%TuO%VvO%YwO%ZwO%]xO~O_%YO!w%]O$}%WO'X$aO~P8tO!`%^O!c%bO~O!`%cO~O!PSO~O^$ZO&}%kO'R$ZO~O^$ZO&}%nO'R$ZO~O^$ZO&}%pO'R$ZO~O'O!XO'P!WO'Q%tO~OPZXYZXiZXrZXsZXuZX}ZX}cX!]ZX!^ZX!`ZX!fZX!wZX!wcX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'aZX'nZX'uZX'vZX~OzZXzcX~P;aO|%vOz&cX}&cX~P){O}!]Oz'hX~OP#]OY#cOi#QOr!zOs!zOu!{O}!]O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~Oz'hX~P>WOz%{O~Ou&OO!S&YO!T&RO!U&RO'X$aO~O]&POj&PO|&SO'd%|O!O'iP!O'tP~P@ZOz'qX}'qX!X'qX!_'qX'n'qX~O!w'qX#S!{X!O'qX~PASO!w&ZOz'sX}'sX~O}&[Oz'rX~Oz&^O~O!w#dO~PASOR&bO!P&_O!k&aO'W$_O~Ob&gO!`$WO'W$_O~Or$mO!`$lO~O!O&hO~P`Or!zOs!zOu!{O!^!xO!`!yO'aQOP!baY!bai!ba}!ba!]!ba!f!ba#W!ba#X!ba#Y!ba#Z!ba#[!ba#]!ba#^!ba#_!ba#a!ba#c!ba#e!ba#f!ba'n!ba'u!ba'v!ba~O^!ba'R!baz!ba!_!ba'c!ba!P!ba$|!ba!X!ba~PC]O!_&iO~O!X!vO!w&kO'n&jO}'pX^'pX'R'pX~O!_'pX~PEuO}&oO!_'oX~O!_&qO~Ou$sO!P$tO#R&rO'W$_O~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'W9VO'aQO'mYO'zaO~O]#pOg#}Oi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'W&vO'a#rO~O#S&xO~O]#pOg#}Oi#qOj#pOk#pOn$OOp$POu#wO!P#xO!Z$UO!`#uO#R$VO#p$SO$Z$QO$]$RO$`$TO'W&vO'a#rO~O'['kP~PJ^O|&|O!_'lP~P){O'd'OO'mYO~OP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!P!bO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'W'^O'aQO'mYO'z:hO~O!`!yO~O}#aO^$Xa'R$Xa!_$Xaz$Xa!P$Xa$|$Xa!X$Xa~O#`'eO~PH}O!X'gO!P'wX#s'wX#v'wX#}'wX~Or'hO~PNyOr'hO!P'wX#s'wX#v'wX#}'wX~O!P'jO#s'nO#v'iO#}'oO~O|'rO~PLRO#v#eO#}'uO~Or$aXu$aX!^$aX'n$aX'u$aX'v$aX~OReX}eX!weX'[eX'[$aX~P!!cOj'wO~O'O'yO'P'xO'Q'{O~Or'}Ou(OO'n#ZO'u(QO'v(SO~O'['|O~P!#lO'[(VO~O]#pOg#}Oi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'a#rO~O|(ZO'W(WO!_'{P~P!$ZO#S(]O~O|(aO'W(^Oz'|P~P!$ZO^(jOi(oOu(gO!S(mO!T(fO!U(fO!`(dO!t(nO$s(iO'X$aO'd(cO~O!O(lO~P!&RO!^!xOr'`Xu'`X'n'`X'u'`X'v'`X}'`X!w'`X~O'['`X#i'`X~P!&}OR(rO!w(qO}'_X'['_X~O}(sO'['^X~O'W(uO~O!`(zO~O'W&vO~O!`(dO~Ou$sO|!qO!P$tO#Q!tO#R!qO'W$_O!_'oP~O!X!vO#S)OO~OP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'aQO'n#ZO'u!|O'v!}O~O^!Ya}!Ya'R!Yaz!Ya!_!Ya'c!Ya!P!Ya$|!Ya!X!Ya~P!)`OR)WO!P&_O!k)VO$|)UO']$bO~O'W$yO'['^P~O!X)ZO!P'ZX^'ZX'R'ZX~O!`$WO']$bO~O!`$WO'W$_O']$bO~O!X!vO#S&xO~O$})gO'W)cO!O(TP~O})hO[(SX~O'd'OO~OY)lO~O[)mO~O!P$jO'W$_O'X$aO[(SP~Ou$sO|)rO!P$tO'W$_Oz'rP~O]&VOj&VO|)sO'd'OO!O'tP~O})tO^(PX'R(PX~O!w)xO']$bO~OR){O!P#xO']$bO~O!P)}O~Or*PO!PSO~O!j*UO~Ob*ZO~O'W(uO!O(RP~Ob$hO~O$}tO'W$yO~P8tOY*aO[*`O~OPTOQTO]cOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#t^O${qO'aQO'mYO'zaO~O!P!bO#p!kO'W9VO~P!0uO[*`O^$ZO'R$ZO~O^*eO#`*gO%P*gO%Q*gO~P){O!`%^O~O%p*lO~O!P*nO~O&Q*qO&R*pOP&OaQ&OaW&Oa]&Oa^&Oaa&Oab&Oag&Oai&Oaj&Oak&Oan&Oap&Oau&Oaw&Oax&Oay&Oa!P&Oa!Z&Oa!`&Oa!c&Oa!d&Oa!e&Oa!f&Oa!g&Oa!j&Oa#`&Oa#p&Oa#t&Oa${&Oa$}&Oa%P&Oa%Q&Oa%T&Oa%V&Oa%Y&Oa%Z&Oa%]&Oa%j&Oa%p&Oa%r&Oa%t&Oa%v&Oa%y&Oa&P&Oa&T&Oa&V&Oa&X&Oa&Z&Oa&]&Oa&|&Oa'W&Oa'a&Oa'm&Oa'z&Oa!O&Oa%w&Oa_&Oa%|&Oa~O'W*tO~O'c*wO~Oz&ca}&ca~P!)`O}!]Oz'ha~Oz'ha~P>WO}&[Oz'ra~O}tX}!VX!OtX!O!VX!XtX!X!VX!`!VX!wtX']!VX~O!X+OO!w*}O}#PX}'jX!O#PX!O'jX!X'jX!`'jX']'jX~O!X+QO!`$WO']$bO}!RX!O!RX~O]%}Oj%}Ou&OO'd(cO~OP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!P!bO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'aQO'mYO'z:hO~O'W9sO~P!:sO}+UO!O'iX~O!O+WO~O!X+OO!w*}O}#PX!O#PX~O}+XO!O'tX~O!O+ZO~O]%}Oj%}Ou&OO'X$aO'd(cO~O!T+[O!U+[O~P!=qOu$sO|+_O!P$tO'W$_Oz&hX}&hX~O^+dO!S+gO!T+cO!U+cO!n+kO!o+iO!p+jO!q+hO!t+lO'X$aO'd(cO'm+aO~O!O+fO~P!>rOR+qO!P&_O!k+pO~O!w+wO}'pa!_'pa^'pa'R'pa~O!X!vO~P!?|O}&oO!_'oa~Ou$sO|+zO!P$tO#Q+|O#R+zO'W$_O}&jX!_&jX~O^!zi}!zi'R!ziz!zi!_!zi'c!zi!P!zi$|!zi!X!zi~P!)`O#S!va}!va!_!va!w!va!P!va^!va'R!vaz!va~P!#lO#S'`XP'`XY'`X^'`Xi'`Xs'`X!]'`X!`'`X!f'`X#W'`X#X'`X#Y'`X#Z'`X#['`X#]'`X#^'`X#_'`X#a'`X#c'`X#e'`X#f'`X'R'`X'a'`X!_'`Xz'`X!P'`X'c'`X$|'`X!X'`X~P!&}O},VO'['kX~P!#lO'[,XO~O},YO!_'lX~P!)`O!_,]O~Oz,^O~OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'aQOY#Vi^#Vii#Vi}#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O#W#Vi~P!EZO#W#OO~P!EZOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO'aQOY#Vi^#Vi}#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~Oi#Vi~P!GuOi#QO~P!GuOP#]Oi#QOr!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO'aQO^#Vi}#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'u#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P!JaOY#cO!]#SO#]#SO#^#SO#_#SO~P!JaOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO'aQO^#Vi}#Vi#c#Vi#e#Vi#f#Vi'R#Vi'n#Vi'v#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O'u#Vi~P!MXO'u!|O~P!MXOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO'aQO'u!|O^#Vi}#Vi#e#Vi#f#Vi'R#Vi'n#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~O'v#Vi~P# sO'v!}O~P# sOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO'aQO'u!|O'v!}O~O^#Vi}#Vi#f#Vi'R#Vi'n#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~P#$_OPZXYZXiZXrZXsZXuZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'aZX'nZX'uZX'vZX}ZX!OZX~O#iZX~P#&rOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO#e9cO#f9dO'aQO'n#ZO'u!|O'v!}O~O#i,`O~P#(|OP'fXY'fXi'fXr'fXs'fXu'fX!]'fX!^'fX!`'fX!f'fX#W'fX#X'fX#Y'fX#Z'fX#['fX#]'fX#^'fX#a'fX#c'fX#e'fX#f'fX'a'fX'n'fX'u'fX'v'fX}'fX~O!w9hO#k9hO#_'fX#i'fX!O'fX~P#*wO^&ma}&ma'R&ma!_&ma'c&maz&ma!P&ma$|&ma!X&ma~P!)`OP#ViY#Vi^#Vii#Vis#Vi}#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'R#Vi'a#Viz#Vi!_#Vi'c#Vi!P#Vi$|#Vi!X#Vi~P!#lO^#ji}#ji'R#jiz#ji!_#ji'c#ji!P#ji$|#ji!X#ji~P!)`O#v,bO~O#v,cO~O!X'gO!w,dO!P#zX#s#zX#v#zX#}#zX~O|,eO~O!P'jO#s,gO#v'iO#},hO~O}9eO!O'eX~P#(|O!O,iO~O#},kO~O'O'yO'P'xO'Q,nO~O],qOj,qOz,rO~O}cX!XcX!_cX!_$aX'ncX~P!!cO!_,xO~P!#lO},yO!X!vO'n&jO!_'{X~O!_-OO~Oz$aX}$aX!X$hX~P!!cO}-QOz'|X~P!#lO!X-SO~Oz-UO~O|(ZO'W$_O!_'{P~Oi-YO!X!vO!`$WO']$bO'n&jO~O!X)ZO~O!O-`O~P!&RO!T-aO!U-aO'X$aO'd(cO~Ou-cO'd(cO~O!t-dO~O'W$yO}&rX'[&rX~O}(sO'['^a~Or-iOs-iOu-jO'noa'uoa'voa}oa!woa~O'[oa#ioa~P#5{Or'}Ou(OO'n$Ya'u$Ya'v$Ya}$Ya!w$Ya~O'[$Ya#i$Ya~P#6qOr'}Ou(OO'n$[a'u$[a'v$[a}$[a!w$[a~O'[$[a#i$[a~P#7dO]-kO~O#S-lO~O'[$ja}$ja#i$ja!w$ja~P!#lO#S-oO~OR-xO!P&_O!k-wO$|-vO~O'[-yO~O]#pOi#qOj#pOk#pOn$OOp9iOu#wO!P#xO!Z:lO!`#uO#R9oO#p$SO$Z9kO$]9mO$`$TO'a#rO~Og-{O'W-zO~P#9ZO!X)ZO!P'Za^'Za'R'Za~O#S.RO~OYZX}cX!OcX~O}.SO!O(TX~O!O.UO~OY.VO~O'W)cO~O!P$jO'W$_O[&zX}&zX~O})hO[(Sa~O!_.[O~P!)`O].^O~OY._O~O[.`O~OR-xO!P&_O!k-wO$|-vO']$bO~O})tO^(Pa'R(Pa~O!w.fO~OR.iO!P#xO~O'd'OO!O(QP~OR.sO!P.oO!k.rO$|.qO']$bO~OY.}O}.{O!O(RX~O!O/OO~O[/QO^$ZO'R$ZO~O]/RO~O#_/TO%n/UO~P0zO!w#dO#_/TO%n/UO~O^/VO~P){O^/XO~O%w/]OP%uiQ%uiW%ui]%ui^%uia%uib%uig%uii%uij%uik%uin%uip%uiu%uiw%uix%uiy%ui!P%ui!Z%ui!`%ui!c%ui!d%ui!e%ui!f%ui!g%ui!j%ui#`%ui#p%ui#t%ui${%ui$}%ui%P%ui%Q%ui%T%ui%V%ui%Y%ui%Z%ui%]%ui%j%ui%p%ui%r%ui%t%ui%v%ui%y%ui&P%ui&T%ui&V%ui&X%ui&Z%ui&]%ui&|%ui'W%ui'a%ui'm%ui'z%ui!O%ui_%ui%|%ui~O_/cO!O/aO%|/bO~P`O!PSO!`/fO~O}#aO'c$Xa~Oz&ci}&ci~P!)`O}!]Oz'hi~O}&[Oz'ri~Oz/jO~O}!Ra!O!Ra~P#(|O]%}Oj%}O|/pO'd(cO}&dX!O&dX~P@ZO}+UO!O'ia~O]&VOj&VO|)sO'd'OO}&iX!O&iX~O}+XO!O'ta~Oz'si}'si~P!)`O^$ZO!X!vO!`$WO!f/{O!w/yO'R$ZO']$bO'n&jO~O!O0OO~P!>rO!T0PO!U0PO'X$aO'd(cO'm+aO~O!S0QO~P#GTO!PSO!S0QO!q0SO!t0TO~P#GTO!S0QO!o0VO!p0VO!q0SO!t0TO~P#GTO!P&_O~O!P&_O~P!#lO}'pi!_'pi^'pi'R'pi~P!)`O!w0`O}'pi!_'pi^'pi'R'pi~O}&oO!_'oi~Ou$sO!P$tO#R0bO'W$_O~O#SoaPoaYoa^oaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa'Roa'aoa!_oazoa!Poa'coa$|oa!Xoa~P#5{O#S$YaP$YaY$Ya^$Yai$Yas$Ya!]$Ya!^$Ya!`$Ya!f$Ya#W$Ya#X$Ya#Y$Ya#Z$Ya#[$Ya#]$Ya#^$Ya#_$Ya#a$Ya#c$Ya#e$Ya#f$Ya'R$Ya'a$Ya!_$Yaz$Ya!P$Ya'c$Ya$|$Ya!X$Ya~P#6qO#S$[aP$[aY$[a^$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a'R$[a'a$[a!_$[az$[a!P$[a'c$[a$|$[a!X$[a~P#7dO#S$jaP$jaY$ja^$jai$jas$ja}$ja!]$ja!^$ja!`$ja!f$ja#W$ja#X$ja#Y$ja#Z$ja#[$ja#]$ja#^$ja#_$ja#a$ja#c$ja#e$ja#f$ja'R$ja'a$ja!_$jaz$ja!P$ja!w$ja'c$ja$|$ja!X$ja~P!#lO^!zq}!zq'R!zqz!zq!_!zq'c!zq!P!zq$|!zq!X!zq~P!)`O}&eX'[&eX~PJ^O},VO'['ka~O|0jO}&fX!_&fX~P){O},YO!_'la~O},YO!_'la~P!)`O#i!ba!O!ba~PC]O#i!Ya}!Ya!O!Ya~P#(|O!P0}O#t^O#{1OO~O!O1SO~O'c1TO~P!#lO^$Uq}$Uq'R$Uqz$Uq!_$Uq'c$Uq!P$Uq$|$Uq!X$Uq~P!)`Oz1UO~O],qOj,qO~Or'}Ou(OO'v(SO'n$ti'u$ti}$ti!w$ti~O'[$ti#i$ti~P$'tOr'}Ou(OO'n$vi'u$vi'v$vi}$vi!w$vi~O'[$vi#i$vi~P$(gO#i1VO~P!#lO|1XO'W$_O}&nX!_&nX~O},yO!_'{a~O},yO!X!vO!_'{a~O},yO!X!vO'n&jO!_'{a~O'[$ci}$ci#i$ci!w$ci~P!#lO|1`O'W(^Oz&pX}&pX~P!$ZO}-QOz'|a~O}-QOz'|a~P!#lO!X!vO~O!X!vO#_1jO~Oi1nO!X!vO'n&jO~O}'_i'['_i~P!#lO!w1qO}'_i'['_i~P!#lO!_1tO~O^$Vq}$Vq'R$Vqz$Vq!_$Vq'c$Vq!P$Vq$|$Vq!X$Vq~P!)`O}1xO!P'}X~P!#lO!P&_O$|1{O~O!P&_O$|1{O~P!#lO!P$aX$qZX^$aX'R$aX~P!!cO$q2POrfXufX!PfX'nfX'ufX'vfX^fX'RfX~O$q2PO~O$}2WO'W)cO}&yX!O&yX~O}.SO!O(Ta~OY2[O~O[2]O~O]2`O~OR2bO!P&_O!k2aO$|1{O~O^$ZO'R$ZO~P!#lO!P#xO~P!#lO}2gO!w2iO!O(QX~O!O2jO~Ou(gO!S2sO!T2lO!U2lO!n2rO!o2qO!p2qO!t2pO'X$aO'd(cO'm+aO~O!O2oO~P$0uOR2zO!P.oO!k2yO$|2xO~OR2zO!P.oO!k2yO$|2xO']$bO~O'W(uO}&xX!O&xX~O}.{O!O(Ra~O'd3TO~O]3VO~O[3XO~O!_3[O~P){O^3^O~O^3^O~P){O#_3`O%n3aO~PEuO_/cO!O3eO%|/bO~P`O!X3gO~O&R3hOP&OqQ&OqW&Oq]&Oq^&Oqa&Oqb&Oqg&Oqi&Oqj&Oqk&Oqn&Oqp&Oqu&Oqw&Oqx&Oqy&Oq!P&Oq!Z&Oq!`&Oq!c&Oq!d&Oq!e&Oq!f&Oq!g&Oq!j&Oq#`&Oq#p&Oq#t&Oq${&Oq$}&Oq%P&Oq%Q&Oq%T&Oq%V&Oq%Y&Oq%Z&Oq%]&Oq%j&Oq%p&Oq%r&Oq%t&Oq%v&Oq%y&Oq&P&Oq&T&Oq&V&Oq&X&Oq&Z&Oq&]&Oq&|&Oq'W&Oq'a&Oq'm&Oq'z&Oq!O&Oq%w&Oq_&Oq%|&Oq~O}#Pi!O#Pi~P#(|O!w3jO}#Pi!O#Pi~O}!Ri!O!Ri~P#(|O^$ZO!w3qO'R$ZO~O^$ZO!X!vO!w3qO'R$ZO~O!T3uO!U3uO'X$aO'd(cO'm+aO~O^$ZO!X!vO!`$WO!f3vO!w3qO'R$ZO']$bO'n&jO~O!S3wO~P$9_O!S3wO!q3zO!t3{O~P$9_O^$ZO!X!vO!f3vO!w3qO'R$ZO'n&jO~O}'pq!_'pq^'pq'R'pq~P!)`O}&oO!_'oq~O#S$tiP$tiY$ti^$tii$tis$ti!]$ti!^$ti!`$ti!f$ti#W$ti#X$ti#Y$ti#Z$ti#[$ti#]$ti#^$ti#_$ti#a$ti#c$ti#e$ti#f$ti'R$ti'a$ti!_$tiz$ti!P$ti'c$ti$|$ti!X$ti~P$'tO#S$viP$viY$vi^$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi'R$vi'a$vi!_$viz$vi!P$vi'c$vi$|$vi!X$vi~P$(gO#S$ciP$ciY$ci^$cii$cis$ci}$ci!]$ci!^$ci!`$ci!f$ci#W$ci#X$ci#Y$ci#Z$ci#[$ci#]$ci#^$ci#_$ci#a$ci#c$ci#e$ci#f$ci'R$ci'a$ci!_$ciz$ci!P$ci!w$ci'c$ci$|$ci!X$ci~P!#lO}&ea'[&ea~P!#lO}&fa!_&fa~P!)`O},YO!_'li~O#i!zi}!zi!O!zi~P#(|OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'aQOY#Vii#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~O#W#Vi~P$BuO#W9YO~P$BuOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO'aQOY#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~Oi#Vi~P$D}Oi9[O~P$D}OP#]Oi9[Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O'aQO#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'u#Vi'v#Vi}#Vi!O#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P$GVOY9gO!]9^O#]9^O#^9^O#_9^O~P$GVOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O'aQO#c#Vi#e#Vi#f#Vi#i#Vi'n#Vi'v#Vi}#Vi!O#Vi~O'u#Vi~P$IkO'u!|O~P$IkOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO'aQO'u!|O#e#Vi#f#Vi#i#Vi'n#Vi}#Vi!O#Vi~O'v#Vi~P$KsO'v!}O~P$KsOP#]OY9gOi9[Or!zOs!zOu!{O!]9^O!^!xO!`!yO!f#]O#W9YO#X9ZO#Y9ZO#Z9ZO#[9]O#]9^O#^9^O#_9^O#a9_O#c9aO#e9cO'aQO'u!|O'v!}O~O#f#Vi#i#Vi'n#Vi}#Vi!O#Vi~P$M{O^#gy}#gy'R#gyz#gy!_#gy'c#gy!P#gy$|#gy!X#gy~P!)`OP#ViY#Vii#Vis#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'a#Vi}#Vi!O#Vi~P!#lO!^!xOP'`XY'`Xi'`Xr'`Xs'`Xu'`X!]'`X!`'`X!f'`X#W'`X#X'`X#Y'`X#Z'`X#['`X#]'`X#^'`X#_'`X#a'`X#c'`X#e'`X#f'`X#i'`X'a'`X'n'`X'u'`X'v'`X}'`X!O'`X~O#i#ji}#ji!O#ji~P#(|O!O4]O~O}&ma!O&ma~P#(|O!X!vO'n&jO}&na!_&na~O},yO!_'{i~O},yO!X!vO!_'{i~Oz&pa}&pa~P!#lO!X4dO~O}-QOz'|i~P!#lO}-QOz'|i~Oz4jO~O!X!vO#_4pO~Oi4qO!X!vO'n&jO~Oz4sO~O'[$eq}$eq#i$eq!w$eq~P!#lO^$Vy}$Vy'R$Vyz$Vy!_$Vy'c$Vy!P$Vy$|$Vy!X$Vy~P!)`O}1xO!P'}a~O!P&_O$|4xO~O!P&_O$|4xO~P!#lO^!zy}!zy'R!zyz!zy!_!zy'c!zy!P!zy$|!zy!X!zy~P!)`OY4{O~O}.SO!O(Ti~O]5QO~O[5RO~O'd'OO}&uX!O&uX~O}2gO!O(Qa~O!O5`O~P$0uOu-cO'd(cO'm+aO~O!S5cO!T5bO!U5bO!t0TO'X$aO'd(cO'm+aO~O!o5dO!p5dO~P%,eO!T5bO!U5bO'X$aO'd(cO'm+aO~O!P.oO~O!P.oO$|5fO~O!P.oO$|5fO~P!#lOR5kO!P.oO!k5jO$|5fO~OY5pO}&xa!O&xa~O}.{O!O(Ri~O]5sO~O!_5tO~O!_5uO~O!_5vO~O!_5vO~P){O^5xO~O!X5{O~O!_5}O~O}'si!O'si~P#(|O^$ZO'R$ZO~P!)`O^$ZO!w6SO'R$ZO~O^$ZO!X!vO!w6SO'R$ZO~O!T6XO!U6XO'X$aO'd(cO'm+aO~O^$ZO!X!vO!f6YO!w6SO'R$ZO'n&jO~O!`$WO']$bO~P%1PO!S6ZO~P%0nO}'py!_'py^'py'R'py~P!)`O#S$eqP$eqY$eq^$eqi$eqs$eq}$eq!]$eq!^$eq!`$eq!f$eq#W$eq#X$eq#Y$eq#Z$eq#[$eq#]$eq#^$eq#_$eq#a$eq#c$eq#e$eq#f$eq'R$eq'a$eq!_$eqz$eq!P$eq!w$eq'c$eq$|$eq!X$eq~P!#lO}&fi!_&fi~P!)`O#i!zq}!zq!O!zq~P#(|Or-iOs-iOu-jOPoaYoaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa#ioa'aoa'noa'uoa'voa}oa!Ooa~Or'}Ou(OOP$YaY$Yai$Yas$Ya!]$Ya!^$Ya!`$Ya!f$Ya#W$Ya#X$Ya#Y$Ya#Z$Ya#[$Ya#]$Ya#^$Ya#_$Ya#a$Ya#c$Ya#e$Ya#f$Ya#i$Ya'a$Ya'n$Ya'u$Ya'v$Ya}$Ya!O$Ya~Or'}Ou(OOP$[aY$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a#i$[a'a$[a'n$[a'u$[a'v$[a}$[a!O$[a~OP$jaY$jai$jas$ja!]$ja!^$ja!`$ja!f$ja#W$ja#X$ja#Y$ja#Z$ja#[$ja#]$ja#^$ja#_$ja#a$ja#c$ja#e$ja#f$ja#i$ja'a$ja}$ja!O$ja~P!#lO#i$Uq}$Uq!O$Uq~P#(|O#i$Vq}$Vq!O$Vq~P#(|O!O6eO~O'[$xy}$xy#i$xy!w$xy~P!#lO!X!vO}&ni!_&ni~O!X!vO'n&jO}&ni!_&ni~O},yO!_'{q~Oz&pi}&pi~P!#lO}-QOz'|q~Oz6lO~P!#lOz6lO~O}'_y'['_y~P!#lO}&sa!P&sa~P!#lO!P$pq^$pq'R$pq~P!#lOY6tO~O}.SO!O(Tq~O]6wO~O!P&_O$|6xO~O!P&_O$|6xO~P!#lO!w6yO}&ua!O&ua~O}2gO!O(Qi~P#(|O!T7PO!U7PO'X$aO'd(cO'm+aO~O!S7RO!t3{O~P%@nO!P.oO$|7UO~O!P.oO$|7UO~P!#lO'd7[O~O}.{O!O(Rq~O!_7_O~O!_7_O~P){O!_7aO~O!_7bO~O}#Py!O#Py~P#(|O^$ZO!w7hO'R$ZO~O^$ZO!X!vO!w7hO'R$ZO~O!T7kO!U7kO'X$aO'd(cO'm+aO~O^$ZO!X!vO!f7lO!w7hO'R$ZO'n&jO~O#S$xyP$xyY$xy^$xyi$xys$xy}$xy!]$xy!^$xy!`$xy!f$xy#W$xy#X$xy#Y$xy#Z$xy#[$xy#]$xy#^$xy#_$xy#a$xy#c$xy#e$xy#f$xy'R$xy'a$xy!_$xyz$xy!P$xy!w$xy'c$xy$|$xy!X$xy~P!#lO#i#gy}#gy!O#gy~P#(|OP$ciY$cii$cis$ci!]$ci!^$ci!`$ci!f$ci#W$ci#X$ci#Y$ci#Z$ci#[$ci#]$ci#^$ci#_$ci#a$ci#c$ci#e$ci#f$ci#i$ci'a$ci}$ci!O$ci~P!#lOr'}Ou(OO'v(SOP$tiY$tii$tis$ti!]$ti!^$ti!`$ti!f$ti#W$ti#X$ti#Y$ti#Z$ti#[$ti#]$ti#^$ti#_$ti#a$ti#c$ti#e$ti#f$ti#i$ti'a$ti'n$ti'u$ti}$ti!O$ti~Or'}Ou(OOP$viY$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi#i$vi'a$vi'n$vi'u$vi'v$vi}$vi!O$vi~O#i$Vy}$Vy!O$Vy~P#(|O#i!zy}!zy!O!zy~P#(|O!X!vO}&nq!_&nq~O},yO!_'{y~Oz&pq}&pq~P!#lOz7rO~P!#lO}.SO!O(Ty~O}2gO!O(Qq~O!T8OO!U8OO'X$aO'd(cO'm+aO~O!P.oO$|8RO~O!P.oO$|8RO~P!#lO!_8UO~O&R8VOP&O!ZQ&O!ZW&O!Z]&O!Z^&O!Za&O!Zb&O!Zg&O!Zi&O!Zj&O!Zk&O!Zn&O!Zp&O!Zu&O!Zw&O!Zx&O!Zy&O!Z!P&O!Z!Z&O!Z!`&O!Z!c&O!Z!d&O!Z!e&O!Z!f&O!Z!g&O!Z!j&O!Z#`&O!Z#p&O!Z#t&O!Z${&O!Z$}&O!Z%P&O!Z%Q&O!Z%T&O!Z%V&O!Z%Y&O!Z%Z&O!Z%]&O!Z%j&O!Z%p&O!Z%r&O!Z%t&O!Z%v&O!Z%y&O!Z&P&O!Z&T&O!Z&V&O!Z&X&O!Z&Z&O!Z&]&O!Z&|&O!Z'W&O!Z'a&O!Z'm&O!Z'z&O!Z!O&O!Z%w&O!Z_&O!Z%|&O!Z~O^$ZO!w8[O'R$ZO~O^$ZO!X!vO!w8[O'R$ZO~OP$eqY$eqi$eqs$eq!]$eq!^$eq!`$eq!f$eq#W$eq#X$eq#Y$eq#Z$eq#[$eq#]$eq#^$eq#_$eq#a$eq#c$eq#e$eq#f$eq#i$eq'a$eq}$eq!O$eq~P!#lO}&uq!O&uq~P#(|O^$ZO!w8qO'R$ZO~OP$xyY$xyi$xys$xy!]$xy!^$xy!`$xy!f$xy#W$xy#X$xy#Y$xy#Z$xy#[$xy#]$xy#^$xy#_$xy#a$xy#c$xy#e$xy#f$xy#i$xy'a$xy}$xy!O$xy~P!#lO'c'eX~P.jO'cZXzZX!_ZX%nZX!PZX$|ZX!XZX~P$zO!XcX!_ZX!_cX'ncX~P;aOP9SOQ9SO]cOa:jOb!iOgcOi9SOjcOkcOn9SOp9SOuROwcOxcOycO!PSO!Z9UO!`UO!c9SO!d9SO!e9SO!f9SO!g9SO!j!hO#p!kO#t^O'W'^O'aQO'mYO'z:hO~O}9eO!O$Xa~O]#pOg#}Oi#qOj#pOk#pOn$OOp9jOu#wO!P#xO!Z:mO!`#uO#R9pO#p$SO$Z9lO$]9nO$`$TO'W&vO'a#rO~O#`'eO~P&+}O!OZX!OcX~P;aO#S9XO~O!X!vO#S9XO~O!w9hO~O#_9^O~O!w9qO}'sX!O'sX~O!w9hO}'qX!O'qX~O#S9rO~O'[9tO~P!#lO#S9yO~O#S9zO~O!X!vO#S9{O~O!X!vO#S9rO~O#i9|O~P#(|O#S9}O~O#S:OO~O#S:PO~O#S:QO~O#i:RO~P!#lO#i:SO~P!#lO#t~!^!n!p!q#Q#R'z$Z$]$`$q${$|$}%T%V%Y%Z%]%_~TS#t'z#Xy'T'U#v'T'W'd~", +- goto: "#Dk(XPPPPPPP(YP(jP*^PPPP-sPP.Y3j5^5qP5qPPP5q5qP5qP7_PP7dP7xPPPP<XPPPP<X>wPPP>}AYP<XPCsPPPPEk<XPPPPPGd<XPPJcK`PPPPKdL|PMUNVPK`<X<X!#^!&V!*v!*v!.TPPP!.[!1O<XPPPPPPPPPP!3sP!5UPP<X!6cP<XP<X<X<X<XP<X!8vPP!;mP!>`!>h!>l!>lP!;jP!>p!>pP!AcP!Ag<X<X!Am!D_5qP5qP5q5qP!Eb5q5q!GY5q!I[5q!J|5q5q!Kj!Md!Md!Mh!Md!MpP!MdP5q!Nl5q# v5q5q-sPPP##TPP##m##mP##mP#$S##mPP#$YP#$PP#$P#$lMQ#$P#%Z#%a#%d(Y#%g(YP#%n#%n#%nP(YP(YP(YP(YPP(YP#%t#%wP#%w(YPPP(YP(YP(YP(YP(YP(Y(Y#%{#&V#&]#&c#&q#&w#&}#'X#'_#'i#'o#'}#(T#(Z#(i#)O#*b#*p#*v#*|#+S#+Y#+d#+j#+p#+z#,^#,dPPPPPPPPP#,jPP#-^#1[PP#2r#2y#3RP#7_PP#7c#9v#?p#?t#?w#?z#@V#@YPP#@]#@a#AO#As#Aw#BZPP#B_#Be#BiP#Bl#Bp#Bs#Cc#Cy#DO#DR#DU#D[#D_#Dc#DgmhOSj}!m$Y%a%d%e%g*i*n/]/`Q$gmQ$npQ%XyS&R!b+UQ&f!iS(f#x(kQ)a$hQ)n$pQ*Y%RQ+[&YS+c&_+eQ+u&gQ-a(mQ.z*ZY0P+g+h+i+j+kS2l.o2nU3u0Q0S0VU5b2q2r2sS6X3w3zS7P5c5dQ7k6ZR8O7R$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!j'`#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ(v$PQ)f$jQ*[%UQ*c%^Q,P9iQ-|)ZQ.X)gQ/S*aQ2V.SQ3R.{Q4U9jR4}2WpeOSjy}!m$Y%W%a%d%e%g*i*n/]/`R*^%Y&WVOSTjkn}!S!W!]!j!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:j:kW!cRU!`&SQ$`lQ$fmS$kp$pv$urs!q!t$W$s&[&o&r)r)s)t*g+O+_+z+|/f0bQ$}wQ&c!hQ&e!iS(Y#u(dS)`$g$hQ)d$jQ)q$rQ*T%PQ*X%RS+t&f&gQ,}(ZQ.Q)aQ.W)gQ.Y)hQ.])lQ.u*US.y*Y*ZQ0^+uQ1W,yQ2U.SQ2Y.VQ2_._Q3Q.zQ4a1XQ4|2WQ5P2[Q6s4{R7u6t!Y$dm!i$f$g$h&Q&e&f&g(e)`)a+R+b+t+u-Z.Q/u/|0R0^1m3t3y6V7i8]Q)X$`Q)y$zQ)|${Q*W%RQ.a)qQ.t*TU.x*X*Y*ZQ2{.uS3P.y.zQ5]2kQ5o3QS6}5^5aS7|7O7QQ8g7}R8v8hW#{a$b(s:hS$zt%WQ${uQ$|vR)w$x$V#za!v!x#c#u#w$Q$R$V&b'x(R(T(U(](a(q(r)U)W)Z)x){+q,V-Q-S-l-v-x.f.i.q.s1V1`1j1q1x1{2P2b2x2z4d4p4x5f5k6x7U8R9g9k9l9m9n9o9p9u9v9w9x9y9z9}:O:R:S:h:n:oV(w$P9i9jU&V!b$t+XQ'P!zQ)k$mQ.j)}Q1r-iR5X2g&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k$]#`Z!_!n$^%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,a,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:a&ZcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ&T!bR/q+UY%}!b&R&Y+U+[S(e#x(kS+b&_+eS-Z(f(mQ-[(gQ-b(nQ.l*PU/|+c+g+hU0R+i+j+kS0W+l2pQ1m-aQ1o-cQ1p-dS2k.o2nU3t0P0Q0SQ3x0TQ3y0VS5^2l2sS5a2q2rU6V3u3w3zQ6[3{S7O5b5cQ7Q5dS7i6X6ZS7}7P7RQ8]7kR8h8OlhOSj}!m$Y%a%d%e%g*i*n/]/`Q%i!QS&s!u9XQ)^$eQ*R$}Q*S%OQ+r&dS,T&x9rS-n)O9{Q.O)_Q.n*QQ/d*pQ/e*qQ/m+PQ0U+iQ0[+sS1w-o:PQ2Q.PS2T.R:QQ3k/oQ3n/wQ3}0]Q4z2RQ5|3hQ6P3mQ6T3sQ6]4OQ7c5}Q7f6UQ8X7gQ8l8VQ8n8ZR8y8p$W#_Z!_!n%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:aU(p#y&w0{T)S$^,a$W#^Z!_!n%u%y&t&{'R'S'T'U'V'W'X'Y'Z'[']'_'b'f'p)j*y+S+]+v,U,[,_,o-m/k/n0_0i0m0n0o0p0q0r0s0t0u0v0w0x0y0|1R1v2S3l3o4P4S4T4Y4Z5Z6O6R6_6c6d7e7x8Y8o8z9T:aQ'a#_S)R$^,aR-p)S&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ%d{Q%e|Q%g!OQ%h!PR/[*lQ&`!hQ)T$`Q+o&cS-u)X)qS0X+m+nW1z-r-s-t.aS3|0Y0ZU4w1|1}2OU6q4v5T5UQ7t6rR8c7wT+d&_+eS+b&_+eU/|+c+g+hU0R+i+j+kS0W+l2pS2k.o2nU3t0P0Q0SQ3x0TQ3y0VS5^2l2sS5a2q2rU6V3u3w3zQ6[3{S7O5b5cQ7Q5dS7i6X6ZS7}7P7RQ8]7kR8h8OS+d&_+eT2m.o2nS&m!p/YQ,|(YQ-X(eS/{+b2kQ1],}S1g-Y-bU3v0R0W5aQ4`1WS4n1n1pU6Y3x3y7QQ6g4aQ6p4qR7l6[Q!wXS&l!p/YQ)P$XQ)[$cQ)b$iQ+x&mQ,{(YQ-W(eQ-](hQ-})]Q.v*VS/z+b2kS1[,|,}S1f-X-bQ1i-[Q1l-^Q2}.wW3r/{0R0W5aQ4_1WQ4c1]S4h1g1pQ4o1oQ5m3OW6W3v3x3y7QS6f4`4aQ6k4jQ6n4nQ6{5[Q7Y5nS7j6Y6[Q7n6gQ7p6lQ7s6pQ7z6|Q8T7ZQ8^7lQ8a7rQ8e7{Q8t8fQ8|8uQ9Q8}Q:Z:UQ:d:_R:e:`$nWORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qS!wn!j!j:T#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR:Z:j$nXORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qQ$Xb!Y$cm!i$f$g$h&Q&e&f&g(e)`)a+R+b+t+u-Z.Q/u/|0R0^1m3t3y6V7i8]S$in!jQ)]$dQ*V%RW.w*W*X*Y*ZU3O.x.y.zQ5[2kS5n3P3QU6|5]5^5aQ7Z5oU7{6}7O7QS8f7|7}S8u8g8hQ8}8v!j:U#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kQ:_:iR:`:j$f]OSTjk}!S!W!]!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qU!gRU!`v$urs!q!t$W$s&[&o&r)r)s)t*g+O+_+z+|/f0bQ*d%^!h:V#[#j'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR:Y&SS&W!b$tR/s+X$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!j'`#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kR*c%^$noORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8qQ'P!z!k:W#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k!h#UZ!_$^%u%y&t&{'Y'Z'[']'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9T!R9`'_'p+S,a/k/n0m0u0v0w0x0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!d#WZ!_$^%u%y&t&{'[']'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9T}9b'_'p+S,a/k/n0m0w0x0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!`#[Z!_$^%u%y&t&{'b'f)j*y+]+v,U,[,o-m0_0i0y1v2S3o4P4S6R7e8Y8o8z9Tl(U#s&y(},w-P-e-f0g1u4^4r:[:f:gx:k'_'p+S,a/k/n0m0|1R3l4T4Y4Z5Z6O6_6c6d7x:a!`:n&u'd(X(_+n,S,l-T-q-t.e.g0Z0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7WZ:o0z4X6`7m8_&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kS#k`#lR1O,d&a_ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j#l$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,d,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kS#f^#mT'i#h'mT#g^#mT'k#h'm&a`ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#j#l$Y$l%Y%]%^%a%c%d%e%g%k%v&O&S&Z&a&k&x&|'r'|)O)V*e*i*n*}+Q+p+w,Y,`,d,e-j-o-w.R.r/T/U/V/X/]/`/b/p/y0`0j0}2a2i2y3^3`3a3j3q5j5x6S6y7h8[8q9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:kT#k`#lQ#n`R't#l$nbORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$Y$l%Y%]%^%a%c%d%e%g%k%v&O&Z&a&k&x&|'|)O)V*e*i*n+p+w,Y,`-j-o-w.R.r/T/U/V/X/]/`/b/y0`0j2a2y3^3`3a3q5j5x6S7h8[8q!k:i#[#j&S'r*}+Q,e/p0}2i3j6y9S9U9X9Y9Z9[9]9^9_9`9a9b9c9d9e9h9q9r9t9{9|:P:Q:k#RdOSUj}!S!W!m!{#j$Y%Y%]%^%a%c%d%e%g%k&O&a'r)V*e*i*n+p,e-j-w.r/T/U/V/X/]/`/b0}2a2y3^3`3a5j5xt#ya!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:o!|&w!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:RQ({$TQ,p'}c0{9g9l9n9p9v9x9z:O:St#va!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:oS(h#x(kQ(|$UQ-^(i!|:]!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:Rb:^9g9l9n9p9v9x9z:O:SQ:b:lR:c:mt#ya!x$Q$R$V(R(T(U(](q(r,V-l1V1q:h:n:o!|&w!v#c#u#w&b'x(a)U)W)Z)x){+q-Q-S-v-x.f.i.q.s1`1j1x1{2P2b2x2z4d4p4x5f5k6x7U8R9k9m9o9u9w9y9}:Rc0{9g9l9n9p9v9x9z:O:SlfOSj}!m$Y%a%d%e%g*i*n/]/`Q(`#wQ*u%nQ*v%pR1_-Q$U#za!v!x#c#u#w$Q$R$V&b'x(R(T(U(](a(q(r)U)W)Z)x){+q,V-Q-S-l-v-x.f.i.q.s1V1`1j1q1x1{2P2b2x2z4d4p4x5f5k6x7U8R9g9k9l9m9n9o9p9u9v9w9x9y9z9}:O:R:S:h:n:oQ)z${Q.h)|Q2e.gR5W2fT(j#x(kS(j#x(kT2m.o2nQ)[$cQ-](hQ-})]Q.v*VQ2}.wQ5m3OQ6{5[Q7Y5nQ7z6|Q8T7ZQ8e7{Q8t8fQ8|8uR9Q8}l(R#s&y(},w-P-e-f0g1u4^4r:[:f:g!`9u&u'd(X(_+n,S,l-T-q-t.e.g0Z0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7WZ9v0z4X6`7m8_n(T#s&y(},u,w-P-e-f0g1u4^4r:[:f:g!b9w&u'd(X(_+n,S,l-T-q-t.e.g0Z0d0f1^1b2O2d2f2v4R4e4k4t4y5U5i6^6i6o7W]9x0z4X6`6a7m8_peOSjy}!m$Y%W%a%d%e%g*i*n/]/`Q%TxR*e%^peOSjy}!m$Y%W%a%d%e%g*i*n/]/`R%TxQ*O$|R.d)wqeOSjy}!m$Y%W%a%d%e%g*i*n/]/`Q.p*TS2w.t.uW5e2t2u2v2{U7T5g5h5iU8P7S7V7WQ8i8QR8w8jQ%[yR*_%WR3U.}R7]5pS$kp$pR.Y)hQ%azR*i%bR*o%hT/^*n/`QjOQ!mST$]j!mQ'z#rR,m'zQ!YQR%s!YQ!^RU%w!^%x*zQ%x!_R*z%yQ+V&TR/r+VQ,W&yR0h,WQ,Z&{S0k,Z0lR0l,[Q+e&_R/}+eQ&]!eQ*{%zT+`&]*{Q+Y&WR/t+YQ&p!rQ+y&nU+}&p+y0cR0c,OQ'm#hR,f'mQ#l`R's#lQ#bZU'c#b*x9fQ*x9TR9f'pQ,z(YW1Y,z1Z4b6hU1Z,{,|,}S4b1[1]R6h4c#q(P#s&u&y'd(X(_(x(y(}+n,Q,R,S,l,u,v,w-P-T-e-f-q-t.e.g0Z0d0e0f0g0z1^1b1u2O2d2f2v4R4V4W4X4^4e4k4r4t4y5U5i6^6`6a6b6i6o7W7m8_:[:f:gQ-R(_U1a-R1c4fQ1c-TR4f1bQ(k#xR-_(kQ(t#|R-h(tQ1y-qR4u1yQ)u$vR.c)uQ2h.jS5Y2h6zR6z5ZQ*Q$}R.m*QQ2n.oR5_2nQ.|*[S3S.|5qR5q3UQ.T)dW2X.T2Z5O6uQ2Z.WQ5O2YR6u5PQ)i$kR.Z)iQ/`*nR3d/`WiOSj!mQ%f}Q)Q$YQ*h%aQ*j%dQ*k%eQ*m%gQ/Z*iS/^*n/`R3c/]Q$[gQ%j!RQ%m!TQ%o!UQ%q!VQ)p$qQ)v$wQ*^%[Q*s%lS/P*_*bQ/g*rQ/h*uQ/i*vS/x+b2kQ1d-VQ1e-WQ1k-]Q2^.^Q2c.eQ2|.vQ3W/RQ3b/[Y3p/z/{0R0W5aQ4g1fQ4i1hQ4l1lQ5S2`Q5V2dQ5l2}Q5r3V[6Q3o3r3v3x3y7QQ6j4hQ6m4mQ6v5QQ7X5mQ7^5sW7d6R6W6Y6[Q7o6kQ7q6nQ7v6wQ7y6{Q8S7YU8W7e7j7lQ8`7pQ8b7sQ8d7zQ8k8TS8m8Y8^Q8r8aQ8s8eQ8x8oQ8{8tQ9O8zQ9P8|R9R9QQ$emQ&d!iU)_$f$g$hQ+P&QU+s&e&f&gQ-V(eS.P)`)aQ/o+RQ/w+bS0]+t+uQ1h-ZQ2R.QQ3m/uS3s/|0RQ4O0^Q4m1mS6U3t3yQ7g6VQ8Z7iR8p8]S#ta:hR)Y$bU#|a$b:hR-g(sQ#saS&u!v)ZQ&y!xQ'd#cQ(X#uQ(_#wQ(x$QQ(y$RQ(}$VQ+n&bQ,Q9kQ,R9mQ,S9oQ,l'xQ,u(RQ,v(TQ,w(UQ-P(]Q-T(aQ-e(qQ-f(rd-q)U-v.q1{2x4x5f6x7U8RQ-t)WQ.e)xQ.g){Q0Z+qQ0d9uQ0e9wQ0f9yQ0g,VQ0z9gQ1^-QQ1b-SQ1u-lQ2O-xQ2d.fQ2f.iQ2v.sQ4R9}Q4V9lQ4W9nQ4X9pQ4^1VQ4e1`Q4k1jQ4r1qQ4t1xQ4y2PQ5U2bQ5i2zQ6^:RQ6`9zQ6a9vQ6b9xQ6i4dQ6o4pQ7W5kQ7m:OQ8_:SQ:[:hQ:f:nR:g:oT'y#r'zlgOSj}!m$Y%a%d%e%g*i*n/]/`S!oU%cQ%l!SQ%r!WQ'Q!{Q'q#jS*b%Y%]Q*f%^Q*r%kQ*|&OQ+m&aQ,j'rQ-s)VQ/W*eQ0Y+pQ1Q,eQ1s-jQ1}-wQ2u.rQ3Y/TQ3Z/UQ3]/VQ3_/XQ3f/bQ4[0}Q5T2aQ5h2yQ5w3^Q5y3`Q5z3aQ7V5jR7`5x!vZOSUj}!S!m!{$Y%Y%]%^%a%c%d%e%g%k&O&a)V*e*i*n+p-j-w.r/T/U/V/X/]/`/b2a2y3^3`3a5j5xQ!_RQ!nTQ$^kQ%u!]Q%y!`Q&t!uQ&{!yQ'R#OQ'S#PQ'T#QQ'U#RQ'V#SQ'W#TQ'X#UQ'Y#VQ'Z#WQ'[#XQ']#YQ'_#[Q'b#aQ'f#dW'p#j'r,e0}Q)j$lQ*y%vS+S&S/pQ+]&ZQ+v&kQ,U&xQ,[&|Q,_9SQ,a9UQ,o'|Q-m)OQ/k*}Q/n+QQ0_+wQ0i,YQ0m9XQ0n9YQ0o9ZQ0p9[Q0q9]Q0r9^Q0s9_Q0t9`Q0u9aQ0v9bQ0w9cQ0x9dQ0y,`Q0|9hQ1R9eQ1v-oQ2S.RQ3l9qQ3o/yQ4P0`Q4S0jQ4T9rQ4Y9tQ4Z9{Q5Z2iQ6O3jQ6R3qQ6_9|Q6c:PQ6d:QQ7e6SQ7x6yQ8Y7hQ8o8[Q8z8qQ9T!WR:a:kT!XQ!YR!aRR&U!bS&Q!b+US+R&R&YR/u+[R&z!xR&}!yT!sU$WS!rU$WU$vrs*gS&n!q!tQ+{&oQ,O&rQ.b)tS0a+z+|R4Q0b[!dR!`$s&[)r+_h!pUrs!q!t$W&o&r)t+z+|0bQ/Y*gQ/l+OQ3i/fT:X&S)sT!fR$sS!eR$sS%z!`)rS+T&S)sQ+^&[R/v+_T&X!b$tQ#h^R'v#mT'l#h'mR1P,dT([#u(dR(b#wQ-r)UQ1|-vQ2t.qQ4v1{Q5g2xQ6r4xQ7S5fQ7w6xQ8Q7UR8j8RlhOSj}!m$Y%a%d%e%g*i*n/]/`Q%ZyR*^%WV$wrs*gR.k)}R*]%UQ$opR)o$pR)e$jT%_z%bT%`z%bT/_*n/`", +- nodeNames: "⚠ ArithOp ArithOp extends LineComment BlockComment Script ExportDeclaration export Star as VariableName from String ; default FunctionDeclaration async function VariableDefinition TypeParamList TypeDefinition ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType VoidType void TypeofType typeof MemberExpression . ?. PropertyName [ TemplateString null super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewExpression new TypeArgList CompareOp < ) ( ArgList UnaryExpression await yield delete LogicOp BitOp ParenthesizedExpression ClassExpression class extends ClassBody MethodDeclaration Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression PrivatePropertyName BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof in const CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXStartTag JSXSelfClosingTag JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast ArrowFunction TypeParamList SequenceExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature CallSignature TypePredicate is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody MethodDeclaration AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try catch finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement", +- maxTerm: 330, ++ states: "$1dO`QYOOO'QQ!LdO'#CgO'XOSO'#DSO)dQYO'#DXO)tQYO'#DdO){QYO'#DnO-xQYO'#DtOOQO'#EX'#EXO.]QWO'#EWO.bQWO'#EWOOQ!LS'#Eb'#EbO0aQ!LdO'#IsO2wQ!LdO'#ItO3eQWO'#EvO3jQpO'#F_OOQ!LS'#FO'#FOO3uO!bO'#FOO4TQWO'#FfO5bQWO'#FeOOQ!LS'#It'#ItOOQ!LQ'#Is'#IsOOQQ'#J^'#J^O5gQWO'#HlO5lQ!LYO'#HmOOQQ'#Ie'#IeOOQQ'#Hn'#HnQ`QYOOO){QYO'#DfO5tQWO'#GYO5yQ#tO'#ClO6XQWO'#EVO6dQWO'#EcO6iQ#tO'#E}O7TQWO'#GYO7YQWO'#G^O7eQWO'#G^O7sQWO'#GaO7sQWO'#GbO7sQWO'#GdO5tQWO'#GgO8dQWO'#GjO9rQWO'#CcO:SQWO'#GwO:[QWO'#G}O:[QWO'#HPO`QYO'#HRO:[QWO'#HTO:[QWO'#HWO:aQWO'#H^O:fQ!LZO'#HbO){QYO'#HdO:qQ!LZO'#HfO:|Q!LZO'#HhO5lQ!LYO'#HjO){QYO'#IuOOOS'#Hp'#HpO;XOSO,59nOOQ!LS,59n,59nO=jQbO'#CgO=tQYO'#HqO>RQWO'#IvO@QQbO'#IvO'dQYO'#IvO@XQWO,59sO@oQ&jO'#D^OAhQWO'#EXOAuQWO'#JROBQQWO'#JQOBYQWO,5:uOB_QWO'#JPOBfQWO'#DuO5yQ#tO'#EVOBtQWO'#EVOCPQ`O'#E}OOQ!LS,5:O,5:OOCXQYO,5:OOEVQ!LdO,5:YOEsQWO,5:`OF^Q!LYO'#JOO7YQWO'#I}OFeQWO'#I}OFmQWO,5:tOFrQWO'#I}OGQQYO,5:rOIQQWO'#ESOJ[QWO,5:rOKkQWO'#DhOKrQYO'#DmOK|Q&jO,5:{O){QYO,5:{OOQQ'#En'#EnOOQQ'#Ep'#EpO){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}O){QYO,5:}OOQQ'#Et'#EtOLUQYO,5;_OOQ!LS,5;d,5;dOOQ!LS,5;e,5;eONUQWO,5;eOOQ!LS,5;f,5;fO){QYO'#H{ONZQ!LYO,5<RONuQWO,5:}O){QYO,5;bON|QpO'#FTO! yQpO'#JVO! eQpO'#JVO!!QQpO'#JVOOQO'#JV'#JVO!!fQpO,5;mOOOO,5;y,5;yO!!wQYO'#FaOOOO'#Hz'#HzO3uO!bO,5;jO!#OQpO'#FcOOQ!LS,5;j,5;jO!#oQ,UO'#CqOOQ!LS'#Ct'#CtO!$SQWO'#CtO!$XOSO'#CxO!$uQ#tO,5<OO!$|QWO,5<QO!&YQWO'#FpO!&gQWO'#FqO!&lQWO'#FuO!'nQ&jO'#FyO!(aQ,UO'#InOOQ!LS'#In'#InO!(kQWO'#ImO!(yQWO'#IlOOQ!LS'#Cr'#CrOOQ!LS'#Cy'#CyO!)RQWO'#C{OJaQWO'#FhOJaQWO'#FjO!)WQWO'#FlO!)]QWO'#FmO!)bQWO'#FsOJaQWO'#FxO!)gQWO'#EYO!*OQWO,5<PO`QYO,5>WOOQQ'#Ih'#IhOOQQ,5>X,5>XOOQQ-E;l-E;lO!+zQ!LdO,5:QOOQ!LQ'#Co'#CoO!,kQ#tO,5<tOOQO'#Ce'#CeO!,|QWO'#CpO!-UQ!LYO'#IiO5bQWO'#IiO:aQWO,59WO!-dQpO,59WO!-lQ#tO,59WO5yQ#tO,59WO!-wQWO,5:rO!.PQWO'#GvO!.[QWO'#JbO){QYO,5;gO!.dQ&jO,5;iO!.iQWO,5=aO!.nQWO,5=aO!.sQWO,5=aO5lQ!LYO,5=aO5tQWO,5<tO!/RQWO'#EZO!/dQ&jO'#E[OOQ!LQ'#JP'#JPO!/uQ!LYO'#J_O5lQ!LYO,5<xO7sQWO,5=OOOQO'#Cq'#CqO!0QQpO,5<{O!0YQ#tO,5<|O!0eQWO,5=OO!0jQ`O,5=RO:aQWO'#GlO5tQWO'#GnO!0rQWO'#GnO5yQ#tO'#GqO!0wQWO'#GqOOQQ,5=U,5=UO!0|QWO'#GrO!1UQWO'#ClO!1ZQWO,58}O!1eQWO,58}O!3gQYO,58}OOQQ,58},58}O!3tQ!LYO,58}O){QYO,58}O!4PQYO'#GyOOQQ'#Gz'#GzOOQQ'#G{'#G{O`QYO,5=cO!4aQWO,5=cO){QYO'#DtO`QYO,5=iO`QYO,5=kO!4fQWO,5=mO`QYO,5=oO!4kQWO,5=rO!4pQYO,5=xOOQQ,5=|,5=|O){QYO,5=|O5lQ!LYO,5>OOOQQ,5>Q,5>QO!8qQWO,5>QOOQQ,5>S,5>SO!8qQWO,5>SOOQQ,5>U,5>UO!8vQ`O,5?aOOOS-E;n-E;nOOQ!LS1G/Y1G/YO!8{QbO,5>]O){QYO,5>]OOQO-E;o-E;oO!9VQWO,5?bO!9_QbO,5?bO!9fQWO,5?lOOQ!LS1G/_1G/_O!9nQpO'#DQOOQO'#Ix'#IxO){QYO'#IxO!:]QpO'#IxO!:zQpO'#D_O!;]Q&jO'#D_O!=hQYO'#D_O!=oQWO'#IwO!=wQWO,59xO!=|QWO'#E]O!>[QWO'#JSO!>dQWO,5:vO!>zQ&jO'#D_O){QYO,5?mO!?UQWO'#HvO!9fQWO,5?lOOQ!LQ1G0a1G0aO!@bQ&jO'#DxOOQ!LS,5:a,5:aO){QYO,5:aOIQQWO,5:aO!@iQWO,5:aO:aQWO,5:qO!-dQpO,5:qO!-lQ#tO,5:qO5yQ#tO,5:qOOQ!LS1G/j1G/jOOQ!LS1G/z1G/zOOQ!LQ'#ER'#ERO){QYO,5?jO!@tQ!LYO,5?jO!AVQ!LYO,5?jO!A^QWO,5?iO!AfQWO'#HxO!A^QWO,5?iOOQ!LQ1G0`1G0`O7YQWO,5?iOOQ!LS1G0^1G0^O!BQQ!LdO1G0^O!BqQ!LbO,5:nOOQ!LS'#Fo'#FoO!C_Q!LdO'#InOGQQYO1G0^O!E^Q#tO'#IyO!EhQWO,5:SO!EmQbO'#IzO){QYO'#IzO!EwQWO,5:XOOQ!LS'#DQ'#DQOOQ!LS1G0g1G0gO!E|QWO1G0gO!H_Q!LdO1G0iO!HfQ!LdO1G0iO!JyQ!LdO1G0iO!KQQ!LdO1G0iO!MXQ!LdO1G0iO!MlQ!LdO1G0iO#!]Q!LdO1G0iO#!dQ!LdO1G0iO#$wQ!LdO1G0iO#%OQ!LdO1G0iO#&sQ!LdO1G0iO#)mQ7^O'#CgO#+hQ7^O1G0yO#-cQ7^O'#ItOOQ!LS1G1P1G1PO#-vQ!LdO,5>gOOQ!LQ-E;y-E;yO#.gQ!LdO1G0iOOQ!LS1G0i1G0iO#0iQ!LdO1G0|O#1YQpO,5;qO#1bQpO,5;rO#1jQpO'#FYO#2RQWO'#FXOOQO'#JW'#JWOOQO'#Hy'#HyO#2WQpO1G1XOOQ!LS1G1X1G1XOOOO1G1d1G1dO#2iQ7^O'#IsO#2sQWO,5;{OLUQYO,5;{OOOO-E;x-E;xOOQ!LS1G1U1G1UOOQ!LS,5;},5;}O#2xQpO,5;}OOQ!LS,59`,59`OIQQWO'#IpOOOS'#Ho'#HoO#2}OSO,59dOOQ!LS,59d,59dO){QYO1G1jO!)]QWO'#H}O#3YQWO,5<cOOQ!LS,5<`,5<`OOQO'#GT'#GTOJaQWO,5<nOOQO'#GV'#GVOJaQWO,5<pOJaQWO,5<rOOQO1G1l1G1lO#3eQ`O'#CoO#3xQ`O,5<[O#4PQWO'#JZO5tQWO'#JZO#4_QWO,5<^OJaQWO,5<]O#4dQ`O'#FoO#4qQ`O'#J[O#4{QWO'#J[OIQQWO'#J[O#5QQWO,5<aOOQ!LQ'#Dc'#DcO#5VQWO'#FrO#5bQpO'#FzO!'iQ&jO'#FzO!'iQ&jO'#F|O#5sQWO'#F}O!)bQWO'#GQOOQO'#IP'#IPO#5xQ&jO,5<eOOQ!LS,5<e,5<eO#6PQ&jO'#FzO#6_Q&jO'#F{O#6gQ&jO'#F{OOQ!LS,5<s,5<sOJaQWO,5?XOJaQWO,5?XO#6lQWO'#IQO#6wQWO,5?WOOQ!LS'#Cg'#CgO#7kQ#tO,59gOOQ!LS,59g,59gO#8^Q#tO,5<SO#9PQ#tO,5<UO#9ZQWO,5<WOOQ!LS,5<X,5<XO#9`QWO,5<_O#9eQ#tO,5<dOGQQYO1G1kO#9uQWO1G1kOOQQ1G3r1G3rOOQ!LS1G/l1G/lONUQWO1G/lOOQQ1G2`1G2`OIQQWO1G2`O){QYO1G2`OIQQWO1G2`O#9zQWO1G2`O#:YQWO,59[O#;cQWO'#ESOOQ!LQ,5?T,5?TO#;mQ!LYO,5?TOOQQ1G.r1G.rO:aQWO1G.rO!-dQpO1G.rO!-lQ#tO1G.rO#;{QWO1G0^O#<QQWO'#CgO#<]QWO'#JcO#<eQWO,5=bO#<jQWO'#JcO#<oQWO'#JcO#<tQWO'#IYO#=SQWO,5?|O#=[QbO1G1ROOQ!LS1G1T1G1TO5tQWO1G2{O#=cQWO1G2{O#=hQWO1G2{O#=mQWO1G2{OOQQ1G2{1G2{O#=rQ#tO1G2`O7YQWO'#JQO7YQWO'#E]O7YQWO'#ISO#>TQ!LYO,5?yOOQQ1G2d1G2dO!0eQWO1G2jOIQQWO1G2gO#>`QWO1G2gOOQQ1G2h1G2hOIQQWO1G2hO#>eQWO1G2hO#>mQ&jO'#GfOOQQ1G2j1G2jO!'iQ&jO'#IUO!0jQ`O1G2mOOQQ1G2m1G2mOOQQ,5=W,5=WO#>uQ#tO,5=YO5tQWO,5=YO#5sQWO,5=]O5bQWO,5=]O!-dQpO,5=]O!-lQ#tO,5=]O5yQ#tO,5=]O#?WQWO'#JaO#?cQWO,5=^OOQQ1G.i1G.iO#?hQ!LYO1G.iO#?sQWO1G.iO!)RQWO1G.iO5lQ!LYO1G.iO#?xQbO,5@OO#@SQWO,5@OO#@_QYO,5=eO#@fQWO,5=eO7YQWO,5@OOOQQ1G2}1G2}O`QYO1G2}OOQQ1G3T1G3TOOQQ1G3V1G3VO:[QWO1G3XO#@kQYO1G3ZO#DfQYO'#HYOOQQ1G3^1G3^O:aQWO1G3dO#DsQWO1G3dO5lQ!LYO1G3hOOQQ1G3j1G3jOOQ!LQ'#Fv'#FvO5lQ!LYO1G3lO5lQ!LYO1G3nOOOS1G4{1G4{O#D{Q`O,5<RO#ETQbO1G3wO#E_QWO1G4|O#EgQWO1G5WO#EoQWO,5?dOLUQYO,5:wO7YQWO,5:wO:aQWO,59yOLUQYO,59yO!-dQpO,59yO#EtQ7^O,59yOOQO,5:w,5:wO#FOQ&jO'#HrO#FfQWO,5?cOOQ!LS1G/d1G/dO#FnQ&jO'#HwO#GSQWO,5?nOOQ!LQ1G0b1G0bO!;]Q&jO,59yO#G[QbO1G5XOOQO,5>b,5>bO7YQWO,5>bOOQO-E;t-E;tOOQ!LQ'#EO'#EOO#GfQ!LrO'#EPO!@YQ&jO'#DyOOQO'#Hu'#HuO#HQQ&jO,5:dOOQ!LS,5:d,5:dO#HXQ&jO'#DyO#HjQ&jO'#DyO#HqQ&jO'#EUO#HtQ&jO'#EPO#IRQ&jO'#EPO!@YQ&jO'#EPO#IfQWO1G/{O#IkQ`O1G/{OOQ!LS1G/{1G/{O){QYO1G/{OIQQWO1G/{OOQ!LS1G0]1G0]O:aQWO1G0]O!-dQpO1G0]O!-lQ#tO1G0]O#IrQ!LdO1G5UO){QYO1G5UO#JSQ!LYO1G5UO#JeQWO1G5TO7YQWO,5>dOOQO,5>d,5>dO#JmQWO,5>dOOQO-E;v-E;vO#JeQWO1G5TO#J{Q!LdO,59gO#LzQ!LdO,5<SO#N|Q!LdO,5<UO$#OQ!LdO,5<dOOQ!LS7+%x7+%xO$%WQ!LdO7+%xO$%wQWO'#HsO$&RQWO,5?eOOQ!LS1G/n1G/nO$&ZQYO'#HtO$&hQWO,5?fO$&pQbO,5?fOOQ!LS1G/s1G/sOOQ!LS7+&R7+&RO$&zQ7^O,5:YO){QYO7+&eO$'UQ7^O,5:QOOQO1G1]1G1]OOQO1G1^1G1^O$'cQMhO,5;tOLUQYO,5;sOOQO-E;w-E;wOOQ!LS7+&s7+&sOOOO7+'O7+'OOOOO1G1g1G1gO$'nQWO1G1gOOQ!LS1G1i1G1iO$'sQ`O,5?[OOOS-E;m-E;mOOQ!LS1G/O1G/OO$'zQ!LdO7+'UOOQ!LS,5>i,5>iO$(kQWO,5>iOOQ!LS1G1}1G1}P$(pQWO'#H}POQ!LS-E;{-E;{O$)aQ#tO1G2YO$*SQ#tO1G2[O$*^Q#tO1G2^OOQ!LS1G1v1G1vO$*eQWO'#H|O$*sQWO,5?uO$*sQWO,5?uO$*{QWO,5?uO$+WQWO,5?uOOQO1G1x1G1xO$+fQ#tO1G1wO$+vQWO'#IOO$,WQWO,5?vOIQQWO,5?vO$,`Q`O,5?vOOQ!LS1G1{1G1{O5lQ!LYO,5<fO5lQ!LYO,5<gO$,jQWO,5<gO#5nQWO,5<gO!-dQpO,5<fO$,oQWO,5<hO5lQ!LYO,5<iO$,jQWO,5<lOOQO-E;}-E;}OOQ!LS1G2P1G2PO!'iQ&jO,5<fO$,wQWO,5<gO!'iQ&jO,5<hO!'iQ&jO,5<gO$-SQ#tO1G4sO$-^Q#tO1G4sOOQO,5>l,5>lOOQO-E<O-E<OO!.dQ&jO,59iO){QYO,59iO$-kQWO1G1rOJaQWO1G1yO$-pQ!LdO7+'VOOQ!LS7+'V7+'VOGQQYO7+'VOOQ!LS7+%W7+%WO$.aQ`O'#J]O#IfQWO7+'zO$.kQWO7+'zO$.sQ`O7+'zOOQQ7+'z7+'zOIQQWO7+'zO){QYO7+'zOIQQWO7+'zOOQO1G.v1G.vO$.}Q!LbO'#CgO$/_Q!LbO,5<jO$/|QWO,5<jOOQ!LQ1G4o1G4oOOQQ7+$^7+$^O:aQWO7+$^O!-dQpO7+$^OGQQYO7+%xO$0RQWO'#IXO$0aQWO,5?}OOQO1G2|1G2|O5tQWO,5?}O$0aQWO,5?}O$0iQWO,5?}OOQO,5>t,5>tOOQO-E<W-E<WOOQ!LS7+&m7+&mO$0nQWO7+(gO5lQ!LYO7+(gO5tQWO7+(gO$0sQWO7+(gO$0xQWO7+'zOOQ!LQ,5>n,5>nOOQ!LQ-E<Q-E<QOOQQ7+(U7+(UO$1WQ!LbO7+(ROIQQWO7+(RO$1bQ`O7+(SOOQQ7+(S7+(SOIQQWO7+(SO$1iQWO'#J`O$1tQWO,5=QOOQO,5>p,5>pOOQO-E<S-E<SOOQQ7+(X7+(XO$2nQ&jO'#GoOOQQ1G2t1G2tOIQQWO1G2tO){QYO1G2tOIQQWO1G2tO$2uQWO1G2tO$3TQ#tO1G2tO5lQ!LYO1G2wO#5sQWO1G2wO5bQWO1G2wO!-dQpO1G2wO!-lQ#tO1G2wO$3fQWO'#IWO$3qQWO,5?{O$3yQ&jO,5?{OOQ!LQ1G2x1G2xOOQQ7+$T7+$TO$4OQWO7+$TO5lQ!LYO7+$TO$4TQWO7+$TO){QYO1G5jO){QYO1G5kO$4YQYO1G3PO$4aQWO1G3PO$4fQYO1G3PO$4mQ!LYO1G5jOOQQ7+(i7+(iO5lQ!LYO7+(sO`QYO7+(uOOQQ'#Jf'#JfOOQQ'#IZ'#IZO$4wQYO,5=tOOQQ,5=t,5=tO){QYO'#HZO$5UQWO'#H]OOQQ7+)O7+)OO$5ZQYO7+)OO7YQWO7+)OOOQQ7+)S7+)SOOQQ7+)W7+)WOOQQ7+)Y7+)YOOQO1G5O1G5OO$9XQ7^O1G0cO$9cQWO1G0cOOQO1G/e1G/eO$9nQ7^O1G/eO:aQWO1G/eOLUQYO'#D_OOQO,5>^,5>^OOQO-E;p-E;pOOQO,5>c,5>cOOQO-E;u-E;uO!-dQpO1G/eOOQO1G3|1G3|O:aQWO,5:eOOQO,5:k,5:kO){QYO,5:kO$9xQ!LYO,5:kO$:TQ!LYO,5:kO!-dQpO,5:eOOQO-E;s-E;sOOQ!LS1G0O1G0OO!@YQ&jO,5:eO$:cQ&jO,5:eO$:tQ!LrO,5:kO$;`Q&jO,5:eO!@YQ&jO,5:kOOQO,5:p,5:pO$;gQ&jO,5:kO$;tQ!LYO,5:kOOQ!LS7+%g7+%gO#IfQWO7+%gO#IkQ`O7+%gOOQ!LS7+%w7+%wO:aQWO7+%wO!-dQpO7+%wO$<YQ!LdO7+*pO){QYO7+*pOOQO1G4O1G4OO7YQWO1G4OO$<jQWO7+*oO$<rQ!LdO1G2YO$>tQ!LdO1G2[O$@vQ!LdO1G1wO$COQ#tO,5>_OOQO-E;q-E;qO$CYQbO,5>`O){QYO,5>`OOQO-E;r-E;rO$CdQWO1G5QO$ClQ7^O1G0^O$EsQ7^O1G0iO$EzQ7^O1G0iO$G{Q7^O1G0iO$HSQ7^O1G0iO$IwQ7^O1G0iO$J[Q7^O1G0iO$LiQ7^O1G0iO$LpQ7^O1G0iO$NqQ7^O1G0iO$NxQ7^O1G0iO%!mQ7^O1G0iO%#QQ!LdO<<JPO%#qQ7^O1G0iO%%aQ7^O'#InO%'^Q7^O1G0|OLUQYO'#F[OOQO'#JX'#JXOOQO1G1`1G1`O%'kQWO1G1_O%'pQ7^O,5>gOOOO7+'R7+'ROOOS1G4v1G4vOOQ!LS1G4T1G4TOJaQWO7+'xO%'zQWO,5>hO5tQWO,5>hOOQO-E;z-E;zO%(YQWO1G5aO%(YQWO1G5aO%(bQWO1G5aO%(mQ`O,5>jO%(wQWO,5>jOIQQWO,5>jOOQO-E;|-E;|O%(|Q`O1G5bO%)WQWO1G5bOOQO1G2Q1G2QOOQO1G2R1G2RO5lQ!LYO1G2RO$,jQWO1G2RO5lQ!LYO1G2QO%)`QWO1G2SOIQQWO1G2SOOQO1G2T1G2TO5lQ!LYO1G2WO!-dQpO1G2QO#5nQWO1G2RO%)eQWO1G2SO%)mQWO1G2ROJaQWO7+*_OOQ!LS1G/T1G/TO%)xQWO1G/TOOQ!LS7+'^7+'^O%)}Q#tO7+'eO%*_Q!LdO<<JqOOQ!LS<<Jq<<JqOIQQWO'#IRO%+OQWO,5?wOOQQ<<Kf<<KfOIQQWO<<KfO#IfQWO<<KfO%+WQWO<<KfO%+`Q`O<<KfOIQQWO1G2UOOQQ<<Gx<<GxO:aQWO<<GxO%+jQ!LdO<<IdOOQ!LS<<Id<<IdOOQO,5>s,5>sO%,ZQWO,5>sO#<oQWO,5>sOOQO-E<V-E<VO%,`QWO1G5iO%,`QWO1G5iO5tQWO1G5iO%,hQWO<<LROOQQ<<LR<<LRO%,mQWO<<LRO5lQ!LYO<<LRO){QYO<<KfOIQQWO<<KfOOQQ<<Km<<KmO$1WQ!LbO<<KmOOQQ<<Kn<<KnO$1bQ`O<<KnO%,rQ&jO'#ITO%,}QWO,5?zOLUQYO,5?zOOQQ1G2l1G2lO#GfQ!LrO'#EPO!@YQ&jO'#GpOOQO'#IV'#IVO%-VQ&jO,5=ZOOQQ,5=Z,5=ZO%-^Q&jO'#EPO%-iQ&jO'#EPO%.QQ&jO'#EPO%.[Q&jO'#GpO%.mQWO7+(`O%.rQWO7+(`O%.zQ`O7+(`OOQQ7+(`7+(`OIQQWO7+(`O){QYO7+(`OIQQWO7+(`O%/UQWO7+(`OOQQ7+(c7+(cO5lQ!LYO7+(cO#5sQWO7+(cO5bQWO7+(cO!-dQpO7+(cO%/dQWO,5>rOOQO-E<U-E<UOOQO'#Gs'#GsO%/oQWO1G5gO5lQ!LYO<<GoOOQQ<<Go<<GoO%/wQWO<<GoO%/|QWO7++UO%0RQWO7++VOOQQ7+(k7+(kO%0WQWO7+(kO%0]QYO7+(kO%0dQWO7+(kO){QYO7++UO){QYO7++VOOQQ<<L_<<L_OOQQ<<La<<LaOOQQ-E<X-E<XOOQQ1G3`1G3`O%0iQWO,5=uOOQQ,5=w,5=wO:aQWO<<LjO%0nQWO<<LjOLUQYO7+%}OOQO7+%P7+%PO%0sQ7^O1G5XO:aQWO7+%POOQO1G0P1G0PO%0}Q!LdO1G0VOOQO1G0V1G0VO){QYO1G0VO%1XQ!LYO1G0VO:aQWO1G0PO!-dQpO1G0PO!@YQ&jO1G0PO%1dQ!LYO1G0VO%1rQ&jO1G0PO%2TQ!LYO1G0VO%2iQ!LrO1G0VO%2sQ&jO1G0PO!@YQ&jO1G0VOOQ!LS<<IR<<IROOQ!LS<<Ic<<IcO:aQWO<<IcO%2zQ!LdO<<N[OOQO7+)j7+)jO%3[Q!LdO7+'eO%5dQbO1G3zO%5nQ7^O7+%xO%5{Q7^O,59gO%7xQ7^O,5<SO%9uQ7^O,5<UO%;rQ7^O,5<dO%=bQ7^O7+'UO%=oQ7^O7+'VO%=|QWO,5;vOOQO7+&y7+&yO%>RQ#tO<<KdOOQO1G4S1G4SO%>cQWO1G4SO%>nQWO1G4SO%>|QWO7+*{O%>|QWO7+*{OIQQWO1G4UO%?UQ`O1G4UO%?`QWO7+*|OOQO7+'m7+'mO5lQ!LYO7+'mOOQO7+'l7+'lO$,jQWO7+'nO%?hQ`O7+'nOOQO7+'r7+'rO5lQ!LYO7+'lO$,jQWO7+'mO%?oQWO7+'nOIQQWO7+'nO#5nQWO7+'mO%?tQ#tO<<MyOOQ!LS7+$o7+$oO%@OQ`O,5>mOOQO-E<P-E<PO#IfQWOANAQOOQQANAQANAQOIQQWOANAQO%@YQ!LbO7+'pOOQQAN=dAN=dO5tQWO1G4_OOQO1G4_1G4_O%@gQWO1G4_O%@lQWO7++TO%@lQWO7++TO5lQ!LYOANAmO%@tQWOANAmOOQQANAmANAmO%@yQWOANAQO%ARQ`OANAQOOQQANAXANAXOOQQANAYANAYO%A]QWO,5>oOOQO-E<R-E<RO%AhQ7^O1G5fO#5sQWO,5=[O5bQWO,5=[O!-dQpO,5=[OOQO-E<T-E<TOOQQ1G2u1G2uO$:tQ!LrO,5:kO!@YQ&jO,5=[O%ArQ&jO,5=[O%BTQ&jO,5:kOOQQ<<Kz<<KzOIQQWO<<KzO%.mQWO<<KzO%B_QWO<<KzO%BgQ`O<<KzO){QYO<<KzOIQQWO<<KzOOQQ<<K}<<K}O5lQ!LYO<<K}O#5sQWO<<K}O5bQWO<<K}O%BqQ&jO1G4^O%BvQWO7++ROOQQAN=ZAN=ZO5lQ!LYOAN=ZOOQQ<<Np<<NpOOQQ<<Nq<<NqOOQQ<<LV<<LVO%COQWO<<LVO%CTQYO<<LVO%C[QWO<<NpO%CaQWO<<NqOOQQ1G3a1G3aOOQQANBUANBUO:aQWOANBUO%CfQ7^O<<IiOOQO<<Hk<<HkOOQO7+%q7+%qO%0}Q!LdO7+%qO){QYO7+%qOOQO7+%k7+%kO:aQWO7+%kO!-dQpO7+%kO%CpQ!LYO7+%qO!@YQ&jO7+%kO%C{Q!LYO7+%qO%DZQ&jO7+%kO%DlQ!LYO7+%qOOQ!LSAN>}AN>}O%EQQ!LdO<<KdO%GYQ7^O<<JPO%GgQ7^O1G1wO%IVQ7^O1G2YO%KSQ7^O1G2[O%MPQ7^O<<JqO%M^Q7^O<<IdOOQO1G1b1G1bOOQO7+)n7+)nO%MkQWO7+)nO%MvQWO<<NgO%NOQ`O7+)pOOQO<<KX<<KXO5lQ!LYO<<KYO$,jQWO<<KYOOQO<<KW<<KWO5lQ!LYO<<KXO%NYQ`O<<KYO$,jQWO<<KXOOQQG26lG26lO#IfQWOG26lOOQO7+)y7+)yO5tQWO7+)yO%NaQWO<<NoOOQQG27XG27XO5lQ!LYOG27XOIQQWOG26lOLUQYO1G4ZO%NiQWO7++QO5lQ!LYO1G2vO#5sQWO1G2vO5bQWO1G2vO!-dQpO1G2vO!@YQ&jO1G2vO%2iQ!LrO1G0VO%NqQ&jO1G2vO%.mQWOANAfOOQQANAfANAfOIQQWOANAfO& SQWOANAfO& [Q`OANAfOOQQANAiANAiO5lQ!LYOANAiO#5sQWOANAiOOQO'#Gt'#GtOOQO7+)x7+)xOOQQG22uG22uOOQQANAqANAqO& fQWOANAqOOQQAND[AND[OOQQAND]AND]O& kQYOG27pOOQO<<I]<<I]O%0}Q!LdO<<I]OOQO<<IV<<IVO:aQWO<<IVO){QYO<<I]O!-dQpO<<IVO&%iQ!LYO<<I]O!@YQ&jO<<IVO&%tQ!LYO<<I]O&&SQ7^O7+'eOOQO<<MY<<MYOOQOAN@tAN@tO5lQ!LYOAN@tOOQOAN@sAN@sO$,jQWOAN@tO5lQ!LYOAN@sOOQQLD,WLD,WOOQO<<Me<<MeOOQQLD,sLD,sO#IfQWOLD,WO&'rQ7^O7+)uOOQO7+(b7+(bO5lQ!LYO7+(bO#5sQWO7+(bO5bQWO7+(bO!-dQpO7+(bO!@YQ&jO7+(bOOQQG27QG27QO%.mQWOG27QOIQQWOG27QOOQQG27TG27TO5lQ!LYOG27TOOQQG27]G27]O:aQWOLD-[OOQOAN>wAN>wOOQOAN>qAN>qO%0}Q!LdOAN>wO:aQWOAN>qO){QYOAN>wO!-dQpOAN>qO&'|Q!LYOAN>wO&(XQ7^O<<KdOOQOG26`G26`O5lQ!LYOG26`OOQOG26_G26_OOQQ!$( r!$( rOOQO<<K|<<K|O5lQ!LYO<<K|O#5sQWO<<K|O5bQWO<<K|O!-dQpO<<K|OOQQLD,lLD,lO%.mQWOLD,lOOQQLD,oLD,oOOQQ!$(!v!$(!vOOQOG24cG24cOOQOG24]G24]O%0}Q!LdOG24cO:aQWOG24]O){QYOG24cOOQOLD+zLD+zOOQOANAhANAhO5lQ!LYOANAhO#5sQWOANAhO5bQWOANAhOOQQ!$(!W!$(!WOOQOLD)}LD)}OOQOLD)wLD)wO%0}Q!LdOLD)}OOQOG27SG27SO5lQ!LYOG27SO#5sQWOG27SOOQO!$'Mi!$'MiOOQOLD,nLD,nO5lQ!LYOLD,nOOQO!$(!Y!$(!YOLUQYO'#DnO&)wQbO'#IsOLUQYO'#DfO&*OQ!LdO'#CgO&*iQbO'#CgO&*yQYO,5:rOLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO,5:}OLUQYO'#H{O&,yQWO,5<RO&.]QWO,5:}OLUQYO,5;bO!)RQWO'#C{O!)RQWO'#C{OIQQWO'#FhO&-RQWO'#FhOIQQWO'#FjO&-RQWO'#FjOIQQWO'#FxO&-RQWO'#FxOLUQYO,5?mO&*yQYO1G0^O&.dQ7^O'#CgOLUQYO1G1jOIQQWO,5<nO&-RQWO,5<nOIQQWO,5<pO&-RQWO,5<pOIQQWO,5<]O&-RQWO,5<]O&*yQYO1G1kOLUQYO7+&eOIQQWO1G1yO&-RQWO1G1yO&*yQYO7+'VO&*yQYO7+%xOIQQWO7+'xO&-RQWO7+'xO&.nQWO'#EWO&.sQWO'#EWO&.{QWO'#EvO&/QQWO'#EcO&/VQWO'#JRO&/bQWO'#JPO&/mQWO,5:rO&/rQ#tO,5<OO&/yQWO'#FqO&0OQWO'#FqO&0TQWO,5<PO&0]QWO,5:rO&0eQ7^O1G0yO&0lQWO,5<_O&0qQWO,5<_O&0vQWO1G1kO&0{QWO1G0^O&1QQ#tO1G2^O&1XQ#tO1G2^O4TQWO'#FfO5bQWO'#FeOBtQWO'#EVOLUQYO,5;_O!)bQWO'#FsO!)bQWO'#FsOJaQWO,5<rOJaQWO,5<r", ++ stateData: "&2W~O'VOS'WOSSOSTOS~OPTOQTOWyO]cO^hOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#`sO#ppO#t^O$}qO%PtO%RrO%SrO%VuO%XvO%[wO%]wO%_xO%lzO%r{O%t|O%v}O%x!OO%{!PO&R!QO&V!RO&X!SO&Z!TO&]!UO&_!VO'YPO'cQO'oYO'|aO~OPZXYZX^ZXiZXrZXsZXuZX}ZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'TZX'cZX'pZX'wZX'xZX~O!X$jX~P$zO'Q!XO'R!WO'S!ZO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y![O'cQO'oYO'|aO~O|!`O}!]Oz'jPz'tP~P'dO!O!lO~P`OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y9XO'cQO'oYO'|aO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!P!bO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'cQO'oYO'|aO~O|!qO#Q!tO#R!qO'Y9YO!_'qP~P+{O#S!uO~O!X!vO#S!uO~OP#]OY#cOi#QOr!zOs!zOu!{O}#aO!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~O^'gX'T'gX!_'gXz'gX!P'gX%O'gX!X'gX~P.jO!w#dO#k#dOP'hXY'hX^'hXi'hXr'hXs'hXu'hX}'hX!]'hX!^'hX!`'hX!f'hX#W'hX#X'hX#Y'hX#Z'hX#['hX#]'hX#^'hX#a'hX#c'hX#e'hX#f'hX'c'hX'p'hX'w'hX'x'hX~O#_'hX'T'hXz'hX!_'hX'e'hX!P'hX%O'hX!X'hX~P0zO!w#dO~O#v#fO#x#eO$P#kO~O!P#lO#t^O$S#mO$U#oO~O]#rOg$POi#sOj#rOk#rOn$QOp$ROu#yO!P#zO!Z$WO!`#wO#R$XO#p$UO$]$SO$_$TO$b$VO'Y#qO'c#tO'^'`P~O!`$YO~O!X$[O~O^$]O'T$]O~O'Y$aO~O!`$YO'Y$aO'Z$cO'_$dO~Ob$jO!`$YO'Y$aO~O#_#SO~O]$sOr$oO!P$lO!`$nO%P$rO'Y$aO'Z$cO[(UP~O!j$tO~Ou$uO!P$vO'Y$aO~Ou$uO!P$vO%X$zO'Y$aO~O'Y${O~O#`sO%PtO%RrO%SrO%VuO%XvO%[wO%]wO~Oa%UOb%TO!j%RO$}%SO%a%QO~P7xOa%XObmO!P%WO!jlO#`sO$}qO%RrO%SrO%VuO%XvO%[wO%]wO%_xO~O_%[O!w%_O%P%YO'Z$cO~P8wO!`%`O!c%dO~O!`%eO~O!PSO~O^$]O'P%mO'T$]O~O^$]O'P%pO'T$]O~O^$]O'P%rO'T$]O~O'Q!XO'R!WO'S%vO~OPZXYZXiZXrZXsZXuZX}ZX}cX!]ZX!^ZX!`ZX!fZX!wZX!wcX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'cZX'pZX'wZX'xZX~OzZXzcX~P;dO|%xOz&eX}&eX~P){O}!]Oz'jX~OP#]OY#cOi#QOr!zOs!zOu!{O}!]O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~Oz'jX~P>ZOz%}O~Ou&QO!S&[O!T&TO!U&TO'Z$cO~O]&ROj&RO|&UO'f&OO!O'kP!O'vP~P@^Oz'sX}'sX!X'sX!_'sX'p'sX~O!w'sX#S!{X!O'sX~PAVO!w&]Oz'uX}'uX~O}&^Oz'tX~Oz&`O~O!w#dO~PAVOR&dO!P&aO!k&cO'Y$aO~Ob&iO!`$YO'Y$aO~Or$oO!`$nO~O!O&jO~P`Or!zOs!zOu!{O!^!xO!`!yO'cQOP!baY!bai!ba}!ba!]!ba!f!ba#W!ba#X!ba#Y!ba#Z!ba#[!ba#]!ba#^!ba#_!ba#a!ba#c!ba#e!ba#f!ba'p!ba'w!ba'x!ba~O^!ba'T!baz!ba!_!ba'e!ba!P!ba%O!ba!X!ba~PC`O!_&kO~O!X!vO!w&mO'p&lO}'rX^'rX'T'rX~O!_'rX~PExO}&qO!_'qX~O!_&sO~Ou$uO!P$vO#R&tO'Y$aO~OPTOQTO]cOa!jOb!iOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!PSO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!j!hO#p!kO#t^O'Y9XO'cQO'oYO'|aO~O]#rOg$POi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'Y&xO'c#tO~O#S&zO~O]#rOg$POi#sOj#rOk#rOn$QOp$ROu#yO!P#zO!Z$WO!`#wO#R$XO#p$UO$]$SO$_$TO$b$VO'Y&xO'c#tO~O'^'mP~PJaO|'OO!_'nP~P){O'f'QO'oYO~OP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!P!bO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'Y'`O'cQO'oYO'|:jO~O!`!yO~O}#aO^$Za'T$Za!_$Zaz$Za!P$Za%O$Za!X$Za~O#`'gO~PIQOr'jO!X'iO!P#wX#s#wX#v#wX#x#wX$P#wX~O!X'iO!P'yX#s'yX#v'yX#x'yX$P'yX~Or'jO~P! eOr'jO!P'yX#s'yX#v'yX#x'yX$P'yX~O!P'lO#s'pO#v'kO#x'kO$P'qO~O|'tO~PLUO#v#fO#x#eO$P'wO~Or$cXu$cX!^$cX'p$cX'w$cX'x$cX~OReX}eX!weX'^eX'^$cX~P!#ZOj'yO~O'Q'{O'R'zO'S'}O~Or(POu(QO'p#ZO'w(SO'x(UO~O'^(OO~P!$dO'^(XO~O]#rOg$POi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'c#tO~O|(]O'Y(YO!_'}P~P!%RO#S(_O~O|(cO'Y(`Oz(OP~P!%RO^(lOi(qOu(iO!S(oO!T(hO!U(hO!`(fO!t(pO$u(kO'Z$cO'f(eO~O!O(nO~P!&yO!^!xOr'bXu'bX'p'bX'w'bX'x'bX}'bX!w'bX~O'^'bX#i'bX~P!'uOR(tO!w(sO}'aX'^'aX~O}(uO'^'`X~O'Y(wO~O!`(|O~O'Y&xO~O!`(fO~Ou$uO|!qO!P$vO#Q!tO#R!qO'Y$aO!_'qP~O!X!vO#S)QO~OP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO#f#YO'cQO'p#ZO'w!|O'x!}O~O^!Ya}!Ya'T!Yaz!Ya!_!Ya'e!Ya!P!Ya%O!Ya!X!Ya~P!*WOR)YO!P&aO!k)XO%O)WO'_$dO~O'Y${O'^'`P~O!X)]O!P']X^']X'T']X~O!`$YO'_$dO~O!`$YO'Y$aO'_$dO~O!X!vO#S&zO~O%P)iO'Y)eO!O(VP~O})jO[(UX~O'f'QO~OY)nO~O[)oO~O!P$lO'Y$aO'Z$cO[(UP~Ou$uO|)tO!P$vO'Y$aOz'tP~O]&XOj&XO|)uO'f'QO!O'vP~O})vO^(RX'T(RX~O!w)zO'_$dO~OR)}O!P#zO'_$dO~O!P*PO~Or*RO!PSO~O!j*WO~Ob*]O~O'Y(wO!O(TP~Ob$jO~O%PtO'Y${O~P8wOY*cO[*bO~OPTOQTO]cOanObmOgcOiTOjcOkcOnTOpTOuROwcOxcOycO!ZkO!`UO!cTO!dTO!eTO!fTO!gTO!jlO#t^O$}qO'cQO'oYO'|aO~O!P!bO#p!kO'Y9XO~P!1mO[*bO^$]O'T$]O~O^*gO#`*iO%R*iO%S*iO~P){O!`%`O~O%r*nO~O!P*pO~O&S*sO&T*rOP&QaQ&QaW&Qa]&Qa^&Qaa&Qab&Qag&Qai&Qaj&Qak&Qan&Qap&Qau&Qaw&Qax&Qay&Qa!P&Qa!Z&Qa!`&Qa!c&Qa!d&Qa!e&Qa!f&Qa!g&Qa!j&Qa#`&Qa#p&Qa#t&Qa$}&Qa%P&Qa%R&Qa%S&Qa%V&Qa%X&Qa%[&Qa%]&Qa%_&Qa%l&Qa%r&Qa%t&Qa%v&Qa%x&Qa%{&Qa&R&Qa&V&Qa&X&Qa&Z&Qa&]&Qa&_&Qa'O&Qa'Y&Qa'c&Qa'o&Qa'|&Qa!O&Qa%y&Qa_&Qa&O&Qa~O'Y*vO~O'e*yO~Oz&ea}&ea~P!*WO}!]Oz'ja~Oz'ja~P>ZO}&^Oz'ta~O}tX}!VX!OtX!O!VX!XtX!X!VX!`!VX!wtX'_!VX~O!X+QO!w+PO}#PX}'lX!O#PX!O'lX!X'lX!`'lX'_'lX~O!X+SO!`$YO'_$dO}!RX!O!RX~O]&POj&POu&QO'f(eO~OP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!P!bO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'cQO'oYO'|:jO~O'Y9uO~P!;kO}+WO!O'kX~O!O+YO~O!X+QO!w+PO}#PX!O#PX~O}+ZO!O'vX~O!O+]O~O]&POj&POu&QO'Z$cO'f(eO~O!T+^O!U+^O~P!>iOu$uO|+aO!P$vO'Y$aOz&jX}&jX~O^+fO!S+iO!T+eO!U+eO!n+mO!o+kO!p+lO!q+jO!t+nO'Z$cO'f(eO'o+cO~O!O+hO~P!?jOR+sO!P&aO!k+rO~O!w+yO}'ra!_'ra^'ra'T'ra~O!X!vO~P!@tO}&qO!_'qa~Ou$uO|+|O!P$vO#Q,OO#R+|O'Y$aO}&lX!_&lX~O^!zi}!zi'T!ziz!zi!_!zi'e!zi!P!zi%O!zi!X!zi~P!*WO#S!va}!va!_!va!w!va!P!va^!va'T!vaz!va~P!$dO#S'bXP'bXY'bX^'bXi'bXs'bX!]'bX!`'bX!f'bX#W'bX#X'bX#Y'bX#Z'bX#['bX#]'bX#^'bX#_'bX#a'bX#c'bX#e'bX#f'bX'T'bX'c'bX!_'bXz'bX!P'bX'e'bX%O'bX!X'bX~P!'uO},XO'^'mX~P!$dO'^,ZO~O},[O!_'nX~P!*WO!_,_O~Oz,`O~OP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'cQOY#Vi^#Vii#Vi}#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O#W#Vi~P!FRO#W#OO~P!FROP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO'cQOY#Vi^#Vi}#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~Oi#Vi~P!HmOi#QO~P!HmOP#]Oi#QOr!zOs!zOu!{O!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO'cQO^#Vi}#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'w#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P!KXOY#cO!]#SO#]#SO#^#SO#_#SO~P!KXOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO'cQO^#Vi}#Vi#c#Vi#e#Vi#f#Vi'T#Vi'p#Vi'x#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O'w#Vi~P!NPO'w!|O~P!NPOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO'cQO'w!|O^#Vi}#Vi#e#Vi#f#Vi'T#Vi'p#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~O'x#Vi~P#!kO'x!}O~P#!kOP#]OY#cOi#QOr!zOs!zOu!{O!]#SO!^!xO!`!yO!f#]O#W#OO#X#PO#Y#PO#Z#PO#[#RO#]#SO#^#SO#_#SO#a#TO#c#VO#e#XO'cQO'w!|O'x!}O~O^#Vi}#Vi#f#Vi'T#Vi'p#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~P#%VOPZXYZXiZXrZXsZXuZX!]ZX!^ZX!`ZX!fZX!wZX#ScX#WZX#XZX#YZX#ZZX#[ZX#]ZX#^ZX#_ZX#aZX#cZX#eZX#fZX#kZX'cZX'pZX'wZX'xZX}ZX!OZX~O#iZX~P#'jOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO#e9eO#f9fO'cQO'p#ZO'w!|O'x!}O~O#i,bO~P#)tOP'hXY'hXi'hXr'hXs'hXu'hX!]'hX!^'hX!`'hX!f'hX#W'hX#X'hX#Y'hX#Z'hX#['hX#]'hX#^'hX#a'hX#c'hX#e'hX#f'hX'c'hX'p'hX'w'hX'x'hX}'hX~O!w9jO#k9jO#_'hX#i'hX!O'hX~P#+oO^&oa}&oa'T&oa!_&oa'e&oaz&oa!P&oa%O&oa!X&oa~P!*WOP#ViY#Vi^#Vii#Vis#Vi}#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi'T#Vi'c#Viz#Vi!_#Vi'e#Vi!P#Vi%O#Vi!X#Vi~P!$dO^#ji}#ji'T#jiz#ji!_#ji'e#ji!P#ji%O#ji!X#ji~P!*WO#v,dO#x,dO~O#v,eO#x,eO~O!X'iO!w,fO!P#|X#s#|X#v#|X#x#|X$P#|X~O|,gO~O!P'lO#s,iO#v'kO#x'kO$P,jO~O}9gO!O'gX~P#)tO!O,kO~O$P,mO~O'Q'{O'R'zO'S,pO~O],sOj,sOz,tO~O}cX!XcX!_cX!_$cX'pcX~P!#ZO!_,zO~P!$dO},{O!X!vO'p&lO!_'}X~O!_-QO~Oz$cX}$cX!X$jX~P!#ZO}-SOz(OX~P!$dO!X-UO~Oz-WO~O|(]O'Y$aO!_'}P~Oi-[O!X!vO!`$YO'_$dO'p&lO~O!X)]O~O!O-bO~P!&yO!T-cO!U-cO'Z$cO'f(eO~Ou-eO'f(eO~O!t-fO~O'Y${O}&tX'^&tX~O}(uO'^'`a~Or-kOs-kOu-lO'poa'woa'xoa}oa!woa~O'^oa#ioa~P#7POr(POu(QO'p$[a'w$[a'x$[a}$[a!w$[a~O'^$[a#i$[a~P#7uOr(POu(QO'p$^a'w$^a'x$^a}$^a!w$^a~O'^$^a#i$^a~P#8hO]-mO~O#S-nO~O'^$la}$la#i$la!w$la~P!$dO#S-qO~OR-zO!P&aO!k-yO%O-xO~O'^-{O~O]#rOi#sOj#rOk#rOn$QOp9kOu#yO!P#zO!Z:nO!`#wO#R9qO#p$UO$]9mO$_9oO$b$VO'c#tO~Og-}O'Y-|O~P#:_O!X)]O!P']a^']a'T']a~O#S.TO~OYZX}cX!OcX~O}.UO!O(VX~O!O.WO~OY.XO~O'Y)eO~O!P$lO'Y$aO[&|X}&|X~O})jO[(Ua~O!_.^O~P!*WO].`O~OY.aO~O[.bO~OR-zO!P&aO!k-yO%O-xO'_$dO~O})vO^(Ra'T(Ra~O!w.hO~OR.kO!P#zO~O'f'QO!O(SP~OR.uO!P.qO!k.tO%O.sO'_$dO~OY/PO}.}O!O(TX~O!O/QO~O[/SO^$]O'T$]O~O]/TO~O#_/VO%p/WO~P0zO!w#dO#_/VO%p/WO~O^/XO~P){O^/ZO~O%y/_OP%wiQ%wiW%wi]%wi^%wia%wib%wig%wii%wij%wik%win%wip%wiu%wiw%wix%wiy%wi!P%wi!Z%wi!`%wi!c%wi!d%wi!e%wi!f%wi!g%wi!j%wi#`%wi#p%wi#t%wi$}%wi%P%wi%R%wi%S%wi%V%wi%X%wi%[%wi%]%wi%_%wi%l%wi%r%wi%t%wi%v%wi%x%wi%{%wi&R%wi&V%wi&X%wi&Z%wi&]%wi&_%wi'O%wi'Y%wi'c%wi'o%wi'|%wi!O%wi_%wi&O%wi~O_/eO!O/cO&O/dO~P`O!PSO!`/hO~O}#aO'e$Za~Oz&ei}&ei~P!*WO}!]Oz'ji~O}&^Oz'ti~Oz/lO~O}!Ra!O!Ra~P#)tO]&POj&PO|/rO'f(eO}&fX!O&fX~P@^O}+WO!O'ka~O]&XOj&XO|)uO'f'QO}&kX!O&kX~O}+ZO!O'va~Oz'ui}'ui~P!*WO^$]O!X!vO!`$YO!f/}O!w/{O'T$]O'_$dO'p&lO~O!O0QO~P!?jO!T0RO!U0RO'Z$cO'f(eO'o+cO~O!S0SO~P#HXO!PSO!S0SO!q0UO!t0VO~P#HXO!S0SO!o0XO!p0XO!q0UO!t0VO~P#HXO!P&aO~O!P&aO~P!$dO}'ri!_'ri^'ri'T'ri~P!*WO!w0bO}'ri!_'ri^'ri'T'ri~O}&qO!_'qi~Ou$uO!P$vO#R0dO'Y$aO~O#SoaPoaYoa^oaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa'Toa'coa!_oazoa!Poa'eoa%Ooa!Xoa~P#7PO#S$[aP$[aY$[a^$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a'T$[a'c$[a!_$[az$[a!P$[a'e$[a%O$[a!X$[a~P#7uO#S$^aP$^aY$^a^$^ai$^as$^a!]$^a!^$^a!`$^a!f$^a#W$^a#X$^a#Y$^a#Z$^a#[$^a#]$^a#^$^a#_$^a#a$^a#c$^a#e$^a#f$^a'T$^a'c$^a!_$^az$^a!P$^a'e$^a%O$^a!X$^a~P#8hO#S$laP$laY$la^$lai$las$la}$la!]$la!^$la!`$la!f$la#W$la#X$la#Y$la#Z$la#[$la#]$la#^$la#_$la#a$la#c$la#e$la#f$la'T$la'c$la!_$laz$la!P$la!w$la'e$la%O$la!X$la~P!$dO^!zq}!zq'T!zqz!zq!_!zq'e!zq!P!zq%O!zq!X!zq~P!*WO}&gX'^&gX~PJaO},XO'^'ma~O|0lO}&hX!_&hX~P){O},[O!_'na~O},[O!_'na~P!*WO#i!ba!O!ba~PC`O#i!Ya}!Ya!O!Ya~P#)tO!P1PO#t^O#}1QO~O!O1UO~O'e1VO~P!$dO^$Wq}$Wq'T$Wqz$Wq!_$Wq'e$Wq!P$Wq%O$Wq!X$Wq~P!*WOz1WO~O],sOj,sO~Or(POu(QO'x(UO'p$vi'w$vi}$vi!w$vi~O'^$vi#i$vi~P$(xOr(POu(QO'p$xi'w$xi'x$xi}$xi!w$xi~O'^$xi#i$xi~P$)kO#i1XO~P!$dO|1ZO'Y$aO}&pX!_&pX~O},{O!_'}a~O},{O!X!vO!_'}a~O},{O!X!vO'p&lO!_'}a~O'^$ei}$ei#i$ei!w$ei~P!$dO|1bO'Y(`Oz&rX}&rX~P!%RO}-SOz(Oa~O}-SOz(Oa~P!$dO!X!vO~O!X!vO#_1lO~Oi1pO!X!vO'p&lO~O}'ai'^'ai~P!$dO!w1sO}'ai'^'ai~P!$dO!_1vO~O^$Xq}$Xq'T$Xqz$Xq!_$Xq'e$Xq!P$Xq%O$Xq!X$Xq~P!*WO}1zO!P(PX~P!$dO!P&aO%O1}O~O!P&aO%O1}O~P!$dO!P$cX$sZX^$cX'T$cX~P!#ZO$s2ROrfXufX!PfX'pfX'wfX'xfX^fX'TfX~O$s2RO~O%P2YO'Y)eO}&{X!O&{X~O}.UO!O(Va~OY2^O~O[2_O~O]2bO~OR2dO!P&aO!k2cO%O1}O~O^$]O'T$]O~P!$dO!P#zO~P!$dO}2iO!w2kO!O(SX~O!O2lO~Ou(iO!S2uO!T2nO!U2nO!n2tO!o2sO!p2sO!t2rO'Z$cO'f(eO'o+cO~O!O2qO~P$1yOR2|O!P.qO!k2{O%O2zO~OR2|O!P.qO!k2{O%O2zO'_$dO~O'Y(wO}&zX!O&zX~O}.}O!O(Ta~O'f3VO~O]3XO~O[3ZO~O!_3^O~P){O^3`O~O^3`O~P){O#_3bO%p3cO~PExO_/eO!O3gO&O/dO~P`O!X3iO~O&T3jOP&QqQ&QqW&Qq]&Qq^&Qqa&Qqb&Qqg&Qqi&Qqj&Qqk&Qqn&Qqp&Qqu&Qqw&Qqx&Qqy&Qq!P&Qq!Z&Qq!`&Qq!c&Qq!d&Qq!e&Qq!f&Qq!g&Qq!j&Qq#`&Qq#p&Qq#t&Qq$}&Qq%P&Qq%R&Qq%S&Qq%V&Qq%X&Qq%[&Qq%]&Qq%_&Qq%l&Qq%r&Qq%t&Qq%v&Qq%x&Qq%{&Qq&R&Qq&V&Qq&X&Qq&Z&Qq&]&Qq&_&Qq'O&Qq'Y&Qq'c&Qq'o&Qq'|&Qq!O&Qq%y&Qq_&Qq&O&Qq~O}#Pi!O#Pi~P#)tO!w3lO}#Pi!O#Pi~O}!Ri!O!Ri~P#)tO^$]O!w3sO'T$]O~O^$]O!X!vO!w3sO'T$]O~O!T3wO!U3wO'Z$cO'f(eO'o+cO~O^$]O!X!vO!`$YO!f3xO!w3sO'T$]O'_$dO'p&lO~O!S3yO~P$:cO!S3yO!q3|O!t3}O~P$:cO^$]O!X!vO!f3xO!w3sO'T$]O'p&lO~O}'rq!_'rq^'rq'T'rq~P!*WO}&qO!_'qq~O#S$viP$viY$vi^$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi'T$vi'c$vi!_$viz$vi!P$vi'e$vi%O$vi!X$vi~P$(xO#S$xiP$xiY$xi^$xii$xis$xi!]$xi!^$xi!`$xi!f$xi#W$xi#X$xi#Y$xi#Z$xi#[$xi#]$xi#^$xi#_$xi#a$xi#c$xi#e$xi#f$xi'T$xi'c$xi!_$xiz$xi!P$xi'e$xi%O$xi!X$xi~P$)kO#S$eiP$eiY$ei^$eii$eis$ei}$ei!]$ei!^$ei!`$ei!f$ei#W$ei#X$ei#Y$ei#Z$ei#[$ei#]$ei#^$ei#_$ei#a$ei#c$ei#e$ei#f$ei'T$ei'c$ei!_$eiz$ei!P$ei!w$ei'e$ei%O$ei!X$ei~P!$dO}&ga'^&ga~P!$dO}&ha!_&ha~P!*WO},[O!_'ni~O#i!zi}!zi!O!zi~P#)tOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O'cQOY#Vii#Vi!]#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~O#W#Vi~P$CyO#W9[O~P$CyOP#]Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O'cQOY#Vi!]#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~Oi#Vi~P$FROi9^O~P$FROP#]Oi9^Or!zOs!zOu!{O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O'cQO#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'w#Vi'x#Vi}#Vi!O#Vi~OY#Vi!]#Vi#]#Vi#^#Vi#_#Vi~P$HZOY9iO!]9`O#]9`O#^9`O#_9`O~P$HZOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO'cQO#c#Vi#e#Vi#f#Vi#i#Vi'p#Vi'x#Vi}#Vi!O#Vi~O'w#Vi~P$JoO'w!|O~P$JoOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO'cQO'w!|O#e#Vi#f#Vi#i#Vi'p#Vi}#Vi!O#Vi~O'x#Vi~P$LwO'x!}O~P$LwOP#]OY9iOi9^Or!zOs!zOu!{O!]9`O!^!xO!`!yO!f#]O#W9[O#X9]O#Y9]O#Z9]O#[9_O#]9`O#^9`O#_9`O#a9aO#c9cO#e9eO'cQO'w!|O'x!}O~O#f#Vi#i#Vi'p#Vi}#Vi!O#Vi~P% PO^#gy}#gy'T#gyz#gy!_#gy'e#gy!P#gy%O#gy!X#gy~P!*WOP#ViY#Vii#Vis#Vi!]#Vi!^#Vi!`#Vi!f#Vi#W#Vi#X#Vi#Y#Vi#Z#Vi#[#Vi#]#Vi#^#Vi#_#Vi#a#Vi#c#Vi#e#Vi#f#Vi#i#Vi'c#Vi}#Vi!O#Vi~P!$dO!^!xOP'bXY'bXi'bXr'bXs'bXu'bX!]'bX!`'bX!f'bX#W'bX#X'bX#Y'bX#Z'bX#['bX#]'bX#^'bX#_'bX#a'bX#c'bX#e'bX#f'bX#i'bX'c'bX'p'bX'w'bX'x'bX}'bX!O'bX~O#i#ji}#ji!O#ji~P#)tO!O4_O~O}&oa!O&oa~P#)tO!X!vO'p&lO}&pa!_&pa~O},{O!_'}i~O},{O!X!vO!_'}i~Oz&ra}&ra~P!$dO!X4fO~O}-SOz(Oi~P!$dO}-SOz(Oi~Oz4lO~O!X!vO#_4rO~Oi4sO!X!vO'p&lO~Oz4uO~O'^$gq}$gq#i$gq!w$gq~P!$dO^$Xy}$Xy'T$Xyz$Xy!_$Xy'e$Xy!P$Xy%O$Xy!X$Xy~P!*WO}1zO!P(Pa~O!P&aO%O4zO~O!P&aO%O4zO~P!$dO^!zy}!zy'T!zyz!zy!_!zy'e!zy!P!zy%O!zy!X!zy~P!*WOY4}O~O}.UO!O(Vi~O]5SO~O[5TO~O'f'QO}&wX!O&wX~O}2iO!O(Sa~O!O5bO~P$1yOu-eO'f(eO'o+cO~O!S5eO!T5dO!U5dO!t0VO'Z$cO'f(eO'o+cO~O!o5fO!p5fO~P%-iO!T5dO!U5dO'Z$cO'f(eO'o+cO~O!P.qO~O!P.qO%O5hO~O!P.qO%O5hO~P!$dOR5mO!P.qO!k5lO%O5hO~OY5rO}&za!O&za~O}.}O!O(Ti~O]5uO~O!_5vO~O!_5wO~O!_5xO~O!_5xO~P){O^5zO~O!X5}O~O!_6PO~O}'ui!O'ui~P#)tO^$]O'T$]O~P!*WO^$]O!w6UO'T$]O~O^$]O!X!vO!w6UO'T$]O~O!T6ZO!U6ZO'Z$cO'f(eO'o+cO~O^$]O!X!vO!f6[O!w6UO'T$]O'p&lO~O!`$YO'_$dO~P%2TO!S6]O~P%1rO}'ry!_'ry^'ry'T'ry~P!*WO#S$gqP$gqY$gq^$gqi$gqs$gq}$gq!]$gq!^$gq!`$gq!f$gq#W$gq#X$gq#Y$gq#Z$gq#[$gq#]$gq#^$gq#_$gq#a$gq#c$gq#e$gq#f$gq'T$gq'c$gq!_$gqz$gq!P$gq!w$gq'e$gq%O$gq!X$gq~P!$dO}&hi!_&hi~P!*WO#i!zq}!zq!O!zq~P#)tOr-kOs-kOu-lOPoaYoaioa!]oa!^oa!`oa!foa#Woa#Xoa#Yoa#Zoa#[oa#]oa#^oa#_oa#aoa#coa#eoa#foa#ioa'coa'poa'woa'xoa}oa!Ooa~Or(POu(QOP$[aY$[ai$[as$[a!]$[a!^$[a!`$[a!f$[a#W$[a#X$[a#Y$[a#Z$[a#[$[a#]$[a#^$[a#_$[a#a$[a#c$[a#e$[a#f$[a#i$[a'c$[a'p$[a'w$[a'x$[a}$[a!O$[a~Or(POu(QOP$^aY$^ai$^as$^a!]$^a!^$^a!`$^a!f$^a#W$^a#X$^a#Y$^a#Z$^a#[$^a#]$^a#^$^a#_$^a#a$^a#c$^a#e$^a#f$^a#i$^a'c$^a'p$^a'w$^a'x$^a}$^a!O$^a~OP$laY$lai$las$la!]$la!^$la!`$la!f$la#W$la#X$la#Y$la#Z$la#[$la#]$la#^$la#_$la#a$la#c$la#e$la#f$la#i$la'c$la}$la!O$la~P!$dO#i$Wq}$Wq!O$Wq~P#)tO#i$Xq}$Xq!O$Xq~P#)tO!O6gO~O'^$zy}$zy#i$zy!w$zy~P!$dO!X!vO}&pi!_&pi~O!X!vO'p&lO}&pi!_&pi~O},{O!_'}q~Oz&ri}&ri~P!$dO}-SOz(Oq~Oz6nO~P!$dOz6nO~O}'ay'^'ay~P!$dO}&ua!P&ua~P!$dO!P$rq^$rq'T$rq~P!$dOY6vO~O}.UO!O(Vq~O]6yO~O!P&aO%O6zO~O!P&aO%O6zO~P!$dO!w6{O}&wa!O&wa~O}2iO!O(Si~P#)tO!T7RO!U7RO'Z$cO'f(eO'o+cO~O!S7TO!t3}O~P%ArO!P.qO%O7WO~O!P.qO%O7WO~P!$dO'f7^O~O}.}O!O(Tq~O!_7aO~O!_7aO~P){O!_7cO~O!_7dO~O}#Py!O#Py~P#)tO^$]O!w7jO'T$]O~O^$]O!X!vO!w7jO'T$]O~O!T7mO!U7mO'Z$cO'f(eO'o+cO~O^$]O!X!vO!f7nO!w7jO'T$]O'p&lO~O#S$zyP$zyY$zy^$zyi$zys$zy}$zy!]$zy!^$zy!`$zy!f$zy#W$zy#X$zy#Y$zy#Z$zy#[$zy#]$zy#^$zy#_$zy#a$zy#c$zy#e$zy#f$zy'T$zy'c$zy!_$zyz$zy!P$zy!w$zy'e$zy%O$zy!X$zy~P!$dO#i#gy}#gy!O#gy~P#)tOP$eiY$eii$eis$ei!]$ei!^$ei!`$ei!f$ei#W$ei#X$ei#Y$ei#Z$ei#[$ei#]$ei#^$ei#_$ei#a$ei#c$ei#e$ei#f$ei#i$ei'c$ei}$ei!O$ei~P!$dOr(POu(QO'x(UOP$viY$vii$vis$vi!]$vi!^$vi!`$vi!f$vi#W$vi#X$vi#Y$vi#Z$vi#[$vi#]$vi#^$vi#_$vi#a$vi#c$vi#e$vi#f$vi#i$vi'c$vi'p$vi'w$vi}$vi!O$vi~Or(POu(QOP$xiY$xii$xis$xi!]$xi!^$xi!`$xi!f$xi#W$xi#X$xi#Y$xi#Z$xi#[$xi#]$xi#^$xi#_$xi#a$xi#c$xi#e$xi#f$xi#i$xi'c$xi'p$xi'w$xi'x$xi}$xi!O$xi~O#i$Xy}$Xy!O$Xy~P#)tO#i!zy}!zy!O!zy~P#)tO!X!vO}&pq!_&pq~O},{O!_'}y~Oz&rq}&rq~P!$dOz7tO~P!$dO}.UO!O(Vy~O}2iO!O(Sq~O!T8QO!U8QO'Z$cO'f(eO'o+cO~O!P.qO%O8TO~O!P.qO%O8TO~P!$dO!_8WO~O&T8XOP&Q!ZQ&Q!ZW&Q!Z]&Q!Z^&Q!Za&Q!Zb&Q!Zg&Q!Zi&Q!Zj&Q!Zk&Q!Zn&Q!Zp&Q!Zu&Q!Zw&Q!Zx&Q!Zy&Q!Z!P&Q!Z!Z&Q!Z!`&Q!Z!c&Q!Z!d&Q!Z!e&Q!Z!f&Q!Z!g&Q!Z!j&Q!Z#`&Q!Z#p&Q!Z#t&Q!Z$}&Q!Z%P&Q!Z%R&Q!Z%S&Q!Z%V&Q!Z%X&Q!Z%[&Q!Z%]&Q!Z%_&Q!Z%l&Q!Z%r&Q!Z%t&Q!Z%v&Q!Z%x&Q!Z%{&Q!Z&R&Q!Z&V&Q!Z&X&Q!Z&Z&Q!Z&]&Q!Z&_&Q!Z'O&Q!Z'Y&Q!Z'c&Q!Z'o&Q!Z'|&Q!Z!O&Q!Z%y&Q!Z_&Q!Z&O&Q!Z~O^$]O!w8^O'T$]O~O^$]O!X!vO!w8^O'T$]O~OP$gqY$gqi$gqs$gq!]$gq!^$gq!`$gq!f$gq#W$gq#X$gq#Y$gq#Z$gq#[$gq#]$gq#^$gq#_$gq#a$gq#c$gq#e$gq#f$gq#i$gq'c$gq}$gq!O$gq~P!$dO}&wq!O&wq~P#)tO^$]O!w8sO'T$]O~OP$zyY$zyi$zys$zy!]$zy!^$zy!`$zy!f$zy#W$zy#X$zy#Y$zy#Z$zy#[$zy#]$zy#^$zy#_$zy#a$zy#c$zy#e$zy#f$zy#i$zy'c$zy}$zy!O$zy~P!$dO'e'gX~P.jO'eZXzZX!_ZX%pZX!PZX%OZX!XZX~P$zO!XcX!_ZX!_cX'pcX~P;dOP9UOQ9UO]cOa:lOb!iOgcOi9UOjcOkcOn9UOp9UOuROwcOxcOycO!PSO!Z9WO!`UO!c9UO!d9UO!e9UO!f9UO!g9UO!j!hO#p!kO#t^O'Y'`O'cQO'oYO'|:jO~O}9gO!O$Za~O]#rOg$POi#sOj#rOk#rOn$QOp9lOu#yO!P#zO!Z:oO!`#wO#R9rO#p$UO$]9nO$_9pO$b$VO'Y&xO'c#tO~O#`'gO~P&-RO!OZX!OcX~P;dO#S9ZO~O!X!vO#S9ZO~O!w9jO~O#_9`O~O!w9sO}'uX!O'uX~O!w9jO}'sX!O'sX~O#S9tO~O'^9vO~P!$dO#S9{O~O#S9|O~O!X!vO#S9}O~O!X!vO#S9tO~O#i:OO~P#)tO#S:PO~O#S:QO~O#S:RO~O#S:SO~O#i:TO~P!$dO#i:UO~P!$dO#t~!^!n!p!q#Q#R'|$]$_$b$s$}%O%P%V%X%[%]%_%a~TS#t'|#Xy'V'W'f'W'Y#v#x#v~", ++ goto: "#Dq(ZPPPPPPP([P(lP*`PPPP-uPP.[3l5`5sP5sPPP5s5sP5sP7aPP7fP7zPPPP<ZPPPP<Z>yPPP?PA[P<ZPCuPPPPEm<ZPPPPPGf<ZPPJeKbPPPPKfMOPMWNXPKb<Z<Z!#`!&X!*x!*x!.VPPP!.^!1Q<ZPPPPPPPPPP!3uP!5WPP<Z!6eP<ZP<Z<Z<Z<ZP<Z!8xPP!;oP!>bP!>f!>n!>r!>rP!;lP!>v!>vP!AiP!Am<Z<Z!As!De5sP5sP5s5sP!Eh5s5s!G`5s!Ib5s!KS5s5s!Kp!Mj!Mj!Mn!Mj!MvP!MjP5s!Nr5s# |5s5s-uPPP##ZPP##s##sP##sP#$Y##sPP#$`P#$VP#$V#$rMS#$V#%a#%g#%j([#%m([P#%t#%t#%tP([P([P([P([PP([P#%z#%}P#%}([PPP([P([P([P([P([P([([#&R#&]#&c#&i#&w#&}#'T#'_#'e#'o#'u#(T#(Z#(a#(o#)U#*h#*v#*|#+S#+Y#+`#+j#+p#+v#,Q#,d#,jPPPPPPPPP#,pPP#-d#1bPP#2x#3P#3XP#7ePP#7i#9|#?v#?z#?}#@Q#@]#@`PP#@c#@g#AU#Ay#A}#BaPP#Be#Bk#BoP#Br#Bv#By#Ci#DP#DU#DX#D[#Db#De#Di#DmmhOSj}!m$[%c%f%g%i*k*p/_/bQ$imQ$ppQ%ZyS&T!b+WQ&h!iS(h#z(mQ)c$jQ)p$rQ*[%TQ+^&[S+e&a+gQ+w&iQ-c(oQ.|*]Y0R+i+j+k+l+mS2n.q2pU3w0S0U0XU5d2s2t2uS6Z3y3|S7R5e5fQ7m6]R8Q7T$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!j'b#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ(x$RQ)h$lQ*^%WQ*e%`Q,R9kQ.O)]Q.Z)iQ/U*cQ2X.UQ3T.}Q4W9lR5P2YpeOSjy}!m$[%Y%c%f%g%i*k*p/_/bR*`%[&WVOSTjkn}!S!W!]!j!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:l:mW!cRU!`&UQ$blQ$hmS$mp$rv$wrs!q!t$Y$u&^&q&t)t)u)v*i+Q+a+|,O/h0dQ%PwQ&e!hQ&g!iS([#w(fS)b$i$jQ)f$lQ)s$tQ*V%RQ*Z%TS+v&h&iQ-P(]Q.S)cQ.Y)iQ.[)jQ._)nQ.w*WS.{*[*]Q0`+wQ1Y,{Q2W.UQ2[.XQ2a.aQ3S.|Q4c1ZQ5O2YQ5R2^Q6u4}R7w6v!Y$fm!i$h$i$j&S&g&h&i(g)b)c+T+d+v+w-].S/w0O0T0`1o3v3{6X7k8_Q)Z$bQ){$|Q*O$}Q*Y%TQ.c)sQ.v*VU.z*Z*[*]Q2}.wS3R.{.|Q5_2mQ5q3SS7P5`5cS8O7Q7SQ8i8PR8x8jW#}a$d(u:jS$|t%YQ$}uQ%OvR)y$z$V#|a!v!x#c#w#y$S$T$X&d'z(T(V(W(_(c(s(t)W)Y)])z)}+s,X-S-U-n-x-z.h.k.s.u1X1b1l1s1z1}2R2d2z2|4f4r4z5h5m6z7W8T9i9m9n9o9p9q9r9w9x9y9z9{9|:P:Q:T:U:j:p:qV(y$R9k9lU&X!b$v+ZQ'R!zQ)m$oQ.l*PQ1t-kR5Z2i&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m$]#`Z!_!n$`%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,c,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:c&ZcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ&V!bR/s+WY&P!b&T&[+W+^S(g#z(mS+d&a+gS-](h(oQ-^(iQ-d(pQ.n*RU0O+e+i+jU0T+k+l+mS0Y+n2rQ1o-cQ1q-eQ1r-fS2m.q2pU3v0R0S0UQ3z0VQ3{0XS5`2n2uS5c2s2tU6X3w3y3|Q6^3}S7Q5d5eQ7S5fS7k6Z6]S8P7R7TQ8_7mR8j8QlhOSj}!m$[%c%f%g%i*k*p/_/bQ%k!QS&u!u9ZQ)`$gQ*T%PQ*U%QQ+t&fS,V&z9tS-p)Q9}Q.Q)aQ.p*SQ/f*rQ/g*sQ/o+RQ0W+kQ0^+uS1y-q:RQ2S.RS2V.T:SQ3m/qQ3p/yQ4P0_Q4|2TQ6O3jQ6R3oQ6V3uQ6_4QQ7e6PQ7h6WQ8Z7iQ8n8XQ8p8]R8{8r$W#_Z!_!n%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:cU(r#{&y0}T)U$`,c$W#^Z!_!n%w%{&v&}'T'U'V'W'X'Y'Z'[']'^'_'a'd'h'r)l*{+U+_+x,W,^,a,q-o/m/p0a0k0o0p0q0r0s0t0u0v0w0x0y0z0{1O1T1x2U3n3q4R4U4V4[4]5]6Q6T6a6e6f7g7z8[8q8|9V:cQ'c#_S)T$`,cR-r)U&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ%f{Q%g|Q%i!OQ%j!PR/^*nQ&b!hQ)V$bQ+q&eS-w)Z)sS0Z+o+pW1|-t-u-v.cS4O0[0]U4y2O2P2QU6s4x5V5WQ7v6tR8e7yT+f&a+gS+d&a+gU0O+e+i+jU0T+k+l+mS0Y+n2rS2m.q2pU3v0R0S0UQ3z0VQ3{0XS5`2n2uS5c2s2tU6X3w3y3|Q6^3}S7Q5d5eQ7S5fS7k6Z6]S8P7R7TQ8_7mR8j8QS+f&a+gT2o.q2pS&o!p/[Q-O([Q-Z(gS/}+d2mQ1_-PS1i-[-dU3x0T0Y5cQ4b1YS4p1p1rU6[3z3{7SQ6i4cQ6r4sR7n6^Q!wXS&n!p/[Q)R$ZQ)^$eQ)d$kQ+z&oQ,}([Q-Y(gQ-_(jQ.P)_Q.x*XS/|+d2mS1^-O-PS1h-Z-dQ1k-^Q1n-`Q3P.yW3t/}0T0Y5cQ4a1YQ4e1_S4j1i1rQ4q1qQ5o3QW6Y3x3z3{7SS6h4b4cQ6m4lQ6p4pQ6}5^Q7[5pS7l6[6^Q7p6iQ7r6nQ7u6rQ7|7OQ8V7]Q8`7nQ8c7tQ8g7}Q8v8hQ9O8wQ9S9PQ:]:WQ:f:aR:g:b$nWORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sS!wn!j!j:V#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR:]:l$nXORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sQ$Zb!Y$em!i$h$i$j&S&g&h&i(g)b)c+T+d+v+w-].S/w0O0T0`1o3v3{6X7k8_S$kn!jQ)_$fQ*X%TW.y*Y*Z*[*]U3Q.z.{.|Q5^2mS5p3R3SU7O5_5`5cQ7]5qU7}7P7Q7SS8h8O8PS8w8i8jQ9P8x!j:W#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mQ:a:kR:b:l$f]OSTjk}!S!W!]!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sU!gRU!`v$wrs!q!t$Y$u&^&q&t)t)u)v*i+Q+a+|,O/h0dQ*f%`!h:X#[#l't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR:[&US&Y!b$vR/u+Z$l[ORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!j'b#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mR*e%`$noORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8sQ'R!z!k:Y#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m!h#UZ!_$`%w%{&v&}'[']'^'_'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9V!R9b'a'r+U,c/m/p0o0w0x0y0z1O1T3n4V4[4]5]6Q6a6e6f7z:c!d#WZ!_$`%w%{&v&}'^'_'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9V}9d'a'r+U,c/m/p0o0y0z1O1T3n4V4[4]5]6Q6a6e6f7z:c!`#[Z!_$`%w%{&v&}'d'h)l*{+_+x,W,^,q-o0a0k0{1x2U3q4R4U6T7g8[8q8|9Vl(W#u&{)P,y-R-g-h0i1w4`4t:^:h:ix:m'a'r+U,c/m/p0o1O1T3n4V4[4]5]6Q6a6e6f7z:c!`:p&w'f(Z(a+p,U,n-V-s-v.g.i0]0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7YZ:q0|4Z6b7o8a&YcORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mS#m`#nR1Q,f&a_ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l#n$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,f,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mT#i^#oS#g^#oT'k#j'oT#h^#oT'm#j'o&a`ORSTU`jk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#[#a#d#l#n$[$n%[%_%`%c%e%f%g%i%m%x&Q&U&]&c&m&z'O't(O)Q)X*g*k*p+P+S+r+y,[,b,f,g-l-q-y.T.t/V/W/X/Z/_/b/d/r/{0b0l1P2c2k2{3`3b3c3l3s5l5z6U6{7j8^8s9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:mT#m`#nQ#p`R'v#n$nbORSTUjk}!S!W!]!`!m!u!y!{#O#P#Q#R#S#T#U#V#W#X#Y#a#d$[$n%[%_%`%c%e%f%g%i%m%x&Q&]&c&m&z'O(O)Q)X*g*k*p+r+y,[,b-l-q-y.T.t/V/W/X/Z/_/b/d/{0b0l2c2{3`3b3c3s5l5z6U7j8^8s!k:k#[#l&U't+P+S,g/r1P2k3l6{9U9W9Z9[9]9^9_9`9a9b9c9d9e9f9g9j9s9t9v9}:O:R:S:m#RdOSUj}!S!W!m!{#l$[%[%_%`%c%e%f%g%i%m&Q&c't)X*g*k*p+r,g-l-y.t/V/W/X/Z/_/b/d1P2c2{3`3b3c5l5zt#{a!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:q!|&y!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:TQ(}$VQ,r(Pc0}9i9n9p9r9x9z9|:Q:Ut#xa!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:qS(j#z(mQ)O$WQ-`(k!|:_!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:Tb:`9i9n9p9r9x9z9|:Q:UQ:d:nR:e:ot#{a!x$S$T$X(T(V(W(_(s(t,X-n1X1s:j:p:q!|&y!v#c#w#y&d'z(c)W)Y)])z)}+s-S-U-x-z.h.k.s.u1b1l1z1}2R2d2z2|4f4r4z5h5m6z7W8T9m9o9q9w9y9{:P:Tc0}9i9n9p9r9x9z9|:Q:UlfOSj}!m$[%c%f%g%i*k*p/_/bQ(b#yQ*w%pQ*x%rR1a-S$U#|a!v!x#c#w#y$S$T$X&d'z(T(V(W(_(c(s(t)W)Y)])z)}+s,X-S-U-n-x-z.h.k.s.u1X1b1l1s1z1}2R2d2z2|4f4r4z5h5m6z7W8T9i9m9n9o9p9q9r9w9x9y9z9{9|:P:Q:T:U:j:p:qQ)|$}Q.j*OQ2g.iR5Y2hT(l#z(mS(l#z(mT2o.q2pQ)^$eQ-_(jQ.P)_Q.x*XQ3P.yQ5o3QQ6}5^Q7[5pQ7|7OQ8V7]Q8g7}Q8v8hQ9O8wR9S9Pl(T#u&{)P,y-R-g-h0i1w4`4t:^:h:i!`9w&w'f(Z(a+p,U,n-V-s-v.g.i0]0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7YZ9x0|4Z6b7o8an(V#u&{)P,w,y-R-g-h0i1w4`4t:^:h:i!b9y&w'f(Z(a+p,U,n-V-s-v.g.i0]0f0h1`1d2Q2f2h2x4T4g4m4v4{5W5k6`6k6q7Y]9z0|4Z6b6c7o8apeOSjy}!m$[%Y%c%f%g%i*k*p/_/bQ%VxR*g%`peOSjy}!m$[%Y%c%f%g%i*k*p/_/bR%VxQ*Q%OR.f)yqeOSjy}!m$[%Y%c%f%g%i*k*p/_/bQ.r*VS2y.v.wW5g2v2w2x2}U7V5i5j5kU8R7U7X7YQ8k8SR8y8lQ%^yR*a%YR3W/PR7_5rS$mp$rR.[)jQ%czR*k%dR*q%jT/`*p/bQjOQ!mST$_j!mQ'|#tR,o'|Q!YQR%u!YQ!^RU%y!^%z*|Q%z!_R*|%{Q+X&VR/t+XQ,Y&{R0j,YQ,]&}S0m,]0nR0n,^Q+g&aR0P+gQ&_!eQ*}%|T+b&_*}Q+[&YR/v+[Q&r!rQ+{&pU,P&r+{0eR0e,QQ'o#jR,h'oQ#n`R'u#nQ#bZU'e#b*z9hQ*z9VR9h'rQ,|([W1[,|1]4d6jU1],}-O-PS4d1^1_R6j4e#q(R#u&w&{'f(Z(a(z({)P+p,S,T,U,n,w,x,y-R-V-g-h-s-v.g.i0]0f0g0h0i0|1`1d1w2Q2f2h2x4T4X4Y4Z4`4g4m4t4v4{5W5k6`6b6c6d6k6q7Y7o8a:^:h:iQ-T(aU1c-T1e4hQ1e-VR4h1dQ(m#zR-a(mQ(v$OR-j(vQ1{-sR4w1{Q)w$xR.e)wQ2j.lS5[2j6|R6|5]Q*S%PR.o*SQ2p.qR5a2pQ/O*^S3U/O5sR5s3WQ.V)fW2Z.V2]5Q6wQ2].YQ5Q2[R6w5RQ)k$mR.])kQ/b*pR3f/bWiOSj!mQ%h}Q)S$[Q*j%cQ*l%fQ*m%gQ*o%iQ/]*kS/`*p/bR3e/_Q$^gQ%l!RQ%o!TQ%q!UQ%s!VQ)r$sQ)x$yQ*`%^Q*u%nS/R*a*dQ/i*tQ/j*wQ/k*xS/z+d2mQ1f-XQ1g-YQ1m-_Q2`.`Q2e.gQ3O.xQ3Y/TQ3d/^Y3r/|/}0T0Y5cQ4i1hQ4k1jQ4n1nQ5U2bQ5X2fQ5n3PQ5t3X[6S3q3t3x3z3{7SQ6l4jQ6o4oQ6x5SQ7Z5oQ7`5uW7f6T6Y6[6^Q7q6mQ7s6pQ7x6yQ7{6}Q8U7[U8Y7g7l7nQ8b7rQ8d7uQ8f7|Q8m8VS8o8[8`Q8t8cQ8u8gQ8z8qQ8}8vQ9Q8|Q9R9OR9T9SQ$gmQ&f!iU)a$h$i$jQ+R&SU+u&g&h&iQ-X(gS.R)b)cQ/q+TQ/y+dS0_+v+wQ1j-]Q2T.SQ3o/wS3u0O0TQ4Q0`Q4o1oS6W3v3{Q7i6XQ8]7kR8r8_S#va:jR)[$dU$Oa$d:jR-i(uQ#uaS&w!v)]Q&{!xQ'f#cQ(Z#wQ(a#yQ(z$SQ({$TQ)P$XQ+p&dQ,S9mQ,T9oQ,U9qQ,n'zQ,w(TQ,x(VQ,y(WQ-R(_Q-V(cQ-g(sQ-h(td-s)W-x.s1}2z4z5h6z7W8TQ-v)YQ.g)zQ.i)}Q0]+sQ0f9wQ0g9yQ0h9{Q0i,XQ0|9iQ1`-SQ1d-UQ1w-nQ2Q-zQ2f.hQ2h.kQ2x.uQ4T:PQ4X9nQ4Y9pQ4Z9rQ4`1XQ4g1bQ4m1lQ4t1sQ4v1zQ4{2RQ5W2dQ5k2|Q6`:TQ6b9|Q6c9xQ6d9zQ6k4fQ6q4rQ7Y5mQ7o:QQ8a:UQ:^:jQ:h:pR:i:qT'{#t'|lgOSj}!m$[%c%f%g%i*k*p/_/bS!oU%eQ%n!SQ%t!WQ'S!{Q's#lS*d%[%_Q*h%`Q*t%mQ+O&QQ+o&cQ,l'tQ-u)XQ/Y*gQ0[+rQ1S,gQ1u-lQ2P-yQ2w.tQ3[/VQ3]/WQ3_/XQ3a/ZQ3h/dQ4^1PQ5V2cQ5j2{Q5y3`Q5{3bQ5|3cQ7X5lR7b5z!vZOSUj}!S!m!{$[%[%_%`%c%e%f%g%i%m&Q&c)X*g*k*p+r-l-y.t/V/W/X/Z/_/b/d2c2{3`3b3c5l5zQ!_RQ!nTQ$`kQ%w!]Q%{!`Q&v!uQ&}!yQ'T#OQ'U#PQ'V#QQ'W#RQ'X#SQ'Y#TQ'Z#UQ'[#VQ']#WQ'^#XQ'_#YQ'a#[Q'd#aQ'h#dW'r#l't,g1PQ)l$nQ*{%xS+U&U/rQ+_&]Q+x&mQ,W&zQ,^'OQ,a9UQ,c9WQ,q(OQ-o)QQ/m+PQ/p+SQ0a+yQ0k,[Q0o9ZQ0p9[Q0q9]Q0r9^Q0s9_Q0t9`Q0u9aQ0v9bQ0w9cQ0x9dQ0y9eQ0z9fQ0{,bQ1O9jQ1T9gQ1x-qQ2U.TQ3n9sQ3q/{Q4R0bQ4U0lQ4V9tQ4[9vQ4]9}Q5]2kQ6Q3lQ6T3sQ6a:OQ6e:RQ6f:SQ7g6UQ7z6{Q8[7jQ8q8^Q8|8sQ9V!WR:c:mT!XQ!YR!aRR&W!bS&S!b+WS+T&T&[R/w+^R&|!xR'P!yT!sU$YS!rU$YU$xrs*iS&p!q!tQ+}&qQ,Q&tQ.d)vS0c+|,OR4S0d[!dR!`$u&^)t+ah!pUrs!q!t$Y&q&t)v+|,O0dQ/[*iQ/n+QQ3k/hT:Z&U)uT!fR$uS!eR$uS%|!`)tS+V&U)uQ+`&^R/x+aT&Z!b$vQ#j^R'x#oT'n#j'oR1R,fT(^#w(fR(d#yQ-t)WQ2O-xQ2v.sQ4x1}Q5i2zQ6t4zQ7U5hQ7y6zQ8S7WR8l8TlhOSj}!m$[%c%f%g%i*k*p/_/bQ%]yR*`%YV$yrs*iR.m*PR*_%WQ$qpR)q$rR)g$lT%az%dT%bz%dT/a*p/b", ++ nodeNames: "⚠ ArithOp ArithOp extends LineComment BlockComment Script ExportDeclaration export Star as VariableName from String ; default FunctionDeclaration async function VariableDefinition TypeParamList TypeDefinition ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType VoidType void TypeofType typeof MemberExpression . ?. PropertyName [ TemplateString null super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewExpression new TypeArgList CompareOp < ) ( ArgList UnaryExpression await yield delete LogicOp BitOp ParenthesizedExpression ClassExpression class extends ClassBody MethodDeclaration Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression PrivatePropertyName BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof in const CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXStartTag JSXSelfClosingTag JSXIdentifier JSXBuiltin JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast ArrowFunction TypeParamList SequenceExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature CallSignature TypePredicate is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody MethodDeclaration AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try catch finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement", ++ maxTerm: 332, + context: trackNewline, + nodeProps: [ +- [NodeProp.group, -26,7,14,16,54,180,184,187,188,190,193,196,207,209,215,217,219,221,224,230,234,236,238,240,242,244,245,"Statement",-30,11,13,23,26,27,38,39,40,41,43,48,56,64,70,71,87,88,97,99,115,118,120,121,122,123,125,126,144,145,147,"Expression",-22,22,24,28,29,31,148,150,152,153,155,156,157,159,160,161,163,164,165,174,176,178,179,"Type",-3,75,81,86,"ClassItem"], +- [NodeProp.closedBy, 37,"]",47,"}",62,")",128,"JSXSelfCloseEndTag JSXEndTag",142,"JSXEndTag"], +- [NodeProp.openedBy, 42,"[",46,"{",61,"(",127,"JSXStartTag",137,"JSXStartTag JSXStartCloseTag"] ++ [NodeProp.group, -26,7,14,16,54,182,186,189,190,192,195,198,209,211,217,219,221,223,226,232,236,238,240,242,244,246,247,"Statement",-30,11,13,23,26,27,38,39,40,41,43,48,56,64,70,71,87,88,97,99,115,118,120,121,122,123,125,126,146,147,149,"Expression",-22,22,24,28,29,31,150,152,154,155,157,158,159,161,162,163,165,166,167,176,178,180,181,"Type",-3,75,81,86,"ClassItem"], ++ [NodeProp.closedBy, 37,"]",47,"}",62,")",128,"JSXSelfCloseEndTag JSXEndTag",144,"JSXEndTag"], ++ [NodeProp.openedBy, 42,"[",46,"{",61,"(",127,"JSXStartTag",139,"JSXStartTag JSXStartCloseTag"] + ], + skippedNodes: [0,4,5], + repeatNodeCount: 28, +- tokenData: "!C}~R!`OX%TXY%cYZ'RZ[%c[]%T]^'R^p%Tpq%cqr'crs(kst0htu2`uv4pvw5ewx6cxy<yyz=Zz{=k{|>k|}?O}!O>k!O!P?`!P!QCl!Q!R!0[!R![!1q![!]!7s!]!^!8V!^!_!8g!_!`!9d!`!a!:[!a!b!<R!b!c%T!c!}2`!}#O!=d#O#P%T#P#Q!=t#Q#R!>U#R#S2`#S#T!>i#T#o2`#o#p!>y#p#q!?O#q#r!?f#r#s!?x#s$f%T$f$g%c$g#BY2`#BY#BZ!@Y#BZ$IS2`$IS$I_!@Y$I_$I|2`$I|$I}!Bq$I}$JO!Bq$JO$JT2`$JT$JU!@Y$JU$KV2`$KV$KW!@Y$KW&FU2`&FU&FV!@Y&FV?HT2`?HT?HU!@Y?HU~2`W%YR$QWO!^%T!_#o%T#p~%T,T%jg$QW'T+{OX%TXY%cYZ%TZ[%c[p%Tpq%cq!^%T!_#o%T#p$f%T$f$g%c$g#BY%T#BY#BZ%c#BZ$IS%T$IS$I_%c$I_$JT%T$JT$JU%c$JU$KV%T$KV$KW%c$KW&FU%T&FU&FV%c&FV?HT%T?HT?HU%c?HU~%T,T'YR$QW'U+{O!^%T!_#o%T#p~%T$T'jS$QW!f#{O!^%T!_!`'v!`#o%T#p~%T$O'}S#a#v$QWO!^%T!_!`(Z!`#o%T#p~%T$O(bR#a#v$QWO!^%T!_#o%T#p~%T'u(rZ$QW]!ROY(kYZ)eZr(krs*rs!^(k!^!_+U!_#O(k#O#P-b#P#o(k#o#p+U#p~(k&r)jV$QWOr)ers*Ps!^)e!^!_*a!_#o)e#o#p*a#p~)e&r*WR#{&j$QWO!^%T!_#o%T#p~%T&j*dROr*ars*ms~*a&j*rO#{&j'u*{R#{&j$QW]!RO!^%T!_#o%T#p~%T'm+ZV]!ROY+UYZ*aZr+Urs+ps#O+U#O#P+w#P~+U'm+wO#{&j]!R'm+zROr+Urs,Ts~+U'm,[U#{&j]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R,sU]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R-[O]!R!R-_PO~,n'u-gV$QWOr(krs-|s!^(k!^!_+U!_#o(k#o#p+U#p~(k'u.VZ#{&j$QW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/PZ$QW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/yR$QW]!RO!^%T!_#o%T#p~%T!Z0XT$QWO!^.x!^!_,n!_#o.x#o#p,n#p~.xy0mZ$QWOt%Ttu1`u!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`y1g]$QW'mqOt%Ttu1`u!Q%T!Q![1`![!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`&i2k_$QW#vS'W%k'dpOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`[3q_$QW#vSOt%Ttu3ju}%T}!O3j!O!Q%T!Q![3j![!^%T!_!c%T!c!}3j!}#R%T#R#S3j#S#T%T#T#o3j#p$g%T$g~3j$O4wS#Y#v$QWO!^%T!_!`5T!`#o%T#p~%T$O5[R$QW#k#vO!^%T!_#o%T#p~%T%r5lU'v%j$QWOv%Tvw6Ow!^%T!_!`5T!`#o%T#p~%T$O6VS$QW#e#vO!^%T!_!`5T!`#o%T#p~%T'u6jZ$QW]!ROY6cYZ7]Zw6cwx*rx!^6c!^!_8T!_#O6c#O#P:T#P#o6c#o#p8T#p~6c&r7bV$QWOw7]wx*Px!^7]!^!_7w!_#o7]#o#p7w#p~7]&j7zROw7wwx*mx~7w'm8YV]!ROY8TYZ7wZw8Twx+px#O8T#O#P8o#P~8T'm8rROw8Twx8{x~8T'm9SU#{&j]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R9kU]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R:QPO~9f'u:YV$QWOw6cwx:ox!^6c!^!_8T!_#o6c#o#p8T#p~6c'u:xZ#{&j$QW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z;rZ$QW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z<jT$QWO!^;k!^!_9f!_#o;k#o#p9f#p~;k%V=QR!`$}$QWO!^%T!_#o%T#p~%TZ=bR!_R$QWO!^%T!_#o%T#p~%T%R=tU'X!R#Z#v$QWOz%Tz{>W{!^%T!_!`5T!`#o%T#p~%T$O>_S#W#v$QWO!^%T!_!`5T!`#o%T#p~%T$u>rSi$m$QWO!^%T!_!`5T!`#o%T#p~%T&i?VR}&a$QWO!^%T!_#o%T#p~%T&i?gVr%n$QWO!O%T!O!P?|!P!Q%T!Q![@r![!^%T!_#o%T#p~%Ty@RT$QWO!O%T!O!P@b!P!^%T!_#o%T#p~%Ty@iR|q$QWO!^%T!_#o%T#p~%Ty@yZ$QWjqO!Q%T!Q![@r![!^%T!_!g%T!g!hAl!h#R%T#R#S@r#S#X%T#X#YAl#Y#o%T#p~%TyAqZ$QWO{%T{|Bd|}%T}!OBd!O!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyBiV$QWO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyCVV$QWjqO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%T,TCs`$QW#X#vOYDuYZ%TZzDuz{Jl{!PDu!P!Q!-e!Q!^Du!^!_Fx!_!`!.^!`!a!/]!a!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXD|[$QWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXEy_$QWyPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%TPF}VyPOYFxZ!PFx!P!QGd!Q!}Fx!}#OG{#O#PHh#P~FxPGiUyP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGdPHOTOYG{Z#OG{#O#PH_#P#QFx#Q~G{PHbQOYG{Z~G{PHkQOYFxZ~FxXHvY$QWOYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~HqXIkV$QWOYHqYZ%TZ!^Hq!^!_G{!_#oHq#o#pG{#p~HqXJVV$QWOYDuYZ%TZ!^Du!^!_Fx!_#oDu#o#pFx#p~Du,TJs^$QWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q!,R!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,TKtV$QWOzKoz{LZ{!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TL`X$QWOzKoz{LZ{!PKo!P!QL{!Q!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TMSR$QWT+{O!^%T!_#o%T#p~%T+{M`ROzM]z{Mi{~M]+{MlTOzM]z{Mi{!PM]!P!QM{!Q~M]+{NQOT+{,TNX^$QWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q! T!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,T! ^_$QWT+{yPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%T+{!!bYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!&x!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#VYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!#u!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#|UT+{yP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGd+{!$cWOY!$`YZM]Zz!$`z{!${{#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%OYOY!$`YZM]Zz!$`z{!${{!P!$`!P!Q!%n!Q#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%sTT+{OYG{Z#OG{#O#PH_#P#QFx#Q~G{+{!&VTOY!$`YZM]Zz!$`z{!${{~!$`+{!&iTOY!!]YZM]Zz!!]z{!#Q{~!!]+{!&}_yPOzM]z{Mi{#ZM]#Z#[!&x#[#]M]#]#^!&x#^#aM]#a#b!&x#b#gM]#g#h!&x#h#iM]#i#j!&x#j#mM]#m#n!&x#n~M],T!(R[$QWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!(|^$QWOY!'|YZKoZz!'|z{!(w{!P!'|!P!Q!)x!Q!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!*PY$QWT+{OYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~Hq,T!*tX$QWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#o!'|#o#p!$`#p~!'|,T!+fX$QWOYJlYZKoZzJlz{NQ{!^Jl!^!_!!]!_#oJl#o#p!!]#p~Jl,T!,Yc$QWyPOzKoz{LZ{!^Ko!^!_M]!_#ZKo#Z#[!,R#[#]Ko#]#^!,R#^#aKo#a#b!,R#b#gKo#g#h!,R#h#iKo#i#j!,R#j#mKo#m#n!,R#n#oKo#o#pM]#p~Ko,T!-lV$QWS+{OY!-eYZ%TZ!^!-e!^!_!.R!_#o!-e#o#p!.R#p~!-e+{!.WQS+{OY!.RZ~!.R$P!.g[$QW#k#vyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Du]!/f[#sS$QWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Duy!0cd$QWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#U%T#U#V!3X#V#X%T#X#YAl#Y#b%T#b#c!2w#c#d!4m#d#l%T#l#m!5{#m#o%T#p~%Ty!1x_$QWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#X%T#X#YAl#Y#b%T#b#c!2w#c#o%T#p~%Ty!3OR$QWjqO!^%T!_#o%T#p~%Ty!3^W$QWO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#o%T#p~%Ty!3}Y$QWjqO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#b%T#b#c!2w#c#o%T#p~%Ty!4rV$QWO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#o%T#p~%Ty!5`X$QWjqO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#b%T#b#c!2w#c#o%T#p~%Ty!6QZ$QWO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#o%T#p~%Ty!6z]$QWjqO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#b%T#b#c!2w#c#o%T#p~%T%w!7|R!XV$QW#i%hO!^%T!_#o%T#p~%T!P!8^R^w$QWO!^%T!_#o%T#p~%T+c!8rR']d!]%Y#t&s'zP!P!Q!8{!^!_!9Q!_!`!9_W!9QO$SW#v!9VP#[#v!_!`!9Y#v!9_O#k#v#v!9dO#]#v%w!9kT!w%o$QWO!^%T!_!`'v!`!a!9z!a#o%T#p~%T$P!:RR#S#w$QWO!^%T!_#o%T#p~%T%w!:gT'[!s#]#v#}S$QWO!^%T!_!`!:v!`!a!;W!a#o%T#p~%T$O!:}R#]#v$QWO!^%T!_#o%T#p~%T$O!;_T#[#v$QWO!^%T!_!`5T!`!a!;n!a#o%T#p~%T$O!;uS#[#v$QWO!^%T!_!`5T!`#o%T#p~%T%w!<YV'n%o$QWO!O%T!O!P!<o!P!^%T!_!a%T!a!b!=P!b#o%T#p~%T$`!<vRs$W$QWO!^%T!_#o%T#p~%T$O!=WS$QW#f#vO!^%T!_!`5T!`#o%T#p~%T&e!=kRu&]$QWO!^%T!_#o%T#p~%TZ!={RzR$QWO!^%T!_#o%T#p~%T$O!>]S#c#v$QWO!^%T!_!`5T!`#o%T#p~%T$P!>pR$QW'a#wO!^%T!_#o%T#p~%T~!?OO!P~%r!?VT'u%j$QWO!^%T!_!`5T!`#o%T#p#q!=P#q~%T$u!?oR!O$k$QW'cQO!^%T!_#o%T#p~%TX!@PR!gP$QWO!^%T!_#o%T#p~%T,T!@gr$QW'T+{#vS'W%k'dpOX%TXY%cYZ%TZ[%c[p%Tpq%cqt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$f%T$f$g%c$g#BY2`#BY#BZ!@Y#BZ$IS2`$IS$I_!@Y$I_$JT2`$JT$JU!@Y$JU$KV2`$KV$KW!@Y$KW&FU2`&FU&FV!@Y&FV?HT2`?HT?HU!@Y?HU~2`,T!CO_$QW'U+{#vS'W%k'dpOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`", ++ tokenData: "!F_~R!`OX%TXY%cYZ'RZ[%c[]%T]^'R^p%Tpq%cqr'crs(kst0htu2`uv4pvw5ewx6cxy<yyz=Zz{=k{|>k|}?O}!O>k!O!P?`!P!QCl!Q!R!0[!R![!1q![!]!7s!]!^!8V!^!_!8g!_!`!9d!`!a!:[!a!b!<R!b!c%T!c!}2`!}#O!=d#O#P%T#P#Q!=t#Q#R!>U#R#S2`#S#T!>i#T#o!>y#o#p!AZ#p#q!A`#q#r!Av#r#s!BY#s$f%T$f$g%c$g#BY2`#BY#BZ!Bj#BZ$IS2`$IS$I_!Bj$I_$I|2`$I|$I}!ER$I}$JO!ER$JO$JT2`$JT$JU!Bj$JU$KV2`$KV$KW!Bj$KW&FU2`&FU&FV!Bj&FV?HT2`?HT?HU!Bj?HU~2`W%YR$SWO!^%T!_#o%T#p~%T,T%jg$SW'V+{OX%TXY%cYZ%TZ[%c[p%Tpq%cq!^%T!_#o%T#p$f%T$f$g%c$g#BY%T#BY#BZ%c#BZ$IS%T$IS$I_%c$I_$JT%T$JT$JU%c$JU$KV%T$KV$KW%c$KW&FU%T&FU&FV%c&FV?HT%T?HT?HU%c?HU~%T,T'YR$SW'W+{O!^%T!_#o%T#p~%T$T'jS$SW!f#{O!^%T!_!`'v!`#o%T#p~%T$O'}S#a#v$SWO!^%T!_!`(Z!`#o%T#p~%T$O(bR#a#v$SWO!^%T!_#o%T#p~%T'u(rZ$SW]!ROY(kYZ)eZr(krs*rs!^(k!^!_+U!_#O(k#O#P-b#P#o(k#o#p+U#p~(k&r)jV$SWOr)ers*Ps!^)e!^!_*a!_#o)e#o#p*a#p~)e&r*WR#}&j$SWO!^%T!_#o%T#p~%T&j*dROr*ars*ms~*a&j*rO#}&j'u*{R#}&j$SW]!RO!^%T!_#o%T#p~%T'm+ZV]!ROY+UYZ*aZr+Urs+ps#O+U#O#P+w#P~+U'm+wO#}&j]!R'm+zROr+Urs,Ts~+U'm,[U#}&j]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R,sU]!ROY,nZr,nrs-Vs#O,n#O#P-[#P~,n!R-[O]!R!R-_PO~,n'u-gV$SWOr(krs-|s!^(k!^!_+U!_#o(k#o#p+U#p~(k'u.VZ#}&j$SW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/PZ$SW]!ROY.xYZ%TZr.xrs/rs!^.x!^!_,n!_#O.x#O#P0S#P#o.x#o#p,n#p~.x!Z/yR$SW]!RO!^%T!_#o%T#p~%T!Z0XT$SWO!^.x!^!_,n!_#o.x#o#p,n#p~.xy0mZ$SWOt%Ttu1`u!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`y1g]$SW'oqOt%Ttu1`u!Q%T!Q![1`![!^%T!_!c%T!c!}1`!}#R%T#R#S1`#S#T%T#T#o1`#p$g%T$g~1`&i2k_$SW'fp'Y%k#vSOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`[3q_$SW#vSOt%Ttu3ju}%T}!O3j!O!Q%T!Q![3j![!^%T!_!c%T!c!}3j!}#R%T#R#S3j#S#T%T#T#o3j#p$g%T$g~3j$O4wS#Y#v$SWO!^%T!_!`5T!`#o%T#p~%T$O5[R$SW#k#vO!^%T!_#o%T#p~%T%r5lU'x%j$SWOv%Tvw6Ow!^%T!_!`5T!`#o%T#p~%T$O6VS$SW#e#vO!^%T!_!`5T!`#o%T#p~%T'u6jZ$SW]!ROY6cYZ7]Zw6cwx*rx!^6c!^!_8T!_#O6c#O#P:T#P#o6c#o#p8T#p~6c&r7bV$SWOw7]wx*Px!^7]!^!_7w!_#o7]#o#p7w#p~7]&j7zROw7wwx*mx~7w'm8YV]!ROY8TYZ7wZw8Twx+px#O8T#O#P8o#P~8T'm8rROw8Twx8{x~8T'm9SU#}&j]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R9kU]!ROY9fZw9fwx-Vx#O9f#O#P9}#P~9f!R:QPO~9f'u:YV$SWOw6cwx:ox!^6c!^!_8T!_#o6c#o#p8T#p~6c'u:xZ#}&j$SW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z;rZ$SW]!ROY;kYZ%TZw;kwx/rx!^;k!^!_9f!_#O;k#O#P<e#P#o;k#o#p9f#p~;k!Z<jT$SWO!^;k!^!_9f!_#o;k#o#p9f#p~;k%V=QR!`$}$SWO!^%T!_#o%T#p~%TZ=bR!_R$SWO!^%T!_#o%T#p~%T%R=tU'Z!R#Z#v$SWOz%Tz{>W{!^%T!_!`5T!`#o%T#p~%T$O>_S#W#v$SWO!^%T!_!`5T!`#o%T#p~%T$u>rSi$m$SWO!^%T!_!`5T!`#o%T#p~%T&i?VR}&a$SWO!^%T!_#o%T#p~%T&i?gVr%n$SWO!O%T!O!P?|!P!Q%T!Q![@r![!^%T!_#o%T#p~%Ty@RT$SWO!O%T!O!P@b!P!^%T!_#o%T#p~%Ty@iR|q$SWO!^%T!_#o%T#p~%Ty@yZ$SWjqO!Q%T!Q![@r![!^%T!_!g%T!g!hAl!h#R%T#R#S@r#S#X%T#X#YAl#Y#o%T#p~%TyAqZ$SWO{%T{|Bd|}%T}!OBd!O!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyBiV$SWO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%TyCVV$SWjqO!Q%T!Q![CO![!^%T!_#R%T#R#SCO#S#o%T#p~%T,TCs`$SW#X#vOYDuYZ%TZzDuz{Jl{!PDu!P!Q!-e!Q!^Du!^!_Fx!_!`!.^!`!a!/]!a!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXD|[$SWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~DuXEy_$SWyPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%TPF}VyPOYFxZ!PFx!P!QGd!Q!}Fx!}#OG{#O#PHh#P~FxPGiUyP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGdPHOTOYG{Z#OG{#O#PH_#P#QFx#Q~G{PHbQOYG{Z~G{PHkQOYFxZ~FxXHvY$SWOYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~HqXIkV$SWOYHqYZ%TZ!^Hq!^!_G{!_#oHq#o#pG{#p~HqXJVV$SWOYDuYZ%TZ!^Du!^!_Fx!_#oDu#o#pFx#p~Du,TJs^$SWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q!,R!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,TKtV$SWOzKoz{LZ{!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TL`X$SWOzKoz{LZ{!PKo!P!QL{!Q!^Ko!^!_M]!_#oKo#o#pM]#p~Ko,TMSR$SWT+{O!^%T!_#o%T#p~%T+{M`ROzM]z{Mi{~M]+{MlTOzM]z{Mi{!PM]!P!QM{!Q~M]+{NQOT+{,TNX^$SWyPOYJlYZKoZzJlz{NQ{!PJl!P!Q! T!Q!^Jl!^!_!!]!_!}Jl!}#O!'|#O#P!+a#P#oJl#o#p!!]#p~Jl,T! ^_$SWT+{yPO!^%T!_#Z%T#Z#[Er#[#]%T#]#^Er#^#a%T#a#bEr#b#g%T#g#hEr#h#i%T#i#jEr#j#m%T#m#nEr#n#o%T#p~%T+{!!bYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!&x!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#VYyPOY!!]YZM]Zz!!]z{!#Q{!P!!]!P!Q!#u!Q!}!!]!}#O!$`#O#P!&f#P~!!]+{!#|UT+{yP#Z#[Gd#]#^Gd#a#bGd#g#hGd#i#jGd#m#nGd+{!$cWOY!$`YZM]Zz!$`z{!${{#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%OYOY!$`YZM]Zz!$`z{!${{!P!$`!P!Q!%n!Q#O!$`#O#P!&S#P#Q!!]#Q~!$`+{!%sTT+{OYG{Z#OG{#O#PH_#P#QFx#Q~G{+{!&VTOY!$`YZM]Zz!$`z{!${{~!$`+{!&iTOY!!]YZM]Zz!!]z{!#Q{~!!]+{!&}_yPOzM]z{Mi{#ZM]#Z#[!&x#[#]M]#]#^!&x#^#aM]#a#b!&x#b#gM]#g#h!&x#h#iM]#i#j!&x#j#mM]#m#n!&x#n~M],T!(R[$SWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!(|^$SWOY!'|YZKoZz!'|z{!(w{!P!'|!P!Q!)x!Q!^!'|!^!_!$`!_#O!'|#O#P!*o#P#QJl#Q#o!'|#o#p!$`#p~!'|,T!*PY$SWT+{OYHqYZ%TZ!^Hq!^!_G{!_#OHq#O#PIf#P#QDu#Q#oHq#o#pG{#p~Hq,T!*tX$SWOY!'|YZKoZz!'|z{!(w{!^!'|!^!_!$`!_#o!'|#o#p!$`#p~!'|,T!+fX$SWOYJlYZKoZzJlz{NQ{!^Jl!^!_!!]!_#oJl#o#p!!]#p~Jl,T!,Yc$SWyPOzKoz{LZ{!^Ko!^!_M]!_#ZKo#Z#[!,R#[#]Ko#]#^!,R#^#aKo#a#b!,R#b#gKo#g#h!,R#h#iKo#i#j!,R#j#mKo#m#n!,R#n#oKo#o#pM]#p~Ko,T!-lV$SWS+{OY!-eYZ%TZ!^!-e!^!_!.R!_#o!-e#o#p!.R#p~!-e+{!.WQS+{OY!.RZ~!.R$P!.g[$SW#k#vyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Du]!/f[#sS$SWyPOYDuYZ%TZ!PDu!P!QEr!Q!^Du!^!_Fx!_!}Du!}#OHq#O#PJQ#P#oDu#o#pFx#p~Duy!0cd$SWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#U%T#U#V!3X#V#X%T#X#YAl#Y#b%T#b#c!2w#c#d!4m#d#l%T#l#m!5{#m#o%T#p~%Ty!1x_$SWjqO!O%T!O!P@r!P!Q%T!Q![!1q![!^%T!_!g%T!g!hAl!h#R%T#R#S!1q#S#X%T#X#YAl#Y#b%T#b#c!2w#c#o%T#p~%Ty!3OR$SWjqO!^%T!_#o%T#p~%Ty!3^W$SWO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#o%T#p~%Ty!3}Y$SWjqO!Q%T!Q!R!3v!R!S!3v!S!^%T!_#R%T#R#S!3v#S#b%T#b#c!2w#c#o%T#p~%Ty!4rV$SWO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#o%T#p~%Ty!5`X$SWjqO!Q%T!Q!Y!5X!Y!^%T!_#R%T#R#S!5X#S#b%T#b#c!2w#c#o%T#p~%Ty!6QZ$SWO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#o%T#p~%Ty!6z]$SWjqO!Q%T!Q![!6s![!^%T!_!c%T!c!i!6s!i#R%T#R#S!6s#S#T%T#T#Z!6s#Z#b%T#b#c!2w#c#o%T#p~%T%w!7|R!XV$SW#i%hO!^%T!_#o%T#p~%T!P!8^R^w$SWO!^%T!_#o%T#p~%T+c!8rR'_d!]%Y#t&s'|P!P!Q!8{!^!_!9Q!_!`!9_W!9QO$UW#v!9VP#[#v!_!`!9Y#v!9_O#k#v#v!9dO#]#v%w!9kT!w%o$SWO!^%T!_!`'v!`!a!9z!a#o%T#p~%T$P!:RR#S#w$SWO!^%T!_#o%T#p~%T%w!:gT'^!s#]#v$PS$SWO!^%T!_!`!:v!`!a!;W!a#o%T#p~%T$O!:}R#]#v$SWO!^%T!_#o%T#p~%T$O!;_T#[#v$SWO!^%T!_!`5T!`!a!;n!a#o%T#p~%T$O!;uS#[#v$SWO!^%T!_!`5T!`#o%T#p~%T%w!<YV'p%o$SWO!O%T!O!P!<o!P!^%T!_!a%T!a!b!=P!b#o%T#p~%T$`!<vRs$W$SWO!^%T!_#o%T#p~%T$O!=WS$SW#f#vO!^%T!_!`5T!`#o%T#p~%T&e!=kRu&]$SWO!^%T!_#o%T#p~%TZ!={RzR$SWO!^%T!_#o%T#p~%T$O!>]S#c#v$SWO!^%T!_!`5T!`#o%T#p~%T$P!>pR$SW'c#wO!^%T!_#o%T#p~%T&i!?U_$SW'fp'Y%k#xSOt%Ttu!>yu}%T}!O!@T!O!Q%T!Q![!>y![!^%T!_!c%T!c!}!>y!}#R%T#R#S!>y#S#T%T#T#o!>y#p$g%T$g~!>y[!@[_$SW#xSOt%Ttu!@Tu}%T}!O!@T!O!Q%T!Q![!@T![!^%T!_!c%T!c!}!@T!}#R%T#R#S!@T#S#T%T#T#o!@T#p$g%T$g~!@T~!A`O!P~%r!AgT'w%j$SWO!^%T!_!`5T!`#o%T#p#q!=P#q~%T$u!BPR!O$k$SW'eQO!^%T!_#o%T#p~%TX!BaR!gP$SWO!^%T!_#o%T#p~%T,T!Bwr$SW'V+{'fp'Y%k#vSOX%TXY%cYZ%TZ[%c[p%Tpq%cqt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$f%T$f$g%c$g#BY2`#BY#BZ!Bj#BZ$IS2`$IS$I_!Bj$I_$JT2`$JT$JU!Bj$JU$KV2`$KV$KW!Bj$KW&FU2`&FU&FV!Bj&FV?HT2`?HT?HU!Bj?HU~2`,T!E`_$SW'W+{'fp'Y%k#vSOt%Ttu2`u}%T}!O3j!O!Q%T!Q![2`![!^%T!_!c%T!c!}2`!}#R%T#R#S2`#S#T%T#T#o2`#p$g%T$g~2`", + tokenizers: [noSemicolon, incdecToken, template, 0, 1, 2, 3, 4, 5, 6, 7, 8, insertSemicolon], + topRules: {"Script":[0,6]}, +- dialects: {jsx: 11282, ts: 11284}, +- dynamicPrecedences: {"145":1,"172":1}, +- specialized: [{term: 284, get: (value, stack) => (tsExtends(value, stack) << 1)},{term: 284, get: value => spec_identifier[value] || -1},{term: 296, get: value => spec_word[value] || -1},{term: 59, get: value => spec_LessThan[value] || -1}], +- tokenPrec: 11305 ++ dialects: {jsx: 11332, ts: 11334}, ++ dynamicPrecedences: {"147":1,"174":1}, ++ specialized: [{term: 286, get: (value, stack) => (tsExtends(value, stack) << 1)},{term: 286, get: value => spec_identifier[value] || -1},{term: 298, get: value => spec_word[value] || -1},{term: 59, get: value => spec_LessThan[value] || -1}], ++ tokenPrec: 11355 + }) +diff --git a/node_modules/@lezer/javascript/src/parser.terms.js b/node_modules/@lezer/javascript/src/parser.terms.js +index fc7710f..49e292f 100644 +--- a/node_modules/@lezer/javascript/src/parser.terms.js ++++ b/node_modules/@lezer/javascript/src/parser.terms.js +@@ -1,15 +1,15 @@ + // This file was generated by lezer-generator. You probably shouldn't edit it. + export const +- noSemi = 275, ++ noSemi = 277, + incdec = 1, + incdecPrefix = 2, +- templateContent = 276, +- templateDollarBrace = 277, +- templateEnd = 278, +- insertSemi = 279, ++ templateContent = 278, ++ templateDollarBrace = 279, ++ templateEnd = 280, ++ insertSemi = 281, + TSExtends = 3, +- spaces = 281, +- newline = 282, ++ spaces = 283, ++ newline = 284, + LineComment = 4, + BlockComment = 5, + Script = 6, +@@ -61,38 +61,39 @@ export const + JSXStartTag = 128, + JSXSelfClosingTag = 129, + JSXIdentifier = 130, +- JSXNamespacedName = 131, +- JSXMemberExpression = 132, +- JSXAttributeValue = 135, +- JSXEndTag = 137, +- JSXOpenTag = 138, +- JSXFragmentTag = 139, +- JSXText = 140, +- JSXEscape = 141, +- JSXStartCloseTag = 142, +- JSXCloseTag = 143, +- SequenceExpression = 147, +- TypeName = 155, +- ParamTypeList = 158, +- IndexedType = 160, +- Label = 162, +- ObjectType = 165, +- MethodType = 166, +- PropertyType = 167, +- IndexSignature = 168, +- TypePredicate = 170, +- ClassDeclaration = 180, +- VariableDeclaration = 184, +- TypeAliasDeclaration = 187, +- InterfaceDeclaration = 188, +- EnumDeclaration = 190, +- NamespaceDeclaration = 193, +- AmbientDeclaration = 196, +- ExportGroup = 204, +- ImportDeclaration = 207, +- ImportGroup = 208, +- ForSpec = 211, +- ForInSpec = 212, +- ForOfSpec = 213, ++ JSXLowerIdentifier = 132, ++ JSXNamespacedName = 133, ++ JSXMemberExpression = 134, ++ JSXAttributeValue = 137, ++ JSXEndTag = 139, ++ JSXOpenTag = 140, ++ JSXFragmentTag = 141, ++ JSXText = 142, ++ JSXEscape = 143, ++ JSXStartCloseTag = 144, ++ JSXCloseTag = 145, ++ SequenceExpression = 149, ++ TypeName = 157, ++ ParamTypeList = 160, ++ IndexedType = 162, ++ Label = 164, ++ ObjectType = 167, ++ MethodType = 168, ++ PropertyType = 169, ++ IndexSignature = 170, ++ TypePredicate = 172, ++ ClassDeclaration = 182, ++ VariableDeclaration = 186, ++ TypeAliasDeclaration = 189, ++ InterfaceDeclaration = 190, ++ EnumDeclaration = 192, ++ NamespaceDeclaration = 195, ++ AmbientDeclaration = 198, ++ ExportGroup = 206, ++ ImportDeclaration = 209, ++ ImportGroup = 210, ++ ForSpec = 213, ++ ForInSpec = 214, ++ ForOfSpec = 215, + Dialect_jsx = 0, + Dialect_ts = 1 diff --git a/beta/patches/next+12.3.2-canary.7.patch b/beta/patches/next+12.3.2-canary.7.patch new file mode 100644 index 000000000..ee8d132de --- /dev/null +++ b/beta/patches/next+12.3.2-canary.7.patch @@ -0,0 +1,22 @@ +diff --git a/node_modules/next/dist/server/render.js b/node_modules/next/dist/server/render.js +index 3a141de..72a8749 100644 +--- a/node_modules/next/dist/server/render.js ++++ b/node_modules/next/dist/server/render.js +@@ -752,9 +752,14 @@ async function renderToHTML(req, res, pathname, query, renderOpts) { + // Enabling react concurrent rendering mode: __NEXT_REACT_ROOT = true + const renderShell = async (EnhancedApp, EnhancedComponent)=>{ + const content = renderContent(EnhancedApp, EnhancedComponent); +- return await (0, _nodeWebStreamsHelper).renderToInitialStream({ +- ReactDOMServer, +- element: content ++ return new Promise((resolve, reject) => { ++ (0, _nodeWebStreamsHelper).renderToInitialStream({ ++ ReactDOMServer, ++ element: content, ++ streamOptions: { ++ onError: reject ++ } ++ }).then(resolve, reject); + }); + }; + const createBodyResult = (initialStream, suffix)=>{ diff --git a/beta/patches/next-remote-watch+1.0.0.patch b/beta/patches/next-remote-watch+1.0.0.patch new file mode 100644 index 000000000..c9ecef84d --- /dev/null +++ b/beta/patches/next-remote-watch+1.0.0.patch @@ -0,0 +1,16 @@ +diff --git a/node_modules/next-remote-watch/bin/next-remote-watch b/node_modules/next-remote-watch/bin/next-remote-watch +index c055b66..a2f749c 100755 +--- a/node_modules/next-remote-watch/bin/next-remote-watch ++++ b/node_modules/next-remote-watch/bin/next-remote-watch +@@ -66,7 +66,10 @@ app.prepare().then(() => { + } + } + +- app.server.hotReloader.send('reloadPage') ++ app.server.hotReloader.send({ ++ event: 'serverOnlyChanges', ++ pages: ['/[[...markdownPath]]'] ++ }); + } + ) + } diff --git a/beta/plugins/markdownToHtml.js b/beta/plugins/markdownToHtml.js new file mode 100644 index 000000000..0d5fe7afb --- /dev/null +++ b/beta/plugins/markdownToHtml.js @@ -0,0 +1,30 @@ +const remark = require('remark'); +const externalLinks = require('remark-external-links'); // Add _target and rel to external links +const customHeaders = require('./remark-header-custom-ids'); // Custom header id's for i18n +const images = require('remark-images'); // Improved image syntax +const unrwapImages = require('remark-unwrap-images'); // Removes <p> wrapper around images +const smartyPants = require('./remark-smartypants'); // Cleans up typography +const html = require('remark-html'); + +module.exports = { + remarkPlugins: [ + externalLinks, + customHeaders, + images, + unrwapImages, + smartyPants, + ], + markdownToHtml, +}; + +async function markdownToHtml(markdown) { + const result = await remark() + .use(externalLinks) + .use(customHeaders) + .use(images) + .use(unrwapImages) + .use(smartyPants) + .use(html) + .process(markdown); + return result.toString(); +} diff --git a/beta/plugins/remark-header-custom-ids.js b/beta/plugins/remark-header-custom-ids.js new file mode 100644 index 000000000..356de1bf1 --- /dev/null +++ b/beta/plugins/remark-header-custom-ids.js @@ -0,0 +1,76 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/*! + * Based on 'gatsby-remark-autolink-headers' + * Original Author: Kyle Mathews <mathews.kyle@gmail.com> + * Updated by Jared Palmer; + * Copyright (c) 2015 Gatsbyjs + */ + +const toString = require('mdast-util-to-string'); +const visit = require('unist-util-visit'); +const toSlug = require('github-slugger').slug; + +function patch(context, key, value) { + if (!context[key]) { + context[key] = value; + } + return context[key]; +} + +const svgIcon = `<svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>`; + +module.exports = ({icon = svgIcon, className = `anchor`} = {}) => { + return function transformer(tree) { + const ids = new Set(); + visit(tree, 'heading', (node) => { + let children = [...node.children]; + let id; + if (children[children.length - 1].type === 'mdxTextExpression') { + // # My header {/*my-header*/} + id = children.pop().value; + const isValidCustomId = id.startsWith('/*') && id.endsWith('*/'); + if (!isValidCustomId) { + throw Error( + 'Expected header ID to be like: {/*some-header*/}. ' + + 'Instead, received: ' + + id + ); + } + id = id.slice(2, id.length - 2); + if (id !== toSlug(id)) { + throw Error( + 'Expected header ID to be a valid slug. You specified: {/*' + + id + + '*/}. Replace it with: {/*' + + toSlug(id) + + '*/}' + ); + } + } else { + // # My header + id = toSlug(toString(node)); + } + + if (ids.has(id)) { + throw Error( + 'Cannot have a duplicate header with id "' + + id + + '" on the page. ' + + 'Rename the section or give it an explicit unique ID. ' + + 'For example: #### Arguments {/*setstate-arguments*/}' + ); + } + ids.add(id); + + const data = patch(node, 'data', {}); + patch(data, 'id', id); + patch(data, 'htmlAttributes', {}); + patch(data, 'hProperties', {}); + patch(data.htmlAttributes, 'id', id); + patch(data.hProperties, 'id', id); + }); + }; +}; diff --git a/beta/plugins/remark-smartypants.js b/beta/plugins/remark-smartypants.js new file mode 100644 index 000000000..7dd1b0c4a --- /dev/null +++ b/beta/plugins/remark-smartypants.js @@ -0,0 +1,21 @@ +const visit = require('unist-util-visit'); +const retext = require('retext'); +const smartypants = require('retext-smartypants'); + +function check(parent) { + if (parent.tagName === 'script') return false; + if (parent.tagName === 'style') return false; + return true; +} + +module.exports = function (options) { + const processor = retext().use(smartypants, options); + + function transformer(tree) { + visit(tree, 'text', (node, index, parent) => { + if (check(parent)) node.value = String(processor.processSync(node.value)); + }); + } + + return transformer; +}; diff --git a/beta/postcss.config.js b/beta/postcss.config.js new file mode 100644 index 000000000..427294522 --- /dev/null +++ b/beta/postcss.config.js @@ -0,0 +1,20 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + 'postcss-flexbugs-fixes': {}, + 'postcss-preset-env': { + autoprefixer: { + flexbox: 'no-2009', + }, + stage: 3, + features: { + 'custom-properties': false, + }, + }, + }, +} diff --git a/beta/public/external.png b/beta/public/external.png new file mode 100644 index 000000000..423ec131b Binary files /dev/null and b/beta/public/external.png differ diff --git a/beta/public/favicon.ico b/beta/public/favicon.ico new file mode 100644 index 000000000..38fd8641c Binary files /dev/null and b/beta/public/favicon.ico differ diff --git a/beta/public/favicon_dark.ico b/beta/public/favicon_dark.ico new file mode 100644 index 000000000..9bcfdcd7b Binary files /dev/null and b/beta/public/favicon_dark.ico differ diff --git a/beta/public/fonts/Source-Code-Pro-Regular.woff2 b/beta/public/fonts/Source-Code-Pro-Regular.woff2 new file mode 100644 index 000000000..655cd9e81 Binary files /dev/null and b/beta/public/fonts/Source-Code-Pro-Regular.woff2 differ diff --git a/beta/public/html/single-file-example.html b/beta/public/html/single-file-example.html new file mode 100644 index 000000000..380a169f5 --- /dev/null +++ b/beta/public/html/single-file-example.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8" /> + <title>Hello World</title> + <script src="https://unpkg.com/react@18/umd/react.development.js"></script> + <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> + + <!-- Don't use this in production—do this: https://reactjs.org/docs/add-react-to-a-website#add-jsx-to-a-project --> + <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> + </head> + <body> + <div id="root"></div> + <script type="text/babel"> + + ReactDOM.render( + <h1>Hello, world!</h1>, + document.getElementById('root') + ); + + </script> + <!-- + Note: this page is a great way to try React but it's not suitable for production. + It slowly compiles JSX with Babel in the browser and uses a large development build of React. + + Read this section for a production-ready setup with JSX: + https://reactjs.org/docs/docs/add-react-to-a-website#try-react-with-jsx + + In a larger project, you can use an integrated toolchain that includes JSX instead: + https://reactjs.org/docs/start-a-new-react-project + + You can also use React without JSX, in which case you can remove Babel: + https://reactjs.org/docs/add-react-to-a-website#add-react-in-one-minute + --> + </body> +</html> diff --git a/beta/public/icons/logo-white.svg b/beta/public/icons/logo-white.svg new file mode 100644 index 000000000..7ed0e8b05 --- /dev/null +++ b/beta/public/icons/logo-white.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348"> + <title>React Logo</title> + <circle cx="0" cy="0" r="2.05" fill="#fff"/> + <g stroke="#fff" stroke-width="1" fill="none"> + <ellipse rx="11" ry="4.2"/> + <ellipse rx="11" ry="4.2" transform="rotate(60)"/> + <ellipse rx="11" ry="4.2" transform="rotate(120)"/> + </g> +</svg> diff --git a/beta/public/icons/logo.svg b/beta/public/icons/logo.svg new file mode 100644 index 000000000..ea77a618d --- /dev/null +++ b/beta/public/icons/logo.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348"> + <title>React Logo</title> + <circle cx="0" cy="0" r="2.05" fill="#61dafb"/> + <g stroke="#61dafb" stroke-width="1" fill="none"> + <ellipse rx="11" ry="4.2"/> + <ellipse rx="11" ry="4.2" transform="rotate(60)"/> + <ellipse rx="11" ry="4.2" transform="rotate(120)"/> + </g> +</svg> diff --git a/beta/public/images/blog/animal-sounds.jpg b/beta/public/images/blog/animal-sounds.jpg new file mode 100644 index 000000000..abe6d815e Binary files /dev/null and b/beta/public/images/blog/animal-sounds.jpg differ diff --git a/beta/public/images/blog/chatapp.png b/beta/public/images/blog/chatapp.png new file mode 100644 index 000000000..de8c09ff3 Binary files /dev/null and b/beta/public/images/blog/chatapp.png differ diff --git a/beta/public/images/blog/cra-better-output.png b/beta/public/images/blog/cra-better-output.png new file mode 100644 index 000000000..44ed320ff Binary files /dev/null and b/beta/public/images/blog/cra-better-output.png differ diff --git a/beta/public/images/blog/cra-dynamic-import.gif b/beta/public/images/blog/cra-dynamic-import.gif new file mode 100644 index 000000000..88db2896f Binary files /dev/null and b/beta/public/images/blog/cra-dynamic-import.gif differ diff --git a/beta/public/images/blog/cra-jest-search.gif b/beta/public/images/blog/cra-jest-search.gif new file mode 100644 index 000000000..ed6819d89 Binary files /dev/null and b/beta/public/images/blog/cra-jest-search.gif differ diff --git a/beta/public/images/blog/cra-pwa.png b/beta/public/images/blog/cra-pwa.png new file mode 100644 index 000000000..9862ae565 Binary files /dev/null and b/beta/public/images/blog/cra-pwa.png differ diff --git a/beta/public/images/blog/cra-runtime-error.gif b/beta/public/images/blog/cra-runtime-error.gif new file mode 100644 index 000000000..7b4405f62 Binary files /dev/null and b/beta/public/images/blog/cra-runtime-error.gif differ diff --git a/beta/public/images/blog/cra-update-exports.gif b/beta/public/images/blog/cra-update-exports.gif new file mode 100644 index 000000000..2820d63df Binary files /dev/null and b/beta/public/images/blog/cra-update-exports.gif differ diff --git a/beta/public/images/blog/create-apps-with-no-configuration/compiled-successfully.png b/beta/public/images/blog/create-apps-with-no-configuration/compiled-successfully.png new file mode 100644 index 000000000..223f5abf2 Binary files /dev/null and b/beta/public/images/blog/create-apps-with-no-configuration/compiled-successfully.png differ diff --git a/beta/public/images/blog/create-apps-with-no-configuration/compiled-with-warnings.png b/beta/public/images/blog/create-apps-with-no-configuration/compiled-with-warnings.png new file mode 100644 index 000000000..20aa25e3c Binary files /dev/null and b/beta/public/images/blog/create-apps-with-no-configuration/compiled-with-warnings.png differ diff --git a/beta/public/images/blog/create-apps-with-no-configuration/created-folder.png b/beta/public/images/blog/create-apps-with-no-configuration/created-folder.png new file mode 100644 index 000000000..44aff6dcd Binary files /dev/null and b/beta/public/images/blog/create-apps-with-no-configuration/created-folder.png differ diff --git a/beta/public/images/blog/create-apps-with-no-configuration/failed-to-compile.png b/beta/public/images/blog/create-apps-with-no-configuration/failed-to-compile.png new file mode 100644 index 000000000..a72b5fb2e Binary files /dev/null and b/beta/public/images/blog/create-apps-with-no-configuration/failed-to-compile.png differ diff --git a/beta/public/images/blog/create-apps-with-no-configuration/npm-run-build.png b/beta/public/images/blog/create-apps-with-no-configuration/npm-run-build.png new file mode 100644 index 000000000..a7b931e12 Binary files /dev/null and b/beta/public/images/blog/create-apps-with-no-configuration/npm-run-build.png differ diff --git a/beta/public/images/blog/devtools-component-filters.gif b/beta/public/images/blog/devtools-component-filters.gif new file mode 100644 index 000000000..287c66757 Binary files /dev/null and b/beta/public/images/blog/devtools-component-filters.gif differ diff --git a/beta/public/images/blog/devtools-full.gif b/beta/public/images/blog/devtools-full.gif new file mode 100644 index 000000000..fd7ed9493 Binary files /dev/null and b/beta/public/images/blog/devtools-full.gif differ diff --git a/beta/public/images/blog/devtools-highlight-updates.png b/beta/public/images/blog/devtools-highlight-updates.png new file mode 100644 index 000000000..780bcf2ef Binary files /dev/null and b/beta/public/images/blog/devtools-highlight-updates.png differ diff --git a/beta/public/images/blog/devtools-search.gif b/beta/public/images/blog/devtools-search.gif new file mode 100644 index 000000000..22d80051d Binary files /dev/null and b/beta/public/images/blog/devtools-search.gif differ diff --git a/beta/public/images/blog/devtools-side-pane.gif b/beta/public/images/blog/devtools-side-pane.gif new file mode 100644 index 000000000..381e3554e Binary files /dev/null and b/beta/public/images/blog/devtools-side-pane.gif differ diff --git a/beta/public/images/blog/devtools-tree-view.png b/beta/public/images/blog/devtools-tree-view.png new file mode 100644 index 000000000..854a39f8f Binary files /dev/null and b/beta/public/images/blog/devtools-tree-view.png differ diff --git a/beta/public/images/blog/devtools-v4-screenshot.png b/beta/public/images/blog/devtools-v4-screenshot.png new file mode 100644 index 000000000..83a67d548 Binary files /dev/null and b/beta/public/images/blog/devtools-v4-screenshot.png differ diff --git a/beta/public/images/blog/dog-tutorial.png b/beta/public/images/blog/dog-tutorial.png new file mode 100644 index 000000000..72f8b4341 Binary files /dev/null and b/beta/public/images/blog/dog-tutorial.png differ diff --git a/beta/public/images/blog/first-look.png b/beta/public/images/blog/first-look.png new file mode 100644 index 000000000..d36aa1f35 Binary files /dev/null and b/beta/public/images/blog/first-look.png differ diff --git a/beta/public/images/blog/flux-chart.png b/beta/public/images/blog/flux-chart.png new file mode 100644 index 000000000..fbfdf94ea Binary files /dev/null and b/beta/public/images/blog/flux-chart.png differ diff --git a/beta/public/images/blog/flux-diagram.png b/beta/public/images/blog/flux-diagram.png new file mode 100644 index 000000000..69cf64e0b Binary files /dev/null and b/beta/public/images/blog/flux-diagram.png differ diff --git a/beta/public/images/blog/genesis_skeleton.png b/beta/public/images/blog/genesis_skeleton.png new file mode 100644 index 000000000..4b7c51e29 Binary files /dev/null and b/beta/public/images/blog/genesis_skeleton.png differ diff --git a/beta/public/images/blog/gpu-cursor-move.gif b/beta/public/images/blog/gpu-cursor-move.gif new file mode 100644 index 000000000..b3a621f78 Binary files /dev/null and b/beta/public/images/blog/gpu-cursor-move.gif differ diff --git a/beta/public/images/blog/guess_filter.jpg b/beta/public/images/blog/guess_filter.jpg new file mode 100644 index 000000000..1983df1c8 Binary files /dev/null and b/beta/public/images/blog/guess_filter.jpg differ diff --git a/beta/public/images/blog/hacker-news-react-native.png b/beta/public/images/blog/hacker-news-react-native.png new file mode 100644 index 000000000..a65679aba Binary files /dev/null and b/beta/public/images/blog/hacker-news-react-native.png differ diff --git a/beta/public/images/blog/highlight-updates-example.gif b/beta/public/images/blog/highlight-updates-example.gif new file mode 100644 index 000000000..ab0c87d0c Binary files /dev/null and b/beta/public/images/blog/highlight-updates-example.gif differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/commit-selector.png b/beta/public/images/blog/introducing-the-react-profiler/commit-selector.png new file mode 100644 index 000000000..9c027444d Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/commit-selector.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/component-chart.png b/beta/public/images/blog/introducing-the-react-profiler/component-chart.png new file mode 100644 index 000000000..3a3615db2 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/component-chart.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/devtools-profiler-tab.png b/beta/public/images/blog/introducing-the-react-profiler/devtools-profiler-tab.png new file mode 100644 index 000000000..eabb3e500 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/devtools-profiler-tab.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/filtering-commits.gif b/beta/public/images/blog/introducing-the-react-profiler/filtering-commits.gif new file mode 100644 index 000000000..1d73258e3 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/filtering-commits.gif differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/flame-chart.png b/beta/public/images/blog/introducing-the-react-profiler/flame-chart.png new file mode 100644 index 000000000..80840b55c Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/flame-chart.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/interactions-for-commit.png b/beta/public/images/blog/introducing-the-react-profiler/interactions-for-commit.png new file mode 100644 index 000000000..8f1a14c61 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/interactions-for-commit.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/interactions.png b/beta/public/images/blog/introducing-the-react-profiler/interactions.png new file mode 100644 index 000000000..5683ac94a Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/interactions.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/navigate-between-interactions-and-commits.gif b/beta/public/images/blog/introducing-the-react-profiler/navigate-between-interactions-and-commits.gif new file mode 100644 index 000000000..e500459c3 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/navigate-between-interactions-and-commits.gif differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/no-interactions.png b/beta/public/images/blog/introducing-the-react-profiler/no-interactions.png new file mode 100644 index 000000000..d70199fc7 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/no-interactions.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/no-profiler-data-multi-root.png b/beta/public/images/blog/introducing-the-react-profiler/no-profiler-data-multi-root.png new file mode 100644 index 000000000..e56fd4128 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/no-profiler-data-multi-root.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/no-render-times-for-selected-component.png b/beta/public/images/blog/introducing-the-react-profiler/no-render-times-for-selected-component.png new file mode 100644 index 000000000..f77b42f85 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/no-render-times-for-selected-component.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/no-timing-data-for-commit.png b/beta/public/images/blog/introducing-the-react-profiler/no-timing-data-for-commit.png new file mode 100644 index 000000000..84a4dcac2 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/no-timing-data-for-commit.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/props-and-state.gif b/beta/public/images/blog/introducing-the-react-profiler/props-and-state.gif new file mode 100644 index 000000000..b0b05b127 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/props-and-state.gif differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/ranked-chart.png b/beta/public/images/blog/introducing-the-react-profiler/ranked-chart.png new file mode 100644 index 000000000..01246f5e5 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/ranked-chart.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/see-all-commits-for-a-fiber.gif b/beta/public/images/blog/introducing-the-react-profiler/see-all-commits-for-a-fiber.gif new file mode 100644 index 000000000..dac21e249 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/see-all-commits-for-a-fiber.gif differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/see-which-props-changed.gif b/beta/public/images/blog/introducing-the-react-profiler/see-which-props-changed.gif new file mode 100644 index 000000000..ae965be9b Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/see-which-props-changed.gif differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/select-a-root-to-view-profiling-data.gif b/beta/public/images/blog/introducing-the-react-profiler/select-a-root-to-view-profiling-data.gif new file mode 100644 index 000000000..b53f31025 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/select-a-root-to-view-profiling-data.gif differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/start-profiling.png b/beta/public/images/blog/introducing-the-react-profiler/start-profiling.png new file mode 100644 index 000000000..7256474c0 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/start-profiling.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/stop-profiling.png b/beta/public/images/blog/introducing-the-react-profiler/stop-profiling.png new file mode 100644 index 000000000..4ef27f333 Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/stop-profiling.png differ diff --git a/beta/public/images/blog/introducing-the-react-profiler/zoom-in-and-out.gif b/beta/public/images/blog/introducing-the-react-profiler/zoom-in-and-out.gif new file mode 100644 index 000000000..3a8be2c9b Binary files /dev/null and b/beta/public/images/blog/introducing-the-react-profiler/zoom-in-and-out.gif differ diff --git a/beta/public/images/blog/jsx-compiler.png b/beta/public/images/blog/jsx-compiler.png new file mode 100644 index 000000000..9b5e169c0 Binary files /dev/null and b/beta/public/images/blog/jsx-compiler.png differ diff --git a/beta/public/images/blog/kendoui.png b/beta/public/images/blog/kendoui.png new file mode 100644 index 000000000..8b18bf438 Binary files /dev/null and b/beta/public/images/blog/kendoui.png differ diff --git a/beta/public/images/blog/khan-academy-editor.png b/beta/public/images/blog/khan-academy-editor.png new file mode 100644 index 000000000..f0413939d Binary files /dev/null and b/beta/public/images/blog/khan-academy-editor.png differ diff --git a/beta/public/images/blog/landoflisp.png b/beta/public/images/blog/landoflisp.png new file mode 100644 index 000000000..42bad2d4b Binary files /dev/null and b/beta/public/images/blog/landoflisp.png differ diff --git a/beta/public/images/blog/lights-out.png b/beta/public/images/blog/lights-out.png new file mode 100644 index 000000000..78c545e7e Binary files /dev/null and b/beta/public/images/blog/lights-out.png differ diff --git a/beta/public/images/blog/makona-editor.png b/beta/public/images/blog/makona-editor.png new file mode 100644 index 000000000..4fc4ece99 Binary files /dev/null and b/beta/public/images/blog/makona-editor.png differ diff --git a/beta/public/images/blog/markdown_refactor.png b/beta/public/images/blog/markdown_refactor.png new file mode 100644 index 000000000..b81679541 Binary files /dev/null and b/beta/public/images/blog/markdown_refactor.png differ diff --git a/beta/public/images/blog/modus-create.gif b/beta/public/images/blog/modus-create.gif new file mode 100644 index 000000000..194252ad1 Binary files /dev/null and b/beta/public/images/blog/modus-create.gif differ diff --git a/beta/public/images/blog/monkeys.gif b/beta/public/images/blog/monkeys.gif new file mode 100644 index 000000000..3da6185ce Binary files /dev/null and b/beta/public/images/blog/monkeys.gif differ diff --git a/beta/public/images/blog/ngreact.png b/beta/public/images/blog/ngreact.png new file mode 100644 index 000000000..6ce7e3418 Binary files /dev/null and b/beta/public/images/blog/ngreact.png differ diff --git a/beta/public/images/blog/om-backbone.png b/beta/public/images/blog/om-backbone.png new file mode 100644 index 000000000..df411b280 Binary files /dev/null and b/beta/public/images/blog/om-backbone.png differ diff --git a/beta/public/images/blog/parse-react.jpg b/beta/public/images/blog/parse-react.jpg new file mode 100644 index 000000000..15e45d2ae Binary files /dev/null and b/beta/public/images/blog/parse-react.jpg differ diff --git a/beta/public/images/blog/polarr.jpg b/beta/public/images/blog/polarr.jpg new file mode 100644 index 000000000..1cfb3cc90 Binary files /dev/null and b/beta/public/images/blog/polarr.jpg differ diff --git a/beta/public/images/blog/propeller-logo.png b/beta/public/images/blog/propeller-logo.png new file mode 100644 index 000000000..81cfaa181 Binary files /dev/null and b/beta/public/images/blog/propeller-logo.png differ diff --git a/beta/public/images/blog/property-finder.png b/beta/public/images/blog/property-finder.png new file mode 100644 index 000000000..709448b4f Binary files /dev/null and b/beta/public/images/blog/property-finder.png differ diff --git a/beta/public/images/blog/quiztime.png b/beta/public/images/blog/quiztime.png new file mode 100644 index 000000000..7021036e6 Binary files /dev/null and b/beta/public/images/blog/quiztime.png differ diff --git a/beta/public/images/blog/react-50k-mock-full.jpg b/beta/public/images/blog/react-50k-mock-full.jpg new file mode 100644 index 000000000..1ebac0063 Binary files /dev/null and b/beta/public/images/blog/react-50k-mock-full.jpg differ diff --git a/beta/public/images/blog/react-50k-mock.jpg b/beta/public/images/blog/react-50k-mock.jpg new file mode 100644 index 000000000..bc2de61d4 Binary files /dev/null and b/beta/public/images/blog/react-50k-mock.jpg differ diff --git a/beta/public/images/blog/react-50k-tshirt.jpg b/beta/public/images/blog/react-50k-tshirt.jpg new file mode 100644 index 000000000..c96b44ca5 Binary files /dev/null and b/beta/public/images/blog/react-50k-tshirt.jpg differ diff --git a/beta/public/images/blog/react-browserify-gulp.jpg b/beta/public/images/blog/react-browserify-gulp.jpg new file mode 100644 index 000000000..d4389d6e9 Binary files /dev/null and b/beta/public/images/blog/react-browserify-gulp.jpg differ diff --git a/beta/public/images/blog/react-dev-tools.jpg b/beta/public/images/blog/react-dev-tools.jpg new file mode 100644 index 000000000..7eef44629 Binary files /dev/null and b/beta/public/images/blog/react-dev-tools.jpg differ diff --git a/beta/public/images/blog/react-diff-tree.png b/beta/public/images/blog/react-diff-tree.png new file mode 100644 index 000000000..9935e4ae1 Binary files /dev/null and b/beta/public/images/blog/react-diff-tree.png differ diff --git a/beta/public/images/blog/react-draggable.png b/beta/public/images/blog/react-draggable.png new file mode 100644 index 000000000..c0cb61f16 Binary files /dev/null and b/beta/public/images/blog/react-draggable.png differ diff --git a/beta/public/images/blog/react-hackathon.jpg b/beta/public/images/blog/react-hackathon.jpg new file mode 100644 index 000000000..2537d9865 Binary files /dev/null and b/beta/public/images/blog/react-hackathon.jpg differ diff --git a/beta/public/images/blog/react-page.png b/beta/public/images/blog/react-page.png new file mode 100644 index 000000000..3249fb370 Binary files /dev/null and b/beta/public/images/blog/react-page.png differ diff --git a/beta/public/images/blog/react-perf-chrome-timeline.png b/beta/public/images/blog/react-perf-chrome-timeline.png new file mode 100644 index 000000000..e9db8acfb Binary files /dev/null and b/beta/public/images/blog/react-perf-chrome-timeline.png differ diff --git a/beta/public/images/blog/react-php.png b/beta/public/images/blog/react-php.png new file mode 100644 index 000000000..cb7e69abc Binary files /dev/null and b/beta/public/images/blog/react-php.png differ diff --git a/beta/public/images/blog/react-svg-fbp.png b/beta/public/images/blog/react-svg-fbp.png new file mode 100644 index 000000000..ef89babad Binary files /dev/null and b/beta/public/images/blog/react-svg-fbp.png differ diff --git a/beta/public/images/blog/react-v16.13.0/hydration-warning-after.png b/beta/public/images/blog/react-v16.13.0/hydration-warning-after.png new file mode 100644 index 000000000..92b6c9919 Binary files /dev/null and b/beta/public/images/blog/react-v16.13.0/hydration-warning-after.png differ diff --git a/beta/public/images/blog/react-v16.13.0/hydration-warning-before.png b/beta/public/images/blog/react-v16.13.0/hydration-warning-before.png new file mode 100644 index 000000000..381ab8677 Binary files /dev/null and b/beta/public/images/blog/react-v16.13.0/hydration-warning-before.png differ diff --git a/beta/public/images/blog/react-v16.9.0/codemod.gif b/beta/public/images/blog/react-v16.9.0/codemod.gif new file mode 100644 index 000000000..955b30fdc Binary files /dev/null and b/beta/public/images/blog/react-v16.9.0/codemod.gif differ diff --git a/beta/public/images/blog/react-v16.9.0/cwm-warning.png b/beta/public/images/blog/react-v16.9.0/cwm-warning.png new file mode 100644 index 000000000..3ee69d748 Binary files /dev/null and b/beta/public/images/blog/react-v16.9.0/cwm-warning.png differ diff --git a/beta/public/images/blog/react-v17-rc/react_17_delegation.png b/beta/public/images/blog/react-v17-rc/react_17_delegation.png new file mode 100644 index 000000000..c8b23c0d6 Binary files /dev/null and b/beta/public/images/blog/react-v17-rc/react_17_delegation.png differ diff --git a/beta/public/images/blog/reactive-bookmarklet.png b/beta/public/images/blog/reactive-bookmarklet.png new file mode 100644 index 000000000..25351e717 Binary files /dev/null and b/beta/public/images/blog/reactive-bookmarklet.png differ diff --git a/beta/public/images/blog/reflux-flux.png b/beta/public/images/blog/reflux-flux.png new file mode 100644 index 000000000..dad38c7d1 Binary files /dev/null and b/beta/public/images/blog/reflux-flux.png differ diff --git a/beta/public/images/blog/relay-components/relay-architecture.png b/beta/public/images/blog/relay-components/relay-architecture.png new file mode 100644 index 000000000..0cfbff0d8 Binary files /dev/null and b/beta/public/images/blog/relay-components/relay-architecture.png differ diff --git a/beta/public/images/blog/relay-components/relay-containers-data-flow.png b/beta/public/images/blog/relay-components/relay-containers-data-flow.png new file mode 100644 index 000000000..2f062dd65 Binary files /dev/null and b/beta/public/images/blog/relay-components/relay-containers-data-flow.png differ diff --git a/beta/public/images/blog/relay-components/relay-containers.png b/beta/public/images/blog/relay-components/relay-containers.png new file mode 100644 index 000000000..be7dee719 Binary files /dev/null and b/beta/public/images/blog/relay-components/relay-containers.png differ diff --git a/beta/public/images/blog/relay-components/sample-newsfeed.png b/beta/public/images/blog/relay-components/sample-newsfeed.png new file mode 100644 index 000000000..0e7f5b5de Binary files /dev/null and b/beta/public/images/blog/relay-components/sample-newsfeed.png differ diff --git a/beta/public/images/blog/relay-visual-architecture-tour.png b/beta/public/images/blog/relay-visual-architecture-tour.png new file mode 100644 index 000000000..b35c4978f Binary files /dev/null and b/beta/public/images/blog/relay-visual-architecture-tour.png differ diff --git a/beta/public/images/blog/release-script-build-confirmation.png b/beta/public/images/blog/release-script-build-confirmation.png new file mode 100644 index 000000000..02026d172 Binary files /dev/null and b/beta/public/images/blog/release-script-build-confirmation.png differ diff --git a/beta/public/images/blog/release-script-build-overview.png b/beta/public/images/blog/release-script-build-overview.png new file mode 100644 index 000000000..d726be464 Binary files /dev/null and b/beta/public/images/blog/release-script-build-overview.png differ diff --git a/beta/public/images/blog/release-script-publish-confirmation.png b/beta/public/images/blog/release-script-publish-confirmation.png new file mode 100644 index 000000000..e05e64830 Binary files /dev/null and b/beta/public/images/blog/release-script-publish-confirmation.png differ diff --git a/beta/public/images/blog/resistance-calculator.png b/beta/public/images/blog/resistance-calculator.png new file mode 100644 index 000000000..16e8b3536 Binary files /dev/null and b/beta/public/images/blog/resistance-calculator.png differ diff --git a/beta/public/images/blog/skills-matter.png b/beta/public/images/blog/skills-matter.png new file mode 100644 index 000000000..4a4858c3d Binary files /dev/null and b/beta/public/images/blog/skills-matter.png differ diff --git a/beta/public/images/blog/snake.png b/beta/public/images/blog/snake.png new file mode 100644 index 000000000..96d72b38a Binary files /dev/null and b/beta/public/images/blog/snake.png differ diff --git a/beta/public/images/blog/steve_reverse.gif b/beta/public/images/blog/steve_reverse.gif new file mode 100644 index 000000000..a442fbbd9 Binary files /dev/null and b/beta/public/images/blog/steve_reverse.gif differ diff --git a/beta/public/images/blog/strict-mode-unsafe-lifecycles-warning.png b/beta/public/images/blog/strict-mode-unsafe-lifecycles-warning.png new file mode 100644 index 000000000..fbdeccde6 Binary files /dev/null and b/beta/public/images/blog/strict-mode-unsafe-lifecycles-warning.png differ diff --git a/beta/public/images/blog/sweet-jsx.png b/beta/public/images/blog/sweet-jsx.png new file mode 100644 index 000000000..f20aeede4 Binary files /dev/null and b/beta/public/images/blog/sweet-jsx.png differ diff --git a/beta/public/images/blog/tcomb-react-native.png b/beta/public/images/blog/tcomb-react-native.png new file mode 100644 index 000000000..98120c758 Binary files /dev/null and b/beta/public/images/blog/tcomb-react-native.png differ diff --git a/beta/public/images/blog/thinking-in-react-components.png b/beta/public/images/blog/thinking-in-react-components.png new file mode 100644 index 000000000..c71a86bcb Binary files /dev/null and b/beta/public/images/blog/thinking-in-react-components.png differ diff --git a/beta/public/images/blog/thinking-in-react-mock.png b/beta/public/images/blog/thinking-in-react-mock.png new file mode 100644 index 000000000..78bd00a4a Binary files /dev/null and b/beta/public/images/blog/thinking-in-react-mock.png differ diff --git a/beta/public/images/blog/todomvc.png b/beta/public/images/blog/todomvc.png new file mode 100644 index 000000000..ee78eb1cd Binary files /dev/null and b/beta/public/images/blog/todomvc.png differ diff --git a/beta/public/images/blog/turboreact.png b/beta/public/images/blog/turboreact.png new file mode 100644 index 000000000..e8ef8cd3a Binary files /dev/null and b/beta/public/images/blog/turboreact.png differ diff --git a/beta/public/images/blog/tutsplus.png b/beta/public/images/blog/tutsplus.png new file mode 100644 index 000000000..771653086 Binary files /dev/null and b/beta/public/images/blog/tutsplus.png differ diff --git a/beta/public/images/blog/unite.png b/beta/public/images/blog/unite.png new file mode 100644 index 000000000..ab45a5355 Binary files /dev/null and b/beta/public/images/blog/unite.png differ diff --git a/beta/public/images/blog/versioning-1.png b/beta/public/images/blog/versioning-1.png new file mode 100644 index 000000000..c13f98fd1 Binary files /dev/null and b/beta/public/images/blog/versioning-1.png differ diff --git a/beta/public/images/blog/versioning-2.png b/beta/public/images/blog/versioning-2.png new file mode 100644 index 000000000..39de2a010 Binary files /dev/null and b/beta/public/images/blog/versioning-2.png differ diff --git a/beta/public/images/blog/versioning-3.png b/beta/public/images/blog/versioning-3.png new file mode 100644 index 000000000..1824e89a9 Binary files /dev/null and b/beta/public/images/blog/versioning-3.png differ diff --git a/beta/public/images/blog/versioning-4.png b/beta/public/images/blog/versioning-4.png new file mode 100644 index 000000000..13ba32e39 Binary files /dev/null and b/beta/public/images/blog/versioning-4.png differ diff --git a/beta/public/images/blog/versioning-5.png b/beta/public/images/blog/versioning-5.png new file mode 100644 index 000000000..542a3926b Binary files /dev/null and b/beta/public/images/blog/versioning-5.png differ diff --git a/beta/public/images/blog/versioning-6.png b/beta/public/images/blog/versioning-6.png new file mode 100644 index 000000000..e82bc7136 Binary files /dev/null and b/beta/public/images/blog/versioning-6.png differ diff --git a/beta/public/images/blog/versioning-poll.png b/beta/public/images/blog/versioning-poll.png new file mode 100644 index 000000000..8b3e18d93 Binary files /dev/null and b/beta/public/images/blog/versioning-poll.png differ diff --git a/beta/public/images/blog/warn-legacy-context-in-strict-mode.png b/beta/public/images/blog/warn-legacy-context-in-strict-mode.png new file mode 100644 index 000000000..e061325ac Binary files /dev/null and b/beta/public/images/blog/warn-legacy-context-in-strict-mode.png differ diff --git a/beta/public/images/blog/weather.png b/beta/public/images/blog/weather.png new file mode 100644 index 000000000..90c5e6fea Binary files /dev/null and b/beta/public/images/blog/weather.png differ diff --git a/beta/public/images/blog/wolfenstein_react.png b/beta/public/images/blog/wolfenstein_react.png new file mode 100644 index 000000000..98241b864 Binary files /dev/null and b/beta/public/images/blog/wolfenstein_react.png differ diff --git a/beta/public/images/blog/xoxo2013.png b/beta/public/images/blog/xoxo2013.png new file mode 100644 index 000000000..d35989095 Binary files /dev/null and b/beta/public/images/blog/xoxo2013.png differ diff --git a/beta/public/images/blog/xreact.png b/beta/public/images/blog/xreact.png new file mode 100644 index 000000000..ba23489d5 Binary files /dev/null and b/beta/public/images/blog/xreact.png differ diff --git a/beta/public/images/docs/blur-popover-close.gif b/beta/public/images/docs/blur-popover-close.gif new file mode 100644 index 000000000..fefc6b8a4 Binary files /dev/null and b/beta/public/images/docs/blur-popover-close.gif differ diff --git a/beta/public/images/docs/cdn-cors-header.png b/beta/public/images/docs/cdn-cors-header.png new file mode 100644 index 000000000..31b047cbd Binary files /dev/null and b/beta/public/images/docs/cdn-cors-header.png differ diff --git a/beta/public/images/docs/cm-steps-simple.png b/beta/public/images/docs/cm-steps-simple.png new file mode 100644 index 000000000..e5683f9e6 Binary files /dev/null and b/beta/public/images/docs/cm-steps-simple.png differ diff --git a/beta/public/images/docs/codewinds-004.png b/beta/public/images/docs/codewinds-004.png new file mode 100644 index 000000000..6c4bc997f Binary files /dev/null and b/beta/public/images/docs/codewinds-004.png differ diff --git a/beta/public/images/docs/devtools-dev.png b/beta/public/images/docs/devtools-dev.png new file mode 100644 index 000000000..5347b4b8d Binary files /dev/null and b/beta/public/images/docs/devtools-dev.png differ diff --git a/beta/public/images/docs/devtools-prod.png b/beta/public/images/docs/devtools-prod.png new file mode 100644 index 000000000..4e200fb48 Binary files /dev/null and b/beta/public/images/docs/devtools-prod.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_context_close.dark.png b/beta/public/images/docs/diagrams/passing_data_context_close.dark.png new file mode 100644 index 000000000..59045299d Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_context_close.dark.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_context_close.png b/beta/public/images/docs/diagrams/passing_data_context_close.png new file mode 100644 index 000000000..4591aacce Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_context_close.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_context_far.dark.png b/beta/public/images/docs/diagrams/passing_data_context_far.dark.png new file mode 100644 index 000000000..7e0d0bc58 Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_context_far.dark.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_context_far.png b/beta/public/images/docs/diagrams/passing_data_context_far.png new file mode 100644 index 000000000..7a4c85150 Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_context_far.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_lifting_state.dark.png b/beta/public/images/docs/diagrams/passing_data_lifting_state.dark.png new file mode 100644 index 000000000..dd7b48556 Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_lifting_state.dark.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_lifting_state.png b/beta/public/images/docs/diagrams/passing_data_lifting_state.png new file mode 100644 index 000000000..919b4e684 Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_lifting_state.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_prop_drilling.dark.png b/beta/public/images/docs/diagrams/passing_data_prop_drilling.dark.png new file mode 100644 index 000000000..1cbd5a2f8 Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_prop_drilling.dark.png differ diff --git a/beta/public/images/docs/diagrams/passing_data_prop_drilling.png b/beta/public/images/docs/diagrams/passing_data_prop_drilling.png new file mode 100644 index 000000000..c22cb2c52 Binary files /dev/null and b/beta/public/images/docs/diagrams/passing_data_prop_drilling.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_add_component.dark.png b/beta/public/images/docs/diagrams/preserving_state_add_component.dark.png new file mode 100644 index 000000000..24155f936 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_add_component.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_add_component.png b/beta/public/images/docs/diagrams/preserving_state_add_component.png new file mode 100644 index 000000000..d98e7bd3d Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_add_component.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_position_p1.dark.png b/beta/public/images/docs/diagrams/preserving_state_diff_position_p1.dark.png new file mode 100644 index 000000000..b2c2efc67 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_position_p1.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_position_p1.png b/beta/public/images/docs/diagrams/preserving_state_diff_position_p1.png new file mode 100644 index 000000000..84d4ef52d Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_position_p1.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_position_p2.dark.png b/beta/public/images/docs/diagrams/preserving_state_diff_position_p2.dark.png new file mode 100644 index 000000000..9472414cf Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_position_p2.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_position_p2.png b/beta/public/images/docs/diagrams/preserving_state_diff_position_p2.png new file mode 100644 index 000000000..fad05b2e2 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_position_p2.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_position_p3.dark.png b/beta/public/images/docs/diagrams/preserving_state_diff_position_p3.dark.png new file mode 100644 index 000000000..2b520eb6a Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_position_p3.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_position_p3.png b/beta/public/images/docs/diagrams/preserving_state_diff_position_p3.png new file mode 100644 index 000000000..1fbbe510a Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_position_p3.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_pt1.dark.png b/beta/public/images/docs/diagrams/preserving_state_diff_pt1.dark.png new file mode 100644 index 000000000..0bb042c94 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_pt1.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_pt1.png b/beta/public/images/docs/diagrams/preserving_state_diff_pt1.png new file mode 100644 index 000000000..1cbc6874f Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_pt1.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_pt2.dark.png b/beta/public/images/docs/diagrams/preserving_state_diff_pt2.dark.png new file mode 100644 index 000000000..ae6bdf95b Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_pt2.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_pt2.png b/beta/public/images/docs/diagrams/preserving_state_diff_pt2.png new file mode 100644 index 000000000..3d52b278e Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_pt2.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_same_pt1.dark.png b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt1.dark.png new file mode 100644 index 000000000..f0eb4a786 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt1.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_same_pt1.png b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt1.png new file mode 100644 index 000000000..3d22270d8 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt1.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_same_pt2.dark.png b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt2.dark.png new file mode 100644 index 000000000..07ef5a761 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt2.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_diff_same_pt2.png b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt2.png new file mode 100644 index 000000000..1bbc87a3e Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_diff_same_pt2.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_dom_tree.dark.png b/beta/public/images/docs/diagrams/preserving_state_dom_tree.dark.png new file mode 100644 index 000000000..4f7c8afdc Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_dom_tree.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_dom_tree.png b/beta/public/images/docs/diagrams/preserving_state_dom_tree.png new file mode 100644 index 000000000..3b38a1bd3 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_dom_tree.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_increment.dark.png b/beta/public/images/docs/diagrams/preserving_state_increment.dark.png new file mode 100644 index 000000000..5bc133611 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_increment.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_increment.png b/beta/public/images/docs/diagrams/preserving_state_increment.png new file mode 100644 index 000000000..9b9ce9ca8 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_increment.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_remove_component.dark.png b/beta/public/images/docs/diagrams/preserving_state_remove_component.dark.png new file mode 100644 index 000000000..cb6a466ac Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_remove_component.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_remove_component.png b/beta/public/images/docs/diagrams/preserving_state_remove_component.png new file mode 100644 index 000000000..966c1744b Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_remove_component.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_same_component.dark.png b/beta/public/images/docs/diagrams/preserving_state_same_component.dark.png new file mode 100644 index 000000000..013fcaa80 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_same_component.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_same_component.png b/beta/public/images/docs/diagrams/preserving_state_same_component.png new file mode 100644 index 000000000..4cb1b32ed Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_same_component.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_tree.dark.png b/beta/public/images/docs/diagrams/preserving_state_tree.dark.png new file mode 100644 index 000000000..452343857 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_tree.dark.png differ diff --git a/beta/public/images/docs/diagrams/preserving_state_tree.png b/beta/public/images/docs/diagrams/preserving_state_tree.png new file mode 100644 index 000000000..a77e49ab2 Binary files /dev/null and b/beta/public/images/docs/diagrams/preserving_state_tree.png differ diff --git a/beta/public/images/docs/diagrams/responding_to_input_flow.dark.png b/beta/public/images/docs/diagrams/responding_to_input_flow.dark.png new file mode 100644 index 000000000..60e5c8f36 Binary files /dev/null and b/beta/public/images/docs/diagrams/responding_to_input_flow.dark.png differ diff --git a/beta/public/images/docs/diagrams/responding_to_input_flow.png b/beta/public/images/docs/diagrams/responding_to_input_flow.png new file mode 100644 index 000000000..cbbec9ac3 Binary files /dev/null and b/beta/public/images/docs/diagrams/responding_to_input_flow.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_child.dark.png b/beta/public/images/docs/diagrams/sharing_data_child.dark.png new file mode 100644 index 000000000..3efdc29fe Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_child.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_child.png b/beta/public/images/docs/diagrams/sharing_data_child.png new file mode 100644 index 000000000..afc794bcd Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_child.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_child_clicked.dark.png b/beta/public/images/docs/diagrams/sharing_data_child_clicked.dark.png new file mode 100644 index 000000000..cf60c235f Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_child_clicked.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_child_clicked.png b/beta/public/images/docs/diagrams/sharing_data_child_clicked.png new file mode 100644 index 000000000..c14fb4a8f Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_child_clicked.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_parent.dark.png b/beta/public/images/docs/diagrams/sharing_data_parent.dark.png new file mode 100644 index 000000000..d4f369835 Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_parent.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_parent.png b/beta/public/images/docs/diagrams/sharing_data_parent.png new file mode 100644 index 000000000..8c7b70305 Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_parent.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_parent_clicked.dark.png b/beta/public/images/docs/diagrams/sharing_data_parent_clicked.dark.png new file mode 100644 index 000000000..ac4c765de Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_parent_clicked.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_data_parent_clicked.png b/beta/public/images/docs/diagrams/sharing_data_parent_clicked.png new file mode 100644 index 000000000..ca16cb8dd Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_data_parent_clicked.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_child.dark.png b/beta/public/images/docs/diagrams/sharing_state_child.dark.png new file mode 100644 index 000000000..dfaf897b0 Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_child.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_child.png b/beta/public/images/docs/diagrams/sharing_state_child.png new file mode 100644 index 000000000..c3a9f0ec9 Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_child.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_child_clicked.dark.png b/beta/public/images/docs/diagrams/sharing_state_child_clicked.dark.png new file mode 100644 index 000000000..bc6b3c89a Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_child_clicked.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_child_clicked.png b/beta/public/images/docs/diagrams/sharing_state_child_clicked.png new file mode 100644 index 000000000..d4dddbd9b Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_child_clicked.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_parent.dark.png b/beta/public/images/docs/diagrams/sharing_state_parent.dark.png new file mode 100644 index 000000000..ba937de7e Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_parent.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_parent.png b/beta/public/images/docs/diagrams/sharing_state_parent.png new file mode 100644 index 000000000..3aaaef724 Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_parent.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_parent_clicked.dark.png b/beta/public/images/docs/diagrams/sharing_state_parent_clicked.dark.png new file mode 100644 index 000000000..22ef522b4 Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_parent_clicked.dark.png differ diff --git a/beta/public/images/docs/diagrams/sharing_state_parent_clicked.png b/beta/public/images/docs/diagrams/sharing_state_parent_clicked.png new file mode 100644 index 000000000..406cd8314 Binary files /dev/null and b/beta/public/images/docs/diagrams/sharing_state_parent_clicked.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_form.dark.png b/beta/public/images/docs/diagrams/writing_jsx_form.dark.png new file mode 100644 index 000000000..3f7184239 Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_form.dark.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_form.png b/beta/public/images/docs/diagrams/writing_jsx_form.png new file mode 100644 index 000000000..21baa97fc Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_form.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_html.dark.png b/beta/public/images/docs/diagrams/writing_jsx_html.dark.png new file mode 100644 index 000000000..c73d3fb42 Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_html.dark.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_html.png b/beta/public/images/docs/diagrams/writing_jsx_html.png new file mode 100644 index 000000000..6f30314a3 Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_html.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_js.dark.png b/beta/public/images/docs/diagrams/writing_jsx_js.dark.png new file mode 100644 index 000000000..aee00cb98 Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_js.dark.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_js.png b/beta/public/images/docs/diagrams/writing_jsx_js.png new file mode 100644 index 000000000..9c3d44216 Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_js.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_sidebar.dark.png b/beta/public/images/docs/diagrams/writing_jsx_sidebar.dark.png new file mode 100644 index 000000000..5fa45be1c Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_sidebar.dark.png differ diff --git a/beta/public/images/docs/diagrams/writing_jsx_sidebar.png b/beta/public/images/docs/diagrams/writing_jsx_sidebar.png new file mode 100644 index 000000000..b1474562d Binary files /dev/null and b/beta/public/images/docs/diagrams/writing_jsx_sidebar.png differ diff --git a/beta/public/images/docs/error-boundaries-stack-trace-line-numbers.png b/beta/public/images/docs/error-boundaries-stack-trace-line-numbers.png new file mode 100644 index 000000000..db828905a Binary files /dev/null and b/beta/public/images/docs/error-boundaries-stack-trace-line-numbers.png differ diff --git a/beta/public/images/docs/error-boundaries-stack-trace.png b/beta/public/images/docs/error-boundaries-stack-trace.png new file mode 100644 index 000000000..f0d49d903 Binary files /dev/null and b/beta/public/images/docs/error-boundaries-stack-trace.png differ diff --git a/beta/public/images/docs/granular-dom-updates.gif b/beta/public/images/docs/granular-dom-updates.gif new file mode 100644 index 000000000..1651b0dae Binary files /dev/null and b/beta/public/images/docs/granular-dom-updates.gif differ diff --git a/beta/public/images/docs/illustrations/i_browser-paint.png b/beta/public/images/docs/illustrations/i_browser-paint.png new file mode 100644 index 000000000..b0d61fa8f Binary files /dev/null and b/beta/public/images/docs/illustrations/i_browser-paint.png differ diff --git a/beta/public/images/docs/illustrations/i_children-prop.png b/beta/public/images/docs/illustrations/i_children-prop.png new file mode 100644 index 000000000..296c626dc Binary files /dev/null and b/beta/public/images/docs/illustrations/i_children-prop.png differ diff --git a/beta/public/images/docs/illustrations/i_declarative-ui-programming.png b/beta/public/images/docs/illustrations/i_declarative-ui-programming.png new file mode 100644 index 000000000..206d73e51 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_declarative-ui-programming.png differ diff --git a/beta/public/images/docs/illustrations/i_html_js.svg b/beta/public/images/docs/illustrations/i_html_js.svg new file mode 100644 index 000000000..9b6f5bad8 --- /dev/null +++ b/beta/public/images/docs/illustrations/i_html_js.svg @@ -0,0 +1,95 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="800" height="355" viewBox="0 0 800 355"> + <defs> + <style> + .a { + fill: #e0ddf0; + } + + .b { + fill: #a19ad1; + } + + .c, .h { + font-size: 14px; +font-family: Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace ; + } + .c { + fill: #312c59; + } + + .c, .d, .h, .i { + font-weight: 700; + } + + .d, .i { + font-size: 24px; + font-family: system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; + } + + .d { + fill: #8179c1; + } + + .e { + fill: #cdeffc; + } + + .f { + fill: none; + stroke: #416c7c; + stroke-miterlimit: 10; + stroke-width: 3px; + } + + .g { + fill: #81d8f7; + } + + .h { + fill: #154854; + } + + .i { + fill: #61a2b9; + } + + .j { + letter-spacing: -0.02em; + } + </style> + </defs> + <g> + <rect class="a" x="424.31" y="39" width="290.19" height="273"/> + <g> + <rect class="b" x="466.74" y="103.5" width="203.99" height="37.5" rx="4.83"/> + <text class="c" transform="translate(479.95 126.36)">onSubmit() { ... }</text> + </g> + <g> + <rect class="b" x="467.15" y="153.75" width="203.99" height="37.5" rx="4.83"/> + <text class="c" transform="translate(480.34 176.6)">login() { ... }</text> + </g> + <g> + <rect class="b" x="467.15" y="203.75" width="203.99" height="37.5" rx="4.83"/> + <text class="c" transform="translate(480.34 226.6)">onClick() { ... }</text> + </g> + <text class="d" transform="translate(554.31 76.12)">JS</text> + </g> + <g> + <rect class="e" x="75.5" y="39" width="291.37" height="273"/> + <path class="f" d="M213.69,124.36v17A16.67,16.67,0,0,1,197,158H175.83a16.68,16.68,0,0,0-16.68,16.67v13.24"/> + <path class="f" d="M214.15,124.36v17A16.68,16.68,0,0,0,230.83,158H252a16.67,16.67,0,0,1,16.67,16.67V220.5"/> + <g> + <rect class="g" x="178" y="103.5" width="76.5" height="37.5" rx="4.83"/> + <text class="h" transform="translate(194.95 126.36)"><div></text> + </g> + <g> + <rect class="g" x="230.8" y="217.92" width="76.5" height="37.5" rx="4.83"/> + <text class="h" transform="translate(243.55 240.78)"><form></text> + </g> + <g> + <rect class="g" x="123.81" y="182.92" width="76.5" height="37.5" rx="4.83"/> + <text class="h" transform="translate(149.15 205.78)"><p></text> + </g> + <text class="i" transform="translate(189.88 76.12)">HTML</text> + </g> +</svg> diff --git a/beta/public/images/docs/illustrations/i_imperative-ui-programming.png b/beta/public/images/docs/illustrations/i_imperative-ui-programming.png new file mode 100644 index 000000000..1e3b5fdd8 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_imperative-ui-programming.png differ diff --git a/beta/public/images/docs/illustrations/i_import-export.svg b/beta/public/images/docs/illustrations/i_import-export.svg new file mode 100644 index 000000000..eb62e2b8c --- /dev/null +++ b/beta/public/images/docs/illustrations/i_import-export.svg @@ -0,0 +1,100 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="800" height="395" viewBox="0 0 800 395"> + <defs> + <style> + .a { + fill: none; + } + + .h { + fill: #fff; + stroke: #a19ad1; + stroke-miterlimit: 10; + stroke-width: 4px; + } + + .b { + fill: #cdeffc; + } + + .c, .j { + font-size: 14px; + font-family: system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; + } + + .c { + fill: #18162d; + } + + .c, .e, .g, .j { + font-weight: 700; + } + + .d { + font-family: Courier; + font-weight: 400; + } + + .e, .g { + font-size: 18px; + font-family: Arial-BoldMT, Arial; + } + + .e { + fill: #a19ad1; + } + + .f { + fill: #bec3c3; + } + + .g { + fill: #6257b2; + } + + .i { + fill: #c0bce0; + } + + .j { + fill: #312c59; + } + </style> + </defs> + <g> + <rect class="h" x="46.5" y="33.55" width="212" height="292.45"/> + <g> + <rect class="b" x="67.75" y="173.55" width="164.75" height="115.45" rx="4.83"/> + <text class="c" transform="translate(79.95 200.91)">export default <tspan class="d"><tspan x="0.56" y="16.8">function </tspan><tspan x="0" y="33.6">Button() { </tspan><tspan x="0" y="50.4" xml:space="preserve"> ... </tspan><tspan x="0" y="67.2">}</tspan></tspan></text> + </g> + <text class="e" transform="translate(86.99 62.68)">Component.js</text> + <rect class="f" x="68.11" y="76" width="164.75" height="40" rx="4.83"/> + <rect class="f" x="68.11" y="124.33" width="164.75" height="40" rx="4.83"/> + <text class="g" transform="translate(69.21 355.13)">one default export</text> + </g> + <g> + <text class="g" transform="translate(295.2 355.13)">multiple named exports</text> + <rect class="h" x="294.5" y="33.55" width="212" height="292.45"/> + <g> + <rect class="i" x="315.75" y="185.55" width="164.75" height="97.45" rx="4.83"/> + <text class="j" transform="translate(327.95 212.91)">export <tspan class="d" x="58.81" y="0">function </tspan><tspan class="d"><tspan x="0" y="16.8">Checkbox() { </tspan><tspan x="0" y="33.6" xml:space="preserve"> ... </tspan><tspan x="0" y="50.4">}</tspan></tspan></text> + </g> + <g> + <rect class="i" x="315.75" y="77.55" width="164.75" height="97.45" rx="4.83"/> + <text class="j" transform="translate(327.95 104.91)">export <tspan class="d" x="58.81" y="0">function </tspan><tspan class="d"><tspan x="0" y="16.8">Slider() { </tspan><tspan x="0" y="33.6" xml:space="preserve"> ... </tspan><tspan x="0" y="50.4">}</tspan></tspan></text> + </g> + <text class="e" transform="translate(329.98 62.68)">Components.js</text> + </g> + <g> + <rect class="h" x="543.5" y="33.55" width="212" height="292.45"/> + <g> + <rect class="i" x="564.75" y="77.55" width="164.75" height="97.45" rx="4.83"/> + <text class="j" transform="translate(576.95 104.91)">export <tspan class="d" x="58.81" y="0">function </tspan><tspan class="d"><tspan x="0" y="16.8">Avatar() { </tspan><tspan x="0" y="33.6" xml:space="preserve"> ... </tspan><tspan x="0" y="50.4">}</tspan></tspan></text> + </g> + <text class="e" transform="translate(559.48 62.68)">MixedComponents.js</text> + <g> + <rect class="b" x="565.11" y="185.28" width="164.75" height="115.45" rx="4.83"/> + <text class="c" transform="translate(577.31 212.64)">export default <tspan class="d"><tspan x="0.56" y="16.8">function </tspan><tspan x="0" y="33.6">FriendsList() { </tspan><tspan x="0" y="50.4" xml:space="preserve"> ... </tspan><tspan x="0" y="67.2">}</tspan></tspan></text> + </g> + <text class="g" transform="translate(577.2 355.18)">named export(s)<tspan x="-26.49" y="21.6">and one default export</tspan></text> + </g> +</svg> diff --git a/beta/public/images/docs/illustrations/i_inputs1.png b/beta/public/images/docs/illustrations/i_inputs1.png new file mode 100644 index 000000000..35150b22d Binary files /dev/null and b/beta/public/images/docs/illustrations/i_inputs1.png differ diff --git a/beta/public/images/docs/illustrations/i_inputs2.png b/beta/public/images/docs/illustrations/i_inputs2.png new file mode 100644 index 000000000..f519af36e Binary files /dev/null and b/beta/public/images/docs/illustrations/i_inputs2.png differ diff --git a/beta/public/images/docs/illustrations/i_jsx.svg b/beta/public/images/docs/illustrations/i_jsx.svg new file mode 100644 index 000000000..629cad665 --- /dev/null +++ b/beta/public/images/docs/illustrations/i_jsx.svg @@ -0,0 +1,55 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="800" height="355" viewBox="0 0 800 355"> + <defs> + <style> + .a { + fill: #a19ad1; + } + + .b { + fill: #cdeffc; + } + + .c { + font-size: 14px; + fill: #18162d; +font-family: Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace ; + } + .c, .f { + font-weight: 700; + } + + .d { + fill: #81d8f7; + } + + .e { + fill: #20363e; + } + + .f { + font-size: 24px; + fill: #fff; + font-family: system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; + } + + .g { + letter-spacing: -0.06em; + } + </style> + </defs> + <g> + <g> + <rect class="a" x="424.5" y="39" width="290" height="273"/> + <rect class="b" x="446.51" y="173" width="247.76" height="84" rx="4.83"/> + <text class="c" transform="translate(457.95 126.36)">Form() {<tspan x="0" y="16.8" xml:space="preserve"> onClick() { ... }</tspan><tspan x="0" y="33.6" xml:space="preserve"> onSubmit() { ... }</tspan><tspan class="d"><tspan x="0" y="67.2" xml:space="preserve"> </tspan><tspan class="e" x="16.8" y="67.2"><form onSubmit></tspan></tspan><tspan class="e"><tspan x="0" y="84" xml:space="preserve"> <input onClick /></tspan><tspan x="0" y="100.8" xml:space="preserve"> <input onClick /></tspan><tspan x="0" y="117.6" xml:space="preserve"> </form> </tspan></tspan><tspan x="0" y="151.2">}</tspan></text> + <text class="f" transform="translate(526.64 76.12)">Form.js</text> + </g> + <g> + <rect class="a" x="76.5" y="39" width="290" height="273"/> + <rect class="b" x="127.66" y="223.25" width="218.35" height="35" rx="4.83"/> + <rect class="b" x="127.5" y="156" width="218.35" height="35" rx="4.83"/> + <text class="c" transform="translate(109.95 126.36)">Sidebar() {<tspan x="0" y="16.8" xml:space="preserve"> isLoggedIn() {</tspan><tspan class="d"><tspan x="0" y="50.4" xml:space="preserve"> </tspan><tspan class="e" x="33.61" y="50.4"><p>Welcome</p> </tspan></tspan><tspan x="0" y="84" xml:space="preserve"> } else {</tspan><tspan class="d"><tspan x="0" y="117.6" xml:space="preserve"> </tspan><tspan class="e" x="33.61" y="117.6"><Form /> </tspan></tspan><tspan x="0" y="151.2" xml:space="preserve"> }</tspan><tspan x="0" y="168">}</tspan></text> + <text class="f" transform="translate(165.29 76.12)">Sidebar.js</text> + </g> + </g> +</svg> diff --git a/beta/public/images/docs/illustrations/i_puritea-recipe.png b/beta/public/images/docs/illustrations/i_puritea-recipe.png new file mode 100644 index 000000000..46dba7b24 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_puritea-recipe.png differ diff --git a/beta/public/images/docs/illustrations/i_react-batching.png b/beta/public/images/docs/illustrations/i_react-batching.png new file mode 100644 index 000000000..e213ba3ad Binary files /dev/null and b/beta/public/images/docs/illustrations/i_react-batching.png differ diff --git a/beta/public/images/docs/illustrations/i_ref.png b/beta/public/images/docs/illustrations/i_ref.png new file mode 100644 index 000000000..c391e57e4 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_ref.png differ diff --git a/beta/public/images/docs/illustrations/i_render-and-commit1.png b/beta/public/images/docs/illustrations/i_render-and-commit1.png new file mode 100644 index 000000000..d62ba5806 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_render-and-commit1.png differ diff --git a/beta/public/images/docs/illustrations/i_render-and-commit2.png b/beta/public/images/docs/illustrations/i_render-and-commit2.png new file mode 100644 index 000000000..e01d0cce7 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_render-and-commit2.png differ diff --git a/beta/public/images/docs/illustrations/i_render-and-commit3.png b/beta/public/images/docs/illustrations/i_render-and-commit3.png new file mode 100644 index 000000000..bdf58c0e0 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_render-and-commit3.png differ diff --git a/beta/public/images/docs/illustrations/i_render1.png b/beta/public/images/docs/illustrations/i_render1.png new file mode 100644 index 000000000..3613c7691 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_render1.png differ diff --git a/beta/public/images/docs/illustrations/i_render2.png b/beta/public/images/docs/illustrations/i_render2.png new file mode 100644 index 000000000..ca53a7785 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_render2.png differ diff --git a/beta/public/images/docs/illustrations/i_render3.png b/beta/public/images/docs/illustrations/i_render3.png new file mode 100644 index 000000000..797860a7c Binary files /dev/null and b/beta/public/images/docs/illustrations/i_render3.png differ diff --git a/beta/public/images/docs/illustrations/i_rerender1.png b/beta/public/images/docs/illustrations/i_rerender1.png new file mode 100644 index 000000000..e94361e7b Binary files /dev/null and b/beta/public/images/docs/illustrations/i_rerender1.png differ diff --git a/beta/public/images/docs/illustrations/i_rerender2.png b/beta/public/images/docs/illustrations/i_rerender2.png new file mode 100644 index 000000000..6d85cae84 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_rerender2.png differ diff --git a/beta/public/images/docs/illustrations/i_rerender3.png b/beta/public/images/docs/illustrations/i_rerender3.png new file mode 100644 index 000000000..d589a9b93 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_rerender3.png differ diff --git a/beta/public/images/docs/illustrations/i_state-snapshot1.png b/beta/public/images/docs/illustrations/i_state-snapshot1.png new file mode 100644 index 000000000..69bbf2b72 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_state-snapshot1.png differ diff --git a/beta/public/images/docs/illustrations/i_state-snapshot2.png b/beta/public/images/docs/illustrations/i_state-snapshot2.png new file mode 100644 index 000000000..eea48a218 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_state-snapshot2.png differ diff --git a/beta/public/images/docs/illustrations/i_state-snapshot3.png b/beta/public/images/docs/illustrations/i_state-snapshot3.png new file mode 100644 index 000000000..a393528e2 Binary files /dev/null and b/beta/public/images/docs/illustrations/i_state-snapshot3.png differ diff --git a/beta/public/images/docs/implementation-notes-tree.png b/beta/public/images/docs/implementation-notes-tree.png new file mode 100644 index 000000000..923ef5d0d Binary files /dev/null and b/beta/public/images/docs/implementation-notes-tree.png differ diff --git a/beta/public/images/docs/javascript-jabber.png b/beta/public/images/docs/javascript-jabber.png new file mode 100644 index 000000000..57d63c41c Binary files /dev/null and b/beta/public/images/docs/javascript-jabber.png differ diff --git a/beta/public/images/docs/keyboard-focus.png b/beta/public/images/docs/keyboard-focus.png new file mode 100644 index 000000000..65a8dc300 Binary files /dev/null and b/beta/public/images/docs/keyboard-focus.png differ diff --git a/beta/public/images/docs/outerclick-with-keyboard.gif b/beta/public/images/docs/outerclick-with-keyboard.gif new file mode 100644 index 000000000..c82d299f8 Binary files /dev/null and b/beta/public/images/docs/outerclick-with-keyboard.gif differ diff --git a/beta/public/images/docs/outerclick-with-mouse.gif b/beta/public/images/docs/outerclick-with-mouse.gif new file mode 100644 index 000000000..e562e0324 Binary files /dev/null and b/beta/public/images/docs/outerclick-with-mouse.gif differ diff --git a/beta/public/images/docs/perf-dom.png b/beta/public/images/docs/perf-dom.png new file mode 100644 index 000000000..9a843c6ca Binary files /dev/null and b/beta/public/images/docs/perf-dom.png differ diff --git a/beta/public/images/docs/perf-exclusive.png b/beta/public/images/docs/perf-exclusive.png new file mode 100644 index 000000000..a8ad5003c Binary files /dev/null and b/beta/public/images/docs/perf-exclusive.png differ diff --git a/beta/public/images/docs/perf-inclusive.png b/beta/public/images/docs/perf-inclusive.png new file mode 100644 index 000000000..e46b370aa Binary files /dev/null and b/beta/public/images/docs/perf-inclusive.png differ diff --git a/beta/public/images/docs/perf-wasted.png b/beta/public/images/docs/perf-wasted.png new file mode 100644 index 000000000..c73131818 Binary files /dev/null and b/beta/public/images/docs/perf-wasted.png differ diff --git a/beta/public/images/docs/react-devtools-extension.png b/beta/public/images/docs/react-devtools-extension.png new file mode 100644 index 000000000..6ea2aad8d Binary files /dev/null and b/beta/public/images/docs/react-devtools-extension.png differ diff --git a/beta/public/images/docs/react-devtools-standalone.png b/beta/public/images/docs/react-devtools-standalone.png new file mode 100644 index 000000000..07da74137 Binary files /dev/null and b/beta/public/images/docs/react-devtools-standalone.png differ diff --git a/beta/public/images/docs/react-devtools-state.gif b/beta/public/images/docs/react-devtools-state.gif new file mode 100644 index 000000000..c9ff6cd35 Binary files /dev/null and b/beta/public/images/docs/react-devtools-state.gif differ diff --git a/beta/public/images/docs/react-devtools-usedebugvalue.png b/beta/public/images/docs/react-devtools-usedebugvalue.png new file mode 100644 index 000000000..854044318 Binary files /dev/null and b/beta/public/images/docs/react-devtools-usedebugvalue.png differ diff --git a/beta/public/images/docs/s_thinking-in-react_ui.png b/beta/public/images/docs/s_thinking-in-react_ui.png new file mode 100644 index 000000000..21802352c Binary files /dev/null and b/beta/public/images/docs/s_thinking-in-react_ui.png differ diff --git a/beta/public/images/docs/s_thinking-in-react_ui_outline.png b/beta/public/images/docs/s_thinking-in-react_ui_outline.png new file mode 100644 index 000000000..d38f3e19f Binary files /dev/null and b/beta/public/images/docs/s_thinking-in-react_ui_outline.png differ diff --git a/beta/public/images/docs/should-component-update.png b/beta/public/images/docs/should-component-update.png new file mode 100644 index 000000000..590af60b8 Binary files /dev/null and b/beta/public/images/docs/should-component-update.png differ diff --git a/beta/public/images/docs/source/i_browser-paint.psd b/beta/public/images/docs/source/i_browser-paint.psd new file mode 100644 index 000000000..bb541e889 Binary files /dev/null and b/beta/public/images/docs/source/i_browser-paint.psd differ diff --git a/beta/public/images/docs/source/i_children-prop.psd b/beta/public/images/docs/source/i_children-prop.psd new file mode 100644 index 000000000..2577c0aa0 Binary files /dev/null and b/beta/public/images/docs/source/i_children-prop.psd differ diff --git a/beta/public/images/docs/source/i_declarative-ui-programming.psd b/beta/public/images/docs/source/i_declarative-ui-programming.psd new file mode 100644 index 000000000..3b1a9a4d1 Binary files /dev/null and b/beta/public/images/docs/source/i_declarative-ui-programming.psd differ diff --git a/beta/public/images/docs/source/i_imperative-ui-programming.psd b/beta/public/images/docs/source/i_imperative-ui-programming.psd new file mode 100644 index 000000000..c1511b022 Binary files /dev/null and b/beta/public/images/docs/source/i_imperative-ui-programming.psd differ diff --git a/beta/public/images/docs/source/i_keys-in-trees.psd b/beta/public/images/docs/source/i_keys-in-trees.psd new file mode 100644 index 000000000..bab04dc08 Binary files /dev/null and b/beta/public/images/docs/source/i_keys-in-trees.psd differ diff --git a/beta/public/images/docs/source/i_puritea-recipe.psd b/beta/public/images/docs/source/i_puritea-recipe.psd new file mode 100644 index 000000000..72a33ab30 Binary files /dev/null and b/beta/public/images/docs/source/i_puritea-recipe.psd differ diff --git a/beta/public/images/docs/source/i_react-batching.psd b/beta/public/images/docs/source/i_react-batching.psd new file mode 100644 index 000000000..88bcd617b Binary files /dev/null and b/beta/public/images/docs/source/i_react-batching.psd differ diff --git a/beta/public/images/docs/source/i_react-is-blind-to-ui-swap.psd b/beta/public/images/docs/source/i_react-is-blind-to-ui-swap.psd new file mode 100644 index 000000000..5e422ba63 Binary files /dev/null and b/beta/public/images/docs/source/i_react-is-blind-to-ui-swap.psd differ diff --git a/beta/public/images/docs/source/i_ref.psd b/beta/public/images/docs/source/i_ref.psd new file mode 100644 index 000000000..12349b431 Binary files /dev/null and b/beta/public/images/docs/source/i_ref.psd differ diff --git a/beta/public/images/docs/source/i_render-commit.psd b/beta/public/images/docs/source/i_render-commit.psd new file mode 100644 index 000000000..99f62802a Binary files /dev/null and b/beta/public/images/docs/source/i_render-commit.psd differ diff --git a/beta/public/images/docs/source/i_rerender.psd b/beta/public/images/docs/source/i_rerender.psd new file mode 100644 index 000000000..9dc83d5ed Binary files /dev/null and b/beta/public/images/docs/source/i_rerender.psd differ diff --git a/beta/public/images/docs/source/i_rerendering.psd b/beta/public/images/docs/source/i_rerendering.psd new file mode 100644 index 000000000..2ba1cba58 Binary files /dev/null and b/beta/public/images/docs/source/i_rerendering.psd differ diff --git a/beta/public/images/docs/source/i_state-snapshot.psd b/beta/public/images/docs/source/i_state-snapshot.psd new file mode 100644 index 000000000..b8d54ddcc Binary files /dev/null and b/beta/public/images/docs/source/i_state-snapshot.psd differ diff --git a/beta/public/images/docs/source/s_inputs.psd b/beta/public/images/docs/source/s_inputs.psd new file mode 100644 index 000000000..60354742a Binary files /dev/null and b/beta/public/images/docs/source/s_inputs.psd differ diff --git a/beta/public/images/docs/source/s_ui-snapshot.psd b/beta/public/images/docs/source/s_ui-snapshot.psd new file mode 100644 index 000000000..41c65f8fe Binary files /dev/null and b/beta/public/images/docs/source/s_ui-snapshot.psd differ diff --git a/beta/public/images/docs/thinking-in-react-tagtree.png b/beta/public/images/docs/thinking-in-react-tagtree.png new file mode 100644 index 000000000..3d4db2d23 Binary files /dev/null and b/beta/public/images/docs/thinking-in-react-tagtree.png differ diff --git a/beta/public/images/external.png b/beta/public/images/external.png new file mode 100644 index 000000000..748a27e44 Binary files /dev/null and b/beta/public/images/external.png differ diff --git a/beta/public/images/external_2x.png b/beta/public/images/external_2x.png new file mode 100644 index 000000000..66230854d Binary files /dev/null and b/beta/public/images/external_2x.png differ diff --git a/beta/public/images/g_arrow.png b/beta/public/images/g_arrow.png new file mode 100644 index 000000000..36258add5 Binary files /dev/null and b/beta/public/images/g_arrow.png differ diff --git a/beta/public/images/history.png b/beta/public/images/history.png new file mode 100644 index 000000000..4ca56a866 Binary files /dev/null and b/beta/public/images/history.png differ diff --git a/beta/public/images/noise.png b/beta/public/images/noise.png new file mode 100644 index 000000000..698f924f4 Binary files /dev/null and b/beta/public/images/noise.png differ diff --git a/beta/public/images/og-blog.png b/beta/public/images/og-blog.png new file mode 100644 index 000000000..33407683b Binary files /dev/null and b/beta/public/images/og-blog.png differ diff --git a/beta/public/images/og-community.png b/beta/public/images/og-community.png new file mode 100644 index 000000000..f68af3c0f Binary files /dev/null and b/beta/public/images/og-community.png differ diff --git a/beta/public/images/og-home.png b/beta/public/images/og-home.png new file mode 100644 index 000000000..c1f759ce3 Binary files /dev/null and b/beta/public/images/og-home.png differ diff --git a/beta/public/images/og-learn.png b/beta/public/images/og-learn.png new file mode 100644 index 000000000..d1f7d0801 Binary files /dev/null and b/beta/public/images/og-learn.png differ diff --git a/beta/public/images/og-reference.png b/beta/public/images/og-reference.png new file mode 100644 index 000000000..7fcf87147 Binary files /dev/null and b/beta/public/images/og-reference.png differ diff --git a/beta/public/images/og-unknown.png b/beta/public/images/og-unknown.png new file mode 100644 index 000000000..c1f759ce3 Binary files /dev/null and b/beta/public/images/og-unknown.png differ diff --git a/beta/public/images/oss_logo.png b/beta/public/images/oss_logo.png new file mode 100644 index 000000000..3f376bee9 Binary files /dev/null and b/beta/public/images/oss_logo.png differ diff --git a/beta/public/images/search.png b/beta/public/images/search.png new file mode 100644 index 000000000..151377693 Binary files /dev/null and b/beta/public/images/search.png differ diff --git a/beta/public/images/team/acdlite.jpg b/beta/public/images/team/acdlite.jpg new file mode 100644 index 000000000..19ddb901f Binary files /dev/null and b/beta/public/images/team/acdlite.jpg differ diff --git a/beta/public/images/team/gaearon.jpg b/beta/public/images/team/gaearon.jpg new file mode 100644 index 000000000..dff5a5173 Binary files /dev/null and b/beta/public/images/team/gaearon.jpg differ diff --git a/beta/public/images/team/jasonbonta.jpg b/beta/public/images/team/jasonbonta.jpg new file mode 100644 index 000000000..139eb5ece Binary files /dev/null and b/beta/public/images/team/jasonbonta.jpg differ diff --git a/beta/public/images/team/joe.jpg b/beta/public/images/team/joe.jpg new file mode 100644 index 000000000..7eb702e79 Binary files /dev/null and b/beta/public/images/team/joe.jpg differ diff --git a/beta/public/images/team/josh.jpg b/beta/public/images/team/josh.jpg new file mode 100644 index 000000000..ebed23562 Binary files /dev/null and b/beta/public/images/team/josh.jpg differ diff --git a/beta/public/images/team/lauren.jpg b/beta/public/images/team/lauren.jpg new file mode 100644 index 000000000..1485cf8ff Binary files /dev/null and b/beta/public/images/team/lauren.jpg differ diff --git a/beta/public/images/team/lunaruan.jpg b/beta/public/images/team/lunaruan.jpg new file mode 100644 index 000000000..f6de76d4e Binary files /dev/null and b/beta/public/images/team/lunaruan.jpg differ diff --git a/beta/public/images/team/mofei-zhang.png b/beta/public/images/team/mofei-zhang.png new file mode 100644 index 000000000..9f957ada3 Binary files /dev/null and b/beta/public/images/team/mofei-zhang.png differ diff --git a/beta/public/images/team/rickhanlonii.jpg b/beta/public/images/team/rickhanlonii.jpg new file mode 100644 index 000000000..9165db5bc Binary files /dev/null and b/beta/public/images/team/rickhanlonii.jpg differ diff --git a/beta/public/images/team/sam.jpg b/beta/public/images/team/sam.jpg new file mode 100644 index 000000000..f73474b91 Binary files /dev/null and b/beta/public/images/team/sam.jpg differ diff --git a/beta/public/images/team/sathya.jpg b/beta/public/images/team/sathya.jpg new file mode 100644 index 000000000..3d8ca7d58 Binary files /dev/null and b/beta/public/images/team/sathya.jpg differ diff --git a/beta/public/images/team/sebmarkbage.jpg b/beta/public/images/team/sebmarkbage.jpg new file mode 100644 index 000000000..b73e13cd7 Binary files /dev/null and b/beta/public/images/team/sebmarkbage.jpg differ diff --git a/beta/public/images/team/sebsilbermann.jpg b/beta/public/images/team/sebsilbermann.jpg new file mode 100644 index 000000000..f6fa04b37 Binary files /dev/null and b/beta/public/images/team/sebsilbermann.jpg differ diff --git a/beta/public/images/team/seth.jpg b/beta/public/images/team/seth.jpg new file mode 100644 index 000000000..c665a0b00 Binary files /dev/null and b/beta/public/images/team/seth.jpg differ diff --git a/beta/public/images/team/sophiebits.jpg b/beta/public/images/team/sophiebits.jpg new file mode 100644 index 000000000..da5548aca Binary files /dev/null and b/beta/public/images/team/sophiebits.jpg differ diff --git a/beta/public/images/team/tianyu.jpg b/beta/public/images/team/tianyu.jpg new file mode 100644 index 000000000..aeb6ed9fa Binary files /dev/null and b/beta/public/images/team/tianyu.jpg differ diff --git a/beta/public/images/team/yuzhi.jpg b/beta/public/images/team/yuzhi.jpg new file mode 100644 index 000000000..c3f4175d7 Binary files /dev/null and b/beta/public/images/team/yuzhi.jpg differ diff --git a/beta/public/images/tutorial/board-filled-with-ones.png b/beta/public/images/tutorial/board-filled-with-ones.png new file mode 100644 index 000000000..d4ed6805a Binary files /dev/null and b/beta/public/images/tutorial/board-filled-with-ones.png differ diff --git a/beta/public/images/tutorial/board-filled-with-value.png b/beta/public/images/tutorial/board-filled-with-value.png new file mode 100644 index 000000000..f3badc7f6 Binary files /dev/null and b/beta/public/images/tutorial/board-filled-with-value.png differ diff --git a/beta/public/images/tutorial/codesandbox-devtools.png b/beta/public/images/tutorial/codesandbox-devtools.png new file mode 100644 index 000000000..3ef9b2f8f Binary files /dev/null and b/beta/public/images/tutorial/codesandbox-devtools.png differ diff --git a/beta/public/images/tutorial/devtools-select.gif b/beta/public/images/tutorial/devtools-select.gif new file mode 100644 index 000000000..dd1e1aa61 Binary files /dev/null and b/beta/public/images/tutorial/devtools-select.gif differ diff --git a/beta/public/images/tutorial/empty-board.png b/beta/public/images/tutorial/empty-board.png new file mode 100644 index 000000000..f10f05046 Binary files /dev/null and b/beta/public/images/tutorial/empty-board.png differ diff --git a/beta/public/images/tutorial/nine-x-filled-squares.png b/beta/public/images/tutorial/nine-x-filled-squares.png new file mode 100644 index 000000000..164334e6b Binary files /dev/null and b/beta/public/images/tutorial/nine-x-filled-squares.png differ diff --git a/beta/public/images/tutorial/number-filled-board.png b/beta/public/images/tutorial/number-filled-board.png new file mode 100644 index 000000000..f510d00bb Binary files /dev/null and b/beta/public/images/tutorial/number-filled-board.png differ diff --git a/beta/public/images/tutorial/o-replaces-x.gif b/beta/public/images/tutorial/o-replaces-x.gif new file mode 100644 index 000000000..cb0c63691 Binary files /dev/null and b/beta/public/images/tutorial/o-replaces-x.gif differ diff --git a/beta/public/images/tutorial/react-starter-code-codesandbox.png b/beta/public/images/tutorial/react-starter-code-codesandbox.png new file mode 100644 index 000000000..66fb0fdf1 Binary files /dev/null and b/beta/public/images/tutorial/react-starter-code-codesandbox.png differ diff --git a/beta/public/images/tutorial/tictac-adding-x-s.gif b/beta/public/images/tutorial/tictac-adding-x-s.gif new file mode 100644 index 000000000..0f17188c8 Binary files /dev/null and b/beta/public/images/tutorial/tictac-adding-x-s.gif differ diff --git a/beta/public/images/tutorial/two-x-filled-squares.png b/beta/public/images/tutorial/two-x-filled-squares.png new file mode 100644 index 000000000..35638bc0d Binary files /dev/null and b/beta/public/images/tutorial/two-x-filled-squares.png differ diff --git a/beta/public/images/tutorial/x-filled-square.png b/beta/public/images/tutorial/x-filled-square.png new file mode 100644 index 000000000..2b42baad9 Binary files /dev/null and b/beta/public/images/tutorial/x-filled-square.png differ diff --git a/beta/public/js/jsfiddle-integration-babel.js b/beta/public/js/jsfiddle-integration-babel.js new file mode 100644 index 000000000..006c79c8a --- /dev/null +++ b/beta/public/js/jsfiddle-integration-babel.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// Do not delete or move this file. +// Many fiddles reference it so we have to keep it here. +(function() { + var tag = document.querySelector( + 'script[type="application/javascript;version=1.7"]' + ); + if (!tag || tag.textContent.indexOf('window.onload=function(){') !== -1) { + alert('Bad JSFiddle configuration, please fork the original React JSFiddle'); + } + tag.setAttribute('type', 'text/babel'); + tag.textContent = tag.textContent.replace(/^\/\/<!\[CDATA\[/, ''); +})(); diff --git a/beta/public/js/jsfiddle-integration.js b/beta/public/js/jsfiddle-integration.js new file mode 100644 index 000000000..fcf09e43f --- /dev/null +++ b/beta/public/js/jsfiddle-integration.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// Do not delete or move this file. +// Many fiddles reference it so we have to keep it here. +(function() { + var tag = document.querySelector( + 'script[type="application/javascript;version=1.7"]' + ); + if (!tag || tag.textContent.indexOf('window.onload=function(){') !== -1) { + alert('Bad JSFiddle configuration, please fork the original React JSFiddle'); + } + tag.setAttribute('type', 'text/jsx;harmony=true'); + tag.textContent = tag.textContent.replace(/^\/\/<!\[CDATA\[/, ''); +})(); diff --git a/beta/public/logo-180x180.png b/beta/public/logo-180x180.png new file mode 100644 index 000000000..2b1f8704a Binary files /dev/null and b/beta/public/logo-180x180.png differ diff --git a/beta/public/logo-512x512.png b/beta/public/logo-512x512.png new file mode 100644 index 000000000..e7b803863 Binary files /dev/null and b/beta/public/logo-512x512.png differ diff --git a/beta/public/logo-og.png b/beta/public/logo-og.png new file mode 100644 index 000000000..d2e830d37 Binary files /dev/null and b/beta/public/logo-og.png differ diff --git a/beta/public/robots.txt b/beta/public/robots.txt new file mode 100644 index 000000000..eb0536286 --- /dev/null +++ b/beta/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/beta/scripts/downloadFonts.js b/beta/scripts/downloadFonts.js new file mode 100644 index 000000000..098513ac9 --- /dev/null +++ b/beta/scripts/downloadFonts.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +const {execSync} = require('child_process'); + +// So that we don't need to check them into the repo. +// Serving them from the same domain is better for perf so do this on deploy. +execSync( + 'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2' +); +execSync( + 'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2' +); +execSync( + 'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2' +); diff --git a/beta/scripts/headingIDHelpers/generateHeadingIDs.js b/beta/scripts/headingIDHelpers/generateHeadingIDs.js new file mode 100644 index 000000000..40925d444 --- /dev/null +++ b/beta/scripts/headingIDHelpers/generateHeadingIDs.js @@ -0,0 +1,110 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// To do: Make this ESM. +// To do: properly check heading numbers (headings with the same text get +// numbered, this script doesn’t check that). + +const assert = require('assert'); +const fs = require('fs'); +const GithubSlugger = require('github-slugger'); +const walk = require('./walk'); + +let modules; + +function stripLinks(line) { + return line.replace(/\[([^\]]+)\]\([^)]+\)/, (match, p1) => p1); +} + +function addHeaderID(line, slugger) { + // check if we're a header at all + if (!line.startsWith('#')) { + return line; + } + + const match = + /^(#+\s+)(.+?)(\s*\{(?:\/\*|#)([^\}\*\/]+)(?:\*\/)?\}\s*)?$/.exec(line); + const before = match[1] + match[2]; + const proc = modules + .unified() + .use(modules.remarkParse) + .use(modules.remarkSlug); + const tree = proc.runSync(proc.parse(before)); + const head = tree.children[0]; + assert( + head && head.type === 'heading', + 'expected `' + + before + + '` to be a heading, is it using a normal space after `#`?' + ); + const autoId = head.data.id; + const existingId = match[4]; + const id = existingId || autoId; + // Ignore numbers: + const cleanExisting = existingId + ? existingId.replace(/-\d+$/, '') + : undefined; + const cleanAuto = autoId.replace(/-\d+$/, ''); + + if (cleanExisting && cleanExisting !== cleanAuto) { + console.log( + 'Note: heading `%s` has a different ID (`%s`) than what GH generates for it: `%s`:', + before, + existingId, + autoId + ); + } + + return match[1] + match[2] + ' {/*' + id + '*/}'; +} + +function addHeaderIDs(lines) { + // Sluggers should be per file + const slugger = new GithubSlugger(); + let inCode = false; + const results = []; + lines.forEach((line) => { + // Ignore code blocks + if (line.startsWith('```')) { + inCode = !inCode; + results.push(line); + return; + } + if (inCode) { + results.push(line); + return; + } + + results.push(addHeaderID(line, slugger)); + }); + return results; +} + +async function main(paths) { + paths = paths.length === 0 ? ['src/content'] : paths; + + const [unifiedMod, remarkParseMod, remarkSlugMod] = await Promise.all([ + import('unified'), + import('remark-parse'), + import('remark-slug'), + ]); + const unified = unifiedMod.unified; + const remarkParse = remarkParseMod.default; + const remarkSlug = remarkSlugMod.default; + modules = {unified, remarkParse, remarkSlug}; + const files = paths.map((path) => [...walk(path)]).flat(); + + files.forEach((file) => { + if (!(file.endsWith('.md') || file.endsWith('.mdx'))) { + return; + } + + const content = fs.readFileSync(file, 'utf8'); + const lines = content.split('\n'); + const updatedLines = addHeaderIDs(lines); + fs.writeFileSync(file, updatedLines.join('\n')); + }); +} + +module.exports = main; diff --git a/beta/scripts/headingIDHelpers/validateHeadingIDs.js b/beta/scripts/headingIDHelpers/validateHeadingIDs.js new file mode 100644 index 000000000..c3cf1ab8c --- /dev/null +++ b/beta/scripts/headingIDHelpers/validateHeadingIDs.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ +const fs = require('fs'); +const walk = require('./walk'); + +/** + * Validate if there is a custom heading id and exit if there isn't a heading + * @param {string} line + * @returns + */ +function validateHeaderId(line) { + if (!line.startsWith('#')) { + return; + } + + const match = /\{\/\*(.*?)\*\/}/.exec(line); + const id = match; + if (!id) { + console.error('Run yarn fix-headings to generate headings.'); + process.exit(1); + } +} + +/** + * Loops through the lines to skip code blocks + * @param {Array<string>} lines + */ +function validateHeaderIds(lines) { + let inCode = false; + const results = []; + lines.forEach((line) => { + // Ignore code blocks + if (line.startsWith('```')) { + inCode = !inCode; + + results.push(line); + return; + } + if (inCode) { + results.push(line); + return; + } + validateHeaderId(line); + }); +} +/** + * paths are basically array of path for which we have to validate heading IDs + * @param {Array<string>} paths + */ +async function main(paths) { + paths = paths.length === 0 ? ['src/content'] : paths; + const files = paths.map((path) => [...walk(path)]).flat(); + + files.forEach((file) => { + if (!(file.endsWith('.md') || file.endsWith('.mdx'))) { + return; + } + + const content = fs.readFileSync(file, 'utf8'); + const lines = content.split('\n'); + validateHeaderIds(lines); + }); +} + +module.exports = main; diff --git a/beta/scripts/headingIDHelpers/walk.js b/beta/scripts/headingIDHelpers/walk.js new file mode 100644 index 000000000..721274e09 --- /dev/null +++ b/beta/scripts/headingIDHelpers/walk.js @@ -0,0 +1,24 @@ +const fs = require('fs'); + +module.exports = function walk(dir) { + let results = []; + /** + * If the param is a directory we can return the file + */ + if(dir.includes('md')){ + return [dir]; + } + const list = fs.readdirSync(dir); + list.forEach(function (file) { + file = dir + '/' + file; + const stat = fs.statSync(file); + if (stat && stat.isDirectory()) { + /* Recurse into a subdirectory */ + results = results.concat(walk(file)); + } else { + /* Is a file */ + results.push(file); + } + }); + return results; +}; diff --git a/beta/scripts/headingIdLinter.js b/beta/scripts/headingIdLinter.js new file mode 100644 index 000000000..037e4945f --- /dev/null +++ b/beta/scripts/headingIdLinter.js @@ -0,0 +1,16 @@ +const validateHeaderIds = require('./headingIDHelpers/validateHeadingIDs'); +const generateHeadingIds = require('./headingIDHelpers/generateHeadingIDs'); + +/** + * yarn lint-heading-ids --> Checks all files and causes an error if heading ID is missing + * yarn lint-heading-ids --fix --> Fixes all markdown file's heading IDs + * yarn lint-heading-ids path/to/markdown.md --> Checks that particular file for missing heading ID (path can denote a directory or particular file) + * yarn lint-heading-ids --fix path/to/markdown.md --> Fixes that particular file's markdown IDs (path can denote a directory or particular file) +*/ + +const markdownPaths = process.argv.slice(2); +if (markdownPaths.includes('--fix')) { + generateHeadingIds(markdownPaths.filter((path) => path !== '--fix')); +} else { + validateHeaderIds(markdownPaths); +} diff --git a/beta/src/components/Breadcrumbs.tsx b/beta/src/components/Breadcrumbs.tsx new file mode 100644 index 000000000..96b50cd30 --- /dev/null +++ b/beta/src/components/Breadcrumbs.tsx @@ -0,0 +1,43 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Fragment} from 'react'; +import Link from 'next/link'; +import type {RouteItem} from 'components/Layout/getRouteMeta'; + +function Breadcrumbs({breadcrumbs}: {breadcrumbs: RouteItem[]}) { + return ( + <div className="flex flex-wrap"> + {breadcrumbs.map( + (crumb, i) => + crumb.path && ( + <div className="flex mb-3 mt-0.5 items-center" key={i}> + <Fragment key={crumb.path}> + <Link href={crumb.path}> + <a className="text-link dark:text-link-dark text-sm tracking-wide font-bold uppercase mr-1 hover:underline"> + {crumb.title} + </a> + </Link> + <span className="inline-block mr-1 text-link dark:text-link-dark text-lg"> + <svg + width="20" + height="20" + viewBox="0 0 20 20" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M6.86612 13.6161C6.37796 14.1043 6.37796 14.8957 6.86612 15.3839C7.35427 15.872 8.14572 15.872 8.63388 15.3839L13.1339 10.8839C13.622 10.3957 13.622 9.60428 13.1339 9.11612L8.63388 4.61612C8.14572 4.12797 7.35427 4.12797 6.86612 4.61612C6.37796 5.10428 6.37796 5.89573 6.86612 6.38388L10.4822 10L6.86612 13.6161Z" + fill="currentColor" + /> + </svg> + </span> + </Fragment> + </div> + ) + )} + </div> + ); +} + +export default Breadcrumbs; diff --git a/beta/src/components/Button.tsx b/beta/src/components/Button.tsx new file mode 100644 index 000000000..9d4d0cf2b --- /dev/null +++ b/beta/src/components/Button.tsx @@ -0,0 +1,51 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import cn from 'classnames'; + +interface ButtonProps { + children: React.ReactNode; + onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; + active: boolean; + className?: string; + style?: Record<string, string>; +} + +export function Button({ + children, + onClick, + active, + className, + style, +}: ButtonProps) { + return ( + <button + style={style} + onMouseDown={(evt) => { + evt.preventDefault(); + evt.stopPropagation(); + }} + onClick={onClick} + className={cn( + className, + 'text-base leading-tight font-bold border rounded-lg py-2 px-4 focus:ring-1 focus:ring-offset-2 focus:ring-link active:bg-link active:border-link active:text-white active:ring-0 active:ring-offset-0 outline-none inline-flex items-center my-1', + { + 'bg-link border-link text-white hover:bg-link focus:bg-link active:bg-link': + active, + 'bg-transparent text-secondary dark:text-secondary-dark bg-secondary-button dark:bg-secondary-button-dark hover:text-link focus:text-link border-transparent': + !active, + } + )}> + {children} + </button> + ); +} + +Button.defaultProps = { + active: false, + style: {}, +}; + +export default Button; diff --git a/beta/src/components/ButtonLink.tsx b/beta/src/components/ButtonLink.tsx new file mode 100644 index 000000000..b3fe73ad9 --- /dev/null +++ b/beta/src/components/ButtonLink.tsx @@ -0,0 +1,45 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import cn from 'classnames'; +import NextLink from 'next/link'; + +interface ButtonLinkProps { + size?: 'md' | 'lg'; + type?: 'primary' | 'secondary'; + label?: string; + target?: '_self' | '_blank'; +} + +function ButtonLink({ + href, + className, + children, + type = 'primary', + size = 'md', + label, + target = '_self', + ...props +}: JSX.IntrinsicElements['a'] & ButtonLinkProps) { + const classes = cn( + className, + 'inline-flex font-bold items-center border-2 border-transparent outline-none focus:ring-1 focus:ring-offset-2 focus:ring-link active:bg-link active:text-white active:ring-0 active:ring-offset-0 leading-normal', + { + 'bg-link text-white hover:bg-opacity-80': type === 'primary', + 'bg-secondary-button dark:bg-secondary-button-dark text-primary dark:text-primary-dark hover:text-link focus:bg-link focus:text-white focus:border-link focus:border-2': + type === 'secondary', + 'text-lg rounded-lg p-4': size === 'lg', + 'text-base rounded-lg px-4 py-1.5': size === 'md', + } + ); + return ( + <NextLink href={href as string}> + <a className={classes} {...props} aria-label={label} target={target}> + {children} + </a> + </NextLink> + ); +} + +export default ButtonLink; diff --git a/beta/src/components/DocsFooter.tsx b/beta/src/components/DocsFooter.tsx new file mode 100644 index 000000000..3932d0910 --- /dev/null +++ b/beta/src/components/DocsFooter.tsx @@ -0,0 +1,90 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import NextLink from 'next/link'; +import {memo} from 'react'; +import cn from 'classnames'; +import {IconNavArrow} from './Icon/IconNavArrow'; +import type {RouteMeta} from './Layout/getRouteMeta'; + +export type DocsPageFooterProps = Pick< + RouteMeta, + 'route' | 'nextRoute' | 'prevRoute' +>; + +function areEqual(prevProps: DocsPageFooterProps, props: DocsPageFooterProps) { + return prevProps.route?.path === props.route?.path; +} + +export const DocsPageFooter = memo<DocsPageFooterProps>( + function DocsPageFooter({nextRoute, prevRoute, route}) { + if (!route || route?.heading) { + return null; + } + + return ( + <> + {prevRoute?.path || nextRoute?.path ? ( + <> + <div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-4 py-4 md:py-12"> + {prevRoute?.path ? ( + <FooterLink + type="Previous" + title={prevRoute.title} + href={prevRoute.path} + /> + ) : ( + <div /> + )} + + {nextRoute?.path ? ( + <FooterLink + type="Next" + title={nextRoute.title} + href={nextRoute.path} + /> + ) : ( + <div /> + )} + </div> + </> + ) : null} + </> + ); + }, + areEqual +); + +function FooterLink({ + href, + title, + type, +}: { + href: string; + title: string; + type: 'Previous' | 'Next'; +}) { + return ( + <NextLink href={href}> + <a + className={cn( + 'flex gap-x-4 md:gap-x-6 items-center w-full md:w-80 px-4 md:px-5 py-6 border-2 border-transparent text-base leading-base text-link dark:text-link-dark rounded-lg group focus:text-link dark:focus:text-link-dark focus:bg-highlight focus:border-link dark:focus:bg-highlight-dark dark:focus:border-link-dark focus:border-opacity-100 focus:border-2 focus:ring-1 focus:ring-offset-4 focus:ring-blue-40 active:ring-0 active:ring-offset-0 hover:bg-gray-5 dark:hover:bg-gray-80', + { + 'flex-row-reverse justify-self-end text-right': type === 'Next', + } + )}> + <IconNavArrow + className="text-gray-30 dark:text-gray-50 inline group-focus:text-link dark:group-focus:text-link-dark" + displayDirection={type === 'Previous' ? 'left' : 'right'} + /> + <span> + <span className="block no-underline text-sm tracking-wide text-secondary dark:text-secondary-dark uppercase font-bold group-focus:text-link dark:group-focus:text-link-dark group-focus:text-opacity-100"> + {type} + </span> + <span className="block text-lg group-hover:underline">{title}</span> + </span> + </a> + </NextLink> + ); +} diff --git a/beta/src/components/ExternalLink.tsx b/beta/src/components/ExternalLink.tsx new file mode 100644 index 000000000..38b1f2c5f --- /dev/null +++ b/beta/src/components/ExternalLink.tsx @@ -0,0 +1,16 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +export function ExternalLink({ + href, + target, + children, + ...props +}: JSX.IntrinsicElements['a']) { + return ( + <a href={href} target={target ?? '_blank'} rel="noopener" {...props}> + {children} + </a> + ); +} diff --git a/beta/src/components/Icon/IconArrow.tsx b/beta/src/components/Icon/IconArrow.tsx new file mode 100644 index 000000000..53bde1326 --- /dev/null +++ b/beta/src/components/Icon/IconArrow.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import cn from 'classnames'; + +export const IconArrow = memo< + JSX.IntrinsicElements['svg'] & { + displayDirection: 'left' | 'right' | 'up' | 'down'; + } +>(function IconArrow({displayDirection, className, ...rest}) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="1.33em" + height="1.33em" + fill="currentColor" + {...rest} + className={cn(className, { + 'rotate-180': displayDirection === 'right', + })}> + <path fill="none" d="M0 0h24v24H0z" /> + <path d="M7.828 11H20v2H7.828l5.364 5.364-1.414 1.414L4 12l7.778-7.778 1.414 1.414z" /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconArrowSmall.tsx b/beta/src/components/Icon/IconArrowSmall.tsx new file mode 100644 index 000000000..cf85988d2 --- /dev/null +++ b/beta/src/components/Icon/IconArrowSmall.tsx @@ -0,0 +1,31 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import cn from 'classnames'; + +export const IconArrowSmall = memo< + JSX.IntrinsicElements['svg'] & { + displayDirection: 'left' | 'right' | 'up' | 'down'; + } +>(function IconArrowSmall({displayDirection, className, ...rest}) { + const classes = cn(className, { + 'rotate-180': displayDirection === 'left', + 'rotate-90': displayDirection === 'down', + }); + return ( + <svg + width="1em" + height="1em" + viewBox="0 0 20 20" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={classes} + {...rest}> + <path + d="M6.86612 13.6161C6.37796 14.1043 6.37796 14.8957 6.86612 15.3839C7.35427 15.872 8.14572 15.872 8.63388 15.3839L13.1339 10.8839C13.622 10.3957 13.622 9.60428 13.1339 9.11612L8.63388 4.61612C8.14572 4.12797 7.35427 4.12797 6.86612 4.61612C6.37796 5.10428 6.37796 5.89573 6.86612 6.38388L10.4822 10L6.86612 13.6161Z" + fill="currentColor"></path> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconChevron.tsx b/beta/src/components/Icon/IconChevron.tsx new file mode 100644 index 000000000..1184d77d2 --- /dev/null +++ b/beta/src/components/Icon/IconChevron.tsx @@ -0,0 +1,40 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import cn from 'classnames'; + +export const IconChevron = memo< + JSX.IntrinsicElements['svg'] & { + displayDirection: 'up' | 'down' | 'left' | 'right'; + } +>(function IconChevron({className, displayDirection}) { + const classes = cn( + { + 'rotate-0': displayDirection === 'down', + 'rotate-90': displayDirection === 'left', + 'rotate-180': displayDirection === 'up', + '-rotate-90': displayDirection === 'right', + }, + className + ); + return ( + <svg + className={classes} + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 20 20"> + <g fill="none" fillRule="evenodd" transform="translate(-446 -398)"> + <path + fill="currentColor" + fillRule="nonzero" + d="M95.8838835,240.366117 C95.3957281,239.877961 94.6042719,239.877961 94.1161165,240.366117 C93.6279612,240.854272 93.6279612,241.645728 94.1161165,242.133883 L98.6161165,246.633883 C99.1042719,247.122039 99.8957281,247.122039 100.383883,246.633883 L104.883883,242.133883 C105.372039,241.645728 105.372039,240.854272 104.883883,240.366117 C104.395728,239.877961 103.604272,239.877961 103.116117,240.366117 L99.5,243.982233 L95.8838835,240.366117 Z" + transform="translate(356.5 164.5)" + /> + <polygon points="446 418 466 418 466 398 446 398" /> + </g> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconClose.tsx b/beta/src/components/Icon/IconClose.tsx new file mode 100644 index 000000000..5ad352cf0 --- /dev/null +++ b/beta/src/components/Icon/IconClose.tsx @@ -0,0 +1,26 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconClose = memo<JSX.IntrinsicElements['svg']>(function IconClose( + props +) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="1.33em" + height="1.33em" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth={2} + strokeLinecap="round" + strokeLinejoin="round" + {...props}> + <line x1={18} y1={6} x2={6} y2={18} /> + <line x1={6} y1={6} x2={18} y2={18} /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconCodeBlock.tsx b/beta/src/components/Icon/IconCodeBlock.tsx new file mode 100644 index 000000000..755a2ae34 --- /dev/null +++ b/beta/src/components/Icon/IconCodeBlock.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconCodeBlock = memo<JSX.IntrinsicElements['svg']>( + function IconCodeBlock({className}) { + return ( + <svg + className={className} + width="1.33em" + height="1em" + viewBox="0 0 24 18" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M24 9L18.343 14.657L16.929 13.243L21.172 9L16.929 4.757L18.343 3.343L24 9ZM2.828 9L7.071 13.243L5.657 14.657L0 9L5.657 3.343L7.07 4.757L2.828 9ZM9.788 18H7.66L14.212 0H16.34L9.788 18Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconCopy.tsx b/beta/src/components/Icon/IconCopy.tsx new file mode 100644 index 000000000..500cd4fda --- /dev/null +++ b/beta/src/components/Icon/IconCopy.tsx @@ -0,0 +1,32 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconCopy = memo<JSX.IntrinsicElements['svg']>(function IconCopy({ + className, +}) { + return ( + <svg + className={className} + width="1em" + height="1em" + viewBox="0 0 18 18" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M5.40382 15.3671C5.03332 15.1901 4.70081 14.9381 4.42481 14.6286C4.34831 14.5431 4.23931 14.5001 4.12981 14.5206L3.66181 14.6081C3.33531 14.6691 3.02032 14.4361 2.96232 14.0876L1.30981 4.12512C1.28181 3.95662 1.31731 3.7861 1.40981 3.6456C1.50231 3.5051 1.64082 3.41162 1.79932 3.38162L3.22131 3.00012C3.37681 2.97062 3.48981 2.82761 3.48981 2.65961V1.9101C3.48981 1.8276 3.49381 1.74561 3.49931 1.66461C3.50931 1.53461 3.35181 1.57211 3.35181 1.57211L1.64381 2.0076C1.18481 2.0936 0.751316 2.32662 0.451316 2.70612C0.0808162 3.17362 -0.0686885 3.77259 0.0293115 4.36459L1.68231 14.3281C1.84531 15.3081 2.65031 16.0001 3.55631 16.0001C3.66531 16.0001 3.77631 15.9896 3.88731 15.9691L5.36632 15.6916C5.52332 15.6626 5.54982 15.4366 5.40382 15.3671ZM14.9331 4.55801H12.9116C12.1351 4.55801 11.5001 3.91502 11.5001 3.12952V1.06802C11.5001 0.480524 11.0196 0 10.4321 0H7.44161C6.36911 0 5.50011 0.879508 5.50011 1.96451V12.1665C5.50011 13.179 6.33412 14 7.36262 14H14.1371C15.1661 14 16.0001 13.179 16.0001 12.1665V5.625C16.0001 5.038 15.5201 4.55801 14.9331 4.55801Z" + fill="currentColor" + />{' '} + <path + fillRule="evenodd" + clipRule="evenodd" + d="M12.5888 0.0914385C12.4493 0.00843847 12.5158 0.252449 12.5158 0.252449C12.5653 0.428449 12.5918 0.613451 12.5918 0.804451V2.90296C12.5918 3.17646 12.8158 3.40046 13.0903 3.40046H15.1718C15.3883 3.40046 15.5968 3.43495 15.7918 3.49845C15.7918 3.49845 15.9373 3.50844 15.9008 3.43494C15.8383 3.33744 15.7673 3.24494 15.6833 3.16044L12.8303 0.289467C12.7558 0.214467 12.6743 0.149438 12.5888 0.0914385Z" + fill="currentColor" + /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconDeepDive.tsx b/beta/src/components/Icon/IconDeepDive.tsx new file mode 100644 index 000000000..fe3e7774c --- /dev/null +++ b/beta/src/components/Icon/IconDeepDive.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconDeepDive = memo<JSX.IntrinsicElements['svg']>( + function IconDeepDive({className}) { + return ( + <svg + className={className} + width="0.78em" + height="0.78em" + viewBox="0 0 14 14" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M7.62728 12.3491V13.6825H6.29395V12.3491H0.960612C0.783801 12.3491 0.614232 12.2789 0.489207 12.1539C0.364183 12.0288 0.293945 11.8593 0.293945 11.6825V1.01579C0.293945 0.838979 0.364183 0.669409 0.489207 0.544385C0.614232 0.419361 0.783801 0.349123 0.960612 0.349123H4.96061C5.339 0.348674 5.71313 0.42896 6.05803 0.584621C6.40292 0.740282 6.71063 0.967734 6.96061 1.25179C7.2106 0.967734 7.51831 0.740282 7.8632 0.584621C8.20809 0.42896 8.58222 0.348674 8.96061 0.349123H12.9606C13.1374 0.349123 13.307 0.419361 13.432 0.544385C13.557 0.669409 13.6273 0.838979 13.6273 1.01579V11.6825C13.6273 11.8593 13.557 12.0288 13.432 12.1539C13.307 12.2789 13.1374 12.3491 12.9606 12.3491H7.62728ZM12.2939 11.0158V1.68246H8.96061C8.60699 1.68246 8.26785 1.82293 8.0178 2.07298C7.76776 2.32303 7.62728 2.66217 7.62728 3.01579V11.0158H12.2939ZM6.29395 11.0158V3.01579C6.29395 2.66217 6.15347 2.32303 5.90342 2.07298C5.65337 1.82293 5.31423 1.68246 4.96061 1.68246H1.62728V11.0158H6.29395Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconDownload.tsx b/beta/src/components/Icon/IconDownload.tsx new file mode 100644 index 000000000..c0e7f49c2 --- /dev/null +++ b/beta/src/components/Icon/IconDownload.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconDownload = memo<JSX.IntrinsicElements['svg']>( + function IconDownload({className}) { + return ( + <svg + width="1em" + height="1em" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className}> + <path + d="M20.5 22H3.5C3.10218 22 2.72064 21.842 2.43934 21.5607C2.15804 21.2794 2 20.8978 2 20.5V15.5C2 15.3674 2.05268 15.2402 2.14645 15.1464C2.24021 15.0527 2.36739 15 2.5 15H3.5C3.63261 15 3.75979 15.0527 3.85355 15.1464C3.94732 15.2402 4 15.3674 4 15.5V20H20V15.5C20 15.3674 20.0527 15.2402 20.1464 15.1464C20.2402 15.0527 20.3674 15 20.5 15H21.5C21.6326 15 21.7598 15.0527 21.8536 15.1464C21.9473 15.2402 22 15.3674 22 15.5V20.5C22 20.8978 21.842 21.2794 21.5607 21.5607C21.2794 21.842 20.8978 22 20.5 22Z" + fill="currentColor" + /> + <path + d="M10.9999 2.5V13.79L8.81994 11.61C8.72479 11.5178 8.59747 11.4662 8.46494 11.4662C8.33241 11.4662 8.20509 11.5178 8.10994 11.61L7.39994 12.32C7.30769 12.4151 7.2561 12.5425 7.2561 12.675C7.2561 12.8075 7.30769 12.9348 7.39994 13.03L10.9399 16.56C11.0785 16.7003 11.2436 16.8117 11.4255 16.8877C11.6075 16.9637 11.8027 17.0029 11.9999 17.0029C12.1971 17.0029 12.3924 16.9637 12.5743 16.8877C12.7563 16.8117 12.9214 16.7003 13.0599 16.56L16.5999 13C16.6922 12.9048 16.7438 12.7775 16.7438 12.645C16.7438 12.5125 16.6922 12.3851 16.5999 12.29L15.8899 11.58C15.7948 11.4878 15.6675 11.4362 15.5349 11.4362C15.4024 11.4362 15.2751 11.4878 15.1799 11.58L12.9999 13.79V2.5C12.9999 2.36739 12.9473 2.24021 12.8535 2.14645C12.7597 2.05268 12.6325 2 12.4999 2H11.4999C11.3673 2 11.2402 2.05268 11.1464 2.14645C11.0526 2.24021 10.9999 2.36739 10.9999 2.5Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconError.tsx b/beta/src/components/Icon/IconError.tsx new file mode 100644 index 000000000..f101f62b2 --- /dev/null +++ b/beta/src/components/Icon/IconError.tsx @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconError = memo<JSX.IntrinsicElements['svg']>(function IconError({ + className, +}) { + return ( + <svg + className={className} + width="1.33em" + height="1.33em" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg"> + <circle cx="10.1626" cy="9.99951" r="9.47021" fill="currentColor" /> + <path d="M6.22705 5.95996L14.2798 14.0127" stroke="white" /> + <path d="M14.2798 5.95996L6.22705 14.0127" stroke="white" /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconFacebookCircle.tsx b/beta/src/components/Icon/IconFacebookCircle.tsx new file mode 100644 index 000000000..0900d6815 --- /dev/null +++ b/beta/src/components/Icon/IconFacebookCircle.tsx @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconFacebookCircle = memo<JSX.IntrinsicElements['svg']>( + function IconFacebookCircle(props) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="1.33em" + height="1.33em" + fill="currentColor" + {...props}> + <path fill="none" d="M0 0h24v24H0z" /> + <path d="M12 2C6.477 2 2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.879V14.89h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.989C18.343 21.129 22 16.99 22 12c0-5.523-4.477-10-10-10z" /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconGitHub.tsx b/beta/src/components/Icon/IconGitHub.tsx new file mode 100644 index 000000000..de2a982bd --- /dev/null +++ b/beta/src/components/Icon/IconGitHub.tsx @@ -0,0 +1,21 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconGitHub = memo<JSX.IntrinsicElements['svg']>( + function IconGitHub(props) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="1.33em" + height="1.33em" + viewBox="0 -2 24 24" + fill="currentColor" + {...props}> + <path d="M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0"></path> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconHamburger.tsx b/beta/src/components/Icon/IconHamburger.tsx new file mode 100644 index 000000000..5e6aa725a --- /dev/null +++ b/beta/src/components/Icon/IconHamburger.tsx @@ -0,0 +1,26 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconHamburger = memo<JSX.IntrinsicElements['svg']>( + function IconHamburger(props) { + return ( + <svg + width="1.33em" + height="1.33em" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + {...props}> + <line x1="3" y1="12" x2="21" y2="12"></line> + <line x1="3" y1="6" x2="21" y2="6"></line> + <line x1="3" y1="18" x2="21" y2="18"></line> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconHint.tsx b/beta/src/components/Icon/IconHint.tsx new file mode 100644 index 000000000..b802bc79c --- /dev/null +++ b/beta/src/components/Icon/IconHint.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import cn from 'classnames'; + +export const IconHint = memo<JSX.IntrinsicElements['svg']>(function IconHint({ + className, +}) { + return ( + <svg + className={cn('inline -mt-0.5', className)} + width="12" + height="14" + viewBox="0 0 12 15" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M4.53487 11H5.21954V7.66665H6.55287V11H7.23754C7.32554 10.1986 7.7342 9.53732 8.39754 8.81532C8.47287 8.73398 8.9522 8.23732 9.00887 8.16665C9.47973 7.5784 9.77486 6.86913 9.86028 6.1205C9.9457 5.37187 9.81794 4.61434 9.4917 3.93514C9.16547 3.25594 8.65402 2.6827 8.01628 2.28143C7.37853 1.88016 6.64041 1.66719 5.88692 1.66703C5.13344 1.66686 4.39523 1.87953 3.75731 2.28052C3.11939 2.68152 2.60771 3.25454 2.28118 3.9336C1.95465 4.61266 1.82656 5.37014 1.91167 6.1188C1.99677 6.86747 2.2916 7.57687 2.7622 8.16532C2.81954 8.23665 3.3002 8.73398 3.3742 8.81465C4.0382 9.53732 4.44687 10.1986 4.53487 11ZM4.55287 12.3333V13H7.21954V12.3333H4.55287ZM1.7222 8.99998C1.09433 8.21551 0.700836 7.26963 0.587047 6.2713C0.473258 5.27296 0.643804 4.26279 1.07904 3.35715C1.51428 2.4515 2.19649 1.68723 3.04711 1.15237C3.89772 0.617512 4.88213 0.333824 5.88692 0.333984C6.89172 0.334145 7.87604 0.61815 8.72648 1.15328C9.57692 1.68841 10.2589 2.4529 10.6938 3.35869C11.1288 4.26447 11.299 5.27469 11.1849 6.27299C11.0708 7.27129 10.677 8.21705 10.0489 9.00132C9.63554 9.51598 8.55287 10.3333 8.55287 11.3333V13C8.55287 13.3536 8.41239 13.6927 8.16235 13.9428C7.9123 14.1928 7.57316 14.3333 7.21954 14.3333H4.55287C4.19925 14.3333 3.86011 14.1928 3.61006 13.9428C3.36001 13.6927 3.21954 13.3536 3.21954 13V11.3333C3.21954 10.3333 2.1362 9.51598 1.7222 8.99998Z" + fill="currentColor" + /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconInstagram.tsx b/beta/src/components/Icon/IconInstagram.tsx new file mode 100644 index 000000000..6868a0a75 --- /dev/null +++ b/beta/src/components/Icon/IconInstagram.tsx @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconInstagram = memo<JSX.IntrinsicElements['svg']>( + function IconInstagram(props) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="1.33em" + height="1.33em" + fill="currentColor" + {...props}> + <path fill="none" d="M0 0h24v24H0z" /> + <path d="M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm0-2a5 5 0 1 1 0 10 5 5 0 0 1 0-10zm6.5-.25a1.25 1.25 0 0 1-2.5 0 1.25 1.25 0 0 1 2.5 0zM12 4c-2.474 0-2.878.007-4.029.058-.784.037-1.31.142-1.798.332-.434.168-.747.369-1.08.703a2.89 2.89 0 0 0-.704 1.08c-.19.49-.295 1.015-.331 1.798C4.006 9.075 4 9.461 4 12c0 2.474.007 2.878.058 4.029.037.783.142 1.31.331 1.797.17.435.37.748.702 1.08.337.336.65.537 1.08.703.494.191 1.02.297 1.8.333C9.075 19.994 9.461 20 12 20c2.474 0 2.878-.007 4.029-.058.782-.037 1.309-.142 1.797-.331.433-.169.748-.37 1.08-.702.337-.337.538-.65.704-1.08.19-.493.296-1.02.332-1.8.052-1.104.058-1.49.058-4.029 0-2.474-.007-2.878-.058-4.029-.037-.782-.142-1.31-.332-1.798a2.911 2.911 0 0 0-.703-1.08 2.884 2.884 0 0 0-1.08-.704c-.49-.19-1.016-.295-1.798-.331C14.925 4.006 14.539 4 12 4zm0-2c2.717 0 3.056.01 4.122.06 1.065.05 1.79.217 2.428.465.66.254 1.216.598 1.772 1.153a4.908 4.908 0 0 1 1.153 1.772c.247.637.415 1.363.465 2.428.047 1.066.06 1.405.06 4.122 0 2.717-.01 3.056-.06 4.122-.05 1.065-.218 1.79-.465 2.428a4.883 4.883 0 0 1-1.153 1.772 4.915 4.915 0 0 1-1.772 1.153c-.637.247-1.363.415-2.428.465-1.066.047-1.405.06-4.122.06-2.717 0-3.056-.01-4.122-.06-1.065-.05-1.79-.218-2.428-.465a4.89 4.89 0 0 1-1.772-1.153 4.904 4.904 0 0 1-1.153-1.772c-.248-.637-.415-1.363-.465-2.428C2.013 15.056 2 14.717 2 12c0-2.717.01-3.056.06-4.122.05-1.066.217-1.79.465-2.428a4.88 4.88 0 0 1 1.153-1.772A4.897 4.897 0 0 1 5.45 2.525c.638-.248 1.362-.415 2.428-.465C8.944 2.013 9.283 2 12 2z" /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconLink.tsx b/beta/src/components/Icon/IconLink.tsx new file mode 100644 index 000000000..587b4e6ed --- /dev/null +++ b/beta/src/components/Icon/IconLink.tsx @@ -0,0 +1,25 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconLink = memo<JSX.IntrinsicElements['svg']>(function IconLink( + props +) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="1.33em" + height="1.33em" + viewBox="0 -2 24 24" + fill="currentColor" + {...props}> + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" + /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconNavArrow.tsx b/beta/src/components/Icon/IconNavArrow.tsx new file mode 100644 index 000000000..93eed6e3c --- /dev/null +++ b/beta/src/components/Icon/IconNavArrow.tsx @@ -0,0 +1,42 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import cn from 'classnames'; + +export const IconNavArrow = memo< + JSX.IntrinsicElements['svg'] & { + displayDirection: 'right' | 'down' | 'left'; + } +>(function IconNavArrow({displayDirection = 'right', className}) { + const classes = cn( + 'duration-100 ease-in transition', + { + 'rotate-0': displayDirection === 'down', + '-rotate-90': displayDirection === 'right', + 'rotate-90': displayDirection === 'left', + }, + className + ); + + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 20 20" + className={classes} + style={{minWidth: 20, minHeight: 20}}> + <g fill="none" fillRule="evenodd" transform="translate(-446 -398)"> + <path + fill="currentColor" + fillRule="nonzero" + d="M95.8838835,240.366117 C95.3957281,239.877961 94.6042719,239.877961 94.1161165,240.366117 C93.6279612,240.854272 93.6279612,241.645728 94.1161165,242.133883 L98.6161165,246.633883 C99.1042719,247.122039 99.8957281,247.122039 100.383883,246.633883 L104.883883,242.133883 C105.372039,241.645728 105.372039,240.854272 104.883883,240.366117 C104.395728,239.877961 103.604272,239.877961 103.116117,240.366117 L99.5,243.982233 L95.8838835,240.366117 Z" + transform="translate(356.5 164.5)" + /> + <polygon points="446 418 466 418 466 398 446 398" /> + </g> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconNewPage.tsx b/beta/src/components/Icon/IconNewPage.tsx new file mode 100644 index 000000000..e32901c5a --- /dev/null +++ b/beta/src/components/Icon/IconNewPage.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconNewPage = memo<JSX.IntrinsicElements['svg']>( + function IconNewPage(props) { + return ( + <svg + width="1em" + height="1em" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props}> + <path + d="M20.5001 2H15.5001C15.3675 2 15.2403 2.05268 15.1465 2.14645C15.0528 2.24021 15.0001 2.36739 15.0001 2.5V3.5C15.0001 3.63261 15.0528 3.75979 15.1465 3.85355C15.2403 3.94732 15.3675 4 15.5001 4H18.5901L7.6501 14.94C7.60323 14.9865 7.56604 15.0418 7.54065 15.1027C7.51527 15.1636 7.5022 15.229 7.5022 15.295C7.5022 15.361 7.51527 15.4264 7.54065 15.4873C7.56604 15.5482 7.60323 15.6035 7.6501 15.65L8.3501 16.35C8.39658 16.3969 8.45188 16.4341 8.51281 16.4594C8.57374 16.4848 8.63909 16.4979 8.7051 16.4979C8.7711 16.4979 8.83646 16.4848 8.89738 16.4594C8.95831 16.4341 9.01362 16.3969 9.0601 16.35L20.0001 5.41V8.5C20.0001 8.63261 20.0528 8.75979 20.1465 8.85355C20.2403 8.94732 20.3675 9 20.5001 9H21.5001C21.6327 9 21.7599 8.94732 21.8537 8.85355C21.9474 8.75979 22.0001 8.63261 22.0001 8.5V3.5C22.0001 3.10218 21.8421 2.72064 21.5608 2.43934C21.2795 2.15804 20.8979 2 20.5001 2V2Z" + fill="currentColor" + /> + <path + d="M21.5 13H20.5C20.3674 13 20.2402 13.0527 20.1464 13.1464C20.0527 13.2402 20 13.3674 20 13.5V20H4V4H10.5C10.6326 4 10.7598 3.94732 10.8536 3.85355C10.9473 3.75979 11 3.63261 11 3.5V2.5C11 2.36739 10.9473 2.24021 10.8536 2.14645C10.7598 2.05268 10.6326 2 10.5 2H3.5C3.10218 2 2.72064 2.15804 2.43934 2.43934C2.15804 2.72064 2 3.10218 2 3.5V20.5C2 20.8978 2.15804 21.2794 2.43934 21.5607C2.72064 21.842 3.10218 22 3.5 22H20.5C20.8978 22 21.2794 21.842 21.5607 21.5607C21.842 21.2794 22 20.8978 22 20.5V13.5C22 13.3674 21.9473 13.2402 21.8536 13.1464C21.7598 13.0527 21.6326 13 21.5 13Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconNote.tsx b/beta/src/components/Icon/IconNote.tsx new file mode 100644 index 000000000..a0ac1293e --- /dev/null +++ b/beta/src/components/Icon/IconNote.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconNote = memo<JSX.IntrinsicElements['svg']>(function IconNote({ + className, +}) { + return ( + <svg + className={className} + width="1em" + height="1.05em" + viewBox="0 0 18 19" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M18 12.2632L12 18.2592L1.002 18.2632C0.737486 18.2642 0.483369 18.1603 0.295486 17.9741C0.107603 17.7879 0.00132309 17.5347 0 17.2702V1.25618C0 0.708184 0.445 0.263184 0.993 0.263184H17.007C17.555 0.263184 18 0.719183 18 1.26518V12.2632ZM16 2.26318H2V16.2632H10V11.2632C10 11.0183 10.09 10.7818 10.2527 10.5988C10.4155 10.4158 10.6397 10.2988 10.883 10.2702L11 10.2632L16 10.2622V2.26318ZM15.171 12.2622L12 12.2632V15.4322L15.171 12.2622Z" + fill="currentColor" + /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconPitfall.tsx b/beta/src/components/Icon/IconPitfall.tsx new file mode 100644 index 000000000..7c0b41dfc --- /dev/null +++ b/beta/src/components/Icon/IconPitfall.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconPitfall = memo<JSX.IntrinsicElements['svg']>( + function IconPitfall({className}) { + return ( + <svg + className={className} + width="1.11em" + height="1.11em" + viewBox="0 0 20 20" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M19 0.398926C19.552 0.398926 20 0.846926 20 1.39893V15.3989C20 15.9509 19.552 16.3989 19 16.3989H4.455L0 19.8989V1.39893C0 0.846926 0.448 0.398926 1 0.398926H19ZM18 2.39893H2V15.7839L3.763 14.3989H18V2.39893ZM8.515 4.81093L8.962 5.49893C7.294 6.40193 7.323 7.85093 7.323 8.16293C7.478 8.14293 7.641 8.13893 7.803 8.15393C8.705 8.23793 9.416 8.97893 9.416 9.89893C9.416 10.8649 8.632 11.6489 7.666 11.6489C7.129 11.6489 6.616 11.4039 6.292 11.0589C5.777 10.5129 5.5 9.89893 5.5 8.90393C5.5 7.15393 6.728 5.58593 8.515 4.81093ZM13.515 4.81093L13.962 5.49893C12.294 6.40193 12.323 7.85093 12.323 8.16293C12.478 8.14293 12.641 8.13893 12.803 8.15393C13.705 8.23793 14.416 8.97893 14.416 9.89893C14.416 10.8649 13.632 11.6489 12.666 11.6489C12.129 11.6489 11.616 11.4039 11.292 11.0589C10.777 10.5129 10.5 9.89893 10.5 8.90393C10.5 7.15393 11.728 5.58593 13.515 4.81093Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconRestart.tsx b/beta/src/components/Icon/IconRestart.tsx new file mode 100644 index 000000000..b4a6b62f5 --- /dev/null +++ b/beta/src/components/Icon/IconRestart.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconRestart = memo<JSX.IntrinsicElements['svg']>( + function IconRestart({className}) { + return ( + <svg + width="1em" + height="1em" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className}> + <path + d="M13.8982 5.20844C12.4626 4.88688 10.9686 4.93769 9.55821 5.35604L11.8524 3.06184C11.8989 3.0154 11.9357 2.96028 11.9608 2.89961C11.986 2.83894 11.9989 2.77391 11.9989 2.70824C11.9989 2.64256 11.986 2.57754 11.9608 2.51686C11.9357 2.45619 11.8989 2.40107 11.8524 2.35464L11.1456 1.64784C11.0992 1.60139 11.0441 1.56455 10.9834 1.53942C10.9227 1.51428 10.8577 1.50134 10.792 1.50134C10.7263 1.50134 10.6613 1.51428 10.6006 1.53942C10.54 1.56455 10.4848 1.60139 10.4384 1.64784L6.14571 5.94054C6.00654 6.07969 5.89615 6.2449 5.82083 6.42673C5.74551 6.60855 5.70675 6.80343 5.70675 7.00024C5.70675 7.19704 5.74551 7.39192 5.82083 7.57374C5.89615 7.75557 6.00654 7.92078 6.14571 8.05994L10.4387 12.3529C10.5325 12.4465 10.6595 12.4991 10.792 12.4991C10.9245 12.4991 11.0516 12.4465 11.1453 12.3529L11.8527 11.6455C11.9463 11.5518 11.9989 11.4247 11.9989 11.2922C11.9989 11.1598 11.9463 11.0327 11.8527 10.9389L8.77481 7.86104C9.99795 7.16236 11.415 6.8801 12.8125 7.05678C14.21 7.23347 15.5122 7.85953 16.523 8.84064C17.5338 9.82176 18.1983 11.1048 18.4165 12.4964C18.6347 13.888 18.3947 15.3129 17.7328 16.5562C17.0708 17.7996 16.0227 18.7942 14.7463 19.3902C13.47 19.9861 12.0345 20.1511 10.6563 19.8603C9.27798 19.5695 8.03152 18.8387 7.10469 17.778C6.17786 16.7172 5.62086 15.384 5.51761 13.9791C5.51156 13.8512 5.45689 13.7303 5.36477 13.6413C5.27265 13.5522 5.15001 13.5017 5.02191 13.5H4.02081C3.95297 13.4996 3.88574 13.5129 3.8232 13.5392C3.76065 13.5655 3.70408 13.6042 3.6569 13.6529C3.60972 13.7017 3.57291 13.7595 3.54869 13.8228C3.52448 13.8862 3.51336 13.9538 3.51601 14.0216C3.61349 15.5965 4.1473 17.1132 5.0577 18.4019C5.9681 19.6906 7.21917 20.7006 8.6709 21.3188C10.1226 21.937 11.7178 22.139 13.2778 21.9022C14.8378 21.6654 16.3011 20.9992 17.504 19.978C18.7069 18.9569 19.6019 17.6212 20.0889 16.1203C20.5759 14.6195 20.6356 13.0128 20.2614 11.4799C19.8872 9.94705 19.0938 8.54858 17.97 7.44098C16.8462 6.33339 15.4363 5.56037 13.8982 5.20844V5.20844Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconRss.tsx b/beta/src/components/Icon/IconRss.tsx new file mode 100644 index 000000000..f2a52ee25 --- /dev/null +++ b/beta/src/components/Icon/IconRss.tsx @@ -0,0 +1,27 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconRss = memo<JSX.IntrinsicElements['svg']>(function IconRss( + props +) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="1em" + height="1em" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth={2} + strokeLinecap="round" + strokeLinejoin="round" + {...props}> + <path d="M4 11a9 9 0 0 1 9 9" /> + <path d="M4 4a16 16 0 0 1 16 16" /> + <circle cx={5} cy={19} r={1} /> + </svg> + ); +}); diff --git a/beta/src/components/Icon/IconSearch.tsx b/beta/src/components/Icon/IconSearch.tsx new file mode 100644 index 000000000..a2816991e --- /dev/null +++ b/beta/src/components/Icon/IconSearch.tsx @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconSearch = memo<JSX.IntrinsicElements['svg']>( + function IconSearch(props) { + return ( + <svg width="1em" height="1em" viewBox="0 0 20 20" {...props}> + <path + d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" + stroke="currentColor" + fill="none" + strokeWidth="2" + fillRule="evenodd" + strokeLinecap="round" + strokeLinejoin="round"></path> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconSolution.tsx b/beta/src/components/Icon/IconSolution.tsx new file mode 100644 index 000000000..668e41afe --- /dev/null +++ b/beta/src/components/Icon/IconSolution.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; +import cn from 'classnames'; + +export const IconSolution = memo<JSX.IntrinsicElements['svg']>( + function IconSolution({className}) { + return ( + <svg + className={cn('inline', className)} + width="0.75em" + height="0.75em" + viewBox="0 0 13 13" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M2.21908 8.74479V12.7448H0.885742V0.078125H7.14041C7.26418 0.0781911 7.3855 0.112714 7.49076 0.177827C7.59602 0.242939 7.68108 0.336071 7.73641 0.446792L8.21908 1.41146H12.2191C12.3959 1.41146 12.5655 1.4817 12.6905 1.60672C12.8155 1.73174 12.8857 1.90131 12.8857 2.07812V9.41146C12.8857 9.58827 12.8155 9.75784 12.6905 9.88286C12.5655 10.0079 12.3959 10.0781 12.2191 10.0781H7.96441C7.84063 10.0781 7.71932 10.0435 7.61406 9.97842C7.50879 9.91331 7.42374 9.82018 7.36841 9.70946L6.88574 8.74479H2.21908ZM2.21908 1.41146V7.41146H7.70974L8.37641 8.74479H11.5524V2.74479H7.39508L6.72841 1.41146H2.21908Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconTerminal.tsx b/beta/src/components/Icon/IconTerminal.tsx new file mode 100644 index 000000000..7b3a97a8c --- /dev/null +++ b/beta/src/components/Icon/IconTerminal.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconTerminal = memo<JSX.IntrinsicElements['svg']>( + function IconTerminal({className}) { + return ( + <svg + className={className} + width="1em" + height="1em" + viewBox="0 0 18 18" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M2.40299 2.61279H14.403C14.5798 2.61279 14.7494 2.68303 14.8744 2.80806C14.9994 2.93308 15.0697 3.10265 15.0697 3.27946V13.9461C15.0697 14.1229 14.9994 14.2925 14.8744 14.4175C14.7494 14.5426 14.5798 14.6128 14.403 14.6128H2.40299C2.22618 14.6128 2.05661 14.5426 1.93159 14.4175C1.80657 14.2925 1.73633 14.1229 1.73633 13.9461V3.27946C1.73633 3.10265 1.80657 2.93308 1.93159 2.80806C2.05661 2.68303 2.22618 2.61279 2.40299 2.61279ZM8.403 10.6128V11.9461H12.403V10.6128H8.403ZM6.01233 8.61279L4.12699 10.4981L5.06966 11.4415L7.89833 8.61279L5.06966 5.78413L4.12699 6.72746L6.01233 8.61279Z" + fill="currentColor" + /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconTwitter.tsx b/beta/src/components/Icon/IconTwitter.tsx new file mode 100644 index 000000000..951171524 --- /dev/null +++ b/beta/src/components/Icon/IconTwitter.tsx @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconTwitter = memo<JSX.IntrinsicElements['svg']>( + function IconTwitter(props) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="1.33em" + height="1.33em" + fill="currentColor" + {...props}> + <path fill="none" d="M0 0h24v24H0z" /> + <path d="M22.162 5.656a8.384 8.384 0 0 1-2.402.658A4.196 4.196 0 0 0 21.6 4c-.82.488-1.719.83-2.656 1.015a4.182 4.182 0 0 0-7.126 3.814 11.874 11.874 0 0 1-8.62-4.37 4.168 4.168 0 0 0-.566 2.103c0 1.45.738 2.731 1.86 3.481a4.168 4.168 0 0 1-1.894-.523v.052a4.185 4.185 0 0 0 3.355 4.101 4.21 4.21 0 0 1-1.89.072A4.185 4.185 0 0 0 7.97 16.65a8.394 8.394 0 0 1-6.191 1.732 11.83 11.83 0 0 0 6.41 1.88c7.693 0 11.9-6.373 11.9-11.9 0-.18-.005-.362-.013-.54a8.496 8.496 0 0 0 2.087-2.165z" /> + </svg> + ); + } +); diff --git a/beta/src/components/Icon/IconWarning.tsx b/beta/src/components/Icon/IconWarning.tsx new file mode 100644 index 000000000..d89b5678e --- /dev/null +++ b/beta/src/components/Icon/IconWarning.tsx @@ -0,0 +1,26 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconWarning = memo<JSX.IntrinsicElements['svg']>( + function IconWarning({className}) { + return ( + <svg + className={className} + width="1.33em" + height="1.33em" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg"> + <g> + <path fill="none" d="M0 0h24v24H0z" /> + <path + d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z" + fill="currentColor" + /> + </g> + </svg> + ); + } +); diff --git a/beta/src/components/Layout/Feedback.tsx b/beta/src/components/Layout/Feedback.tsx new file mode 100644 index 000000000..6bb8a4aac --- /dev/null +++ b/beta/src/components/Layout/Feedback.tsx @@ -0,0 +1,94 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useState} from 'react'; +import {useRouter} from 'next/router'; +import {ga} from '../../utils/analytics'; + +export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { + const {asPath} = useRouter(); + const cleanedPath = asPath.split(/[\?\#]/)[0]; + // Reset on route changes. + return <SendFeedback key={cleanedPath} onSubmit={onSubmit} />; +} + +const thumbsUpIcon = ( + <svg + width="16" + height="18" + viewBox="0 0 16 18" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M9.36603 0.384603C9.36605 0.384617 9.36601 0.384588 9.36603 0.384603L9.45902 0.453415C9.99732 0.851783 10.3873 1.42386 10.5654 2.07648C10.7435 2.72909 10.6993 3.42385 10.44 4.04763L9.27065 6.86008H12.6316C13.5249 6.86008 14.3817 7.22121 15.0134 7.86402C15.6451 8.50683 16 9.37868 16 10.2877V13.7154C16 14.8518 15.5564 15.9416 14.7668 16.7451C13.9771 17.5486 12.9062 18 11.7895 18H5.05263C3.71259 18 2.42743 17.4583 1.47988 16.4941C0.532325 15.5299 0 14.2221 0 12.8585V11.2511C2.40928e-06 9.87711 0.463526 8.54479 1.31308 7.47688L6.66804 0.745592C6.98662 0.345136 7.44414 0.08434 7.94623 0.0171605C8.4483 -0.0500155 8.95656 0.0815891 9.36603 0.384603ZM8.37542 1.77064C8.31492 1.72587 8.23987 1.70646 8.16579 1.71637C8.09171 1.72628 8.02415 1.76477 7.97708 1.82393L2.62213 8.55522C2.0153 9.31801 1.68421 10.2697 1.68421 11.2511V12.8585C1.68421 13.7676 2.03909 14.6394 2.67079 15.2822C3.30249 15.925 4.15927 16.2862 5.05263 16.2862H11.7895C12.4595 16.2862 13.1021 16.0153 13.5759 15.5332C14.0496 15.0511 14.3158 14.3972 14.3158 13.7154V10.2877C14.3158 9.83321 14.1383 9.39729 13.8225 9.07588C13.5066 8.75448 13.0783 8.57392 12.6316 8.57392H8C7.71763 8.57392 7.45405 8.4299 7.29806 8.19039C7.14206 7.95087 7.11442 7.64774 7.22445 7.38311L8.88886 3.37986C9 3.11253 9.01896 2.81477 8.94262 2.53507C8.8663 2.25541 8.69921 2.01027 8.46853 1.83954L8.37542 1.77064Z" + fill="currentColor" + /> + </svg> +); + +const thumbsDownIcon = ( + <svg + width="16" + height="18" + viewBox="0 0 16 18" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M6.63397 17.6154C6.63395 17.6154 6.63399 17.6154 6.63397 17.6154L6.54098 17.5466C6.00268 17.1482 5.61269 16.5761 5.43458 15.9235C5.25648 15.2709 5.30069 14.5761 5.56004 13.9524L6.72935 11.1399L3.36842 11.1399C2.47506 11.1399 1.61829 10.7788 0.986585 10.136C0.354883 9.49316 8.1991e-07 8.62132 8.99384e-07 7.71225L1.19904e-06 4.28458C1.29838e-06 3.14824 0.443605 2.05844 1.23323 1.25492C2.02286 0.451403 3.09383 -1.12829e-06 4.21053 -1.03067e-06L10.9474 -4.41715e-07C12.2874 -3.24565e-07 13.5726 0.541687 14.5201 1.50591C15.4677 2.47013 16 3.77789 16 5.1415L16 6.74893C16 8.12289 15.5365 9.45521 14.6869 10.5231L9.33196 17.2544C9.01338 17.6549 8.55586 17.9157 8.05377 17.9828C7.5517 18.05 7.04344 17.9184 6.63397 17.6154ZM7.62458 16.2294C7.68508 16.2741 7.76013 16.2935 7.83421 16.2836C7.90829 16.2737 7.97585 16.2352 8.02292 16.1761L13.3779 9.44478C13.9847 8.68199 14.3158 7.73033 14.3158 6.74892L14.3158 5.1415C14.3158 4.23242 13.9609 3.36058 13.3292 2.71777C12.6975 2.07496 11.8407 1.71383 10.9474 1.71383L4.21053 1.71383C3.5405 1.71383 2.89793 1.98468 2.42415 2.46679C1.95038 2.94889 1.68421 3.60277 1.68421 4.28458L1.68421 7.71225C1.68421 8.16679 1.86166 8.60271 2.1775 8.92411C2.49335 9.24552 2.92174 9.42608 3.36842 9.42608L8 9.42608C8.28237 9.42608 8.54595 9.5701 8.70195 9.80961C8.85794 10.0491 8.88558 10.3523 8.77555 10.6169L7.11114 14.6201C7 14.8875 6.98105 15.1852 7.05738 15.4649C7.1337 15.7446 7.30079 15.9897 7.53147 16.1605L7.62458 16.2294Z" + fill="currentColor" + /> + </svg> +); + +function sendGAEvent(isPositive: boolean) { + // Fragile. Don't change unless you've tested the network payload + // and verified that the right events actually show up in GA. + ga( + 'send', + 'event', + 'button', + 'feedback', + window.location.pathname, + isPositive ? '1' : '0' + ); +} + +function SendFeedback({onSubmit}: {onSubmit: () => void}) { + const [isSubmitted, setIsSubmitted] = useState(false); + return ( + <div className="max-w-xs w-80 lg:w-auto py-3 shadow-lg rounded-lg m-4 bg-wash dark:bg-gray-95 px-4 flex"> + <p className="w-full font-bold text-primary dark:text-primary-dark text-lg mr-4"> + {isSubmitted ? 'Thank you for your feedback!' : 'Is this page useful?'} + </p> + {!isSubmitted && ( + <button + aria-label="Yes" + className="bg-secondary-button dark:bg-secondary-button-dark rounded-lg text-primary dark:text-primary-dark px-3 mr-2" + onClick={() => { + setIsSubmitted(true); + onSubmit(); + sendGAEvent(true); + }}> + {thumbsUpIcon} + </button> + )} + {!isSubmitted && ( + <button + aria-label="No" + className="bg-secondary-button dark:bg-secondary-button-dark rounded-lg text-primary dark:text-primary-dark px-3" + onClick={() => { + setIsSubmitted(true); + onSubmit(); + sendGAEvent(false); + }}> + {thumbsDownIcon} + </button> + )} + </div> + ); +} diff --git a/beta/src/components/Layout/Footer.tsx b/beta/src/components/Layout/Footer.tsx new file mode 100644 index 000000000..e6dd303b9 --- /dev/null +++ b/beta/src/components/Layout/Footer.tsx @@ -0,0 +1,218 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import NextLink from 'next/link'; +import cn from 'classnames'; +import ButtonLink from 'components/ButtonLink'; +import {ExternalLink} from 'components/ExternalLink'; +import {IconFacebookCircle} from 'components/Icon/IconFacebookCircle'; +import {IconTwitter} from 'components/Icon/IconTwitter'; +import {IconGitHub} from 'components/Icon/IconGitHub'; +import {IconNavArrow} from 'components/Icon/IconNavArrow'; + +export function Footer() { + const socialLinkClasses = 'hover:text-primary dark:text-primary-dark'; + return ( + <> + <div className="self-stretch w-full"> + <div className="mx-auto w-full px-5 sm:px-12 md:px-12 pt-10 md:pt-12 lg:pt-10"> + <hr className="max-w-7xl mx-auto border-border dark:border-border-dark" /> + <div className="flex flex-col items-center m-4 p-4"> + <p className="font-bold text-primary dark:text-primary-dark text-lg mb-4"> + How do you like these docs? + </p> + <div> + <ButtonLink + href="https://www.surveymonkey.co.uk/r/PYRPF3X" + className="mt-1" + type="primary" + size="md" + target="_blank"> + Take our survey! + <IconNavArrow + displayDirection="right" + className="inline ml-1" + /> + </ButtonLink> + </div> + </div> + <hr className="max-w-7xl mx-auto border-border dark:border-border-dark" /> + </div> + <footer className="text-secondary dark:text-secondary-dark py-12 px-5 sm:px-12 md:px-12 sm:py-12 md:py-16 lg:py-14"> + <div className="grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-5 gap-x-12 gap-y-8 max-w-7xl mx-auto "> + <ExternalLink + href="https://opensource.fb.com/" + className="col-span-2 sm:col-span-1 justify-items-start w-44 text-left"> + <div> + <svg + className="mt-4 mb-4" + width="115" + height="13" + viewBox="0 0 115 13" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + d="M9.12655 0.414727V2.061C9.1323 2.15436 9.06215 2.23355 8.97245 2.23945C8.96555 2.23945 8.95865 2.23945 8.95175 2.23945H2.07259V5.60409H7.75002C7.84087 5.59818 7.91792 5.67027 7.92367 5.76364C7.92367 5.76955 7.92367 5.77664 7.92367 5.78255V7.43C7.92942 7.52336 7.85927 7.60254 7.76842 7.60845C7.76267 7.60845 7.75577 7.60845 7.75002 7.60845H2.07259V12.5827C2.07949 12.6761 2.01049 12.7565 1.92079 12.7635C1.91389 12.7635 1.90699 12.7635 1.90009 12.7635H0.175126C0.084278 12.7695 0.00607958 12.6974 0.000329697 12.6028C0.000329697 12.5969 0.000329697 12.5898 0.000329697 12.5839V0.411182C-0.00542019 0.317818 0.0647284 0.237454 0.156727 0.231545C0.162476 0.231545 0.169376 0.231545 0.175126 0.231545H8.9506C9.04145 0.225636 9.1208 0.296545 9.12655 0.389909C9.1277 0.398182 9.1277 0.406454 9.12655 0.414727Z" + fill="currentColor" + /> + <path + d="M23.1608 12.7637H21.2633C21.1656 12.7708 21.0793 12.701 21.0621 12.6018C20.8102 11.5736 20.5055 10.491 20.157 9.39902H14.3324C13.9874 10.491 13.6792 11.5736 13.4354 12.6018C13.4193 12.701 13.3331 12.7708 13.2353 12.7637H11.4068C11.285 12.7637 11.216 12.6916 11.2505 12.5663C12.3475 8.57648 14.0184 4.17539 15.7078 0.469206C15.7549 0.317933 15.8987 0.219842 16.0528 0.232842H18.5172C18.6713 0.219842 18.815 0.317933 18.8621 0.469206C20.6561 4.37393 22.1465 8.4193 23.3195 12.5663C23.3528 12.6904 23.2827 12.7637 23.1608 12.7637ZM19.513 7.46675C18.8771 5.65857 18.1722 3.85157 17.4431 2.2053H17.0348C16.3115 3.85157 15.5974 5.65857 14.9649 7.46675H19.513Z" + fill="currentColor" + /> + <path + d="M26.2773 6.60636C26.2773 2.71818 28.767 0 32.317 0H32.5781C34.8079 0 36.5317 1.16291 37.4459 2.84464C37.5011 2.91082 37.4942 3.01127 37.4287 3.068C37.416 3.07982 37.4011 3.08927 37.385 3.09518L35.8521 3.874C35.7543 3.94018 35.6221 3.91182 35.5577 3.81136C35.5542 3.80545 35.5508 3.79955 35.5473 3.79364C34.9033 2.64845 33.9373 2.03982 32.5091 2.03982H32.248C30.0102 2.03982 28.4692 3.86455 28.4692 6.513C28.4692 9.16145 29.9837 10.9507 32.248 10.9507H32.5091C33.9718 10.9507 34.8251 10.4414 35.4783 9.66255C35.545 9.57036 35.6681 9.54318 35.7658 9.59991L37.3413 10.387C37.3907 10.4095 37.4241 10.4567 37.4287 10.5123C37.4252 10.5619 37.4068 10.6092 37.3758 10.647C36.4098 12.0971 34.6687 12.9917 32.5459 12.9917H32.2848C28.6716 13 26.2773 10.4449 26.2773 6.60636Z" + fill="currentColor" + /> + <path + d="M51.3171 10.9367V12.5829C51.3228 12.6763 51.2527 12.7567 51.1607 12.7626C51.1549 12.7626 51.1492 12.7626 51.1434 12.7626H42.0011C41.9103 12.7685 41.8332 12.6964 41.8275 12.6042C41.8275 12.5971 41.8275 12.59 41.8275 12.5829V0.4102C41.8217 0.316836 41.8907 0.236473 41.9804 0.230563C41.9873 0.230563 41.9942 0.230563 42.0011 0.230563H50.9859C51.0767 0.224654 51.1549 0.296745 51.1607 0.391291C51.1607 0.3972 51.1607 0.404291 51.1607 0.4102V2.05647C51.1664 2.14984 51.0963 2.22902 51.0066 2.23493C50.9997 2.23493 50.9928 2.23493 50.9859 2.23493H43.8986V5.49202H49.6623C49.7531 5.48611 49.8313 5.5582 49.8371 5.65275C49.8371 5.65866 49.8371 5.66575 49.8371 5.67166V7.3002C49.8417 7.39356 49.7715 7.47393 49.6795 7.47865C49.6738 7.47865 49.668 7.47865 49.6623 7.47865H43.8986V10.7547H51.1434C51.2343 10.7487 51.3125 10.8197 51.3171 10.913C51.3182 10.9213 51.3182 10.9284 51.3171 10.9367Z" + fill="currentColor" + /> + <path + d="M67.058 9.32692C67.058 11.518 65.4216 12.7625 62.5305 12.7625H56.5403C56.4495 12.7684 56.3724 12.6963 56.3667 12.6041C56.3667 12.597 56.3667 12.5899 56.3667 12.5828V0.410105C56.3609 0.316741 56.4299 0.236378 56.5196 0.230469C56.5265 0.230469 56.5334 0.230469 56.5403 0.230469H61.9993C64.8121 0.230469 66.3439 1.37565 66.3439 3.46983C66.3439 4.84783 65.6654 5.75192 64.2889 6.17147C66.222 6.59692 67.058 7.78701 67.058 9.32692ZM61.9556 2.18638H58.4389V5.55456H61.9556C63.5322 5.55456 64.2555 5.02629 64.2555 3.87165C64.2555 2.71701 63.5322 2.18638 61.9556 2.18638ZM64.934 9.13902C64.934 7.97492 64.1854 7.44783 62.5398 7.44783H58.4389V10.8113H62.5398C64.2107 10.8113 64.934 10.3102 64.934 9.13902Z" + fill="currentColor" + /> + <path + d="M70.7057 6.5C70.7057 2.72409 73.1436 0 76.9742 0H77.2353C81.0658 0 83.5038 2.71818 83.5038 6.5C83.5038 10.2818 81.0658 13 77.2353 13H76.9742C73.1413 13 70.7057 10.2747 70.7057 6.5ZM77.2353 10.9555C79.7342 10.9555 81.3096 9.19336 81.3096 6.5C81.3096 3.80664 79.7342 2.04455 77.2353 2.04455H76.9742C74.4753 2.04455 72.8998 3.80664 72.8998 6.5C72.8998 9.19336 74.4753 10.9555 76.9742 10.9555H77.2353Z" + fill="currentColor" + /> + <path + d="M87.0387 6.5C87.0387 2.72409 89.4766 0 93.3072 0H93.5683C97.3988 0 99.8368 2.71818 99.8368 6.5C99.8368 10.2818 97.3988 13 93.5683 13H93.3072C89.4766 13 87.0387 10.2747 87.0387 6.5ZM93.5683 10.9555C96.0672 10.9555 97.6426 9.19336 97.6426 6.5C97.6426 3.80664 96.0672 2.04455 93.5683 2.04455H93.3072C90.8083 2.04455 89.2329 3.80664 89.2329 6.5C89.2329 9.19336 90.8083 10.9555 93.3072 10.9555H93.5683Z" + fill="currentColor" + /> + <path + d="M114.855 12.7637H112.608C112.488 12.7744 112.37 12.7153 112.304 12.6113C110.758 10.7511 109.079 9.01266 107.28 7.41129H106.271V12.5829C106.277 12.6763 106.206 12.7567 106.114 12.7626C106.109 12.7626 106.102 12.7626 106.096 12.7626H104.371C104.28 12.7685 104.203 12.6964 104.197 12.6042C104.197 12.5971 104.197 12.59 104.197 12.5829V0.4102C104.192 0.316836 104.261 0.236473 104.35 0.230563C104.357 0.230563 104.364 0.230563 104.371 0.230563H106.096C106.187 0.224654 106.265 0.296745 106.271 0.391291C106.271 0.3972 106.271 0.404291 106.271 0.4102V5.35375H107.295C108.951 3.83393 110.472 2.16638 111.84 0.370018C111.895 0.279018 111.996 0.227018 112.101 0.235291H114.226C114.33 0.235291 114.383 0.289654 114.383 0.360563C114.378 0.411382 114.356 0.458654 114.322 0.495291C112.682 2.59893 110.861 4.54538 108.88 6.31102C111.065 8.21375 113.095 10.2961 114.948 12.538C115.046 12.655 115 12.7637 114.855 12.7637Z" + fill="currentColor" + /> + </svg> + Open Source + </div> + <div className="text-xs text-left mt-2 pr-0.5"> + ©{new Date().getFullYear()} + </div> + </ExternalLink> + <div className="flex flex-col"> + <FooterLink href="/learn" isHeader={true}> + Learn React + </FooterLink> + <FooterLink href="/learn/">Quick Start</FooterLink> + <FooterLink href="/learn/installation">Installation</FooterLink> + <FooterLink href="/learn/describing-the-ui"> + Describing the UI + </FooterLink> + <FooterLink href="/learn/adding-interactivity"> + Adding Interactivity + </FooterLink> + <FooterLink href="/learn/managing-state"> + Managing State + </FooterLink> + <FooterLink href="/learn/escape-hatches"> + Escape Hatches + </FooterLink> + </div> + <div className="flex flex-col"> + <FooterLink href="/reference/react" isHeader={true}> + API Reference + </FooterLink> + <FooterLink href="/reference/react">React APIs</FooterLink> + <FooterLink href="/reference/react-dom"> + React DOM APIs + </FooterLink> + </div> + <div className="flex flex-col sm:col-start-2 xl:col-start-4"> + <FooterLink href="/" isHeader={true}> + Community + </FooterLink> + <FooterLink href="https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md"> + Code of Conduct + </FooterLink> + <FooterLink href="/community/acknowledgements"> + Acknowledgements + </FooterLink> + <FooterLink href="/community/docs-contributors"> + Docs Contributors + </FooterLink> + <FooterLink href="/community/team">Meet the Team</FooterLink> + <FooterLink href="https://reactjs.org/blog">Blog</FooterLink> + {/* <FooterLink href="/">Community Resources</FooterLink> */} + </div> + <div className="flex flex-col"> + <FooterLink isHeader={true}>More</FooterLink> + {/* <FooterLink href="/">Tutorial</FooterLink> */} + {/* <FooterLink href="/">Blog</FooterLink> */} + {/* <FooterLink href="/">Acknowledgements</FooterLink> */} + <FooterLink href="https://reactnative.dev/"> + React Native + </FooterLink> + <FooterLink href="https://opensource.facebook.com/legal/privacy"> + Privacy + </FooterLink> + <FooterLink href="https://opensource.fb.com/legal/terms/"> + Terms + </FooterLink> + <div className="flex flex-row mt-8 gap-x-2"> + <ExternalLink + aria-label="React on Facebook" + href="https://www.facebook.com/react" + className={socialLinkClasses}> + <IconFacebookCircle /> + </ExternalLink> + <ExternalLink + aria-label="React on Twitter" + href="https://twitter.com/reactjs" + className={socialLinkClasses}> + <IconTwitter /> + </ExternalLink> + <ExternalLink + aria-label="React on Github" + href="https://github.com/facebook/react" + className={socialLinkClasses}> + <IconGitHub /> + </ExternalLink> + </div> + </div> + </div> + </footer> + </div> + </> + ); +} + +function FooterLink({ + href, + children, + isHeader = false, +}: { + href?: string; + children: React.ReactNode; + isHeader?: boolean; +}) { + const classes = cn('border-b inline-block border-transparent', { + 'text-sm text-primary dark:text-primary-dark': !isHeader, + 'text-md text-secondary dark:text-secondary-dark my-2 font-bold': isHeader, + 'hover:border-gray-10': href, + }); + + if (!href) { + return <div className={classes}>{children}</div>; + } + + if (href.startsWith('https://')) { + return ( + <div> + <ExternalLink href={href} className={classes}> + {children} + </ExternalLink> + </div> + ); + } + + return ( + <div> + <NextLink href={href}> + <a className={classes}>{children}</a> + </NextLink> + </div> + ); +} diff --git a/beta/src/components/Layout/Nav/Nav.tsx b/beta/src/components/Layout/Nav/Nav.tsx new file mode 100644 index 000000000..4285ece7f --- /dev/null +++ b/beta/src/components/Layout/Nav/Nav.tsx @@ -0,0 +1,387 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useState, useRef, useContext, useEffect, Suspense} from 'react'; +import * as React from 'react'; +import cn from 'classnames'; +import NextLink from 'next/link'; +import {useRouter} from 'next/router'; +import {disableBodyScroll, enableBodyScroll} from 'body-scroll-lock'; + +import {IconClose} from 'components/Icon/IconClose'; +import {IconHamburger} from 'components/Icon/IconHamburger'; +import {Search} from 'components/Search'; +import {Logo} from '../../Logo'; +import {Feedback} from '../Feedback'; +import NavLink from './NavLink'; +import {SidebarRouteTree} from '../Sidebar/SidebarRouteTree'; +import type {RouteItem} from '../getRouteMeta'; +import sidebarLearn from '../../../sidebarLearn.json'; +import sidebarReference from '../../../sidebarReference.json'; + +declare global { + interface Window { + __theme: string; + __setPreferredTheme: (theme: string) => void; + } +} + +const feedbackIcon = ( + <svg + width="28" + height="28" + viewBox="0 0 28 28" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M8.41477 2.29921C8.41479 2.29923 8.41476 2.2992 8.41477 2.29921L8.48839 2.35275C8.91454 2.66267 9.22329 3.10774 9.36429 3.61547C9.50529 4.12319 9.47029 4.6637 9.26497 5.14899L8.33926 7.33703H11C11.7072 7.33703 12.3855 7.61798 12.8856 8.11807C13.3857 8.61817 13.6667 9.29645 13.6667 10.0037V12.6704C13.6667 13.5544 13.3155 14.4023 12.6904 15.0274C12.0652 15.6525 11.2174 16.0037 10.3333 16.0037H5C3.93914 16.0037 2.92172 15.5823 2.17157 14.8321C1.42142 14.082 1 13.0646 1 12.0037V10.7531C1 9.68422 1.36696 8.6477 2.03953 7.81688L6.27886 2.58006C6.53107 2.26851 6.89328 2.06562 7.29077 2.01335C7.68823 1.96109 8.09061 2.06347 8.41477 2.29921ZM7.63054 3.37753C7.58264 3.34269 7.52323 3.32759 7.46459 3.33531C7.40594 3.34302 7.35245 3.37296 7.31519 3.41899L3.07585 8.65581C2.59545 9.24925 2.33333 9.98963 2.33333 10.7531V12.0037C2.33333 12.7109 2.61428 13.3892 3.11438 13.8893C3.61448 14.3894 4.29275 14.6704 5 14.6704H10.3333C10.8638 14.6704 11.3725 14.4596 11.7475 14.0846C12.1226 13.7095 12.3333 13.2008 12.3333 12.6704V10.0037C12.3333 9.65007 12.1929 9.31093 11.9428 9.06088C11.6928 8.81084 11.3536 8.67036 11 8.67036H7.33333C7.10979 8.67036 6.90112 8.55832 6.77763 8.37198C6.65413 8.18564 6.63225 7.94981 6.71936 7.74393L8.03701 4.62947C8.125 4.42149 8.14001 4.18984 8.07958 3.97224C8.01916 3.75467 7.88687 3.56396 7.70425 3.43113L7.63054 3.37753Z" + fill="currentColor" + /> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M19.2517 25.7047C19.2517 25.7047 19.2517 25.7047 19.2517 25.7047L19.1781 25.6512C18.752 25.3412 18.4432 24.8962 18.3022 24.3884C18.1612 23.8807 18.1962 23.3402 18.4015 22.8549L19.3272 20.6669L16.6665 20.6669C15.9593 20.6669 15.281 20.3859 14.7809 19.8858C14.2808 19.3857 13.9998 18.7075 13.9998 18.0002L13.9998 15.3335C13.9998 14.4495 14.351 13.6016 14.9761 12.9765C15.6013 12.3514 16.4491 12.0002 17.3332 12.0002L22.6665 12.0002C23.7274 12.0002 24.7448 12.4216 25.4949 13.1718C26.2451 13.9219 26.6665 14.9393 26.6665 16.0002L26.6665 17.2508C26.6665 18.3197 26.2995 19.3562 25.627 20.187L21.3876 25.4238C21.1354 25.7354 20.7732 25.9383 20.3757 25.9906C19.9783 26.0428 19.5759 25.9404 19.2517 25.7047ZM20.036 24.6264C20.0839 24.6612 20.1433 24.6763 20.2019 24.6686C20.2606 24.6609 20.3141 24.6309 20.3513 24.5849L24.5907 19.3481C25.0711 18.7547 25.3332 18.0143 25.3332 17.2508L25.3332 16.0002C25.3332 15.293 25.0522 14.6147 24.5521 14.1146C24.052 13.6145 23.3738 13.3335 22.6665 13.3335L17.3332 13.3335C16.8027 13.3335 16.294 13.5443 15.919 13.9193C15.5439 14.2944 15.3332 14.8031 15.3332 15.3335L15.3332 18.0002C15.3332 18.3538 15.4736 18.693 15.7237 18.943C15.9737 19.1931 16.3129 19.3335 16.6665 19.3335L20.3332 19.3335C20.5567 19.3335 20.7654 19.4456 20.8889 19.6319C21.0124 19.8183 21.0343 20.0541 20.9471 20.26L19.6295 23.3744C19.5415 23.5824 19.5265 23.8141 19.5869 24.0317C19.6473 24.2492 19.7796 24.4399 19.9623 24.5728L20.036 24.6264Z" + fill="currentColor" + /> + </svg> +); + +const darkIcon = ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="18" + height="18" + viewBox="0 0 24 24"> + <g fill="none" fillRule="evenodd" transform="translate(-444 -204)"> + <path + fill="currentColor" + fillRule="nonzero" + d="M102,21 C102,18.1017141 103.307179,15.4198295 105.51735,13.6246624 C106.001939,13.2310647 105.821611,12.4522936 105.21334,12.3117518 C104.322006,12.1058078 103.414758,12 102.5,12 C95.8722864,12 90.5,17.3722864 90.5,24 C90.5,30.6277136 95.8722864,36 102.5,36 C106.090868,36 109.423902,34.4109093 111.690274,31.7128995 C112.091837,31.2348572 111.767653,30.5041211 111.143759,30.4810139 C106.047479,30.2922628 102,26.1097349 102,21 Z M102.5,34.5 C96.7007136,34.5 92,29.7992864 92,24 C92,18.2007136 96.7007136,13.5 102.5,13.5 C102.807386,13.5 103.113925,13.5136793 103.419249,13.5407785 C101.566047,15.5446378 100.5,18.185162 100.5,21 C100.5,26.3198526 104.287549,30.7714322 109.339814,31.7756638 L109.516565,31.8092927 C107.615276,33.5209452 105.138081,34.5 102.5,34.5 Z" + transform="translate(354.5 192)" + /> + <polygon points="444 228 468 228 468 204 444 204" /> + </g> + </svg> +); + +const lightIcon = ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24"> + <g fill="none" fillRule="evenodd" transform="translate(-444 -204)"> + <g fill="currentColor" transform="translate(354 144)"> + <path + fillRule="nonzero" + d="M108.5 24C108.5 27.5902136 105.590214 30.5 102 30.5 98.4097864 30.5 95.5 27.5902136 95.5 24 95.5 20.4097864 98.4097864 17.5 102 17.5 105.590214 17.5 108.5 20.4097864 108.5 24zM107 24C107 21.2382136 104.761786 19 102 19 99.2382136 19 97 21.2382136 97 24 97 26.7617864 99.2382136 29 102 29 104.761786 29 107 26.7617864 107 24zM101 12.75L101 14.75C101 15.1642136 101.335786 15.5 101.75 15.5 102.164214 15.5 102.5 15.1642136 102.5 14.75L102.5 12.75C102.5 12.3357864 102.164214 12 101.75 12 101.335786 12 101 12.3357864 101 12.75zM95.7255165 14.6323616L96.7485165 16.4038616C96.9556573 16.7625614 97.4143618 16.8854243 97.7730616 16.6782835 98.1317614 16.4711427 98.2546243 16.0124382 98.0474835 15.6537384L97.0244835 13.8822384C96.8173427 13.5235386 96.3586382 13.4006757 95.9999384 13.6078165 95.6412386 13.8149573 95.5183757 14.2736618 95.7255165 14.6323616zM91.8822384 19.0244835L93.6537384 20.0474835C94.0124382 20.2546243 94.4711427 20.1317614 94.6782835 19.7730616 94.8854243 19.4143618 94.7625614 18.9556573 94.4038616 18.7485165L92.6323616 17.7255165C92.2736618 17.5183757 91.8149573 17.6412386 91.6078165 17.9999384 91.4006757 18.3586382 91.5235386 18.8173427 91.8822384 19.0244835zM90.75 25L92.75 25C93.1642136 25 93.5 24.6642136 93.5 24.25 93.5 23.8357864 93.1642136 23.5 92.75 23.5L90.75 23.5C90.3357864 23.5 90 23.8357864 90 24.25 90 24.6642136 90.3357864 25 90.75 25zM92.6323616 30.2744835L94.4038616 29.2514835C94.7625614 29.0443427 94.8854243 28.5856382 94.6782835 28.2269384 94.4711427 27.8682386 94.0124382 27.7453757 93.6537384 27.9525165L91.8822384 28.9755165C91.5235386 29.1826573 91.4006757 29.6413618 91.6078165 30.0000616 91.8149573 30.3587614 92.2736618 30.4816243 92.6323616 30.2744835zM97.0244835 34.1177616L98.0474835 32.3462616C98.2546243 31.9875618 98.1317614 31.5288573 97.7730616 31.3217165 97.4143618 31.1145757 96.9556573 31.2374386 96.7485165 31.5961384L95.7255165 33.3676384C95.5183757 33.7263382 95.6412386 34.1850427 95.9999384 34.3921835 96.3586382 34.5993243 96.8173427 34.4764614 97.0244835 34.1177616zM103 35.25L103 33.25C103 32.8357864 102.664214 32.5 102.25 32.5 101.835786 32.5 101.5 32.8357864 101.5 33.25L101.5 35.25C101.5 35.6642136 101.835786 36 102.25 36 102.664214 36 103 35.6642136 103 35.25zM108.274483 33.3676384L107.251483 31.5961384C107.044343 31.2374386 106.585638 31.1145757 106.226938 31.3217165 105.868239 31.5288573 105.745376 31.9875618 105.952517 32.3462616L106.975517 34.1177616C107.182657 34.4764614 107.641362 34.5993243 108.000062 34.3921835 108.358761 34.1850427 108.481624 33.7263382 108.274483 33.3676384zM112.117762 28.9755165L110.346262 27.9525165C109.987562 27.7453757 109.528857 27.8682386 109.321717 28.2269384 109.114576 28.5856382 109.237439 29.0443427 109.596138 29.2514835L111.367638 30.2744835C111.726338 30.4816243 112.185043 30.3587614 112.392183 30.0000616 112.599324 29.6413618 112.476461 29.1826573 112.117762 28.9755165zM113.25 23L111.25 23C110.835786 23 110.5 23.3357864 110.5 23.75 110.5 24.1642136 110.835786 24.5 111.25 24.5L113.25 24.5C113.664214 24.5 114 24.1642136 114 23.75 114 23.3357864 113.664214 23 113.25 23zM111.367638 17.7255165L109.596138 18.7485165C109.237439 18.9556573 109.114576 19.4143618 109.321717 19.7730616 109.528857 20.1317614 109.987562 20.2546243 110.346262 20.0474835L112.117762 19.0244835C112.476461 18.8173427 112.599324 18.3586382 112.392183 17.9999384 112.185043 17.6412386 111.726338 17.5183757 111.367638 17.7255165zM106.975517 13.8822384L105.952517 15.6537384C105.745376 16.0124382 105.868239 16.4711427 106.226938 16.6782835 106.585638 16.8854243 107.044343 16.7625614 107.251483 16.4038616L108.274483 14.6323616C108.481624 14.2736618 108.358761 13.8149573 108.000062 13.6078165 107.641362 13.4006757 107.182657 13.5235386 106.975517 13.8822384z" + transform="translate(0 48)" + /> + <path + d="M98.6123,60.1372 C98.6123,59.3552 98.8753,58.6427 99.3368,58.0942 C99.5293,57.8657 99.3933,57.5092 99.0943,57.5017 C99.0793,57.5012 99.0633,57.5007 99.0483,57.5007 C97.1578,57.4747 95.5418,59.0312 95.5008,60.9217 C95.4578,62.8907 97.0408,64.5002 98.9998,64.5002 C99.7793,64.5002 100.4983,64.2452 101.0798,63.8142 C101.3183,63.6372 101.2358,63.2627 100.9478,63.1897 C99.5923,62.8457 98.6123,61.6072 98.6123,60.1372" + transform="translate(3 11)" + /> + </g> + <polygon points="444 228 468 228 468 204 444 204" /> + </g> + </svg> +); + +export default function Nav({ + routeTree, + breadcrumbs, + section, +}: { + routeTree: RouteItem; + breadcrumbs: RouteItem[]; + section: 'learn' | 'reference' | 'home'; +}) { + const [isOpen, setIsOpen] = useState(false); + const [showFeedback, setShowFeedback] = useState(false); + const scrollParentRef = useRef<HTMLDivElement>(null); + const feedbackAutohideRef = useRef<any>(null); + const {asPath} = useRouter(); + const feedbackPopupRef = useRef<null | HTMLDivElement>(null); + + // In mobile mode, let the user switch tabs there and back without navigating. + // Seed the tab state from the router, but keep it independent. + const [tab, setTab] = useState(section); + const [prevSection, setPrevSection] = useState(section); + if (prevSection !== section) { + setPrevSection(section); + setTab(section); + } + if (isOpen) { + switch (tab) { + case 'home': + case 'learn': + routeTree = sidebarLearn as RouteItem; + break; + case 'reference': + routeTree = sidebarReference as RouteItem; + break; + } + } + // HACK. Fix up the data structures instead. + if ((routeTree as any).routes.length === 1) { + routeTree = (routeTree as any).routes[0]; + } + + // While the overlay is open, disable body scroll. + useEffect(() => { + if (isOpen) { + const preferredScrollParent = scrollParentRef.current!; + disableBodyScroll(preferredScrollParent); + return () => enableBodyScroll(preferredScrollParent); + } else { + return undefined; + } + }, [isOpen]); + + // Close the overlay on any navigation. + useEffect(() => { + setIsOpen(false); + }, [asPath]); + + // Also close the overlay if the window gets resized past mobile layout. + // (This is also important because we don't want to keep the body locked!) + useEffect(() => { + const media = window.matchMedia(`(max-width: 1023px)`); + function closeIfNeeded() { + if (!media.matches) { + setIsOpen(false); + } + } + closeIfNeeded(); + media.addEventListener('change', closeIfNeeded); + return () => { + media.removeEventListener('change', closeIfNeeded); + }; + }, []); + + function handleFeedback() { + clearTimeout(feedbackAutohideRef.current); + setShowFeedback(!showFeedback); + } + + // Hide the Feedback widget on any click outside. + useEffect(() => { + if (!showFeedback) { + return; + } + function handleDocumentClickCapture(e: MouseEvent) { + if (!feedbackPopupRef.current!.contains(e.target as any)) { + e.stopPropagation(); + e.preventDefault(); + setShowFeedback(false); + } + } + document.addEventListener('click', handleDocumentClickCapture, { + capture: true, + }); + return () => + document.removeEventListener('click', handleDocumentClickCapture, { + capture: true, + }); + }, [showFeedback]); + + function selectTab(nextTab: 'learn' | 'reference') { + setTab(nextTab); + scrollParentRef.current!.scrollTop = 0; + } + + return ( + <div + className={cn( + 'sticky top-0 lg:bottom-0 lg:h-screen flex flex-col', + isOpen && 'h-screen' + )}> + <nav className="items-center w-full flex lg:block justify-between bg-wash dark:bg-wash-dark pt-0 lg:pt-4 pr-5 lg:px-5 z-50"> + <div className="xl:w-full xl:max-w-xs flex items-center"> + <button + type="button" + aria-label="Menu" + onClick={() => setIsOpen(!isOpen)} + className={cn('flex lg:hidden items-center h-full px-4', { + 'text-link dark:text-link-dark mr-0': isOpen, + })}> + {isOpen ? <IconClose /> : <IconHamburger />} + </button> + <NextLink href="/"> + <a className="inline-flex text-l font-normal items-center text-primary dark:text-primary-dark py-1 mr-0 sm:mr-3 whitespace-nowrap"> + <Logo className="text-sm mr-2 w-8 h-8 text-link dark:text-link-dark" /> + React Docs + </a> + </NextLink> + <div className="lg:w-full leading-loose hidden sm:flex flex-initial items-center h-auto pr-5 lg:pr-5 pt-0.5"> + <div className="px-1 mb-px bg-highlight dark:bg-highlight-dark rounded uppercase text-link dark:text-link-dark font-bold tracking-wide text-xs whitespace-nowrap"> + Beta + </div> + </div> + <div className="block dark:hidden"> + <button + type="button" + aria-label="Use Dark Mode" + onClick={() => { + window.__setPreferredTheme('dark'); + }} + className="hidden lg:flex items-center h-full pr-2"> + {darkIcon} + </button> + </div> + <div className="hidden dark:block"> + <button + type="button" + aria-label="Use Light Mode" + onClick={() => { + window.__setPreferredTheme('light'); + }} + className="hidden lg:flex items-center h-full pr-2"> + {lightIcon} + </button> + </div> + </div> + {!isOpen && ( + <div className="hidden lg:block sm:pt-10 lg:pt-4"> + <Search /> + </div> + )} + <div className="px-0 pt-2 w-full 2xl:max-w-xs hidden lg:flex items-center self-center border-b-0 lg:border-b border-border dark:border-border-dark"> + <NavLink + href="/learn" + isActive={section === 'learn' || section === 'home'}> + Learn + </NavLink> + <NavLink href="/reference/react" isActive={section === 'reference'}> + Reference + </NavLink> + </div> + <div className="flex my-4 h-10 mx-0 w-full lg:hidden justify-end lg:max-w-sm"> + <Search /> + <button + aria-label="Give feedback" + type="button" + className={cn( + 'inline-flex lg:hidden items-center rounded-full px-1.5 ml-4 lg:ml-6 relative top-px', + { + 'bg-card dark:bg-card-dark': showFeedback, + } + )} + onClick={handleFeedback}> + {feedbackIcon} + </button> + <div className="block dark:hidden"> + <button + type="button" + aria-label="Use Dark Mode" + onClick={() => { + window.__setPreferredTheme('dark'); + }} + className="flex lg:hidden items-center p-1 h-full ml-4 lg:ml-6"> + {darkIcon} + </button> + </div> + <div + ref={feedbackPopupRef} + className={cn( + 'fixed top-12 right-0', + showFeedback ? 'block' : 'hidden' + )}> + <Feedback + onSubmit={() => { + clearTimeout(feedbackAutohideRef.current); + feedbackAutohideRef.current = setTimeout(() => { + setShowFeedback(false); + }, 1000); + }} + /> + </div> + <div className="hidden dark:block"> + <button + type="button" + aria-label="Use Light Mode" + onClick={() => { + window.__setPreferredTheme('light'); + }} + className="flex lg:hidden items-center p-1 h-full ml-4 lg:ml-6"> + {lightIcon} + </button> + </div> + </div> + </nav> + + {isOpen && ( + <div className="bg-wash dark:bg-wash-dark px-5 flex justify-end border-b border-border dark:border-border-dark items-center self-center w-full z-10"> + <TabButton + isActive={tab === 'learn' || tab === 'home'} + onClick={() => selectTab('learn')}> + Learn + </TabButton> + <TabButton + isActive={tab === 'reference'} + onClick={() => selectTab('reference')}> + Reference + </TabButton> + </div> + )} + + <div + ref={scrollParentRef} + className="overflow-y-scroll no-bg-scrollbar lg:w-[336px] grow bg-wash dark:bg-wash-dark"> + <aside + className={cn( + `lg:grow lg:flex flex-col w-full pb-8 lg:pb-0 lg:max-w-xs z-10`, + isOpen ? 'block z-40' : 'hidden lg:block' + )}> + <nav + role="navigation" + style={{'--bg-opacity': '.2'} as React.CSSProperties} // Need to cast here because CSS vars aren't considered valid in TS types (cuz they could be anything) + className="w-full lg:h-auto grow pr-0 lg:pr-5 pt-6 lg:py-6 md:pt-4 lg:pt-4 scrolling-touch scrolling-gpu"> + {/* No fallback UI so need to be careful not to suspend directly inside. */} + <Suspense fallback={null}> + <SidebarRouteTree + // Don't share state between the desktop and mobile versions. + // This avoids unnecessary animations and visual flicker. + key={isOpen ? 'mobile-overlay' : 'desktop-or-hidden'} + routeTree={routeTree} + breadcrumbs={breadcrumbs} + isForceExpanded={isOpen} + /> + </Suspense> + <div className="h-20" /> + </nav> + <div className="fixed bottom-0 hidden lg:block"> + <Feedback /> + </div> + </aside> + </div> + </div> + ); +} + +function TabButton({ + children, + onClick, + isActive, +}: { + children: any; + onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; + isActive: boolean; +}) { + const classes = cn( + 'inline-flex items-center w-full border-b-2 justify-center text-base leading-9 px-3 pb-0.5 hover:text-link hover:gray-5', + { + 'text-link dark:text-link-dark dark:border-link-dark border-link font-bold': + isActive, + 'border-transparent': !isActive, + } + ); + return ( + <button className={classes} onClick={onClick}> + {children} + </button> + ); +} diff --git a/beta/src/components/Layout/Nav/NavLink.tsx b/beta/src/components/Layout/Nav/NavLink.tsx new file mode 100644 index 000000000..3fddc6e16 --- /dev/null +++ b/beta/src/components/Layout/Nav/NavLink.tsx @@ -0,0 +1,39 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import cn from 'classnames'; +import {ExternalLink} from 'components/ExternalLink'; +import NextLink from 'next/link'; + +interface NavLinkProps { + href: string; + children: React.ReactNode; + isActive: boolean; +} + +export default function NavLink({href, children, isActive}: NavLinkProps) { + const classes = cn( + { + 'text-link border-link dark:text-link-dark dark:border-link-dark font-bold': + isActive, + }, + {'border-transparent': !isActive}, + 'inline-flex w-full items-center border-b-2 justify-center text-base leading-9 px-3 py-0.5 hover:text-link dark:hover:text-link-dark whitespace-nowrap' + ); + + if (href.startsWith('https://')) { + return ( + <ExternalLink href={href} className={classes}> + {children} + </ExternalLink> + ); + } + + return ( + <NextLink href={href}> + <a className={classes}>{children}</a> + </NextLink> + ); +} diff --git a/beta/src/components/Layout/Nav/index.tsx b/beta/src/components/Layout/Nav/index.tsx new file mode 100644 index 000000000..0f4d0e78e --- /dev/null +++ b/beta/src/components/Layout/Nav/index.tsx @@ -0,0 +1,5 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +export {default as Nav} from './Nav'; diff --git a/beta/src/components/Layout/Page.tsx b/beta/src/components/Layout/Page.tsx new file mode 100644 index 000000000..ecb6cc903 --- /dev/null +++ b/beta/src/components/Layout/Page.tsx @@ -0,0 +1,91 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Suspense} from 'react'; +import * as React from 'react'; +import {useRouter} from 'next/router'; +import {Nav} from './Nav'; +import {Footer} from './Footer'; +import {Toc} from './Toc'; +import SocialBanner from '../SocialBanner'; +import {DocsPageFooter} from 'components/DocsFooter'; +import {Seo} from 'components/Seo'; +import PageHeading from 'components/PageHeading'; +import {getRouteMeta} from './getRouteMeta'; +import {TocContext} from '../MDX/TocContext'; +import sidebarLearn from '../../sidebarLearn.json'; +import sidebarReference from '../../sidebarReference.json'; +import type {TocItem} from 'components/MDX/TocContext'; +import type {RouteItem} from 'components/Layout/getRouteMeta'; + +import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); + +interface PageProps { + children: React.ReactNode; + toc: Array<TocItem>; + routeTree: RouteItem; + meta: {title?: string; description?: string}; + section: 'learn' | 'reference' | 'home'; +} + +export function Page({children, toc, routeTree, meta, section}: PageProps) { + const {asPath} = useRouter(); + const cleanedPath = asPath.split(/[\?\#]/)[0]; + const {route, nextRoute, prevRoute, breadcrumbs} = getRouteMeta( + cleanedPath, + routeTree + ); + const title = meta.title || route?.title || ''; + const description = meta.description || route?.description || ''; + const isHomePage = cleanedPath === '/'; + return ( + <> + <SocialBanner /> + <div className="grid grid-cols-only-content lg:grid-cols-sidebar-content 2xl:grid-cols-sidebar-content-toc"> + <div className="fixed lg:sticky top-0 left-0 right-0 py-0 shadow lg:shadow-none z-50"> + <Nav + routeTree={routeTree} + breadcrumbs={breadcrumbs} + section={section} + /> + </div> + {/* No fallback UI so need to be careful not to suspend directly inside. */} + <Suspense fallback={null}> + <main className="min-w-0"> + <div className="lg:hidden h-16 mb-2" /> + <article className="break-words" key={asPath}> + <div className="pl-0"> + <Seo title={title} isHomePage={isHomePage} /> + {!isHomePage && ( + <PageHeading + title={title} + description={description} + tags={route?.tags} + breadcrumbs={breadcrumbs} + /> + )} + <div className="px-5 sm:px-12"> + <div className="max-w-7xl mx-auto"> + <TocContext.Provider value={toc}> + {children} + </TocContext.Provider> + </div> + <DocsPageFooter + route={route} + nextRoute={nextRoute} + prevRoute={prevRoute} + /> + </div> + </div> + </article> + <Footer /> + </main> + </Suspense> + <div className="hidden lg:max-w-xs 2xl:block"> + {toc.length > 0 && <Toc headings={toc} key={asPath} />} + </div> + </div> + </> + ); +} diff --git a/beta/src/components/Layout/Sidebar/SidebarButton.tsx b/beta/src/components/Layout/Sidebar/SidebarButton.tsx new file mode 100644 index 000000000..b1276cdb8 --- /dev/null +++ b/beta/src/components/Layout/Sidebar/SidebarButton.tsx @@ -0,0 +1,57 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import cn from 'classnames'; +import {IconNavArrow} from 'components/Icon/IconNavArrow'; + +interface SidebarButtonProps { + title: string; + heading: boolean; + level: number; + onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; + isExpanded?: boolean; + isBreadcrumb?: boolean; +} + +export function SidebarButton({ + title, + heading, + level, + onClick, + isExpanded, + isBreadcrumb, +}: SidebarButtonProps) { + return ( + <div + className={cn({ + 'my-1': heading || level === 1, + 'my-3': level > 1, + })}> + <button + className={cn( + 'p-2 pr-2 pl-5 w-full rounded-r-lg text-left hover:bg-gray-5 dark:hover:bg-gray-80 relative flex items-center justify-between', + { + 'p-2 text-base': level > 1, + 'text-link bg-highlight dark:bg-highlight-dark text-base font-bold hover:bg-highlight dark:hover:bg-highlight-dark hover:text-link dark:hover:text-link-dark': + !heading && isBreadcrumb && !isExpanded, + 'p-4 my-6 text-2xl lg:my-auto lg:text-sm font-bold': heading, + 'p-2 hover:text-gray-70 text-base font-bold text-primary dark:text-primary-dark': + !heading && !isBreadcrumb, + 'text-primary dark:text-primary-dark': heading && !isBreadcrumb, + 'text-primary dark:text-primary-dark text-base font-bold bg-card dark:bg-card-dark': + !heading && isExpanded, + } + )} + onClick={onClick}> + {title} + {typeof isExpanded && !heading && ( + <span className="pr-2 text-gray-30"> + <IconNavArrow displayDirection={isExpanded ? 'down' : 'right'} /> + </span> + )} + </button> + </div> + ); +} diff --git a/beta/src/components/Layout/Sidebar/SidebarLink.tsx b/beta/src/components/Layout/Sidebar/SidebarLink.tsx new file mode 100644 index 000000000..de5ea8ea7 --- /dev/null +++ b/beta/src/components/Layout/Sidebar/SidebarLink.tsx @@ -0,0 +1,100 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import {useRef, useEffect} from 'react'; +import * as React from 'react'; +import cn from 'classnames'; +import {IconNavArrow} from 'components/Icon/IconNavArrow'; +import Link from 'next/link'; + +interface SidebarLinkProps { + href: string; + selected?: boolean; + title: string; + level: number; + wip: boolean | undefined; + icon?: React.ReactNode; + heading?: boolean; + isExpanded?: boolean; + isBreadcrumb?: boolean; + hideArrow?: boolean; + isPending: boolean; +} + +export function SidebarLink({ + href, + selected = false, + title, + wip, + level, + heading = false, + isExpanded, + isBreadcrumb, + hideArrow, + isPending, +}: SidebarLinkProps) { + const ref = useRef<HTMLAnchorElement>(null); + + useEffect(() => { + if (selected && ref && ref.current) { + // @ts-ignore + if (typeof ref.current.scrollIntoViewIfNeeded === 'function') { + // @ts-ignore + ref.current.scrollIntoViewIfNeeded(); + } + } + }, [ref, selected]); + + let target = ''; + if (href.startsWith('https://')) { + target = '_blank'; + } + return ( + <Link href={href}> + <a + ref={ref} + title={title} + target={target} + aria-current={selected ? 'page' : undefined} + className={cn( + 'p-2 pr-2 w-full rounded-none lg:rounded-r-lg text-left hover:bg-gray-5 dark:hover:bg-gray-80 relative flex items-center justify-between', + { + 'my-6': heading, + 'text-primary dark:text-primary-dark': heading && !isBreadcrumb, + 'text-sm pl-6': level > 0, + 'pl-5': level < 2, + 'text-base font-bold': level === 0, + 'dark:text-primary-dark text-primary ': level === 0 && !selected, + 'text-base text-link dark:text-link-dark': level === 1 && selected, + 'dark:text-primary-dark text-primary': heading, + 'text-base text-secondary dark:text-secondary-dark': + !selected && !heading, + 'text-base text-link dark:text-link-dark bg-highlight dark:bg-highlight-dark border-blue-40 hover:bg-highlight hover:text-link dark:hover:bg-highlight-dark dark:hover:text-link-dark': + selected, + 'dark:bg-gray-70 bg-gray-3 dark:hover:bg-gray-70 hover:bg-gray-3': + isPending, + } + )}> + {/* This here needs to be refactored ofc */} + <span + className={cn({ + 'text-gray-400 dark:text-gray-500': wip, + })}> + {title} + </span> + {isExpanded != null && !heading && !hideArrow && ( + <span + className={cn('pr-1', { + 'text-link': isExpanded, + 'text-gray-30': !isExpanded, + })}> + <IconNavArrow displayDirection={isExpanded ? 'down' : 'right'} /> + </span> + )} + </a> + </Link> + ); +} diff --git a/beta/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/beta/src/components/Layout/Sidebar/SidebarRouteTree.tsx new file mode 100644 index 000000000..5a3b519c2 --- /dev/null +++ b/beta/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -0,0 +1,170 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useRef, useLayoutEffect, Fragment} from 'react'; + +import cn from 'classnames'; +import {useRouter} from 'next/router'; +import {SidebarLink} from './SidebarLink'; +import useCollapse from 'react-collapsed'; +import usePendingRoute from 'hooks/usePendingRoute'; +import type {RouteItem} from 'components/Layout/getRouteMeta'; + +interface SidebarRouteTreeProps { + isForceExpanded: boolean; + breadcrumbs: RouteItem[]; + routeTree: RouteItem; + level?: number; +} + +function CollapseWrapper({ + isExpanded, + duration, + children, +}: { + isExpanded: boolean; + duration: number; + children: any; +}) { + const ref = useRef<HTMLDivElement | null>(null); + const timeoutRef = useRef<number | null>(null); + const {getCollapseProps} = useCollapse({ + isExpanded, + duration, + }); + + // Disable pointer events while animating. + const isExpandedRef = useRef(isExpanded); + if (typeof window !== 'undefined') { + // eslint-disable-next-line react-hooks/rules-of-hooks + useLayoutEffect(() => { + const wasExpanded = isExpandedRef.current; + if (wasExpanded === isExpanded) { + return; + } + isExpandedRef.current = isExpanded; + if (ref.current !== null) { + const node: HTMLDivElement = ref.current; + node.style.pointerEvents = 'none'; + if (timeoutRef.current !== null) { + window.clearTimeout(timeoutRef.current); + } + timeoutRef.current = window.setTimeout(() => { + node.style.pointerEvents = ''; + }, duration + 100); + } + }); + } + + return ( + <div + ref={ref} + className={cn(isExpanded ? 'opacity-100' : 'opacity-50')} + style={{ + transition: `opacity ${duration}ms ease-in-out`, + }}> + <div {...getCollapseProps()}>{children}</div> + </div> + ); +} + +export function SidebarRouteTree({ + isForceExpanded, + breadcrumbs, + routeTree, + level = 0, +}: SidebarRouteTreeProps) { + const slug = useRouter().asPath.split(/[\?\#]/)[0]; + const pendingRoute = usePendingRoute(); + const currentRoutes = routeTree.routes as RouteItem[]; + return ( + <ul> + {currentRoutes.map( + ( + {path, title, routes, wip, heading, hasSectionHeader, sectionHeader}, + index + ) => { + const selected = slug === path; + let listItem = null; + if (!path || !path || heading) { + // if current route item has no path and children treat it as an API sidebar heading + listItem = ( + <SidebarRouteTree + level={level + 1} + isForceExpanded={isForceExpanded} + routeTree={{title, routes}} + breadcrumbs={[]} + /> + ); + } else if (routes) { + // if route has a path and child routes, treat it as an expandable sidebar item + const isBreadcrumb = + breadcrumbs.length > 1 && + breadcrumbs[breadcrumbs.length - 1].path === path; + const isExpanded = isForceExpanded || isBreadcrumb || selected; + listItem = ( + <li key={`${title}-${path}-${level}-heading`}> + <SidebarLink + key={`${title}-${path}-${level}-link`} + href={path} + isPending={pendingRoute === path} + selected={selected} + level={level} + title={title} + wip={wip} + isExpanded={isExpanded} + isBreadcrumb={isBreadcrumb} + hideArrow={isForceExpanded} + /> + <CollapseWrapper duration={250} isExpanded={isExpanded}> + <SidebarRouteTree + isForceExpanded={isForceExpanded} + routeTree={{title, routes}} + breadcrumbs={breadcrumbs} + level={level + 1} + /> + </CollapseWrapper> + </li> + ); + } else { + // if route has a path and no child routes, treat it as a sidebar link + listItem = ( + <li key={`${title}-${path}-${level}-link`}> + <SidebarLink + isPending={pendingRoute === path} + href={path} + selected={selected} + level={level} + title={title} + wip={wip} + /> + </li> + ); + } + if (hasSectionHeader) { + return ( + <Fragment key={`${sectionHeader}-${level}-separator`}> + {index !== 0 && ( + <li + role="separator" + className="mt-4 mb-2 ml-5 border-b border-border dark:border-border-dark" + /> + )} + <h3 + className={cn( + 'mb-1 text-sm font-bold ml-5 text-gray-400 dark:text-gray-500', + index !== 0 && 'mt-2' + )}> + {sectionHeader} + </h3> + </Fragment> + ); + } else { + return listItem; + } + } + )} + </ul> + ); +} diff --git a/beta/src/components/Layout/Sidebar/index.tsx b/beta/src/components/Layout/Sidebar/index.tsx new file mode 100644 index 000000000..d0e291547 --- /dev/null +++ b/beta/src/components/Layout/Sidebar/index.tsx @@ -0,0 +1,7 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +export {SidebarButton} from './SidebarButton'; +export {SidebarLink} from './SidebarLink'; +export {SidebarRouteTree} from './SidebarRouteTree'; diff --git a/beta/src/components/Layout/Toc.tsx b/beta/src/components/Layout/Toc.tsx new file mode 100644 index 000000000..2ff779534 --- /dev/null +++ b/beta/src/components/Layout/Toc.tsx @@ -0,0 +1,60 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import cx from 'classnames'; +import {useTocHighlight} from './useTocHighlight'; +import type {Toc} from '../MDX/TocContext'; + +export function Toc({headings}: {headings: Toc}) { + const {currentIndex} = useTocHighlight(); + // TODO: We currently have a mismatch between the headings in the document + // and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges). + // Select the max TOC item we have here for now, but remove this after the fix. + const selectedIndex = Math.min(currentIndex, headings.length - 1); + return ( + <nav role="navigation" className="pt-[22px] sticky top-0 right-0"> + {headings.length > 0 && ( + <h2 className="mb-3 lg:mb-3 uppercase tracking-wide font-bold text-sm text-secondary dark:text-secondary-dark px-4 w-full"> + On this page + </h2> + )} + <div className="h-full overflow-y-auto pl-4 max-h-[calc(100vh-7.5rem)]"> + <ul className="space-y-2 pb-16"> + {headings.length > 0 && + headings.map((h, i) => { + if (h.url == null) { + // TODO: only log in DEV + console.error('Heading does not have URL'); + } + return ( + <li + key={`heading-${h.url}-${i}`} + className={cx( + 'text-sm px-2 rounded-l-lg', + selectedIndex === i + ? 'bg-highlight dark:bg-highlight-dark' + : null, + { + 'pl-4': h?.depth === 3, + hidden: h.depth && h.depth > 3, + } + )}> + <a + className={cx( + selectedIndex === i + ? 'text-link dark:text-link-dark font-bold' + : 'text-secondary dark:text-secondary-dark', + 'block hover:text-link dark:hover:text-link-dark leading-normal py-2' + )} + href={h.url}> + {h.text} + </a> + </li> + ); + })} + </ul> + </div> + </nav> + ); +} diff --git a/beta/src/components/Layout/getRouteMeta.tsx b/beta/src/components/Layout/getRouteMeta.tsx new file mode 100644 index 000000000..83432d5e9 --- /dev/null +++ b/beta/src/components/Layout/getRouteMeta.tsx @@ -0,0 +1,121 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/** + * While Next.js provides file-based routing, we still need to construct + * a sidebar for navigation and provide each markdown page + * previous/next links and titles. To do this, we construct a nested + * route object that is infinitely nestable. + */ + +export type RouteTag = + | 'foundation' + | 'intermediate' + | 'advanced' + | 'experimental' + | 'deprecated'; + +export interface RouteItem { + /** Page title (for the sidebar) */ + title: string; + /** Optional page description for heading */ + description?: string; + /* Additional meta info for page tagging */ + tags?: RouteTag[]; + /** Path to page */ + path?: string; + /** Whether the entry is a heading */ + heading?: boolean; + /** Whether the page is under construction */ + wip?: boolean; + /** List of sub-routes */ + routes?: RouteItem[]; + /** Adds a section header above the route item */ + hasSectionHeader?: boolean; + /** Title of section header */ + sectionHeader?: string; +} + +export interface Routes { + /** List of routes */ + routes: RouteItem[]; +} + +/** Routing metadata about a given route and it's siblings and parent */ +export interface RouteMeta { + /** The previous route */ + prevRoute?: RouteItem; + /** The next route */ + nextRoute?: RouteItem; + /** The current route */ + route?: RouteItem; + /** Trail of parent routes */ + breadcrumbs?: RouteItem[]; +} + +export function getRouteMeta(cleanedPath: string, routeTree: RouteItem) { + const breadcrumbs = getBreadcrumbs(cleanedPath, routeTree); + return { + ...buildRouteMeta(cleanedPath, routeTree, {}), + breadcrumbs: breadcrumbs.length > 0 ? breadcrumbs : [routeTree], + }; +} + +// Performs a depth-first search to find the current route and its previous/next route +function buildRouteMeta( + searchPath: string, + currentRoute: RouteItem, + ctx: RouteMeta +): RouteMeta { + const {routes} = currentRoute; + + if (ctx.route && !ctx.nextRoute) { + ctx.nextRoute = currentRoute; + } + + if (currentRoute.path === searchPath) { + ctx.route = currentRoute; + } + + if (!ctx.route) { + ctx.prevRoute = currentRoute; + } + + if (!routes) { + return ctx; + } + + for (const route of routes) { + buildRouteMeta(searchPath, route, ctx); + } + + return ctx; +} + +// iterates the route tree from the current route to find its ancestors for breadcrumbs +function getBreadcrumbs( + path: string, + currentRoute: RouteItem, + breadcrumbs: RouteItem[] = [] +): RouteItem[] { + if (currentRoute.path === path) { + return breadcrumbs; + } + + if (!currentRoute.routes) { + return []; + } + + for (const route of currentRoute.routes) { + const childRoute = getBreadcrumbs(path, route, [ + ...breadcrumbs, + currentRoute, + ]); + if (childRoute?.length) { + return childRoute; + } + } + + return []; +} diff --git a/beta/src/components/Layout/useTocHighlight.tsx b/beta/src/components/Layout/useTocHighlight.tsx new file mode 100644 index 000000000..b0879a143 --- /dev/null +++ b/beta/src/components/Layout/useTocHighlight.tsx @@ -0,0 +1,82 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useState, useRef, useEffect} from 'react'; + +const TOP_OFFSET = 75; + +export function getHeaderAnchors(): HTMLAnchorElement[] { + return Array.prototype.filter.call( + document.getElementsByClassName('mdx-header-anchor'), + function (testElement) { + return ( + testElement.parentNode.nodeName === 'H1' || + testElement.parentNode.nodeName === 'H2' || + testElement.parentNode.nodeName === 'H3' + ); + } + ); +} + +/** + * Sets up Table of Contents highlighting. + */ +export function useTocHighlight() { + const [currentIndex, setCurrentIndex] = useState<number>(0); + const timeoutRef = useRef<number | null>(null); + + useEffect(() => { + function updateActiveLink() { + const pageHeight = document.body.scrollHeight; + const scrollPosition = window.scrollY + window.innerHeight; + const headersAnchors = getHeaderAnchors(); + + if (scrollPosition >= 0 && pageHeight - scrollPosition <= TOP_OFFSET) { + // Scrolled to bottom of page. + setCurrentIndex(headersAnchors.length - 1); + return; + } + + let index = -1; + while (index < headersAnchors.length - 1) { + const headerAnchor = headersAnchors[index + 1]; + const {top} = headerAnchor.getBoundingClientRect(); + + if (top >= TOP_OFFSET) { + break; + } + index += 1; + } + + setCurrentIndex(Math.max(index, 0)); + } + + function throttledUpdateActiveLink() { + if (timeoutRef.current === null) { + timeoutRef.current = window.setTimeout(() => { + timeoutRef.current = null; + updateActiveLink(); + }, 100); + } + } + + document.addEventListener('scroll', throttledUpdateActiveLink); + document.addEventListener('resize', throttledUpdateActiveLink); + + updateActiveLink(); + + return () => { + if (timeoutRef.current != null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + document.removeEventListener('scroll', throttledUpdateActiveLink); + document.removeEventListener('resize', throttledUpdateActiveLink); + }; + }, []); + + return { + currentIndex, + }; +} diff --git a/beta/src/components/Logo.tsx b/beta/src/components/Logo.tsx new file mode 100644 index 000000000..67469c0ed --- /dev/null +++ b/beta/src/components/Logo.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +export function Logo(props: JSX.IntrinsicElements['svg']) { + return ( + <svg + width="100%" + height="100%" + viewBox="0 0 410 369" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props}> + <path + d="M204.995 224.552C226.56 224.552 244.042 207.07 244.042 185.506C244.042 163.941 226.56 146.459 204.995 146.459C183.43 146.459 165.948 163.941 165.948 185.506C165.948 207.07 183.43 224.552 204.995 224.552Z" + fill="currentColor" + /> + <path + d="M409.99 184.505C409.99 153.707 381.437 126.667 335.996 108.925C343.342 60.6535 334.19 22.3878 307.492 6.98883C283.649 -6.77511 250.631 -0.0395641 214.512 25.9753C211.316 28.2692 208.143 30.7097 204.97 33.2477C201.822 30.7097 198.65 28.2692 195.477 25.9753C159.359 -0.0395641 126.34 -6.79951 102.497 6.98883C75.8237 22.3878 66.6721 60.6291 74.0422 108.852C28.5529 126.618 0 153.682 0 184.505C0 215.303 28.5528 242.342 73.9934 260.084C66.6477 308.356 75.7993 346.621 102.497 362.02C110.575 366.682 119.727 369 129.684 369C149.085 369 171.61 360.215 195.477 343.034C198.674 340.74 201.847 338.3 205.019 335.762C208.167 338.3 211.34 340.74 214.512 343.034C238.38 360.239 260.905 369 280.306 369C290.263 369 299.415 366.682 307.492 362.02C331.335 348.256 342 316.287 337.534 271.993C337.143 268.089 336.631 264.135 335.996 260.109C381.461 242.367 409.99 215.327 409.99 184.505ZM225.934 41.8136C246.238 27.1955 265.127 19.5814 280.306 19.5814C286.871 19.5814 292.728 20.9968 297.731 23.8765C315.204 33.9798 322.672 62.9475 317.327 102.433C299.756 97.0401 280.306 92.9158 259.392 90.2802C246.872 73.8074 233.597 58.9453 220.003 46.2551C221.98 44.7421 223.957 43.229 225.934 41.8136ZM112.259 23.8765C117.262 20.9968 123.119 19.5814 129.684 19.5814C144.863 19.5814 163.752 27.1711 184.056 41.8136C186.033 43.229 188.01 44.7176 189.986 46.2551C176.393 58.9453 163.142 73.783 150.622 90.2558C129.732 92.8914 110.258 97.0401 92.687 102.409C87.3424 62.9475 94.7857 33.9798 112.259 23.8765ZM19.5233 184.505C19.5233 164.322 40.9014 143.359 77.776 128.253C81.9003 146.141 88.0502 165.054 96.1768 184.456C88.0014 203.881 81.8515 222.819 77.7272 240.732C40.9014 225.626 19.5233 204.687 19.5233 184.505ZM184.056 327.196C154.966 348.134 128.805 354.675 112.259 345.133C94.7857 335.029 87.3181 306.062 92.6626 266.576C110.234 271.969 129.684 276.093 150.598 278.729C163.117 295.202 176.393 310.064 189.986 322.754C188.01 324.292 186.033 325.78 184.056 327.196ZM204.995 310.04C180.591 287.685 157.138 257.815 137.347 223.551C132.051 214.4 121.344 191.396 117 182.489C113.535 190.786 110.112 198.398 107.427 206.5C109.623 210.575 118.092 229.213 120.434 233.288C125.071 241.317 129.928 249.127 134.931 256.692C120.898 254.227 107.915 251.055 96.1035 247.321C102.815 217.011 116.213 182.064 137.347 145.458C142.545 136.453 153.838 116.346 159.5 108C150.568 109.147 143.395 108.767 135 110.5C132.56 114.453 122.777 131.645 120.434 135.721C115.749 143.823 111.454 151.925 107.427 159.978C102.546 146.581 98.8124 133.744 96.1524 121.64C125.755 112.293 162.727 106.411 204.995 106.411C215.562 106.411 237.63 106.197 247.49 106.905C242.048 99.7544 237.38 93.2819 231.694 86.888C227.082 86.7416 209.705 86.888 204.995 86.888C195.672 86.888 186.545 87.2053 177.589 87.7422C186.472 77.1752 195.672 67.5111 204.995 58.9697C229.375 81.3239 252.851 111.195 272.643 145.458C277.841 154.463 289.073 175.426 293.49 184.505C296.98 176.207 300.281 168.64 302.99 160.489C300.793 156.389 291.898 139.747 289.555 135.696C284.918 127.667 280.062 119.858 275.059 112.317C289.092 114.782 302.075 117.954 313.886 121.688C307.175 151.998 293.777 186.945 272.643 223.551C267.445 232.556 252.651 253.178 246.99 261.524C255.922 260.377 265.595 258.663 273.99 256.93C276.43 252.976 287.212 237.364 289.555 233.288C294.216 225.235 298.512 217.182 302.489 209.153C307.224 222.185 310.982 234.997 313.715 247.394C284.138 256.741 247.214 262.598 204.995 262.598C194.428 262.598 169.859 261.208 160 260.5C165.442 267.65 171.304 275.095 176.99 281.489C181.602 281.635 200.285 282.121 204.995 282.121C214.317 282.121 223.444 281.804 232.401 281.267C223.493 291.834 214.317 301.498 204.995 310.04ZM297.731 345.133C281.185 354.699 254.999 348.159 225.934 327.196C223.957 325.78 221.98 324.292 220.003 322.754C233.597 310.064 246.848 295.226 259.367 278.753C280.233 276.118 299.659 271.993 317.205 266.625C317.547 269.089 317.888 271.554 318.132 273.97C321.72 309.649 314.277 335.566 297.731 345.133ZM332.262 240.756C328.065 222.599 321.842 203.686 313.813 184.578C321.988 165.152 328.138 146.215 332.262 128.302C369.088 143.408 390.466 164.322 390.466 184.505C390.466 204.687 369.113 225.626 332.262 240.756Z" + fill="currentColor" + /> + </svg> + ); +} diff --git a/beta/src/components/MDX/Challenges/Challenge.tsx b/beta/src/components/MDX/Challenges/Challenge.tsx new file mode 100644 index 000000000..24e99541c --- /dev/null +++ b/beta/src/components/MDX/Challenges/Challenge.tsx @@ -0,0 +1,133 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useState} from 'react'; +import cn from 'classnames'; +import {Button} from 'components/Button'; +import {ChallengeContents} from './Challenges'; +import {IconHint} from '../../Icon/IconHint'; +import {IconSolution} from '../../Icon/IconSolution'; +import {IconArrowSmall} from '../../Icon/IconArrowSmall'; +import {H4} from '../Heading'; + +interface ChallengeProps { + isRecipes?: boolean; + totalChallenges: number; + currentChallenge: ChallengeContents; + hasNextChallenge: boolean; + handleClickNextChallenge: () => void; +} + +export function Challenge({ + isRecipes, + totalChallenges, + currentChallenge, + hasNextChallenge, + handleClickNextChallenge, +}: ChallengeProps) { + const [showHint, setShowHint] = useState(false); + const [showSolution, setShowSolution] = useState(false); + + const toggleHint = () => { + if (showSolution && !showHint) { + setShowSolution(false); + } + setShowHint((hint) => !hint); + }; + + const toggleSolution = () => { + if (showHint && !showSolution) { + setShowHint(false); + } + setShowSolution((solution) => !solution); + }; + + return ( + <div className="p-5 sm:py-8 sm:px-8"> + <div> + <H4 + className="text-xl text-primary dark:text-primary-dark mb-2 mt-0 font-medium" + id={currentChallenge.id}> + <div className="font-bold block md:inline"> + {isRecipes ? 'Example' : 'Challenge'} {currentChallenge.order} of{' '} + {totalChallenges} + <span className="text-primary dark:text-primary-dark">: </span> + </div> + {currentChallenge.name} + </H4> + {currentChallenge.content} + </div> + <div className="flex justify-between items-center mt-4"> + {currentChallenge.hint ? ( + <div> + <Button className="mr-2" onClick={toggleHint} active={showHint}> + <IconHint className="mr-1.5" />{' '} + {showHint ? 'Hide hint' : 'Show hint'} + </Button> + <Button + className="mr-2" + onClick={toggleSolution} + active={showSolution}> + <IconSolution className="mr-1.5" />{' '} + {showSolution ? 'Hide solution' : 'Show solution'} + </Button> + </div> + ) : ( + !isRecipes && ( + <Button + className="mr-2" + onClick={toggleSolution} + active={showSolution}> + <IconSolution className="mr-1.5" />{' '} + {showSolution ? 'Hide solution' : 'Show solution'} + </Button> + ) + )} + + {hasNextChallenge && ( + <Button + className={cn( + isRecipes + ? 'bg-purple-50 border-purple-50 hover:bg-purple-50 focus:bg-purple-50 active:bg-purple-50' + : 'bg-link dark:bg-link-dark' + )} + onClick={handleClickNextChallenge} + active> + Next {isRecipes ? 'Example' : 'Challenge'} + <IconArrowSmall displayDirection="right" className="block ml-1.5" /> + </Button> + )} + </div> + {showHint && currentChallenge.hint} + + {showSolution && ( + <div className="mt-6"> + <h3 className="text-2xl font-bold text-primary dark:text-primary-dark"> + Solution + </h3> + {currentChallenge.solution} + <div className="flex justify-between items-center mt-4"> + <Button onClick={() => setShowSolution(false)}> + Close solution + </Button> + {hasNextChallenge && ( + <Button + className={cn( + isRecipes ? 'bg-purple-50' : 'bg-link dark:bg-link-dark' + )} + onClick={handleClickNextChallenge} + active> + Next Challenge + <IconArrowSmall + displayDirection="right" + className="block ml-1.5" + /> + </Button> + )} + </div> + </div> + )} + </div> + ); +} diff --git a/beta/src/components/MDX/Challenges/Challenges.tsx b/beta/src/components/MDX/Challenges/Challenges.tsx new file mode 100644 index 000000000..25b1979fd --- /dev/null +++ b/beta/src/components/MDX/Challenges/Challenges.tsx @@ -0,0 +1,157 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Children, useRef, useEffect, useState} from 'react'; +import * as React from 'react'; +import cn from 'classnames'; +import {H2} from 'components/MDX/Heading'; +import {H4} from 'components/MDX/Heading'; +import {Challenge} from './Challenge'; +import {Navigation} from './Navigation'; +import {useRouter} from 'next/router'; + +interface ChallengesProps { + children: React.ReactElement[]; + isRecipes?: boolean; + titleText?: string; + titleId?: string; +} + +export interface ChallengeContents { + id: string; + name: string; + order: number; + content: React.ReactNode; + solution: React.ReactNode; + hint?: React.ReactNode; +} + +const parseChallengeContents = ( + children: React.ReactElement[] +): ChallengeContents[] => { + const contents: ChallengeContents[] = []; + + if (!children) { + return contents; + } + + let challenge: Partial<ChallengeContents> = {}; + let content: React.ReactElement[] = []; + Children.forEach(children, (child) => { + const {props, type} = child; + switch ((type as any).mdxName) { + case 'Solution': { + challenge.solution = child; + challenge.content = content; + contents.push(challenge as ChallengeContents); + challenge = {}; + content = []; + break; + } + case 'Hint': { + challenge.hint = child; + break; + } + case 'h4': { + challenge.order = contents.length + 1; + challenge.name = props.children; + challenge.id = props.id; + break; + } + default: { + content.push(child); + } + } + }); + + return contents; +}; + +enum QueuedScroll { + INIT = 'init', + NEXT = 'next', +} + +export function Challenges({ + children, + isRecipes, + titleText = isRecipes ? 'Try out some examples' : 'Try out some challenges', + titleId = isRecipes ? 'examples' : 'challenges', +}: ChallengesProps) { + const challenges = parseChallengeContents(children); + const totalChallenges = challenges.length; + const scrollAnchorRef = useRef<HTMLDivElement>(null); + const queuedScrollRef = useRef<undefined | QueuedScroll>(QueuedScroll.INIT); + const [activeIndex, setActiveIndex] = useState(0); + const currentChallenge = challenges[activeIndex]; + const {asPath} = useRouter(); + + useEffect(() => { + if (queuedScrollRef.current === QueuedScroll.INIT) { + const initIndex = challenges.findIndex( + (challenge) => challenge.id === asPath.split('#')[1] + ); + if (initIndex === -1) { + queuedScrollRef.current = undefined; + } else if (initIndex !== activeIndex) { + setActiveIndex(initIndex); + } + } + if (queuedScrollRef.current) { + scrollAnchorRef.current!.scrollIntoView({ + block: 'start', + ...(queuedScrollRef.current === QueuedScroll.NEXT && { + behavior: 'smooth', + }), + }); + queuedScrollRef.current = undefined; + } + }, [activeIndex, asPath, challenges]); + + const handleChallengeChange = (index: number) => { + setActiveIndex(index); + }; + + const Heading = isRecipes ? H4 : H2; + return ( + <div className="max-w-7xl mx-auto py-4"> + <div + className={cn( + 'border-gray-10 bg-card dark:bg-card-dark shadow-inner rounded-none -mx-5 sm:mx-auto sm:rounded-lg' + )}> + <div ref={scrollAnchorRef} className="py-2 px-5 sm:px-8 pb-0 md:pb-0"> + <Heading + id={titleId} + className={cn( + 'mb-2 leading-10 relative', + isRecipes + ? 'text-xl text-purple-50 dark:text-purple-30' + : 'text-3xl text-link' + )}> + {titleText} + </Heading> + {totalChallenges > 1 && ( + <Navigation + currentChallenge={currentChallenge} + challenges={challenges} + handleChange={handleChallengeChange} + isRecipes={isRecipes} + /> + )} + </div> + <Challenge + key={currentChallenge.id} + isRecipes={isRecipes} + currentChallenge={currentChallenge} + totalChallenges={totalChallenges} + hasNextChallenge={activeIndex < totalChallenges - 1} + handleClickNextChallenge={() => { + setActiveIndex((i) => i + 1); + queuedScrollRef.current = QueuedScroll.NEXT; + }} + /> + </div> + </div> + ); +} diff --git a/beta/src/components/MDX/Challenges/Navigation.tsx b/beta/src/components/MDX/Challenges/Navigation.tsx new file mode 100644 index 000000000..e448828cf --- /dev/null +++ b/beta/src/components/MDX/Challenges/Navigation.tsx @@ -0,0 +1,134 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useRef, useCallback, useEffect, createRef} from 'react'; +import cn from 'classnames'; +import {IconChevron} from 'components/Icon/IconChevron'; +import {ChallengeContents} from './Challenges'; +import {debounce} from 'debounce'; + +export function Navigation({ + challenges, + handleChange, + currentChallenge, + isRecipes, +}: { + challenges: ChallengeContents[]; + handleChange: (index: number) => void; + currentChallenge: ChallengeContents; + isRecipes?: boolean; +}) { + const containerRef = useRef<HTMLDivElement>(null); + const challengesNavRef = useRef( + challenges.map(() => createRef<HTMLButtonElement>()) + ); + const scrollPos = currentChallenge.order - 1; + const canScrollLeft = scrollPos > 0; + const canScrollRight = scrollPos < challenges.length - 1; + + const handleScrollRight = () => { + if (scrollPos < challenges.length - 1) { + const currentNavRef = challengesNavRef.current[scrollPos + 1].current; + if (!currentNavRef) { + return; + } + if (containerRef.current) { + containerRef.current.scrollLeft = currentNavRef.offsetLeft; + } + handleChange(scrollPos + 1); + } + }; + + const handleScrollLeft = () => { + if (scrollPos > 0) { + const currentNavRef = challengesNavRef.current[scrollPos - 1].current; + if (!currentNavRef) { + return; + } + if (containerRef.current) { + containerRef.current.scrollLeft = currentNavRef.offsetLeft; + } + handleChange(scrollPos - 1); + } + }; + + const handleSelectNav = (index: number) => { + const currentNavRef = challengesNavRef.current[index].current; + if (containerRef.current) { + containerRef.current.scrollLeft = currentNavRef?.offsetLeft || 0; + } + handleChange(index); + }; + + const handleResize = useCallback(() => { + if (containerRef.current) { + const el = containerRef.current; + el.scrollLeft = + challengesNavRef.current[scrollPos].current?.offsetLeft || 0; + } + }, [containerRef, challengesNavRef, scrollPos]); + + useEffect(() => { + handleResize(); + const debouncedHandleResize = debounce(handleResize, 200); + window.addEventListener('resize', debouncedHandleResize); + return () => { + window.removeEventListener('resize', debouncedHandleResize); + }; + }, [handleResize]); + + return ( + <div className="flex items-center justify-between"> + <div className="overflow-hidden"> + <div + ref={containerRef} + className="flex relative transition-transform content-box overflow-x-auto"> + {challenges.map(({name, id, order}, index) => ( + <button + className={cn( + 'py-2 mr-4 text-base border-b-4 duration-100 ease-in transition whitespace-nowrap text-ellipsis', + isRecipes && + currentChallenge.id === id && + 'text-purple-50 border-purple-50 hover:text-purple-50 dark:text-purple-30 dark:border-purple-30 dark:hover:text-purple-30', + !isRecipes && + currentChallenge.id === id && + 'text-link border-link hover:text-link dark:text-link-dark dark:border-link-dark dark:hover:text-link-dark' + )} + onClick={() => handleSelectNav(index)} + key={`button-${id}`} + ref={challengesNavRef.current[index]}> + {order}. {name} + </button> + ))} + </div> + </div> + <div className="flex z-10 pb-2 pl-2"> + <button + onClick={handleScrollLeft} + aria-label="Scroll left" + className={cn( + 'bg-secondary-button dark:bg-secondary-button-dark h-8 px-2 rounded-l border-gray-20 border-r', + { + 'text-primary dark:text-primary-dark': canScrollLeft, + 'text-gray-30': !canScrollLeft, + } + )}> + <IconChevron displayDirection="left" /> + </button> + <button + onClick={handleScrollRight} + aria-label="Scroll right" + className={cn( + 'bg-secondary-button dark:bg-secondary-button-dark h-8 px-2 rounded-r', + { + 'text-primary dark:text-primary-dark': canScrollRight, + 'text-gray-30': !canScrollRight, + } + )}> + <IconChevron displayDirection="right" /> + </button> + </div> + </div> + ); +} diff --git a/beta/src/components/MDX/Challenges/index.tsx b/beta/src/components/MDX/Challenges/index.tsx new file mode 100644 index 000000000..413fd4611 --- /dev/null +++ b/beta/src/components/MDX/Challenges/index.tsx @@ -0,0 +1,14 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +export {Challenges} from './Challenges'; + +export function Hint({children}: {children: React.ReactNode}) { + return <div>{children}</div>; +} + +export function Solution({children}: {children: React.ReactNode}) { + return <div>{children}</div>; +} diff --git a/beta/src/components/MDX/CodeBlock/CodeBlock.tsx b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx new file mode 100644 index 000000000..dfd680563 --- /dev/null +++ b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -0,0 +1,388 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import cn from 'classnames'; +import {highlightTree, HighlightStyle, tags} from '@codemirror/highlight'; +import {javascript} from '@codemirror/lang-javascript'; +import {html} from '@codemirror/lang-html'; +import {css} from '@codemirror/lang-css'; +import rangeParser from 'parse-numeric-range'; +import {CustomTheme} from '../Sandpack/Themes'; + +interface InlineHiglight { + step: number; + line: number; + startColumn: number; + endColumn: number; +} + +const jsxLang = javascript({jsx: true, typescript: false}); +const cssLang = css(); +const htmlLang = html(); + +const CodeBlock = function CodeBlock({ + children: { + props: {className = 'language-js', children: code = '', meta}, + }, + noMargin, +}: { + children: React.ReactNode & { + props: { + className: string; + children?: string; + meta?: string; + }; + }; + className?: string; + noMargin?: boolean; +}) { + code = code.trimEnd(); + let lang = jsxLang; + if (className === 'language-css') { + lang = cssLang; + } else if (className === 'language-html') { + lang = htmlLang; + } + const tree = lang.language.parser.parse(code); + let tokenStarts = new Map(); + let tokenEnds = new Map(); + const highlightTheme = getSyntaxHighlight(CustomTheme); + highlightTree(tree, highlightTheme.match, (from, to, className) => { + tokenStarts.set(from, className); + tokenEnds.set(to, className); + }); + + const highlightedLines = new Map(); + const lines = code.split('\n'); + const lineDecorators = getLineDecorators(code, meta); + for (let decorator of lineDecorators) { + highlightedLines.set(decorator.line - 1, decorator.className); + } + + const inlineDecorators = getInlineDecorators(code, meta); + const decoratorStarts = new Map(); + const decoratorEnds = new Map(); + for (let decorator of inlineDecorators) { + // Find where inline highlight starts and ends. + let decoratorStart = 0; + for (let i = 0; i < decorator.line - 1; i++) { + decoratorStart += lines[i].length + 1; + } + decoratorStart += decorator.startColumn; + const decoratorEnd = + decoratorStart + (decorator.endColumn - decorator.startColumn); + if (decoratorStarts.has(decoratorStart)) { + throw Error('Already opened decorator at ' + decoratorStart); + } + decoratorStarts.set(decoratorStart, decorator.className); + if (decoratorEnds.has(decoratorEnd)) { + throw Error('Already closed decorator at ' + decoratorEnd); + } + decoratorEnds.set(decoratorEnd, decorator.className); + } + + // Produce output based on tokens and decorators. + // We assume tokens never overlap other tokens, and + // decorators never overlap with other decorators. + // However, tokens and decorators may mutually overlap. + // In that case, decorators always take precedence. + let currentDecorator = null; + let currentToken = null; + let buffer = ''; + let lineIndex = 0; + let lineOutput = []; + let finalOutput = []; + for (let i = 0; i < code.length; i++) { + if (tokenEnds.has(i)) { + if (!currentToken) { + throw Error('Cannot close token at ' + i + ' because it was not open.'); + } + if (!currentDecorator) { + lineOutput.push( + <span key={i + '/t'} className={currentToken}> + {buffer} + </span> + ); + buffer = ''; + } + currentToken = null; + } + if (decoratorEnds.has(i)) { + if (!currentDecorator) { + throw Error( + 'Cannot close decorator at ' + i + ' because it was not open.' + ); + } + lineOutput.push( + <span key={i + '/d'} className={currentDecorator}> + {buffer} + </span> + ); + buffer = ''; + currentDecorator = null; + } + if (decoratorStarts.has(i)) { + if (currentDecorator) { + throw Error( + 'Cannot open decorator at ' + i + ' before closing last one.' + ); + } + if (currentToken) { + lineOutput.push( + <span key={i + 'd'} className={currentToken}> + {buffer} + </span> + ); + buffer = ''; + } else { + lineOutput.push(buffer); + buffer = ''; + } + currentDecorator = decoratorStarts.get(i); + } + if (tokenStarts.has(i)) { + if (currentToken) { + throw Error('Cannot open token at ' + i + ' before closing last one.'); + } + currentToken = tokenStarts.get(i); + if (!currentDecorator) { + lineOutput.push(buffer); + buffer = ''; + } + } + if (code[i] === '\n') { + lineOutput.push(buffer); + buffer = ''; + finalOutput.push( + <div + key={lineIndex} + className={'cm-line ' + (highlightedLines.get(lineIndex) ?? '')}> + {lineOutput} + <br /> + </div> + ); + lineOutput = []; + lineIndex++; + } else { + buffer += code[i]; + } + } + if (currentDecorator) { + lineOutput.push( + <span key={'end/d'} className={currentDecorator}> + {buffer} + </span> + ); + } else if (currentToken) { + lineOutput.push( + <span key={'end/t'} className={currentToken}> + {buffer} + </span> + ); + } else { + lineOutput.push(buffer); + } + finalOutput.push( + <div + key={lineIndex} + className={'cm-line ' + (highlightedLines.get(lineIndex) ?? '')}> + {lineOutput} + </div> + ); + + return ( + <div + className={cn( + 'sandpack sandpack--codeblock', + 'rounded-lg h-full w-full overflow-x-auto flex items-center bg-wash dark:bg-gray-95 shadow-lg', + !noMargin && 'my-8' + )}> + <div className="sp-wrapper"> + <div className="sp-stack"> + <div className="sp-code-editor"> + <pre className="sp-cm sp-pristine sp-javascript flex align-start"> + <code className="sp-pre-placeholder grow-[2]">{finalOutput}</code> + </pre> + </div> + </div> + </div> + </div> + ); +}; + +export default CodeBlock; + +function classNameToken(name: string): string { + return `sp-syntax-${name}`; +} + +function getSyntaxHighlight(theme: any): HighlightStyle { + return HighlightStyle.define([ + {tag: tags.link, textdecorator: 'underline'}, + {tag: tags.emphasis, fontStyle: 'italic'}, + {tag: tags.strong, fontWeight: 'bold'}, + + { + tag: tags.keyword, + class: classNameToken('keyword'), + }, + { + tag: [tags.atom, tags.number, tags.bool], + class: classNameToken('static'), + }, + { + tag: tags.standard(tags.tagName), + class: classNameToken('tag'), + }, + {tag: tags.variableName, class: classNameToken('plain')}, + { + // Highlight function call + tag: tags.function(tags.variableName), + class: classNameToken('definition'), + }, + { + // Highlight function definition differently (eg: functional component def in React) + tag: [tags.definition(tags.function(tags.variableName)), tags.tagName], + class: classNameToken('definition'), + }, + { + tag: tags.propertyName, + class: classNameToken('property'), + }, + { + tag: [tags.literal, tags.inserted], + class: classNameToken(theme.syntax.string ? 'string' : 'static'), + }, + { + tag: tags.punctuation, + class: classNameToken('punctuation'), + }, + { + tag: [tags.comment, tags.quote], + class: classNameToken('comment'), + }, + ]); +} + +function getLineDecorators( + code: string, + meta: string +): Array<{ + line: number; + className: string; +}> { + if (!meta) { + return []; + } + const linesToHighlight = getHighlightLines(meta); + const highlightedLineConfig = linesToHighlight.map((line) => { + return { + className: 'bg-github-highlight dark:bg-opacity-10', + line, + }; + }); + return highlightedLineConfig; +} + +function getInlineDecorators( + code: string, + meta: string +): Array<{ + step: number; + line: number; + startColumn: number; + endColumn: number; + className: string; +}> { + if (!meta) { + return []; + } + const inlineHighlightLines = getInlineHighlights(meta, code); + const inlineHighlightConfig = inlineHighlightLines.map( + (line: InlineHiglight) => ({ + ...line, + elementAttributes: {'data-step': `${line.step}`}, + className: cn( + 'code-step bg-opacity-10 dark:bg-opacity-20 relative rounded px-1 py-[1.5px] border-b-[2px] border-opacity-60', + { + 'bg-blue-40 border-blue-40 text-blue-60 dark:text-blue-30': + line.step === 1, + 'bg-yellow-40 border-yellow-40 text-yellow-60 dark:text-yellow-30': + line.step === 2, + 'bg-purple-40 border-purple-40 text-purple-60 dark:text-purple-30': + line.step === 3, + 'bg-green-40 border-green-40 text-green-60 dark:text-green-30': + line.step === 4, + } + ), + }) + ); + return inlineHighlightConfig; +} + +/** + * + * @param meta string provided after the language in a markdown block + * @returns array of lines to highlight + * @example + * ```js {1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active + * ... + * ``` + * + * -> The meta is `{1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active` + */ +function getHighlightLines(meta: string): number[] { + const HIGHLIGHT_REGEX = /{([\d,-]+)}/; + const parsedMeta = HIGHLIGHT_REGEX.exec(meta); + if (!parsedMeta) { + return []; + } + return rangeParser(parsedMeta[1]); +} + +/** + * + * @param meta string provided after the language in a markdown block + * @returns InlineHighlight[] + * @example + * ```js {1-3,7} [[1, 1, 'count'], [2, 4, 'setCount']] App.js active + * ... + * ``` + * + * -> The meta is `{1-3,7} [[1, 1, 'count', [2, 4, 'setCount']] App.js active` + */ +function getInlineHighlights(meta: string, code: string) { + const INLINE_HIGHT_REGEX = /(\[\[.*\]\])/; + const parsedMeta = INLINE_HIGHT_REGEX.exec(meta); + if (!parsedMeta) { + return []; + } + + const lines = code.split('\n'); + const encodedHiglights = JSON.parse(parsedMeta[1]); + return encodedHiglights.map(([step, lineNo, substr, fromIndex]: any[]) => { + const line = lines[lineNo - 1]; + let index = line.indexOf(substr); + const lastIndex = line.lastIndexOf(substr); + if (index !== lastIndex) { + if (fromIndex === undefined) { + throw Error( + "Found '" + + substr + + "' twice. Specify fromIndex as the fourth value in the tuple." + ); + } + index = line.indexOf(substr, fromIndex); + } + if (index === -1) { + throw Error("Could not find: '" + substr + "'"); + } + return { + step, + line: lineNo, + startColumn: index, + endColumn: index + substr.length, + }; + }); +} diff --git a/beta/src/components/MDX/CodeBlock/index.tsx b/beta/src/components/MDX/CodeBlock/index.tsx new file mode 100644 index 000000000..e449e6b9e --- /dev/null +++ b/beta/src/components/MDX/CodeBlock/index.tsx @@ -0,0 +1,39 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import cn from 'classnames'; +import {lazy, memo, Suspense} from 'react'; +const CodeBlock = lazy(() => import('./CodeBlock')); + +export default memo(function CodeBlockWrapper(props: { + children: React.ReactNode & { + props: { + className: string; + children: string; + meta?: string; + }; + }; + isFromPackageImport: boolean; + noMargin?: boolean; + noMarkers?: boolean; +}): any { + const {children, isFromPackageImport} = props; + return ( + <Suspense + fallback={ + <pre + className={cn( + 'rounded-lg leading-6 h-full w-full overflow-x-auto flex items-center bg-wash dark:bg-gray-95 shadow-lg text-[13.6px] overflow-hidden', + !isFromPackageImport && 'my-8' + )}> + <div className="py-[18px] pl-5 font-normal "> + <p className="sp-pre-placeholder overflow-hidden">{children}</p> + </div> + </pre> + }> + <CodeBlock {...props} /> + </Suspense> + ); +}); diff --git a/beta/src/components/MDX/CodeDiagram.tsx b/beta/src/components/MDX/CodeDiagram.tsx new file mode 100644 index 000000000..7a503f068 --- /dev/null +++ b/beta/src/components/MDX/CodeDiagram.tsx @@ -0,0 +1,41 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Children} from 'react'; +import * as React from 'react'; +import CodeBlock from './CodeBlock'; + +interface CodeDiagramProps { + children: React.ReactNode; + flip?: boolean; +} + +export function CodeDiagram({children, flip = false}: CodeDiagramProps) { + const illustration = Children.toArray(children).filter((child: any) => { + return child.type === 'img'; + }); + const content = Children.toArray(children).map((child: any) => { + if (child.type?.mdxName === 'pre') { + return <CodeBlock {...child.props} noMargin={true} noMarkers={true} />; + } else if (child.type === 'img') { + return null; + } else { + return child; + } + }); + if (flip) { + return ( + <section className="my-8 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4"> + {illustration} + <div className="flex flex-col justify-center">{content}</div> + </section> + ); + } + return ( + <section className="my-8 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4"> + <div className="flex flex-col justify-center">{content}</div> + <div className="py-4">{illustration}</div> + </section> + ); +} diff --git a/beta/src/components/MDX/ConsoleBlock.tsx b/beta/src/components/MDX/ConsoleBlock.tsx new file mode 100644 index 000000000..2ec393f03 --- /dev/null +++ b/beta/src/components/MDX/ConsoleBlock.tsx @@ -0,0 +1,76 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {isValidElement} from 'react'; +import * as React from 'react'; +import cn from 'classnames'; +import {IconWarning} from '../Icon/IconWarning'; +import {IconError} from '../Icon/IconError'; + +type LogLevel = 'warning' | 'error' | 'info'; + +interface ConsoleBlockProps { + level?: LogLevel; + children: React.ReactNode; +} + +const Box = ({ + width = '60px', + height = '17px', + className, + customStyles, +}: { + width?: string; + height?: string; + className?: string; + customStyles?: Record<string, string>; +}) => ( + <div className={className} style={{width, height, ...customStyles}}></div> +); + +function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { + let message: React.ReactNode | null; + if (typeof children === 'string') { + message = children; + } else if (isValidElement(children)) { + message = children.props.children; + } + + return ( + <div className="mb-4 text-secondary" translate="no"> + <div className="flex w-full rounded-t-lg bg-gray-200 dark:bg-gray-80"> + <div className="px-4 py-2 border-gray-300 dark:border-gray-90 border-r"> + <Box className="bg-gray-300 dark:bg-gray-90" width="15px" /> + </div> + <div className="flex text-sm px-4"> + <div className="border-b-2 border-gray-300 dark:border-gray-90"> + Console + </div> + <div className="px-4 py-2 flex"> + <Box className="mr-2 bg-gray-300 dark:bg-gray-90" /> + <Box className="mr-2 hidden md:block bg-gray-300 dark:bg-gray-90" /> + <Box className="hidden md:block bg-gray-300 dark:bg-gray-90" /> + </div> + </div> + </div> + <div + className={cn( + 'flex px-4 pt-4 pb-6 items-center content-center font-mono text-code rounded-b-md', + { + 'bg-red-30 text-red-50 dark:text-red-30 bg-opacity-5': + level === 'error', + 'bg-yellow-5 text-yellow-50': level === 'warning', + 'bg-gray-5 text-secondary dark:text-secondary-dark': + level === 'info', + } + )}> + {level === 'error' && <IconError className="self-start mt-1.5" />} + {level === 'warning' && <IconWarning className="self-start mt-1" />} + <div className="px-3">{message}</div> + </div> + </div> + ); +} + +export default ConsoleBlock; diff --git a/beta/src/components/MDX/Diagram.tsx b/beta/src/components/MDX/Diagram.tsx new file mode 100644 index 000000000..7920661da --- /dev/null +++ b/beta/src/components/MDX/Diagram.tsx @@ -0,0 +1,60 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import Image from 'next/image'; + +interface DiagramProps { + name: string; + alt: string; + height: number; + width: number; + children: string; + captionPosition: 'top' | 'bottom' | null; +} + +function Caption({text}: {text: string}) { + return ( + <div className="w-full table"> + <figcaption className="p-1 sm:p-2 mt-0 sm:mt-0 text-gray-40 text-base lg:text-lg text-center leading-tight table-caption"> + {text} + </figcaption> + </div> + ); +} + +export function Diagram({ + name, + alt, + height, + width, + children, + captionPosition, +}: DiagramProps) { + return ( + <figure className="flex flex-col px-0 p-0 sm:p-10 first:mt-0 mt-10 sm:mt-0 justify-center items-center"> + {captionPosition === 'top' && <Caption text={children} />} + <div className="dark-image"> + <Image + src={`/images/docs/diagrams/${name}.dark.png`} + alt={alt} + height={height} + width={width} + /> + </div> + <div className="light-image"> + <Image + src={`/images/docs/diagrams/${name}.png`} + alt={alt} + height={height} + width={width} + /> + </div> + {(!captionPosition || captionPosition === 'bottom') && ( + <Caption text={children} /> + )} + </figure> + ); +} + +export default Diagram; diff --git a/beta/src/components/MDX/DiagramGroup.tsx b/beta/src/components/MDX/DiagramGroup.tsx new file mode 100644 index 000000000..6c5130a3d --- /dev/null +++ b/beta/src/components/MDX/DiagramGroup.tsx @@ -0,0 +1,19 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {ReactNode} from 'react'; + +interface DiagramGroupProps { + children: ReactNode; +} + +export function DiagramGroup({children}: DiagramGroupProps) { + return ( + <div className="flex flex-col sm:flex-row py-2 sm:p-0 sm:space-y-0 justify-center items-start sm:items-center w-full"> + {children} + </div> + ); +} + +export default DiagramGroup; diff --git a/beta/src/components/MDX/ExpandableCallout.tsx b/beta/src/components/MDX/ExpandableCallout.tsx new file mode 100644 index 000000000..6662cbd75 --- /dev/null +++ b/beta/src/components/MDX/ExpandableCallout.tsx @@ -0,0 +1,85 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useRef} from 'react'; +import * as React from 'react'; +import cn from 'classnames'; +import {IconNote} from '../Icon/IconNote'; +import {IconWarning} from '../Icon/IconWarning'; +import {IconPitfall} from '../Icon/IconPitfall'; + +type CalloutVariants = 'deprecated' | 'pitfall' | 'note' | 'wip'; + +interface ExpandableCalloutProps { + children: React.ReactNode; + type: CalloutVariants; +} + +const variantMap = { + deprecated: { + title: 'Deprecated', + Icon: IconWarning, + containerClasses: 'bg-red-5 dark:bg-red-60 dark:bg-opacity-20', + textColor: 'text-red-50 dark:text-red-40', + overlayGradient: + 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', + }, + note: { + title: 'Note', + Icon: IconNote, + containerClasses: + 'bg-green-5 dark:bg-green-60 dark:bg-opacity-20 text-primary dark:text-primary-dark text-lg', + textColor: 'text-green-60 dark:text-green-40', + overlayGradient: + 'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)', + }, + pitfall: { + title: 'Pitfall', + Icon: IconPitfall, + containerClasses: 'bg-yellow-5 dark:bg-yellow-60 dark:bg-opacity-20', + textColor: 'text-yellow-50 dark:text-yellow-40', + overlayGradient: + 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', + }, + wip: { + title: 'Under Construction', + Icon: IconNote, + containerClasses: 'bg-yellow-5 dark:bg-yellow-60 dark:bg-opacity-20', + textColor: 'text-yellow-50 dark:text-yellow-40', + overlayGradient: + 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', + }, +}; + +function ExpandableCallout({children, type}: ExpandableCalloutProps) { + const contentRef = useRef<HTMLDivElement>(null); + const variant = variantMap[type]; + + return ( + <div + className={cn( + 'expandable-callout', + 'pt-8 pb-4 px-5 sm:px-8 my-8 relative rounded-none shadow-inner -mx-5 sm:mx-auto sm:rounded-lg', + variant.containerClasses + )}> + <h3 className={cn('mb-2 text-2xl font-bold', variant.textColor)}> + <variant.Icon + className={cn('inline mr-3 mb-1 text-lg', variant.textColor)} + /> + {variant.title} + </h3> + <div className="relative"> + <div ref={contentRef} className="py-2"> + {children} + </div> + </div> + </div> + ); +} + +ExpandableCallout.defaultProps = { + type: 'note', +}; + +export default ExpandableCallout; diff --git a/beta/src/components/MDX/ExpandableExample.tsx b/beta/src/components/MDX/ExpandableExample.tsx new file mode 100644 index 000000000..1056c52b7 --- /dev/null +++ b/beta/src/components/MDX/ExpandableExample.tsx @@ -0,0 +1,115 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import cn from 'classnames'; +import {IconChevron} from '../Icon/IconChevron'; +import {IconDeepDive} from '../Icon/IconDeepDive'; +import {IconCodeBlock} from '../Icon/IconCodeBlock'; +import {Button} from '../Button'; +import {H4} from './Heading'; +import {useRouter} from 'next/router'; +import {useEffect, useRef, useState} from 'react'; + +interface ExpandableExampleProps { + children: React.ReactNode; + excerpt?: string; + type: 'DeepDive' | 'Example'; +} + +function ExpandableExample({children, excerpt, type}: ExpandableExampleProps) { + if (!Array.isArray(children) || children[0].type.mdxName !== 'h4') { + throw Error( + `Expandable content ${type} is missing a corresponding title at the beginning` + ); + } + const isDeepDive = type === 'DeepDive'; + const isExample = type === 'Example'; + const id = children[0].props.id; + + const {asPath} = useRouter(); + const shouldAutoExpand = id === asPath.split('#')[1]; + const queuedExpandRef = useRef<boolean>(shouldAutoExpand); + const [isExpanded, setIsExpanded] = useState(false); + + useEffect(() => { + if (queuedExpandRef.current) { + queuedExpandRef.current = false; + setIsExpanded(true); + } + }, []); + + return ( + <details + open={isExpanded} + onToggle={(e: any) => { + setIsExpanded(e.currentTarget!.open); + }} + className={cn('my-12 rounded-lg shadow-inner relative', { + 'dark:bg-opacity-20 dark:bg-purple-60 bg-purple-5': isDeepDive, + 'dark:bg-opacity-20 dark:bg-yellow-60 bg-yellow-5': isExample, + })}> + <summary + className="list-none p-8" + tabIndex={-1 /* there's a button instead */} + onClick={(e) => { + // We toggle using a button instead of this whole area, + // with an escape case for the header anchor link + if (!(e.target instanceof SVGElement)) { + e.preventDefault(); + } + }}> + <h5 + className={cn('mb-4 uppercase font-bold flex items-center text-sm', { + 'dark:text-purple-30 text-purple-50': isDeepDive, + 'dark:text-yellow-30 text-yellow-60': isExample, + })}> + {isDeepDive && ( + <> + <IconDeepDive className="inline mr-2 dark:text-purple-30 text-purple-40" /> + Deep Dive + </> + )} + {isExample && ( + <> + <IconCodeBlock className="inline mr-2 dark:text-yellow-30 text-yellow-50" /> + Example + </> + )} + </h5> + <div className="mb-4"> + <H4 + id={id} + className="text-xl font-bold text-primary dark:text-primary-dark"> + {children[0].props.children} + </H4> + {excerpt && <div>{excerpt}</div>} + </div> + <Button + active={true} + className={cn({ + 'bg-purple-50 border-purple-50 hover:bg-purple-40 focus:bg-purple-50 active:bg-purple-50': + isDeepDive, + 'bg-yellow-50 border-yellow-50 hover:bg-yellow-40 focus:bg-yellow-50 active:bg-yellow-50': + isExample, + })} + onClick={() => setIsExpanded((current) => !current)}> + <span className="mr-1"> + <IconChevron displayDirection={isExpanded ? 'up' : 'down'} /> + </span> + {isExpanded ? 'Hide Details' : 'Show Details'} + </Button> + </summary> + <div + className={cn('p-8 border-t', { + 'dark:border-purple-60 border-purple-10 ': isDeepDive, + 'dark:border-yellow-60 border-yellow-50': isExample, + })}> + {children.slice(1)} + </div> + </details> + ); +} + +export default ExpandableExample; diff --git a/beta/src/components/MDX/Heading.tsx b/beta/src/components/MDX/Heading.tsx new file mode 100644 index 000000000..58fed55fe --- /dev/null +++ b/beta/src/components/MDX/Heading.tsx @@ -0,0 +1,90 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import cn from 'classnames'; +import * as React from 'react'; +import {forwardRefWithAs} from 'utils/forwardRefWithAs'; +export interface HeadingProps { + className?: string; + isPageAnchor?: boolean; + children: React.ReactNode; + id?: string; + as?: any; +} + +const Heading = forwardRefWithAs<HeadingProps, 'div'>(function Heading( + {as: Comp = 'div', className, children, id, isPageAnchor = true, ...props}, + ref +) { + let label = 'Link for this heading'; + if (typeof children === 'string') { + label = 'Link for ' + children; + } + + return ( + <Comp id={id} {...props} ref={ref} className={cn('mdx-heading', className)}> + {children} + {isPageAnchor && ( + <a + href={`#${id}`} + aria-label={label} + title={label} + className={cn( + 'mdx-header-anchor', + Comp === 'h1' ? 'hidden' : 'inline-block' + )}> + <svg + width="1em" + height="1em" + viewBox="0 0 13 13" + xmlns="http://www.w3.org/2000/svg" + className="text-gray-70 ml-2 h-5 w-5"> + <g fill="currentColor" fillRule="evenodd"> + <path d="M7.778 7.975a2.5 2.5 0 0 0 .347-3.837L6.017 2.03a2.498 2.498 0 0 0-3.542-.007 2.5 2.5 0 0 0 .006 3.543l1.153 1.15c.07-.29.154-.563.25-.773.036-.077.084-.16.14-.25L3.18 4.85a1.496 1.496 0 0 1 .002-2.12 1.496 1.496 0 0 1 2.12 0l2.124 2.123a1.496 1.496 0 0 1-.333 2.37c.16.246.42.504.685.752z" /> + <path d="M5.657 4.557a2.5 2.5 0 0 0-.347 3.837l2.108 2.108a2.498 2.498 0 0 0 3.542.007 2.5 2.5 0 0 0-.006-3.543L9.802 5.815c-.07.29-.154.565-.25.774-.036.076-.084.16-.14.25l.842.84c.585.587.59 1.532 0 2.122-.587.585-1.532.59-2.12 0L6.008 7.68a1.496 1.496 0 0 1 .332-2.372c-.16-.245-.42-.503-.685-.75z" /> + </g> + </svg> + </a> + )} + </Comp> + ); +}); + +export const H1 = ({className, ...props}: HeadingProps) => ( + <Heading + as="h1" + className={cn(className, 'text-5xl font-bold leading-tight')} + {...props} + /> +); + +export const H2 = ({className, ...props}: HeadingProps) => ( + <Heading + as="h2" + className={cn( + 'text-3xl leading-10 text-primary dark:text-primary-dark font-bold my-6', + className + )} + {...props} + /> +); + +export const H3 = ({className, ...props}: HeadingProps) => ( + <Heading + as="h3" + className={cn( + className, + 'text-2xl leading-9 text-primary dark:text-primary-dark font-bold my-6' + )} + {...props} + /> +); + +export const H4 = ({className, ...props}: HeadingProps) => ( + <Heading + as="h4" + className={cn(className, 'text-xl font-bold leading-9 my-4')} + {...props} + /> +); diff --git a/beta/src/components/MDX/HomepageHero.tsx b/beta/src/components/MDX/HomepageHero.tsx new file mode 100644 index 000000000..65c5c32a8 --- /dev/null +++ b/beta/src/components/MDX/HomepageHero.tsx @@ -0,0 +1,44 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Logo} from 'components/Logo'; +import YouWillLearnCard from 'components/MDX/YouWillLearnCard'; + +function HomepageHero() { + return ( + <> + <div className="mt-8 lg:mt-10 mb-0 sm:mt-8 sm:mb-8 lg:mb-6 flex-col sm:flex-row flex grow items-start sm:items-center justify-start mx-auto max-w-4xl"> + <Logo className="text-link dark:text-link-dark w-20 sm:w-28 mr-4 mb-4 sm:mb-0 h-auto" /> + <div className="flex flex-wrap"> + <h1 className="text-5xl mr-4 -mt-1 flex wrap font-bold leading-tight text-primary dark:text-primary-dark"> + React Docs + </h1> + <div className="inline-flex self-center px-2 mt-1 bg-highlight dark:bg-highlight-dark w-auto rounded text-link dark:text-link-dark uppercase font-bold tracking-wide text-base whitespace-nowrap"> + Beta + </div> + </div> + </div> + <section className="my-8 sm:my-10 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4"> + <div className="flex flex-col justify-center"> + <YouWillLearnCard title="Quick Start" path="/learn"> + <p> + Learn how to think in React with step-by-step explanations and + interactive examples. + </p> + </YouWillLearnCard> + </div> + <div className="flex flex-col justify-center"> + <YouWillLearnCard title="API Reference" path="/reference/react"> + <p> + Look up the API of React Hooks, and see their shape with + color-coded signatures. + </p> + </YouWillLearnCard> + </div> + </section> + </> + ); +} + +export default HomepageHero; diff --git a/beta/src/components/MDX/InlineCode.tsx b/beta/src/components/MDX/InlineCode.tsx new file mode 100644 index 000000000..4a87c2a53 --- /dev/null +++ b/beta/src/components/MDX/InlineCode.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import cn from 'classnames'; + +interface InlineCodeProps { + isLink: boolean; +} +function InlineCode({ + isLink, + ...props +}: JSX.IntrinsicElements['code'] & InlineCodeProps) { + return ( + <code + className={cn( + 'inline text-code text-secondary dark:text-secondary-dark px-1 rounded-md no-underline', + { + 'bg-gray-30 bg-opacity-10 py-px': !isLink, + 'bg-highlight dark:bg-highlight-dark py-0': isLink, + } + )} + {...props} + /> + ); +} + +export default InlineCode; diff --git a/beta/src/components/MDX/Intro.tsx b/beta/src/components/MDX/Intro.tsx new file mode 100644 index 000000000..6acde04d6 --- /dev/null +++ b/beta/src/components/MDX/Intro.tsx @@ -0,0 +1,19 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; + +export interface IntroProps { + children?: React.ReactNode; +} + +function Intro({children}: IntroProps) { + return ( + <div className="text-xl text-primary dark:text-primary-dark leading-relaxed"> + {children} + </div> + ); +} + +export default Intro; diff --git a/beta/src/components/MDX/Link.tsx b/beta/src/components/MDX/Link.tsx new file mode 100644 index 000000000..8986d07a5 --- /dev/null +++ b/beta/src/components/MDX/Link.tsx @@ -0,0 +1,55 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Children, cloneElement} from 'react'; +import NextLink from 'next/link'; +import cn from 'classnames'; + +import {ExternalLink} from 'components/ExternalLink'; + +function Link({ + href, + className, + children, + ...props +}: JSX.IntrinsicElements['a']) { + const classes = + 'inline text-link dark:text-link-dark border-b border-link border-opacity-0 hover:border-opacity-100 duration-100 ease-in transition leading-normal'; + const modifiedChildren = Children.toArray(children).map((child: any) => { + if (child.type?.mdxName && child.type?.mdxName === 'inlineCode') { + return cloneElement(child, { + isLink: true, + }); + } + return child; + }); + + if (!href) { + // eslint-disable-next-line jsx-a11y/anchor-has-content + return <a href={href} className={className} {...props} />; + } + return ( + <> + {href.startsWith('https://') ? ( + <ExternalLink href={href} className={cn(classes, className)} {...props}> + {modifiedChildren} + </ExternalLink> + ) : href.startsWith('#') ? ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + <a className={cn(classes, className)} href={href} {...props}> + {modifiedChildren} + </a> + ) : ( + <NextLink href={href}> + {/* eslint-disable-next-line jsx-a11y/anchor-has-content */} + <a className={cn(classes, className)} {...props}> + {modifiedChildren} + </a> + </NextLink> + )} + </> + ); +} + +export default Link; diff --git a/beta/src/components/MDX/MDXComponents.module.css b/beta/src/components/MDX/MDXComponents.module.css new file mode 100644 index 000000000..9840e77ce --- /dev/null +++ b/beta/src/components/MDX/MDXComponents.module.css @@ -0,0 +1,43 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/* Stop purging. */ +.markdown p { + @apply mb-4 leading-7 whitespace-pre-wrap text-gray-70; +} + +.markdown ol { + @apply mb-4 ml-8 list-decimal; +} + +.markdown ul { + @apply mb-4 ml-8 list-disc; +} + +.markdown h1 { + @apply mb-6 text-4xl font-extrabold tracking-tight; +} + +.markdown h2 { + @apply mt-12 mb-4 text-3xl font-extrabold tracking-tight; +} +.markdown h3 { + @apply mt-8 mb-3 text-2xl font-extrabold tracking-tight; +} +.markdown h4 { + @apply mt-8 mb-3 text-xl font-extrabold tracking-tight; +} + +.markdown code { + @apply text-gray-70 bg-card dark:bg-card-dark p-1 rounded-lg no-underline; + font-size: 90%; +} + +.markdown li { + @apply mb-2; +} + +.markdown a { + @apply inline text-link dark:text-link-dark break-normal border-b border-link border-opacity-0 hover:border-opacity-100 duration-100 ease-in transition leading-normal; +} diff --git a/beta/src/components/MDX/MDXComponents.tsx b/beta/src/components/MDX/MDXComponents.tsx new file mode 100644 index 000000000..194986798 --- /dev/null +++ b/beta/src/components/MDX/MDXComponents.tsx @@ -0,0 +1,434 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Children, useContext, useMemo} from 'react'; +import * as React from 'react'; +import cn from 'classnames'; + +import CodeBlock from './CodeBlock'; +import {CodeDiagram} from './CodeDiagram'; +import ConsoleBlock from './ConsoleBlock'; +import ExpandableCallout from './ExpandableCallout'; +import ExpandableExample from './ExpandableExample'; +import {H1, H2, H3, H4} from './Heading'; +import HomepageHero from './HomepageHero'; +import InlineCode from './InlineCode'; +import Intro from './Intro'; +import Link from './Link'; +import {PackageImport} from './PackageImport'; +import Recap from './Recap'; +import Sandpack from './Sandpack'; +import Diagram from './Diagram'; +import DiagramGroup from './DiagramGroup'; +import SimpleCallout from './SimpleCallout'; +import TerminalBlock from './TerminalBlock'; +import YouWillLearnCard from './YouWillLearnCard'; +import {Challenges, Hint, Solution} from './Challenges'; +import {IconNavArrow} from '../Icon/IconNavArrow'; +import ButtonLink from 'components/ButtonLink'; +import {TocContext} from './TocContext'; +import type {Toc, TocItem} from './TocContext'; +import {TeamMember} from './TeamMember'; + +function CodeStep({children, step}: {children: any; step: number}) { + return ( + <span + data-step={step} + className={cn( + 'code-step bg-opacity-10 dark:bg-opacity-20 relative rounded px-[6px] py-[1.5px] border-b-[2px] border-opacity-60', + { + 'bg-blue-40 border-blue-40 text-blue-60 dark:text-blue-30': + step === 1, + 'bg-yellow-40 border-yellow-40 text-yellow-60 dark:text-yellow-30': + step === 2, + 'bg-purple-40 border-purple-40 text-purple-60 dark:text-purple-30': + step === 3, + 'bg-green-40 border-green-40 text-green-60 dark:text-green-30': + step === 4, + } + )}> + {children} + </span> + ); +} + +const P = (p: JSX.IntrinsicElements['p']) => ( + <p className="whitespace-pre-wrap my-4" {...p} /> +); + +const Strong = (strong: JSX.IntrinsicElements['strong']) => ( + <strong className="font-bold" {...strong} /> +); + +const OL = (p: JSX.IntrinsicElements['ol']) => ( + <ol className="ml-6 my-3 list-decimal" {...p} /> +); +const LI = (p: JSX.IntrinsicElements['li']) => ( + <li className="leading-relaxed mb-1" {...p} /> +); +const UL = (p: JSX.IntrinsicElements['ul']) => ( + <ul className="ml-6 my-3 list-disc" {...p} /> +); + +const Divider = () => ( + <hr className="my-6 block border-b border-t-0 border-border dark:border-border-dark" /> +); +const Wip = ({children}: {children: React.ReactNode}) => ( + <ExpandableCallout type="wip">{children}</ExpandableCallout> +); +const Pitfall = ({children}: {children: React.ReactNode}) => ( + <ExpandableCallout type="pitfall">{children}</ExpandableCallout> +); +const Deprecated = ({children}: {children: React.ReactNode}) => ( + <ExpandableCallout type="deprecated">{children}</ExpandableCallout> +); +const Note = ({children}: {children: React.ReactNode}) => ( + <ExpandableCallout type="note">{children}</ExpandableCallout> +); + +const Blockquote = ({ + children, + ...props +}: JSX.IntrinsicElements['blockquote']) => { + return ( + <blockquote + className="mdx-blockquote py-4 px-8 my-8 shadow-inner bg-highlight dark:bg-highlight-dark bg-opacity-50 rounded-lg leading-6 flex relative" + {...props}> + <span className="block relative">{children}</span> + </blockquote> + ); +}; + +function LearnMore({ + children, + path, +}: { + title: string; + path?: string; + children: any; +}) { + return ( + <> + <section className="p-8 mt-16 mb-16 flex flex-row shadow-inner justify-between items-center bg-card dark:bg-card-dark rounded-lg"> + <div className="flex-col"> + <h2 className="text-primary dark:text-primary-dark font-bold text-2xl leading-tight"> + Ready to learn this topic? + </h2> + {children} + {path ? ( + <ButtonLink + className="mt-1" + label="Read More" + href={path} + type="primary"> + Read More + <IconNavArrow displayDirection="right" className="inline ml-1" /> + </ButtonLink> + ) : null} + </div> + </section> + <hr className="border-border dark:border-border-dark mb-14" /> + </> + ); +} + +function ReadBlogPost({path}: {path: string}) { + return ( + <ButtonLink className="mt-1" label="Read Post" href={path} type="primary"> + Read Post + <IconNavArrow displayDirection="right" className="inline ml-1" /> + </ButtonLink> + ); +} + +function Math({children}: {children: any}) { + return ( + <span + style={{ + fontFamily: 'STIXGeneral-Regular, Georgia, serif', + fontSize: '1.2rem', + }}> + {children} + </span> + ); +} + +function MathI({children}: {children: any}) { + return ( + <span + style={{ + fontFamily: 'STIXGeneral-Italic, Georgia, serif', + fontSize: '1.2rem', + }}> + {children} + </span> + ); +} + +function YouWillLearn({ + children, + isChapter, +}: { + children: any; + isChapter?: boolean; +}) { + let title = isChapter ? 'In this chapter' : 'You will learn'; + return <SimpleCallout title={title}>{children}</SimpleCallout>; +} + +// TODO: typing. +function Recipes(props: any) { + return <Challenges {...props} isRecipes={true} />; +} + +function AuthorCredit({ + author = 'Rachel Lee Nabors', + authorLink = 'http://rachelnabors.com/', +}: { + author: string; + authorLink: string; +}) { + return ( + <div className="sr-only group-hover:not-sr-only group-focus-within:not-sr-only hover:sr-only"> + <p className="bg-card dark:bg-card-dark text-center text-sm text-secondary dark:text-secondary-dark leading-tight dark:text-secondary-dark p-2 rounded-lg absolute left-1/2 top-0 -translate-x-1/2 -translate-y-full group-hover:flex group-hover:opacity-100 after:content-[''] after:absolute after:left-1/2 after:top-[95%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-b-transparent after:border-t-card after:dark:border-t-card-dark opacity-0 transition-opacity duration-300"> + <cite> + Illustrated by{' '} + {authorLink ? ( + <a + target="_blank" + rel="noreferrer" + className="text-link dark:text-link-dark" + href={authorLink}> + {author} + </a> + ) : ( + author + )} + </cite> + </p> + </div> + ); +} + +const IllustrationContext = React.createContext<{ + isInBlock?: boolean; +}>({ + isInBlock: false, +}); + +function Illustration({ + caption, + src, + alt, + author, + authorLink, +}: { + caption: string; + src: string; + alt: string; + author: string; + authorLink: string; +}) { + const {isInBlock} = React.useContext(IllustrationContext); + + return ( + <div className="relative group before:absolute before:-inset-y-16 before:inset-x-0 my-16 mx-0 2xl:mx-auto max-w-4xl 2xl:max-w-6xl"> + <figure className="my-8 flex justify-center"> + <img + src={src} + alt={alt} + style={{maxHeight: 300}} + className="bg-white rounded-lg" + /> + {caption ? ( + <figcaption className="text-center leading-tight mt-4"> + {caption} + </figcaption> + ) : null} + </figure> + {!isInBlock && <AuthorCredit author={author} authorLink={authorLink} />} + </div> + ); +} + +const isInBlockTrue = {isInBlock: true}; + +function IllustrationBlock({ + sequential, + author, + authorLink, + children, +}: { + author: string; + authorLink: string; + sequential: boolean; + children: any; +}) { + const imageInfos = Children.toArray(children).map( + (child: any) => child.props + ); + const images = imageInfos.map((info, index) => ( + <figure key={index}> + <div className="bg-white rounded-lg p-4 flex-1 flex xl:p-6 justify-center items-center my-4"> + <img src={info.src} alt={info.alt} height={info.height} /> + </div> + {info.caption ? ( + <figcaption className="text-secondary dark:text-secondary-dark text-center leading-tight mt-4"> + {info.caption} + </figcaption> + ) : null} + </figure> + )); + return ( + <IllustrationContext.Provider value={isInBlockTrue}> + <div className="relative group before:absolute before:-inset-y-16 before:inset-x-0 my-16 mx-0 2xl:mx-auto max-w-4xl 2xl:max-w-6xl"> + {sequential ? ( + <ol className="mdx-illustration-block flex"> + {images.map((x: any, i: number) => ( + <li className="flex-1" key={i}> + {x} + </li> + ))} + </ol> + ) : ( + <div className="mdx-illustration-block">{images}</div> + )} + <AuthorCredit author={author} authorLink={authorLink} /> + </div> + </IllustrationContext.Provider> + ); +} + +type NestedTocRoot = { + item: null; + children: Array<NestedTocNode>; +}; + +type NestedTocNode = { + item: TocItem; + children: Array<NestedTocNode>; +}; + +function calculateNestedToc(toc: Toc): NestedTocRoot { + const currentAncestors = new Map<number, NestedTocNode | NestedTocRoot>(); + const root: NestedTocRoot = { + item: null, + children: [], + }; + const startIndex = 1; // Skip "Overview" + for (let i = startIndex; i < toc.length; i++) { + const item = toc[i]; + const currentParent: NestedTocNode | NestedTocRoot = + currentAncestors.get(item.depth - 1) || root; + const node: NestedTocNode = { + item, + children: [], + }; + currentParent.children.push(node); + currentAncestors.set(item.depth, node); + } + return root; +} + +function InlineToc() { + const toc = useContext(TocContext); + const root = useMemo(() => calculateNestedToc(toc), [toc]); + if (root.children.length < 2) { + return null; + } + return <InlineTocItem items={root.children} />; +} + +function InlineTocItem({items}: {items: Array<NestedTocNode>}) { + return ( + <UL> + {items.map((node) => ( + <LI key={node.item.url}> + <Link href={node.item.url}>{node.item.text}</Link> + {node.children.length > 0 && <InlineTocItem items={node.children} />} + </LI> + ))} + </UL> + ); +} + +function YouTubeIframe(props: any) { + return ( + <div className="relative h-0 overflow-hidden pt-[56.25%]"> + <iframe + className="absolute inset-0 w-full h-full" + frameBorder="0" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" + allowFullScreen + title="YouTube video player" + {...props} + /> + </div> + ); +} + +export const MDXComponents = { + p: P, + strong: Strong, + blockquote: Blockquote, + ol: OL, + ul: UL, + li: LI, + h1: H1, + h2: H2, + h3: H3, + h4: H4, + hr: Divider, + a: Link, + code: InlineCode, + pre: CodeBlock, + CodeDiagram, + ConsoleBlock, + DeepDive: (props: { + children: React.ReactNode; + title: string; + excerpt: string; + }) => <ExpandableExample {...props} type="DeepDive" />, + Diagram, + DiagramGroup, + FullWidth({children}: {children: any}) { + return children; + }, + MaxWidth({children}: {children: any}) { + return <div className="max-w-4xl ml-0 2xl:mx-auto">{children}</div>; + }, + Pitfall, + Deprecated, + Wip, + HomepageHero, + Illustration, + IllustrationBlock, + Intro, + InlineToc, + LearnMore, + Math, + MathI, + Note, + PackageImport, + ReadBlogPost, + Recap, + Recipes, + Sandpack, + TeamMember, + TerminalBlock, + YouWillLearn, + YouWillLearnCard, + Challenges, + Hint, + Solution, + CodeStep, + YouTubeIframe, +}; + +for (let key in MDXComponents) { + if (MDXComponents.hasOwnProperty(key)) { + const MDXComponent: any = (MDXComponents as any)[key]; + MDXComponent.mdxName = key; + } +} diff --git a/beta/src/components/MDX/PackageImport.tsx b/beta/src/components/MDX/PackageImport.tsx new file mode 100644 index 000000000..5e2da820e --- /dev/null +++ b/beta/src/components/MDX/PackageImport.tsx @@ -0,0 +1,38 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Children} from 'react'; +import * as React from 'react'; +import CodeBlock from './CodeBlock'; + +interface PackageImportProps { + children: React.ReactNode; +} + +export function PackageImport({children}: PackageImportProps) { + const terminal = Children.toArray(children).filter((child: any) => { + return child.type?.mdxName !== 'pre'; + }); + const code = Children.toArray(children).map((child: any, i: number) => { + if (child.type?.mdxName === 'pre') { + return ( + <CodeBlock + {...child.props} + isFromPackageImport + key={i} + noMargin={true} + noMarkers={true} + /> + ); + } else { + return null; + } + }); + return ( + <section className="my-8 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4"> + <div className="flex flex-col justify-center">{terminal}</div> + <div className="flex flex-col justify-center">{code}</div> + </section> + ); +} diff --git a/beta/src/components/MDX/Recap.tsx b/beta/src/components/MDX/Recap.tsx new file mode 100644 index 000000000..d91ed48b4 --- /dev/null +++ b/beta/src/components/MDX/Recap.tsx @@ -0,0 +1,23 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import {H2} from './Heading'; + +interface RecapProps { + children: React.ReactNode; +} + +function Recap({children}: RecapProps) { + return ( + <section> + <H2 isPageAnchor id="recap"> + Recap + </H2> + {children} + </section> + ); +} + +export default Recap; diff --git a/beta/src/components/MDX/Sandpack/Console.tsx b/beta/src/components/MDX/Sandpack/Console.tsx new file mode 100644 index 000000000..23194c870 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/Console.tsx @@ -0,0 +1,242 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +import cn from 'classnames'; +import {useState, useRef, useEffect} from 'react'; +import {IconChevron} from 'components/Icon/IconChevron'; + +import {SandpackCodeViewer, useSandpack} from '@codesandbox/sandpack-react'; +import type {SandpackMessageConsoleMethods} from '@codesandbox/sandpack-client'; + +const getType = ( + message: SandpackMessageConsoleMethods +): 'info' | 'warning' | 'error' => { + if (message === 'log' || message === 'info') { + return 'info'; + } + + if (message === 'warn') { + return 'warning'; + } + + return 'error'; +}; + +const getColor = (message: SandpackMessageConsoleMethods): string => { + if (message === 'warn') { + return 'text-yellow-50'; + } else if (message === 'error') { + return 'text-red-40'; + } else { + return 'text-secondary dark:text-secondary-dark'; + } +}; + +// based on https://github.com/tmpfs/format-util/blob/0e62d430efb0a1c51448709abd3e2406c14d8401/format.js#L1 +// based on https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions +// Implements s, d, i and f placeholders +function formatStr(...inputArgs: any[]): any[] { + const maybeMessage = inputArgs[0]; + if (typeof maybeMessage !== 'string') { + return inputArgs; + } + // If the first argument is a string, check for substitutions. + const args = inputArgs.slice(1); + let formatted: string = String(maybeMessage); + if (args.length) { + const REGEXP = /(%?)(%([jds]))/g; + + formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => { + let arg = args.shift(); + switch (flag) { + case 's': + arg += ''; + break; + case 'd': + case 'i': + arg = parseInt(arg, 10).toString(); + break; + case 'f': + arg = parseFloat(arg).toString(); + break; + } + if (!escaped) { + return arg; + } + args.unshift(arg); + return match; + }); + } + + // Arguments that remain after formatting. + if (args.length) { + for (let i = 0; i < args.length; i++) { + formatted += ' ' + String(args[i]); + } + } + + // Update escaped %% values. + return [formatted.replace(/%{2,2}/g, '%')]; +} + +type ConsoleData = Array<{ + data: Array<string | Record<string, string>>; + id: string; + method: SandpackMessageConsoleMethods; +}>; + +const MAX_MESSAGE_COUNT = 100; + +export const SandpackConsole = ({visible}: {visible: boolean}) => { + const {listen} = useSandpack(); + const [logs, setLogs] = useState<ConsoleData>([]); + const wrapperRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + let isActive = true; + const unsubscribe = listen((message) => { + if (!isActive) { + console.warn('Received an unexpected log from Sandpack.'); + return; + } + if ( + (message.type === 'start' && message.firstLoad) || + message.type === 'refresh' + ) { + setLogs([]); + } + if (message.type === 'console' && message.codesandbox) { + setLogs((prev) => { + const newLogs = message.log + .filter((consoleData) => { + if (!consoleData.method) { + return false; + } + if ( + typeof consoleData.data[0] === 'string' && + consoleData.data[0].indexOf('The above error occurred') !== -1 + ) { + // Don't show React error addendum because + // we have a custom error overlay. + return false; + } + return true; + }) + .map((consoleData) => { + return { + ...consoleData, + data: formatStr(...consoleData.data), + }; + }); + let messages = [...prev, ...newLogs]; + while (messages.length > MAX_MESSAGE_COUNT) { + messages.shift(); + } + return messages; + }); + } + }); + + return () => { + unsubscribe(); + isActive = false; + }; + }, [listen]); + + const [isExpanded, setIsExpanded] = useState(true); + + useEffect(() => { + if (wrapperRef.current) { + wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight; + } + }, [logs]); + + if (!visible || logs.length === 0) { + return null; + } + + return ( + <div className="absolute dark:border-gray-700 bg-white dark:bg-gray-95 border-t bottom-0 w-full dark:text-white"> + <div className="flex justify-between"> + <button + className="flex items-center p-1" + onClick={() => setIsExpanded(!isExpanded)}> + <IconChevron displayDirection={isExpanded ? 'down' : 'right'} /> + <span className="pl-1 text-sm">Console ({logs.length})</span> + </button> + <button + className="p-1" + onClick={() => { + setLogs([]); + }}> + <svg + viewBox="0 0 24 24" + width="18" + height="18" + stroke="currentColor" + strokeWidth="2" + fill="none" + strokeLinecap="round" + strokeLinejoin="round"> + <circle cx="12" cy="12" r="10"></circle> + <line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line> + </svg> + </button> + </div> + {isExpanded && ( + <div className="w-full h-full border-t bg-white dark:border-gray-700 dark:bg-gray-95 min-h-[28px] console"> + <div className="max-h-40 h-auto overflow-auto" ref={wrapperRef}> + {logs.map(({data, id, method}) => { + return ( + <div + key={id} + className={cn( + 'first:border-none border-t dark:border-gray-700 text-md p-1 pl-2 leading-6 font-mono min-h-[32px] whitespace-pre-wrap', + `console-${getType(method)}`, + getColor(method) + )}> + <span className="console-message"> + {data.map((msg, index) => { + if (typeof msg === 'string') { + return <span key={`${msg}-${index}`}>{msg}</span>; + } + + let children; + if (msg != null && typeof msg['@t'] === 'string') { + // CodeSandbox wraps custom types + children = msg['@t']; + } else { + try { + children = JSON.stringify(msg, null, 2); + } catch (error) { + try { + children = Object.prototype.toString.call(msg); + } catch (err) { + children = '[' + typeof msg + ']'; + } + } + } + + return ( + <span + className={cn('console-span')} + key={`${msg}-${index}`}> + <SandpackCodeViewer + initMode="user-visible" + showTabs={false} + // fileType="js" + code={children} + /> + </span> + ); + })} + </span> + </div> + ); + })} + </div> + </div> + )} + </div> + ); +}; diff --git a/beta/src/components/MDX/Sandpack/CustomPreset.tsx b/beta/src/components/MDX/Sandpack/CustomPreset.tsx new file mode 100644 index 000000000..7ef536579 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/CustomPreset.tsx @@ -0,0 +1,148 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +import {memo, useRef, useState} from 'react'; +import {flushSync} from 'react-dom'; +import { + useSandpack, + useActiveCode, + SandpackCodeEditor, + // SandpackReactDevTools, + SandpackLayout, +} from '@codesandbox/sandpack-react'; +import cn from 'classnames'; + +import {IconChevron} from 'components/Icon/IconChevron'; +import {NavigationBar} from './NavigationBar'; +import {Preview} from './Preview'; + +import {useSandpackLint} from './useSandpackLint'; + +export const CustomPreset = memo(function CustomPreset({ + showDevTools, + onDevToolsLoad, + devToolsLoaded, + providedFiles, +}: { + showDevTools: boolean; + devToolsLoaded: boolean; + onDevToolsLoad: () => void; + providedFiles: Array<string>; +}) { + const {lintErrors, lintExtensions} = useSandpackLint(); + const {sandpack} = useSandpack(); + const {code} = useActiveCode(); + const {activeFile} = sandpack; + const lineCountRef = useRef<{[key: string]: number}>({}); + if (!lineCountRef.current[activeFile]) { + lineCountRef.current[activeFile] = code.split('\n').length; + } + const lineCount = lineCountRef.current[activeFile]; + const isExpandable = lineCount > 16; + return ( + <SandboxShell + showDevTools={showDevTools} + onDevToolsLoad={onDevToolsLoad} + devToolsLoaded={devToolsLoaded} + providedFiles={providedFiles} + lintErrors={lintErrors} + lintExtensions={lintExtensions} + isExpandable={isExpandable} + /> + ); +}); + +const SandboxShell = memo(function SandboxShell({ + showDevTools, + onDevToolsLoad, + devToolsLoaded, + providedFiles, + lintErrors, + lintExtensions, + isExpandable, +}: { + showDevTools: boolean; + devToolsLoaded: boolean; + onDevToolsLoad: () => void; + providedFiles: Array<string>; + lintErrors: Array<any>; + lintExtensions: Array<any>; + isExpandable: boolean; +}) { + const containerRef = useRef<HTMLDivElement>(null); + const [isExpanded, setIsExpanded] = useState(false); + return ( + <> + <div + className="shadow-lg dark:shadow-lg-dark rounded-lg" + ref={containerRef}> + <NavigationBar providedFiles={providedFiles} /> + <SandpackLayout + className={cn( + showDevTools && devToolsLoaded && 'sp-layout-devtools', + !(isExpandable || isExpanded) && 'rounded-b-lg overflow-hidden', + isExpanded && 'sp-layout-expanded' + )}> + <Editor lintExtensions={lintExtensions} /> + <Preview + className="order-last xl:order-2" + isExpanded={isExpanded} + lintErrors={lintErrors} + /> + {(isExpandable || isExpanded) && ( + <button + translate="yes" + className="sandpack-expand flex text-base justify-between dark:border-card-dark bg-wash dark:bg-card-dark items-center z-10 p-1 w-full order-2 xl:order-last border-b-1 relative top-0" + onClick={() => { + const nextIsExpanded = !isExpanded; + flushSync(() => { + setIsExpanded(nextIsExpanded); + }); + if (!nextIsExpanded && containerRef.current !== null) { + // @ts-ignore + if (containerRef.current.scrollIntoViewIfNeeded) { + // @ts-ignore + containerRef.current.scrollIntoViewIfNeeded(); + } else { + containerRef.current.scrollIntoView({ + block: 'nearest', + inline: 'nearest', + }); + } + } + }}> + <span className="flex p-2 focus:outline-none text-primary dark:text-primary-dark leading-[20px]"> + <IconChevron + className="inline mr-1.5 text-xl" + displayDirection={isExpanded ? 'up' : 'down'} + /> + {isExpanded ? 'Show less' : 'Show more'} + </span> + </button> + )} + </SandpackLayout> + + {/* {showDevTools && ( + // @ts-ignore TODO(@danilowoz): support devtools + <SandpackReactDevTools onLoadModule={onDevToolsLoad} /> + )} */} + </div> + </> + ); +}); + +const Editor = memo(function Editor({ + lintExtensions, +}: { + lintExtensions: Array<any>; +}) { + return ( + <SandpackCodeEditor + showLineNumbers + showInlineErrors + showTabs={false} + showRunButton={false} + extensions={lintExtensions} + /> + ); +}); diff --git a/beta/src/components/MDX/Sandpack/DownloadButton.tsx b/beta/src/components/MDX/Sandpack/DownloadButton.tsx new file mode 100644 index 000000000..ef974b1af --- /dev/null +++ b/beta/src/components/MDX/Sandpack/DownloadButton.tsx @@ -0,0 +1,105 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useSyncExternalStore} from 'react'; +import {useSandpack} from '@codesandbox/sandpack-react'; +import {IconDownload} from '../../Icon/IconDownload'; +export interface DownloadButtonProps {} + +let supportsImportMap: boolean | void; + +function useSupportsImportMap() { + function subscribe() { + // It never updates. + return () => {}; + } + function getCurrentValue() { + if (supportsImportMap === undefined) { + supportsImportMap = + (HTMLScriptElement as any).supports && + (HTMLScriptElement as any).supports('importmap'); + } + return supportsImportMap; + } + function getServerSnapshot() { + return false; + } + + return useSyncExternalStore(subscribe, getCurrentValue, getServerSnapshot); +} + +const SUPPORTED_FILES = ['/App.js', '/styles.css']; + +export function DownloadButton({ + providedFiles, +}: { + providedFiles: Array<string>; +}) { + const {sandpack} = useSandpack(); + const supported = useSupportsImportMap(); + if (!supported) { + return null; + } + if (providedFiles.some((file) => !SUPPORTED_FILES.includes(file))) { + return null; + } + + const downloadHTML = () => { + const css = sandpack.files['/styles.css']?.code ?? ''; + const code = sandpack.files['/App.js']?.code ?? ''; + const blob = new Blob([ + `<!DOCTYPE html> +<html> +<body> + <div id="root"></div> +</body> +<!-- This setup is not suitable for production. --> +<!-- Only use it in development! --> +<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> +<script type="importmap"> +{ + "imports": { + "react": "https://esm.sh/react?dev", + "react-dom/client": "https://esm.sh/react-dom/client?dev" + } +} +</script> +<script type="text/babel" data-type="module"> +import React, { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +${code.replace('export default ', 'let App = ')} + +const root = createRoot(document.getElementById('root')); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +</script> +<style> +${css} +</style> +</html>`, + ]); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = 'sandbox.html'; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + }; + + return ( + <button + className="text-sm text-primary dark:text-primary-dark inline-flex items-center hover:text-link duration-100 ease-in transition mx-1" + onClick={downloadHTML} + title="Download Sandbox" + type="button"> + <IconDownload className="inline mr-1" /> Download + </button> + ); +} diff --git a/beta/src/components/MDX/Sandpack/ErrorMessage.tsx b/beta/src/components/MDX/Sandpack/ErrorMessage.tsx new file mode 100644 index 000000000..7c67ee461 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/ErrorMessage.tsx @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +interface ErrorType { + title?: string; + message: string; + column?: number; + line?: number; + path?: string; +} + +export function ErrorMessage({error, ...props}: {error: ErrorType}) { + const {message, title} = error; + + return ( + <div className="bg-white border-2 border-red-40 rounded-lg p-6" {...props}> + <h2 className="text-red-40 text-xl mb-4">{title || 'Error'}</h2> + <pre className="text-secondary whitespace-pre-wrap break-words leading-tight"> + {message} + </pre> + </div> + ); +} diff --git a/beta/src/components/MDX/Sandpack/LoadingOverlay.tsx b/beta/src/components/MDX/Sandpack/LoadingOverlay.tsx new file mode 100644 index 000000000..7c261866d --- /dev/null +++ b/beta/src/components/MDX/Sandpack/LoadingOverlay.tsx @@ -0,0 +1,142 @@ +import {useState} from 'react'; + +import { + LoadingOverlayState, + OpenInCodeSandboxButton, + useSandpack, +} from '@codesandbox/sandpack-react'; +import {useEffect} from 'react'; + +const FADE_ANIMATION_DURATION = 200; + +export const LoadingOverlay = ({ + clientId, + dependenciesLoading, + forceLoading, +}: { + clientId: string; + dependenciesLoading: boolean; + forceLoading: boolean; +} & React.HTMLAttributes<HTMLDivElement>): JSX.Element | null => { + const loadingOverlayState = useLoadingOverlayState( + clientId, + dependenciesLoading, + forceLoading + ); + + if (loadingOverlayState === 'HIDDEN') { + return null; + } + + if (loadingOverlayState === 'TIMEOUT') { + return ( + <div className="sp-overlay sp-error"> + <div className="sp-error-message"> + Unable to establish connection with the sandpack bundler. Make sure + you are online or try again later. If the problem persists, please + report it via{' '} + <a + className="sp-error-message" + href="mailto:hello@codesandbox.io?subject=Sandpack Timeout Error"> + email + </a>{' '} + or submit an issue on{' '} + <a + className="sp-error-message" + href="https://github.com/codesandbox/sandpack/issues" + rel="noreferrer noopener" + target="_blank"> + GitHub. + </a> + </div> + </div> + ); + } + + const stillLoading = + loadingOverlayState === 'LOADING' || loadingOverlayState === 'PRE_FADING'; + + return ( + <div + className="sp-overlay sp-loading" + style={{ + opacity: stillLoading ? 1 : 0, + transition: `opacity ${FADE_ANIMATION_DURATION}ms ease-out`, + }}> + <div className="sp-cube-wrapper" title="Open in CodeSandbox"> + <OpenInCodeSandboxButton /> + <div className="sp-cube"> + <div className="sp-sides"> + <div className="top" /> + <div className="right" /> + <div className="bottom" /> + <div className="left" /> + <div className="front" /> + <div className="back" /> + </div> + </div> + </div> + </div> + ); +}; + +const useLoadingOverlayState = ( + clientId: string, + dependenciesLoading: boolean, + forceLoading: boolean +): LoadingOverlayState => { + const {sandpack, listen} = useSandpack(); + const [state, setState] = useState<LoadingOverlayState>('HIDDEN'); + + if (state !== 'LOADING' && forceLoading) { + setState('LOADING'); + } + + /** + * Sandpack listener + */ + const sandpackIdle = sandpack.status === 'idle'; + useEffect(() => { + const unsubscribe = listen((message) => { + if (message.type === 'done') { + setState((prev) => { + return prev === 'LOADING' ? 'PRE_FADING' : 'HIDDEN'; + }); + } + }, clientId); + + return () => { + unsubscribe(); + }; + }, [listen, clientId, sandpackIdle]); + + /** + * Fading transient state + */ + useEffect(() => { + let fadeTimeout: ReturnType<typeof setTimeout>; + + if (state === 'PRE_FADING' && !dependenciesLoading) { + setState('FADING'); + } else if (state === 'FADING') { + fadeTimeout = setTimeout( + () => setState('HIDDEN'), + FADE_ANIMATION_DURATION + ); + } + + return () => { + clearTimeout(fadeTimeout); + }; + }, [state, dependenciesLoading]); + + if (sandpack.status === 'timeout') { + return 'TIMEOUT'; + } + + if (sandpack.status !== 'running') { + return 'HIDDEN'; + } + + return state; +}; diff --git a/beta/src/components/MDX/Sandpack/NavigationBar.tsx b/beta/src/components/MDX/Sandpack/NavigationBar.tsx new file mode 100644 index 000000000..8c884a5d8 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/NavigationBar.tsx @@ -0,0 +1,188 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import { + useRef, + useInsertionEffect, + useCallback, + useState, + useEffect, + Fragment, +} from 'react'; +import cn from 'classnames'; +import { + FileTabs, + useSandpack, + useSandpackNavigation, +} from '@codesandbox/sandpack-react'; +import {OpenInCodeSandboxButton} from './OpenInCodeSandboxButton'; +import {ResetButton} from './ResetButton'; +import {DownloadButton} from './DownloadButton'; +import {IconChevron} from '../../Icon/IconChevron'; +import {Listbox} from '@headlessui/react'; + +export function useEvent(fn: any): any { + const ref = useRef(null); + useInsertionEffect(() => { + ref.current = fn; + }, [fn]); + return useCallback((...args: any) => { + const f = ref.current!; + // @ts-ignore + return f(...args); + }, []); +} + +const getFileName = (filePath: string): string => { + const lastIndexOfSlash = filePath.lastIndexOf('/'); + return filePath.slice(lastIndexOfSlash + 1); +}; + +export function NavigationBar({providedFiles}: {providedFiles: Array<string>}) { + const {sandpack} = useSandpack(); + const containerRef = useRef<HTMLDivElement | null>(null); + const tabsRef = useRef<HTMLDivElement | null>(null); + // By default, show the dropdown because all tabs may not fit. + // We don't know whether they'll fit or not until after hydration: + const [showDropdown, setShowDropdown] = useState(true); + const {activeFile, setActiveFile, visibleFiles, clients} = sandpack; + const clientId = Object.keys(clients)[0]; + const {refresh} = useSandpackNavigation(clientId); + const isMultiFile = visibleFiles.length > 1; + const hasJustToggledDropdown = useRef(false); + + // Keep track of whether we can show all tabs or just the dropdown. + const onContainerResize = useEvent((containerWidth: number) => { + if (hasJustToggledDropdown.current === true) { + // Ignore changes likely caused by ourselves. + hasJustToggledDropdown.current = false; + return; + } + if (tabsRef.current === null) { + // Some ResizeObserver calls come after unmount. + return; + } + const tabsWidth = tabsRef.current.getBoundingClientRect().width; + const needsDropdown = tabsWidth >= containerWidth; + if (needsDropdown !== showDropdown) { + hasJustToggledDropdown.current = true; + setShowDropdown(needsDropdown); + } + }); + + useEffect(() => { + if (isMultiFile) { + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + if (entry.contentBoxSize) { + const contentBoxSize = Array.isArray(entry.contentBoxSize) + ? entry.contentBoxSize[0] + : entry.contentBoxSize; + const width = contentBoxSize.inlineSize; + onContainerResize(width); + } + } + }); + const container = containerRef.current!; + resizeObserver.observe(container); + return () => resizeObserver.unobserve(container); + } else { + return; + } + }, [isMultiFile]); + + const handleReset = () => { + /** + * resetAllFiles must come first, otherwise + * the previous content will appears for a second + * when the iframe loads. + * + * Plus, it should only prompts if there's any file changes + */ + if ( + sandpack.editorState === 'dirty' && + confirm('Reset all your edits too?') + ) { + sandpack.resetAllFiles(); + } + + refresh(); + }; + + return ( + <div className="bg-wash dark:bg-card-dark flex justify-between items-center relative z-10 border-b border-border dark:border-border-dark rounded-t-lg text-lg"> + <div className="flex-1 grow min-w-0 px-4 lg:px-6"> + <Listbox value={activeFile} onChange={setActiveFile}> + <div ref={containerRef}> + <div className="relative overflow-hidden"> + <div + ref={tabsRef} + className={cn( + // The container for all tabs is always in the DOM, but + // not always visible. This lets us measure how much space + // the tabs would take if displayed. We use this to decide + // whether to keep showing the dropdown, or show all tabs. + 'w-[fit-content]', + showDropdown ? 'invisible' : '' + )}> + <FileTabs /> + </div> + <Listbox.Button as={Fragment}> + {({open}) => ( + // If tabs don't fit, display the dropdown instead. + // The dropdown is absolutely positioned inside the + // space that's taken by the (invisible) tab list. + <button + className={cn( + 'absolute top-0 left-[2px]', + !showDropdown && 'invisible' + )}> + <span + className={cn( + 'h-full py-2 px-1 mt-px -mb-px flex border-b text-link dark:text-link-dark border-link dark:border-link-dark items-center text-md leading-tight truncate' + )} + style={{maxWidth: '160px'}}> + {getFileName(activeFile)} + {isMultiFile && ( + <span className="ml-2"> + <IconChevron + displayDirection={open ? 'up' : 'down'} + /> + </span> + )} + </span> + </button> + )} + </Listbox.Button> + </div> + </div> + {isMultiFile && showDropdown && ( + <Listbox.Options className="absolute mt-0.5 bg-card dark:bg-card-dark px-2 left-0 right-0 mx-0 rounded-b-lg border-1 border-border dark:border-border-dark rounded-sm shadow-md"> + {visibleFiles.map((filePath: string) => ( + <Listbox.Option key={filePath} value={filePath} as={Fragment}> + {({active}) => ( + <li + className={cn( + 'text-md mx-2 my-4 cursor-pointer', + active && 'text-link dark:text-link-dark' + )}> + {getFileName(filePath)} + </li> + )} + </Listbox.Option> + ))} + </Listbox.Options> + )} + </Listbox> + </div> + <div + className="px-3 flex items-center justify-end text-right" + translate="yes"> + <DownloadButton providedFiles={providedFiles} /> + <ResetButton onReset={handleReset} /> + <OpenInCodeSandboxButton /> + </div> + </div> + ); +} diff --git a/beta/src/components/MDX/Sandpack/OpenInCodeSandboxButton.tsx b/beta/src/components/MDX/Sandpack/OpenInCodeSandboxButton.tsx new file mode 100644 index 000000000..42a2d2743 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/OpenInCodeSandboxButton.tsx @@ -0,0 +1,21 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {UnstyledOpenInCodeSandboxButton} from '@codesandbox/sandpack-react'; +import {IconNewPage} from '../../Icon/IconNewPage'; + +export const OpenInCodeSandboxButton = () => { + return ( + <UnstyledOpenInCodeSandboxButton + className="text-sm text-primary dark:text-primary-dark inline-flex items-center hover:text-link duration-100 ease-in transition mx-1 ml-2 md:ml-1" + title="Open in CodeSandbox"> + <IconNewPage + className="inline ml-1 mr-1 relative top-[1px]" + width="1em" + height="1em" + /> + <span className="hidden md:block">Fork</span> + </UnstyledOpenInCodeSandboxButton> + ); +}; diff --git a/beta/src/components/MDX/Sandpack/Preview.tsx b/beta/src/components/MDX/Sandpack/Preview.tsx new file mode 100644 index 000000000..cec510b81 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/Preview.tsx @@ -0,0 +1,228 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/* eslint-disable react-hooks/exhaustive-deps */ +import {useRef, useState, useEffect, useMemo, useId} from 'react'; +import {useSandpack, SandpackStack} from '@codesandbox/sandpack-react'; +import cn from 'classnames'; +import {ErrorMessage} from './ErrorMessage'; +import {SandpackConsole} from './Console'; +import type {LintDiagnostic} from './useSandpackLint'; +import {CSSProperties} from 'react'; +import {LoadingOverlay} from './LoadingOverlay'; + +type CustomPreviewProps = { + className?: string; + isExpanded: boolean; + lintErrors: LintDiagnostic; +}; + +function useDebounced(value: any): any { + const ref = useRef<any>(null); + const [saved, setSaved] = useState(value); + useEffect(() => { + clearTimeout(ref.current); + ref.current = setTimeout(() => { + setSaved(value); + }, 300); + }, [value]); + return saved; +} + +export function Preview({ + isExpanded, + className, + lintErrors, +}: CustomPreviewProps) { + const {sandpack, listen} = useSandpack(); + const [bundlerIsReady, setBundlerIsReady] = useState(false); + const [showLoading, setShowLoading] = useState(false); + const [iframeComputedHeight, setComputedAutoHeight] = useState<number | null>( + null + ); + + let { + error: rawError, + registerBundler, + unregisterBundler, + errorScreenRegisteredRef, + openInCSBRegisteredRef, + loadingScreenRegisteredRef, + status, + } = sandpack; + + if ( + rawError && + rawError.message === '_csbRefreshUtils.prelude is not a function' + ) { + // Work around a noisy internal error. + rawError = null; + } + + // Memoized because it's fed to debouncing. + const firstLintError = useMemo(() => { + if (lintErrors.length === 0) { + return null; + } else { + const {line, column, message} = lintErrors[0]; + return { + title: 'Lint Error', + message: `${line}:${column} - ${message}`, + }; + } + }, [lintErrors]); + + if (rawError == null || rawError.title === 'Runtime Exception') { + if (firstLintError !== null) { + rawError = firstLintError; + } + } + + if (rawError != null && rawError.title === 'Runtime Exception') { + rawError.title = 'Runtime Error'; + } + + // It changes too fast, causing flicker. + const error = useDebounced(rawError); + + const clientId = useId(); + const iframeRef = useRef<HTMLIFrameElement | null>(null); + + // SandpackPreview immediately registers the custom screens/components so the bundler does not render any of them + // TODO: why are we doing this during render? + openInCSBRegisteredRef.current = true; + errorScreenRegisteredRef.current = true; + loadingScreenRegisteredRef.current = true; + + const sandpackIdle = sandpack.status === 'idle'; + + useEffect(function createBundler() { + const iframeElement = iframeRef.current!; + registerBundler(iframeElement, clientId); + + return () => { + unregisterBundler(clientId); + }; + }, []); + + useEffect( + function bundlerListener() { + let timeout: ReturnType<typeof setTimeout>; + + const unsubscribe = listen((message) => { + if (message.type === 'resize') { + setComputedAutoHeight(message.height); + } else if (message.type === 'start') { + if (message.firstLoad) { + setBundlerIsReady(false); + } + + /** + * The spinner component transition might be longer than + * the bundler loading, so we only show the spinner if + * it takes more than 1s to load the bundler. + */ + timeout = setTimeout(() => { + setShowLoading(true); + }, 500); + } else if (message.type === 'done') { + setBundlerIsReady(true); + setShowLoading(false); + clearTimeout(timeout); + } + }, clientId); + + return () => { + clearTimeout(timeout); + setBundlerIsReady(false); + setComputedAutoHeight(null); + unsubscribe(); + }; + }, + [sandpackIdle] + ); + + // WARNING: + // The layout and styling here is convoluted and really easy to break. + // If you make changes to it, you need to test different cases: + // - Content -> (compile | runtime) error -> content editing flow should work. + // - Errors should expand parent height rather than scroll. + // - Long sandboxes should scroll unless "show more" is toggled. + // - Expanded sandboxes ("show more") have sticky previews and errors. + // - Sandboxes have autoheight based on content. + // - That autoheight should be measured correctly! (Check some long ones.) + // - You shouldn't see nested scrolls (that means autoheight is borked). + // - Ideally you shouldn't see a blank preview tile while recompiling. + // - Container shouldn't be horizontally scrollable (even while loading). + // - It should work on mobile. + // The best way to test it is to actually go through some challenges. + + const hideContent = error || !iframeComputedHeight || !bundlerIsReady; + + const iframeWrapperPosition = (): CSSProperties => { + if (hideContent) { + return {position: 'relative'}; + } + + if (isExpanded) { + return {position: 'sticky', top: '2em'}; + } + + return {}; + }; + + return ( + <SandpackStack className={className}> + <div + className={cn( + 'p-0 sm:p-2 md:p-4 lg:p-8 bg-card dark:bg-wash-dark h-full relative md:rounded-b-lg lg:rounded-b-none', + // Allow content to be scrolled if it's too high to fit. + // Note we don't want this in the expanded state + // because it breaks position: sticky (and isn't needed anyway). + !isExpanded && (error || bundlerIsReady) ? 'overflow-auto' : null + )}> + <div style={iframeWrapperPosition()}> + <iframe + ref={iframeRef} + className={cn( + 'rounded-t-none bg-white md:shadow-md sm:rounded-lg w-full max-w-full transition-opacity', + // We can't *actually* hide content because that would + // break calculating the computed height in the iframe + // (which we're using for autosizing). This is noticeable + // if you make a compiler error and then fix it with code + // that expands the content. You want to measure that. + hideContent + ? 'absolute opacity-0 pointer-events-none duration-75' + : 'opacity-100 duration-150' + )} + title="Sandbox Preview" + style={{ + height: iframeComputedHeight || '15px', + zIndex: isExpanded ? 'initial' : -1, + }} + /> + </div> + + {error && ( + <div + className={cn( + 'z-50', + // This isn't absolutely positioned so that + // the errors can also expand the parent height. + isExpanded ? 'sticky top-8 ' : null + )}> + <ErrorMessage error={error} /> + </div> + )} + + <LoadingOverlay + clientId={clientId} + dependenciesLoading={!bundlerIsReady && iframeComputedHeight === null} + forceLoading={showLoading} + /> + </div> + <SandpackConsole visible={!error} /> + </SandpackStack> + ); +} diff --git a/beta/src/components/MDX/Sandpack/ResetButton.tsx b/beta/src/components/MDX/Sandpack/ResetButton.tsx new file mode 100644 index 000000000..1ac413138 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/ResetButton.tsx @@ -0,0 +1,21 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import {IconRestart} from '../../Icon/IconRestart'; +export interface ResetButtonProps { + onReset: () => void; +} + +export function ResetButton({onReset}: ResetButtonProps) { + return ( + <button + className="text-sm text-primary dark:text-primary-dark inline-flex items-center hover:text-link duration-100 ease-in transition mx-1" + onClick={onReset} + title="Reset Sandbox" + type="button"> + <IconRestart className="inline ml-1 mr-1 relative" /> Reset + </button> + ); +} diff --git a/beta/src/components/MDX/Sandpack/SandpackRoot.tsx b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx new file mode 100644 index 000000000..4b3f41527 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx @@ -0,0 +1,104 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Children, useState} from 'react'; +import * as React from 'react'; +import {SandpackProvider} from '@codesandbox/sandpack-react'; +import {SandpackLogLevel} from '@codesandbox/sandpack-client'; +import {CustomPreset} from './CustomPreset'; +import {createFileMap} from './createFileMap'; +import {CustomTheme} from './Themes'; + +type SandpackProps = { + children: React.ReactNode; + autorun?: boolean; + showDevTools?: boolean; +}; + +const sandboxStyle = ` +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +h1 { + margin-top: 0; + font-size: 22px; +} + +h2 { + margin-top: 0; + font-size: 20px; +} + +h3 { + margin-top: 0; + font-size: 18px; +} + +h4 { + margin-top: 0; + font-size: 16px; +} + +h5 { + margin-top: 0; + font-size: 14px; +} + +h6 { + margin-top: 0; + font-size: 12px; +} + +code { + font-size: 1.2em; +} + +ul { + padding-left: 20px; +} +`.trim(); + +function SandpackRoot(props: SandpackProps) { + let {children, autorun = true, showDevTools = false} = props; + const [devToolsLoaded, setDevToolsLoaded] = useState(false); + const codeSnippets = Children.toArray(children) as React.ReactElement[]; + const files = createFileMap(codeSnippets); + + files['/styles.css'] = { + code: [sandboxStyle, files['/styles.css']?.code ?? ''].join('\n\n'), + hidden: !files['/styles.css']?.visible, + }; + + return ( + <div className="sandpack sandpack--playground my-8"> + <SandpackProvider + template="react" + files={files} + theme={CustomTheme} + options={{ + autorun, + initMode: 'user-visible', + initModeObserverOptions: {rootMargin: '1400px 0px'}, + bundlerURL: 'https://dad0ba0e.sandpack-bundler-4bw.pages.dev', + logLevel: SandpackLogLevel.None, + }}> + <CustomPreset + showDevTools={showDevTools} + onDevToolsLoad={() => setDevToolsLoaded(true)} + devToolsLoaded={devToolsLoaded} + providedFiles={Object.keys(files)} + /> + </SandpackProvider> + </div> + ); +} + +export default SandpackRoot; diff --git a/beta/src/components/MDX/Sandpack/Themes.tsx b/beta/src/components/MDX/Sandpack/Themes.tsx new file mode 100644 index 000000000..98c4b00eb --- /dev/null +++ b/beta/src/components/MDX/Sandpack/Themes.tsx @@ -0,0 +1,43 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import tailwindConfig from '../../../../tailwind.config'; + +export const CustomTheme = { + colors: { + accent: 'inherit', + base: 'inherit', + clickable: 'inherit', + disabled: 'inherit', + error: 'inherit', + errorSurface: 'inherit', + hover: 'inherit', + surface1: 'inherit', + surface2: 'inherit', + surface3: 'inherit', + warning: 'inherit', + warningSurface: 'inherit', + }, + syntax: { + plain: 'inherit', + comment: 'inherit', + keyword: 'inherit', + tag: 'inherit', + punctuation: 'inherit', + definition: 'inherit', + property: 'inherit', + static: 'inherit', + string: 'inherit', + }, + font: { + body: tailwindConfig.theme.extend.fontFamily.sans + .join(', ') + .replace(/"/gm, ''), + mono: tailwindConfig.theme.extend.fontFamily.mono + .join(', ') + .replace(/"/gm, ''), + size: tailwindConfig.theme.extend.fontSize.code, + lineHeight: '24px', + }, +}; diff --git a/beta/src/components/MDX/Sandpack/createFileMap.ts b/beta/src/components/MDX/Sandpack/createFileMap.ts new file mode 100644 index 000000000..89d53cfd2 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/createFileMap.ts @@ -0,0 +1,53 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import type {SandpackFile} from '@codesandbox/sandpack-react'; + +export const createFileMap = (codeSnippets: any) => { + return codeSnippets.reduce( + (result: Record<string, SandpackFile>, codeSnippet: React.ReactElement) => { + if ((codeSnippet.type as any).mdxName !== 'pre') { + return result; + } + const {props} = codeSnippet.props.children; + let filePath; // path in the folder structure + let fileHidden = false; // if the file is available as a tab + let fileActive = false; // if the file tab is shown by default + + if (props.meta) { + const [name, ...params] = props.meta.split(' '); + filePath = '/' + name; + if (params.includes('hidden')) { + fileHidden = true; + } + if (params.includes('active')) { + fileActive = true; + } + } else { + if (props.className === 'language-js') { + filePath = '/App.js'; + } else if (props.className === 'language-css') { + filePath = '/styles.css'; + } else { + throw new Error( + `Code block is missing a filename: ${props.children}` + ); + } + } + if (result[filePath]) { + throw new Error( + `File ${filePath} was defined multiple times. Each file snippet should have a unique path name` + ); + } + result[filePath] = { + code: (props.children || '') as string, + hidden: fileHidden, + active: fileActive, + }; + + return result; + }, + {} + ); +}; diff --git a/beta/src/components/MDX/Sandpack/index.tsx b/beta/src/components/MDX/Sandpack/index.tsx new file mode 100644 index 000000000..3591e0581 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/index.tsx @@ -0,0 +1,70 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {lazy, memo, Children, Suspense} from 'react'; +import {createFileMap} from './createFileMap'; + +const SandpackRoot = lazy(() => import('./SandpackRoot')); + +const SandpackGlimmer = ({code}: {code: string}) => ( + <div className="sandpack sandpack--playground my-8"> + <div className="sp-wrapper"> + <div className="shadow-lg dark:shadow-lg-dark rounded-lg"> + <div className="bg-wash h-10 dark:bg-card-dark flex justify-between items-center relative z-10 border-b border-border dark:border-border-dark rounded-t-lg rounded-b-none"> + <div className="px-4 lg:px-6"> + <div className="sp-tabs"></div> + </div> + <div className="px-3 flex items-center justify-end grow text-right"></div> + </div> + <div className="sp-layout min-h-[216px] flex items-stretch flex-wrap"> + <div className="sp-stack sp-editor max-h-[406px] h-auto overflow-auto"> + <div className="sp-code-editor"> + <div className="sp-cm sp-pristine"> + <div className="cm-editor"> + <div> + <div className="cm-gutters pl-9 sticky min-h-[192px]"> + <div className="cm-gutter cm-lineNumbers whitespace-pre sp-pre-placeholder"> + {code} + </div> + </div> + </div> + </div> + </div> + </div> + </div> + <div className="sp-stack order-last xl:order-2 max-h-[406px] h-auto"> + <div className="p-0 sm:p-2 md:p-4 lg:p-8 bg-card dark:bg-wash-dark h-full relative rounded-b-lg lg:rounded-b-none overflow-auto"></div> + </div> + {code.split('\n').length > 16 && ( + <div className="flex h-[45px] text-base justify-between dark:border-card-dark bg-wash dark:bg-card-dark items-center z-10 rounded-t-none p-1 w-full order-2 xl:order-last border-b-1 relative top-0"></div> + )} + </div> + </div> + </div> + </div> +); + +export default memo(function SandpackWrapper(props: any): any { + const codeSnippet = createFileMap(Children.toArray(props.children)); + + // To set the active file in the fallback we have to find the active file first. + // If there are no active files we fallback to App.js as default. + let activeCodeSnippet = Object.keys(codeSnippet).filter( + (fileName) => + codeSnippet[fileName]?.active === true && + codeSnippet[fileName]?.hidden === false + ); + let activeCode; + if (!activeCodeSnippet.length) { + activeCode = codeSnippet['/App.js'].code; + } else { + activeCode = codeSnippet[activeCodeSnippet[0]].code; + } + + return ( + <Suspense fallback={<SandpackGlimmer code={activeCode} />}> + <SandpackRoot {...props} /> + </Suspense> + ); +}); diff --git a/beta/src/components/MDX/Sandpack/runESLint.tsx b/beta/src/components/MDX/Sandpack/runESLint.tsx new file mode 100644 index 000000000..5fea2f110 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/runESLint.tsx @@ -0,0 +1,86 @@ +// @ts-nocheck + +import {Linter} from 'eslint/lib/linter/linter'; + +import type {Diagnostic} from '@codemirror/lint'; +import type {Text} from '@codemirror/text'; + +const getCodeMirrorPosition = ( + doc: Text, + {line, column}: {line: number; column?: number} +): number => { + return doc.line(line).from + (column ?? 0) - 1; +}; + +const linter = new Linter(); + +// HACK! Eslint requires 'esquery' using `require`, but there's no commonjs interop. +// because of this it tries to run `esquery.parse()`, while there's only `esquery.default.parse()`. +// This hack places the functions in the right place. +const esquery = require('esquery'); +esquery.parse = esquery.default?.parse; +esquery.matches = esquery.default?.matches; + +const reactRules = require('eslint-plugin-react-hooks').rules; +linter.defineRules({ + 'react-hooks/rules-of-hooks': reactRules['rules-of-hooks'], + 'react-hooks/exhaustive-deps': reactRules['exhaustive-deps'], +}); + +const options = { + parserOptions: { + ecmaVersion: 12, + sourceType: 'module', + ecmaFeatures: {jsx: true}, + }, + rules: { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', + }, +}; + +export const runESLint = ( + doc: Text +): {errors: any[]; codeMirrorErrors: Diagnostic[]} => { + const codeString = doc.toString(); + const errors = linter.verify(codeString, options) as any[]; + + const severity = { + 1: 'warning', + 2: 'error', + }; + + const codeMirrorErrors = errors + .map((error) => { + if (!error) return undefined; + + const from = getCodeMirrorPosition(doc, { + line: error.line, + column: error.column, + }); + + const to = getCodeMirrorPosition(doc, { + line: error.endLine ?? error.line, + column: error.endColumn ?? error.column, + }); + + return { + ruleId: error.ruleId, + from, + to, + severity: severity[error.severity], + message: error.message, + }; + }) + .filter(Boolean) as Diagnostic[]; + + return { + codeMirrorErrors, + errors: errors.map((item) => { + return { + ...item, + severity: severity[item.severity], + }; + }), + }; +}; diff --git a/beta/src/components/MDX/Sandpack/useSandpackLint.tsx b/beta/src/components/MDX/Sandpack/useSandpackLint.tsx new file mode 100644 index 000000000..ec05fbe0d --- /dev/null +++ b/beta/src/components/MDX/Sandpack/useSandpackLint.tsx @@ -0,0 +1,40 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// @ts-nocheck + +import {useState, useEffect} from 'react'; +import type {EditorView} from '@codemirror/view'; + +export type LintDiagnostic = { + line: number; + column: number; + severity: 'warning' | 'error'; + message: string; +}[]; + +export const useSandpackLint = () => { + const [lintErrors, setLintErrors] = useState<LintDiagnostic>([]); + const [lintExtensions, setLintExtensions] = useState<any>([]); + useEffect(() => { + const loadLinter = async () => { + const {linter} = await import('@codemirror/lint'); + const onLint = linter(async (props: EditorView) => { + // This is intentionally delayed until CodeMirror calls it + // so that we don't take away bandwidth from things loading early. + const {runESLint} = await import('./runESLint'); + const editorState = props.state.doc; + let {errors, codeMirrorErrors} = runESLint(editorState); + // Ignore parsing or internal linter errors. + const isReactRuleError = (error: any) => error.ruleId != null; + setLintErrors(errors.filter(isReactRuleError)); + return codeMirrorErrors.filter(isReactRuleError); + }); + setLintExtensions([onLint]); + }; + + loadLinter(); + }, []); + return {lintErrors, lintExtensions}; +}; diff --git a/beta/src/components/MDX/SimpleCallout.tsx b/beta/src/components/MDX/SimpleCallout.tsx new file mode 100644 index 000000000..348a42b67 --- /dev/null +++ b/beta/src/components/MDX/SimpleCallout.tsx @@ -0,0 +1,31 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import cn from 'classnames'; +import {H3} from './Heading'; + +interface SimpleCalloutProps { + title: string; + children: React.ReactNode; + className?: string; +} +function SimpleCallout({title, children, className}: SimpleCalloutProps) { + return ( + <div + className={cn( + 'p-6 xl:p-8 pb-4 xl:pb-6 bg-card dark:bg-card-dark rounded-lg shadow-inner text-base text-secondary dark:text-secondary-dark my-8', + className + )}> + <H3 + className="text-primary dark:text-primary-dark mt-0 mb-3 leading-tight" + isPageAnchor={false}> + {title} + </H3> + {children} + </div> + ); +} + +export default SimpleCallout; diff --git a/beta/src/components/MDX/TeamMember.tsx b/beta/src/components/MDX/TeamMember.tsx new file mode 100644 index 000000000..887e9f691 --- /dev/null +++ b/beta/src/components/MDX/TeamMember.tsx @@ -0,0 +1,98 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import Image from 'next/image'; +import {IconTwitter} from '../Icon/IconTwitter'; +import {IconGitHub} from '../Icon/IconGitHub'; +import {ExternalLink} from '../ExternalLink'; +import {IconNewPage} from 'components/Icon/IconNewPage'; +import {H3} from './Heading'; +import {IconLink} from 'components/Icon/IconLink'; + +interface TeamMemberProps { + name: string; + title: string; + permalink: string; + children: React.ReactNode; + photo: string; + twitter?: string; + github?: string; + personal?: string; +} + +// TODO: good alt text for images/links +export function TeamMember({ + name, + title, + permalink, + children, + photo, + github, + twitter, + personal, +}: TeamMemberProps) { + if (name == null || title == null || permalink == null || children == null) { + throw new Error( + 'Expected name, title, permalink, and children for ' + name ?? + title ?? + permalink ?? + 'unknown' + ); + } + return ( + <div className="pb-6 sm:pb-10"> + <div className="flex flex-col sm:flex-row height-auto"> + <div + className="hidden sm:block basis-2/5 rounded overflow-hidden relative" + style={{width: 300, height: 250}}> + <Image src={photo} layout="fill" objectFit="cover" alt={name} /> + </div> + <div + style={{minHeight: 300}} + className="block w-full sm:hidden flex-grow basis-2/5 rounded overflow-hidden relative"> + <Image src={photo} layout="fill" objectFit="cover" alt={name} /> + </div> + <div className="pl-0 sm:pl-6 basis-3/5 items-start"> + <H3 className="mb-1 sm:my-0" id={permalink}> + {name} + </H3> + {title && <div>{title}</div>} + {children} + <div className="sm:flex sm:flex-row"> + {twitter && ( + <div className="mr-4"> + <ExternalLink + aria-label="React on Twitter" + href={`https://twitter.com/${twitter}`} + className="hover:text-primary dark:text-primary-dark flex flex-row items-center"> + <IconTwitter className="pr-2" /> + {twitter} + </ExternalLink> + </div> + )} + {github && ( + <div className="mr-4"> + <ExternalLink + aria-label="GitHub Profile" + href={`https://github.com/${github}`} + className="hover:text-primary dark:text-primary-dark flex flex-row items-center"> + <IconGitHub className="pr-2" /> {github} + </ExternalLink> + </div> + )} + {personal && ( + <ExternalLink + aria-label="Personal Site" + href={`https://${personal}`} + className="hover:text-primary dark:text-primary-dark flex flex-row items-center"> + <IconLink className="pr-2" /> {personal} + </ExternalLink> + )} + </div> + </div> + </div> + </div> + ); +} diff --git a/beta/src/components/MDX/TerminalBlock.tsx b/beta/src/components/MDX/TerminalBlock.tsx new file mode 100644 index 000000000..9fb5ff35f --- /dev/null +++ b/beta/src/components/MDX/TerminalBlock.tsx @@ -0,0 +1,81 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {isValidElement, useState, useEffect} from 'react'; +import * as React from 'react'; +import {IconTerminal} from '../Icon/IconTerminal'; +import {IconCopy} from 'components/Icon/IconCopy'; + +type LogLevel = 'info' | 'warning' | 'error'; + +interface TerminalBlockProps { + level?: LogLevel; + children: React.ReactNode; +} + +function LevelText({type}: {type: LogLevel}) { + switch (type) { + case 'warning': + return <span className="text-yellow-50 bg-none mr-1">Warning: </span>; + case 'error': + return <span className="text-red-40 mr-1">Error: </span>; + default: + return null; + } +} + +function TerminalBlock({level = 'info', children}: TerminalBlockProps) { + let message: string | undefined; + if (typeof children === 'string') { + message = children; + } else if ( + isValidElement(children) && + typeof children.props.children === 'string' + ) { + message = children.props.children; + } else { + throw Error('Expected TerminalBlock children to be a plain string.'); + } + + const [copied, setCopied] = useState(false); + useEffect(() => { + if (!copied) { + return; + } else { + const timer = setTimeout(() => { + setCopied(false); + }, 2000); + return () => clearTimeout(timer); + } + }, [copied]); + + return ( + <div className="rounded-lg bg-secondary dark:bg-gray-50 h-full"> + <div className="bg-gray-90 dark:bg-gray-60 w-full rounded-t-lg"> + <div className="text-primary-dark dark:text-primary-dark flex text-sm px-4 py-0.5 relative justify-between"> + <div> + <IconTerminal className="inline-flex mr-2 self-center" /> Terminal + </div> + <div> + <button + className="w-full text-left text-primary-dark dark:text-primary-dark " + onClick={() => { + window.navigator.clipboard.writeText(message ?? ''); + setCopied(true); + }}> + <IconCopy className="inline-flex mr-2 self-center" />{' '} + {copied ? 'Copied' : 'Copy'} + </button> + </div> + </div> + </div> + <div className="px-8 pt-4 pb-6 text-primary-dark dark:text-primary-dark font-mono text-code whitespace-pre"> + <LevelText type={level} /> + {message} + </div> + </div> + ); +} + +export default TerminalBlock; diff --git a/beta/src/components/MDX/TocContext.tsx b/beta/src/components/MDX/TocContext.tsx new file mode 100644 index 000000000..8aeead370 --- /dev/null +++ b/beta/src/components/MDX/TocContext.tsx @@ -0,0 +1,15 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {createContext} from 'react'; +import type {ReactNode} from 'react'; + +export type TocItem = { + url: string; + text: ReactNode; + depth: number; +}; +export type Toc = Array<TocItem>; + +export const TocContext = createContext<Toc>([]); diff --git a/beta/src/components/MDX/YouWillLearnCard.tsx b/beta/src/components/MDX/YouWillLearnCard.tsx new file mode 100644 index 000000000..839876029 --- /dev/null +++ b/beta/src/components/MDX/YouWillLearnCard.tsx @@ -0,0 +1,39 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import ButtonLink from 'components/ButtonLink'; +import {IconNavArrow} from 'components/Icon/IconNavArrow'; + +interface YouWillLearnCardProps { + title: string; + path: string; + children: React.ReactNode; +} + +function YouWillLearnCard({title, path, children}: YouWillLearnCardProps) { + return ( + <div className="flex flex-col h-full bg-card dark:bg-card-dark shadow-inner justify-between rounded-lg pb-8 p-6 xl:p-8 mt-3"> + <div> + <h4 className="text-primary dark:text-primary-dark font-bold text-2xl leading-tight"> + {title} + </h4> + <div className="my-4">{children}</div> + </div> + <div> + <ButtonLink + href={path} + className="mt-1" + type="primary" + size="md" + label={title}> + Read More + <IconNavArrow displayDirection="right" className="inline ml-1" /> + </ButtonLink> + </div> + </div> + ); +} + +export default YouWillLearnCard; diff --git a/beta/src/components/PageHeading.tsx b/beta/src/components/PageHeading.tsx new file mode 100644 index 000000000..2f4d3b0c8 --- /dev/null +++ b/beta/src/components/PageHeading.tsx @@ -0,0 +1,50 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import Breadcrumbs from 'components/Breadcrumbs'; +import Tag from 'components/Tag'; +import {H1} from './MDX/Heading'; +import type {RouteTag, RouteItem} from './Layout/getRouteMeta'; + +interface PageHeadingProps { + title: string; + status?: string; + description?: string; + tags?: RouteTag[]; + breadcrumbs: RouteItem[]; +} + +function PageHeading({ + title, + status, + description, + tags = [], + breadcrumbs, +}: PageHeadingProps) { + return ( + <div className="px-5 sm:px-12 pt-8 sm:pt-7 lg:pt-5"> + <div className="max-w-4xl ml-0 2xl:mx-auto"> + {breadcrumbs ? <Breadcrumbs breadcrumbs={breadcrumbs} /> : null} + <H1 className="mt-0 text-primary dark:text-primary-dark -mx-.5 break-words"> + {title} + {status ? <em>—{status}</em> : ''} + </H1> + {description && ( + <p className="mt-4 mb-6 text-primary dark:text-primary-dark text-xl text-gray-90 leading-large"> + {description} + </p> + )} + {tags?.length > 0 && ( + <div className="mt-4"> + {tags.map((tag) => ( + <Tag key={tag} variant={tag as RouteTag} /> + ))} + </div> + )} + </div> + </div> + ); +} + +export default PageHeading; diff --git a/beta/src/components/Search.tsx b/beta/src/components/Search.tsx new file mode 100644 index 000000000..49088276d --- /dev/null +++ b/beta/src/components/Search.tsx @@ -0,0 +1,195 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// @ts-ignore +import {IconSearch} from 'components/Icon/IconSearch'; +import Head from 'next/head'; +import Link from 'next/link'; +import Router from 'next/router'; +import {useState, useCallback, useEffect} from 'react'; +import * as React from 'react'; +import {createPortal} from 'react-dom'; +import {siteConfig} from 'siteConfig'; + +export interface SearchProps { + appId?: string; + apiKey?: string; + indexName?: string; + searchParameters?: any; + renderModal?: boolean; +} + +function Hit({hit, children}: any) { + return ( + <Link href={hit.url.replace()}> + <a>{children}</a> + </Link> + ); +} + +function Kbd(props: {children?: React.ReactNode}) { + return ( + <kbd + className="h-6 w-6 border border-transparent mr-1 bg-wash dark:bg-wash-dark text-gray-30 align-middle p-0 inline-flex justify-center items-center text-xs text-center rounded" + {...props} + /> + ); +} + +// Copy-pasted from @docsearch/react to avoid importing the whole bundle. +// Slightly trimmed to features we use. +// (c) Algolia, Inc. +function isEditingContent(event: any) { + var element = event.target; + var tagName = element.tagName; + return ( + element.isContentEditable || + tagName === 'INPUT' || + tagName === 'SELECT' || + tagName === 'TEXTAREA' + ); +} +function useDocSearchKeyboardEvents({ + isOpen, + onOpen, + onClose, +}: { + isOpen: boolean; + onOpen: () => void; + onClose: () => void; +}) { + useEffect(() => { + function onKeyDown(event: any) { + function open() { + // We check that no other DocSearch modal is showing before opening + // another one. + if (!document.body.classList.contains('DocSearch--active')) { + onOpen(); + } + } + if ( + (event.keyCode === 27 && isOpen) || + (event.key === 'k' && (event.metaKey || event.ctrlKey)) || + (!isEditingContent(event) && event.key === '/' && !isOpen) + ) { + event.preventDefault(); + if (isOpen) { + onClose(); + } else if (!document.body.classList.contains('DocSearch--active')) { + open(); + } + } + } + + window.addEventListener('keydown', onKeyDown); + return function () { + window.removeEventListener('keydown', onKeyDown); + }; + }, [isOpen, onOpen, onClose]); +} + +const options = { + appId: siteConfig.algolia.appId, + apiKey: siteConfig.algolia.apiKey, + indexName: siteConfig.algolia.indexName, +}; +let DocSearchModal: any = null; +export function Search({ + searchParameters = { + hitsPerPage: 5, + }, +}: SearchProps) { + const [isShowing, setIsShowing] = useState(false); + + const importDocSearchModalIfNeeded = useCallback( + function importDocSearchModalIfNeeded() { + if (DocSearchModal) { + return Promise.resolve(); + } + + // @ts-ignore + return import('@docsearch/react/modal').then( + ({DocSearchModal: Modal}) => { + DocSearchModal = Modal; + } + ); + }, + [] + ); + + const onOpen = useCallback( + function onOpen() { + importDocSearchModalIfNeeded().then(() => { + setIsShowing(true); + }); + }, + [importDocSearchModalIfNeeded, setIsShowing] + ); + + const onClose = useCallback( + function onClose() { + setIsShowing(false); + }, + [setIsShowing] + ); + + useDocSearchKeyboardEvents({isOpen: isShowing, onOpen, onClose}); + + return ( + <> + <Head> + <link + rel="preconnect" + href={`https://${options.appId}-dsn.algolia.net`} + /> + </Head> + + <button + aria-label="Search" + type="button" + className="inline-flex md:hidden items-center text-lg p-1 ml-4 lg:ml-6" + onClick={onOpen}> + <IconSearch className="align-middle" /> + </button> + + <button + type="button" + className="hidden md:flex relative pl-4 pr-1 py-1 h-10 bg-secondary-button dark:bg-gray-80 outline-none focus:ring focus:outline-none betterhover:hover:bg-opacity-80 pointer items-center shadow-inner text-left w-full text-gray-30 rounded-md align-middle text-sm" + onClick={onOpen}> + <IconSearch className="mr-3 align-middle text-gray-30 shrink-0 group-betterhover:hover:text-gray-70" /> + Search + <span className="ml-auto hidden sm:flex item-center"> + <Kbd>⌘</Kbd> + <Kbd>K</Kbd> + </span> + </button> + + {isShowing && + createPortal( + <DocSearchModal + {...options} + initialScrollY={window.scrollY} + searchParameters={searchParameters} + onClose={onClose} + navigator={{ + navigate({itemUrl}: any) { + Router.push(itemUrl); + }, + }} + transformItems={(items: any[]) => { + return items.map((item) => { + const url = new URL(item.url); + return { + ...item, + url: item.url.replace(url.origin, '').replace('#__next', ''), + }; + }); + }} + hitComponent={Hit} + />, + document.body + )} + </> + ); +} diff --git a/beta/src/components/Seo.tsx b/beta/src/components/Seo.tsx new file mode 100644 index 000000000..1e641d50f --- /dev/null +++ b/beta/src/components/Seo.tsx @@ -0,0 +1,117 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; +import Head from 'next/head'; +import {withRouter, Router} from 'next/router'; + +export interface SeoProps { + title: string; + description?: string; + image?: string; + // jsonld?: JsonLDType | Array<JsonLDType>; + children?: React.ReactNode; + isHomePage: boolean; +} + +export const Seo = withRouter( + ({ + title, + description = 'A JavaScript library for building user interfaces', + image = '/logo-og.png', + router, + children, + isHomePage, + }: SeoProps & {router: Router}) => ( + <Head> + {/* DEFAULT */} + + <meta name="viewport" content="width=device-width, initial-scale=1" /> + + {title != null && ( + <title key="title">{title + (isHomePage ? '' : ' • React')}</title> + )} + {description != null && ( + <meta name="description" key="description" content={description} /> + )} + {/* <link rel="icon" type="image/x-icon" href={favicon} /> + <link rel="apple-touch-icon" href={favicon} /> @todo favicon */} + <meta property="fb:app_id" content="623268441017527" /> + {/* OPEN GRAPH */} + <meta property="og:type" key="og:type" content="website" /> + <meta + property="og:url" + key="og:url" + content={`https://beta.reactjs.org${router.asPath.split(/[\?\#]/)[0]}`} + /> + {title != null && ( + <meta property="og:title" content={title} key="og:title" /> + )} + {description != null && ( + <meta + property="og:description" + key="og:description" + content={description} + /> + )} + + <meta + property="og:image" + key="og:image" + content={`https://beta.reactjs.org${image}`} + /> + + {/* TWITTER */} + <meta + name="twitter:card" + key="twitter:card" + content="summary_large_image" + /> + <meta name="twitter:site" key="twitter:site" content="@reactjs" /> + <meta name="twitter:creator" key="twitter:creator" content="@reactjs" /> + {title != null && ( + <meta name="twitter:title" key="twitter:title" content={title} /> + )} + {description != null && ( + <meta + name="twitter:description" + key="twitter:description" + content={description} + /> + )} + + <meta + name="twitter:image" + key="twitter:image" + content={`https://beta.reactjs.org${image}`} + /> + <meta + name="google-site-verification" + content="j1duf8XRaKuZyGvhPd8GkYXHG7LI4GYbIvAXBsqTC9U" + /> + <link + rel="preload" + href="/fonts/Source-Code-Pro-Regular.woff2" + as="font" + type="font/woff2" + crossOrigin="anonymous" + /> + <link + rel="preload" + href="https://beta.reactjs.org/fonts/Optimistic_Display_W_Md.woff2" + as="font" + type="font/woff2" + crossOrigin="anonymous" + /> + <link + rel="preload" + href="https://beta.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2" + as="font" + type="font/woff2" + crossOrigin="anonymous" + /> + {children} + </Head> + ) +); diff --git a/beta/src/components/SocialBanner.tsx b/beta/src/components/SocialBanner.tsx new file mode 100644 index 000000000..826119c14 --- /dev/null +++ b/beta/src/components/SocialBanner.tsx @@ -0,0 +1,48 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + */ + +import {useRef, useEffect} from 'react'; +import cn from 'classnames'; +import {ExternalLink} from './ExternalLink'; + +const bannerText = 'Support Ukraine 🇺🇦'; +const bannerLink = 'https://opensource.fb.com/support-ukraine'; +const bannerLinkText = 'Help Provide Humanitarian Aid to Ukraine'; + +export default function SocialBanner() { + const ref = useRef<HTMLDivElement | null>(null); + useEffect(() => { + function patchedScrollTo(x: number, y: number) { + if (y === 0) { + // We're trying to reset scroll. + // If we already scrolled past the banner, consider it as y = 0. + const bannerHeight = ref.current?.offsetHeight ?? 0; // Could be zero (e.g. mobile) + y = Math.min(window.scrollY, bannerHeight); + } + return realScrollTo(x, y); + } + const realScrollTo = window.scrollTo; + (window as any).scrollTo = patchedScrollTo; + return () => { + (window as any).scrollTo = realScrollTo; + }; + }, []); + return ( + <div + ref={ref} + className={cn( + `h-[40px] hidden lg:flex w-full bg-gray-100 dark:bg-gray-700 text-base md:text-lg py-2 sm:py-0 items-center justify-center flex-col sm:flex-row z-[100]` + )}> + <div className="hidden sm:block">{bannerText}</div> + <ExternalLink + className="ml-0 sm:ml-1 text-link dark:text-link-dark hover:underline" + href={bannerLink}> + <div className="inline sm:hidden">🇺🇦 </div> + {bannerLinkText} + <span className="hidden sm:inline">.</span> + </ExternalLink> + </div> + ); +} diff --git a/beta/src/components/Tag.tsx b/beta/src/components/Tag.tsx new file mode 100644 index 000000000..7033e030a --- /dev/null +++ b/beta/src/components/Tag.tsx @@ -0,0 +1,52 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import cn from 'classnames'; +import type {RouteTag} from './Layout/getRouteMeta'; + +const variantMap = { + foundation: { + name: 'Foundation', + classes: 'bg-yellow-50 text-white', + }, + intermediate: { + name: 'Intermediate', + classes: 'bg-purple-40 text-white', + }, + advanced: { + name: 'Advanced', + classes: 'bg-green-40 text-white', + }, + experimental: { + name: 'Experimental', + classes: 'bg-ui-orange text-white', + }, + deprecated: { + name: 'Deprecated', + classes: 'bg-red-40 text-white', + }, +}; + +interface TagProps { + variant: RouteTag; + text?: string; + className?: string; +} + +function Tag({text, variant, className}: TagProps) { + const {name, classes} = variantMap[variant]; + return ( + <span className={cn('mr-2', className)}> + <span + className={cn( + 'inline font-bold text-sm uppercase py-1 px-2 rounded', + classes + )}> + {text || name} + </span> + </span> + ); +} + +export default Tag; diff --git a/beta/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md b/beta/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md new file mode 100644 index 000000000..38fa3503e --- /dev/null +++ b/beta/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md @@ -0,0 +1,31 @@ +--- +title: "Introducing Zero-Bundle-Size React Server Components" +--- + +December 21, 2020 by [Dan Abramov](https://twitter.com/dan_abramov), [Lauren Tan](https://twitter.com/potetotes), [Joseph Savona](https://twitter.com/en_JS), and [Sebastian Markbåge](https://twitter.com/sebmarkbage) + +--- + +<Intro> + +2020 has been a long year. As it comes to an end we wanted to share a special Holiday Update on our research into zero-bundle-size **React Server Components**. + +</Intro> + +--- + +To introduce React Server Components, we have prepared a talk and a demo. If you want, you can check them out during the holidays, or later when work picks back up in the new year. + +<YouTubeIframe src="https://www.youtube.com/embed/TQQPAU21ZUw" /> + +**React Server Components are still in research and development.** We are sharing this work in the spirit of transparency and to get initial feedback from the React community. There will be plenty of time for that, so **don't feel like you have to catch up right now!** + +If you want to check them out, we recommend to go in the following order: + +1. **Watch the talk** to learn about React Server Components and see the demo. + +2. **[Clone the demo](http://github.com/reactjs/server-components-demo)** to play with React Server Components on your computer. + +3. **[Read the RFC (with FAQ at the end)](https://github.com/reactjs/rfcs/pull/188)** for a deeper technical breakdown and to provide feedback. + +We are excited to hear from you on the RFC or in replies to the [@reactjs](https://twitter.com/reactjs) Twitter handle. Happy holidays, stay safe, and see you next year! diff --git a/beta/src/content/blog/2021/06/08/the-plan-for-react-18.md b/beta/src/content/blog/2021/06/08/the-plan-for-react-18.md new file mode 100644 index 000000000..0bf744c1d --- /dev/null +++ b/beta/src/content/blog/2021/06/08/the-plan-for-react-18.md @@ -0,0 +1,68 @@ +--- +title: "The Plan for React 18" +--- + +June 8, 2021 by [Andrew Clark](https://twitter.com/acdlite), [Brian Vaughn](https://github.com/bvaughn), [Christine Abernathy](https://twitter.com/abernathyca), [Dan Abramov](https://twitter.com/dan_abramov), [Rachel Nabors](https://twitter.com/rachelnabors), [Rick Hanlon](https://twitter.com/rickhanlonii), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Seth Webster](https://twitter.com/sethwebster) + +--- + +<Intro> + +The React team is excited to share a few updates: + +1. We’ve started work on the React 18 release, which will be our next major version. +2. We’ve created a Working Group to prepare the community for gradual adoption of new features in React 18. +3. We’ve published a React 18 Alpha so that library authors can try it and provide feedback. + +These updates are primarily aimed at maintainers of third-party libraries. If you’re learning, teaching, or using React to build user-facing applications, you can safely ignore this post. But you are welcome to follow the discussions in the React 18 Working Group if you're curious! + +--- + +</Intro> + +## What’s coming in React 18 {/*whats-coming-in-react-18*/} + +When it’s released, React 18 will include out-of-the-box improvements (like [automatic batching](https://github.com/reactwg/react-18/discussions/21)), new APIs (like [`startTransition`](https://github.com/reactwg/react-18/discussions/41)), and a [new streaming server renderer](https://github.com/reactwg/react-18/discussions/37) with built-in support for `React.lazy`. + +These features are possible thanks to a new opt-in mechanism we’re adding in React 18. It’s called “concurrent rendering” and it lets React prepare multiple versions of the UI at the same time. This change is mostly behind-the-scenes, but it unlocks new possibilities to improve both real and perceived performance of your app. + +If you've been following our research into the future of React (we don't expect you to!), you might have heard of something called “concurrent mode” or that it might break your app. In response to this feedback from the community, we’ve redesigned the upgrade strategy for gradual adoption. Instead of an all-or-nothing “mode”, concurrent rendering will only be enabled for updates triggered by one of the new features. In practice, this means **you will be able to adopt React 18 without rewrites and try the new features at your own pace.** + +## A gradual adoption strategy {/*a-gradual-adoption-strategy*/} + +Since concurrency in React 18 is opt-in, there are no significant out-of-the-box breaking changes to component behavior. **You can upgrade to React 18 with minimal or no changes to your application code, with a level of effort comparable to a typical major React release**. Based on our experience converting several apps to React 18, we expect that many users will be able to upgrade within a single afternoon. + +We successfully shipped concurrent features to tens of thousands of components at Facebook, and in our experience, we've found that most React components “just work” without additional changes. We're committed to making sure this is a smooth upgrade for the entire community, so today we're announcing the React 18 Working Group. + +## Working with the community {/*working-with-the-community*/} + +We’re trying something new for this release: We've invited a panel of experts, developers, library authors, and educators from across the React community to participate in our [React 18 Working Group](https://github.com/reactwg/react-18) to provide feedback, ask questions, and collaborate on the release. We couldn't invite everyone we wanted to this initial, small group, but if this experiment works out, we hope there will be more in the future! + +**The goal of the React 18 Working Group is to prepare the ecosystem for a smooth, gradual adoption of React 18 by existing applications and libraries.** The Working Group is hosted on [GitHub Discussions](https://github.com/reactwg/react-18/discussions) and is available for the public to read. Members of the working group can leave feedback, ask questions, and share ideas. The core team will also use the discussions repo to share our research findings. As the stable release gets closer, any important information will also be posted on this blog. + +For more information on upgrading to React 18, or additional resources about the release, see the [React 18 announcement post](https://github.com/reactwg/react-18/discussions/4). + +## Accessing the React 18 Working Group {/*accessing-the-react-18-working-group*/} + +Everyone can read the discussions in the [React 18 Working Group repo](https://github.com/reactwg/react-18). + +Because we expect an initial surge of interest in the Working Group, only invited members will be allowed to create or comment on threads. However, the threads are fully visible to the public, so everyone has access to the same information. We believe this is a good compromise between creating a productive environment for working group members, while maintaining transparency with the wider community. + +As always, you can submit bug reports, questions, and general feedback to our [issue tracker](https://github.com/facebook/react/issues). + +## How to try React 18 Alpha today {/*how-to-try-react-18-alpha-today*/} + +New alphas are [regularly published to npm using the `@alpha` tag](https://github.com/reactwg/react-18/discussions/9). These releases are built using the most recent commit to our main repo. When a feature or bugfix is merged, it will appear in an alpha the following weekday. + +There may be significant behavioral or API changes between alpha releases. Please remember that **alpha releases are not recommended for user-facing, production applications**. + +## Projected React 18 release timeline {/*projected-react-18-release-timeline*/} + +We don't have a specific release date scheduled, but we expect it will take several months of feedback and iteration before React 18 is ready for most production applications. + +* Library Alpha: Available today +* Public Beta: At least several months +* Release Candidate (RC): At least several weeks after Beta +* General Availability: At least several weeks after RC + +More details about our projected release timeline are [available in the Working Group](https://github.com/reactwg/react-18/discussions/9). We'll post updates on this blog when we're closer to a public release. diff --git a/beta/src/content/blog/2021/12/17/react-conf-2021-recap.md b/beta/src/content/blog/2021/12/17/react-conf-2021-recap.md new file mode 100644 index 000000000..87e6e3c6d --- /dev/null +++ b/beta/src/content/blog/2021/12/17/react-conf-2021-recap.md @@ -0,0 +1,157 @@ +--- +title: "React Conf 2021 Recap" +--- + +December 17, 2021 by [Jesslyn Tannady](https://twitter.com/jtannady) and [Rick Hanlon](https://twitter.com/rickhanlonii) + +--- + +<Intro> + +Last week we hosted our 6th React Conf. In previous years, we've used the React Conf stage to deliver industry changing announcements such as [_React Native_](https://engineering.fb.com/2015/03/26/android/react-native-bringing-modern-web-techniques-to-mobile/) and [_React Hooks_](https://reactjs.org/docs/hooks-intro.html). This year, we shared our multi-platform vision for React, starting with the release of React 18 and gradual adoption of concurrent features. + +</Intro> + +--- + +This was the first time React Conf was hosted online, and it was streamed for free, translated to 8 different languages. Participants from all over the world joined our conference Discord and the replay event for accessibility in all timezones. Over 50,000 people registered, with over 60,000 views of 19 talks, and 5,000 participants in Discord across both events. + +All the talks are [available to stream online](https://www.youtube.com/watch?v=FZ0cG47msEk&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa). + +Here’s a summary of what was shared on stage: + +## React 18 and concurrent features {/*react-18-and-concurrent-features*/} + +In the keynote, we shared our vision for the future of React starting with React 18. + +React 18 adds the long-awaited concurrent renderer and updates to Suspense without any major breaking changes. Apps can upgrade to React 18 and begin gradually adopting concurrent features with the amount of effort on par with any other major release. + +**This means there is no concurrent mode, only concurrent features.** + +In the keynote, we also shared our vision for Suspense, Server Components, new React working groups, and our long-term many-platform vision for React Native. + +Watch the full keynote from [Andrew Clark](https://twitter.com/acdlite), [Juan Tejada](https://twitter.com/_jstejada), [Lauren Tan](https://twitter.com/potetotes), and [Rick Hanlon](https://twitter.com/rickhanlonii) here: + +<YouTubeIframe src="https://www.youtube.com/embed/FZ0cG47msEk" /> + +## React 18 for Application Developers {/*react-18-for-application-developers*/} + +In the keynote, we also announced that the React 18 RC is available to try now. Pending further feedback, this is the exact version of React that we will publish to stable early next year. + +To try the React 18 RC, upgrade your dependencies: + +```bash +npm install react@rc react-dom@rc +``` + +and switch to the new `createRoot` API: + +```js +// before +const container = document.getElementById('root'); +ReactDOM.render(<App />, container); + +// after +const container = document.getElementById('root'); +const root = ReactDOM.createRoot(container); +root.render(<App/>); +``` + +For a demo of upgrading to React 18, see [Shruti Kapoor](https://twitter.com/shrutikapoor08)’s talk here: + +<YouTubeIframe src="https://www.youtube.com/embed/ytudH8je5ko" /> + +## Streaming Server Rendering with Suspense {/*streaming-server-rendering-with-suspense*/} + +React 18 also includes improvements to server-side rendering performance using Suspense. + +Streaming server rendering lets you generate HTML from React components on the server, and stream that HTML to your users. In React 18, you can use `Suspense` to break down your app into smaller independent units which can be streamed independently of each other without blocking the rest of the app. This means users will see your content sooner and be able to start interacting with it much faster. + +For a deep dive, see [Shaundai Person](https://twitter.com/shaundai)’s talk here: + +<YouTubeIframe src="https://www.youtube.com/embed/pj5N-Khihgc" /> + +## The first React working group {/*the-first-react-working-group*/} + +For React 18, we created our first Working Group to collaborate with a panel of experts, developers, library maintainers, and educators. Together we worked to create our gradual adoption strategy and refine new APIs such as `useId`, `useSyncExternalStore`, and `useInsertionEffect`. + +For an overview of this work, see [Aakansha' Doshi](https://twitter.com/aakansha1216)'s talk: + +<YouTubeIframe src="https://www.youtube.com/embed/qn7gRClrC9U" /> + +## React Developer Tooling {/*react-developer-tooling*/} + +To support the new features in this release, we also announced the newly formed React DevTools team and a new Timeline Profiler to help developers debug their React apps. + +For more information and a demo of new DevTools features, see [Brian Vaughn](https://twitter.com/brian_d_vaughn)’s talk: + +<YouTubeIframe src="https://www.youtube.com/embed/oxDfrke8rZg" /> + +## React without memo {/*react-without-memo*/} + +Looking further into the future, [Xuan Huang (黄玄)](https://twitter.com/Huxpro) shared an update from our React Labs research into an auto-memoizing compiler. Check out this talk for more information and a demo of the compiler prototype: + +<YouTubeIframe src="https://www.youtube.com/embed/lGEMwh32soc" /> + +## React docs keynote {/*react-docs-keynote*/} + +[Rachel Nabors](https://twitter.com/rachelnabors) kicked off a section of talks about learning and designing with React with a keynote about our investment in React's [new docs](https://beta.reactjs.org/): + +<YouTubeIframe src="https://www.youtube.com/embed/mneDaMYOKP8" /> + +## And more... {/*and-more*/} + +**We also heard talks on learning and designing with React:** + +* Debbie O'Brien: [Things I learnt from the new React docs](https://youtu.be/-7odLW_hG7s). +* Sarah Rainsberger: [Learning in the Browser](https://youtu.be/5X-WEQflCL0). +* Linton Ye: [The ROI of Designing with React](https://youtu.be/7cPWmID5XAk). +* Delba de Oliveira: [Interactive playgrounds with React](https://youtu.be/zL8cz2W0z34). + +**Talks from the Relay, React Native, and PyTorch teams:** + +* Robert Balicki: [Re-introducing Relay](https://youtu.be/lhVGdErZuN4). +* Eric Rozell and Steven Moyes: [React Native Desktop](https://youtu.be/9L4FFrvwJwY). +* Roman Rädle: [On-device Machine Learning for React Native](https://youtu.be/NLj73vrc2I8) + +**And talks from the community on accessibility, tooling, and Server Components:** + +* Daishi Kato: [React 18 for External Store Libraries](https://youtu.be/oPfSC5bQPR8). +* Diego Haz: [Building Accessible Components in React 18](https://youtu.be/dcm8fjBfro8). +* Tafu Nakazaki: [Accessible Japanese Form Components with React](https://youtu.be/S4a0QlsH0pU). +* Lyle Troxell: [UI tools for artists](https://youtu.be/b3l4WxipFsE). +* Helen Lin: [Hydrogen + React 18](https://youtu.be/HS6vIYkSNks). + +## Thank you {/*thank-you*/} + +This was our first year planning a conference ourselves, and we have a lot of people to thank. + +First, thanks to all of our speakers [Aakansha Doshi](https://twitter.com/aakansha1216), [Andrew Clark](https://twitter.com/acdlite), [Brian Vaughn](https://twitter.com/brian_d_vaughn), [Daishi Kato](https://twitter.com/dai_shi), [Debbie O'Brien](https://twitter.com/debs_obrien), [Delba de Oliveira](https://twitter.com/delba_oliveira), [Diego Haz](https://twitter.com/diegohaz), [Eric Rozell](https://twitter.com/EricRozell), [Helen Lin](https://twitter.com/wizardlyhel), [Juan Tejada](https://twitter.com/_jstejada), [Lauren Tan](https://twitter.com/potetotes), [Linton Ye](https://twitter.com/lintonye), [Lyle Troxell](https://twitter.com/lyle), [Rachel Nabors](https://twitter.com/rachelnabors), [Rick Hanlon](https://twitter.com/rickhanlonii), [Robert Balicki](https://twitter.com/StatisticsFTW), [Roman Rädle](https://twitter.com/raedle), [Sarah Rainsberger](https://twitter.com/sarah11918), [Shaundai Person](https://twitter.com/shaundai), [Shruti Kapoor](https://twitter.com/shrutikapoor08), [Steven Moyes](https://twitter.com/moyessa), [Tafu Nakazaki](https://twitter.com/hawaiiman0), and [Xuan Huang (黄玄)](https://twitter.com/Huxpro). + +Thanks to everyone who helped provide feedback on talks including [Andrew Clark](https://twitter.com/acdlite), [Dan Abramov](https://twitter.com/dan_abramov), [Dave McCabe](https://twitter.com/mcc_abe), [Eli White](https://twitter.com/Eli_White), [Joe Savona](https://twitter.com/en_JS), [Lauren Tan](https://twitter.com/potetotes), [Rachel Nabors](https://twitter.com/rachelnabors), and [Tim Yung](https://twitter.com/yungsters). + +Thanks to [Lauren Tan](https://twitter.com/potetotes) for setting up the conference Discord and serving as our Discord admin. + +Thanks to [Seth Webster](https://twitter.com/sethwebster) for feedback on overall direction and making sure we were focused on diversity and inclusion. + +Thanks to [Rachel Nabors](https://twitter.com/rachelnabors) for spearheading our moderation effort, and [Aisha Blake](https://twitter.com/AishaBlake) for creating our moderation guide, leading our moderation team, training the translators and moderators, and helping to moderate both events. + +Thanks to our moderators [Jesslyn Tannady](https://twitter.com/jtannady), [Suzie Grange](https://twitter.com/missuze), [Becca Bailey](https://twitter.com/beccaliz), [Luna Wei](https://twitter.com/lunaleaps), [Joe Previte](https://twitter.com/jsjoeio), [Nicola Corti](https://twitter.com/Cortinico), [Gijs Weterings](https://twitter.com/gweterings), [Claudio Procida](https://twitter.com/claudiopro), Julia Neumann, Mengdi Chen, Jean Zhang, Ricky Li, and [Xuan Huang (黄玄)](https://twitter.com/Huxpro). + +Thanks to [Manjula Dube](https://twitter.com/manjula_dube), [Sahil Mhapsekar](https://twitter.com/apheri0), and Vihang Patel from [React India](https://www.reactindia.io/), and [Jasmine Xie](https://twitter.com/jasmine_xby), [QiChang Li](https://twitter.com/QCL15), and [YanLun Li](https://twitter.com/anneincoding) from [React China](https://twitter.com/ReactChina) for helping moderate our replay event and keep it engaging for the community. + +Thanks to Vercel for publishing their [Virtual Event Starter Kit](https://vercel.com/virtual-event-starter-kit), which the conference website was built on, and to [Lee Robinson](https://twitter.com/leeerob) and [Delba de Oliveira](https://twitter.com/delba_oliveira) for sharing their experience running Next.js Conf. + +Thanks to [Leah Silber](https://twitter.com/wifelette) for sharing her experience running conferences, learnings from running [RustConf](https://rustconf.com/), and for her book [Event Driven](https://leanpub.com/eventdriven/) and the advice it contains for running conferences. + +Thanks to [Kevin Lewis](https://twitter.com/_phzn) and [Rachel Nabors](https://twitter.com/rachelnabors) for sharing their experience running Women of React Conf. + +Thanks to [Aakansha Doshi](https://twitter.com/aakansha1216), [Laurie Barth](https://twitter.com/laurieontech), [Michael Chan](https://twitter.com/chantastic), and [Shaundai Person](https://twitter.com/shaundai) for their advice and ideas throughout planning. + +Thanks to [Dan Lebowitz](https://twitter.com/lebo) for help designing and building the conference website and tickets. + +Thanks to Laura Podolak Waddell, Desmond Osei-Acheampong, Mark Rossi, Josh Toberman and others on the Facebook Video Productions team for recording the videos for the Keynote and Meta employee talks. + +Thanks to our partner HitPlay for helping to organize the conference, editing all the videos in the stream, translating all the talks, and moderating the Discord in multiple languages. + +Finally, thanks to all of our participants for making this a great React Conf! diff --git a/beta/src/content/blog/2022/03/08/react-18-upgrade-guide.md b/beta/src/content/blog/2022/03/08/react-18-upgrade-guide.md new file mode 100644 index 000000000..29ba0b71d --- /dev/null +++ b/beta/src/content/blog/2022/03/08/react-18-upgrade-guide.md @@ -0,0 +1,328 @@ +--- +title: "How to Upgrade to React 18" +--- + +March 08, 2022 by [Rick Hanlon](https://twitter.com/rickhanlonii) + +--- + +<Intro> + +As we shared in the [release post](/blog/2022/03/29/react-v18), React 18 introduces features powered by our new concurrent renderer, with a gradual adoption strategy for existing applications. In this post, we will guide you through the steps for upgrading to React 18. + +Please [report any issues](https://github.com/facebook/react/issues/new/choose) you encounter while upgrading to React 18. + +</Intro> + +<Note> + +For React Native users, React 18 will ship in a future version of React Native. This is because React 18 relies on the New React Native Architecture to benefit from the new capabilities presented in this blogpost. For more information, see the [React Conf keynote here](https://www.youtube.com/watch?v=FZ0cG47msEk&t=1530s). + +</Note> + +--- + +## Installing {/*installing*/} + +To install the latest version of React: + +```bash +npm install react react-dom +``` + +Or if you’re using yarn: + +```bash +yarn add react react-dom +``` + +## Updates to Client Rendering APIs {/*updates-to-client-rendering-apis*/} + +When you first install React 18, you will see a warning in the console: + +<ConsoleBlock level="error"> + +ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot + +</ConsoleBlock> + +React 18 introduces a new root API which provides better ergonomics for managing roots. The new root API also enables the new concurrent renderer, which allows you to opt-into concurrent features. + +```js +// Before +import { render } from 'react-dom'; +const container = document.getElementById('app'); +render(<App tab="home" />, container); + +// After +import { createRoot } from 'react-dom/client'; +const container = document.getElementById('app'); +const root = createRoot(container); // createRoot(container!) if you use TypeScript +root.render(<App tab="home" />); +``` + +We’ve also changed `unmountComponentAtNode` to `root.unmount`: + +```js +// Before +unmountComponentAtNode(container); + +// After +root.unmount(); +``` + +We've also removed the callback from render, since it usually does not have the expected result when using Suspense: + +```js +// Before +const container = document.getElementById('app'); +render(<App tab="home" />, container, () => { + console.log('rendered'); +}); + +// After +function AppWithCallbackAfterRender() { + useEffect(() => { + console.log('rendered'); + }); + + return <App tab="home" /> +} + +const container = document.getElementById('app'); +const root = createRoot(container); +root.render(<AppWithCallbackAfterRender />); +``` + +<Note> + +There is no one-to-one replacement for the old render callback API — it depends on your use case. See the working group post for [Replacing render with createRoot](https://github.com/reactwg/react-18/discussions/5) for more information. + +</Note> + +Finally, if your app uses server-side rendering with hydration, upgrade `hydrate` to `hydrateRoot`: + +```js +// Before +import { hydrate } from 'react-dom'; +const container = document.getElementById('app'); +hydrate(<App tab="home" />, container); + +// After +import { hydrateRoot } from 'react-dom/client'; +const container = document.getElementById('app'); +const root = hydrateRoot(container, <App tab="home" />); +// Unlike with createRoot, you don't need a separate root.render() call here. +``` + +For more information, see the [working group discussion here](https://github.com/reactwg/react-18/discussions/5). + +<Note> + +**If your app doesn't work after upgrading, check whether it's wrapped in `<StrictMode>`.** [Strict Mode has gotten stricter in React 18](#updates-to-strict-mode), and not all your components may be resilient to the new checks it adds in development mode. If removing Strict Mode fixes your app, you can remove it during the upgrade, and then add it back (either at the top or for a part of the tree) after you fix the issues that it's pointing out. + +</Note> + +## Updates to Server Rendering APIs {/*updates-to-server-rendering-apis*/} + +In this release, we’re revamping our `react-dom/server` APIs to fully support Suspense on the server and Streaming SSR. As part of these changes, we're deprecating the old Node streaming API, which does not support incremental Suspense streaming on the server. + +Using this API will now warn: + +* `renderToNodeStream`: **Deprecated ⛔️️** + +Instead, for streaming in Node environments, use: +* `renderToPipeableStream`: **New ✨** + +We're also introducing a new API to support streaming SSR with Suspense for modern edge runtime environments, such as Deno and Cloudflare workers: +* `renderToReadableStream`: **New ✨** + +The following APIs will continue working, but with limited support for Suspense: +* `renderToString`: **Limited** ⚠️ +* `renderToStaticMarkup`: **Limited** ⚠️ + +Finally, this API will continue to work for rendering e-mails: +* `renderToStaticNodeStream` + +For more information on the changes to server rendering APIs, see the working group post on [Upgrading to React 18 on the server](https://github.com/reactwg/react-18/discussions/22), a [deep dive on the new Suspense SSR Architecture](https://github.com/reactwg/react-18/discussions/37), and [Shaundai Person’s](https://twitter.com/shaundai) talk on [Streaming Server Rendering with Suspense](https://www.youtube.com/watch?v=pj5N-Khihgc) at React Conf 2021. + +## Updates to TypeScript definitions {/*updates-to-typescript-definitions*/} + +If your project uses TypeScript, you will need to update your `@types/react` and `@types/react-dom` dependencies to the latest versions. The new types are safer and catch issues that used to be ignored by the type checker. The most notable change is that the `children` prop now needs to be listed explicitly when defining props, for example: + +```typescript{3} +interface MyButtonProps { + color: string; + children?: React.ReactNode; +} +``` + +See the [React 18 typings pull request](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56210) for a full list of type-only changes. It links to example fixes in library types so you can see how to adjust your code. You can use the [automated migration script](https://github.com/eps1lon/types-react-codemod) to help port your application code to the new and safer typings faster. + +If you find a bug in the typings, please [file an issue](https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/new?category=issues-with-a-types-package) in the DefinitelyTyped repo. + +## Automatic Batching {/*automatic-batching*/} + +React 18 adds out-of-the-box performance improvements by doing more batching by default. Batching is when React groups multiple state updates into a single re-render for better performance. Before React 18, we only batched updates inside React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default: + +```js +// Before React 18 only React events were batched + +function handleClick() { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +} + +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will render twice, once for each state update (no batching) +}, 1000); +``` + + +Starting in React 18 with `createRoot`, all updates will be automatically batched, no matter where they originate from. This means that updates inside of timeouts, promises, native event handlers or any other event will batch the same way as updates inside of React events: + +```js +// After React 18 updates inside of timeouts, promises, +// native event handlers or any other event are batched. + +function handleClick() { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +} + +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +}, 1000); +``` + +This is a breaking change, but we expect this to result in less work rendering, and therefore better performance in your applications. To opt-out of automatic batching, you can use `flushSync`: + +```js +import { flushSync } from 'react-dom'; + +function handleClick() { + flushSync(() => { + setCounter(c => c + 1); + }); + // React has updated the DOM by now + flushSync(() => { + setFlag(f => !f); + }); + // React has updated the DOM by now +} +``` + +For more information, see the [Automatic batching deep dive](https://github.com/reactwg/react-18/discussions/21). + +## New APIs for Libraries {/*new-apis-for-libraries*/} + +In the React 18 Working Group we worked with library maintainers to create new APIs needed to support concurrent rendering for use cases specific to their use case in areas like styles, and external stores. To support React 18, some libraries may need to switch to one of the following APIs: + +* `useSyncExternalStore` is a new hook that allows external stores to support concurrent reads by forcing updates to the store to be synchronous. This new API is recommended for any library that integrates with state external to React. For more information, see the [useSyncExternalStore overview post](https://github.com/reactwg/react-18/discussions/70) and [useSyncExternalStore API details](https://github.com/reactwg/react-18/discussions/86). +* `useInsertionEffect` is a new hook that allows CSS-in-JS libraries to address performance issues of injecting styles in render. Unless you've already built a CSS-in-JS library we don't expect you to ever use this. This hook will run after the DOM is mutated, but before layout effects read the new layout. This solves an issue that already exists in React 17 and below, but is even more important in React 18 because React yields to the browser during concurrent rendering, giving it a chance to recalculate layout. For more information, see the [Library Upgrade Guide for `<style>`](https://github.com/reactwg/react-18/discussions/110). + +React 18 also introduces new APIs for concurrent rendering such as `startTransition`, `useDeferredValue` and `useId`, which we share more about in the [release post](/blog/2022/03/29/react-v18). + +## Updates to Strict Mode {/*updates-to-strict-mode*/} + +In the future, we'd like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React would unmount and remount trees using the same component state as before. + +This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects assume they are only mounted or destroyed once. + +To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount. + +Before this change, React would mount the component and create the effects: + +``` +* React mounts the component. + * Layout effects are created. + * Effect effects are created. +``` + +With Strict Mode in React 18, React will simulate unmounting and remounting the component in development mode: + +``` +* React mounts the component. + * Layout effects are created. + * Effect effects are created. +* React simulates unmounting the component. + * Layout effects are destroyed. + * Effects are destroyed. +* React simulates mounting the component with the previous state. + * Layout effect setup code runs + * Effect setup code runs +``` + +For more information, see the Working Group posts for [Adding Reusable State to StrictMode](https://github.com/reactwg/react-18/discussions/19) and [How to support Reusable State in Effects](https://github.com/reactwg/react-18/discussions/18). + +## Configuring Your Testing Environment {/*configuring-your-testing-environment*/} + +When you first update your tests to use `createRoot`, you may see this warning in your test console: + +<ConsoleBlock level="error"> + +The current testing environment is not configured to support act(...) + +</ConsoleBlock> + +To fix this, set `globalThis.IS_REACT_ACT_ENVIRONMENT` to `true` before running your test: + +```js +// In your test setup file +globalThis.IS_REACT_ACT_ENVIRONMENT = true; +``` + +The purpose of the flag is to tell React that it's running in a unit test-like environment. React will log helpful warnings if you forget to wrap an update with `act`. + +You can also set the flag to `false` to tell React that `act` isn't needed. This can be useful for end-to-end tests that simulate a full browser environment. + +Eventually, we expect testing libraries will configure this for you automatically. For example, the [next version of React Testing Library has built-in support for React 18](https://github.com/testing-library/react-testing-library/issues/509#issuecomment-917989936) without any additional configuration. + +[More background on the `act` testing API and related changes](https://github.com/reactwg/react-18/discussions/102) is available in the working group. + +## Dropping Support for Internet Explorer {/*dropping-support-for-internet-explorer*/} + +In this release, React is dropping support for Internet Explorer, which is [going out of support on June 15, 2022](https://blogs.windows.com/windowsexperience/2021/05/19/the-future-of-internet-explorer-on-windows-10-is-in-microsoft-edge). We’re making this change now because new features introduced in React 18 are built using modern browser features such as microtasks which cannot be adequately polyfilled in IE. + +If you need to support Internet Explorer we recommend you stay with React 17. + +## Deprecations {/*deprecations*/} + +* `react-dom`: `ReactDOM.render` has been deprecated. Using it will warn and run your app in React 17 mode. +* `react-dom`: `ReactDOM.hydrate` has been deprecated. Using it will warn and run your app in React 17 mode. +* `react-dom`: `ReactDOM.unmountComponentAtNode` has been deprecated. +* `react-dom`: `ReactDOM.renderSubtreeIntoContainer` has been deprecated. +* `react-dom/server`: `ReactDOMServer.renderToNodeStream` has been deprecated. + +## Other Breaking Changes {/*other-breaking-changes*/} + +* **Consistent useEffect timing**: React now always synchronously flushes effect functions if the update was triggered during a discrete user input event such as a click or a keydown event. Previously, the behavior wasn't always predictable or consistent. +* **Stricter hydration errors**: Hydration mismatches due to missing or extra text content are now treated like errors instead of warnings. React will no longer attempt to "patch up" individual nodes by inserting or deleting a node on the client in an attempt to match the server markup, and will revert to client rendering up to the closest `<Suspense>` boundary in the tree. This ensures the hydrated tree is consistent and avoids potential privacy and security holes that can be caused by hydration mismatches. +* **Suspense trees are always consistent:** If a component suspends before it's fully added to the tree, React will not add it to the tree in an incomplete state or fire its effects. Instead, React will throw away the new tree completely, wait for the asynchronous operation to finish, and then retry rendering again from scratch. React will render the retry attempt concurrently, and without blocking the browser. +* **Layout Effects with Suspense**: When a tree re-suspends and reverts to a fallback, React will now clean up layout effects, and then re-create them when the content inside the boundary is shown again. This fixes an issue which prevented component libraries from correctly measuring layout when used with Suspense. +* **New JS Environment Requirements**: React now depends on modern browsers features including `Promise`, `Symbol`, and `Object.assign`. If you support older browsers and devices such as Internet Explorer which do not provide modern browser features natively or have non-compliant implementations, consider including a global polyfill in your bundled application. + +## Other Notable Changes {/*other-notable-changes*/} + +### React {/*react*/} + +* **Components can now render `undefined`:** React no longer warns if you return `undefined` from a component. This makes the allowed component return values consistent with values that are allowed in the middle of a component tree. We suggest to use a linter to prevent mistakes like forgetting a `return` statement before JSX. +* **In tests, `act` warnings are now opt-in:** If you're running end-to-end tests, the `act` warnings are unnecessary. We've introduced an [opt-in](https://github.com/reactwg/react-18/discussions/102) mechanism so you can enable them only for unit tests where they are useful and beneficial. +* **No warning about `setState` on unmounted components:** Previously, React warned about memory leaks when you call `setState` on an unmounted component. This warning was added for subscriptions, but people primarily run into it in scenarios where setting state is fine, and workarounds make the code worse. We've [removed](https://github.com/facebook/react/pull/22114) this warning. +* **No suppression of console logs:** When you use Strict Mode, React renders each component twice to help you find unexpected side effects. In React 17, we've suppressed console logs for one of the two renders to make the logs easier to read. In response to [community feedback](https://github.com/facebook/react/issues/21783) about this being confusing, we've removed the suppression. Instead, if you have React DevTools installed, the second log's renders will be displayed in grey, and there will be an option (off by default) to suppress them completely. +* **Improved memory usage:** React now cleans up more internal fields on unmount, making the impact from unfixed memory leaks that may exist in your application code less severe. + +### React DOM Server {/*react-dom-server*/} + +* **`renderToString`:** Will no longer error when suspending on the server. Instead, it will emit the fallback HTML for the closest `<Suspense>` boundary and then retry rendering the same content on the client. It is still recommended that you switch to a streaming API like `renderToPipeableStream` or `renderToReadableStream` instead. +* **`renderToStaticMarkup`:** Will no longer error when suspending on the server. Instead, it will emit the fallback HTML for the closest `<Suspense>` boundary. + +## Changelog {/*changelog*/} + +You can view the [full changelog here](https://github.com/facebook/react/blob/main/CHANGELOG.md). diff --git a/beta/src/content/blog/2022/03/29/react-v18.md b/beta/src/content/blog/2022/03/29/react-v18.md new file mode 100644 index 000000000..567b3fed6 --- /dev/null +++ b/beta/src/content/blog/2022/03/29/react-v18.md @@ -0,0 +1,342 @@ +--- +title: "React v18.0" +--- + +March 29, 2022 by [The React Team](/community/team) + +--- + +<Intro> + +React 18 is now available on npm! In our last post, we shared step-by-step instructions for [upgrading your app to React 18](/blog/2022/03/08/react-18-upgrade-guide). In this post, we'll give an overview of what's new in React 18, and what it means for the future. + +</Intro> + +--- + +Our latest major version includes out-of-the-box improvements like automatic batching, new APIs like startTransition, and streaming server-side rendering with support for Suspense. + +Many of the features in React 18 are built on top of our new concurrent renderer, a behind-the-scenes change that unlocks powerful new capabilities. Concurrent React is opt-in — it's only enabled when you use a concurrent feature — but we think it will have a big impact on the way people build applications. + +We've spent years researching and developing support for concurrency in React, and we've taken extra care to provide a gradual adoption path for existing users. Last summer, [we formed the React 18 Working Group](/blog/2021/06/08/the-plan-for-react-18) to gather feedback from experts in the community and ensure a smooth upgrade experience for the entire React ecosystem. + +In case you missed it, we shared a lot of this vision at React Conf 2021: + +* In [the keynote](https://www.youtube.com/watch?v=FZ0cG47msEk&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa), we explain how React 18 fits into our mission to make it easy for developers to build great user experiences +* [Shruti Kapoor](https://twitter.com/shrutikapoor08) [demonstrated how to use the new features in React 18](https://www.youtube.com/watch?v=ytudH8je5ko&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=2) +* [Shaundai Person](https://twitter.com/shaundai) gave us an overview of [streaming server rendering with Suspense](https://www.youtube.com/watch?v=pj5N-Khihgc&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=3) + +Below is a full overview of what to expect in this release, starting with Concurrent Rendering. + +<Note> + +For React Native users, React 18 will ship in React Native with the New React Native Architecture. For more information, see the [React Conf keynote here](https://www.youtube.com/watch?v=FZ0cG47msEk&t=1530s). + +</Note> + +## What is Concurrent React? {/*what-is-concurrent-react*/} + +The most important addition in React 18 is something we hope you never have to think about: concurrency. We think this is largely true for application developers, though the story may be a bit more complicated for library maintainers. + +Concurrency is not a feature, per se. It's a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time. You can think of concurrency as an implementation detail — it's valuable because of the features that it unlocks. React uses sophisticated techniques in its internal implementation, like priority queues and multiple buffering. But you won't see those concepts anywhere in our public APIs. + +When we design APIs, we try to hide implementation details from developers. As a React developer, you focus on *what* you want the user experience to look like, and React handles *how* to deliver that experience. So we don’t expect React developers to know how concurrency works under the hood. + +However, Concurrent React is more important than a typical implementation detail — it's a foundational update to React's core rendering model. So while it's not super important to know how concurrency works, it may be worth knowing what it is at a high level. + +A key property of Concurrent React is that rendering is interruptible. When you first upgrade to React 18, before adding any concurrent features, updates are rendered the same as in previous versions of React — in a single, uninterrupted, synchronous transaction. With synchronous rendering, once an update starts rendering, nothing can interrupt it until the user can see the result on screen. + +In a concurrent render, this is not always the case. React may start rendering an update, pause in the middle, then continue later. It may even abandon an in-progress render altogether. React guarantees that the UI will appear consistent even if a render is interrupted. To do this, it waits to perform DOM mutations until the end, once the entire tree has been evaluated. With this capability, React can prepare new screens in the background without blocking the main thread. This means the UI can respond immediately to user input even if it’s in the middle of a large rendering task, creating a fluid user experience. + +Another example is reusable state. Concurrent React can remove sections of the UI from the screen, then add them back later while reusing the previous state. For example, when a user tabs away from a screen and back, React should be able to restore the previous screen in the same state it was in before. In an upcoming minor, we're planning to add a new component called `<Offscreen>` that implements this pattern. Similarly, you’ll be able to use Offscreen to prepare new UI in the background so that it’s ready before the user reveals it. + +Concurrent rendering is a powerful new tool in React and most of our new features are built to take advantage of it, including Suspense, transitions, and streaming server rendering. But React 18 is just the beginning of what we aim to build on this new foundation. + +## Gradually Adopting Concurrent Features {/*gradually-adopting-concurrent-features*/} + +Technically, concurrent rendering is a breaking change. Because concurrent rendering is interruptible, components behave slightly differently when it is enabled. + +In our testing, we've upgraded thousands of components to React 18. What we've found is that nearly all existing components "just work" with concurrent rendering, without any changes. However, some of them may require some additional migration effort. Although the changes are usually small, you'll still have the ability to make them at your own pace. The new rendering behavior in React 18 is **only enabled in the parts of your app that use new features.** + +The overall upgrade strategy is to get your application running on React 18 without breaking existing code. Then you can gradually start adding concurrent features at your own pace. You can use [`<StrictMode>`](/reference/react/StrictMode) to help surface concurrency-related bugs during development. Strict Mode doesn't affect production behavior, but during development it will log extra warnings and double-invoke functions that are expected to be idempotent. It won't catch everything, but it's effective at preventing the most common types of mistakes. + +After you upgrade to React 18, you’ll be able to start using concurrent features immediately. For example, you can use startTransition to navigate between screens without blocking user input. Or useDeferredValue to throttle expensive re-renders. + +However, long term, we expect the main way you’ll add concurrency to your app is by using a concurrent-enabled library or framework. In most cases, you won’t interact with concurrent APIs directly. For example, instead of developers calling startTransition whenever they navigate to a new screen, router libraries will automatically wrap navigations in startTransition. + +It may take some time for libraries to upgrade to be concurrent compatible. We’ve provided new APIs to make it easier for libraries to take advantage of concurrent features. In the meantime, please be patient with maintainers as we work to gradually migrate the React ecosystem. + +For more info, see our previous post: [How to upgrade to React 18](/blog/2022/03/08/react-18-upgrade-guide). + +## Suspense in Data Frameworks {/*suspense-in-data-frameworks*/} + +In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy. + +In the future, we may expose additional primitives that could make it easier to access your data with Suspense, perhaps without the use of an opinionated framework. However, Suspense works best when it’s deeply integrated into your application’s architecture: your router, your data layer, and your server rendering environment. So even long term, we expect that libraries and frameworks will play a crucial role in the React ecosystem. + +As in previous versions of React, you can also use Suspense for code splitting on the client with React.lazy. But our vision for Suspense has always been about much more than loading code — the goal is to extend support for Suspense so that eventually, the same declarative Suspense fallback can handle any asynchronous operation (loading code, data, images, etc). + +## Server Components is Still in Development {/*server-components-is-still-in-development*/} + +[**Server Components**](/blog/2020/12/21/data-fetching-with-react-server-components) is an upcoming feature that allows developers to build apps that span the server and client, combining the rich interactivity of client-side apps with the improved performance of traditional server rendering. Server Components is not inherently coupled to Concurrent React, but it’s designed to work best with concurrent features like Suspense and streaming server rendering. + +Server Components is still experimental, but we expect to release an initial version in a minor 18.x release. In the meantime, we’re working with frameworks like Next.js, Hydrogen, and Remix to advance the proposal and get it ready for broad adoption. + +## What's New in React 18 {/*whats-new-in-react-18*/} + +### New Feature: Automatic Batching {/*new-feature-automatic-batching*/} + +Batching is when React groups multiple state updates into a single re-render for better performance. Without automatic batching, we only batched updates inside React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default. With automatic batching, these updates will be batched automatically: + + +```js +// Before: only React events were batched. +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will render twice, once for each state update (no batching) +}, 1000); + +// After: updates inside of timeouts, promises, +// native event handlers or any other event are batched. +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +}, 1000); +``` + +For more info, see this post for [Automatic batching for fewer renders in React 18](https://github.com/reactwg/react-18/discussions/21). + +### New Feature: Transitions {/*new-feature-transitions*/} + +A transition is a new concept in React to distinguish between urgent and non-urgent updates. + +* **Urgent updates** reflect direct interaction, like typing, clicking, pressing, and so on. +* **Transition updates** transition the UI from one view to another. + +Urgent updates like typing, clicking, or pressing, need immediate response to match our intuitions about how physical objects behave. Otherwise they feel "wrong". However, transitions are different because the user doesn’t expect to see every intermediate value on screen. + +For example, when you select a filter in a dropdown, you expect the filter button itself to respond immediately when you click. However, the actual results may transition separately. A small delay would be imperceptible and often expected. And if you change the filter again before the results are done rendering, you only care to see the latest results. + +Typically, for the best user experience, a single user input should result in both an urgent update and a non-urgent one. You can use startTransition API inside an input event to inform React which updates are urgent and which are "transitions": + + +```js +import {startTransition} from 'react'; + +// Urgent: Show what was typed +setInputValue(input); + +// Mark any state updates inside as transitions +startTransition(() => { + // Transition: Show the results + setSearchQuery(input); +}); +``` + + +Updates wrapped in startTransition are handled as non-urgent and will be interrupted if more urgent updates like clicks or key presses come in. If a transition gets interrupted by the user (for example, by typing multiple characters in a row), React will throw out the stale rendering work that wasn’t finished and render only the latest update. + + +* `useTransition`: a hook to start transitions, including a value to track the pending state. +* `startTransition`: a method to start transitions when the hook cannot be used. + +Transitions will opt in to concurrent rendering, which allows the update to be interrupted. If the content re-suspends, transitions also tell React to continue showing the current content while rendering the transition content in the background (see the [Suspense RFC](https://github.com/reactjs/rfcs/blob/main/text/0213-suspense-in-react-18.md) for more info). + +[See docs for transitions here](/reference/react/useTransition). + +### New Suspense Features {/*new-suspense-features*/} + +Suspense lets you declaratively specify the loading state for a part of the component tree if it's not yet ready to be displayed: + +```js +<Suspense fallback={<Spinner />}> + <Comments /> +</Suspense> +``` + +Suspense makes the "UI loading state" a first-class declarative concept in the React programming model. This lets us build higher-level features on top of it. + +We introduced a limited version of Suspense several years ago. However, the only supported use case was code splitting with React.lazy, and it wasn't supported at all when rendering on the server. + +In React 18, we've added support for Suspense on the server and expanded its capabilities using concurrent rendering features. + +Suspense in React 18 works best when combined with the transition API. If you suspend during a transition, React will prevent already-visible content from being replaced by a fallback. Instead, React will delay the render until enough data has loaded to prevent a bad loading state. + +For more, see the RFC for [Suspense in React 18](https://github.com/reactjs/rfcs/blob/main/text/0213-suspense-in-react-18.md). + +### New Client and Server Rendering APIs {/*new-client-and-server-rendering-apis*/} + +In this release we took the opportunity to redesign the APIs we expose for rendering on the client and server. These changes allow users to continue using the old APIs in React 17 mode while they upgrade to the new APIs in React 18. + +#### React DOM Client {/*react-dom-client*/} + +These new APIs are now exported from `react-dom/client`: + +* `createRoot`: New method to create a root to `render` or `unmount`. Use it instead of `ReactDOM.render`. New features in React 18 don't work without it. +* `hydrateRoot`: New method to hydrate a server rendered application. Use it instead of `ReactDOM.hydrate` in conjunction with the new React DOM Server APIs. New features in React 18 don't work without it. + +Both `createRoot` and `hydrateRoot` accept a new option called `onRecoverableError` in case you want to be notified when React recovers from errors during rendering or hydration for logging. By default, React will use [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError), or `console.error` in the older browsers. + +[See docs for React DOM Client here](/reference/react-dom/client). + +#### React DOM Server {/*react-dom-server*/} + +These new APIs are now exported from `react-dom/server` and have full support for streaming Suspense on the server: + +* `renderToPipeableStream`: for streaming in Node environments. +* `renderToReadableStream`: for modern edge runtime environments, such as Deno and Cloudflare workers. + +The existing `renderToString` method keeps working but is discouraged. + +[See docs for React DOM Server here](/reference/react-dom/server). + +### New Strict Mode Behaviors {/*new-strict-mode-behaviors*/} + +In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React would unmount and remount trees using the same component state as before. + +This feature will give React apps better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects assume they are only mounted or destroyed once. + +To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount. + +Before this change, React would mount the component and create the effects: + +``` +* React mounts the component. + * Layout effects are created. + * Effects are created. +``` + + +With Strict Mode in React 18, React will simulate unmounting and remounting the component in development mode: + +``` +* React mounts the component. + * Layout effects are created. + * Effects are created. +* React simulates unmounting the component. + * Layout effects are destroyed. + * Effects are destroyed. +* React simulates mounting the component with the previous state. + * Layout effects are created. + * Effects are created. +``` + +[See docs for ensuring reusable state here](/reference/react/StrictMode#fixing-bugs-found-by-re-running-effects-in-development). + +### New Hooks {/*new-hooks*/} + +#### useId {/*useid*/} + +`useId` is a new hook for generating unique IDs on both the client and server, while avoiding hydration mismatches. It is primarily useful for component libraries integrating with accessibility APIs that require unique IDs. This solves an issue that already exists in React 17 and below, but it's even more important in React 18 because of how the new streaming server renderer delivers HTML out-of-order. [See docs here](/reference/react/useId). + +> Note +> +> `useId` is **not** for generating [keys in a list](/learn/rendering-lists#where-to-get-your-key). Keys should be generated from your data. + +#### useTransition {/*usetransition*/} + +`useTransition` and `startTransition` let you mark some state updates as not urgent. Other state updates are considered urgent by default. React will allow urgent state updates (for example, updating a text input) to interrupt non-urgent state updates (for example, rendering a list of search results). [See docs here](/reference/react/useTransition) + +#### useDeferredValue {/*usedeferredvalue*/} + +`useDeferredValue` lets you defer re-rendering a non-urgent part of the tree. It is similar to debouncing, but has a few advantages compared to it. There is no fixed time delay, so React will attempt the deferred render right after the first render is reflected on the screen. The deferred render is interruptible and doesn't block user input. [See docs here](/reference/react/useDeferredValue). + +#### useSyncExternalStore {/*usesyncexternalstore*/} + +`useSyncExternalStore` is a new hook that allows external stores to support concurrent reads by forcing updates to the store to be synchronous. It removes the need for useEffect when implementing subscriptions to external data sources, and is recommended for any library that integrates with state external to React. [See docs here](/reference/react/useSyncExternalStore). + +> Note +> +> `useSyncExternalStore` is intended to be used by libraries, not application code. + +#### useInsertionEffect {/*useinsertioneffect*/} + +`useInsertionEffect` is a new hook that allows CSS-in-JS libraries to address performance issues of injecting styles in render. Unless you’ve already built a CSS-in-JS library we don’t expect you to ever use this. This hook will run after the DOM is mutated, but before layout effects read the new layout. This solves an issue that already exists in React 17 and below, but is even more important in React 18 because React yields to the browser during concurrent rendering, giving it a chance to recalculate layout. [See docs here](/reference/react/useInsertionEffect). + +> Note +> +> `useInsertionEffect` is intended to be used by libraries, not application code. + +## How to Upgrade {/*how-to-upgrade*/} + +See [How to Upgrade to React 18](/blog/2022/03/08/react-18-upgrade-guide) for step-by-step instructions and a full list of breaking and notable changes. + +## Changelog {/*changelog*/} + +### React {/*react*/} + +* Add `useTransition` and `useDeferredValue` to separate urgent updates from transitions. ([#10426](https://github.com/facebook/react/pull/10426), [#10715](https://github.com/facebook/react/pull/10715), [#15593](https://github.com/facebook/react/pull/15593), [#15272](https://github.com/facebook/react/pull/15272), [#15578](https://github.com/facebook/react/pull/15578), [#15769](https://github.com/facebook/react/pull/15769), [#17058](https://github.com/facebook/react/pull/17058), [#18796](https://github.com/facebook/react/pull/18796), [#19121](https://github.com/facebook/react/pull/19121), [#19703](https://github.com/facebook/react/pull/19703), [#19719](https://github.com/facebook/react/pull/19719), [#19724](https://github.com/facebook/react/pull/19724), [#20672](https://github.com/facebook/react/pull/20672), [#20976](https://github.com/facebook/react/pull/20976) by [@acdlite](https://github.com/acdlite), [@lunaruan](https://github.com/lunaruan), [@rickhanlonii](https://github.com/rickhanlonii), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `useId` for generating unique IDs. ([#17322](https://github.com/facebook/react/pull/17322), [#18576](https://github.com/facebook/react/pull/18576), [#22644](https://github.com/facebook/react/pull/22644), [#22672](https://github.com/facebook/react/pull/22672), [#21260](https://github.com/facebook/react/pull/21260) by [@acdlite](https://github.com/acdlite), [@lunaruan](https://github.com/lunaruan), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `useSyncExternalStore` to help external store libraries integrate with React. ([#15022](https://github.com/facebook/react/pull/15022), [#18000](https://github.com/facebook/react/pull/18000), [#18771](https://github.com/facebook/react/pull/18771), [#22211](https://github.com/facebook/react/pull/22211), [#22292](https://github.com/facebook/react/pull/22292), [#22239](https://github.com/facebook/react/pull/22239), [#22347](https://github.com/facebook/react/pull/22347), [#23150](https://github.com/facebook/react/pull/23150) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), and [@drarmstr](https://github.com/drarmstr)) +* Add `startTransition` as a version of `useTransition` without pending feedback. ([#19696](https://github.com/facebook/react/pull/19696) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Add `useInsertionEffect` for CSS-in-JS libraries. ([#21913](https://github.com/facebook/react/pull/21913) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Make Suspense remount layout effects when content reappears. ([#19322](https://github.com/facebook/react/pull/19322), [#19374](https://github.com/facebook/react/pull/19374), [#19523](https://github.com/facebook/react/pull/19523), [#20625](https://github.com/facebook/react/pull/20625), [#21079](https://github.com/facebook/react/pull/21079) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), and [@lunaruan](https://github.com/lunaruan)) +* Make `<StrictMode>` re-run effects to check for restorable state. ([#19523](https://github.com/facebook/react/pull/19523) , [#21418](https://github.com/facebook/react/pull/21418) by [@bvaughn](https://github.com/bvaughn) and [@lunaruan](https://github.com/lunaruan)) +* Assume Symbols are always available. ([#23348](https://github.com/facebook/react/pull/23348) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Remove `object-assign` polyfill. ([#23351](https://github.com/facebook/react/pull/23351) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Remove unsupported `unstable_changedBits` API. ([#20953](https://github.com/facebook/react/pull/20953) by [@acdlite](https://github.com/acdlite)) +* Allow components to render undefined. ([#21869](https://github.com/facebook/react/pull/21869) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Flush `useEffect` resulting from discrete events like clicks synchronously. ([#21150](https://github.com/facebook/react/pull/21150) by [@acdlite](https://github.com/acdlite)) +* Suspense `fallback={undefined}` now behaves the same as `null` and isn't ignored. ([#21854](https://github.com/facebook/react/pull/21854) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Consider all `lazy()` resolving to the same component equivalent. ([#20357](https://github.com/facebook/react/pull/20357) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Don't patch console during first render. ([#22308](https://github.com/facebook/react/pull/22308) by [@lunaruan](https://github.com/lunaruan)) +* Improve memory usage. ([#21039](https://github.com/facebook/react/pull/21039) by [@bgirard](https://github.com/bgirard)) +* Improve messages if string coercion throws (Temporal.*, Symbol, etc.) ([#22064](https://github.com/facebook/react/pull/22064) by [@justingrant](https://github.com/justingrant)) +* Use `setImmediate` when available over `MessageChannel`. ([#20834](https://github.com/facebook/react/pull/20834) by [@gaearon](https://github.com/gaearon)) +* Fix context failing to propagate inside suspended trees. ([#23095](https://github.com/facebook/react/pull/23095) by [@gaearon](https://github.com/gaearon)) +* Fix `useReducer` observing incorrect props by removing the eager bailout mechanism. ([#22445](https://github.com/facebook/react/pull/22445) by [@josephsavona](https://github.com/josephsavona)) +* Fix `setState` being ignored in Safari when appending iframes. ([#23111](https://github.com/facebook/react/pull/23111) by [@gaearon](https://github.com/gaearon)) +* Fix a crash when rendering `ZonedDateTime` in the tree. ([#20617](https://github.com/facebook/react/pull/20617) by [@dimaqq](https://github.com/dimaqq)) +* Fix a crash when document is set to `null` in tests. ([#22695](https://github.com/facebook/react/pull/22695) by [@SimenB](https://github.com/SimenB)) +* Fix `onLoad` not triggering when concurrent features are on. ([#23316](https://github.com/facebook/react/pull/23316) by [@gnoff](https://github.com/gnoff)) +* Fix a warning when a selector returns `NaN`. ([#23333](https://github.com/facebook/react/pull/23333) by [@hachibeeDI](https://github.com/hachibeeDI)) +* Fix a crash when document is set to `null` in tests. ([#22695](https://github.com/facebook/react/pull/22695) by [@SimenB](https://github.com/SimenB)) +* Fix the generated license header. ([#23004](https://github.com/facebook/react/pull/23004) by [@vitaliemiron](https://github.com/vitaliemiron)) +* Add `package.json` as one of the entry points. ([#22954](https://github.com/facebook/react/pull/22954) by [@Jack](https://github.com/Jack-Works)) +* Allow suspending outside a Suspense boundary. ([#23267](https://github.com/facebook/react/pull/23267) by [@acdlite](https://github.com/acdlite)) +* Log a recoverable error whenever hydration fails. ([#23319](https://github.com/facebook/react/pull/23319) by [@acdlite](https://github.com/acdlite)) + +### React DOM {/*react-dom*/} + +* Add `createRoot` and `hydrateRoot`. ([#10239](https://github.com/facebook/react/pull/10239), [#11225](https://github.com/facebook/react/pull/11225), [#12117](https://github.com/facebook/react/pull/12117), [#13732](https://github.com/facebook/react/pull/13732), [#15502](https://github.com/facebook/react/pull/15502), [#15532](https://github.com/facebook/react/pull/15532), [#17035](https://github.com/facebook/react/pull/17035), [#17165](https://github.com/facebook/react/pull/17165), [#20669](https://github.com/facebook/react/pull/20669), [#20748](https://github.com/facebook/react/pull/20748), [#20888](https://github.com/facebook/react/pull/20888), [#21072](https://github.com/facebook/react/pull/21072), [#21417](https://github.com/facebook/react/pull/21417), [#21652](https://github.com/facebook/react/pull/21652), [#21687](https://github.com/facebook/react/pull/21687), [#23207](https://github.com/facebook/react/pull/23207), [#23385](https://github.com/facebook/react/pull/23385) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), [@gaearon](https://github.com/gaearon), [@lunaruan](https://github.com/lunaruan), [@rickhanlonii](https://github.com/rickhanlonii), [@trueadm](https://github.com/trueadm), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add selective hydration. ([#14717](https://github.com/facebook/react/pull/14717), [#14884](https://github.com/facebook/react/pull/14884), [#16725](https://github.com/facebook/react/pull/16725), [#16880](https://github.com/facebook/react/pull/16880), [#17004](https://github.com/facebook/react/pull/17004), [#22416](https://github.com/facebook/react/pull/22416), [#22629](https://github.com/facebook/react/pull/22629), [#22448](https://github.com/facebook/react/pull/22448), [#22856](https://github.com/facebook/react/pull/22856), [#23176](https://github.com/facebook/react/pull/23176) by [@acdlite](https://github.com/acdlite), [@gaearon](https://github.com/gaearon), [@salazarm](https://github.com/salazarm), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `aria-description` to the list of known ARIA attributes. ([#22142](https://github.com/facebook/react/pull/22142) by [@mahyareb](https://github.com/mahyareb)) +* Add `onResize` event to video elements. ([#21973](https://github.com/facebook/react/pull/21973) by [@rileyjshaw](https://github.com/rileyjshaw)) +* Add `imageSizes` and `imageSrcSet` to known props. ([#22550](https://github.com/facebook/react/pull/22550) by [@eps1lon](https://github.com/eps1lon)) +* Allow non-string `<option>` children if `value` is provided. ([#21431](https://github.com/facebook/react/pull/21431) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix `aspectRatio` style not being applied. ([#21100](https://github.com/facebook/react/pull/21100) by [@gaearon](https://github.com/gaearon)) +* Warn if `renderSubtreeIntoContainer` is called. ([#23355](https://github.com/facebook/react/pull/23355) by [@acdlite](https://github.com/acdlite)) + +### React DOM Server {/*react-dom-server-1*/} + +* Add the new streaming renderer. ([#14144](https://github.com/facebook/react/pull/14144), [#20970](https://github.com/facebook/react/pull/20970), [#21056](https://github.com/facebook/react/pull/21056), [#21255](https://github.com/facebook/react/pull/21255), [#21200](https://github.com/facebook/react/pull/21200), [#21257](https://github.com/facebook/react/pull/21257), [#21276](https://github.com/facebook/react/pull/21276), [#22443](https://github.com/facebook/react/pull/22443), [#22450](https://github.com/facebook/react/pull/22450), [#23247](https://github.com/facebook/react/pull/23247), [#24025](https://github.com/facebook/react/pull/24025), [#24030](https://github.com/facebook/react/pull/24030) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix context providers in SSR when handling multiple requests. ([#23171](https://github.com/facebook/react/pull/23171) by [@frandiox](https://github.com/frandiox)) +* Revert to client render on text mismatch. ([#23354](https://github.com/facebook/react/pull/23354) by [@acdlite](https://github.com/acdlite)) +* Deprecate `renderToNodeStream`. ([#23359](https://github.com/facebook/react/pull/23359) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix a spurious error log in the new server renderer. ([#24043](https://github.com/facebook/react/pull/24043) by [@eps1lon](https://github.com/eps1lon)) +* Fix a bug in the new server renderer. ([#22617](https://github.com/facebook/react/pull/22617) by [@shuding](https://github.com/shuding)) +* Ignore function and symbol values inside custom elements on the server. ([#21157](https://github.com/facebook/react/pull/21157) by [@sebmarkbage](https://github.com/sebmarkbage)) + +### React DOM Test Utils {/*react-dom-test-utils*/} + +* Throw when `act` is used in production. ([#21686](https://github.com/facebook/react/pull/21686) by [@acdlite](https://github.com/acdlite)) +* Support disabling spurious act warnings with `global.IS_REACT_ACT_ENVIRONMENT`. ([#22561](https://github.com/facebook/react/pull/22561) by [@acdlite](https://github.com/acdlite)) +* Expand act warning to cover all APIs that might schedule React work. ([#22607](https://github.com/facebook/react/pull/22607) by [@acdlite](https://github.com/acdlite)) +* Make `act` batch updates. ([#21797](https://github.com/facebook/react/pull/21797) by [@acdlite](https://github.com/acdlite)) +* Remove warning for dangling passive effects. ([#22609](https://github.com/facebook/react/pull/22609) by [@acdlite](https://github.com/acdlite)) + +### React Refresh {/*react-refresh*/} + +* Track late-mounted roots in Fast Refresh. ([#22740](https://github.com/facebook/react/pull/22740) by [@anc95](https://github.com/anc95)) +* Add `exports` field to `package.json`. ([#23087](https://github.com/facebook/react/pull/23087) by [@otakustay](https://github.com/otakustay)) + +### Server Components (Experimental) {/*server-components-experimental*/} + +* Add Server Context support. ([#23244](https://github.com/facebook/react/pull/23244) by [@salazarm](https://github.com/salazarm)) +* Add `lazy` support. ([#24068](https://github.com/facebook/react/pull/24068) by [@gnoff](https://github.com/gnoff)) +* Update webpack plugin for webpack 5 ([#22739](https://github.com/facebook/react/pull/22739) by [@michenly](https://github.com/michenly)) +* Fix a mistake in the Node loader. ([#22537](https://github.com/facebook/react/pull/22537) by [@btea](https://github.com/btea)) +* Use `globalThis` instead of `window` for edge environments. ([#22777](https://github.com/facebook/react/pull/22777) by [@huozhi](https://github.com/huozhi)) + diff --git a/beta/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md b/beta/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md new file mode 100644 index 000000000..5533314da --- /dev/null +++ b/beta/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md @@ -0,0 +1,79 @@ +--- +title: "React Labs: What We've Been Working On – June 2022" +--- + +June 15, 2022 by [Andrew Clark](https://twitter.com/acdlite), [Dan Abramov](https://twitter.com/dan_abramov), [Jan Kassens](https://twitter.com/kassens), [Joseph Savona](https://twitter.com/en_JS), [Josh Story](https://twitter.com/joshcstory), [Lauren Tan](https://twitter.com/potetotes), [Luna Ruan](https://twitter.com/lunaruan), [Mengdi Chen](https://twitter.com/mengdi_en), [Rick Hanlon](https://twitter.com/rickhanlonii), [Robert Zhang](https://twitter.com/jiaxuanzhang01), [Sathya Gunasekaran](https://twitter.com/_gsathya), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Xuan Huang](https://twitter.com/Huxpro) + +--- + +<Intro> + +[React 18](https://reactjs.org/blog/2022/03/29/react-v18) was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring. + +</Intro> + +--- + +We typically have a number of projects being worked on at any time, ranging from the more experimental to the clearly defined. Looking ahead, we’d like to start regularly sharing more about what we’ve been working on with the community across these projects. + +To set expectations, this is not a roadmap with clear timelines. Many of these projects are under active research and are difficult to put concrete ship dates on. They may possibly never even ship in their current iteration depending on what we learn. Instead, we want to share with you the problem spaces we’re actively thinking about, and what we’ve learned so far. + +## Server Components {/*server-components*/} + +We announced an [experimental demo of React Server Components](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components) (RSC) in December 2020. Since then we’ve been finishing up its dependencies in React 18, and working on changes inspired by experimental feedback. + +In particular, we’re abandoning the idea of having forked I/O libraries (eg react-fetch), and instead adopting an async/await model for better compatibility. This doesn’t technically block RSC’s release because you can also use routers for data fetching. Another change is that we’re also moving away from the file extension approach in favor of [annotating boundaries](https://github.com/reactjs/rfcs/pull/189#issuecomment-1116482278). + +We’re working together with Vercel and Shopify to unify bundler support for shared semantics in both Webpack and Vite. Before launch, we want to make sure that the semantics of RSCs are the same across the whole React ecosystem. This is the major blocker for reaching stable. + +## Asset Loading {/*asset-loading*/} + +Currently, assets like scripts, external styles, fonts, and images are typically preloaded and loaded using external systems. This can make it tricky to coordinate across new environments like streaming, server components, and more. +We’re looking at adding APIs to preload and load deduplicated external assets through React APIs that work in all React environments. + +We’re also looking at having these support Suspense so you can have images, CSS, and fonts that block display until they’re loaded but don’t block streaming and concurrent rendering. This can help avoid [“popcorning“](https://twitter.com/sebmarkbage/status/1516852731251724293) as the visuals pop and layout shifts. + +## Static Server Rendering Optimizations {/*static-server-rendering-optimizations*/} + +Static Site Generation (SSG) and Incremental Static Regeneration (ISR) are great ways to get performance for cacheable pages, but we think we can add features to improve performance of dynamic Server Side Rendering (SSR) – especially when most but not all of the content is cacheable. We're exploring ways to optimize server rendering utilizing compilation and static passes. + +## React Optimizing Compiler {/*react-compiler*/} + +We gave an [early preview](https://www.youtube.com/watch?v=lGEMwh32soc) of React Forget at React Conf 2021. It’s a compiler that automatically generates the equivalent of `useMemo` and `useCallback` calls to minimize the cost of re-rendering, while retaining React’s programming model. + +Recently, we finished a rewrite of the compiler to make it more reliable and capable. This new architecture allows us to analyze and memoize more complex patterns such as the use of [local mutations](https://beta.reactjs.org/learn/keeping-components-pure#local-mutation-your-components-little-secret), and opens up many new compile-time optimization opportunities beyond just being on par with memoization hooks. + +We’re also working on a playground for exploring many aspects of the compiler. While the goal of the playground is to make development of the compiler easier, we think that it will make it easier to try it out and build intuition for what the compiler does. It reveals various insights into how it works under the hood, and live renders the compiler’s outputs as you type. This will be shipped together with the compiler when it’s released. + +## Offscreen {/*offscreen*/} + +Today, if you want to hide and show a component, you have two options. One is to add or remove it from the tree completely. The problem with this approach is that the state of your UI is lost each time you unmount, including state stored in the DOM, like scroll position. + +The other option is to keep the component mounted and toggle the appearance visually using CSS. This preserves the state of your UI, but it comes at a performance cost, because React must keep rendering the hidden component and all of its children whenever it receives new updates. + +Offscreen introduces a third option: hide the UI visually, but deprioritize its content. The idea is similar in spirit to the `content-visibility` CSS property: when content is hidden, it doesn't need to stay in sync with the rest of the UI. React can defer the rendering work until the rest of the app is idle, or until the content becomes visible again. + +Offscreen is a low level capability that unlocks high level features. Similar to React's other concurrent features like `startTransition`, in most cases you won't interact with the Offscreen API directly, but instead via an opinionated framework to implement patterns like: + +* **Instant transitions.** Some routing frameworks already prefetch data to speed up subsequent navigations, like when hovering over a link. With Offscreen, they'll also be able to prerender the next screen in the background. +* **Reusable state.** Similarly, when navigating between routes or tabs, you can use Offscreen to preserve the state of the previous screen so you can switch back and pick up where you left off. +* **Virtualized list rendering.** When displaying large lists of items, virtualized list frameworks will prerender more rows than are currently visible. You can use Offscreen to prerender the hidden rows at a lower priority than the visible items in the list. +* **Backgrounded content.** We're also exploring a related feature for deprioritizing content in the background without hiding it, like when displaying a modal overlay. + +## Transition Tracing {/*transition-tracing*/} + +Currently, React has two profiling tools. The [original Profiler](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) shows an overview of all the commits in a profiling session. For each commit, it also shows all components that rendered and the amount of time it took for them to render. We also have a beta version of a [Timeline Profiler](https://github.com/reactwg/react-18/discussions/76) introduced in React 18 that shows when components schedule updates and when React works on these updates. Both of these profilers help developers identify performance problems in their code. + +We’ve realized that developers don’t find knowing about individual slow commits or components out of context that useful. It’s more useful to know about what actually causes the slow commits. And that developers want to be able to track specific interactions (eg a button click, an initial load, or a page navigation) to watch for performance regressions and to understand why an interaction was slow and how to fix it. + +We previously tried to solve this issue by creating an [Interaction Tracing API](https://gist.github.com/bvaughn/8de925562903afd2e7a12554adcdda16), but it had some fundamental design flaws that reduced the accuracy of tracking why an interaction was slow and sometimes resulted in interactions never ending. We ended up [removing this API](https://github.com/facebook/react/pull/20037) because of these issues. + +We are working on a new version for the Interaction Tracing API (tentatively called Transition Tracing because it is initiated via `startTransition`) that solves these problems. + +## New React Docs {/*new-react-docs*/} + +Last year, we announced the [beta version](https://beta.reactjs.org/) of the new React documentation website. The new learning materials teach Hooks first and has new diagrams, illustrations, as well as many interactive examples and challenges. We took a break from that work to focus on the React 18 release, but now that React 18 is out, we’re actively working to finish and ship the new documentation. + +We are currently writing a detailed section about effects, as we’ve heard that is one of the more challenging topics for both new and experienced React users. [Synchronizing with Effects](https://beta.reactjs.org/learn/synchronizing-with-effects) is the first published page in the series, and there are more to come in the following weeks. When we first started writing a detailed section about effects, we’ve realized that many common effect patterns can be simplified by adding a new primitive to React. We’ve shared some initial thoughts on that in the [useEvent RFC](https://github.com/reactjs/rfcs/pull/220). It is currently in early research, and we are still iterating on the idea. We appreciate the community’s comments on the RFC so far, as well as the [feedback](https://github.com/reactjs/reactjs.org/issues/3308) and contributions to the ongoing documentation rewrite. We’d specifically like to thank [Harish Kumar](https://github.com/harish-sethuraman) for submitting and reviewing many improvements to the new website implementation. + +*Thanks to [Sophie Alpert](https://twitter.com/sophiebits) for reviewing this blog post!* diff --git a/beta/src/content/blog/index.md b/beta/src/content/blog/index.md new file mode 100644 index 000000000..16339d5ff --- /dev/null +++ b/beta/src/content/blog/index.md @@ -0,0 +1,87 @@ +--- +title: The React Blog +--- + +<Intro> + +This blog is the official source for the updates from the React team. Anything important, including release notes or deprecation notices, will be posted here first. You can also follow the [@reactjs](https://twitter.com/reactjs) account on Twitter, but you won’t miss anything essential if you only read this blog. + +</Intro> + +--- + +### React Labs: What We've Been Working On – June 2022 {/*react-labs-what-weve-been-working-on--june-2022*/} + +*June 15, 2022* + +React 18 was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring. [...] + +<ReadBlogPost path="/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022" /> + +--- + +### React v18.0 {/*react-v180*/} + +*March 29, 2022* + +React 18 is now available on npm! In our last post, we shared step-by-step instructions for upgrading your app to React 18. In this post, we’ll give an overview of what’s new in React 18, and what it means for the future. [...] + +<ReadBlogPost path="/blog/2022/03/29/react-v18" /> + +--- + +### How to Upgrade to React 18 {/*how-to-upgrade-to-react-18*/} + +*March 8, 2022* + +As we shared in the release post, React 18 introduces features powered by our new concurrent renderer, with a gradual adoption strategy for existing applications. In this post, we will guide you through the steps for upgrading to React 18. [...] + +<ReadBlogPost path="/blog/2022/03/08/react-18-upgrade-guide" /> + +--- + +### React Conf 2021 Recap {/*react-conf-2021-recap*/} + +*December 17, 2021* + +Last week we hosted our 6th React Conf. In previous years, we’ve used the React Conf stage to deliver industry changing announcements such as React Native and React Hooks. This year, we shared our multi-platform vision for React, starting with the release of React 18 and gradual adoption of concurrent features. [...] + +<ReadBlogPost path="/blog/2021/12/17/react-conf-2021-recap" /> + +--- + +### The Plan for React 18 {/*the-plan-for-react-18*/} + +*June 8, 2021* + +The React team is excited to share a few updates: + +- We’ve started work on the React 18 release, which will be our next major version. +- We’ve created a Working Group to prepare the community for gradual adoption of new features in React 18. +- We’ve published a React 18 Alpha so that library authors can try it and provide feedback. + +[...] + +<ReadBlogPost path="/blog/2021/06/08/the-plan-for-react-18" /> + +--- + +### Introducing Zero-Bundle-Size React Server Components {/*introducing-zero-bundle-size-react-server-components*/} + +*December 21, 2020* + +2020 has been a long year. As it comes to an end we wanted to share a special Holiday Update on our research into zero-bundle-size React Server Components. To introduce React Server Components, we have prepared a talk and a demo. If you want, you can check them out during the holidays, or later when work picks back up in the new year. [...] + +<ReadBlogPost path="/blog/2020/12/21/data-fetching-with-react-server-components" /> + +--- + +### All release notes {/*all-release-notes*/} + +Not every React release deserves its own blog post, but you can find a detailed changelog for every release in the [`CHANGELOG.md`](https://github.com/facebook/react/blob/main/CHANGELOG.md) file in the React repository, as well as on the [Releases](https://github.com/facebook/react/releases) page. + +--- + +### Older posts {/*older-posts*/} + +See the [older posts.](https://reactjs.org/blog/all.html) diff --git a/beta/src/content/community/acknowledgements.md b/beta/src/content/community/acknowledgements.md new file mode 100644 index 000000000..56e0e8a9b --- /dev/null +++ b/beta/src/content/community/acknowledgements.md @@ -0,0 +1,67 @@ +--- +title: Acknowledgements +--- + +<Intro> + +React was originally created by [Jordan Walke.](https://github.com/jordwalke) Today, React has a [dedicated full-time team working on it](/community/team), as well as over a thousand [open source contributors.](https://github.com/facebook/react/blob/main/AUTHORS) + +</Intro> + +## Past contributors {/*past-contributors*/} + +We'd like to recognize a few people who have made significant contributions to React and its documentation in the past and have helped maintain them over the years: + +* [Almero Steyn](https://github.com/AlmeroSteyn) +* [Andreas Svensson](https://github.com/syranide) +* [Alex Krolick](https://github.com/alexkrolick) +* [Alexey Pyltsyn](https://github.com/lex111) +* [Brandon Dail](https://github.com/aweary) +* [Brian Vaughn](https://github.com/bvaughn) +* [Caleb Meredith](https://github.com/calebmer) +* [Chang Yan](https://github.com/cyan33) +* [Cheng Lou](https://github.com/chenglou) +* [Christoph Nakazawa](https://github.com/cpojer) +* [Christopher Chedeau](https://github.com/vjeux) +* [Clement Hoang](https://github.com/clemmy) +* [Dominic Gannaway](https://github.com/trueadm) +* [Flarnie Marchan](https://github.com/flarnie) +* [Jason Quense](https://github.com/jquense) +* [Jesse Beach](https://github.com/jessebeach) +* [Jessica Franco](https://github.com/Jessidhia) +* [Jim Sproch](https://github.com/jimfb) +* [Josh Duck](https://github.com/joshduck) +* [Joe Critchley](https://github.com/joecritch) +* [Jeff Morrison](https://github.com/jeffmo) +* [Keyan Zhang](https://github.com/keyz) +* [Marco Salazar](https://github.com/salazarm) +* [Nat Alison](https://github.com/tesseralis) +* [Nathan Hunzaker](https://github.com/nhunzaker) +* [Nicolas Gallagher](https://github.com/necolas) +* [Paul O'Shannessy](https://github.com/zpao) +* [Pete Hunt](https://github.com/petehunt) +* [Philipp Spiess](https://github.com/philipp-spiess) +* [Rachel Nabors](https://github.com/rachelnabors) +* [Robert Zhang](https://github.com/robertzhidealx) +* [Sander Spies](https://github.com/sanderspies) +* [Sasha Aickin](https://github.com/aickin) +* [Seth Webster](https://github.com/sethwebster) +* [Sophia Shoemaker](https://github.com/mrscobbler) +* [Sophie Alpert](https://github.com/sophiebits) +* [Sunil Pai](https://github.com/threepointone) +* [Tim Yung](https://github.com/yungsters) +* [Xuan Huang](https://github.com/huxpro) +* [Yuzhi Zheng](https://github.com/yuzhi) + +This list is not exhaustive. + +We'd like to give special thanks to [Tom Occhino](https://github.com/tomocchino) and [Adam Wolff](https://github.com/wolffiex) for their guidance and support over the years. We are also thankful to all the volunteers who [translated React into other languages.](https://translations.reactjs.org/) + +## Additional Thanks {/*additional-thanks*/} + +Additionally, we're grateful to: + +* [Jeff Barczewski](https://github.com/jeffbski) for allowing us to use the `react` package name on npm +* [Christopher Aue](https://christopheraue.net/) for letting us use the reactjs.com domain name and the [@reactjs](https://twitter.com/reactjs) username on Twitter +* [ProjectMoon](https://github.com/ProjectMoon) for letting us use the [flux](https://www.npmjs.com/package/flux) package name on npm +* Shane Anderson for allowing us to use the [react](https://github.com/react) org on GitHub diff --git a/beta/src/content/community/conferences.md b/beta/src/content/community/conferences.md new file mode 100644 index 000000000..4a4943505 --- /dev/null +++ b/beta/src/content/community/conferences.md @@ -0,0 +1,682 @@ +--- +title: React Conferences +--- + +<Intro> + +Do you know of a local React.js conference? Add it here! (Please keep the list chronological) + +</Intro> + +## Upcoming Conferences {/*upcoming-conferences*/} + +### RemixConf 2023 {/*remixconf-2023*/} +May, 2023. Salt Lake City, UT + +[Website](https://remix.run/conf/2023) - [Twitter](https://twitter.com/remix_run) + +### App.js Conf 2023 {/*appjs-conf-2023*/} +May 10 - 12, 2023. In-person in Kraków, Poland + remote + +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) + +### Render(ATL) 2023 🍑 {/*renderatl-2023-*/} +May 31 - June 2, 2023. Atlanta, GA, USA + +[Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/) + +### React Summit 2023 {/*react-summit-2023*/} +June 2 & 6, 2023. In-person in Amsterdam, Netherlands + remote first interactivity (hybrid event) + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### ReactNext 2023 {/*reactnext-2023*/} +June 27th, 2023. Tel Aviv, Israel + +[Website](https://www.react-next.com/) - [Facebook](https://www.facebook.com/ReactNextConf) - [Youtube](https://www.youtube.com/@ReactNext) + +### React Nexus 2023 {/*react-nexus-2023*/} +July 07 & 08, 2023. Bangalore, India (In-person event) + +[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) + +### React India 2023 {/*react-india-2023*/} +Oct 5 - 7, 2023. In-person in Goa, India (hybrid event) + Oct 3 2023 - remote day + +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Youtube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) + +## Past Conferences {/*past-conferences*/} + +### React Day Berlin 2022 {/*react-day-berlin-2022*/} +December 2, 2022. In-person in Berlin, Germany + remote (hybrid event) + +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/c/ReactConferences) + +### React Global Online Summit 22.2 by Geekle {/*react-global-online-summit-222-by-geekle*/} +November 8 - 9, 2022 - Online Summit + +[Website](https://events.geekle.us/react3/) - [LinkedIn](https://www.linkedin.com/posts/geekle-us_event-react-reactjs-activity-6964904611207864320-gpDx?utm_source=share&utm_medium=member_desktop) + +### Remix Conf Europe 2022 {/*remix-conf-europe-2022*/} +November 18, 2022, 7am PST / 10am EST / 4pm CET - remote event + +[Website](https://remixconf.eu/) - [Twitter](https://twitter.com/remixconfeu) + +### React Advanced 2022 {/*react-advanced-2022*/} +October 21 & 25, 2022. In-person in London, UK + remote (hybrid event) + +[Website](https://www.reactadvanced.com/) - [Twitter](https://twitter.com/ReactAdvanced) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Videos](https://www.youtube.com/c/ReactConferences) + +### ReactJS Day 2022 {/*reactjs-day-2022*/} +October 21, 2022 in Verona, Italy + +[Website](https://2022.reactjsday.it/) - [Twitter](https://twitter.com/reactjsday) - [LinkedIn](https://www.linkedin.com/company/grusp/) - [Facebook](https://www.facebook.com/reactjsday/) - [Videos](https://www.youtube.com/c/grusp) + +### React Brussels 2022 {/*react-brussels-2022*/} +October 14, 2022. In-person in Brussels, Belgium + remote (hybrid event) + +[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact) - [LinkedIn](https://www.linkedin.com/events/6938421827153088512/) - [Facebook](https://www.facebook.com/events/1289080838167252/) - [Videos](https://www.youtube.com/channel/UCvES7lMpnx-t934qGxD4w4g) + +### React Alicante 2022 {/*react-alicante-2022*/} +September 29 - October 1, 2022. In-person in Alicante, Spain + remote (hybrid event) + +[Website](https://reactalicante.es/) - [Twitter](https://twitter.com/reactalicante) - [Facebook](https://www.facebook.com/ReactAlicante) - [Videos](https://www.youtube.com/channel/UCaSdUaITU1Cz6PvC97A7e0w) +### React India 2022 {/*react-india-2022*/} +September 22 - 24, 2022. In-person in Goa, India + remote (hybrid event) + +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Videos](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) + +### React Finland 2022 {/*react-finland-2022*/} +September 12 - 16, 2022. In-person in Helsinki, Finland + +[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) - [Schedule](https://react-finland.fi/schedule/) - [Speakers](https://react-finland.fi/speakers/) + +### React Native EU 2022: Powered by callstack {/*react-native-eu-2022-powered-by-callstack*/} +September 1-2, 2022 - Remote event + +[Website](https://www.react-native.eu/?utm_campaign=React_Native_EU&utm_source=referral&utm_content=reactjs_community_conferences) - +[Twitter](https://twitter.com/react_native_eu) - +[Linkedin](https://www.linkedin.com/showcase/react-native-eu) - +[Facebook](https://www.facebook.com/reactnativeeu/) - +[Instagram](https://www.instagram.com/reactnative_eu/) + +### ReactNext 2022 {/*reactnext-2022*/} +June 28, 2022. Tel-Aviv, Israel + +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Videos](https://www.youtube.com/c/ReactNext) + +### React Norway 2022 {/*react-norway-2022*/} +June 24, 2022. In-person at Farris Bad Hotel in Larvik, Norway and online (hybrid event). + +[Website](https://reactnorway.com/) - [Twitter](https://twitter.com/ReactNorway) + +### React Summit 2022 {/*react-summit-2022*/} +June 17 & 21, 2022. In-person in Amsterdam, Netherlands + remote first interactivity (hybrid event) + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### App.js Conf 2022 {/*appjs-conf-2022*/} +June 8 - 10, 2022. In-person in Kraków, Poland + remote + +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) + +### React Day Bangalore 2022 {/*react-day-bangalore-2022*/} +June 8 - 9, 2022. Remote + +[Website](https://reactday.in/) - [Twitter](https://twitter.com/ReactDayIn) - [Linkedin](https://www.linkedin.com/company/react-day/) - [YouTube](https://www.youtube.com/reactify_in) + +### render(ATL) 2022 🍑 {/*renderatl-2022-*/} +June 1 - 4, 2022. Atlanta, GA, USA + +[Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/) + +### RemixConf 2022 {/*remixconf-2022*/} +May 24 - 25, 2022. Salt Lake City, UT + +[Website](https://remix.run/conf/2022) - [Twitter](https://twitter.com/remix_run) - [YouTube](https://www.youtube.com/playlist?list=PLXoynULbYuEC36XutMMWEuTu9uuh171wx) + +### Reactathon 2022 {/*reactathon-2022*/} +May 3 - 5, 2022. Berkeley, CA + +[Website](https://reactathon.com) - [Twitter](https://twitter.com/reactathon) -[YouTube](https://www.youtube.com/watch?v=-YG5cljNXIA) + +### React Global Online Summit 2022 by Geekle {/*react-global-online-summit-2022-by-geekle*/} +April 20 - 21, 2022 - Online Summit + +[Website](https://events.geekle.us/react2/) - [LinkedIn](https://www.linkedin.com/events/reactglobalonlinesummit-226887417664541614081/) + +### React Miami 2022 🌴 {/*react-miami-2022-*/} +April 18 - 19, 2022. Miami, Florida +[Website](https://www.reactmiami.com/) + +### React Live 2022 {/*react-live-2022*/} +April 1, 2022. Amsterdam, The Netherlands + +[Website](https://www.reactlive.nl/) - [Twitter](https://twitter.com/reactlivenl) + +### AgentConf 2022 {/*agentconf-2022*/} + +January 27 - 30, 2022. In-person in Dornbirn and Lech Austria + +[Website](https://agent.sh/) - [Twitter](https://twitter.com/AgentConf) - [Instagram](https://www.instagram.com/teamagent/) + +### React Conf 2021 {/*react-conf-2021*/} +December 8, 2021 - remote event (replay event on December 9) + +[Website](https://conf.reactjs.org/) + +### ReactEurope 2021 {/*reacteurope-2021*/} +December 9-10, 2021 - remote event + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### ReactNext 2021 {/*reactnext-2021*/} +December 15, 2021. Tel-Aviv, Israel + +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) + +### React India 2021 {/*react-india-2021*/} +November 12-13, 2021 - remote event + +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia/) - [LinkedIn](https://www.linkedin.com/showcase/14545585) - [YouTube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w/videos) + +### React Global by Geekle {/*react-global-by-geekle*/} +November 3-4, 2021 - remote event + +[Website](https://geekle.us/react) - [LinkedIn](https://www.linkedin.com/events/javascriptglobalsummit6721691514176720896/) - [YouTube](https://www.youtube.com/watch?v=0HhWIvPhbu0) + +### React Advanced 2021 {/*react-advanced-2021*/} +October 22-23, 2021. In-person in London, UK + remote (hybrid event) + +[Website](https://reactadvanced.com) - [Twitter](https://twitter.com/reactadvanced) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Videos](https://youtube.com/c/ReactConferences) + +### React Conf Brasil 2021 {/*react-conf-brasil-2021*/} +October 16, 2021 - remote event + +[Website](http://reactconf.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Slack](https://react.now.sh) - [Facebook](https://facebook.com/reactconf) - [Instagram](https://instagram.com/reactconfbr) - [YouTube](https://www.youtube.com/channel/UCJL5eorStQfC0x1iiWhvqPA/videos) + +### React Brussels 2021 {/*react-brussels-2021*/} +October 15, 2021 - remote event + +[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact) - [LinkedIn](https://www.linkedin.com/events/6805708233819336704/) + +### render(ATL) 2021 {/*renderatl-2021*/} +September 13-15, 2021. Atlanta, GA, USA + +[Website](https://renderatl.com) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) + +### React Native EU 2021 {/*react-native-eu-2021*/} +September 1-2, 2021 - remote event + +[Website](https://www.react-native.eu/) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu/) - [Instagram](https://www.instagram.com/reactnative_eu/) + +### React Finland 2021 {/*react-finland-2021*/} +August 30 - September 3, 2021 - remote event + +[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) - [LinkedIn](https://www.linkedin.com/company/react-finland/) + +### React Case Study Festival 2021 {/*react-case-study-festival-2021*/} +April 27-28, 2021 - remote event + +[Website](https://link.geekle.us/react/offsite) - [LinkedIn](https://www.linkedin.com/events/reactcasestudyfestival6721300943411015680/) - [Facebook](https://www.facebook.com/events/255715435820203) + +### React Summit - Remote Edition 2021 {/*react-summit---remote-edition-2021*/} +April 14-16, 2021, 7am PST / 10am EST / 4pm CEST - remote event + +[Website](https://remote.reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### React fwdays’21 {/*react-fwdays21*/} +March 27, 2021 - remote event + +[Website](https://fwdays.com/en/event/react-fwdays-2021) - [Twitter](https://twitter.com/fwdays) - [Facebook](https://www.facebook.com/events/1133828147054286) - [LinkedIn](https://www.linkedin.com/events/reactfwdays-21onlineconference6758046347334582273) - [Meetup](https://www.meetup.com/ru-RU/Fwdays/events/275764431/) + +### React Next 2020 {/*react-next-2020*/} +December 1-2, 2020 - remote event + +[Website](https://react-next.com/) - [Twitter](https://twitter.com/reactnext) - [Facebook](https://www.facebook.com/ReactNext2016/) + +### React Conf Brasil 2020 {/*react-conf-brasil-2020*/} +November 21, 2020 - remote event + +[Website](https://reactconf.com.br/) - [Twitter](https://twitter.com/reactconfbr) - [Slack](https://react.now.sh/) + +### React Summit 2020 {/*react-summit-2020*/} +October 15-16, 2020, 7am PST / 10am EST / 4pm CEST - remote event + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### React Native EU 2020 {/*react-native-eu-2020*/} +September 3-4, 2020 - remote event + +[Website](https://www.react-native.eu/) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu/) - [YouTube](https://www.youtube.com/watch?v=m0GfmlGFh3E&list=PLZ3MwD-soTTHy9_88QPLF8DEJkvoB5Tl-) - [Instagram](https://www.instagram.com/reactnative_eu/) + +### ReactEurope 2020 {/*reacteurope-2020*/} +May 14-15, 2020 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### Byteconf React 2020 {/*byteconf-react-2020*/} +May 1, 2020. Streamed online on YouTube. + +[Website](https://www.bytesized.xyz) - [Twitter](https://twitter.com/bytesizedcode) - [YouTube](https://www.youtube.com/channel/UC046lFvJZhiwSRWsoH8SFjg) + +### React Summit - Remote Edition 2020 {/*react-summit---remote-edition-2020*/} +3pm CEST time, April 17, 2020 - remote event + +[Website](https://remote.reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### Reactathon 2020 {/*reactathon-2020*/} +March 30 - 31, 2020 in San Francisco, CA + +[Website](https://www.reactathon.com) - [Twitter](https://twitter.com/reactathon) - [Facebook](https://www.facebook.com/events/575942819854160/) + +### ReactConf AU 2020 {/*reactconf-au-2020*/} +February 27 & 28, 2020 in Sydney, Australia + +[Website](https://reactconfau.com/) - [Twitter](https://twitter.com/reactconfau) - [Facebook](https://www.facebook.com/reactconfau) - [Instagram](https://www.instagram.com/reactconfau/) + +### React Barcamp Cologne 2020 {/*react-barcamp-cologne-2020*/} +February 1-2, 2020 in Cologne, Germany + +[Website](https://react-barcamp.de/) - [Twitter](https://twitter.com/ReactBarcamp) - [Facebook](https://www.facebook.com/reactbarcamp) + +### React Day Berlin 2019 {/*react-day-berlin-2019*/} +December 6, 2019 in Berlin, Germany + +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/reactdayberlin) + +### React Summit 2019 {/*react-summit-2019*/} +November 30, 2019 in Lagos, Nigeria + +[Website](https://reactsummit2019.splashthat.com) -[Twitter](https://twitter.com/react_summit) + +### React Conf Brasil 2019 {/*react-conf-brasil-2019*/} +October 19, 2019 in São Paulo, BR + +[Website](https://reactconf.com.br/) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Slack](https://react.now.sh/) + +### React Advanced 2019 {/*react-advanced-2019*/} +October 25, 2019 in London, UK + +[Website](https://reactadvanced.com) - [Twitter](http://twitter.com/reactadvanced) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Videos](https://youtube.com/c/ReactConferences) + +### React Conf 2019 {/*react-conf-2019*/} +October 24-25, 2019 in Henderson, Nevada USA + +[Website](https://conf.reactjs.org/) - [Twitter](https://twitter.com/reactjs) + +### React Alicante 2019 {/*react-alicante-2019*/} +September 26-28, 2019 in Alicante, Spain + +[Website](http://reactalicante.es/) - [Twitter](https://twitter.com/reactalicante) - [Facebook](https://www.facebook.com/ReactAlicante) + +### React India 2019 {/*react-india-2019*/} +September 26-28, 2019 in Goa, India + +[Website](https://www.reactindia.io/) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) + +### React Boston 2019 {/*react-boston-2019*/} +September 21-22, 2019 in Boston, Massachusetts USA + +[Website](https://www.reactboston.com/) - [Twitter](https://twitter.com/reactboston) + +### React Live 2019 {/*react-live-2019*/} +September 13th, 2019. Amsterdam, The Netherlands + +[Website](https://www.reactlive.nl/) - [Twitter](https://twitter.com/reactlivenl) + +### React New York 2019 {/*react-new-york-2019*/} +September 13th, 2019. New York, USA + +[Website](https://reactnewyork.com/) - [Twitter](https://twitter.com/reactnewyork) + +### ComponentsConf 2019 {/*componentsconf-2019*/} +September 6, 2019 in Melbourne, Australia + +[Website](https://www.componentsconf.com.au/) - [Twitter](https://twitter.com/componentsconf) + +### React Native EU 2019 {/*react-native-eu-2019*/} +September 5-6 in Wrocław, Poland + +[Website](https://react-native.eu) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu) + +### React Conf Iran 2019 {/*react-conf-iran-2019*/} +August 29, 2019. Tehran, Iran. + +[Website](https://reactconf.ir/) - [Videos](https://www.youtube.com/playlist?list=PL-VNqZFI5Nf-Nsj0rD3CWXGPkH-DI_0VY) - [Highlights](https://github.com/ReactConf/react-conf-highlights) + +### React Rally 2019 {/*react-rally-2019*/} +August 22-23, 2019. Salt Lake City, USA. + +[Website](https://www.reactrally.com/) - [Twitter](https://twitter.com/ReactRally) - [Instagram](https://www.instagram.com/reactrally/) + +### Chain React 2019 {/*chain-react-2019*/} +July 11-12, 2019. Portland, OR, USA. + +[Website](https://infinite.red/ChainReactConf) + +### React Loop 2019 {/*react-loop-2019*/} +June 21, 2019 Chicago, Illinois USA + +[Website](https://reactloop.com) - [Twitter](https://twitter.com/ReactLoop) + +### React Norway 2019 {/*react-norway-2019*/} +June 12, 2019. Larvik, Norway + +[Website](https://reactnorway.com) - [Twitter](https://twitter.com/ReactNorway) + +### ReactNext 2019 {/*reactnext-2019*/} +June 11, 2019. Tel Aviv, Israel + +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) + +### React Conf Armenia 2019 {/*react-conf-armenia-2019*/} +May 25, 2019 in Yerevan, Armenia + +[Website](https://reactconf.am/) - [Twitter](https://twitter.com/ReactConfAM) - [Facebook](https://www.facebook.com/reactconf.am/) - [YouTube](https://www.youtube.com/c/JavaScriptConferenceArmenia) - [CFP](http://bit.ly/speakReact) + +### ReactEurope 2019 {/*reacteurope-2019*/} +May 23-24, 2019 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### React.NotAConf 2019 {/*reactnotaconf-2019*/} +May 11 in Sofia, Bulgaria + +[Website](http://react-not-a-conf.com/) - [Twitter](https://twitter.com/reactnotaconf) - [Facebook](https://www.facebook.com/events/780891358936156) + +### ReactJS Girls Conference {/*reactjs-girls-conference*/} +May 3, 2019 in London, UK + +[Website](https://reactjsgirls.com/) - [Twitter](https://twitter.com/reactjsgirls) + +### React Finland 2019 {/*react-finland-2019*/} +April 24-26 in Helsinki, Finland + +[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) + +### React Amsterdam 2019 {/*react-amsterdam-2019*/} +April 12, 2019 in Amsterdam, The Netherlands + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### App.js Conf 2019 {/*appjs-conf-2019*/} +April 4-5, 2019 in Kraków, Poland + +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) + +### Reactathon 2019 {/*reactathon-2019*/} +March 30-31, 2019 in San Francisco, USA + +[Website](https://www.reactathon.com/) - [Twitter](https://twitter.com/reactathon) + +### React Iran 2019 {/*react-iran-2019*/} +January 31, 2019 in Tehran, Iran + +[Website](http://reactiran.com) - [Instagram](https://www.instagram.com/reactiran/) + +### React Day Berlin 2018 {/*react-day-berlin-2018*/} +November 30, Berlin, Germany + +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/channel/UC1EYHmQYBUJjkmL6OtK4rlw) + +### ReactNext 2018 {/*reactnext-2018*/} +November 4 in Tel Aviv, Israel + +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Facebook](https://facebook.com/ReactNext2016) + +### React Conf 2018 {/*react-conf-2018*/} +October 25-26 in Henderson, Nevada USA + +[Website](https://conf.reactjs.org/) + +### React Conf Brasil 2018 {/*react-conf-brasil-2018*/} +October 20 in Sao Paulo, Brazil + +[Website](http://reactconfbr.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/reactconf) + +### ReactJS Day 2018 {/*reactjs-day-2018*/} +October 5 in Verona, Italy + +[Website](http://2018.reactjsday.it) - [Twitter](https://twitter.com/reactjsday) + +### React Boston 2018 {/*react-boston-2018*/} +September 29-30 in Boston, Massachusetts USA + +[Website](http://www.reactboston.com/) - [Twitter](https://twitter.com/ReactBoston) + +### React Alicante 2018 {/*react-alicante-2018*/} +September 13-15 in Alicante, Spain + +[Website](http://reactalicante.es) - [Twitter](https://twitter.com/ReactAlicante) + +### React Native EU 2018 {/*react-native-eu-2018*/} +September 5-6 in Wrocław, Poland + +[Website](https://react-native.eu) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu) + +### Byteconf React 2018 {/*byteconf-react-2018*/} +August 31 streamed online, via Twitch + +[Website](https://byteconf.com) - [Twitch](https://twitch.tv/byteconf) - [Twitter](https://twitter.com/byteconf) + +### ReactFoo Delhi {/*reactfoo-delhi*/} +August 18 in Delhi, India + +[Website](https://reactfoo.in/2018-delhi/) - [Twitter](https://twitter.com/reactfoo) - [Past talks](https://hasgeek.tv) + +### React DEV Conf China {/*react-dev-conf-china*/} +August 18 in Guangzhou, China + +[Website](https://react.w3ctech.com) + +### React Rally {/*react-rally*/} +August 16-17 in Salt Lake City, Utah USA + +[Website](http://www.reactrally.com) - [Twitter](https://twitter.com/reactrally) + +### Chain React 2018 {/*chain-react-2018*/} +July 11-13 in Portland, Oregon USA + +[Website](https://infinite.red/ChainReactConf) - [Twitter](https://twitter.com/chainreactconf) + +### ReactFoo Mumbai {/*reactfoo-mumbai*/} +May 26 in Mumbai, India + +[Website](https://reactfoo.in/2018-mumbai/) - [Twitter](https://twitter.com/reactfoo) - [Past talks](https://hasgeek.tv) + + +### ReactEurope 2018 {/*reacteurope-2018*/} +May 17-18 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### React.NotAConf 2018 {/*reactnotaconf-2018*/} +April 28 in Sofia, Bulgaria + +[Website](http://react-not-a-conf.com/) - [Twitter](https://twitter.com/reactnotaconf) - [Facebook](https://www.facebook.com/groups/1614950305478021/) + +### React Finland 2018 {/*react-finland-2018*/} +April 24-26 in Helsinki, Finland + +[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) + +### React Amsterdam 2018 {/*react-amsterdam-2018*/} +April 13 in Amsterdam, The Netherlands + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) + +### React Native Camp UA 2018 {/*react-native-camp-ua-2018*/} +March 31 in Kiev, Ukraine + +[Website](http://reactnative.com.ua/) - [Twitter](https://twitter.com/reactnativecamp) - [Facebook](https://www.facebook.com/reactnativecamp/) + +### Reactathon 2018 {/*reactathon-2018*/} +March 20-22 in San Francisco, USA + +[Website](https://www.reactathon.com/) - [Twitter](https://twitter.com/reactathon) - [Videos (fundamentals)](https://www.youtube.com/watch?v=knn364bssQU&list=PLRvKvw42Rc7OWK5s-YGGFSmByDzzgC0HP), [Videos (advanced day1)](https://www.youtube.com/watch?v=57hmk4GvJpk&list=PLRvKvw42Rc7N0QpX2Rc5CdrqGuxzwD_0H), [Videos (advanced day2)](https://www.youtube.com/watch?v=1hvQ8p8q0a0&list=PLRvKvw42Rc7Ne46QAjWNWFo1Jf0mQdnIW) + +### ReactFest 2018 {/*reactfest-2018*/} +March 8-9 in London, UK + +[Website](https://reactfest.uk/) - [Twitter](https://twitter.com/ReactFest) - [Videos](https://www.youtube.com/watch?v=YOCrJ5vRCnw&list=PLRgweB8YtNRt-Sf-A0y446wTJNUaAAmle) + +### AgentConf 2018 {/*agentconf-2018*/} +January 25-28 in Dornbirn, Austria + +[Website](http://agent.sh/) + +### ReactFoo Pune {/*reactfoo-pune*/} +January 19-20, Pune, India + +[Website](https://reactfoo.in/2018-pune/) - [Twitter](https://twitter.com/ReactFoo) + +### React Day Berlin 2017 {/*react-day-berlin-2017*/} +December 2, Berlin, Germany + +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/watch?v=UnNLJvHKfSY&list=PL-3BrJ5CiIx5GoXci54-VsrO6GwLhSHEK) + +### React Seoul 2017 {/*react-seoul-2017*/} +November 4 in Seoul, South Korea + +[Website](http://seoul.reactjs.kr/en) + +### ReactiveConf 2017 {/*reactiveconf-2017*/} +October 25–27, Bratislava, Slovakia + +[Website](https://reactiveconf.com) - [Videos](https://www.youtube.com/watch?v=BOKxSFB2hOE&list=PLa2ZZ09WYepMB-I7AiDjDYR8TjO8uoNjs) + +### React Summit 2017 {/*react-summit-2017*/} +October 21 in Lagos, Nigeria + +[Website](https://reactsummit2017.splashthat.com/) - [Twitter](https://twitter.com/DevCircleLagos/) - [Facebook](https://www.facebook.com/groups/DevCLagos/) + +### State.js Conference 2017 {/*statejs-conference-2017*/} +October 13 in Stockholm, Sweden + +[Website](https://statejs.com/) + +### React Conf Brasil 2017 {/*react-conf-brasil-2017*/} +October 7 in Sao Paulo, Brazil + +[Website](http://reactconfbr.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/reactconf/) + +### ReactJS Day 2017 {/*reactjs-day-2017*/} +October 6 in Verona, Italy + +[Website](http://2017.reactjsday.it) - [Twitter](https://twitter.com/reactjsday) - [Videos](https://www.youtube.com/watch?v=bUqqJPIgjNU&list=PLWK9j6ps_unl293VhhN4RYMCISxye3xH9) + +### React Alicante 2017 {/*react-alicante-2017*/} +September 28-30 in Alicante, Spain + +[Website](http://reactalicante.es) - [Twitter](https://twitter.com/ReactAlicante) - [Videos](https://www.youtube.com/watch?v=UMZvRCWo6Dw&list=PLd7nkr8mN0sWvBH_s0foCE6eZTX8BmLUM) + +### React Boston 2017 {/*react-boston-2017*/} +September 23-24 in Boston, Massachusetts USA + +[Website](http://www.reactboston.com/) - [Twitter](https://twitter.com/ReactBoston) - [Videos](https://www.youtube.com/watch?v=2iPE5l3cl_s&list=PL-fCkV3wv4ub8zJMIhmrrLcQqSR5XPlIT) + +### ReactFoo 2017 {/*reactfoo-2017*/} +September 14 in Bangalore, India + +[Website](https://reactfoo.in/2017/) - [Videos](https://www.youtube.com/watch?v=3G6tMg29Wnw&list=PL279M8GbNsespKKm1L0NAzYLO6gU5LvfH) + +### ReactNext 2017 {/*reactnext-2017*/} +September 8-10 in Tel Aviv, Israel + +[Website](http://react-next.com/) - [Twitter](https://twitter.com/ReactNext) - [Videos (Hall A)](https://www.youtube.com/watch?v=eKXQw5kR86c&list=PLMYVq3z1QxSqq6D7jxVdqttOX7H_Brq8Z), [Videos (Hall B)](https://www.youtube.com/watch?v=1InokWxYGnE&list=PLMYVq3z1QxSqCZmaqgTXLsrcJ8mZmBF7T) + +### React Native EU 2017 {/*react-native-eu-2017*/} +September 6-7 in Wroclaw, Poland + +[Website](http://react-native.eu/) - [Videos](https://www.youtube.com/watch?v=453oKJAqfy0&list=PLzUKC1ci01h_hkn7_KoFA-Au0DXLAQZR7) + +### React Rally 2017 {/*react-rally-2017*/} +August 24-25 in Salt Lake City, Utah USA + +[Website](http://www.reactrally.com) - [Twitter](https://twitter.com/reactrally) - [Videos](https://www.youtube.com/watch?v=f4KnHNCZcH4&list=PLUD4kD-wL_zZUhvAIHJjueJDPr6qHvkni) + +### Chain React 2017 {/*chain-react-2017*/} +July 10-11 in Portland, Oregon USA + +[Website](https://infinite.red/ChainReactConf) - [Twitter](https://twitter.com/chainreactconf) - [Videos](https://www.youtube.com/watch?v=cz5BzwgATpc&list=PLFHvL21g9bk3RxJ1Ut5nR_uTZFVOxu522) + +### ReactEurope 2017 {/*reacteurope-2017*/} +May 18th & 19th in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### React Amsterdam 2017 {/*react-amsterdam-2017*/} +April 21st in Amsterdam, The Netherlands + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Videos](https://youtube.com/c/ReactConferences) + +### React London 2017 {/*react-london-2017*/} +March 28th at the [QEII Centre, London](http://qeiicentre.london/) + +[Website](http://react.london/) - [Videos](https://www.youtube.com/watch?v=2j9rSur_mnk&list=PLW6ORi0XZU0CFjdoYeC0f5QReBG-NeNKJ) + +### React Conf 2017 {/*react-conf-2017*/} +March 13-14 in Santa Clara, CA + +[Website](http://conf.reactjs.org/) - [Videos](https://www.youtube.com/watch?v=7HSd1sk07uU&list=PLb0IAmt7-GS3fZ46IGFirdqKTIxlws7e0) + +### Agent Conference 2017 {/*agent-conference-2017*/} +January 20-21 in Dornbirn, Austria + +[Website](http://agent.sh/) + +### React Remote Conf 2016 {/*react-remote-conf-2016*/} +October 26-28 online + +[Website](https://allremoteconfs.com/react-2016) - [Schedule](https://allremoteconfs.com/react-2016#schedule) + +### Reactive 2016 {/*reactive-2016*/} +October 26-28 in Bratislava, Slovakia + +[Website](https://reactiveconf.com/) + +### ReactNL 2016 {/*reactnl-2016*/} +October 13 in Amsterdam, The Netherlands + +[Website](http://reactnl.org/) - [Schedule](http://reactnl.org/#program) + +### ReactNext 2016 {/*reactnext-2016*/} +September 15 in Tel Aviv, Israel + +[Website](http://react-next.com/) - [Schedule](http://react-next.com/#schedule) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) + +### ReactRally 2016 {/*reactrally-2016*/} +August 25-26 in Salt Lake City, UT + +[Website](http://www.reactrally.com/) - [Schedule](http://www.reactrally.com/#/schedule) - [Videos](https://www.youtube.com/playlist?list=PLUD4kD-wL_zYSfU3tIYsb4WqfFQzO_EjQ) + +### ReactEurope 2016 {/*reacteurope-2016*/} +June 2 & 3 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### React Amsterdam 2016 {/*react-amsterdam-2016*/} +April 16 in Amsterdam, The Netherlands + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### React.js Conf 2016 {/*reactjs-conf-2016*/} +February 22 & 23 in San Francisco, CA + +[Website](http://conf2016.reactjs.org/) - [Schedule](http://conf2016.reactjs.org/schedule.html) - [Videos](https://www.youtube.com/playlist?list=PLb0IAmt7-GS0M8Q95RIc2lOM6nc77q1IY) + +### Reactive 2015 {/*reactive-2015*/} +November 2-4 in Bratislava, Slovakia + +[Website](https://reactive2015.com/) - [Schedule](https://reactive2015.com/schedule_speakers.html#schedule) + +### ReactEurope 2015 {/*reacteurope-2015*/} +July 2 & 3 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### React.js Conf 2015 {/*reactjs-conf-2015*/} +January 28 & 29 in Facebook HQ, CA + +[Website](http://conf2015.reactjs.org/) - [Schedule](http://conf2015.reactjs.org/schedule.html) - [Videos](https://www.youtube.com/playlist?list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr) diff --git a/beta/src/content/community/docs-contributors.md b/beta/src/content/community/docs-contributors.md new file mode 100644 index 000000000..0ce50fbf0 --- /dev/null +++ b/beta/src/content/community/docs-contributors.md @@ -0,0 +1,34 @@ +--- +title: Docs Contributors +--- + +<Intro> + +React documentation is written and maintained by the [React team](/community/team) and [external contributors.](https://github.com/reactjs/reactjs.org/graphs/contributors) On this page, we'd like to thank a few people who've made significant contributions to this site. + +</Intro> + +## Content {/*content*/} + +* [Rachel Nabors](https://twitter.com/RachelNabors): editing, writing, illustrating +* [Dan Abramov](https://twitter.com/dan_abramov): writing, curriculum design +* [Sylwia Vargas](https://twitter.com/SylwiaVargas): example code +* [Rick Hanlon](https://twitter.com/rickhanlonii): writing +* [David McCabe](https://twitter.com/mcc_abe): writing + +## Design {/*design*/} + +* [Dan Lebowitz](https://twitter.com/lebo): site design +* [Razvan Gradinar](https://dribbble.com/GradinarRazvan): sandbox design +* [Maggie Appleton](https://maggieappleton.com/): diagram system +* [Sophie Alpert](https://twitter.com/sophiebits): color-coded explanations + +## Development {/*development*/} + +* [Jared Palmer](https://twitter.com/jaredpalmer): site development +* [ThisDotLabs](https://www.thisdot.co/) ([Dane Grant](https://twitter.com/danecando), [Dustin Goodman](https://twitter.com/dustinsgoodman)): site development +* [CodeSandbox](https://codesandbox.io/) ([Ives van Hoorne](https://twitter.com/CompuIves), [Alex Moldovan](https://twitter.com/alexnmoldovan), [Jasper De Moor](https://twitter.com/JasperDeMoor), [Danilo Woznica](https://twitter.com/danilowoz)): sandbox integration +* [Rick Hanlon](https://twitter.com/rickhanlonii): site development +* [Harish Kumar](https://www.strek.in/): development and maintenance + +We'd also like to thank countless alpha testers and community members who gave us feedback along the way. diff --git a/beta/src/content/community/index.md b/beta/src/content/community/index.md new file mode 100644 index 000000000..75b42251a --- /dev/null +++ b/beta/src/content/community/index.md @@ -0,0 +1,32 @@ +--- +title: Community +--- + +<Intro> + +React has a community of millions of developers. On this page we've listed some React-related communities that you can be a part of; see the other pages in this section for additional online and in-person learning materials. + +</Intro> + +## Code of Conduct {/*code-of-conduct*/} + +Before participating in React's communities, [please read our Code of Conduct.](https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md) We have adopted the [Contributor Covenant](https://www.contributor-covenant.org/) and we expect that all community members adhere to the guidelines within. + +## Stack Overflow {/*stack-overflow*/} + +Stack Overflow is a popular forum to ask code-level questions or if you're stuck with a specific error. Read through the [existing questions](https://stackoverflow.com/questions/tagged/reactjs) tagged with **reactjs** or [ask your own](https://stackoverflow.com/questions/ask?tags=reactjs)! + +## Popular Discussion Forums {/*popular-discussion-forums*/} + +There are many online forums which are a great place for discussion about best practices and application architecture as well as the future of React. If you have an answerable code-level question, Stack Overflow is usually a better fit. + +Each community consists of many thousands of React users. + +* [DEV's React community](https://dev.to/t/react) +* [Hashnode's React community](https://hashnode.com/n/reactjs) +* [Reactiflux online chat](https://discord.gg/reactiflux) +* [Reddit's React community](https://www.reddit.com/r/reactjs/) + +## News {/*news*/} + +For the latest news about React, [follow **@reactjs** on Twitter](https://twitter.com/reactjs) and the [official React blog](/blog/) on this website. diff --git a/beta/src/content/community/meetups.md b/beta/src/content/community/meetups.md new file mode 100644 index 000000000..7cbed35dd --- /dev/null +++ b/beta/src/content/community/meetups.md @@ -0,0 +1,221 @@ +--- +title: React Meetups +--- + +<Intro> + +Do you have a local React.js meetup? Add it here! (Please keep the list alphabetical) + +</Intro> + +## Albania {/*albania*/} +* [Tirana](https://www.meetup.com/React-User-Group-Albania/) + +## Argentina {/*argentina*/} +* [Buenos Aires](https://www.meetup.com/es/React-en-Buenos-Aires) +* [Rosario](https://www.meetup.com/es/reactrosario) + +## Australia {/*australia*/} +* [Brisbane](https://www.meetup.com/reactbris/) +* [Melbourne](https://www.meetup.com/React-Melbourne/) +* [Sydney](https://www.meetup.com/React-Sydney/) + +## Austria {/*austria*/} +* [Vienna](https://www.meetup.com/Vienna-ReactJS-Meetup/) + +## Belgium {/*belgium*/} +* [Belgium](https://www.meetup.com/ReactJS-Belgium/) + +## Brazil {/*brazil*/} +* [Belo Horizonte](https://www.meetup.com/reactbh/) +* [Curitiba](https://www.meetup.com/pt-br/ReactJS-CWB/) +* [Florianópolis](https://www.meetup.com/pt-br/ReactJS-Floripa/) +* [Goiânia](https://www.meetup.com/pt-br/React-Goiania/) +* [Joinville](https://www.meetup.com/pt-BR/React-Joinville/) +* [Juiz de Fora](https://www.meetup.com/pt-br/React-Juiz-de-Fora/) +* [Maringá](https://www.meetup.com/pt-BR/React-Maringa/) +* [Porto Alegre](https://www.meetup.com/pt-BR/React-Porto-Alegre/) +* [Rio de Janeiro](https://www.meetup.com/pt-BR/React-Rio-de-Janeiro/) +* [Salvador](https://www.meetup.com/pt-BR/ReactSSA) +* [São Paulo](https://www.meetup.com/pt-BR/ReactJS-SP/) +* [Vila Velha](https://www.meetup.com/pt-BR/React-ES/) + +## Bolivia {/*bolivia*/} +* [Bolivia](https://www.meetup.com/ReactBolivia/) + +## Canada {/*canada*/} +* [Halifax, NS](https://www.meetup.com/Halifax-ReactJS-Meetup/) +* [Montreal, QC - React Native](https://www.meetup.com/fr-FR/React-Native-MTL/) +* [Vancouver, BC](https://www.meetup.com/ReactJS-Vancouver-Meetup/) +* [Ottawa, ON](https://www.meetup.com/Ottawa-ReactJS-Meetup/) +* [Toronto, ON](https://www.meetup.com/Toronto-React-Native/events/) + +## Chile {/*chile*/} +* [Santiago](https://www.meetup.com/es-ES/react-santiago/) + +## China {/*china*/} +* [Beijing](https://www.meetup.com/Beijing-ReactJS-Meetup/) + +## Colombia {/*colombia*/} +* [Bogotá](https://www.meetup.com/meetup-group-iHIeHykY/) +* [Medellin](https://www.meetup.com/React-Medellin/) +* [Cali](https://www.meetup.com/reactcali/) + +## Denmark {/*denmark*/} +* [Aalborg](https://www.meetup.com/Aalborg-React-React-Native-Meetup/) +* [Aarhus](https://www.meetup.com/Aarhus-ReactJS-Meetup/) + +## Egypt {/*egypt*/} +* [Cairo](https://www.meetup.com/react-cairo/) + +## England (UK) {/*england-uk*/} +* [Manchester](https://www.meetup.com/Manchester-React-User-Group/) +* [React.JS Girls London](https://www.meetup.com/ReactJS-Girls-London/) +* [React London : Bring Your Own Project](https://www.meetup.com/React-London-Bring-Your-Own-Project/) + +## France {/*france*/} +* [Nantes](https://www.meetup.com/React-Nantes/) +* [Lille](https://www.meetup.com/ReactBeerLille/) +* [Paris](https://www.meetup.com/ReactJS-Paris/) + +## Germany {/*germany*/} +* [Cologne](https://www.meetup.com/React-Cologne/) +* [Düsseldorf](https://www.meetup.com/de-DE/ReactJS-Meetup-Dusseldorf/) +* [Hamburg](https://www.meetup.com/Hamburg-React-js-Meetup/) +* [Karlsruhe](https://www.meetup.com/react_ka/) +* [Kiel](https://www.meetup.com/Kiel-React-Native-Meetup/) +* [Munich](https://www.meetup.com/ReactJS-Meetup-Munich/) +* [React Berlin](https://www.meetup.com/React-Open-Source/) + +## Greece {/*greece*/} +* [Athens](https://www.meetup.com/React-To-React-Athens-MeetUp/) +* [Thessaloniki](https://www.meetup.com/Thessaloniki-ReactJS-Meetup/) + +## Hungary {/*hungary*/} +* [Budapest](https://www.meetup.com/React-Budapest/) + +## India {/*india*/} +* [Bangalore](https://www.meetup.com/ReactJS-Bangalore/) +* [Bangalore](https://www.meetup.com/React-Native-Bangalore-Meetup) +* [Chandigarh](https://www.meetup.com/Chandigarh-React-Developers/) +* [Chennai](https://www.meetup.com/React-Chennai/) +* [Delhi NCR](https://www.meetup.com/React-Delhi-NCR/) +* [Jaipur](https://www.meetup.com/JaipurJS-Developer-Meetup/) +* [Pune](https://www.meetup.com/ReactJS-and-Friends/) + +## Indonesia {/*indonesia*/} +* [Indonesia](https://www.meetup.com/reactindonesia/) + +## Ireland {/*ireland*/} +* [Dublin](https://www.meetup.com/ReactJS-Dublin/) + +## Israel {/*israel*/} +* [Tel Aviv](https://www.meetup.com/ReactJS-Israel/) + +## Italy {/*italy*/} +* [Milan](https://www.meetup.com/React-JS-Milano/) + +## Kenya {/*kenya*/} +* [Nairobi - Reactdevske](https://kommunity.com/reactjs-developer-community-kenya-reactdevske) + +## Malaysia {/*malaysia*/} +* [Kuala Lumpur](https://www.kl-react.com/) +* [Penang](https://www.facebook.com/groups/reactpenang/) + +## Netherlands {/*netherlands*/} +* [Amsterdam](https://www.meetup.com/React-Amsterdam/) + +## New Zealand {/*new-zealand*/} +* [Wellington](https://www.meetup.com/React-Wellington/) + +## Norway {/*norway*/} +* [Norway](https://reactjs-norway.webflow.io/) +* [Oslo](https://www.meetup.com/ReactJS-Oslo-Meetup/) + +## Pakistan {/*pakistan*/} +* [Karachi](https://www.facebook.com/groups/902678696597634/) +* [Lahore](https://www.facebook.com/groups/ReactjsLahore/) + +## Panama {/*panama*/} +* [Panama](https://www.meetup.com/React-Panama/) + +## Peru {/*peru*/} +* [Lima](https://www.meetup.com/ReactJS-Peru/) + +## Philippines {/*philippines*/} +* [Manila](https://www.meetup.com/reactjs-developers-manila/) +* [Manila - ReactJS PH](https://www.meetup.com/ReactJS-Philippines/) + +## Poland {/*poland*/} +* [Warsaw](https://www.meetup.com/React-js-Warsaw/) +* [Wrocław](https://www.meetup.com/ReactJS-Wroclaw/) + +## Portugal {/*portugal*/} +* [Lisbon](https://www.meetup.com/JavaScript-Lisbon/) + +## Scotland (UK) {/*scotland-uk*/} +* [Edinburgh](https://www.meetup.com/React-Scotland/) + +## Spain {/*spain*/} +* [Barcelona](https://www.meetup.com/ReactJS-Barcelona/) +* [Canarias](https://www.meetup.com/React-Canarias/) + +## Sweden {/*sweden*/} +* [Goteborg](https://www.meetup.com/ReactJS-Goteborg/) +* [Stockholm](https://www.meetup.com/Stockholm-ReactJS-Meetup/) + +## Switzerland {/*switzerland*/} +* [Zurich](https://www.meetup.com/Zurich-ReactJS-Meetup/) + +## Turkey {/*turkey*/} +* [Istanbul](https://kommunity.com/reactjs-istanbul) + +## Ukraine {/*ukraine*/} +* [Kyiv](https://www.meetup.com/Kyiv-ReactJS-Meetup) + +## US {/*us*/} +* [Ann Arbor, MI - ReactJS](https://www.meetup.com/AnnArbor-jsx/) +* [Atlanta, GA - ReactJS](https://www.meetup.com/React-ATL/) +* [Austin, TX - ReactJS](https://www.meetup.com/ReactJS-Austin-Meetup/) +* [Boston, MA - ReactJS](https://www.meetup.com/ReactJS-Boston/) +* [Boston, MA - React Native](https://www.meetup.com/Boston-React-Native-Meetup/) +* [Charlotte, NC - ReactJS](https://www.meetup.com/ReactJS-Charlotte/) +* [Charlotte, NC - React Native](https://www.meetup.com/cltreactnative/) +* [Chicago, IL - ReactJS](https://www.meetup.com/React-Chicago/) +* [Cleveland, OH - ReactJS](https://www.meetup.com/Cleveland-React/) +* [Columbus, OH - ReactJS](https://www.meetup.com/ReactJS-Columbus-meetup/) +* [Dallas, TX - ReactJS](https://www.meetup.com/ReactDallas/) +* [Dallas, TX - [Remote] React JS](https://www.meetup.com/React-JS-Group/) +* [Detroit, MI - Detroit React User Group](https://www.meetup.com/Detroit-React-User-Group/) +* [Indianapolis, IN - React.Indy](https://www.meetup.com/React-Indy) +* [Irvine, CA - ReactJS](https://www.meetup.com/ReactJS-OC/) +* [Kansas City, MO - ReactJS](https://www.meetup.com/Kansas-City-React-Meetup/) +* [Las Vegas, NV - ReactJS](https://www.meetup.com/ReactVegas/) +* [Leesburg, VA - ReactJS](https://www.meetup.com/React-NOVA/) +* [Los Angeles, CA - ReactJS](https://www.meetup.com/socal-react/) +* [Los Angeles, CA - React Native](https://www.meetup.com/React-Native-Los-Angeles/) +* [Miami, FL - ReactJS](https://www.meetup.com/React-Miami/) +* [Nashville, TN - ReactJS](https://www.meetup.com/NashReact-Meetup/) +* [New York, NY - ReactJS](https://www.meetup.com/NYC-Javascript-React-Group/) +* [New York, NY - React Ladies](https://www.meetup.com/React-Ladies/) +* [New York, NY - React Native](https://www.meetup.com/React-Native-NYC/) +* [New York, NY - useReactNYC](https://www.meetup.com/useReactNYC/) +* [Omaha, NE - ReactJS/React Native](https://www.meetup.com/omaha-react-meetup-group/) +* [Palo Alto, CA - React Native](https://www.meetup.com/React-Native-Silicon-Valley/) +* [Philadelphia, PA - ReactJS](https://www.meetup.com/Reactadelphia/) +* [Phoenix, AZ - ReactJS](https://www.meetup.com/ReactJS-Phoenix/) +* [Pittsburgh, PA - ReactJS/React Native](https://www.meetup.com/ReactPgh/) +* [Portland, OR - ReactJS](https://www.meetup.com/Portland-ReactJS/) +* [Provo, UT - ReactJS](https://www.meetup.com/ReactJS-Utah/) +* [Sacramento, CA - ReactJS](https://www.meetup.com/Sacramento-ReactJS-Meetup/) +* [San Diego, CA - San Diego JS](https://www.meetup.com/sandiegojs/) +* [San Francisco - Real World React](https://www.meetup.com/Real-World-React) +* [San Francisco - ReactJS](https://www.meetup.com/ReactJS-San-Francisco/) +* [San Francisco, CA - React Native](https://www.meetup.com/React-Native-San-Francisco/) +* [San Ramon, CA - TriValley Coders](https://www.meetup.com/trivalleycoders/) +* [Santa Monica, CA - ReactJS](https://www.meetup.com/Los-Angeles-ReactJS-User-Group/) +* [Seattle, WA - React Native](https://www.meetup.com/Seattle-React-Native-Meetup/) +* [Seattle, WA - ReactJS](https://www.meetup.com/seattle-react-js/) +* [Tampa, FL - ReactJS](https://www.meetup.com/ReactJS-Tampa-Bay/) +* [Tucson, AZ - ReactJS](https://www.meetup.com/Tucson-ReactJS-Meetup/) +* [Washington, DC - ReactJS](https://www.meetup.com/React-DC/) diff --git a/beta/src/content/community/team.md b/beta/src/content/community/team.md new file mode 100644 index 000000000..144f0eea4 --- /dev/null +++ b/beta/src/content/community/team.md @@ -0,0 +1,87 @@ +--- +title: "Meet the Team" +--- + +<Intro> + +React development is led by a dedicated team working full time at Meta. It also receives contributions from people all over the world. + +</Intro> + +## React Core {/*react-core*/} + +The React Core team members work full time on the core component APIs, the engine that powers React DOM and React Native, React DevTools, and the React documentation website. + +Current members of the React team are listed in alphabetical order below. + +<TeamMember name="Andrew Clark" permalink="andrew-clark" photo="/images/team/acdlite.jpg" github="acdlite" twitter="acdlite" title="Engineer at Meta"> + Andrew got started with web development by making sites with WordPress, and eventually tricked himself into doing JavaScript. His favorite pastime is karaoke. Andrew is either a Disney villain or a Disney princess, depending on the day. +</TeamMember> + +<TeamMember name="Dan Abramov" permalink="dan-abramov" photo="/images/team/gaearon.jpg" github="gaearon" twitter="dan_abramov" title="Engineer at Meta"> + Dan got into programming after he accidentally discovered Visual Basic inside Microsoft PowerPoint. He has found his true calling in turning [Sebastian](#sebastian-markbåge)'s tweets into long-form blog posts. Dan occasionally wins at Fortnite by hiding in a bush until the game ends. +</TeamMember> + +<TeamMember name="Jason Bonta" permalink="jason-bonta" photo="/images/team/jasonbonta.jpg" title="Engineering Manager at Meta"> + Jason likes having large volumes of Amazon packages delivered to the office so that he can build forts. Despite literally walling himself off from his team at times and not understanding how for-of loops work, we appreciate him for the unique qualities he brings to his work. +</TeamMember> + +<TeamMember name="Joe Savona" permalink="joe-savona" photo="/images/team/joe.jpg" github="josephsavona" twitter="en_JS" title="Engineer at Meta"> + Joe was planning to major in math and philosophy but got into computer science after writing physics simulations in Matlab. Prior to React, he worked on Relay, RSocket.js, and the Skip programming language. While he’s not building some sort of reactive system he enjoys running, studying Japanese, and spending time with his family. +</TeamMember> + +<TeamMember name="Josh Story" permalink="josh-story" photo="/images/team/josh.jpg" github="gnoff" twitter="joshcstory" title="Engineer at Vercel"> + Josh majored in Mathematics and discovered programming while in college. His first professional developer job was to program insurance rate calculations in Microsoft Excel, the paragon of Reactive Programming which must be why he now works on React. In between that time Josh has been an IC, Manager, and Executive at a few startups. outside of work he likes to push his limits with cooking. +</TeamMember> + +<TeamMember name="Lauren Tan" permalink="lauren-tan" photo="/images/team/lauren.jpg" github="poteto" twitter="potetotes" personal="no.lol" title="Engineer at Meta"> + Lauren’s programming career peaked when she first discovered the `<marquee>` tag. She’s been chasing that high ever since. When she’s not adding bugs into React, she enjoys dropping cheeky memes in chat, and playing all too many video games with her partner, and her dog Zelda. +</TeamMember> + +<TeamMember name="Luna Ruan" permalink="luna-ruan" photo="/images/team/lunaruan.jpg" github="lunaruan" twitter="lunaruan" title="Engineer at Meta"> + Luna learned programming because she thought it meant creating video games. Instead, she ended up working on the Pinterest web app, and now on React itself. Luna doesn't want to make video games anymore, but she plans to do creative writing if she ever gets bored. +</TeamMember> + +<TeamMember name="Mofei Zhang" permalink="mofei-zhang" photo="/images/team/mofei-zhang.png" github="mofeiZ" title="Engineer at Meta"> + Mofei started programming when she realized it can help her cheat in video games. She focused on operating systems in undergrad / grad school, but now finds herself happily tinkering on React. Outside of work, she enjoys debugging bouldering problems and planning her next backpacking trip(s). +</TeamMember> + +<TeamMember name="Rick Hanlon" permalink="rick-hanlon" photo="/images/team/rickhanlonii.jpg" github="rickhanlonii" twitter="rickhanlonii" personal="rickhanlon.codes" title="Engineer at Meta"> + Ricky majored in theoretical math and somehow found himself on the React Native team for a couple years before joining the React team. When he's not programming you can find him snowboarding, biking, climbing, golfing, or closing GitHub issues that do not match the issue template. +</TeamMember> + +<TeamMember name="Samuel Susla" permalink="samuel-susla" photo="/images/team/sam.jpg" github="sammy-SC" twitter="SamuelSusla" title="Engineer at Meta"> + Samuel’s interest in programming started with the movie Matrix. He still has Matrix screen saver. Before working on React, he was focused on writing iOS apps. Outside of work, Samuel enjoys playing beach volleyball, squash, badminton and spending time with his family. +</TeamMember> + +<TeamMember name="Sathya Gunasekaran " permalink="sathya-gunasekaran" photo="/images/team/sathya.jpg" github="gsathya" twitter="_gsathya" title="Engineer at Meta"> + Sathya hated the Dragon Book in school but somehow ended up working on compilers all his career. When he's not compiling React components, he's either drinking coffee or eating yet another Dosa. +</TeamMember> + +<TeamMember name="Sebastian Markbåge" permalink="sebastian-markbåge" photo="/images/team/sebmarkbage.jpg" github="sebmarkbage" twitter="sebmarkbage" title="Engineer at Vercel"> + Sebastian majored in psychology. He's usually quiet. Even when he says something, it often doesn't make sense to the rest of us until a few months later. The correct way to pronounce his surname is "mark-boa-geh" but he settled for "mark-beige" out of pragmatism -- and that's how he approaches React. +</TeamMember> + +<TeamMember name="Sebastian Silbermann" permalink="sebastian-silbermann" photo="/images/team/sebsilbermann.jpg" github="eps1lon" twitter="sebsilbermann" title="Independent Engineer"> + Sebastian learned programming to make the browser games he played during class more enjoyable. Eventually this lead to contributing to as much open source code as possible. Outside of coding he's busy making sure people don't confuse him with the other Sebastians and Zilberman of the React community. +</TeamMember> + +<TeamMember name="Seth Webster" permalink="seth-webster" photo="/images/team/seth.jpg" github="sethwebster" twitter="sethwebster" personal="sethwebster.com" title="Engineering Manager at Meta"> + Seth started programming as a kid growing up in Tucson, AZ. After school, he was bitten by the music bug and was a touring musician for about 10 years before returning to *work*, starting with Intuit. In his spare time, he loves [taking pictures](https://www.sethwebster.com) and flying for animal rescues in the northeastern United States. +</TeamMember> + +<TeamMember name="Sophie Alpert" permalink="sophie-alpert" photo="/images/team/sophiebits.jpg" github="sophiebits" twitter="sophiebits" personal="sophiebits.com" title="Independent Engineer"> + Four days after React was released, Sophie rewrote the entirety of her then-current project to use it, which she now realizes was perhaps a bit reckless. After she became the project's #1 committer, she wondered why she wasn't getting paid by Facebook like everyone else was and joined the team officially to lead React through its adolescent years. Though she quit that job years ago, somehow she's still in the team's group chats and “providing value”. +</TeamMember> + +<TeamMember name="Tianyu Yao" permalink="tianyu-yao" photo="/images/team/tianyu.jpg" github="tyao1" twitter="tianyu0" title="Engineer at Meta"> + Tianyu’s interest in computers started as a kid because he loves video games. So he majored in computer science and still plays childish games like League of Legends. When he is not in front of a computer, he enjoys playing with his two kittens, hiking and kayaking. +</TeamMember> + +<TeamMember name="Yuzhi Zheng" permalink="yuzhi-zheng" photo="/images/team/yuzhi.jpg" github="yuzhi" twitter="yuzhiz" title="Engineering Manager at Meta"> + Yuzhi studied Computer Science in school. She liked the instant gratification of seeing code come to life without having to physically be in a laboratory. Now she’s a manager in the React org. Before management, she used to work on the Relay data fetching framework. In her spare time, Yuzhi enjoys optimizing her life via gardening and home improvement projects. +</TeamMember> + +## Past contributors {/*past-contributors*/} + +You can find the past team members and other people who significantly contributed to React over the years on the [acknowledgements](/community/acknowledgements) page. diff --git a/beta/src/content/community/versioning-policy.md b/beta/src/content/community/versioning-policy.md new file mode 100644 index 000000000..7f2bc12eb --- /dev/null +++ b/beta/src/content/community/versioning-policy.md @@ -0,0 +1,160 @@ +--- +title: Versioning Policy +--- + +<Intro> + +All stable builds of React go through a high level of testing and follow semantic versioning (semver). React also offers unstable release channels to encourage early feedback on experimental features. This page describes what you can expect from React releases. + +</Intro> + +## Stable releases {/*stable-releases*/} + +Stable React releases (also known as "Latest" release channel) follow [semantic versioning (semver)](https://semver.org/) principles. + +That means that with a version number **x.y.z**: + +* When releasing **critical bug fixes**, we make a **patch release** by changing the **z** number (ex: 15.6.2 to 15.6.3). +* When releasing **new features** or **non-critical fixes**, we make a **minor release** by changing the **y** number (ex: 15.6.2 to 15.7.0). +* When releasing **breaking changes**, we make a **major release** by changing the **x** number (ex: 15.6.2 to 16.0.0). + +Major releases can also contain new features, and any release can include bug fixes. + +Minor releases are the most common type of release. + +### Breaking Changes {/*breaking-changes*/} + +Breaking changes are inconvenient for everyone, so we try to minimize the number of major releases – for example, React 15 was released in April 2016 and React 16 was released in September 2017, and React 17 was released in October 2020. + +Instead, we release new features in minor versions. That means that minor releases are often more interesting and compelling than majors, despite their unassuming name. + +### Commitment to stability {/*commitment-to-stability*/} + +As we change React over time, we try to minimize the effort required to take advantage of new features. When possible, we'll keep an older API working, even if that means putting it in a separate package. For example, [mixins have been discouraged for years](/blog/2016/07/13/mixins-considered-harmful.html) but they're supported to this day [via create-react-class](/docs/react-without-es6.html#mixins) and many codebases continue to use them in stable, legacy code. + +Over a million developers use React, collectively maintaining millions of components. The Facebook codebase alone has over 50,000 React components. That means we need to make it as easy as possible to upgrade to new versions of React; if we make large changes without a migration path, people will be stuck on old versions. We test these upgrade paths on Facebook itself – if our team of less than 10 people can update 50,000+ components alone, we hope the upgrade will be manageable for anyone using React. In many cases, we write [automated scripts](https://github.com/reactjs/react-codemod) to upgrade component syntax, which we then include in the open-source release for everyone to use. + +### Gradual upgrades via warnings {/*gradual-upgrades-via-warnings*/} + +Development builds of React include many helpful warnings. Whenever possible, we add warnings in preparation for future breaking changes. That way, if your app has no warnings on the latest release, it will be compatible with the next major release. This allows you to upgrade your apps one component at a time. + +Development warnings won't affect the runtime behavior of your app. That way, you can feel confident that your app will behave the same way between the development and production builds -- the only differences are that the production build won't log the warnings and that it is more efficient. (If you ever notice otherwise, please file an issue.) + +### What counts as a breaking change? {/*what-counts-as-a-breaking-change*/} + +In general, we *don't* bump the major version number for changes to: + +* **Development warnings.** Since these don't affect production behavior, we may add new warnings or modify existing warnings in between major versions. In fact, this is what allows us to reliably warn about upcoming breaking changes. +* **APIs starting with `unstable_`.** These are provided as experimental features whose APIs we are not yet confident in. By releasing these with an `unstable_` prefix, we can iterate faster and get to a stable API sooner. +* **Alpha and canary versions of React.** We provide alpha versions of React as a way to test new features early, but we need the flexibility to make changes based on what we learn in the alpha period. If you use these versions, note that APIs may change before the stable release. +* **Undocumented APIs and internal data structures.** If you access internal property names like `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED` or `__reactInternalInstance$uk43rzhitjg`, there is no warranty. You are on your own. + +This policy is designed to be pragmatic: certainly, we don't want to cause headaches for you. If we bumped the major version for all of these changes, we would end up releasing more major versions and ultimately causing more versioning pain for the community. It would also mean that we can't make progress in improving React as fast as we'd like. + +That said, if we expect that a change on this list will cause broad problems in the community, we will still do our best to provide a gradual migration path. + +### If a minor release includes no new features, why isn't it a patch? {/*if-a-minor-release-includes-no-new-features-why-isnt-it-a-patch*/} + +It's possible that a minor release will not include new features. [This is allowed by semver](https://semver.org/#spec-item-7), which states **"[a minor version] MAY be incremented if substantial new functionality or improvements are introduced within the private code. It MAY include patch level changes."** + +However, it does raise the question of why these releases aren't versioned as patches instead. + +The answer is that any change to React (or other software) carries some risk of breaking in unexpected ways. Imagine a scenario where a patch release that fixes one bug accidentally introduces a different bug. This would not only be disruptive to developers, but also harm their confidence in future patch releases. It's especially regrettable if the original fix is for a bug that is rarely encountered in practice. + +We have a pretty good track record for keeping React releases free of bugs, but patch releases have an even higher bar for reliability because most developers assume they can be adopted without adverse consequences. + +For these reasons, we reserve patch releases only for the most critical bugs and security vulnerabilities. + +If a release includes non-essential changes — such as internal refactors, changes to implementation details, performance improvements, or minor bugfixes — we will bump the minor version even when there are no new features. + +## All release channels {/*all-release-channels*/} + +React relies on a thriving open source community to file bug reports, open pull requests, and [submit RFCs](https://github.com/reactjs/rfcs). To encourage feedback we sometimes share special builds of React that include unreleased features. + +<Note> + +This section will be most relevant to developers who work on frameworks, libraries, or developer tooling. Developers who use React primarily to build user-facing applications should not need to worry about our prerelease channels. + +</Note> + +Each of React's release channels is designed for a distinct use case: + +- [**Latest**](#latest-channel) is for stable, semver React releases. It's what you get when you install React from npm. This is the channel you're already using today. **Use this for all user-facing React applications.** +- [**Next**](#next-channel) tracks the main branch of the React source code repository. Think of these as release candidates for the next minor semver release. Use this for integration testing between React and third party projects. +- [**Experimental**](#experimental-channel) includes experimental APIs and features that aren't available in the stable releases. These also track the main branch, but with additional feature flags turned on. Use this to try out upcoming features before they are released. + +All releases are published to npm, but only Latest uses semantic versioning. Prereleases (those in the Next and Experimental channels) have versions generated from a hash of their contents and the commit date, e.g. `0.0.0-68053d940-20210623` for Next and `0.0.0-experimental-68053d940-20210623` for Experimental. + +**The only officially supported release channel for user-facing applications is Latest**. Next and Experimental releases are provided for testing purposes only, and we provide no guarantees that behavior won't change between releases. They do not follow the semver protocol that we use for releases from Latest. + +By publishing prereleases to the same registry that we use for stable releases, we are able to take advantage of the many tools that support the npm workflow, like [unpkg](https://unpkg.com) and [CodeSandbox](https://codesandbox.io). + +### Latest channel {/*latest-channel*/} + +Latest is the channel used for stable React releases. It corresponds to the `latest` tag on npm. It is the recommended channel for all React apps that are shipped to real users. + +**If you're not sure which channel you should use, it's Latest.** If you're a React developer, this is what you're already using. You can expect updates to Latest to be extremely stable. Versions follow the semantic versioning scheme, as [described earlier.](#stable-releases) + +### Next channel {/*next-channel*/} + +The Next channel is a prerelease channel that tracks the main branch of the React repository. We use prereleases in the Next channel as release candidates for the Latest channel. You can think of Next as a superset of Latest that is updated more frequently. + +The degree of change between the most recent Next release and the most recent Latest release is approximately the same as you would find between two minor semver releases. However, **the Next channel does not conform to semantic versioning.** You should expect occasional breaking changes between successive releases in the Next channel. + +**Do not use prereleases in user-facing applications.** + +Releases in Next are published with the `next` tag on npm. Versions are generated from a hash of the build's contents and the commit date, e.g. `0.0.0-68053d940-20210623`. + +#### Using the next channel for integration testing {/*using-the-next-channel-for-integration-testing*/} + +The Next channel is designed to support integration testing between React and other projects. + +All changes to React go through extensive internal testing before they are released to the public. However, there are a myriad of environments and configurations used throughout the React ecosystem, and it's not possible for us to test against every single one. + +If you're the author of a third party React framework, library, developer tool, or similar infrastructure-type project, you can help us keep React stable for your users and the entire React community by periodically running your test suite against the most recent changes. If you're interested, follow these steps: + +- Set up a cron job using your preferred continuous integration platform. Cron jobs are supported by both [CircleCI](https://circleci.com/docs/2.0/triggers/#scheduled-builds) and [Travis CI](https://docs.travis-ci.com/user/cron-jobs/). +- In the cron job, update your React packages to the most recent React release in the Next channel, using `next` tag on npm. Using the npm cli: + + ```console + npm update react@next react-dom@next + ``` + + Or yarn: + + ```console + yarn upgrade react@next react-dom@next + ``` +- Run your test suite against the updated packages. +- If everything passes, great! You can expect that your project will work with the next minor React release. +- If something breaks unexpectedly, please let us know by [filing an issue](https://github.com/facebook/react/issues). + +A project that uses this workflow is Next.js. (No pun intended! Seriously!) You can refer to their [CircleCI configuration](https://github.com/zeit/next.js/blob/c0a1c0f93966fe33edd93fb53e5fafb0dcd80a9e/.circleci/config.yml) as an example. + +### Experimental channel {/*experimental-channel*/} + +Like Next, the Experimental channel is a prerelease channel that tracks the main branch of the React repository. Unlike Next, Experimental releases include additional features and APIs that are not ready for wider release. + +Usually, an update to Next is accompanied by a corresponding update to Experimental. They are based on the same source revision, but are built using a different set of feature flags. + +Experimental releases may be significantly different than releases to Next and Latest. **Do not use Experimental releases in user-facing applications.** You should expect frequent breaking changes between releases in the Experimental channel. + +Releases in Experimental are published with the `experimental` tag on npm. Versions are generated from a hash of the build's contents and the commit date, e.g. `0.0.0-experimental-68053d940-20210623`. + +#### What goes into an experimental release? {/*what-goes-into-an-experimental-release*/} + +Experimental features are ones that are not ready to be released to the wider public, and may change drastically before they are finalized. Some experiments may never be finalized -- the reason we have experiments is to test the viability of proposed changes. + +For example, if the Experimental channel had existed when we announced Hooks, we would have released Hooks to the Experimental channel weeks before they were available in Latest. + +You may find it valuable to run integration tests against Experimental. This is up to you. However, be advised that Experimental is even less stable than Next. **We do not guarantee any stability between Experimental releases.** + +#### How can I learn more about experimental features? {/*how-can-i-learn-more-about-experimental-features*/} + +Experimental features may or may not be documented. Usually, experiments aren't documented until they are close to shipping in Next or Latest. + +If a feature is not documented, they may be accompanied by an [RFC](https://github.com/reactjs/rfcs). + +We will post to the [React blog](/blog) when we're ready to announce new experiments, but that doesn't mean we will publicize every experiment. + +You can always refer to our public GitHub repository's [history](https://github.com/facebook/react/commits/main) for a comprehensive list of changes. diff --git a/beta/src/content/community/videos.md b/beta/src/content/community/videos.md new file mode 100644 index 000000000..379bb09e2 --- /dev/null +++ b/beta/src/content/community/videos.md @@ -0,0 +1,123 @@ +--- +title: React Videos +--- + +<Intro> + +Videos dedicated to the discussion of React and the React ecosystem. + +</Intro> + +## React Conf 2021 {/*react-conf-2021*/} + +### React 18 Keynote {/*react-18-keynote*/} + +In the keynote, we shared our vision for the future of React starting with React 18. + +Watch the full keynote from [Andrew Clark](https://twitter.com/acdlite), [Juan Tejada](https://twitter.com/_jstejada), [Lauren Tan](https://twitter.com/potetotes), and [Rick Hanlon](https://twitter.com/rickhanlonii) here: + +<YouTubeIframe src="https://www.youtube.com/embed/FZ0cG47msEk" title="YouTube video player" /> + +### React 18 for Application Developers {/*react-18-for-application-developers*/} + +For a demo of upgrading to React 18, see [Shruti Kapoor](https://twitter.com/shrutikapoor08)’s talk here: + +<YouTubeIframe src="https://www.youtube.com/embed/ytudH8je5ko" title="YouTube video player" /> + +### Streaming Server Rendering with Suspense {/*streaming-server-rendering-with-suspense*/} + +React 18 also includes improvements to server-side rendering performance using Suspense. + +Streaming server rendering lets you generate HTML from React components on the server, and stream that HTML to your users. In React 18, you can use `Suspense` to break down your app into smaller independent units which can be streamed independently of each other without blocking the rest of the app. This means users will see your content sooner and be able to start interacting with it much faster. + +For a deep dive, see [Shaundai Person](https://twitter.com/shaundai)’s talk here: + +<YouTubeIframe src="https://www.youtube.com/embed/pj5N-Khihgc" title="YouTube video player" /> + +### The first React working group {/*the-first-react-working-group*/} + +For React 18, we created our first Working Group to collaborate with a panel of experts, developers, library maintainers, and educators. Together we worked to create our gradual adoption strategy and refine new APIs such as `useId`, `useSyncExternalStore`, and `useInsertionEffect`. + +For an overview of this work, see [Aakansha' Doshi](https://twitter.com/aakansha1216)'s talk: + +<YouTubeIframe src="https://www.youtube.com/embed/qn7gRClrC9U" title="YouTube video player" /> + +### React Developer Tooling {/*react-developer-tooling*/} + +To support the new features in this release, we also announced the newly formed React DevTools team and a new Timeline Profiler to help developers debug their React apps. + +For more information and a demo of new DevTools features, see [Brian Vaughn](https://twitter.com/brian_d_vaughn)’s talk: + +<YouTubeIframe src="https://www.youtube.com/embed/oxDfrke8rZg" title="YouTube video player" /> + +### React without memo {/*react-without-memo*/} + +Looking further into the future, [Xuan Huang (黄玄)](https://twitter.com/Huxpro) shared an update from our React Labs research into an auto-memoizing compiler. Check out this talk for more information and a demo of the compiler prototype: + +<YouTubeIframe src="https://www.youtube.com/embed/lGEMwh32soc" title="YouTube video player" /> + +### React docs keynote {/*react-docs-keynote*/} + +[Rachel Nabors](https://twitter.com/rachelnabors) kicked off a section of talks about learning and designing with React with a keynote about our investment in React's [new docs](https://beta.reactjs.org/): + +<YouTubeIframe src="https://www.youtube.com/embed/mneDaMYOKP8" title="YouTube video player" /> + +### And more... {/*and-more*/} + +**We also heard talks on learning and designing with React:** + +* Debbie O'Brien: [Things I learnt from the new React docs](https://youtu.be/-7odLW_hG7s). +* Sarah Rainsberger: [Learning in the Browser](https://youtu.be/5X-WEQflCL0). +* Linton Ye: [The ROI of Designing with React](https://youtu.be/7cPWmID5XAk). +* Delba de Oliveira: [Interactive playgrounds with React](https://youtu.be/zL8cz2W0z34). + +**Talks from the Relay, React Native, and PyTorch teams:** + +* Robert Balicki: [Re-introducing Relay](https://youtu.be/lhVGdErZuN4). +* Eric Rozell and Steven Moyes: [React Native Desktop](https://youtu.be/9L4FFrvwJwY). +* Roman Rädle: [On-device Machine Learning for React Native](https://youtu.be/NLj73vrc2I8) + +**And talks from the community on accessibility, tooling, and Server Components:** + +* Daishi Kato: [React 18 for External Store Libraries](https://youtu.be/oPfSC5bQPR8). +* Diego Haz: [Building Accessible Components in React 18](https://youtu.be/dcm8fjBfro8). +* Tafu Nakazaki: [Accessible Japanese Form Components with React](https://youtu.be/S4a0QlsH0pU). +* Lyle Troxell: [UI tools for artists](https://youtu.be/b3l4WxipFsE). +* Helen Lin: [Hydrogen + React 18](https://youtu.be/HS6vIYkSNks). + +## Older videos {/*older-videos*/} + +### React Conf 2019 {/*react-conf-2019*/} + +A playlist of videos from React Conf 2019. +<YouTubeIframe title="React Conf 2019" src="https://www.youtube-nocookie.com/embed/playlist?list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh" /> + +### React Conf 2018 {/*react-conf-2018*/} + +A playlist of videos from React Conf 2018. +<YouTubeIframe title="React Conf 2018" src="https://www.youtube-nocookie.com/embed/playlist?list=PLPxbbTqCLbGE5AihOSExAa4wUM-P42EIJ" /> + +### React.js Conf 2017 {/*reactjs-conf-2017*/} + +A playlist of videos from React.js Conf 2017. +<YouTubeIframe title="React.js Conf 2017" src="https://www.youtube-nocookie.com/embed/playlist?list=PLb0IAmt7-GS3fZ46IGFirdqKTIxlws7e0" /> + +### React.js Conf 2016 {/*reactjs-conf-2016*/} + +A playlist of videos from React.js Conf 2016. +<YouTubeIframe title="React.js Conf 2016" src="https://www.youtube-nocookie.com/embed/playlist?list=PLb0IAmt7-GS0M8Q95RIc2lOM6nc77q1IY" /> + +### React.js Conf 2015 {/*reactjs-conf-2015*/} + +A playlist of videos from React.js Conf 2015. +<YouTubeIframe title="React.js Conf 2015" src="https://www.youtube-nocookie.com/embed/playlist?list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr" /> + +### Rethinking Best Practices {/*rethinking-best-practices*/} + +Pete Hunt's talk at JSConf EU 2013 covers three topics: throwing out the notion of templates and building views with JavaScript, “re-rendering” your entire application when your data changes, and a lightweight implementation of the DOM and events - (2013 - 0h30m). +<YouTubeIframe title="Pete Hunt: React: Rethinking Best Practices - JSConf EU 2013" src="https://www.youtube-nocookie.com/embed/x7cQ3mrcKaY" /> + +### Introduction to React {/*introduction-to-react*/} + +Tom Occhino and Jordan Walke introduce React at Facebook Seattle - (2013 - 1h20m). +<YouTubeIframe title="Tom Occhino and Jordan Walke introduce React at Facebook Seattle" src="https://www.youtube-nocookie.com/embed/XxVg_s8xAms" /> diff --git a/beta/src/content/index.md b/beta/src/content/index.md new file mode 100644 index 000000000..78c663c02 --- /dev/null +++ b/beta/src/content/index.md @@ -0,0 +1,32 @@ +--- +id: home +title: React Docs Beta +permalink: index.html +--- + +<HomepageHero /> + +## What is this site? {/*what-is-this-site*/} + +We are rewriting the React documentation with a few differences: + +- All explanations are **written using Hooks** rather than classes. +- We've added **interactive examples** and visual diagrams. +- Guides include **challenges (with solutions!)** to check your understanding. + +This beta website contains the current draft of the new docs. + +## How much content is ready? {/*how-much-content-is-ready*/} + +* [Learn React](/learn): ~99% finished. +* [API Reference](/reference/react): 100% finished. + +You can track our progress [on GitHub.](https://github.com/reactjs/reactjs.org/issues/3308) + +## How can I provide feedback? {/*how-can-i-provide-feedback*/} + +Please use [this GitHub issue](https://github.com/reactjs/reactjs.org/issues/3308) or [this anonymous form](https://www.surveymonkey.co.uk/r/PYRPF3X) for high-level feedback. If you spot something that doesn't make sense, please tell us! Additionally, each page has thumbs up/down buttons in the corner. + +## Will this site replace the main site? {/*will-this-site-replace-the-main-site*/} + +We aim to switch this site to be the main one once we reach content parity with the [existing React documentation.](https://reactjs.org/) The old React website will be archived at a subdomain so you'll still be able to access it. Old content links will redirect to the archived subdomain, which will have a notice about outdated content. \ No newline at end of file diff --git a/beta/src/content/learn/add-react-to-a-website.md b/beta/src/content/learn/add-react-to-a-website.md new file mode 100644 index 000000000..0f496882e --- /dev/null +++ b/beta/src/content/learn/add-react-to-a-website.md @@ -0,0 +1,277 @@ +--- +title: Add React to a Website +--- + +<Intro> + +You don't have to build your whole website with React. Adding React to HTML doesn't require installation, takes a minute, and lets you start writing interactive components right away. + +</Intro> + +<YouWillLearn> + +* How to add React to an HTML page in one minute +* What is the JSX syntax and how to quickly try it +* How to set up a JSX preprocessor for production + +</YouWillLearn> + +## Add React in one minute {/*add-react-in-one-minute*/} + +React has been designed from the start for gradual adoption. Most websites aren't (and don't need to be) fully built with React. This guide shows how to add some “sprinkles of interactivity” to an existing HTML page. + +Try this out with your own website or [an empty HTML file.](https://gist.github.com/gaearon/edf814aeee85062bc9b9830aeaf27b88/archive/3b31c3cdcea7dfcfd38a81905a0052dd8e5f71ec.zip) All you need is an internet connection and a text editor like Notepad or VSCode. (Here's [how to configure your editor](/learn/editor-setup/) for syntax highlighting!) + +### Step 1: Add a root HTML tag {/*step-1-add-a-root-html-tag*/} + +First, open the HTML page you want to edit. Add an empty `<div>` tag to mark the spot where you want to display something with React. Give this `<div>` a unique `id` attribute value. For example: + +```html {3} +<!-- ... existing HTML ... --> + +<div id="like-button-root"></div> + +<!-- ... existing HTML ... --> +``` + +It's called a "root" because it's where the React tree will start. You can place a root HTML tag like this anywhere inside the `<body>` tag. Leave it empty because React will replace its contents with your React component. + +You may have as many root HTML tags as you need on one page. + +### Step 2: Add the script tags {/*step-2-add-the-script-tags*/} + +In the HTML page, right before the closing `</body>` tag, add three `<script>` tags for the following files: + +- [`react.development.js`](https://unpkg.com/react@18/umd/react.development.js) lets you define React components. +- [`react-dom.development.js`](https://unpkg.com/react-dom@18/umd/react-dom.development.js) lets React render HTML elements to the [DOM.](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) +- **`like-button.js`** is where you'll write your component in the next step! + +Your HTML should now end like this: + +```html + <!-- end of the page --> + <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> + <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script> + <script src="like-button.js"></script> + </body> +</html> +``` + +<Pitfall> + +Before deploying to a live website, make sure to replace `development.js` with `production.min.js`! Development builds of React provide more helpful error messages, but slow down your website *a lot.* + +</Pitfall> + +### Step 3: Create a React component {/*step-3-create-a-react-component*/} + +Create a file called **`like-button.js`** next to your HTML page, add this code snippet, and save the file. This code defines a React component called `LikeButton`. (Learn more about making components in the [Quick Start!](/learn)) + +```js +'use strict'; + +function LikeButton() { + const [liked, setLiked] = React.useState(false); + + if (liked) { + return 'You liked this!'; + } + + return React.createElement( + 'button', + { + onClick: () => setLiked(true), + }, + 'Like' + ); +} +``` + +### Step 4: Add your React component to the page {/*step-4-add-your-react-component-to-the-page*/} + +Lastly, add three lines to the bottom of **`like-button.js`.** These lines of code find the `<div>` you added to the HTML in the first step, create a React root, and then display the "Like" button React component inside of it: + +```js +const rootNode = document.getElementById('like-button-root'); +const root = ReactDOM.createRoot(rootNode); +root.render(React.createElement(LikeButton)); +``` + +**Congratulations! You have just rendered your first React component to your website!** + +- [View the full example source code](https://gist.github.com/gaearon/0b535239e7f39c524f9c7dc77c44f09e) +- [Download the full example (2KB zipped)](https://gist.github.com/gaearon/0b535239e7f39c524f9c7dc77c44f09e/archive/651935b26a48ac68b2de032d874526f2d0896848.zip) + +#### You can reuse components! {/*you-can-reuse-components*/} + +You might want to display React components in multiple places on the same HTML page. This is useful if React-powered parts of your page are separate from each other. You can do this by putting multiple root tags in your HTML and then rendering React components inside each of them with `ReactDOM.createRoot()`. For example: + +1. In **`index.html`,** add an additional container element `<div id="another-root"></div>`. +2. In **`like-button.js`,** add three more lines at the end: + +```js {6,7,8,9} +const anotherRootNode = document.getElementById('another-root'); +const anotherRoot = ReactDOM.createRoot(anotherRootNode); +anotherRoot.render(React.createElement(LikeButton)); +``` + +If you need to render the same component in many places, you can assign a CSS `class` instead of `id` to each root, and then find them all. Here is [an example that displays three "Like" buttons and passes data to each.](https://gist.github.com/gaearon/779b12e05ffd5f51ffadd50b7ded5bc8) + +### Step 5: Minify JavaScript for production {/*step-5-minify-javascript-for-production*/} + +Unminified JavaScript can significantly slow down page load times for your users. Before deploying your website to production, it's a good idea to minify its scripts. + +- **If you don't have a minification step** for your scripts, [here's one way to set it up.](https://gist.github.com/gaearon/ee0201910608f15df3f8cd66aa83f98e) +- **If you already minify** your application scripts, your site will be production-ready if you ensure that the deployed HTML loads the versions of React ending in `production.min.js` like so: + +```html +<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script> +<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script> +``` + +## Try React with JSX {/*try-react-with-jsx*/} + +The examples above rely on features that are natively supported by browsers. This is why **`like-button.js`** uses a JavaScript function call to tell React what to display: + +```js +return React.createElement('button', {onClick: () => setLiked(true)}, 'Like'); +``` + +However, React also offers an option to use [JSX](/learn/writing-markup-with-jsx), an HTML-like JavaScript syntax, instead: + +```jsx +return <button onClick={() => setLiked(true)}>Like</button>; +``` + +These two code snippets are equivalent. JSX is popular syntax for describing markup in JavaScript. Many people find it familiar and helpful for writing UI code--both with React and with other libraries. + +> You can play with transforming HTML markup into JSX using [this online converter.](https://babeljs.io/en/repl#?babili=false&browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=DwIwrgLhD2B2AEcDCAbAlgYwNYF4DeAFAJTw4B88EAFmgM4B0tAphAMoQCGETBe86WJgBMAXJQBOYJvAC-RGWQBQ8FfAAyaQYuAB6cFDhkgA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.17) + +### Try JSX {/*try-jsx*/} + +The quickest way to try JSX is to add the Babel compiler as a `<script>` tag to the page. Put it before **`like-button.js`,** and then add `type="text/babel"` attribute to the `<script>` tag for **`like-button.js`**: + +```html {3,4} + <script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script> + <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script> + <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> + <script src="like-button.js" type="text/babel"></script> +</body> +``` + +Now you can open **`like-button.js`** and replace + +```js +return React.createElement( + 'button', + { + onClick: () => setLiked(true), + }, + 'Like' +); +``` + +with the equivalent JSX code: + +```jsx +return ( + <button onClick={() => setLiked(true)}> + Like + </button> +); +``` + +It may feel a bit unusual at first to mix JS with markup, but it will grow on you! Check out [Writing Markup in JSX](/learn/writing-markup-with-jsx) for an introduction. Here is [an example HTML file with JSX](https://raw.githubusercontent.com/reactjs/reactjs.org/main/static/html/single-file-example.html) that you can download and play with. + +<Pitfall> + +The Babel `<script>` compiler is fine for learning and creating simple demos. However, **it makes your website slow and isn't suitable for production.** When you're ready to move forward, remove the Babel `<script>` tag and remove the `type="text/babel"` attribute you've added in this step. Instead, in the next section you will set up a JSX preprocessor to convert all your `<script>` tags from JSX to JS. + +</Pitfall> + +### Add JSX to a project {/*add-jsx-to-a-project*/} + +Adding JSX to a project doesn't require complicated tools like a [bundler](/learn/start-a-new-react-project#custom-toolchains) or a development server. Adding a JSX preprocessor is a lot like adding a CSS preprocessor. + +Go to your project folder in the terminal, and paste these two commands (**Be sure you have [Node.js](https://nodejs.org/) installed!**): + +1. `npm init -y` (if it fails, [here's a fix](https://gist.github.com/gaearon/246f6380610e262f8a648e3e51cad40d)) +2. `npm install @babel/cli@7 babel-preset-react-app@10` + +You only need npm to install the JSX preprocessor. You won't need it for anything else. Both React and the application code can stay as `<script>` tags with no changes. + +Congratulations! You just added a **production-ready JSX setup** to your project. + +### Run the JSX Preprocessor {/*run-the-jsx-preprocessor*/} + +You can preprocess JSX so that every time you save a file with JSX in it, the transform will be re-run, converting the JSX file into a new, plain JavaScript file that the browser can understand. Here's how to set this up: + +1. Create a folder called **`src`.** +2. In your terminal, run this command: `npx babel --watch src --out-dir . --presets babel-preset-react-app/prod ` (Don't wait for it to finish! This command starts an automated watcher for edits to JSX inside `src`.) +3. Move your JSX-ified **`like-button.js`** ([it should look like this!](https://gist.githubusercontent.com/gaearon/be5ae0fbf563d6c5fe5c1563907b13d2/raw/4c0d0b8c7f4fcb341720424c28c72059f8174c62/like-button.js)) to the new **`src`** folder. + +The watcher will create a preprocessed **`like-button.js`** with the plain JavaScript code suitable for the browser. + +<Pitfall> + +If you see an error message saying "You have mistakenly installed the `babel` package", you might have missed [the previous step.](#add-jsx-to-a-project) Perform it in the same folder, and then try again. + +</Pitfall> + +The tool you just used is called Babel, and you can learn more about it from [its documentation.](https://babeljs.io/docs/en/babel-cli/) In addition to JSX, it lets you use the most recent JavaScript syntax features without worrying about breaking older browsers. + +If you're getting comfortable with build tools and want them to do more for you, [we cover some of the most popular and approachable toolchains here.](/learn/start-a-new-react-project) + +<DeepDive> + +#### React without JSX {/*react-without-jsx*/} + +Originally JSX was introduced to make writing components with React feel as familiar as writing HTML. Since then, the syntax has become widespread. However, there may be instances where you do not want to use or cannot use JSX. You have two options: + +- Use a JSX alternative like [htm](https://github.com/developit/htm) which uses JavaScript [template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) instead of a compiler. +- Use [`React.createElement()`](/reference/react/createElement) which has a special structure explained below. + +With JSX, you would write a component like so: + +```jsx +function Hello(props) { + return <div>Hello {props.toWhat}</div>; +} + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Hello toWhat="World" />, ); +``` + +With `React.createElement()`, you would write it like this: + +```js +function Hello(props) { + return React.createElement('div', null, 'Hello ', props.toWhat); +} + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + React.createElement(Hello, { toWhat: 'World' }, null) +); +``` + +It accepts several arguments: `React.createElement(component, props, ...children)`. + +Here's how they work: + +1. A **component,** which can be a string representing an HTML element or a function component +2. An object of any [**props** you want to pass](/learn/passing-props-to-a-component) +3. The rest are **children** the component might have, such as text strings or other elements + +If you get tired of typing `React.createElement()`, one common pattern is to assign a shorthand: + +```js +const e = React.createElement; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(e('div', null, 'Hello World')); +``` + +Then, if you prefer this style, it can be just as convenient as JSX. + +</DeepDive> diff --git a/beta/src/content/learn/adding-interactivity.md b/beta/src/content/learn/adding-interactivity.md new file mode 100644 index 000000000..bdf75623c --- /dev/null +++ b/beta/src/content/learn/adding-interactivity.md @@ -0,0 +1,797 @@ +--- +title: Adding Interactivity +--- + +<Intro> + +Some things on the screen update in response to user input. For example, clicking an image gallery switches the active image. In React, data that changes over time is called *state.* You can add state to any component, and update it as needed. In this chapter, you'll learn how to write components that handle interactions, update their state, and display different output over time. + +</Intro> + +<YouWillLearn isChapter={true}> + +* [How to handle user-initiated events](/learn/responding-to-events) +* [How to make components "remember" information with state](/learn/state-a-components-memory) +* [How React updates the UI in two phases](/learn/render-and-commit) +* [Why state doesn't update right after you change it](/learn/state-as-a-snapshot) +* [How to queue multiple state updates](/learn/queueing-a-series-of-state-updates) +* [How to update an object in state](/learn/updating-objects-in-state) +* [How to update an array in state](/learn/updating-arrays-in-state) + +</YouWillLearn> + +## Responding to events {/*responding-to-events*/} + +React lets you add *event handlers* to your JSX. Event handlers are your own functions that will be triggered in response to user interactions like clicking, hovering, focusing on form inputs, and so on. + +Built-in components like `<button>` only support built-in browser events like `onClick`. However, you can also create your own components, and give their event handler props any application-specific names that you like. + +<Sandpack> + +```js +export default function App() { + return ( + <Toolbar + onPlayMovie={() => alert('Playing!')} + onUploadImage={() => alert('Uploading!')} + /> + ); +} + +function Toolbar({ onPlayMovie, onUploadImage }) { + return ( + <div> + <Button onClick={onPlayMovie}> + Play Movie + </Button> + <Button onClick={onUploadImage}> + Upload Image + </Button> + </div> + ); +} + +function Button({ onClick, children }) { + return ( + <button onClick={onClick}> + {children} + </button> + ); +} +``` + +```css +button { margin-right: 10px; } +``` + +</Sandpack> + +<LearnMore path="/learn/responding-to-events"> + +Read **[Responding to Events](/learn/responding-to-events)** to learn how to add event handlers. + +</LearnMore> + +## State: a component's memory {/*state-a-components-memory*/} + +Components often need to change what's on the screen as a result of an interaction. Typing into the form should update the input field, clicking "next" on an image carousel should change which image is displayed, clicking "buy" puts a product in the shopping cart. Components need to "remember" things: the current input value, the current image, the shopping cart. In React, this kind of component-specific memory is called *state.* + +You can add state to a component with a [`useState`](/reference/react/useState) Hook. *Hooks* are special functions that let your components use React features (state is one of those features). The `useState` Hook lets you declare a state variable. It takes the initial state and returns a pair of values: the current state, and a state setter function that lets you update it. + +```js +const [index, setIndex] = useState(0); +const [showMore, setShowMore] = useState(false); +``` + +Here is how an image gallery uses and updates state on click: + +<Sandpack> + +```js +import { useState } from 'react'; +import { sculptureList } from './data.js'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + const [showMore, setShowMore] = useState(false); + + function handleNextClick() { + setIndex(index + 1); + } + + function handleMoreClick() { + setShowMore(!showMore); + } + + let sculpture = sculptureList[index]; + return ( + <> + <button onClick={handleNextClick}> + Next + </button> + <h2> + <i>{sculpture.name} </i> + by {sculpture.artist} + </h2> + <h3> + ({index + 1} of {sculptureList.length}) + </h3> + <button onClick={handleMoreClick}> + {showMore ? 'Hide' : 'Show'} details + </button> + {showMore && <p>{sculpture.description}</p>} + <img + src={sculpture.url} + alt={sculpture.alt} + /> + </> + ); +} +``` + +```js data.js +export const sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; +``` + +```css +h2 { margin-top: 10px; margin-bottom: 0; } +h3 { + margin-top: 5px; + font-weight: normal; + font-size: 100%; +} +img { width: 120px; height: 120px; } +button { + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +<LearnMore path="/learn/state-a-components-memory"> + +Read **[State: A Component's Memory](/learn/state-a-components-memory)** to learn how to remember a value and update it on interaction. + +</LearnMore> + +## Render and commit {/*render-and-commit*/} + +Before your components are displayed on the screen, they must be rendered by React. Understanding the steps in this process will help you think about how your code executes and explain its behavior. + +Imagine that your components are cooks in the kitchen, assembling tasty dishes from ingredients. In this scenario, React is the waiter who puts in requests from customers and brings them their orders. This process of requesting and serving UI has three steps: + +1. **Triggering** a render (delivering the diner's order to the kitchen) +2. **Rendering** the component (preparing the order in the kitchen) +3. **Committing** to the DOM (placing the order on the table) + +<IllustrationBlock sequential> + <Illustration caption="Trigger" alt="React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen." src="/images/docs/illustrations/i_render-and-commit1.png" /> + <Illustration caption="Render" alt="The Card Chef gives React a fresh Card component." src="/images/docs/illustrations/i_render-and-commit2.png" /> + <Illustration caption="Commit" alt="React delivers the Card to the user at their table." src="/images/docs/illustrations/i_render-and-commit3.png" /> +</IllustrationBlock> + +<LearnMore path="/learn/render-and-commit"> + +Read **[Render and Commit](/learn/render-and-commit)** to learn the lifecycle of a UI update. + +</LearnMore> + +## State as a snapshot {/*state-as-a-snapshot*/} + +Unlike regular JavaScript variables, React state behaves more like a snapshot. Setting it does not change the state variable you already have, but instead triggers a re-render. This can be surprising at first! + +```js +console.log(count); // 0 +setCount(count + 1); // Request a re-render with 1 +console.log(count); // Still 0! +``` + +React works this way to help you avoid subtle bugs. Here is a little chat app. Try to guess what happens if you press "Send" first and *then* change the recipient to Bob. Whose name will appear in the `alert` five seconds later? + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [to, setTo] = useState('Alice'); + const [message, setMessage] = useState('Hello'); + + function handleSubmit(e) { + e.preventDefault(); + setTimeout(() => { + alert(`You said ${message} to ${to}`); + }, 5000); + } + + return ( + <form onSubmit={handleSubmit}> + <label> + To:{' '} + <select + value={to} + onChange={e => setTo(e.target.value)}> + <option value="Alice">Alice</option> + <option value="Bob">Bob</option> + </select> + </label> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <button type="submit">Send</button> + </form> + ); +} +``` + +```css +label, textarea { margin-bottom: 10px; display: block; } +``` + +</Sandpack> + + +<LearnMore path="/learn/state-as-a-snapshot"> + +Read **[State as a Snapshot](/learn/state-as-a-snapshot)** to learn why state appears "fixed" and unchanging inside the event handlers. + +</LearnMore> + +## Queueing a series of state updates {/*queueing-a-series-of-state-updates*/} + +This component is buggy: clicking "+3" increments the score only once. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [score, setScore] = useState(0); + + function increment() { + setScore(score + 1); + } + + return ( + <> + <button onClick={() => increment()}>+1</button> + <button onClick={() => { + increment(); + increment(); + increment(); + }}>+3</button> + <h1>Score: {score}</h1> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +``` + +</Sandpack> + +[State as a Snapshot](/learn/state-as-a-snapshot) explains why this is happening. Setting state requests a new re-render, but does not change it in the already running code. So `score` continues to be `0` right after you call `setScore(score + 1)`. + +```js +console.log(score); // 0 +setScore(score + 1); // setScore(0 + 1); +console.log(score); // 0 +setScore(score + 1); // setScore(0 + 1); +console.log(score); // 0 +setScore(score + 1); // setScore(0 + 1); +console.log(score); // 0 +``` + +You can fix this by passing an *updater function* when setting state. Notice how replacing `setScore(score + 1)` with `setScore(s => s + 1)` fixes the "+3" button. This is handy if you need to queue multiple state updates. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [score, setScore] = useState(0); + + function increment() { + setScore(s => s + 1); + } + + return ( + <> + <button onClick={() => increment()}>+1</button> + <button onClick={() => { + increment(); + increment(); + increment(); + }}>+3</button> + <h1>Score: {score}</h1> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +``` + +</Sandpack> + +<LearnMore path="/learn/queueing-a-series-of-state-updates"> + +Read **[Queueing a Series of State Updates](/learn/queueing-a-series-of-state-updates)** to learn how to queue multiple updates before the next render. + +</LearnMore> + +## Updating objects in state {/*updating-objects-in-state*/} + +State can hold any kind of JavaScript value, including objects. But you shouldn't change objects and arrays that you hold in the React state directly. Instead, when you want to update an object and array, you need to create a new one (or make a copy of an existing one), and then update the state to use that copy. + +Usually, you will use the `...` spread syntax to copy objects and arrays that you want to change. For example, updating a nested object could look like this: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [person, setPerson] = useState({ + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } + }); + + function handleNameChange(e) { + setPerson({ + ...person, + name: e.target.value + }); + } + + function handleTitleChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + title: e.target.value + } + }); + } + + function handleCityChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + city: e.target.value + } + }); + } + + function handleImageChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + image: e.target.value + } + }); + } + + return ( + <> + <label> + Name: + <input + value={person.name} + onChange={handleNameChange} + /> + </label> + <label> + Title: + <input + value={person.artwork.title} + onChange={handleTitleChange} + /> + </label> + <label> + City: + <input + value={person.artwork.city} + onChange={handleCityChange} + /> + </label> + <label> + Image: + <input + value={person.artwork.image} + onChange={handleImageChange} + /> + </label> + <p> + <i>{person.artwork.title}</i> + {' by '} + {person.name} + <br /> + (located in {person.artwork.city}) + </p> + <img + src={person.artwork.image} + alt={person.artwork.title} + /> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +img { width: 200px; height: 200px; } +``` + +</Sandpack> + +If copying objects in code gets tedious, you can use a library like [Immer](https://github.com/immerjs/use-immer) to reduce repetitive code: + +<Sandpack> + +```js +import { useImmer } from 'use-immer'; + +export default function Form() { + const [person, updatePerson] = useImmer({ + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } + }); + + function handleNameChange(e) { + updatePerson(draft => { + draft.name = e.target.value; + }); + } + + function handleTitleChange(e) { + updatePerson(draft => { + draft.artwork.title = e.target.value; + }); + } + + function handleCityChange(e) { + updatePerson(draft => { + draft.artwork.city = e.target.value; + }); + } + + function handleImageChange(e) { + updatePerson(draft => { + draft.artwork.image = e.target.value; + }); + } + + return ( + <> + <label> + Name: + <input + value={person.name} + onChange={handleNameChange} + /> + </label> + <label> + Title: + <input + value={person.artwork.title} + onChange={handleTitleChange} + /> + </label> + <label> + City: + <input + value={person.artwork.city} + onChange={handleCityChange} + /> + </label> + <label> + Image: + <input + value={person.artwork.image} + onChange={handleImageChange} + /> + </label> + <p> + <i>{person.artwork.title}</i> + {' by '} + {person.name} + <br /> + (located in {person.artwork.city}) + </p> + <img + src={person.artwork.image} + alt={person.artwork.title} + /> + </> + ); +} +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +img { width: 200px; height: 200px; } +``` + +</Sandpack> + +<LearnMore path="/learn/updating-objects-in-state"> + +Read **[Updating Objects in State](/learn/updating-objects-in-state)** to learn how to update objects correctly. + +</LearnMore> + +## Updating arrays in state {/*updating-arrays-in-state*/} + +Arrays are another type of mutable JavaScript objects you can store in state and should treat as read-only. Just like with objects, when you want to update an array stored in state, you need to create a new one (or make a copy of an existing one), and then set state to use the new array: + +<Sandpack> + +```js +import { useState } from 'react'; + +let nextId = 3; +const initialList = [ + { id: 0, title: 'Big Bellies', seen: false }, + { id: 1, title: 'Lunar Landscape', seen: false }, + { id: 2, title: 'Terracotta Army', seen: true }, +]; + +export default function BucketList() { + const [list, setList] = useState( + initialList + ); + + function handleToggle(artworkId, nextSeen) { + setList(list.map(artwork => { + if (artwork.id === artworkId) { + return { ...artwork, seen: nextSeen }; + } else { + return artwork; + } + })); + } + + return ( + <> + <h1>Art Bucket List</h1> + <h2>My list of art to see:</h2> + <ItemList + artworks={list} + onToggle={handleToggle} /> + </> + ); +} + +function ItemList({ artworks, onToggle }) { + return ( + <ul> + {artworks.map(artwork => ( + <li key={artwork.id}> + <label> + <input + type="checkbox" + checked={artwork.seen} + onChange={e => { + onToggle( + artwork.id, + e.target.checked + ); + }} + /> + {artwork.title} + </label> + </li> + ))} + </ul> + ); +} +``` + +</Sandpack> + +If copying arrays in code gets tedious, you can use a library like [Immer](https://github.com/immerjs/use-immer) to reduce repetitive code: + +<Sandpack> + +```js +import { useState } from 'react'; +import { useImmer } from 'use-immer'; + +let nextId = 3; +const initialList = [ + { id: 0, title: 'Big Bellies', seen: false }, + { id: 1, title: 'Lunar Landscape', seen: false }, + { id: 2, title: 'Terracotta Army', seen: true }, +]; + +export default function BucketList() { + const [list, updateList] = useImmer(initialList); + + function handleToggle(artworkId, nextSeen) { + updateList(draft => { + const artwork = draft.find(a => + a.id === artworkId + ); + artwork.seen = nextSeen; + }); + } + + return ( + <> + <h1>Art Bucket List</h1> + <h2>My list of art to see:</h2> + <ItemList + artworks={list} + onToggle={handleToggle} /> + </> + ); +} + +function ItemList({ artworks, onToggle }) { + return ( + <ul> + {artworks.map(artwork => ( + <li key={artwork.id}> + <label> + <input + type="checkbox" + checked={artwork.seen} + onChange={e => { + onToggle( + artwork.id, + e.target.checked + ); + }} + /> + {artwork.title} + </label> + </li> + ))} + </ul> + ); +} +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +<LearnMore path="/learn/updating-arrays-in-state"> + +Read **[Updating Arrays in State](/learn/updating-arrays-in-state)** to learn how to update arrays correctly. + +</LearnMore> + +## What's next? {/*whats-next*/} + +Head over to [Responding to Events](/learn/responding-to-events) to start reading this chapter page by page! + +Or, if you're already familiar with these topics, why not read about [Managing State](/learn/managing-state)? diff --git a/beta/src/content/learn/choosing-the-state-structure.md b/beta/src/content/learn/choosing-the-state-structure.md new file mode 100644 index 000000000..70969d3fc --- /dev/null +++ b/beta/src/content/learn/choosing-the-state-structure.md @@ -0,0 +1,2850 @@ +--- +title: Choosing the State Structure +--- + +<Intro> + +Structuring state well can make a difference between a component that is pleasant to modify and debug, and one that is a constant source of bugs. Here are some tips you should consider when structuring state. + +</Intro> + +<YouWillLearn> + +* When to use a single vs multiple state variables +* What to avoid when organizing state +* How to fix common issues with the state structure + +</YouWillLearn> + +## Principles for structuring state {/*principles-for-structuring-state*/} + +When you write a component that holds some state, you'll have to make choices about how many state variables to use and what the shape of their data should be. While it's possible to write correct programs even with a suboptimal state structure, there are a few principles that can guide you to make better choices: + +1. **Group related state.** If you always update two or more state variables at the same time, consider merging them into a single state variable. +2. **Avoid contradictions in state.** When the state is structured in a way that several pieces of state may contradict and "disagree" with each other, you leave room for mistakes. Try to avoid this. +3. **Avoid redundant state.** If you can calculate some information from the component's props or its existing state variables during rendering, you should not put that information into that component's state. +4. **Avoid duplication in state.** When the same data is duplicated between multiple state variables, or within nested objects, it is difficult to keep them in sync. Reduce duplication when you can. +5. **Avoid deeply nested state.** Deeply hierarchical state is not very convenient to update. When possible, prefer to structure state in a flat way. + +The goal behind these principles is to *make state easy to update without introducing mistakes*. Removing redundant and duplicate data from state helps ensure that all its pieces stay in sync. This is similar to how a database engineer might want to ["normalize" the database structure](https://docs.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description) to reduce the chance of bugs. To paraphrase Albert Einstein, **"Make your state as simple as it can be--but no simpler."** + +Now let's see how these principles apply in action. + +## Group related state {/*group-related-state*/} + +You might sometimes be unsure between using a single or multiple state variables. + +Should you do this? + +```js +const [x, setX] = useState(0); +const [y, setY] = useState(0); +``` + +Or this? + +```js +const [position, setPosition] = useState({ x: 0, y: 0 }); +``` + +Technically, you can use either of these approaches. But **if some two state variables always change together, it might be a good idea to unify them into a single state variable.** Then you won't forget to always keep them in sync, like in this example where moving the cursor updates both coordinates of the red dot: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function MovingDot() { + const [position, setPosition] = useState({ + x: 0, + y: 0 + }); + return ( + <div + onPointerMove={e => { + setPosition({ + x: e.clientX, + y: e.clientY + }); + }} + style={{ + position: 'relative', + width: '100vw', + height: '100vh', + }}> + <div style={{ + position: 'absolute', + backgroundColor: 'red', + borderRadius: '50%', + transform: `translate(${position.x}px, ${position.y}px)`, + left: -10, + top: -10, + width: 20, + height: 20, + }} /> + </div> + ) +} +``` + +```css +body { margin: 0; padding: 0; height: 250px; } +``` + +</Sandpack> + +Another case where you'll group data into an object or an array is when you don't know how many different pieces of state you'll need. For example, it's helpful when you have a form where the user can add custom fields. + +<Pitfall> + +If your state variable is an object, remember that [you can't update only one field in it](/learn/updating-objects-in-state) without explicitly copying the other fields. For example, you can't do `setPosition({ x: 100 })` in the above example because it would not have the `y` property at all! Instead, if you wanted to set `x` alone, you would either do `setPosition({ ...position, x: 100 })`, or split them into two state variables and do `setX(100)`. + +</Pitfall> + +## Avoid contradictions in state {/*avoid-contradictions-in-state*/} + +Here is a hotel feedback form with `isSending` and `isSent` state variables: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FeedbackForm() { + const [text, setText] = useState(''); + const [isSending, setIsSending] = useState(false); + const [isSent, setIsSent] = useState(false); + + async function handleSubmit(e) { + e.preventDefault(); + setIsSending(true); + await sendMessage(text); + setIsSending(false); + setIsSent(true); + } + + if (isSent) { + return <h1>Thanks for feedback!</h1> + } + + return ( + <form onSubmit={handleSubmit}> + <p>How was your stay at The Prancing Pony?</p> + <textarea + disabled={isSending} + value={text} + onChange={e => setText(e.target.value)} + /> + <br /> + <button + disabled={isSending} + type="submit" + > + Send + </button> + {isSending && <p>Sending...</p>} + </form> + ); +} + +// Pretend to send a message. +function sendMessage(text) { + return new Promise(resolve => { + setTimeout(resolve, 2000); + }); +} +``` + +</Sandpack> + +While this code works, it leaves the door open for "impossible" states. For example, if you forget to call `setIsSent` and `setIsSending` together, you may end up in a situation where both `isSending` and `isSent` are `true` at the same time. The more complex your component is, the harder it will be to understand what happened. + +**Since `isSending` and `isSent` should never be `true` at the same time, it is better to replace them with one `status` state variable that may take one of *three* valid states:** `'typing'` (initial), `'sending'`, and `'sent'`: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FeedbackForm() { + const [text, setText] = useState(''); + const [status, setStatus] = useState('typing'); + + async function handleSubmit(e) { + e.preventDefault(); + setStatus('sending'); + await sendMessage(text); + setStatus('sent'); + } + + const isSending = status === 'sending'; + const isSent = status === 'sent'; + + if (isSent) { + return <h1>Thanks for feedback!</h1> + } + + return ( + <form onSubmit={handleSubmit}> + <p>How was your stay at The Prancing Pony?</p> + <textarea + disabled={isSending} + value={text} + onChange={e => setText(e.target.value)} + /> + <br /> + <button + disabled={isSending} + type="submit" + > + Send + </button> + {isSending && <p>Sending...</p>} + </form> + ); +} + +// Pretend to send a message. +function sendMessage(text) { + return new Promise(resolve => { + setTimeout(resolve, 2000); + }); +} +``` + +</Sandpack> + +You can still declare some constants for readability: + +```js +const isSending = status === 'sending'; +const isSent = status === 'sent'; +``` + +But they're not state variables, so you don't need to worry about them getting out of sync with each other. + +## Avoid redundant state {/*avoid-redundant-state*/} + +If you can calculate some information from the component's props or its existing state variables during rendering, you **should not** put that information into that component's state. + +For example, take this form. It works, but can you find any redundant state in it? + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [fullName, setFullName] = useState(''); + + function handleFirstNameChange(e) { + setFirstName(e.target.value); + setFullName(e.target.value + ' ' + lastName); + } + + function handleLastNameChange(e) { + setLastName(e.target.value); + setFullName(firstName + ' ' + e.target.value); + } + + return ( + <> + <h2>Let’s check you in</h2> + <label> + First name:{' '} + <input + value={firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name:{' '} + <input + value={lastName} + onChange={handleLastNameChange} + /> + </label> + <p> + Your ticket will be issued to: <b>{fullName}</b> + </p> + </> + ); +} +``` + +```css +label { display: block; margin-bottom: 5px; } +``` + +</Sandpack> + +This form has three state variables: `firstName`, `lastName`, and `fullName`. However, `fullName` is redundant. **You can always calculate `fullName` from `firstName` and `lastName` during render, so remove it from state.** + +This is how you can do it: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + + const fullName = firstName + ' ' + lastName; + + function handleFirstNameChange(e) { + setFirstName(e.target.value); + } + + function handleLastNameChange(e) { + setLastName(e.target.value); + } + + return ( + <> + <h2>Let’s check you in</h2> + <label> + First name:{' '} + <input + value={firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name:{' '} + <input + value={lastName} + onChange={handleLastNameChange} + /> + </label> + <p> + Your ticket will be issued to: <b>{fullName}</b> + </p> + </> + ); +} +``` + +```css +label { display: block; margin-bottom: 5px; } +``` + +</Sandpack> + +Here, `fullName` is *not* a state variable. Instead, it's calculated during render: + +```js +const fullName = firstName + ' ' + lastName; +``` + +As a result, the change handlers don't need to do anything special to update it. When you call `setFirstName` or `setLastName`, you trigger a re-render, and then the next `fullName` will be calculated from the fresh data. + +<DeepDive> + +#### Don't mirror props in state {/*don-t-mirror-props-in-state*/} + +A common example of redundant state is code like this: + +```js +function Message({ messageColor }) { + const [color, setColor] = useState(messageColor); +``` + +Here, a `color` state variable is initialized to the `messageColor` prop. The problem is that **if the parent component passes a different value of `messageColor` later (for example, `'red'` instead of `'blue'`), the `color` *state variable* would not be updated!** The state is only initialized during the first render. + +This is why "mirroring" some prop in a state variable can lead to confusion. Instead, use the `messageColor` prop directly in your code. If you want to give it a shorter name, use a constant: + +```js +function Message({ messageColor }) { + const color = messageColor; +``` + +This way it won't get out of sync with the prop passed from the parent component. + +"Mirroring" props into state only makes sense when you *want* to ignore all updates for a specific prop. By convention, start the prop name with `initial` or `default` to clarify that its new values are ignored: + +```js +function Message({ initialColor }) { + // The `color` state variable holds the *first* value of `initialColor`. + // Further changes to the `initialColor` prop are ignored. + const [color, setColor] = useState(initialColor); +``` + +</DeepDive> + +## Avoid duplication in state {/*avoid-duplication-in-state*/} + +This menu list component lets you choose a single travel snack out of several: + +<Sandpack> + +```js +import { useState } from 'react'; + +const initialItems = [ + { title: 'pretzels', id: 0 }, + { title: 'crispy seaweed', id: 1 }, + { title: 'granola bar', id: 2 }, +]; + +export default function Menu() { + const [items, setItems] = useState(initialItems); + const [selectedItem, setSelectedItem] = useState( + items[0] + ); + + return ( + <> + <h2>What's your travel snack?</h2> + <ul> + {items.map(item => ( + <li key={item.id}> + {item.title} + {' '} + <button onClick={() => { + setSelectedItem(item); + }}>Choose</button> + </li> + ))} + </ul> + <p>You picked {selectedItem.title}.</p> + </> + ); +} +``` + +```css +button { margin-top: 10px; } +``` + +</Sandpack> + +Currently, it stores the selected item as an object in the `selectedItem` state variable. However, this is not great: **the contents of the `selectedItem` is the same object as one of the items inside the `items` list.** This means that the information about the item itself is duplicated in two places. + +Why is this a problem? Let's make each item editable: + +<Sandpack> + +```js +import { useState } from 'react'; + +const initialItems = [ + { title: 'pretzels', id: 0 }, + { title: 'crispy seaweed', id: 1 }, + { title: 'granola bar', id: 2 }, +]; + +export default function Menu() { + const [items, setItems] = useState(initialItems); + const [selectedItem, setSelectedItem] = useState( + items[0] + ); + + function handleItemChange(id, e) { + setItems(items.map(item => { + if (item.id === id) { + return { + ...item, + title: e.target.value, + }; + } else { + return item; + } + })); + } + + return ( + <> + <h2>What's your travel snack?</h2> + <ul> + {items.map((item, index) => ( + <li key={item.id}> + <input + value={item.title} + onChange={e => { + handleItemChange(item.id, e) + }} + /> + {' '} + <button onClick={() => { + setSelectedItem(item); + }}>Choose</button> + </li> + ))} + </ul> + <p>You picked {selectedItem.title}.</p> + </> + ); +} +``` + +```css +button { margin-top: 10px; } +``` + +</Sandpack> + +Notice how if you first click "Choose" on an item and *then* edit it, **the input updates but the label at the bottom does not reflect the edits.** This is because you have duplicated state, and you forgot to update `selectedItem`. + +Although you could update `selectedItem` too, an easier fix is to remove duplication. In this example, instead of a `selectedItem` object (which creates a duplication with objects inside `items`), you hold the `selectedId` in state, and *then* get the `selectedItem` by searching the `items` array for an item with that ID: + +<Sandpack> + +```js +import { useState } from 'react'; + +const initialItems = [ + { title: 'pretzels', id: 0 }, + { title: 'crispy seaweed', id: 1 }, + { title: 'granola bar', id: 2 }, +]; + +export default function Menu() { + const [items, setItems] = useState(initialItems); + const [selectedId, setSelectedId] = useState(0); + + const selectedItem = items.find(item => + item.id === selectedId + ); + + function handleItemChange(id, e) { + setItems(items.map(item => { + if (item.id === id) { + return { + ...item, + title: e.target.value, + }; + } else { + return item; + } + })); + } + + return ( + <> + <h2>What's your travel snack?</h2> + <ul> + {items.map((item, index) => ( + <li key={item.id}> + <input + value={item.title} + onChange={e => { + handleItemChange(item.id, e) + }} + /> + {' '} + <button onClick={() => { + setSelectedId(item.id); + }}>Choose</button> + </li> + ))} + </ul> + <p>You picked {selectedItem.title}.</p> + </> + ); +} +``` + +```css +button { margin-top: 10px; } +``` + +</Sandpack> + +(Alternatively, you may hold the selected index in state.) + +The state used to be duplicated like this: + +* `items = [{ id: 0, title: 'pretzels'}, ...]` +* `selectedItem = {id: 0, title: 'pretzels'}` + +But after the change it's like this: + +* `items = [{ id: 0, title: 'pretzels'}, ...]` +* `selectedId = 0` + +The duplication is gone, and you only keep the essential state! + +Now if you edit the *selected* item, the message below will update immediately. This is because `setItems` triggers a re-render, and `items.find(...)` would find the item with the updated title. You didn't need to hold *the selected item* in state, because only the *selected ID* is essential. The rest could be calculated during render. + +## Avoid deeply nested state {/*avoid-deeply-nested-state*/} + +Imagine a travel plan consisting of planets, continents, and countries. You might be tempted to structure its state using nested objects and arrays, like in this example: + +<Sandpack> + +```js +import { useState } from 'react'; +import { initialTravelPlan } from './places.js'; + +function PlaceTree({ place }) { + const childPlaces = place.childPlaces; + return ( + <li> + {place.title} + {childPlaces.length > 0 && ( + <ol> + {childPlaces.map(place => ( + <PlaceTree key={place.id} place={place} /> + ))} + </ol> + )} + </li> + ); +} + +export default function TravelPlan() { + const [plan, setPlan] = useState(initialTravelPlan); + const planets = plan.childPlaces; + return ( + <> + <h2>Places to visit</h2> + <ol> + {planets.map(place => ( + <PlaceTree key={place.id} place={place} /> + ))} + </ol> + </> + ); +} +``` + +```js places.js active +export const initialTravelPlan = { + id: 0, + title: '(Root)', + childPlaces: [{ + id: 1, + title: 'Earth', + childPlaces: [{ + id: 2, + title: 'Africa', + childPlaces: [{ + id: 3, + title: 'Botswana', + childPlaces: [] + }, { + id: 4, + title: 'Egypt', + childPlaces: [] + }, { + id: 5, + title: 'Kenya', + childPlaces: [] + }, { + id: 6, + title: 'Madagascar', + childPlaces: [] + }, { + id: 7, + title: 'Morocco', + childPlaces: [] + }, { + id: 8, + title: 'Nigeria', + childPlaces: [] + }, { + id: 9, + title: 'South Africa', + childPlaces: [] + }] + }, { + id: 10, + title: 'Americas', + childPlaces: [{ + id: 11, + title: 'Argentina', + childPlaces: [] + }, { + id: 12, + title: 'Brazil', + childPlaces: [] + }, { + id: 13, + title: 'Barbados', + childPlaces: [] + }, { + id: 14, + title: 'Canada', + childPlaces: [] + }, { + id: 15, + title: 'Jamaica', + childPlaces: [] + }, { + id: 16, + title: 'Mexico', + childPlaces: [] + }, { + id: 17, + title: 'Trinidad and Tobago', + childPlaces: [] + }, { + id: 18, + title: 'Venezuela', + childPlaces: [] + }] + }, { + id: 19, + title: 'Asia', + childPlaces: [{ + id: 20, + title: 'China', + childPlaces: [] + }, { + id: 21, + title: 'Hong Kong', + childPlaces: [] + }, { + id: 22, + title: 'India', + childPlaces: [] + }, { + id: 23, + title: 'Singapore', + childPlaces: [] + }, { + id: 24, + title: 'South Korea', + childPlaces: [] + }, { + id: 25, + title: 'Thailand', + childPlaces: [] + }, { + id: 26, + title: 'Vietnam', + childPlaces: [] + }] + }, { + id: 27, + title: 'Europe', + childPlaces: [{ + id: 28, + title: 'Croatia', + childPlaces: [], + }, { + id: 29, + title: 'France', + childPlaces: [], + }, { + id: 30, + title: 'Germany', + childPlaces: [], + }, { + id: 31, + title: 'Italy', + childPlaces: [], + }, { + id: 32, + title: 'Portugal', + childPlaces: [], + }, { + id: 33, + title: 'Spain', + childPlaces: [], + }, { + id: 34, + title: 'Turkey', + childPlaces: [], + }] + }, { + id: 35, + title: 'Oceania', + childPlaces: [{ + id: 36, + title: 'Australia', + childPlaces: [], + }, { + id: 37, + title: 'Bora Bora (French Polynesia)', + childPlaces: [], + }, { + id: 38, + title: 'Easter Island (Chile)', + childPlaces: [], + }, { + id: 39, + title: 'Fiji', + childPlaces: [], + }, { + id: 40, + title: 'Hawaii (the USA)', + childPlaces: [], + }, { + id: 41, + title: 'New Zealand', + childPlaces: [], + }, { + id: 42, + title: 'Vanuatu', + childPlaces: [], + }] + }] + }, { + id: 43, + title: 'Moon', + childPlaces: [{ + id: 44, + title: 'Rheita', + childPlaces: [] + }, { + id: 45, + title: 'Piccolomini', + childPlaces: [] + }, { + id: 46, + title: 'Tycho', + childPlaces: [] + }] + }, { + id: 47, + title: 'Mars', + childPlaces: [{ + id: 48, + title: 'Corn Town', + childPlaces: [] + }, { + id: 49, + title: 'Green Hill', + childPlaces: [] + }] + }] +}; +``` + +</Sandpack> + +Now let's say you want to add a button to delete a place you've already visited. How would you go about it? [Updating nested state](/learn/updating-objects-in-state#updating-a-nested-object) involves making copies of objects all the way up from the part that changed. Deleting a deeply nested place would involve copying its entire parent place chain. Such code can be very verbose. + +**If the state is too nested to update easily, consider making it "flat".** Here is one way you can restructure this data. Instead of a tree-like structure where each `place` has an array of *its child places*, you can have each place hold an array of *its child place IDs*. Then you can store a mapping from each place ID to the corresponding place. + +This data restructuring might remind you of seeing a database table: + +<Sandpack> + +```js +import { useState } from 'react'; +import { initialTravelPlan } from './places.js'; + +function PlaceTree({ id, placesById }) { + const place = placesById[id]; + const childIds = place.childIds; + return ( + <li> + {place.title} + {childIds.length > 0 && ( + <ol> + {childIds.map(childId => ( + <PlaceTree + key={childId} + id={childId} + placesById={placesById} + /> + ))} + </ol> + )} + </li> + ); +} + +export default function TravelPlan() { + const [plan, setPlan] = useState(initialTravelPlan); + const root = plan[0]; + const planetIds = root.childIds; + return ( + <> + <h2>Places to visit</h2> + <ol> + {planetIds.map(id => ( + <PlaceTree + key={id} + id={id} + placesById={plan} + /> + ))} + </ol> + </> + ); +} +``` + +```js places.js active +export const initialTravelPlan = { + 0: { + id: 0, + title: '(Root)', + childIds: [1, 43, 47], + }, + 1: { + id: 1, + title: 'Earth', + childIds: [2, 10, 19, 27, 35] + }, + 2: { + id: 2, + title: 'Africa', + childIds: [3, 4, 5, 6 , 7, 8, 9] + }, + 3: { + id: 3, + title: 'Botswana', + childIds: [] + }, + 4: { + id: 4, + title: 'Egypt', + childIds: [] + }, + 5: { + id: 5, + title: 'Kenya', + childIds: [] + }, + 6: { + id: 6, + title: 'Madagascar', + childIds: [] + }, + 7: { + id: 7, + title: 'Morocco', + childIds: [] + }, + 8: { + id: 8, + title: 'Nigeria', + childIds: [] + }, + 9: { + id: 9, + title: 'South Africa', + childIds: [] + }, + 10: { + id: 10, + title: 'Americas', + childIds: [11, 12, 13, 14, 15, 16, 17, 18], + }, + 11: { + id: 11, + title: 'Argentina', + childIds: [] + }, + 12: { + id: 12, + title: 'Brazil', + childIds: [] + }, + 13: { + id: 13, + title: 'Barbados', + childIds: [] + }, + 14: { + id: 14, + title: 'Canada', + childIds: [] + }, + 15: { + id: 15, + title: 'Jamaica', + childIds: [] + }, + 16: { + id: 16, + title: 'Mexico', + childIds: [] + }, + 17: { + id: 17, + title: 'Trinidad and Tobago', + childIds: [] + }, + 18: { + id: 18, + title: 'Venezuela', + childIds: [] + }, + 19: { + id: 19, + title: 'Asia', + childIds: [20, 21, 22, 23, 24, 25, 26], + }, + 20: { + id: 20, + title: 'China', + childIds: [] + }, + 21: { + id: 21, + title: 'Hong Kong', + childIds: [] + }, + 22: { + id: 22, + title: 'India', + childIds: [] + }, + 23: { + id: 23, + title: 'Singapore', + childIds: [] + }, + 24: { + id: 24, + title: 'South Korea', + childIds: [] + }, + 25: { + id: 25, + title: 'Thailand', + childIds: [] + }, + 26: { + id: 26, + title: 'Vietnam', + childIds: [] + }, + 27: { + id: 27, + title: 'Europe', + childIds: [28, 29, 30, 31, 32, 33, 34], + }, + 28: { + id: 28, + title: 'Croatia', + childIds: [] + }, + 29: { + id: 29, + title: 'France', + childIds: [] + }, + 30: { + id: 30, + title: 'Germany', + childIds: [] + }, + 31: { + id: 31, + title: 'Italy', + childIds: [] + }, + 32: { + id: 32, + title: 'Portugal', + childIds: [] + }, + 33: { + id: 33, + title: 'Spain', + childIds: [] + }, + 34: { + id: 34, + title: 'Turkey', + childIds: [] + }, + 35: { + id: 35, + title: 'Oceania', + childIds: [36, 37, 38, 39, 40, 41, 42], + }, + 36: { + id: 36, + title: 'Australia', + childIds: [] + }, + 37: { + id: 37, + title: 'Bora Bora (French Polynesia)', + childIds: [] + }, + 38: { + id: 38, + title: 'Easter Island (Chile)', + childIds: [] + }, + 39: { + id: 39, + title: 'Fiji', + childIds: [] + }, + 40: { + id: 40, + title: 'Hawaii (the USA)', + childIds: [] + }, + 41: { + id: 41, + title: 'New Zealand', + childIds: [] + }, + 42: { + id: 42, + title: 'Vanuatu', + childIds: [] + }, + 43: { + id: 43, + title: 'Moon', + childIds: [44, 45, 46] + }, + 44: { + id: 44, + title: 'Rheita', + childIds: [] + }, + 45: { + id: 45, + title: 'Piccolomini', + childIds: [] + }, + 46: { + id: 46, + title: 'Tycho', + childIds: [] + }, + 47: { + id: 47, + title: 'Mars', + childIds: [48, 49] + }, + 48: { + id: 48, + title: 'Corn Town', + childIds: [] + }, + 49: { + id: 49, + title: 'Green Hill', + childIds: [] + } +}; +``` + +</Sandpack> + +**Now that the state is "flat" (also known as "normalized"), updating nested items becomes easier.** + +In order to remove a place now, you only need to update two levels of state: + +- The updated version of its *parent* place should exclude the removed ID from its `childIds` array. +- The updated version of the root "table" object should include the updated version of the parent place. + +Here is an example of how you could go about it: + +<Sandpack> + +```js +import { useState } from 'react'; +import { initialTravelPlan } from './places.js'; + +export default function TravelPlan() { + const [plan, setPlan] = useState(initialTravelPlan); + + function handleComplete(parentId, childId) { + const parent = plan[parentId]; + // Create a new version of the parent place + // that doesn't include this child ID. + const nextParent = { + ...parent, + childIds: parent.childIds + .filter(id => id !== childId) + }; + // Update the root state object... + setPlan({ + ...plan, + // ...so that it has the updated parent. + [parentId]: nextParent + }); + } + + const root = plan[0]; + const planetIds = root.childIds; + return ( + <> + <h2>Places to visit</h2> + <ol> + {planetIds.map(id => ( + <PlaceTree + key={id} + id={id} + parentId={0} + placesById={plan} + onComplete={handleComplete} + /> + ))} + </ol> + </> + ); +} + +function PlaceTree({ id, parentId, placesById, onComplete }) { + const place = placesById[id]; + const childIds = place.childIds; + return ( + <li> + {place.title} + <button onClick={() => { + onComplete(parentId, id); + }}> + Complete + </button> + {childIds.length > 0 && + <ol> + {childIds.map(childId => ( + <PlaceTree + key={childId} + id={childId} + parentId={id} + placesById={placesById} + onComplete={onComplete} + /> + ))} + </ol> + } + </li> + ); +} +``` + +```js places.js +export const initialTravelPlan = { + 0: { + id: 0, + title: '(Root)', + childIds: [1, 43, 47], + }, + 1: { + id: 1, + title: 'Earth', + childIds: [2, 10, 19, 27, 35] + }, + 2: { + id: 2, + title: 'Africa', + childIds: [3, 4, 5, 6 , 7, 8, 9] + }, + 3: { + id: 3, + title: 'Botswana', + childIds: [] + }, + 4: { + id: 4, + title: 'Egypt', + childIds: [] + }, + 5: { + id: 5, + title: 'Kenya', + childIds: [] + }, + 6: { + id: 6, + title: 'Madagascar', + childIds: [] + }, + 7: { + id: 7, + title: 'Morocco', + childIds: [] + }, + 8: { + id: 8, + title: 'Nigeria', + childIds: [] + }, + 9: { + id: 9, + title: 'South Africa', + childIds: [] + }, + 10: { + id: 10, + title: 'Americas', + childIds: [11, 12, 13, 14, 15, 16, 17, 18], + }, + 11: { + id: 11, + title: 'Argentina', + childIds: [] + }, + 12: { + id: 12, + title: 'Brazil', + childIds: [] + }, + 13: { + id: 13, + title: 'Barbados', + childIds: [] + }, + 14: { + id: 14, + title: 'Canada', + childIds: [] + }, + 15: { + id: 15, + title: 'Jamaica', + childIds: [] + }, + 16: { + id: 16, + title: 'Mexico', + childIds: [] + }, + 17: { + id: 17, + title: 'Trinidad and Tobago', + childIds: [] + }, + 18: { + id: 18, + title: 'Venezuela', + childIds: [] + }, + 19: { + id: 19, + title: 'Asia', + childIds: [20, 21, 22, 23, 24, 25, 26], + }, + 20: { + id: 20, + title: 'China', + childIds: [] + }, + 21: { + id: 21, + title: 'Hong Kong', + childIds: [] + }, + 22: { + id: 22, + title: 'India', + childIds: [] + }, + 23: { + id: 23, + title: 'Singapore', + childIds: [] + }, + 24: { + id: 24, + title: 'South Korea', + childIds: [] + }, + 25: { + id: 25, + title: 'Thailand', + childIds: [] + }, + 26: { + id: 26, + title: 'Vietnam', + childIds: [] + }, + 27: { + id: 27, + title: 'Europe', + childIds: [28, 29, 30, 31, 32, 33, 34], + }, + 28: { + id: 28, + title: 'Croatia', + childIds: [] + }, + 29: { + id: 29, + title: 'France', + childIds: [] + }, + 30: { + id: 30, + title: 'Germany', + childIds: [] + }, + 31: { + id: 31, + title: 'Italy', + childIds: [] + }, + 32: { + id: 32, + title: 'Portugal', + childIds: [] + }, + 33: { + id: 33, + title: 'Spain', + childIds: [] + }, + 34: { + id: 34, + title: 'Turkey', + childIds: [] + }, + 35: { + id: 35, + title: 'Oceania', + childIds: [36, 37, 38, 39, 40, 41,, 42], + }, + 36: { + id: 36, + title: 'Australia', + childIds: [] + }, + 37: { + id: 37, + title: 'Bora Bora (French Polynesia)', + childIds: [] + }, + 38: { + id: 38, + title: 'Easter Island (Chile)', + childIds: [] + }, + 39: { + id: 39, + title: 'Fiji', + childIds: [] + }, + 40: { + id: 40, + title: 'Hawaii (the USA)', + childIds: [] + }, + 41: { + id: 41, + title: 'New Zealand', + childIds: [] + }, + 42: { + id: 42, + title: 'Vanuatu', + childIds: [] + }, + 43: { + id: 43, + title: 'Moon', + childIds: [44, 45, 46] + }, + 44: { + id: 44, + title: 'Rheita', + childIds: [] + }, + 45: { + id: 45, + title: 'Piccolomini', + childIds: [] + }, + 46: { + id: 46, + title: 'Tycho', + childIds: [] + }, + 47: { + id: 47, + title: 'Mars', + childIds: [48, 49] + }, + 48: { + id: 48, + title: 'Corn Town', + childIds: [] + }, + 49: { + id: 49, + title: 'Green Hill', + childIds: [] + } +}; +``` + +```css +button { margin: 10px; } +``` + +</Sandpack> + +You can nest state as much as you like, but making it "flat" can solve numerous problems. It makes state easier to update, and it helps ensure you don't have duplication in different parts of a nested object. + +<DeepDive> + +#### Improving memory usage {/*improving-memory-usage*/} + +Ideally, you would also remove the deleted items (and their children!) from the "table" object to improve memory usage. This version does that. It also [uses Immer](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) to make the update logic more concise. + +<Sandpack> + +```js +import { useImmer } from 'use-immer'; +import { initialTravelPlan } from './places.js'; + +export default function TravelPlan() { + const [plan, updatePlan] = useImmer(initialTravelPlan); + + function handleComplete(parentId, childId) { + updatePlan(draft => { + // Remove from the parent place's child IDs. + const parent = draft[parentId]; + parent.childIds = parent.childIds + .filter(id => id !== childId); + + // Forget this place and all its subtree. + deleteAllChildren(childId); + function deleteAllChildren(id) { + const place = draft[id]; + place.childIds.forEach(deleteAllChildren); + delete draft[id]; + } + }); + } + + const root = plan[0]; + const planetIds = root.childIds; + return ( + <> + <h2>Places to visit</h2> + <ol> + {planetIds.map(id => ( + <PlaceTree + key={id} + id={id} + parentId={0} + placesById={plan} + onComplete={handleComplete} + /> + ))} + </ol> + </> + ); +} + +function PlaceTree({ id, parentId, placesById, onComplete }) { + const place = placesById[id]; + const childIds = place.childIds; + return ( + <li> + {place.title} + <button onClick={() => { + onComplete(parentId, id); + }}> + Complete + </button> + {childIds.length > 0 && + <ol> + {childIds.map(childId => ( + <PlaceTree + key={childId} + id={childId} + parentId={id} + placesById={placesById} + onComplete={onComplete} + /> + ))} + </ol> + } + </li> + ); +} +``` + +```js places.js +export const initialTravelPlan = { + 0: { + id: 0, + title: '(Root)', + childIds: [1, 43, 47], + }, + 1: { + id: 1, + title: 'Earth', + childIds: [2, 10, 19, 27, 35] + }, + 2: { + id: 2, + title: 'Africa', + childIds: [3, 4, 5, 6 , 7, 8, 9] + }, + 3: { + id: 3, + title: 'Botswana', + childIds: [] + }, + 4: { + id: 4, + title: 'Egypt', + childIds: [] + }, + 5: { + id: 5, + title: 'Kenya', + childIds: [] + }, + 6: { + id: 6, + title: 'Madagascar', + childIds: [] + }, + 7: { + id: 7, + title: 'Morocco', + childIds: [] + }, + 8: { + id: 8, + title: 'Nigeria', + childIds: [] + }, + 9: { + id: 9, + title: 'South Africa', + childIds: [] + }, + 10: { + id: 10, + title: 'Americas', + childIds: [11, 12, 13, 14, 15, 16, 17, 18], + }, + 11: { + id: 11, + title: 'Argentina', + childIds: [] + }, + 12: { + id: 12, + title: 'Brazil', + childIds: [] + }, + 13: { + id: 13, + title: 'Barbados', + childIds: [] + }, + 14: { + id: 14, + title: 'Canada', + childIds: [] + }, + 15: { + id: 15, + title: 'Jamaica', + childIds: [] + }, + 16: { + id: 16, + title: 'Mexico', + childIds: [] + }, + 17: { + id: 17, + title: 'Trinidad and Tobago', + childIds: [] + }, + 18: { + id: 18, + title: 'Venezuela', + childIds: [] + }, + 19: { + id: 19, + title: 'Asia', + childIds: [20, 21, 22, 23, 24, 25, 26], + }, + 20: { + id: 20, + title: 'China', + childIds: [] + }, + 21: { + id: 21, + title: 'Hong Kong', + childIds: [] + }, + 22: { + id: 22, + title: 'India', + childIds: [] + }, + 23: { + id: 23, + title: 'Singapore', + childIds: [] + }, + 24: { + id: 24, + title: 'South Korea', + childIds: [] + }, + 25: { + id: 25, + title: 'Thailand', + childIds: [] + }, + 26: { + id: 26, + title: 'Vietnam', + childIds: [] + }, + 27: { + id: 27, + title: 'Europe', + childIds: [28, 29, 30, 31, 32, 33, 34], + }, + 28: { + id: 28, + title: 'Croatia', + childIds: [] + }, + 29: { + id: 29, + title: 'France', + childIds: [] + }, + 30: { + id: 30, + title: 'Germany', + childIds: [] + }, + 31: { + id: 31, + title: 'Italy', + childIds: [] + }, + 32: { + id: 32, + title: 'Portugal', + childIds: [] + }, + 33: { + id: 33, + title: 'Spain', + childIds: [] + }, + 34: { + id: 34, + title: 'Turkey', + childIds: [] + }, + 35: { + id: 35, + title: 'Oceania', + childIds: [36, 37, 38, 39, 40, 41,, 42], + }, + 36: { + id: 36, + title: 'Australia', + childIds: [] + }, + 37: { + id: 37, + title: 'Bora Bora (French Polynesia)', + childIds: [] + }, + 38: { + id: 38, + title: 'Easter Island (Chile)', + childIds: [] + }, + 39: { + id: 39, + title: 'Fiji', + childIds: [] + }, + 40: { + id: 40, + title: 'Hawaii (the USA)', + childIds: [] + }, + 41: { + id: 41, + title: 'New Zealand', + childIds: [] + }, + 42: { + id: 42, + title: 'Vanuatu', + childIds: [] + }, + 43: { + id: 43, + title: 'Moon', + childIds: [44, 45, 46] + }, + 44: { + id: 44, + title: 'Rheita', + childIds: [] + }, + 45: { + id: 45, + title: 'Piccolomini', + childIds: [] + }, + 46: { + id: 46, + title: 'Tycho', + childIds: [] + }, + 47: { + id: 47, + title: 'Mars', + childIds: [48, 49] + }, + 48: { + id: 48, + title: 'Corn Town', + childIds: [] + }, + 49: { + id: 49, + title: 'Green Hill', + childIds: [] + } +}; +``` + +```css +button { margin: 10px; } +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +</DeepDive> + +Sometimes, you can also reduce state nesting by moving some of the nested state into the child components. This works well for ephemeral UI state that doesn't need to be stored, like whether an item is hovered. + +<Recap> + +* If two state variables always update together, consider merging them into one. +* Choose your state variables carefully to avoid creating "impossible" states. +* Structure your state in a way that reduces the chances that you'll make a mistake updating it. +* Avoid redundant and duplicate state so that you don't need to keep it in sync. +* Don't put props *into* state unless you specifically want to prevent updates. +* For UI patterns like selection, keep ID or index in state instead of the object itself. +* If updating deeply nested state is complicated, try flattening it. + +</Recap> + +<Challenges> + +#### Fix a component that's not updating {/*fix-a-component-thats-not-updating*/} + +This `Clock` component receives two props: `color` and `time`. When you select a different color in the select box, the `Clock` component receives a different `color` prop from its parent component. However, for some reason, the displayed color doesn't update. Why? Fix the problem. + +<Sandpack> + +```js Clock.js active +import { useState } from 'react'; + +export default function Clock(props) { + const [color, setColor] = useState(props.color); + return ( + <h1 style={{ color: color }}> + {props.time} + </h1> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import Clock from './Clock.js'; + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} + +export default function App() { + const time = useTime(); + const [color, setColor] = useState('lightcoral'); + return ( + <div> + <p> + Pick a color:{' '} + <select value={color} onChange={e => setColor(e.target.value)}> + <option value="lightcoral">lightcoral</option> + <option value="midnightblue">midnightblue</option> + <option value="rebeccapurple">rebeccapurple</option> + </select> + </p> + <Clock color={color} time={time.toLocaleTimeString()} /> + </div> + ); +} +``` + +</Sandpack> + +<Solution> + +The issue is that this component has `color` state initialized with the initial value of the `color` prop. But when the `color` prop changes, this does not affect the state variable! So they get out of sync. To fix this issue, remove the state variable altogether, and use the `color` prop directly. + +<Sandpack> + +```js Clock.js active +import { useState } from 'react'; + +export default function Clock(props) { + return ( + <h1 style={{ color: props.color }}> + {props.time} + </h1> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import Clock from './Clock.js'; + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} + +export default function App() { + const time = useTime(); + const [color, setColor] = useState('lightcoral'); + return ( + <div> + <p> + Pick a color:{' '} + <select value={color} onChange={e => setColor(e.target.value)}> + <option value="lightcoral">lightcoral</option> + <option value="midnightblue">midnightblue</option> + <option value="rebeccapurple">rebeccapurple</option> + </select> + </p> + <Clock color={color} time={time.toLocaleTimeString()} /> + </div> + ); +} +``` + +</Sandpack> + +Or, using the destructuring syntax: + +<Sandpack> + +```js Clock.js active +import { useState } from 'react'; + +export default function Clock({ color, time }) { + return ( + <h1 style={{ color: color }}> + {time} + </h1> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import Clock from './Clock.js'; + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} + +export default function App() { + const time = useTime(); + const [color, setColor] = useState('lightcoral'); + return ( + <div> + <p> + Pick a color:{' '} + <select value={color} onChange={e => setColor(e.target.value)}> + <option value="lightcoral">lightcoral</option> + <option value="midnightblue">midnightblue</option> + <option value="rebeccapurple">rebeccapurple</option> + </select> + </p> + <Clock color={color} time={time.toLocaleTimeString()} /> + </div> + ); +} +``` + +</Sandpack> + +</Solution> + +#### Fix a broken packing list {/*fix-a-broken-packing-list*/} + +This packing list has a footer that shows how many items are packed, and how many items there are overall. It seems to work at first, but it is buggy. For example, if you mark an item as packed and then delete it, the counter will not be updated correctly. Fix the counter so that it's always correct. + +<Hint> + +Is any state in this example redundant? + +</Hint> + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AddItem from './AddItem.js'; +import PackingList from './PackingList.js'; + +let nextId = 3; +const initialItems = [ + { id: 0, title: 'Warm socks', packed: true }, + { id: 1, title: 'Travel journal', packed: false }, + { id: 2, title: 'Watercolors', packed: false }, +]; + +export default function TravelPlan() { + const [items, setItems] = useState(initialItems); + const [total, setTotal] = useState(3); + const [packed, setPacked] = useState(1); + + function handleAddItem(title) { + setTotal(total + 1); + setItems([ + ...items, + { + id: nextId++, + title: title, + packed: false + } + ]); + } + + function handleChangeItem(nextItem) { + if (nextItem.packed) { + setPacked(packed + 1); + } else { + setPacked(packed - 1); + } + setItems(items.map(item => { + if (item.id === nextItem.id) { + return nextItem; + } else { + return item; + } + })); + } + + function handleDeleteItem(itemId) { + setTotal(total - 1); + setItems( + items.filter(item => item.id !== itemId) + ); + } + + return ( + <> + <AddItem + onAddItem={handleAddItem} + /> + <PackingList + items={items} + onChangeItem={handleChangeItem} + onDeleteItem={handleDeleteItem} + /> + <hr /> + <b>{packed} out of {total} packed!</b> + </> + ); +} +``` + +```js AddItem.js hidden +import { useState } from 'react'; + +export default function AddItem({ onAddItem }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add item" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddItem(title); + }}>Add</button> + </> + ) +} +``` + +```js PackingList.js hidden +import { useState } from 'react'; + +export default function PackingList({ + items, + onChangeItem, + onDeleteItem +}) { + return ( + <ul> + {items.map(item => ( + <li key={item.id}> + <label> + <input + type="checkbox" + checked={item.packed} + onChange={e => { + onChangeItem({ + ...item, + packed: e.target.checked + }); + }} + /> + {' '} + {item.title} + </label> + <button onClick={() => onDeleteItem(item.id)}> + Delete + </button> + </li> + ))} + </ul> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +<Solution> + +Although you could carefully change each event handler to update the `total` and `packed` counters correctly, the root problem is that these state variables exist at all. They are redundant because you can always calculate the number of items (packed or total) from the `items` array itself. Remove the redundant state to fix the bug: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AddItem from './AddItem.js'; +import PackingList from './PackingList.js'; + +let nextId = 3; +const initialItems = [ + { id: 0, title: 'Warm socks', packed: true }, + { id: 1, title: 'Travel journal', packed: false }, + { id: 2, title: 'Watercolors', packed: false }, +]; + +export default function TravelPlan() { + const [items, setItems] = useState(initialItems); + + const total = items.length; + const packed = items + .filter(item => item.packed) + .length; + + function handleAddItem(title) { + setItems([ + ...items, + { + id: nextId++, + title: title, + packed: false + } + ]); + } + + function handleChangeItem(nextItem) { + setItems(items.map(item => { + if (item.id === nextItem.id) { + return nextItem; + } else { + return item; + } + })); + } + + function handleDeleteItem(itemId) { + setItems( + items.filter(item => item.id !== itemId) + ); + } + + return ( + <> + <AddItem + onAddItem={handleAddItem} + /> + <PackingList + items={items} + onChangeItem={handleChangeItem} + onDeleteItem={handleDeleteItem} + /> + <hr /> + <b>{packed} out of {total} packed!</b> + </> + ); +} +``` + +```js AddItem.js hidden +import { useState } from 'react'; + +export default function AddItem({ onAddItem }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add item" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddItem(title); + }}>Add</button> + </> + ) +} +``` + +```js PackingList.js hidden +import { useState } from 'react'; + +export default function PackingList({ + items, + onChangeItem, + onDeleteItem +}) { + return ( + <ul> + {items.map(item => ( + <li key={item.id}> + <label> + <input + type="checkbox" + checked={item.packed} + onChange={e => { + onChangeItem({ + ...item, + packed: e.target.checked + }); + }} + /> + {' '} + {item.title} + </label> + <button onClick={() => onDeleteItem(item.id)}> + Delete + </button> + </li> + ))} + </ul> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +Notice how the event handlers are only concerned with calling `setItems` after this change. The item counts are now calculated during the next render from `items`, so they are always up-to-date. + +</Solution> + +#### Fix the disappearing selection {/*fix-the-disappearing-selection*/} + +There is a list of `letters` in state. When you hover or focus a particular letter, it gets highlighted. The currently highlighted letter is stored in the `highlightedLetter` state variable. You can "star" and "unstar" individual letters, which updates the `letters` array in state. + +This code works, but there is a minor UI glitch. When you press "Star" or "Unstar", the highlighting disappears for a moment. However, it reappears as soon as you move your pointer or switch to another letter with keyboard. Why is this happening? Fix it so that the highlighting doesn't disappear after the button click. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { initialLetters } from './data.js'; +import Letter from './Letter.js'; + +export default function MailClient() { + const [letters, setLetters] = useState(initialLetters); + const [highlightedLetter, setHighlightedLetter] = useState(null); + + function handleHover(letter) { + setHighlightedLetter(letter); + } + + function handleStar(starred) { + setLetters(letters.map(letter => { + if (letter.id === starred.id) { + return { + ...letter, + isStarred: !letter.isStarred + }; + } else { + return letter; + } + })); + } + + return ( + <> + <h2>Inbox</h2> + <ul> + {letters.map(letter => ( + <Letter + key={letter.id} + letter={letter} + isHighlighted={ + letter === highlightedLetter + } + onHover={handleHover} + onToggleStar={handleStar} + /> + ))} + </ul> + </> + ); +} +``` + +```js Letter.js +export default function Letter({ + letter, + isHighlighted, + onHover, + onToggleStar, +}) { + return ( + <li + className={ + isHighlighted ? 'highlighted' : '' + } + onFocus={() => { + onHover(letter); + }} + onPointerMove={() => { + onHover(letter); + }} + > + <button onClick={() => { + onToggleStar(letter); + }}> + {letter.isStarred ? 'Unstar' : 'Star'} + </button> + {letter.subject} + </li> + ) +} +``` + +```js data.js +export const initialLetters = [{ + id: 0, + subject: 'Ready for adventure?', + isStarred: true, +}, { + id: 1, + subject: 'Time to check in!', + isStarred: false, +}, { + id: 2, + subject: 'Festival Begins in Just SEVEN Days!', + isStarred: false, +}]; +``` + +```css +button { margin: 5px; } +li { border-radius: 5px; } +.highlighted { background: #d2eaff; } +``` + +</Sandpack> + +<Solution> + +The problem is that you're holding the letter object in `highlightedLetter`. But you're also holding the same information in the `letters` array. So your state has duplication! When you update the `letters` array after the button click, you create a new letter object which is different from `highlightedLetter`. This is why `highlightedLetter === letter` check becomes `false`, and the highlight disappears. It reappears the next time you call `setHighlightedLetter` when the pointer moves. + +To fix the issue, remove the duplication from state. Instead of storing *the letter itself* in two places, store the `highlightedId` instead. Then you can check `isHighlighted` for each letter with `letter.id === highlightedId`, which will work even if the `letter` object has changed since the last render. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { initialLetters } from './data.js'; +import Letter from './Letter.js'; + +export default function MailClient() { + const [letters, setLetters] = useState(initialLetters); + const [highlightedId, setHighlightedId ] = useState(null); + + function handleHover(letterId) { + setHighlightedId(letterId); + } + + function handleStar(starredId) { + setLetters(letters.map(letter => { + if (letter.id === starredId) { + return { + ...letter, + isStarred: !letter.isStarred + }; + } else { + return letter; + } + })); + } + + return ( + <> + <h2>Inbox</h2> + <ul> + {letters.map(letter => ( + <Letter + key={letter.id} + letter={letter} + isHighlighted={ + letter.id === highlightedId + } + onHover={handleHover} + onToggleStar={handleStar} + /> + ))} + </ul> + </> + ); +} +``` + +```js Letter.js +export default function Letter({ + letter, + isHighlighted, + onHover, + onToggleStar, +}) { + return ( + <li + className={ + isHighlighted ? 'highlighted' : '' + } + onFocus={() => { + onHover(letter.id); + }} + onPointerMove={() => { + onHover(letter.id); + }} + > + <button onClick={() => { + onToggleStar(letter.id); + }}> + {letter.isStarred ? 'Unstar' : 'Star'} + </button> + {letter.subject} + </li> + ) +} +``` + +```js data.js +export const initialLetters = [{ + id: 0, + subject: 'Ready for adventure?', + isStarred: true, +}, { + id: 1, + subject: 'Time to check in!', + isStarred: false, +}, { + id: 2, + subject: 'Festival Begins in Just SEVEN Days!', + isStarred: false, +}]; +``` + +```css +button { margin: 5px; } +li { border-radius: 5px; } +.highlighted { background: #d2eaff; } +``` + +</Sandpack> + +</Solution> + +#### Implement multiple selection {/*implement-multiple-selection*/} + +In this example, each `Letter` has an `isSelected` prop and an `onToggle` handler that marks it as selected. This works, but the state is stored as a `selectedId` (either `null` or an ID), so only one letter can get selected at any given time. + +Change the state structure to support multiple selection. (How would you structure it? Think about this before writing the code.) Each checkbox should become independent from the others. Clicking a selected letter should uncheck it. Finally, the footer should show the correct number of the selected items. + +<Hint> + +Instead of a single selected ID, you might want to hold an array or a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) of selected IDs in state. + +</Hint> + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { letters } from './data.js'; +import Letter from './Letter.js'; + +export default function MailClient() { + const [selectedId, setSelectedId] = useState(null); + + // TODO: allow multiple selection + const selectedCount = 1; + + function handleToggle(toggledId) { + // TODO: allow multiple selection + setSelectedId(toggledId); + } + + return ( + <> + <h2>Inbox</h2> + <ul> + {letters.map(letter => ( + <Letter + key={letter.id} + letter={letter} + isSelected={ + // TODO: allow multiple selection + letter.id === selectedId + } + onToggle={handleToggle} + /> + ))} + <hr /> + <p> + <b> + You selected {selectedCount} letters + </b> + </p> + </ul> + </> + ); +} +``` + +```js Letter.js +export default function Letter({ + letter, + onToggle, + isSelected, +}) { + return ( + <li className={ + isSelected ? 'selected' : '' + }> + <label> + <input + type="checkbox" + checked={isSelected} + onChange={() => { + onToggle(letter.id); + }} + /> + {letter.subject} + </label> + </li> + ) +} +``` + +```js data.js +export const letters = [{ + id: 0, + subject: 'Ready for adventure?', + isStarred: true, +}, { + id: 1, + subject: 'Time to check in!', + isStarred: false, +}, { + id: 2, + subject: 'Festival Begins in Just SEVEN Days!', + isStarred: false, +}]; +``` + +```css +input { margin: 5px; } +li { border-radius: 5px; } +label { width: 100%; padding: 5px; display: inline-block; } +.selected { background: #d2eaff; } +``` + +</Sandpack> + +<Solution> + +Instead of a single `selectedId`, keep a `selectedIds` *array* in state. For example, if you select the first and the last letter, it would contain `[0, 2]`. When nothing is selected, it would be an empty `[]` array: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { letters } from './data.js'; +import Letter from './Letter.js'; + +export default function MailClient() { + const [selectedIds, setSelectedIds] = useState([]); + + const selectedCount = selectedIds.length; + + function handleToggle(toggledId) { + // Was it previously selected? + if (selectedIds.includes(toggledId)) { + // Then remove this ID from the array. + setSelectedIds(selectedIds.filter(id => + id !== toggledId + )); + } else { + // Otherwise, add this ID to the array. + setSelectedIds([ + ...selectedIds, + toggledId + ]); + } + } + + return ( + <> + <h2>Inbox</h2> + <ul> + {letters.map(letter => ( + <Letter + key={letter.id} + letter={letter} + isSelected={ + selectedIds.includes(letter.id) + } + onToggle={handleToggle} + /> + ))} + <hr /> + <p> + <b> + You selected {selectedCount} letters + </b> + </p> + </ul> + </> + ); +} +``` + +```js Letter.js +export default function Letter({ + letter, + onToggle, + isSelected, +}) { + return ( + <li className={ + isSelected ? 'selected' : '' + }> + <label> + <input + type="checkbox" + checked={isSelected} + onChange={() => { + onToggle(letter.id); + }} + /> + {letter.subject} + </label> + </li> + ) +} +``` + +```js data.js +export const letters = [{ + id: 0, + subject: 'Ready for adventure?', + isStarred: true, +}, { + id: 1, + subject: 'Time to check in!', + isStarred: false, +}, { + id: 2, + subject: 'Festival Begins in Just SEVEN Days!', + isStarred: false, +}]; +``` + +```css +input { margin: 5px; } +li { border-radius: 5px; } +label { width: 100%; padding: 5px; display: inline-block; } +.selected { background: #d2eaff; } +``` + +</Sandpack> + +One minor downside of using an array is that for each item, you're calling `selectedIds.includes(letter.id)` to check whether it's selected. If the array is very large, this can become a performance problem because array search with [`includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) takes linear time, and you're doing this search for each individual item. + +To fix this, you can hold a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) in state instead, which provides a fast [`has()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) operation: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { letters } from './data.js'; +import Letter from './Letter.js'; + +export default function MailClient() { + const [selectedIds, setSelectedIds] = useState( + new Set() + ); + + const selectedCount = selectedIds.size; + + function handleToggle(toggledId) { + // Create a copy (to avoid mutation). + const nextIds = new Set(selectedIds); + if (nextIds.has(toggledId)) { + nextIds.delete(toggledId); + } else { + nextIds.add(toggledId); + } + setSelectedIds(nextIds); + } + + return ( + <> + <h2>Inbox</h2> + <ul> + {letters.map(letter => ( + <Letter + key={letter.id} + letter={letter} + isSelected={ + selectedIds.has(letter.id) + } + onToggle={handleToggle} + /> + ))} + <hr /> + <p> + <b> + You selected {selectedCount} letters + </b> + </p> + </ul> + </> + ); +} +``` + +```js Letter.js +export default function Letter({ + letter, + onToggle, + isSelected, +}) { + return ( + <li className={ + isSelected ? 'selected' : '' + }> + <label> + <input + type="checkbox" + checked={isSelected} + onChange={() => { + onToggle(letter.id); + }} + /> + {letter.subject} + </label> + </li> + ) +} +``` + +```js data.js +export const letters = [{ + id: 0, + subject: 'Ready for adventure?', + isStarred: true, +}, { + id: 1, + subject: 'Time to check in!', + isStarred: false, +}, { + id: 2, + subject: 'Festival Begins in Just SEVEN Days!', + isStarred: false, +}]; +``` + +```css +input { margin: 5px; } +li { border-radius: 5px; } +label { width: 100%; padding: 5px; display: inline-block; } +.selected { background: #d2eaff; } +``` + +</Sandpack> + +Now each item does a `selectedIds.has(letter.id)` check, which is very fast. + +Keep in mind that you [should not mutate objects in state](/learn/updating-objects-in-state), and that includes Sets, too. This is why the `handleToggle` function creates a *copy* of the Set first, and then updates that copy. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/conditional-rendering.md b/beta/src/content/learn/conditional-rendering.md new file mode 100644 index 000000000..a0486d246 --- /dev/null +++ b/beta/src/content/learn/conditional-rendering.md @@ -0,0 +1,766 @@ +--- +title: Conditional Rendering +--- + +<Intro> + +Your components will often need to display different things depending on different conditions. In React, you can conditionally render JSX using JavaScript syntax like `if` statements, `&&`, and `? :` operators. + +</Intro> + +<YouWillLearn> + +* How to return different JSX depending on a condition +* How to conditionally include or exclude a piece of JSX +* Common conditional syntax shortcuts you’ll encounter in React codebases + +</YouWillLearn> + +## Conditionally returning JSX {/*conditionally-returning-jsx*/} + +Let’s say you have a `PackingList` component rendering several `Item`s, which can be marked as packed or not: + +<Sandpack> + +```js +function Item({ name, isPacked }) { + return <li className="item">{name}</li>; +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +Notice that some of the `Item` components have their `isPacked` prop set to `true` instead of `false`. You want to add a checkmark (✔) to packed items if `isPacked={true}`. + +You can write this as an [`if`/`else` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) like so: + +```js +if (isPacked) { + return <li className="item">{name} ✔</li>; +} +return <li className="item">{name}</li>; +``` + +If the `isPacked` prop is `true`, this code **returns a different JSX tree.** With this change, some of the items get a checkmark at the end: + +<Sandpack> + +```js +function Item({ name, isPacked }) { + if (isPacked) { + return <li className="item">{name} ✔</li>; + } + return <li className="item">{name}</li>; +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +Try editing what gets returned in either case, and see how the result changes! + +Notice how you're creating branching logic with JavaScript's `if` and `return` statements. In React, control flow (like conditions) is handled by JavaScript. + +### Conditionally returning nothing with `null` {/*conditionally-returning-nothing-with-null*/} + +In some situations, you won't want to render anything at all. For example, say you don't want to show packed items at all. A component must return something. In this case, you can return `null`: + +```js +if (isPacked) { + return null; +} +return <li className="item">{name}</li>; +``` + +If `isPacked` is true, the component will return nothing, `null`. Otherwise, it will return JSX to render. + +<Sandpack> + +```js +function Item({ name, isPacked }) { + if (isPacked) { + return null; + } + return <li className="item">{name}</li>; +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +In practice, returning `null` from a component isn't common because it might surprise a developer trying to render it. More often, you would conditionally include or exclude the component in the parent component's JSX. Here's how to do that! + +## Conditionally including JSX {/*conditionally-including-jsx*/} + +In the previous example, you controlled which (if any!) JSX tree would be returned by the component. You may already have noticed some duplication in the render output: + +```js +<li className="item">{name} ✔</li> +``` + +is very similar to + +```js +<li className="item">{name}</li> +``` + +Both of the conditional branches return `<li className="item">...</li>`: + +```js +if (isPacked) { + return <li className="item">{name} ✔</li>; +} +return <li className="item">{name}</li>; +``` + +While this duplication isn't harmful, it could make your code harder to maintain. What if you want to change the `className`? You'd have to do it in two places in your code! In such a situation, you could conditionally include a little JSX to make your code more [DRY.](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) + +### Conditional (ternary) operator (`? :`) {/*conditional-ternary-operator--*/} + +JavaScript has a compact syntax for writing a conditional expression -- the [conditional operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) or "ternary operator". + +Instead of this: + +```js +if (isPacked) { + return <li className="item">{name} ✔</li>; +} +return <li className="item">{name}</li>; +``` + +You can write this: + +```js +return ( + <li className="item"> + {isPacked ? name + ' ✔' : name} + </li> +); +``` + +You can read it as *"if `isPacked` is true, then (`?`) render `name + ' ✔'`, otherwise (`:`) render `name`"*. + +<DeepDive> + +#### Are these two examples fully equivalent? {/*are-these-two-examples-fully-equivalent*/} + +If you're coming from an object-oriented programming background, you might assume that the two examples above are subtly different because one of them may create two different "instances" of `<li>`. But JSX elements aren't "instances" because they don't hold any internal state and aren't real DOM nodes. They're lightweight descriptions, like blueprints. So these two examples, in fact, *are* completely equivalent. [Preserving and Resetting State](/learn/preserving-and-resetting-state) goes into detail about how this works. + +</DeepDive> + +Now let's say you want to wrap the completed item's text into another HTML tag, like `<del>` to strike it out. You can add even more newlines and parentheses so that it's easier to nest more JSX each of the cases: + +<Sandpack> + +```js +function Item({ name, isPacked }) { + return ( + <li className="item"> + {isPacked ? ( + <del> + {name + ' ✔'} + </del> + ) : ( + name + )} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +This style works well for simple conditions, but use it in moderation. If your components get messy with too much nested conditional markup, consider extracting child components to clean things up. In React, markup is a part of your code, so you can use tools like variables and functions to tidy up complex expressions. + +### Logical AND operator (`&&`) {/*logical-and-operator-*/} + +Another common shortcut you'll encounter is the [JavaScript logical AND (`&&`) operator.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND#:~:text=The%20logical%20AND%20(%20%26%26%20)%20operator,it%20returns%20a%20Boolean%20value.) Inside React components, it often comes up when you want to render some JSX when the condition is true, **or render nothing otherwise.** With `&&`, you could conditionally render the checkmark only if `isPacked` is `true`: + +```js +return ( + <li className="item"> + {name} {isPacked && '✔'} + </li> +); +``` + +You can read this as *"if `isPacked`, then (`&&`) render the checkmark, otherwise, render nothing"*. + +Here it is in action: + +<Sandpack> + +```js +function Item({ name, isPacked }) { + return ( + <li className="item"> + {name} {isPacked && '✔'} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +A [JavaScript && expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND) returns the value of its right side (in our case, the checkmark) if the left side (our condition) is `true`. But if the condition is `false`, the whole expression becomes `false`. React considers `false` as a "hole" in the JSX tree, just like `null` or `undefined`, and doesn't render anything in its place. + + +<Pitfall> + +**Don't put numbers on the left side of `&&`.** + +To test the condition, JavaScript converts the left side to a boolean automatically. However, if the left side is `0`, then the whole expression gets that value (`0`), and React will happily render `0` rather than nothing. + +For example, a common mistake is to write code like `messageCount && <p>New messages</p>`. It's easy to assume that it renders nothing when `messageCount` is `0`, but it really renders the `0` itself! + +To fix it, make the left side a boolean: `messageCount > 0 && <p>New messages</p>`. + +</Pitfall> + +### Conditionally assigning JSX to a variable {/*conditionally-assigning-jsx-to-a-variable*/} + +When the shortcuts get in the way of writing plain code, try using an `if` statement and a variable. You can reassign variables defined with [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), so start by providing the default content you want to display, the name: + +```js +let itemContent = name; +``` + +Use an `if` statement to reassign a JSX expression to `itemContent` if `isPacked` is `true`: + +```js +if (isPacked) { + itemContent = name + " ✔"; +} +``` + +[Curly braces open the "window into JavaScript".](/learn/javascript-in-jsx-with-curly-braces#using-curly-braces-a-window-into-the-javascript-world) Embed the variable with curly braces in the returned JSX tree, nesting the previously calculated expression inside of JSX: + +```js +<li className="item"> + {itemContent} +</li> +``` + +This style is the most verbose, but it's also the most flexible. Here it is in action: + +<Sandpack> + +```js +function Item({ name, isPacked }) { + let itemContent = name; + if (isPacked) { + itemContent = name + " ✔"; + } + return ( + <li className="item"> + {itemContent} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +Like before, this works not only for text, but for arbitrary JSX too: + +<Sandpack> + +```js +function Item({ name, isPacked }) { + let itemContent = name; + if (isPacked) { + itemContent = ( + <del> + {name + " ✔"} + </del> + ); + } + return ( + <li className="item"> + {itemContent} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +If you're not familiar with JavaScript, this variety of styles might seem overwhelming at first. However, learning them will help you read and write any JavaScript code -- and not just React components! Pick the one you prefer for a start, and then consult this reference again if you forget how the other ones work. + +<Recap> + +* In React, you control branching logic with JavaScript. +* You can return a JSX expression conditionally with an `if` statement. +* You can conditionally save some JSX to a variable and then include it inside other JSX by using the curly braces. +* In JSX, `{cond ? <A /> : <B />}` means *"if `cond`, render `<A />`, otherwise `<B />`"*. +* In JSX, `{cond && <A />}` means *"if `cond`, render `<A />`, otherwise nothing"*. +* The shortcuts are common, but you don't have to use them if you prefer plain `if`. + +</Recap> + + + +<Challenges> + +#### Show an icon for incomplete items with `? :` {/*show-an-icon-for-incomplete-items-with--*/} + +Use the conditional operator (`cond ? a : b`) to render a ❌ if `isPacked` isn’t `true`. + +<Sandpack> + +```js +function Item({ name, isPacked }) { + return ( + <li className="item"> + {name} {isPacked && '✔'} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +<Solution> + +<Sandpack> + +```js +function Item({ name, isPacked }) { + return ( + <li className="item"> + {name} {isPacked ? '✔' : '❌'} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +</Solution> + +#### Show the item importance with `&&` {/*show-the-item-importance-with-*/} + +In this example, each `Item` receives a numerical `importance` prop. Use the `&&` operator to render "_(Importance: X)_" in italics, but only for items that have non-zero importance. Your item list should end up looking like this: + +* Space suit _(Importance: 9)_ +* Helmet with a golden leaf +* Photo of Tam _(Importance: 6)_ + +Don't forget to add a space between the two labels! + +<Sandpack> + +```js +function Item({ name, importance }) { + return ( + <li className="item"> + {name} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + importance={9} + name="Space suit" + /> + <Item + importance={0} + name="Helmet with a golden leaf" + /> + <Item + importance={6} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +<Solution> + +This should do the trick: + +<Sandpack> + +```js +function Item({ name, importance }) { + return ( + <li className="item"> + {name} + {importance > 0 && ' '} + {importance > 0 && + <i>(Importance: {importance})</i> + } + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + importance={9} + name="Space suit" + /> + <Item + importance={0} + name="Helmet with a golden leaf" + /> + <Item + importance={6} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +Note that you must write `importance > 0 && ...` rather than `importance && ...` so that if the `importance` is `0`, `0` isn't rendered as the result! + +In this solution, two separate conditions are used to insert a space between then name and the importance label. Alternatively, you could use a fragment with a leading space: `importance > 0 && <> <i>...</i></>` or add a space immediately inside the `<i>`: `importance > 0 && <i> ...</i>`. + +</Solution> + +#### Refactor a series of `? :` to `if` and variables {/*refactor-a-series-of---to-if-and-variables*/} + +This `Drink` component uses a series of `? :` conditions to show different information depending on whether the `name` prop is `"tea"` or `"coffee"`. The problem is that the information about each drink is spread across multiple conditions. Refactor this code to use a single `if` statement instead of three `? :` conditions. + +<Sandpack> + +```js +function Drink({ name }) { + return ( + <section> + <h1>{name}</h1> + <dl> + <dt>Part of plant</dt> + <dd>{name === 'tea' ? 'leaf' : 'bean'}</dd> + <dt>Caffeine content</dt> + <dd>{name === 'tea' ? '15–70 mg/cup' : '80–185 mg/cup'}</dd> + <dt>Age</dt> + <dd>{name === 'tea' ? '4,000+ years' : '1,000+ years'}</dd> + </dl> + </section> + ); +} + +export default function DrinkList() { + return ( + <div> + <Drink name="tea" /> + <Drink name="coffee" /> + </div> + ); +} +``` + +</Sandpack> + +Once you've refactored the code to use `if`, do you have further ideas on how to simplify it? + +<Solution> + +There are multiple ways you could go about this, but here is one starting point: + +<Sandpack> + +```js +function Drink({ name }) { + let part, caffeine, age; + if (name === 'tea') { + part = 'leaf'; + caffeine = '15–70 mg/cup'; + age = '4,000+ years'; + } else if (name === 'coffee') { + part = 'bean'; + caffeine = '80–185 mg/cup'; + age = '1,000+ years'; + } + return ( + <section> + <h1>{name}</h1> + <dl> + <dt>Part of plant</dt> + <dd>{part}</dd> + <dt>Caffeine content</dt> + <dd>{caffeine}</dd> + <dt>Age</dt> + <dd>{age}</dd> + </dl> + </section> + ); +} + +export default function DrinkList() { + return ( + <div> + <Drink name="tea" /> + <Drink name="coffee" /> + </div> + ); +} +``` + +</Sandpack> + +Here the information about each drink is grouped together instead of being spread across multiple conditions. This makes it easier to add more drinks in the future. + +Another solution would be to remove the condition altogether by moving the information into objects: + +<Sandpack> + +```js +const drinks = { + tea: { + part: 'leaf', + caffeine: '15–70 mg/cup', + age: '4,000+ years' + }, + coffee: { + part: 'bean', + caffeine: '80–185 mg/cup', + age: '1,000+ years' + } +}; + +function Drink({ name }) { + const info = drinks[name]; + return ( + <section> + <h1>{name}</h1> + <dl> + <dt>Part of plant</dt> + <dd>{info.part}</dd> + <dt>Caffeine content</dt> + <dd>{info.caffeine}</dd> + <dt>Age</dt> + <dd>{info.age}</dd> + </dl> + </section> + ); +} + +export default function DrinkList() { + return ( + <div> + <Drink name="tea" /> + <Drink name="coffee" /> + </div> + ); +} +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/describing-the-ui.md b/beta/src/content/learn/describing-the-ui.md new file mode 100644 index 000000000..6df4c0475 --- /dev/null +++ b/beta/src/content/learn/describing-the-ui.md @@ -0,0 +1,530 @@ +--- +title: Describing the UI +--- + +<Intro> + +React is a JavaScript library for rendering user interfaces (UI). UI is built from small units like buttons, text, and images. React lets you combine them into reusable, nestable *components.* From web sites to phone apps, everything on the screen can be broken down into components. In this chapter, you'll learn to create, customize, and conditionally display React components. + +</Intro> + +<YouWillLearn isChapter={true}> + +* [How to write your first React component](/learn/your-first-component) +* [When and how to create multi-component files](/learn/importing-and-exporting-components) +* [How to add markup to JavaScript with JSX](/learn/writing-markup-with-jsx) +* [How to use curly braces with JSX to access JavaScript functionality from your components](/learn/javascript-in-jsx-with-curly-braces) +* [How to configure components with props](/learn/passing-props-to-a-component) +* [How to conditionally render components](/learn/conditional-rendering) +* [How to render multiple components at a time](/learn/rendering-lists) +* [How to avoid confusing bugs by keeping components pure](/learn/keeping-components-pure) + +</YouWillLearn> + +## Your first component {/*your-first-component*/} + +React applications are built from isolated pieces of UI called *components*. A React component is a JavaScript function that you can sprinkle with markup. Components can be as small as a button, or as large as an entire page. Here is a `Gallery` component rendering three `Profile` components: + +<Sandpack> + +```js +function Profile() { + return ( + <img + src="https://i.imgur.com/MK3eW3As.jpg" + alt="Katherine Johnson" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +<LearnMore path="/learn/your-first-component"> + +Read **[Your First Component](/learn/your-first-component)** to learn how to declare and use React components. + +</LearnMore> + +## Importing and exporting components {/*importing-and-exporting-components*/} + +You can declare many components in one file, but large files can get difficult to navigate. To solve this, you can *export* a component into its own file, and then *import* that component from another file: + + +<Sandpack> + +```js App.js hidden +import Gallery from './Gallery.js'; + +export default function App() { + return ( + <Gallery /> + ); +} +``` + +```js Gallery.js active +import Profile from './Profile.js'; + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```js Profile.js +export default function Profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; } +``` + +</Sandpack> + +<LearnMore path="/learn/importing-and-exporting-components"> + +Read **[Importing and Exporting Components](/learn/importing-and-exporting-components)** to learn how to split components into their own files. + +</LearnMore> + +## Writing markup with JSX {/*writing-markup-with-jsx*/} + +Each React component is a JavaScript function that may contain some markup that React renders into the browser. React components use a syntax extension called JSX to represent that markup. JSX looks a lot like HTML, but it is a bit stricter and can display dynamic information. + +If we paste existing HTML markup into a React component, it won't always work: + +<Sandpack> + +```js +export default function TodoList() { + return ( + // This doesn't quite work! + <h1>Hedy Lamarr's Todos</h1> + <img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + class="photo" + > + <ul> + <li>Invent new traffic lights + <li>Rehearse a movie scene + <li>Improve spectrum technology + </ul> + ); +} +``` + +```css +img { height: 90px; } +``` + +</Sandpack> + +If you have existing HTML like this, you can fix it using a [converter](https://transform.tools/html-to-jsx): + +<Sandpack> + +```js +export default function TodoList() { + return ( + <> + <h1>Hedy Lamarr's Todos</h1> + <img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + className="photo" + /> + <ul> + <li>Invent new traffic lights</li> + <li>Rehearse a movie scene</li> + <li>Improve spectrum technology</li> + </ul> + </> + ); +} +``` + +```css +img { height: 90px; } +``` + +</Sandpack> + +<LearnMore path="/learn/writing-markup-with-jsx"> + +Read **[Writing Markup with JSX](/learn/writing-markup-with-jsx)** to learn how to write valid JSX. + +</LearnMore> + +## JavaScript in JSX with curly braces {/*javascript-in-jsx-with-curly-braces*/} + +JSX lets you write HTML-like markup inside a JavaScript file, keeping rendering logic and content in the same place. Sometimes you will want to add a little JavaScript logic or reference a dynamic property inside that markup. In this situation, you can use curly braces in your JSX to "open a window" to JavaScript: + +<Sandpack> + +```js +const person = { + name: 'Gregorio Y. Zara', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src="https://i.imgur.com/7vQD0fPs.jpg" + alt="Gregorio Y. Zara" + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +<LearnMore path="/learn/javascript-in-jsx-with-curly-braces"> + +Read **[JavaScript in JSX with Curly Braces](/learn/javascript-in-jsx-with-curly-braces)** to learn how to access JavaScript data from JSX. + +</LearnMore> + +## Passing props to a component {/*passing-props-to-a-component*/} + +React components use *props* to communicate with each other. Every parent component can pass some information to its child components by giving them props. Props might remind you of HTML attributes, but you can pass any JavaScript value through them, including objects, arrays, functions, and even JSX! + +<Sandpack> + +```js +import { getImageUrl } from './utils.js' + +export default function Profile() { + return ( + <Card> + <Avatar + size={100} + person={{ + name: 'Katsuko Saruhashi', + imageId: 'YfeOqp2' + }} + /> + </Card> + ); +} + +function Avatar({ person, size }) { + return ( + <img + className="avatar" + src={getImageUrl(person)} + alt={person.name} + width={size} + height={size} + /> + ); +} + +function Card({ children }) { + return ( + <div className="card"> + {children} + </div> + ); +} + +``` + +```js utils.js +export function getImageUrl(person, size = 's') { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.card { + width: fit-content; + margin: 5px; + padding: 5px; + font-size: 20px; + text-align: center; + border: 1px solid #aaa; + border-radius: 20px; + background: #fff; +} +.avatar { + margin: 20px; + border-radius: 50%; +} +``` + +</Sandpack> + +<LearnMore path="/learn/passing-props-to-a-component"> + +Read **[Passing Props to a Component](/learn/passing-props-to-a-component)** to learn how to pass and read props. + +</LearnMore> + +## Conditional rendering {/*conditional-rendering*/} + +Your components will often need to display different things depending on different conditions. In React, you can conditionally render JSX using JavaScript syntax like `if` statements, `&&`, and `? :` operators. + +In this example, the JavaScript `&&` operator is used to conditionally render a checkmark: + +<Sandpack> + +```js +function Item({ name, isPacked }) { + return ( + <li className="item"> + {name} {isPacked && '✔'} + </li> + ); +} + +export default function PackingList() { + return ( + <section> + <h1>Sally Ride's Packing List</h1> + <ul> + <Item + isPacked={true} + name="Space suit" + /> + <Item + isPacked={true} + name="Helmet with a golden leaf" + /> + <Item + isPacked={false} + name="Photo of Tam" + /> + </ul> + </section> + ); +} +``` + +</Sandpack> + +<LearnMore path="/learn/conditional-rendering"> + +Read **[Conditional Rendering](/learn/conditional-rendering)** to learn the different ways to render content conditionally. + +</LearnMore> + +## Rendering lists {/*rendering-lists*/} + +You will often want to display multiple similar components from a collection of data. You can use JavaScript's `filter()` and `map()` with React to filter and transform your array of data into an array of components. + +For each array item, you will need to specify a `key`. Usually, you will want to use an ID from the database as a `key`. Keys let React keep track of each item's place in the list even if the list changes. + +<Sandpack> + +```js App.js +import { people } from './data.js'; +import { getImageUrl } from './utils.js'; + +export default function List() { + const listItems = people.map(person => + <li key={person.id}> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + ); + return ( + <article> + <h1>Scientists</h1> + <ul>{listItems}</ul> + </article> + ); +} +``` + +```js data.js +export const people = [{ + id: 0, + name: 'Creola Katherine Johnson', + profession: 'mathematician', + accomplishment: 'spaceflight calculations', + imageId: 'MK3eW3A' +}, { + id: 1, + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', + accomplishment: 'discovery of Arctic ozone hole', + imageId: 'mynHUSa' +}, { + id: 2, + name: 'Mohammad Abdus Salam', + profession: 'physicist', + accomplishment: 'electromagnetism theory', + imageId: 'bE7W1ji' +}, { + id: 3, + name: 'Percy Lavon Julian', + profession: 'chemist', + accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', + imageId: 'IOjWm71' +}, { + id: 4, + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', + accomplishment: 'white dwarf star mass calculations', + imageId: 'lrWQx8l' +}]; +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + 's.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; +} +img { width: 100px; height: 100px; border-radius: 50%; } +h1 { font-size: 22px; } +h2 { font-size: 20px; } +``` + +</Sandpack> + +<LearnMore path="/learn/rendering-lists"> + +Read **[Rendering Lists](/learn/rendering-lists)** to learn how to render a list of components, and how to choose a key. + +</LearnMore> + +## Keeping components pure {/*keeping-components-pure*/} + +Some JavaScript functions are *pure.* A pure function: + +* **Minds its own business.** It does not change any objects or variables that existed before it was called. +* **Same inputs, same output.** Given the same inputs, a pure function should always return the same result. + +By strictly only writing your components as pure functions, you can avoid an entire class of baffling bugs and unpredictable behavior as your codebase grows. Here is an example of an impure component: + +<Sandpack> + +```js +let guest = 0; + +function Cup() { + // Bad: changing a preexisting variable! + guest = guest + 1; + return <h2>Tea cup for guest #{guest}</h2>; +} + +export default function TeaSet() { + return ( + <> + <Cup /> + <Cup /> + <Cup /> + </> + ); +} +``` + +</Sandpack> + +You can make this component pure by passing a prop instead of modifying a preexisting variable: + +<Sandpack> + +```js +function Cup({ guest }) { + return <h2>Tea cup for guest #{guest}</h2>; +} + +export default function TeaSet() { + return ( + <> + <Cup guest={1} /> + <Cup guest={2} /> + <Cup guest={3} /> + </> + ); +} +``` + +</Sandpack> + +<LearnMore path="/learn/keeping-components-pure"> + +Read **[Keeping Components Pure](/learn/keeping-components-pure)** to learn how to write components as pure, predictable functions. + +</LearnMore> + +## What's next? {/*whats-next*/} + +Head over to [Your First Component](/learn/your-first-component) to start reading this chapter page by page! + +Or, if you're already familiar with these topics, why not read about [Adding Interactivity](/learn/adding-interactivity)? diff --git a/beta/src/content/learn/editor-setup.md b/beta/src/content/learn/editor-setup.md new file mode 100644 index 000000000..0b7f55418 --- /dev/null +++ b/beta/src/content/learn/editor-setup.md @@ -0,0 +1,62 @@ +--- +title: Editor Setup +--- + +<Intro> + +A properly configured editor can make code clearer to read and faster to write. It can even help you catch bugs as you write them! If this is your first time setting up an editor or you're looking to tune up your current editor, we have a few recommendations. + +</Intro> + +<YouWillLearn> + +* What the most popular editors are +* How to format your code automatically + +</YouWillLearn> + +## Your editor {/*your-editor*/} + +[VS Code](https://code.visualstudio.com/) is one of the most popular editors in use today. It has a large marketplace of extensions and integrates well with popular services like GitHub. Most of the features listed below can be added to VS Code as extensions as well, making it highly configurable! + +Other popular text editors used in the React community include: + +* [WebStorm](https://www.jetbrains.com/webstorm/) is an integrated development environment designed specifically for JavaScript. +* [Sublime Text](https://www.sublimetext.com/) has support for JSX and TypeScript, [syntax highlighting](https://stackoverflow.com/a/70960574/458193) and autocomplete built in. +* [Vim](https://www.vim.org/) is a highly configurable text editor built to make creating and changing any kind of text very efficient. It is included as "vi" with most UNIX systems and with Apple OS X. + +## Recommended text editor features {/*recommended-text-editor-features*/} + +Some editors come with these features built in, but others might require adding an extension. Check to see what support your editor of choice provides to be sure! + +### Linting {/*linting*/} + +Code linters find problems in your code as you write, helping you fix them early. [ESLint](https://eslint.org/) is a popular, open source linter for JavaScript. + +* [Install ESLint with the recommended configuration for React](https://www.npmjs.com/package/eslint-config-react-app) (be sure you have [Node installed!](https://nodejs.org/en/download/current/)) +* [Integrate ESLint in VSCode with the official extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + +**Make sure that you've enabled all the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks) rules for your project.** They are essential and catch the most severe bugs early. The recommended [`eslint-config-react-app`](https://www.npmjs.com/package/eslint-config-react-app) preset already includes them. + +### Formatting {/*formatting*/} + +The last thing you want to do when sharing your code with another contributor is get into an discussion about [tabs vs spaces](https://www.google.com/search?q=tabs+vs+spaces)! Fortunately, [Prettier](https://prettier.io/) will clean up your code by reformatting it to conform to preset, configurable rules. Run Prettier, and all your tabs will be converted to spaces—and your indentation, quotes, etc will also all be changed to conform to the configuration. In the ideal setup, Prettier will run when you save your file, quickly making these edits for you. + +You can install the [Prettier extension in VSCode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) by following these steps: + +1. Launch VS Code +2. Use Quick Open (press Ctrl/Cmd+P) +3. Paste in `ext install esbenp.prettier-vscode` +4. Press Enter + +#### Formatting on save {/*formatting-on-save*/} + +Ideally, you should format your code on every save. VS Code has settings for this! + +1. In VS Code, press `CTRL/CMD + SHIFT + P`. +2. Type "settings" +3. Hit Enter +4. In the search bar, type "format on save" +5. Be sure the "format on save" option is ticked! + +> If your ESLint preset has formatting rules, they may conflict with Prettier. We recommend to disable all formatting rules in your ESLint preset using [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) so that ESLint is *only* used for catching logical mistakes. If you want to enforce that files are formatted before a pull request is merged, use [`prettier --check`](https://prettier.io/docs/en/cli.html#--check) for your continuous integration. diff --git a/beta/src/content/learn/escape-hatches.md b/beta/src/content/learn/escape-hatches.md new file mode 100644 index 000000000..dd4119bd6 --- /dev/null +++ b/beta/src/content/learn/escape-hatches.md @@ -0,0 +1,848 @@ +--- +title: Escape Hatches +--- + +<Intro> + +Some of your components may need to control and synchronize with systems outside of React. For example, you might need to focus an input using the browser API, play and pause a video player implemented without React, or connect and listen to messages from a remote server. In this chapter, you'll learn the escape hatches that let you "step outside" React and connect to external systems. Most of your application logic and data flow should not rely on these features. + +</Intro> + +<YouWillLearn isChapter={true}> + +* [How to "remember" information without re-rendering](/learn/referencing-values-with-refs) +* [How to access DOM elements managed by React](/learn/manipulating-the-dom-with-refs) +* [How to synchronize components with external systems](/learn/synchronizing-with-effects) +* [How to remove unnecessary Effects from your components](/learn/you-might-not-need-an-effect) +* [How an Effect's lifecycle is different from a component's](/learn/lifecycle-of-reactive-effects) +* [How to prevent some values from re-triggering Effects](/learn/separating-events-from-effects) +* [How to make your Effect re-run less often](/learn/removing-effect-dependencies) +* [How to share logic between components](/learn/reusing-logic-with-custom-hooks) + +</YouWillLearn> + +## Referencing values with refs {/*referencing-values-with-refs*/} + +When you want a component to "remember" some information, but you don't want that information to [trigger new renders](/learn/render-and-commit), you can use a *ref*: + +```js +const ref = useRef(0); +``` + +Like state, refs are retained by React between re-renders. However, setting state re-renders a component. Changing a ref does not! You can access the current value of that ref through the `ref.current` property. + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Counter() { + let ref = useRef(0); + + function handleClick() { + ref.current = ref.current + 1; + alert('You clicked ' + ref.current + ' times!'); + } + + return ( + <button onClick={handleClick}> + Click me! + </button> + ); +} +``` + +</Sandpack> + +A ref is like a secret pocket of your component that React doesn't track. For example, you can use refs to store [timeout IDs](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#return_value), [DOM elements](https://developer.mozilla.org/en-US/docs/Web/API/Element), and other objects that don't impact the component's rendering output. + +<LearnMore path="/learn/referencing-values-with-refs"> + +Read **[Referencing Values with Refs](/learn/referencing-values-with-refs)** to learn how to use refs to remember information. + +</LearnMore> + +## Manipulating the DOM with refs {/*manipulating-the-dom-with-refs*/} + +React automatically updates the DOM to match your render output, so your components won't often need to manipulate it. However, sometimes you might need access to the DOM elements managed by React—for example, to focus a node, scroll to it, or measure its size and position. There is no built-in way to do those things in React, so you will need a ref to the DOM node. For example, clicking the button will focus the input using a ref: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <input ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +<LearnMore path="/learn/manipulating-the-dom-with-refs"> + +Read **[Manipulating the DOM with Refs](/learn/manipulating-the-dom-with-refs)** to learn how to access DOM elements managed by React. + +</LearnMore> + +## Synchronizing with Effects {/*synchronizing-with-effects*/} + +Some components need to synchronize with external systems. For example, you might want to control a non-React component based on the React state, set up a server connection, or send an analytics log when a component appears on the screen. Unlike event handlers, which let you handle particular events, *Effects* let you run some code after rendering. Use them to synchronize your component with some system outside of React. + +Press Play/Pause multiple times and see how the video player stays synchronized to the `isPlaying` prop value: + +<Sandpack> + +```js +import { useState, useRef, useEffect } from 'react'; + +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + + useEffect(() => { + if (isPlaying) { + ref.current.play(); + } else { + ref.current.pause(); + } + }, [isPlaying]); + + return <video ref={ref} src={src} loop playsInline />; +} + +export default function App() { + const [isPlaying, setIsPlaying] = useState(false); + return ( + <> + <button onClick={() => setIsPlaying(!isPlaying)}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <VideoPlayer + isPlaying={isPlaying} + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + /> + </> + ); +} +``` + +```css +button { display: block; margin-bottom: 20px; } +video { width: 250px; } +``` + +</Sandpack> + +Many Effects also need to "clean up" after themselves. For example, if your Effect sets up a connection to a chat server, it should return a *cleanup function* that tells React how to disconnect your component from that server: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export default function ChatRoom() { + useEffect(() => { + const connection = createConnection(); + connection.connect(); + return () => connection.disconnect(); + }, []); + return <h1>Welcome to the chat!</h1>; +} +``` + +```js chat.js +export function createConnection() { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting...'); + }, + disconnect() { + console.log('❌ Disconnected.'); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +In development, React will immediately run and clean up your Effect one extra time. This is why you see `"✅ Connecting..."` printed twice. This ensures that you don't forget to implement the cleanup function. + +<LearnMore path="/learn/synchronizing-with-effects"> + +Read **[Synchronizing with Effects](/learn/synchronizing-with-effects)** to learn how to synchronize components with external systems. + +</LearnMore> + +## You Might Not Need An Effect {/*you-might-not-need-an-effect*/} + +Effects are an escape hatch from the React paradigm. They let you "step outside" of React and synchronize your components with some external system. If there is no external system involved (for example, if you want to update a component's state when some props or state change), you shouldn't need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone. + +There are two common cases in which you don't need Effects: +- **You don't need Effects to transform data for rendering.** +- **You don't need Effects to handle user events.** + +For example, you don't need an Effect to adjust some state based on other state: + +```js {5-9} +function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(firstName + ' ' + lastName); + }, [firstName, lastName]); + // ... +} +``` + +Instead, calculate as much as you can while rendering: + +```js {4-5} +function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + // ✅ Good: calculated during rendering + const fullName = firstName + ' ' + lastName; + // ... +} +``` + +However, you *do* need Effects to synchronize with external systems. + +<LearnMore path="/learn/you-might-not-need-an-effect"> + +Read **[You Might Not Need an Effect](/learn/you-might-not-need-an-effect)** to learn how to remove unnecessary Effects. + +</LearnMore> + +## Lifecycle of reactive effects {/*lifecycle-of-reactive-effects*/} + +Effects have a different lifecycle from components. Components may mount, update, or unmount. An Effect can only do two things: to start synchronizing something, and later to stop synchronizing it. This cycle can happen multiple times if your Effect depends on props and state that change over time. + +This Effect depends on the value of the `roomId` prop. Props are *reactive values,* which means they can change on a re-render. Notice that the Effect *re-synchronizes* (and re-connects to the server) after you update the `roomId`: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1>; +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +React provides a linter rule to check that you've specified your Effect's dependencies correctly. If you forget to specify `roomId` in the list of dependencies in the above example, the linter will find that bug automatically. + +<LearnMore path="/learn/lifecycle-of-reactive-effects"> + +Read **[Lifecycle of Reactive Events](/learn/lifecycle-of-reactive-effects)** to learn how an Effect's lifecycle is different from a component's. + +</LearnMore> + +## Separating events from Effects {/*separating-events-from-effects*/} + +<Wip> + +This section describes an **experimental API that has not yet been added to React,** so you can't use it yet. + +</Wip> + +Event handlers only re-run when you perform the same interaction again. Unlike event handlers, Effects re-synchronize if some value they read, like a prop or a state variable, is different from what it was on last render. Sometimes, you want a mix of both behaviors: an Effect that re-runs in response to some values but not others. + +All code inside Effects is *reactive.* It will run again if some reactive value it reads has changed due to a re-render. For example, this Effect will re-connect to the chat if either `roomId` or `theme` have changed after interaction: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { createConnection, sendMessage } from './chat.js'; +import { showNotification } from './notifications.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId, theme }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + showNotification('Connected!', theme); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, theme]); + + return <h1>Welcome to the {roomId} room!</h1> +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <hr /> + <ChatRoom + roomId={roomId} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + let connectedCallback; + let timeout; + return { + connect() { + timeout = setTimeout(() => { + if (connectedCallback) { + connectedCallback(); + } + }, 100); + }, + on(event, callback) { + if (connectedCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'connected') { + throw Error('Only "connected" event is supported.'); + } + connectedCallback = callback; + }, + disconnect() { + clearTimeout(timeout); + } + }; +} +``` + +```js notifications.js +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label { display: block; margin-top: 10px; } +``` + +</Sandpack> + +This is not ideal. You want to re-connect to the chat only if the `roomId` has changed. Switching the `theme` shouldn't re-connect to the chat! Move the code reading `theme` out of your Effect into an *Event function*: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { createConnection, sendMessage } from './chat.js'; +import { showNotification } from './notifications.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Connected!', theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + onConnected(); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1> +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <hr /> + <ChatRoom + roomId={roomId} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + let connectedCallback; + let timeout; + return { + connect() { + timeout = setTimeout(() => { + if (connectedCallback) { + connectedCallback(); + } + }, 100); + }, + on(event, callback) { + if (connectedCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'connected') { + throw Error('Only "connected" event is supported.'); + } + connectedCallback = callback; + }, + disconnect() { + clearTimeout(timeout); + } + }; +} +``` + +```js notifications.js hidden +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label { display: block; margin-top: 10px; } +``` + +</Sandpack> + +Code inside Event functions isn't reactive, so changing the `theme` no longer makes your Effect re-connect. + +<LearnMore path="/learn/separating-events-from-effects"> + +Read **[Separating Events from Effects](/learn/separating-events-from-effects)** to learn how to prevent some values from re-triggering Effects. + +</LearnMore> + +## Removing Effect dependencies {/*removing-effect-dependencies*/} + +When you write an Effect, the linter will verify that you've included every reactive value (like props and state) that the Effect reads in the list of your Effect's dependencies. This ensures that your Effect remains synchronized with the latest props and state of your component. Unnecessary dependencies may cause your Effect to run too often, or even create an infinite loop. The way you remove them depends on the case. + +For example, this Effect depends on the `options` object which gets re-created every time you edit the input: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input value={message} onChange={e => setMessage(e.target.value)} /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +You don't want the chat to re-connect every time you start typing a message in that chat. To fix this problem, move creation of the `options` object inside the Effect so that the Effect only depends on the `roomId` string: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input value={message} onChange={e => setMessage(e.target.value)} /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Notice that you didn't start by editing the dependency list to remove the `options` dependency. That would be wrong. Instead, you changed the surrounding code so that the dependency became *unnecessary.* You can think of the dependency list as a list of all the reactive values used by your Effect's code. You don't intentionally choose what to put on that list. The list describes your code. To change the dependency list, change the code. + +<LearnMore path="/learn/removing-effect-dependencies"> + +Read **[Removing Effect Dependencies](/learn/removing-effect-dependencies)** to learn how to make your Effect re-run less often. + +</LearnMore> + +## Reusing logic with custom Hooks {/*reusing-logic-with-custom-hooks*/} + +React comes with built-in Hooks like `useState`, `useContext`, and `useEffect`. Sometimes, you’ll wish that there was a Hook for some more specific purpose: for example, to fetch data, to keep track of whether the user is online, or to connect to a chat room. To do this, you can create your own Hooks for your application's needs. + +In this example, the `usePointerPosition` custom Hook tracks the cursor position, while `useDelayedValue` custom Hook returns a value that's "lagging behind" the value you passed by a certain number of milliseconds. Move the cursor over the sandbox preview area to see a moving trail of dots following the cursor: + +<Sandpack> + +```js +import { usePointerPosition } from './usePointerPosition.js'; +import { useDelayedValue } from './useDelayedValue.js'; + +export default function Canvas() { + const pos1 = usePointerPosition(); + const pos2 = useDelayedValue(pos1, 100); + const pos3 = useDelayedValue(pos2, 200); + const pos4 = useDelayedValue(pos3, 100); + const pos5 = useDelayedValue(pos3, 50); + return ( + <> + <Dot position={pos1} opacity={1} /> + <Dot position={pos2} opacity={0.8} /> + <Dot position={pos3} opacity={0.6} /> + <Dot position={pos4} opacity={0.4} /> + <Dot position={pos5} opacity={0.2} /> + </> + ); +} + +function Dot({ position, opacity }) { + return ( + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + ); +} +``` + +```js usePointerPosition.js +import { useState, useEffect } from 'react'; + +export function usePointerPosition() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + useEffect(() => { + function handleMove(e) { + setPosition({ x: e.clientX, y: e.clientY }); + } + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + }, []); + return position; +} +``` + +```js useDelayedValue.js +import { useState, useEffect } from 'react'; + +export function useDelayedValue(value, delay) { + const [delayedValue, setDelayedValue] = useState(value); + + useEffect(() => { + setTimeout(() => { + setDelayedValue(value); + }, delay); + }, [value, delay]); + + return delayedValue; +} +``` + +```css +body { min-height: 300px; } +``` + +</Sandpack> + +You can create custom Hooks, compose them together, pass data between them, and reuse them between components. As your app grows, you will write fewer Effects by hand because you'll be able to reuse custom Hooks you already wrote. There are also many excellent custom Hooks maintained by the React community. + +<LearnMore path="/learn/reusing-logic-with-custom-hooks"> + +Read **[Reusing Logic with Custom Hooks](/learn/reusing-logic-with-custom-hooks)** to learn how to share logic between components. + +</LearnMore> + +## What's next? {/*whats-next*/} + +Head over to [Referencing Values with Refs](/learn/referencing-values-with-refs) to start reading this chapter page by page! diff --git a/beta/src/content/learn/extracting-state-logic-into-a-reducer.md b/beta/src/content/learn/extracting-state-logic-into-a-reducer.md new file mode 100644 index 000000000..a4b0ac94f --- /dev/null +++ b/beta/src/content/learn/extracting-state-logic-into-a-reducer.md @@ -0,0 +1,2629 @@ +--- +title: Extracting State Logic into a Reducer +--- + +<Intro> + +Components with many state updates spread across many event handlers can get overwhelming. For these cases, you can consolidate all the state update logic outside your component in a single function, called a _reducer._ + +</Intro> + +<YouWillLearn> + +- What a reducer function is +- How to refactor `useState` to `useReducer` +- When to use a reducer +- How to write one well + +</YouWillLearn> + +## Consolidate state logic with a reducer {/*consolidate-state-logic-with-a-reducer*/} + +As your components grow in complexity, it can get harder to see at a glance all the different ways in which a component's state gets updated. For example, the `TaskApp` component below holds an array of `tasks` in state and uses three different event handlers to add, remove, and edit tasks: + +<Sandpack> + +```js App.js +import {useState} from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +export default function TaskApp() { + const [tasks, setTasks] = useState(initialTasks); + + function handleAddTask(text) { + setTasks([ + ...tasks, + { + id: nextId++, + text: text, + done: false, + }, + ]); + } + + function handleChangeTask(task) { + setTasks( + tasks.map((t) => { + if (t.id === task.id) { + return task; + } else { + return t; + } + }) + ); + } + + function handleDeleteTask(taskId) { + setTasks(tasks.filter((t) => t.id !== taskId)); + } + + return ( + <> + <h1>Prague itinerary</h1> + <AddTask onAddTask={handleAddTask} /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +let nextId = 3; +const initialTasks = [ + {id: 0, text: 'Visit Kafka Museum', done: true}, + {id: 1, text: 'Watch a puppet show', done: false}, + {id: 2, text: 'Lennon Wall pic', done: false}, +]; +``` + +```js AddTask.js hidden +import {useState} from 'react'; + +export default function AddTask({onAddTask}) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={(e) => setText(e.target.value)} + /> + <button + onClick={() => { + setText(''); + onAddTask(text); + }}> + Add + </button> + </> + ); +} +``` + +```js TaskList.js hidden +import {useState} from 'react'; + +export default function TaskList({tasks, onChangeTask, onDeleteTask}) { + return ( + <ul> + {tasks.map((task) => ( + <li key={task.id}> + <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} /> + </li> + ))} + </ul> + ); +} + +function Task({task, onChange, onDelete}) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={(e) => { + onChange({ + ...task, + text: e.target.value, + }); + }} + /> + <button onClick={() => setIsEditing(false)}>Save</button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}>Edit</button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={(e) => { + onChange({ + ...task, + done: e.target.checked, + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}>Delete</button> + </label> + ); +} +``` + +```css +button { + margin: 5px; +} +li { + list-style-type: none; +} +ul, +li { + margin: 0; + padding: 0; +} +``` + +</Sandpack> + +Each of its event handlers calls `setTasks` in order to update the state. As this component grows, so does the amount of state logic sprinkled throughout it. To reduce this complexity and keep all your logic in one easy-to-access place, you can move that state logic into a single function outside your component, **called a "reducer".** + +Reducers are a different way to handle state. You can migrate from `useState` to `useReducer` in three steps: + +1. **Move** from setting state to dispatching actions. +2. **Write** a reducer function. +3. **Use** the reducer from your component. + +### Step 1: Move from setting state to dispatching actions {/*step-1-move-from-setting-state-to-dispatching-actions*/} + +Your event handlers currently specify _what to do_ by setting state: + +```js +function handleAddTask(text) { + setTasks([ + ...tasks, + { + id: nextId++, + text: text, + done: false, + }, + ]); +} + +function handleChangeTask(task) { + setTasks( + tasks.map((t) => { + if (t.id === task.id) { + return task; + } else { + return t; + } + }) + ); +} + +function handleDeleteTask(taskId) { + setTasks(tasks.filter((t) => t.id !== taskId)); +} +``` + +Remove all the state setting logic. What you are left with are three event handlers: + +- `handleAddTask(text)` is called when the user presses "Add". +- `handleChangeTask(task)` is called when the user toggles a task or presses "Save". +- `handleDeleteTask(taskId)` is called when the user presses "Delete". + +Managing state with reducers is slightly different from directly setting state. Instead of telling React "what to do" by setting state, you specify "what the user just did" by dispatching "actions" from your event handlers. (The state update logic will live elsewhere!) So instead of "setting `tasks`" via an event handler, you're dispatching an "added/changed/deleted a task" action. This is more descriptive of the user's intent. + +```js +function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); +} + +function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task, + }); +} + +function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId, + }); +} +``` + +The object you pass to `dispatch` is called an "action": + +```js {3-7} +function handleDeleteTask(taskId) { + dispatch( + // "action" object: + { + type: 'deleted', + id: taskId, + } + ); +} +``` + +It is a regular JavaScript object. You decide what to put in it, but generally it should contain the minimal information about _what happened_. (You will add the `dispatch` function itself in a later step.) + +<Note> + +An action object can have any shape. + +By convention, it is common to give it a string `type` that describes what happened, and pass any additional information in other fields. The `type` is specific to a component, so in this example either `'added'` or `'added_task'` would be fine. Choose a name that says what happened! + +```js +dispatch({ + // specific to component + type: 'what_happened', + // other fields go here +}); +``` + +</Note> + +### Step 2: Write a reducer function {/*step-2-write-a-reducer-function*/} + +A reducer function is where you will put your state logic. It takes two arguments, the current state and the action object, and it returns the next state: + +```js +function yourReducer(state, action) { + // return next state for React to set +} +``` + +React will set the state to what you return from the reducer. + +To move your state setting logic from your event handlers to a reducer function in this example, you will: + +1. Declare the current state (`tasks`) as the first argument. +2. Declare the `action` object as the second argument. +3. Return the _next_ state from the reducer (which React will set the state to). + +Here is all the state setting logic migrated to a reducer function: + +```js +function tasksReducer(tasks, action) { + if (action.type === 'added') { + return [ + ...tasks, + { + id: action.id, + text: action.text, + done: false, + }, + ]; + } else if (action.type === 'changed') { + return tasks.map((t) => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } else if (action.type === 'deleted') { + return tasks.filter((t) => t.id !== action.id); + } else { + throw Error('Unknown action: ' + action.type); + } +} +``` + +> Because the reducer function takes state (`tasks`) as an argument, you can **declare it outside of your component.** This decreases the indentation level and can make your code easier to read. + +<Note> + +The code above uses if/else statements, but it's a convention to use [switch statements](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/switch) inside reducers. The result is the same, but it can be easier to read switch statements at a glance. + +We'll be using them throughout the rest of this documentation like so: + +```js +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [ + ...tasks, + { + id: action.id, + text: action.text, + done: false, + }, + ]; + } + case 'changed': { + return tasks.map((t) => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter((t) => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +We recommend to wrap each `case` block into the `{` and `}` curly braces so that variables declared inside of different `case`s don't clash with each other. Also, a `case` should usually end with a `return`. If you forget to `return`, the code will "fall through" to the next `case`, which can lead to mistakes! + +If you're not yet comfortable with switch statements, using if/else is completely fine. + +</Note> + +<DeepDive> + +#### Why are reducers called this way? {/*why-are-reducers-called-this-way*/} + +Although reducers can "reduce" the amount of code inside your component, they are actually named after the [`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) operation that you can perform on arrays. + +The `reduce()` operation lets you take an array and "accumulate" a single value out of many: + +``` +const arr = [1, 2, 3, 4, 5]; +const sum = arr.reduce( + (result, number) => result + number +); // 1 + 2 + 3 + 4 + 5 +``` + +The function you pass to `reduce` is known as a "reducer". It takes the _result so far_ and the _current item,_ then it returns the _next result._ React reducers are an example of the same idea: they take the _state so far_ and the _action_, and return the _next state._ In this way, they accumulate actions over time into state. + +You could even use the `reduce()` method with an `initialState` and an array of `actions` to calculate the final state by passing your reducer function to it: + +<Sandpack> + +```js index.js active +import tasksReducer from './tasksReducer.js'; + +let initialState = []; +let actions = [ + {type: 'added', id: 1, text: 'Visit Kafka Museum'}, + {type: 'added', id: 2, text: 'Watch a puppet show'}, + {type: 'deleted', id: 1}, + {type: 'added', id: 3, text: 'Lennon Wall pic'}, +]; + +let finalState = actions.reduce(tasksReducer, initialState); + +const output = document.getElementById('output'); +output.textContent = JSON.stringify(finalState, null, 2); +``` + +```js tasksReducer.js +export default function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [ + ...tasks, + { + id: action.id, + text: action.text, + done: false, + }, + ]; + } + case 'changed': { + return tasks.map((t) => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter((t) => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```html public/index.html +<pre id="output"></pre> +``` + +</Sandpack> + +You probably won't need to do this yourself, but this is similar to what React does! + +</DeepDive> + +### Step 3: Use the reducer from your component {/*step-3-use-the-reducer-from-your-component*/} + +Finally, you need to hook up the `tasksReducer` to your component. Make sure to import the `useReducer` Hook from React: + +```js +import {useReducer} from 'react'; +``` + +Then you can replace `useState`: + +```js +const [tasks, setTasks] = useState(initialTasks); +``` + +with `useReducer` like so: + +```js +const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); +``` + +The `useReducer` Hook is similar to `useState`—you must pass it an initial state and it returns a stateful value and a way to set state (in this case, the dispatch function). But it's a little different. + +The `useReducer` Hook takes two arguments: + +1. A reducer function +2. An initial state + +And it returns: + +1. A stateful value +2. A dispatch function (to "dispatch" user actions to the reducer) + +Now it's fully wired up! Here, the reducer is declared at the bottom of the component file: + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task, + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId, + }); + } + + return ( + <> + <h1>Prague itinerary</h1> + <AddTask onAddTask={handleAddTask} /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [ + ...tasks, + { + id: action.id, + text: action.text, + done: false, + }, + ]; + } + case 'changed': { + return tasks.map((t) => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter((t) => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +let nextId = 3; +const initialTasks = [ + {id: 0, text: 'Visit Kafka Museum', done: true}, + {id: 1, text: 'Watch a puppet show', done: false}, + {id: 2, text: 'Lennon Wall pic', done: false}, +]; +``` + +```js AddTask.js hidden +import {useState} from 'react'; + +export default function AddTask({onAddTask}) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={(e) => setText(e.target.value)} + /> + <button + onClick={() => { + setText(''); + onAddTask(text); + }}> + Add + </button> + </> + ); +} +``` + +```js TaskList.js hidden +import {useState} from 'react'; + +export default function TaskList({tasks, onChangeTask, onDeleteTask}) { + return ( + <ul> + {tasks.map((task) => ( + <li key={task.id}> + <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} /> + </li> + ))} + </ul> + ); +} + +function Task({task, onChange, onDelete}) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={(e) => { + onChange({ + ...task, + text: e.target.value, + }); + }} + /> + <button onClick={() => setIsEditing(false)}>Save</button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}>Edit</button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={(e) => { + onChange({ + ...task, + done: e.target.checked, + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}>Delete</button> + </label> + ); +} +``` + +```css +button { + margin: 5px; +} +li { + list-style-type: none; +} +ul, +li { + margin: 0; + padding: 0; +} +``` + +</Sandpack> + +If you want, you can even move the reducer to a different file: + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; +import tasksReducer from './tasksReducer.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task, + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId, + }); + } + + return ( + <> + <h1>Prague itinerary</h1> + <AddTask onAddTask={handleAddTask} /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +let nextId = 3; +const initialTasks = [ + {id: 0, text: 'Visit Kafka Museum', done: true}, + {id: 1, text: 'Watch a puppet show', done: false}, + {id: 2, text: 'Lennon Wall pic', done: false}, +]; +``` + +```js tasksReducer.js +export default function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [ + ...tasks, + { + id: action.id, + text: action.text, + done: false, + }, + ]; + } + case 'changed': { + return tasks.map((t) => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter((t) => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js AddTask.js hidden +import {useState} from 'react'; + +export default function AddTask({onAddTask}) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={(e) => setText(e.target.value)} + /> + <button + onClick={() => { + setText(''); + onAddTask(text); + }}> + Add + </button> + </> + ); +} +``` + +```js TaskList.js hidden +import {useState} from 'react'; + +export default function TaskList({tasks, onChangeTask, onDeleteTask}) { + return ( + <ul> + {tasks.map((task) => ( + <li key={task.id}> + <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} /> + </li> + ))} + </ul> + ); +} + +function Task({task, onChange, onDelete}) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={(e) => { + onChange({ + ...task, + text: e.target.value, + }); + }} + /> + <button onClick={() => setIsEditing(false)}>Save</button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}>Edit</button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={(e) => { + onChange({ + ...task, + done: e.target.checked, + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}>Delete</button> + </label> + ); +} +``` + +```css +button { + margin: 5px; +} +li { + list-style-type: none; +} +ul, +li { + margin: 0; + padding: 0; +} +``` + +</Sandpack> + +Component logic can be easier to read when you separate concerns like this. Now the event handlers only specify _what happened_ by dispatching actions, and the reducer function determines _how the state updates_ in response to them. + +## Comparing `useState` and `useReducer` {/*comparing-usestate-and-usereducer*/} + +Reducers are not without downsides! Here's a few ways you can compare them: + +- **Code size:** Generally, with `useState` you have to write less code upfront. With `useReducer`, you have to write both a reducer function _and_ dispatch actions. However, `useReducer` can help cut down on the code if many event handlers modify state in a similar way. +- **Readability:** `useState` is very easy to read when the state updates are simple. When they get more complex, they can bloat your component's code and make it difficult to scan. In this case, `useReducer` lets you cleanly separate the _how_ of update logic from the _what happened_ of event handlers. +- **Debugging:** When you have a bug with `useState`, it can be difficult to tell _where_ the state was set incorrectly, and _why_. With `useReducer`, you can add a console log into your reducer to see every state update, and _why_ it happened (due to which `action`). If each `action` is correct, you'll know that the mistake is in the reducer logic itself. However, you have to step through more code than with `useState`. +- **Testing:** A reducer is a pure function that doesn't depend on your component. This means that you can export and test it separately in isolation. While generally it's best to test components in a more realistic environment, for complex state update logic it can be useful to assert that your reducer returns a particular state for a particular initial state and action. +- **Personal preference:** Some people like reducers, others don't. That's okay. It's a matter of preference. You can always convert between `useState` and `useReducer` back and forth: they are equivalent! + +We recommend using a reducer if you often encounter bugs due to incorrect state updates in some component, and want to introduce more structure to its code. You don't have to use reducers for everything: feel free to mix and match! You can even `useState` and `useReducer` in the same component. + +## Writing reducers well {/*writing-reducers-well*/} + +Keep these two tips in mind when writing reducers: + +- **Reducers must be pure.** Similar to [state updater functions](/learn/queueing-a-series-of-state-updates), reducers run during rendering! (Actions are queued until the next render.) This means that reducers [must be pure](/learn/keeping-components-pure)—same inputs always result in the same output. They should not send requests, schedule timeouts, or perform any side effects (operations that impact things outside the component). They should update [objects](/learn/updating-objects-in-state) and [arrays](/learn/updating-arrays-in-state) without mutations. +- **Each action describes a single user interaction, even if that leads to multiple changes in the data.** For example, if a user presses "Reset" on a form with five fields managed by a reducer, it makes more sense to dispatch one `reset_form` action rather than five separate `set_field` actions. If you log every action in a reducer, that log should be clear enough for you to reconstruct what interactions or responses happened in what order. This helps with debugging! + +## Writing concise reducers with Immer {/*writing-concise-reducers-with-immer*/} + +Just like with [updating objects](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) and [arrays](/learn/updating-arrays-in-state#write-concise-update-logic-with-immer) in regular state, you can use the Immer library to make reducers more concise. Here, [`useImmerReducer`](https://github.com/immerjs/use-immer#useimmerreducer) lets you mutate the state with `push` or `arr[i] =` assignment: + +<Sandpack> + +```js App.js +import {useImmerReducer} from 'use-immer'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +function tasksReducer(draft, action) { + switch (action.type) { + case 'added': { + draft.push({ + id: action.id, + text: action.text, + done: false, + }); + break; + } + case 'changed': { + const index = draft.findIndex((t) => t.id === action.task.id); + draft[index] = action.task; + break; + } + case 'deleted': { + return draft.filter((t) => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +export default function TaskApp() { + const [tasks, dispatch] = useImmerReducer(tasksReducer, initialTasks); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task, + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId, + }); + } + + return ( + <> + <h1>Prague itinerary</h1> + <AddTask onAddTask={handleAddTask} /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +let nextId = 3; +const initialTasks = [ + {id: 0, text: 'Visit Kafka Museum', done: true}, + {id: 1, text: 'Watch a puppet show', done: false}, + {id: 2, text: 'Lennon Wall pic', done: false}, +]; +``` + +```js AddTask.js hidden +import {useState} from 'react'; + +export default function AddTask({onAddTask}) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={(e) => setText(e.target.value)} + /> + <button + onClick={() => { + setText(''); + onAddTask(text); + }}> + Add + </button> + </> + ); +} +``` + +```js TaskList.js hidden +import {useState} from 'react'; + +export default function TaskList({tasks, onChangeTask, onDeleteTask}) { + return ( + <ul> + {tasks.map((task) => ( + <li key={task.id}> + <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} /> + </li> + ))} + </ul> + ); +} + +function Task({task, onChange, onDelete}) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={(e) => { + onChange({ + ...task, + text: e.target.value, + }); + }} + /> + <button onClick={() => setIsEditing(false)}>Save</button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}>Edit</button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={(e) => { + onChange({ + ...task, + done: e.target.checked, + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}>Delete</button> + </label> + ); +} +``` + +```css +button { + margin: 5px; +} +li { + list-style-type: none; +} +ul, +li { + margin: 0; + padding: 0; +} +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +Reducers must be pure, so they shouldn't mutate state. But Immer provides you with a special `draft` object which is safe to mutate. Under the hood, Immer will create a copy of your state with the changes you made to the `draft`. This is why reducers managed by `useImmerReducer` can mutate their first argument and don't need to return state. + +<Recap> + +- To convert from `useState` to `useReducer`: + 1. Dispatch actions from event handlers. + 2. Write a reducer function that returns the next state for a given state and action. + 3. Replace `useState` with `useReducer`. +- Reducers require you to write a bit more code, but they help with debugging and testing. +- Reducers must be pure. +- Each action describes a single user interaction. +- Use Immer if you want to write reducers in a mutating style. + +</Recap> + +<Challenges> + +#### Dispatch actions from event handlers {/*dispatch-actions-from-event-handlers*/} + +Currently, the event handlers in `ContactList.js` and `Chat.js` have `// TODO` comments. This is why typing into the input doesn't work, and clicking on the buttons doesn't change the selected recipient. + +Replace these two `// TODO`s with the code to `dispatch` the corresponding actions. To see the expected shape and the type of the actions, check the reducer in `messengerReducer.js`. The reducer is already written so you won't need to change it. You only need to dispatch the actions in `ContactList.js` and `Chat.js`. + +<Hint> + +The `dispatch` function is already available in both of these components because it was passed as a prop. So you need to call `dispatch` with the corresponding action object. + +To check the action object shape, you can look at the reducer and see which `action` fields it expects to see. For example, the `changed_selection` case in the reducer looks like this: + +```js +case 'changed_selection': { + return { + ...state, + selectedId: action.contactId + }; +} +``` + +This means that your action object should have a `type: 'changed_selection'`. You also see the `action.contactId` being used, so you need to include a `contactId` property into your action. + +</Hint> + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.message; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + message: 'Hello', +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + message: '', + }; + } + case 'edited_message': { + return { + ...state, + message: action.message, + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js ContactList.js +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + // TODO: dispatch changed_selection + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + // TODO: dispatch edited_message + // (Read the input value from e.target.value) + }} + /> + <br /> + <button>Send to {contact.email}</button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +<Solution> + +From the reducer code, you can infer that actions need to look like this: + +```js +// When the user presses "Alice" +dispatch({ + type: 'changed_selection', + contactId: 1, +}); + +// When user types "Hello!" +dispatch({ + type: 'edited_message', + message: 'Hello!', +}); +``` + +Here is the example updated to dispatch the corresponding messages: + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.message; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + message: 'Hello', +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + message: '', + }; + } + case 'edited_message': { + return { + ...state, + message: action.message, + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js ContactList.js +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button>Send to {contact.email}</button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +</Solution> + +#### Clear the input on sending a message {/*clear-the-input-on-sending-a-message*/} + +Currently, pressing "Send" doesn't do anything. Add an event handler to the "Send" button that will: + +1. Show an `alert` with the recipient's email and the message. +2. Clear the message input. + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.message; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + message: 'Hello', +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + message: '', + }; + } + case 'edited_message': { + return { + ...state, + message: action.message, + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js ContactList.js +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js active +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button>Send to {contact.email}</button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +<Solution> + +There are a couple of ways you could do it in the "Send" button event handler. One approach is to show an alert and then dispatch an `edited_message` action with an empty `message`: + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.message; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + message: 'Hello', +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + message: '', + }; + } + case 'edited_message': { + return { + ...state, + message: action.message, + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js ContactList.js +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js active +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button + onClick={() => { + alert(`Sending "${message}" to ${contact.email}`); + dispatch({ + type: 'edited_message', + message: '', + }); + }}> + Send to {contact.email} + </button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +This works and clears the input when you hit "Send". + +However, _from the user's perspective_, sending a message is a different action than editing the field. To reflect that, you could instead create a _new_ action called `sent_message`, and handle it separately in the reducer: + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.message; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js active +export const initialState = { + selectedId: 0, + message: 'Hello', +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + message: '', + }; + } + case 'edited_message': { + return { + ...state, + message: action.message, + }; + } + case 'sent_message': { + return { + ...state, + message: '', + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js ContactList.js +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js active +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button + onClick={() => { + alert(`Sending "${message}" to ${contact.email}`); + dispatch({ + type: 'sent_message', + }); + }}> + Send to {contact.email} + </button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +The resulting behavior is the same. But keep in mind that action types should ideally describe "what the user did" rather than "how you want the state to change". This makes it easier to later add more features. + +With either solution, it's important that you **don't** place the `alert` inside a reducer. The reducer should be a pure function--it should only calculate the next state. It should not "do" anything, including displaying messages to the user. That should happen in the event handler. (To help catch mistakes like this, React will call your reducers multiple times in Strict Mode. This is why, if you put an alert in a reducer, it fires twice.) + +</Solution> + +#### Restore input values when switching between tabs {/*restore-input-values-when-switching-between-tabs*/} + +In this example, switching between different recipients always clears the text input: + +```js +case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + message: '' // Clears the input + }; +``` + +This is because you don't want to share a single message draft between several recipients. But it would be better if your app "remembered" a draft for each contact separately, restoring them when you switch contacts. + +Your task is to change the way the state is structured so that you remember a separate message draft _per contact_. You would need to make a few changes to the reducer, the initial state, and the components. + +<Hint> + +You can structure your state like this: + +```js +export const initialState = { + selectedId: 0, + messages: { + 0: 'Hello, Taylor', // Draft for contactId = 0 + 1: 'Hello, Alice', // Draft for contactId = 1 + }, +}; +``` + +The `[key]: value` [computed property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names) syntax can help you update the `messages` object: + +```js +{ + ...state.messages, + [id]: message +} +``` + +</Hint> + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.message; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + message: 'Hello', +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + message: '', + }; + } + case 'edited_message': { + return { + ...state, + message: action.message, + }; + } + case 'sent_message': { + return { + ...state, + message: '', + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js ContactList.js +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button + onClick={() => { + alert(`Sending "${message}" to ${contact.email}`); + dispatch({ + type: 'sent_message', + }); + }}> + Send to {contact.email} + </button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +<Solution> + +You'll need to update the reducer to store and update a separate message draft per contact: + +```js +// When the input is edited +case 'edited_message': { + return { + // Keep other state like selection + ...state, + messages: { + // Keep messages for other contacts + ...state.messages, + // But change the selected contact's message + [state.selectedId]: action.message + } + }; +} +``` + +You would also update the `Messenger` component to read the message for the currently selected contact: + +```js +const message = state.messages[state.selectedId]; +``` + +Here is the complete solution: + +<Sandpack> + +```js App.js +import {useReducer} from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.messages[state.selectedId]; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + messages: { + 0: 'Hello, Taylor', + 1: 'Hello, Alice', + 2: 'Hello, Bob', + }, +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + }; + } + case 'edited_message': { + return { + ...state, + messages: { + ...state.messages, + [state.selectedId]: action.message, + }, + }; + } + case 'sent_message': { + return { + ...state, + messages: { + ...state.messages, + [state.selectedId]: '', + }, + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js ContactList.js +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button + onClick={() => { + alert(`Sending "${message}" to ${contact.email}`); + dispatch({ + type: 'sent_message', + }); + }}> + Send to {contact.email} + </button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +Notably, you didn't need to change any of the event handlers to implement this different behavior. Without a reducer, you would have to change every event handler that updates the state. + +</Solution> + +#### Implement `useReducer` from scratch {/*implement-usereducer-from-scratch*/} + +In the earlier examples, you imported the `useReducer` Hook from React. This time, you will implement _the `useReducer` Hook itself!_ Here is a stub to get you started. It shouldn't take more than 10 lines of code. + +To test your changes, try typing into the input or select a contact. + +<Hint> + +Here is a more detailed sketch of the implementation: + +```js +export function useReducer(reducer, initialState) { + const [state, setState] = useState(initialState); + + function dispatch(action) { + // ??? + } + + return [state, dispatch]; +} +``` + +Recall that a reducer function takes two arguments--the current state and the action object--and it returns the next state. What should your `dispatch` implementation do with it? + +</Hint> + +<Sandpack> + +```js App.js +import {useReducer} from './MyReact.js'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.messages[state.selectedId]; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + messages: { + 0: 'Hello, Taylor', + 1: 'Hello, Alice', + 2: 'Hello, Bob', + }, +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + }; + } + case 'edited_message': { + return { + ...state, + messages: { + ...state.messages, + [state.selectedId]: action.message, + }, + }; + } + case 'sent_message': { + return { + ...state, + messages: { + ...state.messages, + [state.selectedId]: '', + }, + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js MyReact.js active +import {useState} from 'react'; + +export function useReducer(reducer, initialState) { + const [state, setState] = useState(initialState); + + // ??? + + return [state, dispatch]; +} +``` + +```js ContactList.js hidden +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js hidden +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button + onClick={() => { + alert(`Sending "${message}" to ${contact.email}`); + dispatch({ + type: 'sent_message', + }); + }}> + Send to {contact.email} + </button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +<Solution> + +Dispatching an action calls a reducer with the current state and the action, and stores the result as the next state. This is what it looks like in code: + +<Sandpack> + +```js App.js +import {useReducer} from './MyReact.js'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; +import {initialState, messengerReducer} from './messengerReducer'; + +export default function Messenger() { + const [state, dispatch] = useReducer(messengerReducer, initialState); + const message = state.messages[state.selectedId]; + const contact = contacts.find((c) => c.id === state.selectedId); + return ( + <div> + <ContactList + contacts={contacts} + selectedId={state.selectedId} + dispatch={dispatch} + /> + <Chat + key={contact.id} + message={message} + contact={contact} + dispatch={dispatch} + /> + </div> + ); +} + +const contacts = [ + {id: 0, name: 'Taylor', email: 'taylor@mail.com'}, + {id: 1, name: 'Alice', email: 'alice@mail.com'}, + {id: 2, name: 'Bob', email: 'bob@mail.com'}, +]; +``` + +```js messengerReducer.js +export const initialState = { + selectedId: 0, + messages: { + 0: 'Hello, Taylor', + 1: 'Hello, Alice', + 2: 'Hello, Bob', + }, +}; + +export function messengerReducer(state, action) { + switch (action.type) { + case 'changed_selection': { + return { + ...state, + selectedId: action.contactId, + }; + } + case 'edited_message': { + return { + ...state, + messages: { + ...state.messages, + [state.selectedId]: action.message, + }, + }; + } + case 'sent_message': { + return { + ...state, + messages: { + ...state.messages, + [state.selectedId]: '', + }, + }; + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} +``` + +```js MyReact.js active +import {useState} from 'react'; + +export function useReducer(reducer, initialState) { + const [state, setState] = useState(initialState); + + function dispatch(action) { + const nextState = reducer(state, action); + setState(nextState); + } + + return [state, dispatch]; +} +``` + +```js ContactList.js hidden +export default function ContactList({contacts, selectedId, dispatch}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map((contact) => ( + <li key={contact.id}> + <button + onClick={() => { + dispatch({ + type: 'changed_selection', + contactId: contact.id, + }); + }}> + {selectedId === contact.id ? <b>{contact.name}</b> : contact.name} + </button> + </li> + ))} + </ul> + </section> + ); +} +``` + +```js Chat.js hidden +import {useState} from 'react'; + +export default function Chat({contact, message, dispatch}) { + return ( + <section className="chat"> + <textarea + value={message} + placeholder={'Chat to ' + contact.name} + onChange={(e) => { + dispatch({ + type: 'edited_message', + message: e.target.value, + }); + }} + /> + <br /> + <button + onClick={() => { + alert(`Sending "${message}" to ${contact.email}`); + dispatch({ + type: 'sent_message', + }); + }}> + Send to {contact.email} + </button> + </section> + ); +} +``` + +```css +.chat, +.contact-list { + float: left; + margin-bottom: 20px; +} +ul, +li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +Though it doesn't matter in most cases, a slightly more accurate implementation looks like this: + +```js +function dispatch(action) { + setState((s) => reducer(s, action)); +} +``` + +This is because the dispatched actions are queued until the next render, [similar to the updater functions.](/learn/queueing-a-series-of-state-updates) + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/importing-and-exporting-components.md b/beta/src/content/learn/importing-and-exporting-components.md new file mode 100644 index 000000000..5aedc2dff --- /dev/null +++ b/beta/src/content/learn/importing-and-exporting-components.md @@ -0,0 +1,418 @@ +--- +title: Importing and Exporting Components +--- + +<Intro> + +The magic of components lies in their reusability: you can create components that are composed of other components. But as you nest more and more components, it often makes sense to start splitting them into different files. This lets you keep your files easy to scan and reuse components in more places. + +</Intro> + +<YouWillLearn> + +* What a root component file is +* How to import and export a component +* When to use default and named imports and exports +* How to import and export multiple components from one file +* How to split components into multiple files + +</YouWillLearn> + +## The root component file {/*the-root-component-file*/} + +In [Your First Component](/learn/your-first-component), you made a `Profile` component and a `Gallery` component that renders it: + +<Sandpack> + +```js +function Profile() { + return ( + <img + src="https://i.imgur.com/MK3eW3As.jpg" + alt="Katherine Johnson" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +These currently live in a **root component file,** named `App.js` in this example. In [Create React App](https://create-react-app.dev/), your app lives in `src/App.js`. Depending on your setup, your root component could be in another file, though. If you use a framework with file-based routing, such as Next.js, your root component will be different for every page. + +## Exporting and importing a component {/*exporting-and-importing-a-component*/} + +What if you want to change the landing screen in the future and put a list of science books there? Or place all the profiles somewhere else? It makes sense to move `Gallery` and `Profile` out of the root component file. This will make them more modular and reusable in other files. You can move a component in three steps: + +1. **Make** a new JS file to put the components in. +2. **Export** your function component from that file (using either [default](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/export#using_the_default_export) or [named](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/export#using_named_exports) exports). +3. **Import** it in the file where you’ll use the component (using the corresponding technique for importing [default](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import#importing_defaults) or [named](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import#import_a_single_export_from_a_module) exports). + +Here both `Profile` and `Gallery` have been moved out of `App.js` into a new file called `Gallery.js`. Now you can change `App.js` to import `Gallery` from `Gallery.js`: + +<Sandpack> + +```js App.js +import Gallery from './Gallery.js'; + +export default function App() { + return ( + <Gallery /> + ); +} +``` + +```js Gallery.js +function Profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +Notice how this example is broken down into two component files now: + +1. `Gallery.js`: + - Defines the `Profile` component which is only used within the same file and is not exported. + - Exports the `Gallery` component as a **default export.** +2. `App.js`: + - Imports `Gallery` as a **default import** from `Gallery.js`. + - Exports the root `App` component as a **default export.** + + +<Note> + +You may encounter files that leave off the `.js` file extension like so: + +```js +import Gallery from './Gallery'; +``` + +Either `'./Gallery.js'` or `'./Gallery'` will work with React, though the former is closer to how [native ES Modules](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules) work. + +</Note> + +<DeepDive> + +#### Default vs named exports {/*default-vs-named-exports*/} + +There are two primary ways to export values with JavaScript: default exports and named exports. So far, our examples have only used default exports. But you can use one or both of them in the same file. **A file can have no more than one _default_ export, but it can have as many _named_ exports as you like.** + +![Default and named exports](/images/docs/illustrations/i_import-export.svg) + +How you export your component dictates how you must import it. You will get an error if you try to import a default export the same way you would a named export! This chart can help you keep track: + +| Syntax | Export statement | Import statement | +| ----------- | ----------- | ----------- | +| Default | `export default function Button() {}` | `import Button from './button.js';` | +| Named | `export function Button() {}` | `import { Button } from './button.js';` | + +When you write a _default_ import, you can put any name you want after `import`. For example, you could write `import Banana from './button.js'` instead and it would still provide you with the same default export. In contrast, with named imports, the name has to match on both sides. That's why they are called _named_ imports! + +**People often use default exports if the file exports only one component, and use named exports if it exports multiple components and values.** Regardless of which coding style you prefer, always give meaningful names to your component functions and the files that contain them. Components without names, like `export default () => {}`, are discouraged because they make debugging harder. + +</DeepDive> + +## Exporting and importing multiple components from the same file {/*exporting-and-importing-multiple-components-from-the-same-file*/} + +What if you want to show just one `Profile` instead of a gallery? You can export the `Profile` component, too. But `Gallery.js` already has a *default* export, and you can't have _two_ default exports. You could create a new file with a default export, or you could add a *named* export for `Profile`. **A file can only have one default export, but it can have numerous named exports!** + +> To reduce the potential confusion between default and named exports, some teams choose to only stick to one style (default or named), or avoid mixing them in a single file. It's a matter of preference. Do what works best for you! + +First, **export** `Profile` from `Gallery.js` using a named export (no `default` keyword): + +```js +export function Profile() { + // ... +} +``` + +Then, **import** `Profile` from `Gallery.js` to `App.js` using a named import (with the curly braces): + +```js +import { Profile } from './Gallery.js'; +``` + +Finally, **render** `<Profile />` from the `App` component: + +```js +export default function App() { + return <Profile />; +} +``` + +Now `Gallery.js` contains two exports: a default `Gallery` export, and a named `Profile` export. `App.js` imports both of them. Try editing `<Profile />` to `<Gallery />` and back in this example: + +<Sandpack> + +```js App.js +import Gallery from './Gallery.js'; +import { Profile } from './Gallery.js'; + +export default function App() { + return ( + <Profile /> + ); +} +``` + +```js Gallery.js +export function Profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +Now you're using a mix of default and named exports: + +* `Gallery.js`: + - Exports the `Profile` component as a **named export called `Profile`.** + - Exports the `Gallery` component as a **default export.** +* `App.js`: + - Imports `Profile` as a **named import called `Profile`** from `Gallery.js`. + - Imports `Gallery` as a **default import** from `Gallery.js`. + - Exports the root `App` component as a **default export.** + +<Recap> + +On this page you learned: + +* What a root component file is +* How to import and export a component +* When and how to use default and named imports and exports +* How to export multiple components from the same file + +</Recap> + + + +<Challenges> + +#### Split the components further {/*split-the-components-further*/} + +Currently, `Gallery.js` exports both `Profile` and `Gallery`, which is a bit confusing. + +Move the `Profile` component to its own `Profile.js`, and then change the `App` component to render both `<Profile />` and `<Gallery />` one after another. + +You may use either a default or a named export for `Profile`, but make sure that you use the corresponding import syntax in both `App.js` and `Gallery.js`! You can refer to the table from the deep dive above: + +| Syntax | Export statement | Import statement | +| ----------- | ----------- | ----------- | +| Default | `export default function Button() {}` | `import Button from './button.js';` | +| Named | `export function Button() {}` | `import { Button } from './button.js';` | + +<Hint> + +Don't forget to import your components where they are called. Doesn't `Gallery` use `Profile`, too? + +</Hint> + +<Sandpack> + +```js App.js +import Gallery from './Gallery.js'; +import { Profile } from './Gallery.js'; + +export default function App() { + return ( + <div> + <Profile /> + </div> + ); +} +``` + +```js Gallery.js active +// Move me to Profile.js! +export function Profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```js Profile.js +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +After you get it working with one kind of exports, make it work with the other kind. + +<Solution> + +This is the solution with named exports: + +<Sandpack> + +```js App.js +import Gallery from './Gallery.js'; +import { Profile } from './Profile.js'; + +export default function App() { + return ( + <div> + <Profile /> + <Gallery /> + </div> + ); +} +``` + +```js Gallery.js +import { Profile } from './Profile.js'; + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```js Profile.js +export function Profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +This is the solution with default exports: + +<Sandpack> + +```js App.js +import Gallery from './Gallery.js'; +import Profile from './Profile.js'; + +export default function App() { + return ( + <div> + <Profile /> + <Gallery /> + </div> + ); +} +``` + +```js Gallery.js +import Profile from './Profile.js'; + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```js Profile.js +export default function Profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/index.md b/beta/src/content/learn/index.md new file mode 100644 index 000000000..720b7c08e --- /dev/null +++ b/beta/src/content/learn/index.md @@ -0,0 +1,539 @@ +--- +title: Quick Start +--- + +<Intro> + +Welcome to the React documentation! This page will give you an introduction to the 80% of React concepts that you will use on a daily basis. + +</Intro> + +<YouWillLearn> + +- How to create and nest components +- How to add markup and styles +- How to display data +- How to render conditions and lists +- How to respond to events and update the screen +- How to share data between components + +</YouWillLearn> + +## Creating and nesting components {/*components*/} + +React apps are made out of *components*. A component is a piece of the UI (user interface) that has its own logic and appearance. A component can be as small as a button, or as large as an entire page. + +React components are JavaScript functions that return markup: + +```js +function MyButton() { + return ( + <button>I'm a button</button> + ); +} +``` + +Now that you've declared `MyButton`, you can nest it into another component: + +```js {5} +export default function MyApp() { + return ( + <div> + <h1>Welcome to my app</h1> + <MyButton /> + </div> + ); +} +``` + +Notice that `<MyButton />` starts with a capital letter. That's how you know it's a React component. React component names must always start with a capital letter, while HTML tags must be lowercase. + +Have a look at the result: + +<Sandpack> + +```js +function MyButton() { + return ( + <button> + I'm a button + </button> + ); +} + +export default function MyApp() { + return ( + <div> + <h1>Welcome to my app</h1> + <MyButton /> + </div> + ); +} +``` + +</Sandpack> + +The `export default` keywords specify the main component in the file. If you're not familiar with some piece of JavaScript syntax, [MDN](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export) and [javascript.info](https://javascript.info/import-export) have great references. + +## Writing markup with JSX {/*writing-markup-with-jsx*/} + +The markup syntax you've seen above is called *JSX*. It is optional, but most React projects use JSX for its convenience. All of the [tools we recommend for local development](/learn/installation) support JSX out of the box. + +JSX is stricter than HTML. You have to close tags like `<br />`. Your component also can't return multiple JSX tags. You have to wrap them into a shared parent, like a `<div>...</div>` or an empty `<>...</>` wrapper: + +```js {3,6} +function AboutPage() { + return ( + <> + <h1>About</h1> + <p>Hello there.<br />How do you do?</p> + </> + ); +} +``` + +If you have a lot of HTML to port to JSX, you can use an [online converter.](https://transform.tools/html-to-jsx) + +## Adding styles {/*adding-styles*/} + +In React, you specify a CSS class with `className`. It works the same way as the HTML [`class`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class) attribute: + +```js +<img className="avatar" /> +``` + +Then you write the CSS rules for it in a separate CSS file: + +```css +/* In your CSS */ +.avatar { + border-radius: 50%; +} +``` + +React does not prescribe how you add CSS files. In the simplest case, you'll add a [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) tag to your HTML. If you use a build tool or a framework, consult its documentation to learn how to add a CSS file to your project. + +## Displaying data {/*displaying-data*/} + +JSX lets you put markup into JavaScript. Curly braces let you "escape back" into JavaScript so that you can embed some variable from your code and display it to the user. For example, this will display `user.name`: + +```js {3} +return ( + <h1> + {user.name} + </h1> +); +``` + +You can also "escape into JavaScript" from JSX attributes, but you have to use curly braces *instead of* quotes. For example, `className="avatar"` passes the `"avatar"` string as the CSS class, but `src={user.imageUrl}` reads the JavaScript `user.imageUrl` variable value, and then passes that value as the `src` attribute: + +```js {3,4} +return ( + <img + className="avatar" + src={user.imageUrl} + /> +); +``` + +You can put more complex expressions inside the JSX curly braces too, for example, [string concatenation](https://javascript.info/operators#string-concatenation-with-binary): + +<Sandpack> + +```js +const user = { + name: 'Hedy Lamarr', + imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg', + imageSize: 90, +}; + +export default function Profile() { + return ( + <> + <h1>{user.name}</h1> + <img + className="avatar" + src={user.imageUrl} + alt={'Photo of ' + user.name} + style={{ + width: user.imageSize, + height: user.imageSize + }} + /> + </> + ); +} +``` + +```css +.avatar { + border-radius: 50%; +} + +.large { + border: 4px solid gold; +} +``` + +</Sandpack> + +In the above example, `style={{}}` is not a special syntax, but a regular `{}` object inside the `style={ }` JSX curly braces. You can use the `style` attribute when your styles depend on JavaScript variables. + +## Conditional rendering {/*conditional-rendering*/} + +In React, there is no special syntax for writing conditions. Instead, you'll use the same techniques as you use when writing regular JavaScript code. For example, you can use an [`if`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) statement to conditionally include JSX: + +```js +let content; +if (isLoggedIn) { + content = <AdminPanel />; +} else { + content = <LoginForm />; +} +return ( + <div> + {content} + </div> +); +``` + +If you prefer more compact code, you can use the [conditional `?` operator.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) Unlike `if`, it works inside JSX: + +```js +<div> + {isLoggedIn ? ( + <AdminPanel /> + ) : ( + <LoginForm /> + )} +</div> +``` + +When you don't need the `else` branch, you can also use a shorter [logical `&&` syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND#short-circuit_evaluation): + +```js +<div> + {isLoggedIn && <AdminPanel />} +</div> +``` + +All of these approaches also work for conditionally specifying attributes. If you're unfamiliar with some of this JavaScript syntax, you can start by always using `if...else`. + +## Rendering lists {/*rendering-lists*/} + +You will rely on JavaScript features like [`for` loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for) and the [array `map()` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) to render lists of components. + +For example, let's say you have an array of products: + +```js +const products = [ + { title: 'Cabbage', id: 1 }, + { title: 'Garlic', id: 2 }, + { title: 'Apple', id: 3 }, +]; +``` + +Inside your component, use the `map()` function to transform an array of products into an array of `<li>` items: + +```js +const listItems = products.map(product => + <li key={product.id}> + {product.title} + </li> +); + +return ( + <ul>{listItems}</ul> +); +``` + +Notice how `<li>` has a `key` attribute. For each item in a list, you should pass a string or a number that uniquely identifies that item among its siblings. Usually, a key should be coming from your data, such as a database ID. React will rely on your keys to understand what happened if you later insert, delete, or reorder the items. + +<Sandpack> + +```js +const products = [ + { title: 'Cabbage', isFruit: false, id: 1 }, + { title: 'Garlic', isFruit: false, id: 2 }, + { title: 'Apple', isFruit: true, id: 3 }, +]; + +export default function ShoppingList() { + const listItems = products.map(product => + <li + key={product.id} + style={{ + color: product.isFruit ? 'magenta' : 'darkgreen' + }} + > + {product.title} + </li> + ); + + return ( + <ul>{listItems}</ul> + ); +} +``` + +</Sandpack> + +## Responding to events {/*responding-to-events*/} + +You can respond to events by declaring *event handler* functions inside your components: + +```js {2-4,7} +function MyButton() { + function handleClick() { + alert('You clicked me!'); + } + + return ( + <button onClick={handleClick}> + Click me + </button> + ); +} +``` + +Notice how `onClick={handleClick}` has no parentheses at the end! Do not _call_ the event handler function: you only need to *pass it down*. React will call your event handler when the user clicks the button. + +## Updating the screen {/*updating-the-screen*/} + +Often, you'll want your component to "remember" some information and display it. For example, maybe you want to count the number of times a button is clicked. To do this, add *state* to your component. + +First, import [`useState`](/reference/react/useState) from React: + +```js +import { useState } from 'react'; +``` + +Now you can declare a *state variable* inside your component: + +```js +function MyButton() { + const [count, setCount] = useState(0); +``` + +You will get two things from `useState`: the current state (`count`), and the function that lets you update it (`setCount`). You can give them any names, but the convention is to call them like `[something, setSomething]`. + +The first time the button is displayed, `count` will be `0` because you passed `0` to `useState()`. When you want to change state, call `setCount()` and pass the new value to it. Clicking this button will increment the counter: + +```js {5} +function MyButton() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ( + <button onClick={handleClick}> + Clicked {count} times + </button> + ); +} +``` + +React will call your component function again. This time, `count` will be `1`. Then it will be `2`. And so on. + +If you render the same component multiple times, each will get its own state. Try clicking each button separately: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function MyApp() { + return ( + <div> + <h1>Counters that update separately</h1> + <MyButton /> + <MyButton /> + </div> + ); +} + +function MyButton() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ( + <button onClick={handleClick}> + Clicked {count} times + </button> + ); +} +``` + +```css +button { + display: block; + margin-bottom: 5px; +} +``` + +</Sandpack> + +Notice how each button "remembers" its own `count` state and doesn't affect other buttons. + +## Using Hooks {/*using-hooks*/} + +Functions starting with `use` are called *Hooks*. `useState` is a built-in Hook provided by React. You can find other built-in Hooks in the [React API reference.](/reference/react) You can also write your own Hooks by combining the existing ones. + +Hooks are more restrictive than regular functions. You can only call Hooks *at the top level* of your components (or other Hooks). If you want to use `useState` in a condition or a loop, extract a new component and put it there. + +## Sharing data between components {/*sharing-data-between-components*/} + +In the previous example, each `MyButton` had its own independent `count`, and when each button was clicked, only the `count` for the button clicked changed: + +<DiagramGroup> + +<Diagram name="sharing_data_child" height={367} width={407} alt="Diagram showing a tree of three components, one parent labeled MyApp and two children labeled MyButton. Both MyButton components contain a count with value zero."> + +Initially, each `MyButton`'s `count` state is `0` + +</Diagram> + +<Diagram name="sharing_data_child_clicked" height={367} width={407} alt="The same diagram as the previous, with the count of the first child MyButton component highlighted indicating a click with the count value incremented to one. The second MyButton component still contains value zero." > + +The first `MyButton` updates its `count` to `1` + +</Diagram> + +</DiagramGroup> + +However, often you'll need components to *share data and always update together*. + +To make both `MyButton` components display the same `count` and update together, you need to move the state from the individual buttons "upwards" to the closest component containing all of them. + +In this example, it is `MyApp`: + +<DiagramGroup> + +<Diagram name="sharing_data_parent" height={385} width={410} alt="Diagram showing a tree of three components, one parent labeled MyApp and two children labeled MyButton. MyApp contains a count value of zero which is passed down to both of the MyButton components, which also show value zero." > + +Initially, `MyApp`'s `count` state is `0` and is passed down to both children + +</Diagram> + +<Diagram name="sharing_data_parent_clicked" height={385} width={410} alt="The same diagram as the previous, with the count of the parent MyApp component highlighted indicating a click with the value incremented to one. The flow to both of the children MyButton components is also highlighted, and the count value in each child is set to one indicating the value was passed down." > + +On click, `MyApp` updates its `count` state to `1` and passes it down to both children + +</Diagram> + +</DiagramGroup> + +Now when you click either button, the `count` in `MyApp` will change, which will change both of the counts in `MyButton`. Here's how you can express this in code. + +First, *move the state up* from `MyButton` into `MyApp`: + +```js {2-6,18} +export default function MyApp() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ( + <div> + <h1>Counters that update separately</h1> + <MyButton /> + <MyButton /> + </div> + ); +} + +function MyButton() { + // ... we're moving code from here ... +} + +``` + +Then, *pass the state down* from `MyApp` to each `MyButton`, together with the shared click handler. You can pass information to `MyButton` using the JSX curly braces, just like you previously did with built-in tags like `<img>`: + +```js {11-12} +export default function MyApp() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ( + <div> + <h1>Counters that update together</h1> + <MyButton count={count} onClick={handleClick} /> + <MyButton count={count} onClick={handleClick} /> + </div> + ); +} +``` + +The information you pass down like this is called _props_. Now the `MyApp` component contains the `count` state and the `handleClick` event handler, and *passes both of them down as props* to each of the buttons. + +Finally, change `MyButton` to *read* the props you have passed from its parent component: + +```js {1,3} +function MyButton({ count, onClick }) { + return ( + <button onClick={onClick}> + Clicked {count} times + </button> + ); +} +``` + +When you click the button, the `onClick` handler fires. Each button's `onClick` prop was set to the `handleClick` function inside `MyApp`, so the code inside of it runs. That code calls `setCount(count + 1)`, incrementing the `count` state variable. The new `count` value is passed as a prop to each button, so they all show the new value. + +This is called "lifting state up". By moving state up, we've shared it between components. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function MyApp() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ( + <div> + <h1>Counters that update together</h1> + <MyButton count={count} onClick={handleClick} /> + <MyButton count={count} onClick={handleClick} /> + </div> + ); +} + +function MyButton({ count, onClick }) { + return ( + <button onClick={onClick}> + Clicked {count} times + </button> + ); +} +``` + +```css +button { + display: block; + margin-bottom: 5px; +} +``` + +</Sandpack> + +## Next Steps {/*next-steps*/} + +By now, you know the basics of how to write React code! + +Check out the [Tutorial](/learn/tutorial-tic-tac-toe) to put them into practice and build your first mini-app with React. diff --git a/beta/src/content/learn/installation.md b/beta/src/content/learn/installation.md new file mode 100644 index 000000000..6ddc66b56 --- /dev/null +++ b/beta/src/content/learn/installation.md @@ -0,0 +1,57 @@ +--- +title: Installation +--- + +<Intro> + +React has been designed from the start for gradual adoption. You can use as little or as much React as you need. Whether you want to get a taste of React, add some interactivity to an HTML page, or start a complex React-powered app, this section will help you get started. + +</Intro> + +<YouWillLearn isChapter={true}> + +* [How to add React to an HTML page](/learn/add-react-to-a-website) +* [How to start a standalone React project](/learn/start-a-new-react-project) +* [How to set up your editor](/learn/editor-setup) +* [How to install React Developer Tools](/learn/react-developer-tools) + +</YouWillLearn> + +## Try React {/*try-react*/} + +You don't need to install anything to play with React. Try editing this sandbox! + +<Sandpack> + +```js +function Greeting({ name }) { + return <h1>Hello, {name}</h1>; +} + +export default function App() { + return <Greeting name="world" /> +} +``` + +</Sandpack> + +You can edit it directly or open it in a new tab by pressing the "Fork" button in the upper right corner. + +Most pages in the React documentation contain sandboxes like this. Outside of the React documentation, there are many online sandboxes that support React: for example, [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react), or [CodePen.](https://codepen.io/pen?&editors=0010&layout=left&prefill_data_id=3f4569d1-1b11-4bce-bd46-89090eed5ddb) + +### Try React locally {/*try-react-locally*/} + +To try React locally on your computer, [download this HTML page.](https://raw.githubusercontent.com/reactjs/reactjs.org/main/static/html/single-file-example.html) Open it in your editor and in your browser! + +## Add React to a page {/*add-react-to-a-page*/} + +If you're working with an existing site and need to add a little bit of React, you can [add React with a script tag.](/learn/add-react-to-a-website) + +## Start a React project {/*start-a-react-project*/} + +If you're ready to [start a standalone project](/learn/start-a-new-react-project) with React, you can set up a minimal toolchain for a pleasant developer experience. You can also start with a framework that makes a lot of decisions for you out of the box. + +## Next steps {/*next-steps*/} + +Head to the [Quick Start](/learn) guide for a tour of the most important React concepts you will encounter every day. + diff --git a/beta/src/content/learn/javascript-in-jsx-with-curly-braces.md b/beta/src/content/learn/javascript-in-jsx-with-curly-braces.md new file mode 100644 index 000000000..aafac8022 --- /dev/null +++ b/beta/src/content/learn/javascript-in-jsx-with-curly-braces.md @@ -0,0 +1,587 @@ +--- +title: JavaScript in JSX with Curly Braces +--- + +<Intro> + +JSX lets you write HTML-like markup inside a JavaScript file, keeping rendering logic and content in the same place. Sometimes you will want to add a little JavaScript logic or reference a dynamic property inside that markup. In this situation, you can use curly braces in your JSX to open a window to JavaScript. + +</Intro> + +<YouWillLearn> + +* How to pass strings with quotes +* How to reference a JavaScript variable inside JSX with curly braces +* How to call a JavaScript function inside JSX with curly braces +* How to use a JavaScript object inside JSX with curly braces + +</YouWillLearn> + +## Passing strings with quotes {/*passing-strings-with-quotes*/} + +When you want to pass a string attribute to JSX, you put it in single or double quotes: + +<Sandpack> + +```js +export default function Avatar() { + return ( + <img + className="avatar" + src="https://i.imgur.com/7vQD0fPs.jpg" + alt="Gregorio Y. Zara" + /> + ); +} +``` + +```css +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +Here, `"https://i.imgur.com/7vQD0fPs.jpg"` and `"Gregorio Y. Zara"` are being passed as strings. + +But what if you want to dynamically specify the `src` or `alt` text? You could **use a value from JavaScript by replacing `"` and `"` with `{` and `}`**: + +<Sandpack> + +```js +export default function Avatar() { + const avatar = 'https://i.imgur.com/7vQD0fPs.jpg'; + const description = 'Gregorio Y. Zara'; + return ( + <img + className="avatar" + src={avatar} + alt={description} + /> + ); +} +``` + +```css +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +Notice the difference between `className="avatar"`, which specifies an `"avatar"` CSS class name that makes the image round, and `src={avatar}` that reads the value of the JavaScript variable called `avatar`. That's because curly braces let you work with JavaScript right there in your markup! + +## Using curly braces: A window into the JavaScript world {/*using-curly-braces-a-window-into-the-javascript-world*/} + +JSX is a special way of writing JavaScript. That means it’s possible to use JavaScript inside it—with curly braces `{ }`. The example below first declares a name for the scientist, `name`, then embeds it with curly braces inside the `<h1>`: + +<Sandpack> + +```js +export default function TodoList() { + const name = 'Gregorio Y. Zara'; + return ( + <h1>{name}'s To Do List</h1> + ); +} +``` + +</Sandpack> + +Try changing `name`'s value from `'Gregorio Y. Zara'` to `'Hedy Lamarr'`. See how the To Do List title changes? + +Any JavaScript expression will work between curly braces, including function calls like `formatDate()`: + +<Sandpack> + +```js +const today = new Date(); + +function formatDate(date) { + return new Intl.DateTimeFormat( + 'en-US', + { weekday: 'long' } + ).format(date); +} + +export default function TodoList() { + return ( + <h1>To Do List for {formatDate(today)}</h1> + ); +} +``` + +</Sandpack> + +### Where to use curly braces {/*where-to-use-curly-braces*/} + +You can only use curly braces in two ways inside JSX: + +1. **As text** directly inside a JSX tag: `<h1>{name}'s To Do List</h1>` works, but `<{tag}>Gregorio Y. Zara's To Do List</{tag}>` will not. +2. **As attributes** immediately following the `=` sign: `src={avatar}` will read the `avatar` variable, but `src="{avatar}"` will pass the string `"{avatar}"`. + +## Using "double curlies": CSS and other objects in JSX {/*using-double-curlies-css-and-other-objects-in-jsx*/} + +In addition to strings, numbers, and other JavaScript expressions, you can even pass objects in JSX. Objects are also denoted with curly braces, like `{ name: "Hedy Lamarr", inventions: 5 }`. Therefore, to pass a JS object in JSX, you must wrap the object in another pair of curly braces: `person={{ name: "Hedy Lamarr", inventions: 5 }}`. + +You may see this with inline CSS styles in JSX. React does not require you to use inline styles (CSS classes work great for most cases). But when you need an inline style, you pass an object to the `style` attribute: + +<Sandpack> + +```js +export default function TodoList() { + return ( + <ul style={{ + backgroundColor: 'black', + color: 'pink' + }}> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +ul { padding: 20px 20px 20px 40px; margin: 0; } +``` + +</Sandpack> + +Try changing the values of `backgroundColor` and `color`. + +You can really see the JavaScript object inside the curly braces when you write it like this: + +```js {2-5} +<ul style={ + { + backgroundColor: 'black', + color: 'pink' + } +}> +``` + +The next time you see `{{` and `}}` in JSX, know that it's nothing more than an object inside the JSX curlies! + +<Pitfall> + +Inline `style` properties are written in camelCase. For example, HTML `<ul style="background-color: black">` would be written as `<ul style={{ backgroundColor: 'black' }}>` in your component. + +</Pitfall> + +## More fun with JavaScript objects and curly braces {/*more-fun-with-javascript-objects-and-curly-braces*/} + +You can move several expressions into one object, and reference them in your JSX inside curly braces: + +<Sandpack> + +```js +const person = { + name: 'Gregorio Y. Zara', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src="https://i.imgur.com/7vQD0fPs.jpg" + alt="Gregorio Y. Zara" + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +In this example, the `person` JavaScript object contains a `name` string and a `theme` object: + +```js +const person = { + name: 'Gregorio Y. Zara', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; +``` + +The component can use these values from `person` like so: + +```js +<div style={person.theme}> + <h1>{person.name}'s Todos</h1> +``` + +JSX is very minimal as a templating language because it lets you organize data and logic using JavaScript. + +<Recap> + +Now you know almost everything about JSX: + +* JSX attributes inside quotes are passed as strings. +* Curly braces let you bring JavaScript logic and variables into your markup. +* They work inside the JSX tag content or immediately after `=` in attributes. +* `{{` and `}}` is not special syntax: it's a JavaScript object tucked inside JSX curly braces. + +</Recap> + +<Challenges> + +#### Fix the mistake {/*fix-the-mistake*/} + +This code crashes with an error saying `Objects are not valid as a React child`: + +<Sandpack> + +```js +const person = { + name: 'Gregorio Y. Zara', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person}'s Todos</h1> + <img + className="avatar" + src="https://i.imgur.com/7vQD0fPs.jpg" + alt="Gregorio Y. Zara" + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +Can you find the problem? + +<Hint>Look for what's inside the curly braces. Are we putting the right thing there?</Hint> + +<Solution> + +This is happening because this example renders *an object itself* into the markup rather than a string: `<h1>{person}'s Todos</h1>` is trying to render the entire `person` object! Including raw objects as text content throws an error because React doesn't know how you want to display them. + +To fix it, replace `<h1>{person}'s Todos</h1>` with `<h1>{person.name}'s Todos</h1>`: + +<Sandpack> + +```js +const person = { + name: 'Gregorio Y. Zara', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src="https://i.imgur.com/7vQD0fPs.jpg" + alt="Gregorio Y. Zara" + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +</Solution> + +#### Extract information into an object {/*extract-information-into-an-object*/} + +Extract the image URL into the `person` object. + +<Sandpack> + +```js +const person = { + name: 'Gregorio Y. Zara', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src="https://i.imgur.com/7vQD0fPs.jpg" + alt="Gregorio Y. Zara" + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +<Solution> + +Move the image URL into a property called `person.imageUrl` and read it from the `<img>` tag using the curlies: + +<Sandpack> + +```js +const person = { + name: 'Gregorio Y. Zara', + imageUrl: "https://i.imgur.com/7vQD0fPs.jpg", + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src={person.imageUrl} + alt="Gregorio Y. Zara" + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +</Solution> + +#### Write an expression inside JSX curly braces {/*write-an-expression-inside-jsx-curly-braces*/} + +In the object below, the full image URL is split into four parts: base URL, `imageId`, `imageSize`, and file extension. + +We want the image URL to combine these attributes together: base URL (always `'https://i.imgur.com/'`), `imageId` (`'7vQD0fP'`), `imageSize` (`'s'`), and file extension (always `'.jpg'`). However, something is wrong with how the `<img>` tag specifies its `src`. + +Can you fix it? + +<Sandpack> + +```js + +const baseUrl = 'https://i.imgur.com/'; +const person = { + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP', + imageSize: 's', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src="{baseUrl}{person.imageId}{person.imageSize}.jpg" + alt={person.name} + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +To check that your fix worked, try changing the value of `imageSize` to `'b'`. The image should resize after your edit. + +<Solution> + +You can write it as `src={baseUrl + person.imageId + person.imageSize + '.jpg'}`. + +1. `{` opens the JavaScript expression +2. `baseUrl + person.imageId + person.imageSize + '.jpg'` produces the correct URL string +3. `}` closes the JavaScript expression + +<Sandpack> + +```js +const baseUrl = 'https://i.imgur.com/'; +const person = { + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP', + imageSize: 's', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src={baseUrl + person.imageId + person.imageSize + '.jpg'} + alt={person.name} + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +You can also move this expression into a separate function like `getImageUrl` below: + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js' + +const person = { + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP', + imageSize: 's', + theme: { + backgroundColor: 'black', + color: 'pink' + } +}; + +export default function TodoList() { + return ( + <div style={person.theme}> + <h1>{person.name}'s Todos</h1> + <img + className="avatar" + src={getImageUrl(person)} + alt={person.name} + /> + <ul> + <li>Improve the videophone</li> + <li>Prepare aeronautics lectures</li> + <li>Work on the alcohol-fuelled engine</li> + </ul> + </div> + ); +} +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + person.imageSize + + '.jpg' + ); +} +``` + +```css +body { padding: 0; margin: 0 } +body > div > div { padding: 20px; } +.avatar { border-radius: 50%; height: 90px; } +``` + +</Sandpack> + +Variables and functions can help you keep the markup simple! + +</Solution> + +</Challenges> \ No newline at end of file diff --git a/beta/src/content/learn/keeping-components-pure.md b/beta/src/content/learn/keeping-components-pure.md new file mode 100644 index 000000000..60760edc5 --- /dev/null +++ b/beta/src/content/learn/keeping-components-pure.md @@ -0,0 +1,864 @@ +--- +title: Keeping Components Pure +--- + +<Intro> + +Some JavaScript functions are *pure.* Pure functions only perform a calculation and nothing more. By strictly only writing your components as pure functions, you can avoid an entire class of baffling bugs and unpredictable behavior as your codebase grows. To get these benefits, though, there are a few rules you must follow. + +</Intro> + +<YouWillLearn> + +* What purity is and how it helps you avoid bugs +* How to keep components pure by keeping changes out of the render phase +* How to use Strict Mode to find mistakes in your components + +</YouWillLearn> + +## Purity: Components as formulas {/*purity-components-as-formulas*/} + +In computer science (and especially the world of functional programming), [a pure function](https://wikipedia.org/wiki/Pure_function) is a function with the following characteristics: + +* **It minds its own business.** It does not change any objects or variables that existed before it was called. +* **Same inputs, same output.** Given the same inputs, a pure function should always return the same result. + +You might already be familiar with one example of pure functions: formulas in math. + +Consider this math formula: <Math><MathI>y</MathI> = 2<MathI>x</MathI></Math>. + +If <Math><MathI>x</MathI> = 2</Math> then <Math><MathI>y</MathI> = 4</Math>. Always. + +If <Math><MathI>x</MathI> = 3</Math> then <Math><MathI>y</MathI> = 6</Math>. Always. + +If <Math><MathI>x</MathI> = 3</Math>, <MathI>y</MathI> won't sometimes be <Math>9</Math> or <Math>–1</Math> or <Math>2.5</Math> depending on the time of day or the state of the stock market. + +If <Math><MathI>y</MathI> = 2<MathI>x</MathI></Math> and <Math><MathI>x</MathI> = 3</Math>, <MathI>y</MathI> will _always_ be <Math>6</Math>. + +If we made this into a JavaScript function, it would look like this: + +```js +function double(number) { + return 2 * number; +} +``` + +In the above example, `double` is a **pure function.** If you pass it `3`, it will return `6`. Always. + +React is designed around this concept. **React assumes that every component you write is a pure function.** This means that React components you write must always return the same JSX given the same inputs: + +<Sandpack> + +```js App.js +function Recipe({ drinkers }) { + return ( + <ol> + <li>Boil {drinkers} cups of water.</li> + <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li> + <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li> + </ol> + ); +} + +export default function App() { + return ( + <section> + <h1>Spiced Chai Recipe</h1> + <h2>For two</h2> + <Recipe drinkers={2} /> + <h2>For a gathering</h2> + <Recipe drinkers={4} /> + </section> + ); +} +``` + +</Sandpack> + +When you pass `drinkers={2}` to `Recipe`, it will return JSX containing `2 cups of water`. Always. + +If you pass `drinkers={4}`, it will return JSX containing `4 cups of water`. Always. + +Just like a math formula. + +You could think of your components as recipes: if you follow them and don't introduce new ingredients during the cooking process, you will get the same dish every time. That "dish" is the JSX that the component serves to React to [render.](/learn/render-and-commit) + +<Illustration src="/images/docs/illustrations/i_puritea-recipe.png" alt="A tea recipe for x people: take x cups of water, add x spoons of tea and 0.5x spoons of spices, and 0.5x cups of milk" /> + +## Side Effects: (un)intended consequences {/*side-effects-unintended-consequences*/} + +React's rendering process must always be pure. Components should only *return* their JSX, and not *change* any objects or variables that existed before rendering—that would make them impure! + +Here is a component that breaks this rule: + +<Sandpack> + +```js +let guest = 0; + +function Cup() { + // Bad: changing a preexisting variable! + guest = guest + 1; + return <h2>Tea cup for guest #{guest}</h2>; +} + +export default function TeaSet() { + return ( + <> + <Cup /> + <Cup /> + <Cup /> + </> + ); +} +``` + +</Sandpack> + +This component is reading and writing a `guest` variable declared outside of it. This means that **calling this component multiple times will produce different JSX!** And what's more, if _other_ components read `guest`, they will produce different JSX, too, depending on when they were rendered! That's not predictable. + +Going back to our formula <Math><MathI>y</MathI> = 2<MathI>x</MathI></Math>, now even if <Math><MathI>x</MathI> = 2</Math>, we cannot trust that <Math><MathI>y</MathI> = 4</Math>. Our tests could fail, our users would be baffled, planes would fall out of the sky—you can see how this would lead to confusing bugs! + +You can fix this component by [passing `guest` as a prop instead](/learn/passing-props-to-a-component): + +<Sandpack> + +```js +function Cup({ guest }) { + return <h2>Tea cup for guest #{guest}</h2>; +} + +export default function TeaSet() { + return ( + <> + <Cup guest={1} /> + <Cup guest={2} /> + <Cup guest={3} /> + </> + ); +} +``` + +</Sandpack> + +Now your component is pure, as the JSX it returns only depends on the `guest` prop. + +In general, you should not expect your components to be rendered in any particular order. It doesn't matter if you call <Math><MathI>y</MathI> = 2<MathI>x</MathI></Math> before or after <Math><MathI>y</MathI> = 5<MathI>x</MathI></Math>: both formulas will resolve independently of each other. In the same way, each component should only "think for itself", and not attempt to coordinate with or depend upon others during rendering. Rendering is like a school exam: each component should calculate JSX on their own! + +<DeepDive> + +#### Detecting impure calculations with StrictMode {/*detecting-impure-calculations-with-strict-mode*/} + +Although you might not have used them all yet, in React there are three kinds of inputs that you can read while rendering: [props](/learn/passing-props-to-a-component), [state](/learn/state-a-components-memory), and [context.](/learn/passing-data-deeply-with-context) You should always treat these inputs as read-only. + +When you want to *change* something in response to user input, you should [set state](/learn/state-a-components-memory) instead of writing to a variable. You should never change preexisting variables or objects while your component is rendering. + +React offers a "Strict Mode" in which it calls each component's function twice during development. **By calling the component functions twice, Strict Mode helps find components that break these rules.** + +Notice how the original example displayed "Guest #2", "Guest #4", and "Guest #6" instead of "Guest #1", "Guest #2", and "Guest #3". The original function was impure, so calling it twice broke it. But the fixed pure version works even if the function is called twice every time. **Pure functions only calculate, so calling them twice won't change anything**--just like calling `double(2)` twice doesn't change what's returned, and solving <Math><MathI>y</MathI> = 2<MathI>x</MathI></Math> twice doesn't change what <MathI>y</MathI> is. Same inputs, same outputs. Always. + +Strict Mode has no effect in production, so it won't slow down the app for your users. To opt into Strict Mode, you can wrap your root component into `<React.StrictMode>`. Some frameworks do this by default. + +</DeepDive> + +### Local mutation: Your component's little secret {/*local-mutation-your-components-little-secret*/} + +In the above example, the problem was that the component changed a *preexisting* variable while rendering. This is often called a **"mutation"** to make it sound a bit scarier. Pure functions don't mutate variables outside of the function's scope or objects that were created before the call—that makes them impure! + +However, **it's completely fine to change variables and objects that you've *just* created while rendering.** In this example, you create an `[]` array, assign it to a `cups` variable, and then `push` a dozen cups into it: + +<Sandpack> + +```js +function Cup({ guest }) { + return <h2>Tea cup for guest #{guest}</h2>; +} + +export default function TeaGathering() { + let cups = []; + for (let i = 1; i <= 12; i++) { + cups.push(<Cup key={i} guest={i} />); + } + return cups; +} +``` + +</Sandpack> + +If the `cups` variable or the `[]` array were created outside the `TeaGathering` function, this would be a huge problem! You would be changing a *preexisting* object by pushing items into that array. + +However, it's fine because you've created them *during the same render*, inside `TeaGathering`. No code outside of `TeaGathering` will ever know that this happened. This is called **"local mutation"**—it's like your component's little secret. + +## Where you _can_ cause side effects {/*where-you-_can_-cause-side-effects*/} + +While functional programming relies heavily on purity, at some point, somewhere, _something_ has to change. That's kind of the point of programming! These changes—updating the screen, starting an animation, changing the data—are called **side effects.** They're things that happen _"on the side"_, not during rendering. + +In React, **side effects usually belong inside [event handlers.](/learn/responding-to-events)** Event handlers are functions that React runs when you perform some action—for example, when you click a button. Even though event handlers are defined *inside* your component, they don't run *during* rendering! **So event handlers don't need to be pure.** + +If you've exhausted all other options and can't find the right event handler for your side effect, you can still attach it to your returned JSX with a [`useEffect`](/reference/react/useEffect) call in your component. This tells React to execute it later, after rendering, when side effects are allowed. **However, this approach should be your last resort.** + +When possible, try to express your logic with rendering alone. You'll be surprised how far this can take you! + +<DeepDive> + +#### Why does React care about purity? {/*why-does-react-care-about-purity*/} + +Writing pure functions takes some habit and discipline. But it also unlocks marvelous opportunities: + +* Your components could run in a different environment—for example, on the server! Since they return the same result for the same inputs, one component can serve many user requests. +* You can improve performance by [skipping rendering](/reference/react/memo) components whose inputs have not changed. This is safe because pure functions always return the same results, so they are safe to cache. +* If some data changes in the middle of rendering a deep component tree, React can restart rendering without wasting time to finish the outdated render. Purity makes it safe to stop calculating at any time. + +Every new React feature we're building takes advantage of purity. From data fetching to animations to performance, keeping components pure unlocks the power of the React paradigm. + +</DeepDive> + +<Recap> + +* A component must be pure, meaning: + * **It minds its own business.** It should not change any objects or variables that existed before rendering. + * **Same inputs, same output.** Given the same inputs, a component should always return the same JSX. +* Rendering can happen at any time, so components should not depend on each others' rendering sequence. +* You should not mutate any of the inputs that your components use for rendering. That includes props, state, and context. To update the screen, ["set" state](/learn/state-a-components-memory) instead of mutating preexisting objects. +* Strive to express your component's logic in the JSX you return. When you need to "change things", you'll usually want to do it in an event handler. As a last resort, you can `useEffect`. +* Writing pure functions takes a bit of practice, but it unlocks the power of React's paradigm. + +</Recap> + + + +<Challenges> + +#### Fix a broken clock {/*fix-a-broken-clock*/} + +This component tries to set the `<h1>`'s CSS class to `"night"` during the time from midnight to six hours in the morning, and `"day"` at all other times. However, it doesn't work. Can you fix this component? + +You can verify whether your solution works by temporarily changing the computer's timezone. When the current time is between midnight and six in the morning, the clock should have inverted colors! + +<Hint> + +Rendering is a *calculation*, it shouldn't try to "do" things. Can you express the same idea differently? + +</Hint> + +<Sandpack> + +```js Clock.js active +export default function Clock({ time }) { + let hours = time.getHours(); + if (hours >= 0 && hours <= 6) { + document.getElementById('time').className = 'night'; + } else { + document.getElementById('time').className = 'day'; + } + return ( + <h1 id="time"> + {time.toLocaleTimeString()} + </h1> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import Clock from './Clock.js'; + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} + +export default function App() { + const time = useTime(); + return ( + <Clock time={time} /> + ); +} +``` + +```css +body > * { + width: 100%; + height: 100%; +} +.day { + background: #fff; + color: #222; +} +.night { + background: #222; + color: #fff; +} +``` + +</Sandpack> + +<Solution> + +You can fix this component by calculating the `className` and including it in the render output: + +<Sandpack> + +```js Clock.js active +export default function Clock({ time }) { + let hours = time.getHours(); + let className; + if (hours >= 0 && hours <= 6) { + className = 'night'; + } else { + className = 'day'; + } + return ( + <h1 className={className}> + {time.toLocaleTimeString()} + </h1> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import Clock from './Clock.js'; + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} + +export default function App() { + const time = useTime(); + return ( + <Clock time={time} /> + ); +} +``` + +```css +body > * { + width: 100%; + height: 100%; +} +.day { + background: #fff; + color: #222; +} +.night { + background: #222; + color: #fff; +} +``` + +</Sandpack> + +In this example, the side effect (modifying the DOM) was not necessary at all. You only needed to return JSX. + +</Solution> + +#### Fix a broken profile {/*fix-a-broken-profile*/} + +Two `Profile` components are rendered side by side with different data. Press "Collapse" on the first profile, and then "Expand" it. You'll notice that both profiles now show the same person. This is a bug. + +Find the cause of the bug and fix it. + +<Hint> + +The buggy code is in `Profile.js`. Make sure you read it all from top to bottom! + +</Hint> + +<Sandpack> + +```js Profile.js +import Panel from './Panel.js'; +import { getImageUrl } from './utils.js'; + +let currentPerson; + +export default function Profile({ person }) { + currentPerson = person; + return ( + <Panel> + <Header /> + <Avatar /> + </Panel> + ) +} + +function Header() { + return <h1>{currentPerson.name}</h1>; +} + +function Avatar() { + return ( + <img + className="avatar" + src={getImageUrl(currentPerson)} + alt={currentPerson.name} + width={50} + height={50} + /> + ); +} +``` + +```js Panel.js hidden +import { useState } from 'react'; + +export default function Panel({ children }) { + const [open, setOpen] = useState(true); + return ( + <section className="panel"> + <button onClick={() => setOpen(!open)}> + {open ? 'Collapse' : 'Expand'} + </button> + {open && children} + </section> + ); +} +``` + +```js App.js +import Profile from './Profile.js'; + +export default function App() { + return ( + <> + <Profile person={{ + imageId: 'lrWQx8l', + name: 'Subrahmanyan Chandrasekhar', + }} /> + <Profile person={{ + imageId: 'MK3eW3A', + name: 'Creola Katherine Johnson', + }} /> + </> + ) +} +``` + +```js utils.js hidden +export function getImageUrl(person, size = 's') { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 5px; border-radius: 50%; } +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; + width: 200px; +} +h1 { margin: 5px; font-size: 18px; } +``` + +</Sandpack> + +<Solution> + +The problem is that the `Profile` component writes to a preexisting variable called `currentPerson`, and the `Header` and `Avatar` components read from it. This makes *all three of them* impure and difficult to predict. + +To fix the bug, remove the `currentPerson` variable. Instead, pass all information from `Profile` to `Header` and `Avatar` via props. You'll need to add a `person` prop to both components and pass it all the way down. + +<Sandpack> + +```js Profile.js active +import Panel from './Panel.js'; +import { getImageUrl } from './utils.js'; + +export default function Profile({ person }) { + return ( + <Panel> + <Header person={person} /> + <Avatar person={person} /> + </Panel> + ) +} + +function Header({ person }) { + return <h1>{person.name}</h1>; +} + +function Avatar({ person }) { + return ( + <img + className="avatar" + src={getImageUrl(person)} + alt={person.name} + width={50} + height={50} + /> + ); +} +``` + +```js Panel.js hidden +import { useState } from 'react'; + +export default function Panel({ children }) { + const [open, setOpen] = useState(true); + return ( + <section className="panel"> + <button onClick={() => setOpen(!open)}> + {open ? 'Collapse' : 'Expand'} + </button> + {open && children} + </section> + ); +} +``` + +```js App.js +import Profile from './Profile.js'; + +export default function App() { + return ( + <> + <Profile person={{ + imageId: 'lrWQx8l', + name: 'Subrahmanyan Chandrasekhar', + }} /> + <Profile person={{ + imageId: 'MK3eW3A', + name: 'Creola Katherine Johnson', + }} /> + </> + ); +} +``` + +```js utils.js hidden +export function getImageUrl(person, size = 's') { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 5px; border-radius: 50%; } +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; + width: 200px; +} +h1 { margin: 5px; font-size: 18px; } +``` + +</Sandpack> + +Remember that React does not guarantee that component functions will execute in any particular order, so you can't communicate between them by setting variables. All communication must happen through props. + +</Solution> + +#### Fix a broken story tray {/*fix-a-broken-story-tray*/} + +The CEO of your company is asking you to add "stories" to your online clock app, and you can't say no. You've written a `StoryTray` component that accepts a list of `stories`, followed by a "Create Story" placeholder. + +You implemented the "Create Story" placeholder by pushing one more fake story at the end of the `stories` array that you receive as a prop. But for some reason, "Create Story" appears more than once. Fix the issue. + +<Sandpack> + +```js StoryTray.js active +export default function StoryTray({ stories }) { + stories.push({ + id: 'create', + label: 'Create Story' + }); + + return ( + <ul> + {stories.map(story => ( + <li key={story.id}> + {story.label} + </li> + ))} + </ul> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import StoryTray from './StoryTray.js'; + +let initialStories = [ + {id: 0, label: "Ankit's Story" }, + {id: 1, label: "Taylor's Story" }, +]; + +export default function App() { + let [stories, setStories] = useState([...initialStories]) + let time = useTime(); + + // HACK: Prevent the memory from growing forever while you read docs. + // We're breaking our own rules here. + if (stories.length > 100) { + stories.length = 100; + } + + return ( + <div + style={{ + width: '100%', + height: '100%', + textAlign: 'center', + }} + > + <h2>It is {time.toLocaleTimeString()} now.</h2> + <StoryTray stories={stories} /> + </div> + ); +} + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} +``` + +```css +ul { + margin: 0; + list-style-type: none; +} + +li { + border: 1px solid #aaa; + border-radius: 6px; + float: left; + margin: 5px; + margin-bottom: 20px; + padding: 5px; + width: 70px; + height: 100px; +} +``` + +```js sandbox.config.json hidden +{ + "hardReloadOnChange": true +} +``` + +</Sandpack> + +<Solution> + +Notice how whenever the clock updates, "Create Story" is added *twice*. This serves as a hint that we have a mutation during rendering--Strict Mode calls components twice to make these issues more noticeable. + +`StoryTray` function is not pure. By calling `push` on the received `stories` array (a prop!), it is mutating an object that was created *before* `StoryTray` started rendering. This makes it buggy and very difficult to predict. + +The simplest fix is to not touch the array at all, and render "Create Story" separately: + +<Sandpack> + +```js StoryTray.js active +export default function StoryTray({ stories }) { + return ( + <ul> + {stories.map(story => ( + <li key={story.id}> + {story.label} + </li> + ))} + <li>Create Story</li> + </ul> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import StoryTray from './StoryTray.js'; + +let initialStories = [ + {id: 0, label: "Ankit's Story" }, + {id: 1, label: "Taylor's Story" }, +]; + +export default function App() { + let [stories, setStories] = useState([...initialStories]) + let time = useTime(); + + // HACK: Prevent the memory from growing forever while you read docs. + // We're breaking our own rules here. + if (stories.length > 100) { + stories.length = 100; + } + + return ( + <div + style={{ + width: '100%', + height: '100%', + textAlign: 'center', + }} + > + <h2>It is {time.toLocaleTimeString()} now.</h2> + <StoryTray stories={stories} /> + </div> + ); +} + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} +``` + +```css +ul { + margin: 0; + list-style-type: none; +} + +li { + border: 1px solid #aaa; + border-radius: 6px; + float: left; + margin: 5px; + margin-bottom: 20px; + padding: 5px; + width: 70px; + height: 100px; +} +``` + +</Sandpack> + +Alternatively, you could create a _new_ array (by copying the existing one) before you push an item into it: + +<Sandpack> + +```js StoryTray.js active +export default function StoryTray({ stories }) { + // Copy the array! + let storiesToDisplay = stories.slice(); + + // Does not affect the original array: + storiesToDisplay.push({ + id: 'create', + label: 'Create Story' + }); + + return ( + <ul> + {storiesToDisplay.map(story => ( + <li key={story.id}> + {story.label} + </li> + ))} + </ul> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import StoryTray from './StoryTray.js'; + +let initialStories = [ + {id: 0, label: "Ankit's Story" }, + {id: 1, label: "Taylor's Story" }, +]; + +export default function App() { + let [stories, setStories] = useState([...initialStories]) + let time = useTime(); + + // HACK: Prevent the memory from growing forever while you read docs. + // We're breaking our own rules here. + if (stories.length > 100) { + stories.length = 100; + } + + return ( + <div + style={{ + width: '100%', + height: '100%', + textAlign: 'center', + }} + > + <h2>It is {time.toLocaleTimeString()} now.</h2> + <StoryTray stories={stories} /> + </div> + ); +} + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} +``` + +```css +ul { + margin: 0; + list-style-type: none; +} + +li { + border: 1px solid #aaa; + border-radius: 6px; + float: left; + margin: 5px; + margin-bottom: 20px; + padding: 5px; + width: 70px; + height: 100px; +} +``` + +</Sandpack> + +This keeps your mutation local and your rendering function pure. However, you still need to be careful: for example, if you tried to change any of the array's existing items, you'd have to clone those items too. + +It is useful to remember which operations on arrays mutate them, and which don't. For example, `push`, `pop`, `reverse`, and `sort` will mutate the original array, but `slice`, `filter`, and `map` will create a new one. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/lifecycle-of-reactive-effects.md b/beta/src/content/learn/lifecycle-of-reactive-effects.md new file mode 100644 index 000000000..1bdf71095 --- /dev/null +++ b/beta/src/content/learn/lifecycle-of-reactive-effects.md @@ -0,0 +1,2105 @@ +--- +title: 'Lifecycle of Reactive Effects' +--- + +<Intro> + +Effects have a different lifecycle from components. Components may mount, update, or unmount. An Effect can only do two things: to start synchronizing something, and later to stop synchronizing it. This cycle can happen multiple times if your Effect depends on props and state that change over time. React provides a linter rule to check that you've specified your Effect's dependencies correctly. This keeps your Effect synchronized to the latest props and state. + +</Intro> + +<YouWillLearn> + +- How an Effect's lifecycle is different from a component's lifecycle +- How to think about each individual Effect in isolation +- When your Effect needs to re-synchronize, and why +- How your Effect's dependencies are determined +- What it means for a value to be reactive +- What an empty dependency array means +- How React verifies your dependencies are correct with a linter +- What to do when you disagree with the linter + +</YouWillLearn> + +## The lifecycle of an Effect {/*the-lifecycle-of-an-effect*/} + +Every React component goes through the same lifecycle: + +- A component _mounts_ when it's added to the screen. +- A component _updates_ when it receives new props or state. This usually happens in response to an interaction. +- A component _unmounts_ when it's removed from the screen. + +**It's a good way to think about components, but _not_ about Effects.** Instead, try to think about each Effect independently from your component's lifecycle. An Effect describes how to [synchronize an external system](/learn/synchronizing-with-effects) to the current props and state. As your code changes, this synchronization will need to happen more or less often. + +To illustrate this point, consider this Effect connecting your component to a chat server: + +```js +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId]); + // ... +} +``` + +Your Effect's body specifies how to **start synchronizing:** + +```js {2-3} + // ... + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + // ... +``` + +The cleanup function returned by your Effect specifies how to **stop synchronizing:** + +```js {5} + // ... + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + // ... +``` + +Intuitively, you might think that React would **start synchronizing** when your component mounts and **stop synchronizing** when your component unmounts. However, this is not the end of the story! Sometimes, it may also be necessary to **start and stop synchronizing multiple times** while the component remains mounted. + +Let's look at _why_ this is necessary, _when_ it happens, and _how_ you can control this behavior. + +<Note> + +Some Effects don't return a cleanup function at all. [More often than not,](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) you'll want to return one--but if you don't, React will behave as if you returned an empty cleanup function that doesn't do anything. + +</Note> + +### Why synchronization may need to happen more than once {/*why-synchronization-may-need-to-happen-more-than-once*/} + +Imagine this `ChatRoom` component receives a `roomId` prop that the user picks in a dropdown. Let's say that initially the user picks the `"general"` room as the `roomId`. Your app displays the `"general"` chat room: + +```js {3} +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId /* "general" */ }) { + // ... + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +After the UI is displayed, React will run your Effect to **start synchronizing.** It connects to the `"general"` room: + +```js {3,4} +function ChatRoom({ roomId /* "general" */ }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // Connects to the "general" room + connection.connect(); + return () => { + connection.disconnect(); // Disconnects from the "general" room + }; + }, [roomId]); + // ... +``` + +So far, so good. + +Later, the user picks a different room in the dropdown (for example, `"travel"`). First, React will update the UI: + +```js {1} +function ChatRoom({ roomId /* "travel" */ }) { + // ... + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +Pause to think about what should happen next. The user sees that `"travel"` is the selected chat room in the UI. However, the Effect that ran the last time is still connected to the `"general"` room. **The `roomId` prop has changed, so whatever your Effect did back then (connecting to the `"general"` room) no longer matches the UI.** + +At this point, you want React to do two things: + +1. Stop synchronizing with the old `roomId` (disconnect from the `"general"` room) +2. Start synchronizing with the new `roomId` (connect to the `"travel"` room) + +**Luckily, you've already taught React how to do both of these things!** Your Effect's body specifies how to start synchronizing, and your cleanup function specifies how to stop synchronizing. All that React needs to do now is to call them in the correct order and with the correct props and state. Let's see how exactly that happens. + +### How React re-synchronizes your Effect {/*how-react-re-synchronizes-your-effect*/} + +Recall that your `ChatRoom` component has received a new value for its `roomId` prop. It used to be `"general"`, and now it is `"travel"`. React needs to re-synchronize your Effect to re-connect you to a different room. + +To **stop synchronizing,** React will call the cleanup function that your Effect returned after connecting to the `"general"` room. Since `roomId` was `"general"`, the cleanup function disconnects from the `"general"` room: + +```js {6} +function ChatRoom({ roomId /* "general" */ }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // Connects to the "general" room + connection.connect(); + return () => { + connection.disconnect(); // Disconnects from the "general" room + }; + // ... +``` + +Then React will run the Effect that you've provided during this render. This time, `roomId` is `"travel"` so it will **start synchronizing** to the `"travel"` chat room (until its cleanup function is eventually called too): + +```js {3,4} +function ChatRoom({ roomId /* "travel" */ }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // Connects to the "travel" room + connection.connect(); + // ... +``` + +Thanks to this, you're now connected to the same room that the user chose in the UI. Disaster averted! + +Every time after your component re-renders with a different `roomId`, your Effect will re-synchronize. For example, let's say the user changes `roomId` from `"travel"` to `"music"`. React will again **stop synchronizing** your Effect by calling its cleanup function (disconnecting you from the `"travel"` room). Then it will **start synchronizing** again by running its body with the new `roomId` prop (connecting you to the `"music"` room). + +Finally, when the user goes to a different screen, `ChatRoom` unmounts. Now there is no need to stay connected at all. React will **stop synchronizing** your Effect one last time and disconnect you from the `"music"` chat room. + +### Thinking from the Effect's perspective {/*thinking-from-the-effects-perspective*/} + +Let's recap everything that's happened from the `ChatRoom` component's perspective: + +1. `ChatRoom` mounted with `roomId` set to `"general"` +1. `ChatRoom` updated with `roomId` set to `"travel"` +1. `ChatRoom` updated with `roomId` set to `"music"` +1. `ChatRoom` unmounted + +During each of these points in the component's lifecycle, your Effect did different things: + +1. Your Effect connected to the `"general"` room +1. Your Effect disconnected from the `"general"` room and connected to the `"travel"` room +1. Your Effect disconnected from the `"travel"` room and connected to the `"music"` room +1. Your Effect disconnected from the `"music"` room + +Now let's think about what happened from the perspective of the Effect itself: + +```js + useEffect(() => { + // Your Effect connected to the room specified with roomId... + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + // ...until it disconnected + connection.disconnect(); + }; + }, [roomId]); +``` + +This code's structure might inspire you to see what happened as a sequence of non-overlapping time periods: + +1. Your Effect connected to the `"general"` room (until it disconnected) +1. Your Effect connected to the `"travel"` room (until it disconnected) +1. Your Effect connected to the `"music"` room (until it disconnected) + +Previously, you were thinking from the component's perspective. When you looked from the component's perspective, it was tempting to think of Effects as "callbacks" or "lifecycle events" that fire at a specific time like "after a render" or "before unmount". This way of thinking gets complicated very fast, so it's best to avoid it. + +**Instead, always focus on a single start/stop cycle at a time. It shouldn't matter whether a component is mounting, updating, or unmounting. All you need to do is to describe how to start synchronization and how to stop it. If you do it well, your Effect will be resilient to being started and stopped as many times as it's needed.** + +This might remind you how you don't think whether a component is mounting or updating when you write the rendering logic that creates JSX. You describe what should be on the screen, and React [figures out the rest.](/learn/reacting-to-input-with-state) + +### How React verifies that your Effect can re-synchronize {/*how-react-verifies-that-your-effect-can-re-synchronize*/} + +Here is a live example that you can play with. Press "Open chat" to mount the `ChatRoom` component: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + return <h1>Welcome to the {roomId} room!</h1>; +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Notice that when the component mounts for the first time, you see three logs: + +1. `✅ Connecting to "general" room at https://localhost:1234...` *(development-only)* +1. `❌ Disconnected from "general" room at https://localhost:1234.` *(development-only)* +1. `✅ Connecting to "general" room at https://localhost:1234...` + +The first two logs are development-only. In development, React always remounts each component once. **In other words, React verifies that your Effect can re-synchronize by forcing it to do that immediately in development.** This might remind you how you might open the door and close it an extra time to check that the door lock works. React starts and stops your Effect one extra time in development to check [you've implemented its cleanup well.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) + +The main reason your Effect will re-synchronize in practice is if some data it uses has changed. In the sandbox above, change the selected chat room. Notice how, when the `roomId` changes, your Effect re-synchronizes. + +However, there are also more unusual cases in which re-synchronization is necessary. For example, try editing the `serverUrl` in the sandbox above while the chat is open. Notice how the Effect re-synchronizes in response to your edits to the code. In the future, React may add more features that take advantage of re-synchronization. + +### How React knows that it needs to re-synchronize the Effect {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} + +You might be wondering how React knew that your Effect needed to re-synchronize after `roomId` changes. It's because *you told React* that this Effect's code depends on `roomId` by including it in the [list of dependencies:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) + +```js {1,3,8} +function ChatRoom({ roomId }) { // The roomId prop may change over time + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // This Effect reads roomId + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId]); // So you tell React that this Effect "depends on" roomId + // ... +``` + +Here's how this works: + +1. You knew `roomId` is a prop, which means it can change over time. +2. You knew that your Effect reads `roomId` (so its logic depends on a value that may change later). +3. This is why you specified it as your Effect's dependency (so that it re-synchronizes when `roomId` changes). + +Every time after your component re-renders, React will look at the array of dependencies that you have passed. If any of the values in the array is different from the value at the same spot that you passed during the previous render, React will re-synchronize your Effect. For example, if you passed `["general"]` during the initial render, and later you passed `["travel"]` during the next render, React will compare `"general"` and `"travel"`. These are different values (compared with [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), so React will re-synchronize your Effect. On the other hand, if your component re-renders but `roomId` has not changed, your Effect will remain connected to the same room. + +### Each Effect represents a separate synchronization process {/*each-effect-represents-a-separate-synchronization-process*/} + +Resist adding unrelated logic to your Effect only because this logic needs to run at the same time as an Effect you already wrote. For example, let's say you want to send an analytics event when the user visits the room. You already have an Effect that depends on `roomId`, so you might feel tempted to add the analytics call right there: + +```js {3} +function ChatRoom({ roomId }) { + useEffect(() => { + logVisit(roomId); + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId]); + // ... +} +``` + +But imagine you later add another dependency to this Effect that needs to re-establish the connection. If this Effect re-synchronizes, it will also call `logVisit(roomId)` for the same room, which you did not intend. Logging the visit **is a separate process** from connecting. This is why they should be written as two separate Effects: + +```js {2-4} +function ChatRoom({ roomId }) { + useEffect(() => { + logVisit(roomId); + }, [roomId]); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + // ... + }, [roomId]); + // ... +} +``` + +**Each Effect in your code should represent a separate and independent synchronization process.** + +In the above example, deleting one Effect wouldn’t break the other Effect's logic. This is a good indication that they synchronize different things, and so it made sense to split them up. On the other hand, if you split up a cohesive piece of logic into separate Effects, the code may look "cleaner" but will be [more difficult to maintain.](/learn/you-might-not-need-an-effect#chains-of-computations) This is why you should think whether the processes are same or separate, not whether the code looks cleaner. + +## Effects "react" to reactive values {/*effects-react-to-reactive-values*/} + +Your Effect reads two variables (`serverUrl` and `roomId`), but you only specified `roomId` as a dependency: + +```js {5,10} +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId]); + // ... +} +``` + +Why doesn't `serverUrl` need to be a dependency? + +This is because the `serverUrl` never changes due to a re-render. It's always the same no matter how many times and with which props and state the component re-renders. Since `serverUrl` never changes, it wouldn't make sense to specify it as a dependency. After all, dependencies only do something when they change over time! + +On the other hand, `roomId` may be different on a re-render. **Props, state, and other values declared inside the component are _reactive_ because they're calculated during rendering and participate in the React data flow.** + +If `serverUrl` was a state variable, it would be reactive. Reactive values must be included in dependencies: + +```js {2,5,10} +function ChatRoom({ roomId }) { // Props change over time + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // State may change over time + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // Your Effect reads props and state + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId, serverUrl]); // So you tell React that this Effect "depends on" on props and state + // ... +} +``` + +By including `serverUrl` as a dependency, you ensure that the Effect re-synchronizes after it changes. + +Try changing the selected chat room or edit the server URL in this sandbox: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, serverUrl]); + + return ( + <> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Whenever you change a reactive value like `roomId` or `serverUrl`, the Effect re-connects to the chat server. + +### What an Effect with empty dependencies means {/*what-an-effect-with-empty-dependencies-means*/} + +What happens if you move both `serverUrl` and `roomId` outside the component? + +```js {1,2} +const serverUrl = 'https://localhost:1234'; +const roomId = 'general'; + +function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, []); // ✅ All dependencies declared + // ... +} +``` + +Now your Effect's code does not use *any* reactive values, so its dependencies can be empty (`[]`). + +If you think from the component's perspective, the empty `[]` dependency array means this Effect connects to the chat room only when the component mounts, and disconnects only when the component unmounts. (Keep in mind that React would still [re-synchronize it an extra time](#how-react-verifies-that-your-effect-can-re-synchronize) in development to stress-test your Effect's logic.) + + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; +const roomId = 'general'; + +function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); + return <h1>Welcome to the {roomId} room!</h1>; +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom />} + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +However, if you [think from the Effect's perspective,](#thinking-from-the-effects-perspective) you don't need to think about mounting and unmounting at all. What's important is you've specified what your Effect does to start and stop synchronizing. Today, it has no reactive dependencies. But if you ever want the user to change `roomId` or `serverUrl` over time (and so they'd have to become reactive), your Effect's code won't change. You will only need to add them to the dependencies. + +### All variables declared in the component body are reactive {/*all-variables-declared-in-the-component-body-are-reactive*/} + +Props and state aren't the only reactive values. Values that you calculate from them are also reactive. If the props or state change, your component will re-render, and the values calculated from them will also change. This is why all variables from the component body used by the Effect should also be in the Effect dependency list. + +Let's say that the user can pick a chat server in the dropdown, but they can also configure a default server in settings. Suppose you've already put the settings state in a [context](/learn/scaling-up-with-reducer-and-context) so you read the `settings` from that context. Now you calculate the `serverUrl` based on the selected server from props and the default server from context: + +```js {3,5,10} +function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive + const settings = useContext(SettingsContext); // settings is reactive + const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // Your Effect reads roomId and serverUrl + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId, serverUrl]); // So it needs to re-synchronize when either of them changes! + // ... +} +``` + +In this example, `serverUrl` is not a prop or a state variable. It's a regular variable that you calculate during rendering. But it's calculated during rendering, so it can change due to a re-render. This is why it's reactive. + +**All values inside the component (including props, state, and variables in your component's body) are reactive. Any reactive value can change on a re-render, so you need to include reactive values as Effect's dependencies.** + +In other words, Effects "react" to all values from the component body. + +<DeepDive> + +#### Can global or mutable values be dependencies? {/*can-global-or-mutable-values-be-dependencies*/} + +Mutable values (including global variables) aren't reactive. + +**A mutable value like [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname) can't be a dependency.** It's mutable, so it can change at any time completely outside of the React rendering data flow. Changing it wouldn't trigger a re-render of your component. Therefore, even if you specified it in the dependencies, React *wouldn't know* to re-synchronize the Effect when it changes. This also breaks the rules of React because reading mutable data during rendering (which is when you calculate the dependencies) breaks [purity of rendering.](/learn/keeping-components-pure) Instead, you should read and subscribe to an external mutable value with [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) + +**A mutable value like [`ref.current`](/reference/react/useRef#reference) or things you read from it also can't be a dependency.** The ref object returned by `useRef` itself can be a dependency, but its `current` property is intentionally mutable. It lets you [keep track of something without triggering a re-render.](/learn/referencing-values-with-refs) But since changing it doesn't trigger a re-render, it's not a reactive value, and React won't know to re-run your Effect when it changes. + +As you'll learn below on this page, a linter will check for these issues automatically. + +</DeepDive> + +### React verifies that you specified every reactive value as a dependency {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} + +If your linter is [configured for React,](/learn/editor-setup#linting) it will check that every reactive value used by your Effect's code is declared as its dependency. For example, this is a lint error because both `roomId` and `serverUrl` are reactive: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +function ChatRoom({ roomId }) { // roomId is reactive + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); // <-- Something's wrong here! + + return ( + <> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +This may look like a React error, but really React is pointing out a bug in your code. Both `roomId` and `serverUrl` may change over time, but you're forgetting to re-synchronize your Effect when they change. As a result, you will remain connected to the initial `roomId` and `serverUrl` even after the user picks different values in the UI. + +To fix the bug, follow the linter's suggestion to specify `roomId` and `serverUrl` as dependencies of your Effect: + +```js {9} +function ChatRoom({ roomId, serverUrl }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [serverUrl, roomId]); // ✅ All dependencies declared + // ... +} +``` + +Try this fix in the sandbox above. Verify that the linter error is gone, and that the chat re-connects when needed. + +<Note> + +In some cases, React *knows* that a value never changes even though it's declared inside the component. For example, the [`set` function](/reference/react/useState#setstate) returned from `useState` and the ref object returned by [`useRef`](/reference/react/useRef) are *stable*--they are guaranteed to not change on a re-render. Stable values aren't reactive, so the linter lets you omit them from the list. However, including them is allowed: they won't change, so it doesn't matter. + +</Note> + +### What to do when you don't want to re-synchronize {/*what-to-do-when-you-dont-want-to-re-synchronize*/} + +In the previous example, you've fixed the lint error by listing `roomId` and `serverUrl` as dependencies. + +**However, you could instead "prove" to the linter that these values aren't reactive values,** i.e. that they *can't* change as a result of a re-render. For example, if `serverUrl` and `roomId` don't depend on rendering and always have the same values, you can move them outside the component. Now they don't need to be dependencies: + +```js {1,2,11} +const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive +const roomId = 'general'; // roomId is not reactive + +function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, []); // ✅ All dependencies declared + // ... +} +``` + +You can also move them *inside the Effect.* They aren't calculated during rendering, so they're not reactive: + +```js {3,4,10} +function ChatRoom() { + useEffect(() => { + const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive + const roomId = 'general'; // roomId is not reactive + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, []); // ✅ All dependencies declared + // ... +} +``` + +**Effects are reactive blocks of code.** They re-synchronize when the values you read inside of them change. Unlike event handlers, which only run once per interaction, Effects run whenever synchronization is necessary. + +**You can't "choose" your dependencies.** Your dependencies must include every [reactive value](#all-variables-declared-in-the-component-body-are-reactive) you read in the Effect. The linter enforces this. Sometimes this may lead to problems like infinite loops and to your Effect re-synchronizing too often. Don't fix these problems by suppressing the linter! Here's what to try instead: + +* **Check that your Effect represents an independent synchronization process.** If your Effect doesn't synchronize anything, [it might be unnecessary.](/learn/you-might-not-need-an-effect) If it synchronizes several independent things, [split it up.](#each-effect-represents-a-separate-synchronization-process) + +* **If you want to read the latest value of props or state without "reacting" to it and re-synchronizing the Effect,** you can split your Effect into a reactive part (which you'll keep in the Effect) and a non-reactive part (which you'll extract into something called an _Event function_). [Read more about separating Events from Effects.](/learn/separating-events-from-effects) + +* **Avoid relying on objects and functions as dependencies.** If you create objects and functions during rendering and then read them from an Effect, they will be different on every render. This will cause your Effect to re-synchronize every time. [Read more about removing unnecessary dependencies from your Effects.](/learn/removing-effect-dependencies) + +<Pitfall> + +The linter is your friend, but its powers are limited. The linter only knows when the dependencies are *wrong*. It doesn't know *the best* way to solve each case. If the linter suggests a dependency, but adding it causes a loop, it doesn't mean the linter should be ignored. It means you need to change the code inside (or outside) the Effect so that that value isn't reactive and doesn't *need* to be a dependency. + +If you have an existing codebase, you might have some Effects that suppress the linter like this: + +```js {3-4} +useEffect(() => { + // ... + // 🔴 Avoid suppressing the linter like this: + // eslint-ignore-next-line react-hooks/exhaustive-deps +}, []); +``` + +On the [next](/learn/separating-events-from-effects) [pages](/learn/removing-effect-dependencies), you'll learn how to fix this code without breaking the rules. It's always worth fixing! + +</Pitfall> + +<Recap> + +- Components can mount, update, and unmount. +- Each Effect has a separate lifecycle from the surrounding component. +- Each Effect describes a separate synchronization process that can *start* and *stop*. +- When you write and read Effects, you should think from each individual Effect's perspective (how to start and stop synchronization) rather than from the component's perspective (how it mounts, updates, or unmounts). +- Values declared inside the component body are "reactive". +- Reactive values should re-synchronize the Effect because they can change over time. +- The linter verifies that all reactive values used inside the Effect are specified as dependencies. +- All errors flagged by the linter are legitimate. There's always a way to fix the code that doesn't break the rules. + +</Recap> + +<Challenges> + +#### Fix reconnecting on every keystroke {/*fix-reconnecting-on-every-keystroke*/} + +In this example, the `ChatRoom` component connects to the chat room when the component mounts, disconnects when it unmounts, and reconnects when you select a different chat room. This behavior is correct, so you need to keep it working. + +However, there is a problem. Whenever you type into the message box input at the bottom, `ChatRoom` *also* reconnects to the chat. (You can notice this by clearing the console and typing into the input.) Fix the issue so that this doesn't happen. + +<Hint> + +You might need to add a dependency array for this Effect. What dependencies should be there? + +</Hint> + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input + value={message} + onChange={e => setMessage(e.target.value)} + /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +<Solution> + +This Effect didn't have a dependency array at all, so it re-synchronized after every re-render. First, add a dependency array. Then, make sure that every reactive value used by the Effect is specified in the array. For example, `roomId` is reactive (because it's a prop), so it should be included in the array. This ensures that when the user selects a different room, the chat reconnects. On the other hand, `serverUrl` is defined outside the component. This is why it doesn't need to be in the array. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input + value={message} + onChange={e => setMessage(e.target.value)} + /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +</Solution> + +#### Switch synchronization on and off {/*switch-synchronization-on-and-off*/} + +In this example, an Effect subscribes to the window [`pointermove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event) event to move a pink dot on the screen. Try hovering over the preview area (or touching the screen if you're on a mobile device), and see how the pink dot follows your movement. + +There is also a checkbox. Ticking the checkbox toggles the `canMove` state variable, but this state variable is not used anywhere in the code. Your task is to change the code so that when `canMove` is `false` (the checkbox is ticked off), the dot should stop moving. After you toggle the checkbox back on (and set `canMove` to `true`), the box should follow the movement again. In other words, whether the dot can move or not should stay synchronized to whether the checkbox is checked. + +<Hint> + +You can't declare an Effect conditionally. However, the code inside the Effect can use conditions! + +</Hint> + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + useEffect(() => { + function handleMove(e) { + setPosition({ x: e.clientX, y: e.clientY }); + } + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + }, []); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + +<Solution> + +One solution is to wrap the `setPosition` call into an `if (canMove) { ... }` condition: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + useEffect(() => { + function handleMove(e) { + if (canMove) { + setPosition({ x: e.clientX, y: e.clientY }); + } + } + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + }, [canMove]); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + +Alternatively, you could wrap the *event subscription* logic into an `if (canMove) { ... }` condition: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + useEffect(() => { + function handleMove(e) { + setPosition({ x: e.clientX, y: e.clientY }); + } + if (canMove) { + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + } + }, [canMove]); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + +In both of these cases, `canMove` is a reactive variable that you read inside the Effect. This is why it must be specified in the list of Effect dependencies. This ensures that the Effect re-synchronizes after every change to its value. + +</Solution> + +#### Investigate a stale value bug {/*investigate-a-stale-value-bug*/} + +In this example, the pink dot should move when the checkbox is on, and should stop moving when the checkbox is off. The logic for this has already been implemented: the `handleMove` event handler checks the `canMove` state variable. + +However, for some reason, the `canMove` state variable inside `handleMove` appears to be "stale": it's always `true`, even after you tick off the checkbox. How is this possible? Find the mistake in the code and fix it. + +<Hint> + +If you see a linter rule being suppressed, remove the suppression! That's where the mistakes usually are. + +</Hint> + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + function handleMove(e) { + if (canMove) { + setPosition({ x: e.clientX, y: e.clientY }); + } + } + + useEffect(() => { + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + +<Solution> + +The problem with the original code was suppressing the dependency linter. If you remove the suppression, you'll see that this Effect depends on the `handleMove` function. This makes sense: `handleMove` is declared inside the component body, which makes it a reactive value. Every reactive value must be specified as a depedency, or it can potentially get stale over time! + +The author of the original code has "lied" to React by saying that the Effect does not depend (`[]`) on any reactive values. This is why React did not re-synchronize the Effect after `canMove` has changed (and `handleMove` with it). Because React did not re-synchronize the Effect, the `handleMove` attached as a listener is the `handleMove` function created during the initial render. During the initial render, `canMove` was `true`, which is why `handleMove` from the initial render will forever see that value. + +**If you never suppress the linter, you will never see problems with stale values.** There are a few different ways to solve this bug, but you should always start by removing the linter suppression. Then change the code to fix the lint error. + +You can change the Effect dependencies to `[handleMove]`, but since it's going to be a newly defined function for every render, you might as well remove dependencies array altogether. Then the Effect *will* re-synchronize after every re-render: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + function handleMove(e) { + if (canMove) { + setPosition({ x: e.clientX, y: e.clientY }); + } + } + + useEffect(() => { + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + }); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + +This solution works, but it's not ideal. If you put `console.log('Resubscribing')` inside the Effect, you'll notice that it resubscribes after every re-render. Resubscribing is fast, but it would still be nice to avoid doing it so often. + +A better fix would be to move the `handleMove` function *inside* the Effect. Then `handleMove` won't be a reactive value, and so your Effect won't depend on a function. Instead, it will need to depend on `canMove` which your code now reads from inside the Effect. This matches the behavior you wanted, since your Effect will now stay synchronized with the value of `canMove`: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + useEffect(() => { + function handleMove(e) { + if (canMove) { + setPosition({ x: e.clientX, y: e.clientY }); + } + } + + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + }, [canMove]); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + +Try adding `console.log('Resubscribing')` inside the Effect body and notice that now it only resubscribes when you toggle the checkbox (`canMove` changes) or edit the code. This makes it better than the previous approach that always resubscribed. + +You'll learn a more general approach to this type of problem in [Separating Events from Effects.](/learn/separating-events-from-effects) + +</Solution> + +#### Fix a connection switch {/*fix-a-connection-switch*/} + +In this example, the chat service in `chat.js` exposes two different APIs: `createEncryptedConnection` and `createUnencryptedConnection`. The root `App` component lets the user choose whether to use encryption or not, and then passes down the corresponding API method to the child `ChatRoom` component as the `createConnection` prop. + +Notice that initially, the console logs say the connection is not encrypted. Try toggling the checkbox on: nothing will happen. However, if you change the selected room after that, then the chat will reconnect *and* enable encryption (as you'll see from the console messages). This is a bug. Fix the bug so that toggling the checkbox *also* causes the chat to reconnect. + +<Hint> + +Suppressing the linter is always suspicious. Could this be a bug? + +</Hint> + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; +import { + createEncryptedConnection, + createUnencryptedConnection, +} from './chat.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isEncrypted, setIsEncrypted] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isEncrypted} + onChange={e => setIsEncrypted(e.target.checked)} + /> + Enable encryption + </label> + <hr /> + <ChatRoom + roomId={roomId} + createConnection={isEncrypted ? + createEncryptedConnection : + createUnencryptedConnection + } + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; + +export default function ChatRoom({ roomId, createConnection }) { + useEffect(() => { + const connection = createConnection(roomId); + connection.connect(); + return () => connection.disconnect(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +export function createEncryptedConnection(roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ 🔐 Connecting to "' + roomId + '... (encrypted)'); + }, + disconnect() { + console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); + } + }; +} + +export function createUnencryptedConnection(roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '... (unencrypted)'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); + } + }; +} +``` + +```css +label { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +<Solution> + +If you remove the linter suppression, you will see a lint error. The problem is that `createConnection` is a prop, so it's a reactive value. It can change over time! (And indeed, it should--when the user ticks the checkbox, the parent component passes a different value of the `createConnection` prop.) This is why it should be a dependency. Include it in the list to fix the bug: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; +import { + createEncryptedConnection, + createUnencryptedConnection, +} from './chat.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isEncrypted, setIsEncrypted] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isEncrypted} + onChange={e => setIsEncrypted(e.target.checked)} + /> + Enable encryption + </label> + <hr /> + <ChatRoom + roomId={roomId} + createConnection={isEncrypted ? + createEncryptedConnection : + createUnencryptedConnection + } + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; + +export default function ChatRoom({ roomId, createConnection }) { + useEffect(() => { + const connection = createConnection(roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, createConnection]); + + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +export function createEncryptedConnection(roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ 🔐 Connecting to "' + roomId + '... (encrypted)'); + }, + disconnect() { + console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); + } + }; +} + +export function createUnencryptedConnection(roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '... (unencrypted)'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); + } + }; +} +``` + +```css +label { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +It is correct that `createConnection` is a dependency. However, this code is a bit fragile because someone could edit the `App` component to pass an inline function as the value of this prop. In that case, its value would be different every time the `App` component re-renders, so the Effect might re-synchronize too often. To avoid this, you can pass `isEncrypted` down instead: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isEncrypted, setIsEncrypted] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isEncrypted} + onChange={e => setIsEncrypted(e.target.checked)} + /> + Enable encryption + </label> + <hr /> + <ChatRoom + roomId={roomId} + isEncrypted={isEncrypted} + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; +import { + createEncryptedConnection, + createUnencryptedConnection, +} from './chat.js'; + +export default function ChatRoom({ roomId, isEncrypted }) { + useEffect(() => { + const createConnection = isEncrypted ? + createEncryptedConnection : + createUnencryptedConnection; + const connection = createConnection(roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, isEncrypted]); + + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +export function createEncryptedConnection(roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ 🔐 Connecting to "' + roomId + '... (encrypted)'); + }, + disconnect() { + console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); + } + }; +} + +export function createUnencryptedConnection(roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '... (unencrypted)'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); + } + }; +} +``` + +```css +label { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +In this version, the `App` component passes a boolean prop instead of a function. Inside the Effect, you decide which function to use. Since both `createEncryptedConnection` and `createUnencryptedConnection` are declared outside the component, they aren't reactive, and don't need to be dependencies. You'll learn more about this in [Removing Effect Dependencies.](/learn/removing-effect-dependencies) + +</Solution> + +#### Populate a chain of select boxes {/*populate-a-chain-of-select-boxes*/} + +In this example, there are two select boxes. One select box lets the user pick a planet. Another select box lets the user pick a place *on that planet.* The second box doesn't work yet. Your task is to make it show the places on the chosen planet. + +Look at how the first select box works. It populates the `planetList` state with the result from the `"/planets"` API call. The currently selected planet's ID is kept in the `planetId` state variable. You need to find where to add some additional code so that the `placeList` state variable is populated with the result of the `"/planets/" + planetId + "/places"` API call. + +If you implement this right, selecting a planet should populate the place list. Changing a planet should change the place list. + +<Hint> + +If you have two independent synchronization processes, you need to write two separate Effects. + +</Hint> + +<Sandpack> + +```js App.js +import { useState, useEffect } from 'react'; +import { fetchData } from './api.js'; + +export default function Page() { + const [planetList, setPlanetList] = useState([]) + const [planetId, setPlanetId] = useState(''); + + const [placeList, setPlaceList] = useState([]); + const [placeId, setPlaceId] = useState(''); + + useEffect(() => { + let ignore = false; + fetchData('/planets').then(result => { + if (!ignore) { + console.log('Fetched a list of planets.'); + setPlanetList(result); + setPlanetId(result[0].id); // Select the first planet + } + }); + return () => { + ignore = true; + } + }, []); + + return ( + <> + <label> + Pick a planet:{' '} + <select value={planetId} onChange={e => { + setPlanetId(e.target.value); + }}> + {planetList.map(planet => + <option key={planet.id} value={planet.id}>{planet.name}</option> + )} + </select> + </label> + <label> + Pick a place:{' '} + <select value={placeId} onChange={e => { + setPlaceId(e.target.value); + }}> + {placeList.map(place => + <option key={place.id} value={place.id}>{place.name}</option> + )} + </select> + </label> + <hr /> + <p>You are going to: {placeId || '???'} on {planetId || '???'} </p> + </> + ); +} +``` + +```js api.js hidden +export function fetchData(url) { + if (url === '/planets') { + return fetchPlanets(); + } else if (url.startsWith('/planets/')) { + const match = url.match(/^\/planets\/([\w-]+)\/places(\/)?$/); + if (!match || !match[1] || !match[1].length) { + throw Error('Expected URL like "/planets/earth/places". Received: "' + url + '".'); + } + return fetchPlaces(match[1]); + } else throw Error('Expected URL like "/planets" or "/planets/earth/places". Received: "' + url + '".'); +} + +async function fetchPlanets() { + return new Promise(resolve => { + setTimeout(() => { + resolve([{ + id: 'earth', + name: 'Earth' + }, { + id: 'venus', + name: 'Venus' + }, { + id: 'mars', + name: 'Mars' + }]); + }, 1000); + }); +} + +async function fetchPlaces(planetId) { + if (typeof planetId !== 'string') { + throw Error( + 'fetchPlaces(planetId) expects a string argument. ' + + 'Instead received: ' + planetId + '.' + ); + } + return new Promise(resolve => { + setTimeout(() => { + if (planetId === 'earth') { + resolve([{ + id: 'laos', + name: 'Laos' + }, { + id: 'spain', + name: 'Spain' + }, { + id: 'vietnam', + name: 'Vietnam' + }]); + } else if (planetId === 'venus') { + resolve([{ + id: 'aurelia', + name: 'Aurelia' + }, { + id: 'diana-chasma', + name: 'Diana Chasma' + }, { + id: 'kumsong-vallis', + name: 'Kŭmsŏng Vallis' + }]); + } else if (planetId === 'mars') { + resolve([{ + id: 'aluminum-city', + name: 'Aluminum City' + }, { + id: 'new-new-york', + name: 'New New York' + }, { + id: 'vishniac', + name: 'Vishniac' + }]); + } else throw Error('Uknown planet ID: ' + planetId); + }, 1000); + }); +} +``` + +```css +label { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +<Solution> + +There are two independent synchronization processes: + +- The first select box is synchronized to the remote list of planets. +- The second select box is synchronized to the remote list of places for the current `planetId`. + +This is why it makes sense to describe them as two separate Effects. Here's an example of how you could do this: + +<Sandpack> + +```js App.js +import { useState, useEffect } from 'react'; +import { fetchData } from './api.js'; + +export default function Page() { + const [planetList, setPlanetList] = useState([]) + const [planetId, setPlanetId] = useState(''); + + const [placeList, setPlaceList] = useState([]); + const [placeId, setPlaceId] = useState(''); + + useEffect(() => { + let ignore = false; + fetchData('/planets').then(result => { + if (!ignore) { + console.log('Fetched a list of planets.'); + setPlanetList(result); + setPlanetId(result[0].id); // Select the first planet + } + }); + return () => { + ignore = true; + } + }, []); + + useEffect(() => { + if (planetId === '') { + // Nothing is selected in the first box yet + return; + } + + let ignore = false; + fetchData('/planets/' + planetId + '/places').then(result => { + if (!ignore) { + console.log('Fetched a list of places on "' + planetId + '".'); + setPlaceList(result); + setPlaceId(result[0].id); // Select the first place + } + }); + return () => { + ignore = true; + } + }, [planetId]); + + return ( + <> + <label> + Pick a planet:{' '} + <select value={planetId} onChange={e => { + setPlanetId(e.target.value); + }}> + {planetList.map(planet => + <option key={planet.id} value={planet.id}>{planet.name}</option> + )} + </select> + </label> + <label> + Pick a place:{' '} + <select value={placeId} onChange={e => { + setPlaceId(e.target.value); + }}> + {placeList.map(place => + <option key={place.id} value={place.id}>{place.name}</option> + )} + </select> + </label> + <hr /> + <p>You are going to: {placeId || '???'} on {planetId || '???'} </p> + </> + ); +} +``` + +```js api.js hidden +export function fetchData(url) { + if (url === '/planets') { + return fetchPlanets(); + } else if (url.startsWith('/planets/')) { + const match = url.match(/^\/planets\/([\w-]+)\/places(\/)?$/); + if (!match || !match[1] || !match[1].length) { + throw Error('Expected URL like "/planets/earth/places". Received: "' + url + '".'); + } + return fetchPlaces(match[1]); + } else throw Error('Expected URL like "/planets" or "/planets/earth/places". Received: "' + url + '".'); +} + +async function fetchPlanets() { + return new Promise(resolve => { + setTimeout(() => { + resolve([{ + id: 'earth', + name: 'Earth' + }, { + id: 'venus', + name: 'Venus' + }, { + id: 'mars', + name: 'Mars' + }]); + }, 1000); + }); +} + +async function fetchPlaces(planetId) { + if (typeof planetId !== 'string') { + throw Error( + 'fetchPlaces(planetId) expects a string argument. ' + + 'Instead received: ' + planetId + '.' + ); + } + return new Promise(resolve => { + setTimeout(() => { + if (planetId === 'earth') { + resolve([{ + id: 'laos', + name: 'Laos' + }, { + id: 'spain', + name: 'Spain' + }, { + id: 'vietnam', + name: 'Vietnam' + }]); + } else if (planetId === 'venus') { + resolve([{ + id: 'aurelia', + name: 'Aurelia' + }, { + id: 'diana-chasma', + name: 'Diana Chasma' + }, { + id: 'kumsong-vallis', + name: 'Kŭmsŏng Vallis' + }]); + } else if (planetId === 'mars') { + resolve([{ + id: 'aluminum-city', + name: 'Aluminum City' + }, { + id: 'new-new-york', + name: 'New New York' + }, { + id: 'vishniac', + name: 'Vishniac' + }]); + } else throw Error('Uknown planet ID: ' + planetId); + }, 1000); + }); +} +``` + +```css +label { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +This code is a bit repetitive. However, that's not a good reason to combine it into a single Effect! If you did this, you'd have to combine both Effect's dependencies into one list, and then changing the planet would refetch the list of all planets. Effects are not a tool for code reuse. + +Instead, to reduce repetition, you can extract some logic into a custom Hook like `useSelectOptions` below: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { useSelectOptions } from './useSelectOptions.js'; + +export default function Page() { + const [ + planetList, + planetId, + setPlanetId + ] = useSelectOptions('/planets'); + + const [ + placeList, + placeId, + setPlaceId + ] = useSelectOptions(planetId ? `/planets/${planetId}/places` : null); + + return ( + <> + <label> + Pick a planet:{' '} + <select value={planetId} onChange={e => { + setPlanetId(e.target.value); + }}> + {planetList?.map(planet => + <option key={planet.id} value={planet.id}>{planet.name}</option> + )} + </select> + </label> + <label> + Pick a place:{' '} + <select value={placeId} onChange={e => { + setPlaceId(e.target.value); + }}> + {placeList?.map(place => + <option key={place.id} value={place.id}>{place.name}</option> + )} + </select> + </label> + <hr /> + <p>You are going to: {placeId || '...'} on {planetId || '...'} </p> + </> + ); +} +``` + +```js useSelectOptions.js +import { useState, useEffect } from 'react'; +import { fetchData } from './api.js'; + +export function useSelectOptions(url) { + const [list, setList] = useState(null); + const [selectedId, setSelectedId] = useState(''); + useEffect(() => { + if (url === null) { + return; + } + + let ignore = false; + fetchData(url).then(result => { + if (!ignore) { + setList(result); + setSelectedId(result[0].id); + } + }); + return () => { + ignore = true; + } + }, [url]); + return [list, selectedId, setSelectedId]; +} +``` + +```js api.js hidden +export function fetchData(url) { + if (url === '/planets') { + return fetchPlanets(); + } else if (url.startsWith('/planets/')) { + const match = url.match(/^\/planets\/([\w-]+)\/places(\/)?$/); + if (!match || !match[1] || !match[1].length) { + throw Error('Expected URL like "/planets/earth/places". Received: "' + url + '".'); + } + return fetchPlaces(match[1]); + } else throw Error('Expected URL like "/planets" or "/planets/earth/places". Received: "' + url + '".'); +} + +async function fetchPlanets() { + return new Promise(resolve => { + setTimeout(() => { + resolve([{ + id: 'earth', + name: 'Earth' + }, { + id: 'venus', + name: 'Venus' + }, { + id: 'mars', + name: 'Mars' + }]); + }, 1000); + }); +} + +async function fetchPlaces(planetId) { + if (typeof planetId !== 'string') { + throw Error( + 'fetchPlaces(planetId) expects a string argument. ' + + 'Instead received: ' + planetId + '.' + ); + } + return new Promise(resolve => { + setTimeout(() => { + if (planetId === 'earth') { + resolve([{ + id: 'laos', + name: 'Laos' + }, { + id: 'spain', + name: 'Spain' + }, { + id: 'vietnam', + name: 'Vietnam' + }]); + } else if (planetId === 'venus') { + resolve([{ + id: 'aurelia', + name: 'Aurelia' + }, { + id: 'diana-chasma', + name: 'Diana Chasma' + }, { + id: 'kumsong-vallis', + name: 'Kŭmsŏng Vallis' + }]); + } else if (planetId === 'mars') { + resolve([{ + id: 'aluminum-city', + name: 'Aluminum City' + }, { + id: 'new-new-york', + name: 'New New York' + }, { + id: 'vishniac', + name: 'Vishniac' + }]); + } else throw Error('Uknown planet ID: ' + planetId); + }, 1000); + }); +} +``` + +```css +label { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +Check the `useSelectOptions.js` tab in the sandbox to see how it works. Ideally, most Effects in your application should eventually be replaced by custom Hooks, whether written by you or by the community. Custom Hooks hide the synchronization logic, so the calling component doesn't know about the Effect. As you keep working on your app, you'll develop a palette of Hooks to choose from, and eventually you won't need to write Effects in your components very often. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/managing-state.md b/beta/src/content/learn/managing-state.md new file mode 100644 index 000000000..4df058106 --- /dev/null +++ b/beta/src/content/learn/managing-state.md @@ -0,0 +1,1017 @@ +--- +title: Managing State +--- + +<Intro> + +As your application grows, it helps to be more intentional about how your state is organized and how the data flows between your components. Redundant or duplicate state is a common source of bugs. In this chapter, you'll learn how to structure your state well, how to keep your state update logic maintainable, and how to share state between distant components. + +</Intro> + +<YouWillLearn isChapter={true}> + +* [How to think about UI changes as state changes](/learn/reacting-to-input-with-state) +* [How to structure state well](/learn/choosing-the-state-structure) +* [How to "lift state up" to share it between components](/learn/sharing-state-between-components) +* [How to control whether the state gets preserved or reset](/learn/preserving-and-resetting-state) +* [How to consolidate complex state logic in a function](/learn/extracting-state-logic-into-a-reducer) +* [How to pass information without "prop drilling"](/learn/passing-data-deeply-with-context) +* [How to scale state management as your app grows](/learn/scaling-up-with-reducer-and-context) + +</YouWillLearn> + +## Reacting to input with state {/*reacting-to-input-with-state*/} + +With React, you won't modify the UI from code directly. For example, you won't write commands like "disable the button", "enable the button", "show the success message", etc. Instead, you will describe the UI you want to see for the different visual states of your component ("initial state", "typing state", "success state"), and then trigger the state changes in response to user input. This is similar to how designers think about UI. + +Here is a quiz form built using React. Note how it uses the `status` state variable to determine whether to enable or disable the submit button, and whether to show the success message instead. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [answer, setAnswer] = useState(''); + const [error, setError] = useState(null); + const [status, setStatus] = useState('typing'); + + if (status === 'success') { + return <h1>That's right!</h1> + } + + async function handleSubmit(e) { + e.preventDefault(); + setStatus('submitting'); + try { + await submitForm(answer); + setStatus('success'); + } catch (err) { + setStatus('typing'); + setError(err); + } + } + + function handleTextareaChange(e) { + setAnswer(e.target.value); + } + + return ( + <> + <h2>City quiz</h2> + <p> + In which city is there a billboard that turns air into drinkable water? + </p> + <form onSubmit={handleSubmit}> + <textarea + value={answer} + onChange={handleTextareaChange} + disabled={status === 'submitting'} + /> + <br /> + <button disabled={ + answer.length === 0 || + status === 'submitting' + }> + Submit + </button> + {error !== null && + <p className="Error"> + {error.message} + </p> + } + </form> + </> + ); +} + +function submitForm(answer) { + // Pretend it's hitting the network. + return new Promise((resolve, reject) => { + setTimeout(() => { + let shouldError = answer.toLowerCase() !== 'lima' + if (shouldError) { + reject(new Error('Good guess but a wrong answer. Try again!')); + } else { + resolve(); + } + }, 1500); + }); +} +``` + +```css +.Error { color: red; } +``` + +</Sandpack> + +<LearnMore path="/learn/reacting-to-input-with-state"> + +Read **[Reacting to Input with State](/learn/reacting-to-input-with-state)** to learn how to approach interactions with a state-driven mindset. + +</LearnMore> + +## Choosing the state structure {/*choosing-the-state-structure*/} + +Structuring state well can make a difference between a component that is pleasant to modify and debug, and one that is a constant source of bugs. The most important principle is that state shouldn't contain redundant or duplicated information. If there's some unnecessary state, it's easy to forget to update it, and introduce bugs! + +For example, this form has a **redundant** `fullName` state variable: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [fullName, setFullName] = useState(''); + + function handleFirstNameChange(e) { + setFirstName(e.target.value); + setFullName(e.target.value + ' ' + lastName); + } + + function handleLastNameChange(e) { + setLastName(e.target.value); + setFullName(firstName + ' ' + e.target.value); + } + + return ( + <> + <h2>Let’s check you in</h2> + <label> + First name:{' '} + <input + value={firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name:{' '} + <input + value={lastName} + onChange={handleLastNameChange} + /> + </label> + <p> + Your ticket will be issued to: <b>{fullName}</b> + </p> + </> + ); +} +``` + +```css +label { display: block; margin-bottom: 5px; } +``` + +</Sandpack> + +You can remove it and simplify the code by calculating `fullName` while the component is rendering: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + + const fullName = firstName + ' ' + lastName; + + function handleFirstNameChange(e) { + setFirstName(e.target.value); + } + + function handleLastNameChange(e) { + setLastName(e.target.value); + } + + return ( + <> + <h2>Let’s check you in</h2> + <label> + First name:{' '} + <input + value={firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name:{' '} + <input + value={lastName} + onChange={handleLastNameChange} + /> + </label> + <p> + Your ticket will be issued to: <b>{fullName}</b> + </p> + </> + ); +} +``` + +```css +label { display: block; margin-bottom: 5px; } +``` + +</Sandpack> + +This might seem like a small change, but many bugs in React apps are fixed this way. + +<LearnMore path="/learn/choosing-the-state-structure"> + +Read **[Choosing the State Structure](/learn/choosing-the-state-structure)** to learn how to design the state shape to avoid bugs. + +</LearnMore> + +## Sharing state between components {/*sharing-state-between-components*/} + +Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as "lifting state up", and it's one of the most common things you will do writing React code. + +In this example, only one panel should be active at a time. To achieve this, instead of keeping the active state inside each individual panel, the parent component holds the state and specifies the props for its children. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Accordion() { + const [activeIndex, setActiveIndex] = useState(0); + return ( + <> + <h2>Almaty, Kazakhstan</h2> + <Panel + title="About" + isActive={activeIndex === 0} + onShow={() => setActiveIndex(0)} + > + With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. + </Panel> + <Panel + title="Etymology" + isActive={activeIndex === 1} + onShow={() => setActiveIndex(1)} + > + The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. + </Panel> + </> + ); +} + +function Panel({ + title, + children, + isActive, + onShow +}) { + return ( + <section className="panel"> + <h3>{title}</h3> + {isActive ? ( + <p>{children}</p> + ) : ( + <button onClick={onShow}> + Show + </button> + )} + </section> + ); +} +``` + +```css +h3, p { margin: 5px 0px; } +.panel { + padding: 10px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +<LearnMore path="/learn/sharing-state-between-components"> + +Read **[Sharing State Between Components](/learn/sharing-state-between-components)** to learn how to lift state up and keep components in sync. + +</LearnMore> + +## Preserving and resetting state {/*preserving-and-resetting-state*/} + +When you re-render a component, React needs to decide which parts of the tree to keep (and update), and which parts to discard or re-create from scratch. In most cases, React's automatic behavior works well enough. By default, React preserves the parts of the tree that "match up" with the previously rendered component tree. + +However, sometimes this is not what you want. For example, in this app, typing a message and then switching the recipient does not reset the input. This can make the user accidentally send a message to the wrong person: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; + +export default function Messenger() { + const [to, setTo] = useState(contacts[0]); + return ( + <div> + <ContactList + contacts={contacts} + selectedContact={to} + onSelect={contact => setTo(contact)} + /> + <Chat contact={to} /> + </div> + ) +} + +const contacts = [ + { name: 'Taylor', email: 'taylor@mail.com' }, + { name: 'Alice', email: 'alice@mail.com' }, + { name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js +export default function ContactList({ + selectedContact, + contacts, + onSelect +}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map(contact => + <li key={contact.email}> + <button onClick={() => { + onSelect(contact); + }}> + {contact.name} + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js Chat.js +import { useState } from 'react'; + +export default function Chat({ contact }) { + const [text, setText] = useState(''); + return ( + <section className="chat"> + <textarea + value={text} + placeholder={'Chat to ' + contact.name} + onChange={e => setText(e.target.value)} + /> + <br /> + <button>Send to {contact.email}</button> + </section> + ); +} +``` + +```css +.chat, .contact-list { + float: left; + margin-bottom: 20px; +} +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +React lets you override the default behavior, and *force* a component to reset its state by passing it a different `key`, like `<Chat key={email} />`. This tells React that if the recipient is different, it should be considered a *different* `Chat` component that needs to be re-created from scratch with the new data (and UI like inputs). Now switching between the recipients always resets the input field--even though you render the same component. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; + +export default function Messenger() { + const [to, setTo] = useState(contacts[0]); + return ( + <div> + <ContactList + contacts={contacts} + selectedContact={to} + onSelect={contact => setTo(contact)} + /> + <Chat key={to.email} contact={to} /> + </div> + ) +} + +const contacts = [ + { name: 'Taylor', email: 'taylor@mail.com' }, + { name: 'Alice', email: 'alice@mail.com' }, + { name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js +export default function ContactList({ + selectedContact, + contacts, + onSelect +}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map(contact => + <li key={contact.email}> + <button onClick={() => { + onSelect(contact); + }}> + {contact.name} + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js Chat.js +import { useState } from 'react'; + +export default function Chat({ contact }) { + const [text, setText] = useState(''); + return ( + <section className="chat"> + <textarea + value={text} + placeholder={'Chat to ' + contact.name} + onChange={e => setText(e.target.value)} + /> + <br /> + <button>Send to {contact.email}</button> + </section> + ); +} +``` + +```css +.chat, .contact-list { + float: left; + margin-bottom: 20px; +} +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +<LearnMore path="/learn/preserving-and-resetting-state"> + +Read **[Preserving and Resetting State](/learn/preserving-and-resetting-state)** to learn the lifetime of state and how to control it. + +</LearnMore> + +## Extracting state logic into a reducer {/*extracting-state-logic-into-a-reducer*/} + +Components with many state updates spread across many event handlers can get overwhelming. For these cases, you can consolidate all the state update logic outside your component in a single function, called "reducer". Your event handlers become concise because they only specify the user "actions". At the bottom of the file, the reducer function specifies how the state should update in response to each action! + +<Sandpack> + +```js App.js +import { useReducer } from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId + }); + } + + return ( + <> + <h1>Prague itinerary</h1> + <AddTask + onAddTask={handleAddTask} + /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +let nextId = 3; +const initialTasks = [ + { id: 0, text: 'Visit Kafka Museum', done: true }, + { id: 1, text: 'Watch a puppet show', done: false }, + { id: 2, text: 'Lennon Wall pic', done: false } +]; +``` + +```js AddTask.js hidden +import { useState } from 'react'; + +export default function AddTask({ onAddTask }) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + onAddTask(text); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js hidden +import { useState } from 'react'; + +export default function TaskList({ + tasks, + onChangeTask, + onDeleteTask +}) { + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task + task={task} + onChange={onChangeTask} + onDelete={onDeleteTask} + /> + </li> + ))} + </ul> + ); +} + +function Task({ task, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + onChange({ + ...task, + text: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + onChange({ + ...task, + done: e.target.checked + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +<LearnMore path="/learn/extracting-state-logic-into-a-reducer"> + +Read **[Extracting State Logic into a Reducer](/learn/extracting-state-logic-into-a-reducer)** to learn how to consolidate logic in the reducer function. + +</LearnMore> + +## Passing data deeply with context {/*passing-data-deeply-with-context*/} + +Usually, you will pass information from a parent component to a child component via props. But passing props can become inconvenient if you need to pass some prop through many components, or if many components need the same information. Context lets the parent component make some information available to any component in the tree below it—no matter how deep it is—without passing it explicitly through props. + +Here, the `Heading` component determines its heading level by "asking" the closest `Section` for its level. Each `Section` tracks its own level by asking the parent `Section` and adding one to it. Every `Section` provides information to all components below it without passing props--it does that through context. + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section> + <Heading>Title</Heading> + <Section> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Section> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Section> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + </Section> + </Section> + </Section> + </Section> + ); +} +``` + +```js Section.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children }) { + const level = useContext(LevelContext); + return ( + <section className="section"> + <LevelContext.Provider value={level + 1}> + {children} + </LevelContext.Provider> + </section> + ); +} +``` + +```js Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 0: + throw Error('Heading must be inside a Section!'); + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(0); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +<LearnMore path="/learn/passing-data-deeply-with-context"> + +Read **[Passing Data Deeply with Context](/learn/passing-data-deeply-with-context)** to learn about using context as an alternative to passing props. + +</LearnMore> + +## Scaling up with reducer and context {/*scaling-up-with-reducer-and-context*/} + +Reducers let you consolidate a component’s state update logic. Context lets you pass information deep down to other components. You can combine reducers and context together to manage state of a complex screen. + +With this approach, a parent component with complex state manages it with a reducer. Other components anywhere deep in the tree can read its state via context. They can also dispatch actions to update that state. + +<Sandpack> + +```js App.js +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; +import { TasksProvider } from './TasksContext.js'; + +export default function TaskApp() { + return ( + <TasksProvider> + <h1>Day off in Kyoto</h1> + <AddTask /> + <TaskList /> + </TasksProvider> + ); +} +``` + +```js TasksContext.js +import { createContext, useContext, useReducer } from 'react'; + +const TasksContext = createContext(null); +const TasksDispatchContext = createContext(null); + +export function TasksProvider({ children }) { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider + value={dispatch} + > + {children} + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} + +export function useTasks() { + return useContext(TasksContext); +} + +export function useTasksDispatch() { + return useContext(TasksDispatchContext); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js AddTask.js +import { useState, useContext } from 'react'; +import { useTasksDispatch } from './TasksContext.js'; + +export default function AddTask({ onAddTask }) { + const [text, setText] = useState(''); + const dispatch = useTasksDispatch(); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + }}>Add</button> + </> + ); +} + +let nextId = 3; +``` + +```js TaskList.js +import { useState, useContext } from 'react'; +import { useTasks, useTasksDispatch } from './TasksContext.js'; + +export default function TaskList() { + const tasks = useTasks(); + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task task={task} /> + </li> + ))} + </ul> + ); +} + +function Task({ task }) { + const [isEditing, setIsEditing] = useState(false); + const dispatch = useTasksDispatch(); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + text: e.target.value + } + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + done: e.target.checked + } + }); + }} + /> + {taskContent} + <button onClick={() => { + dispatch({ + type: 'deleted', + id: task.id + }); + }}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +<LearnMore path="/learn/scaling-up-with-reducer-and-context"> + +Read **[Scaling Up with Reducer and Context](/learn/scaling-up-with-reducer-and-context)** to learn how state management scales in a growing app. + +</LearnMore> + +## What's next? {/*whats-next*/} + +Head over to [Reacting to Input with State](/learn/reacting-to-input-with-state) to start reading this chapter page by page! + +Or, if you're already familiar with these topics, why not read about [Escape Hatches](/learn/escape-hatches)? diff --git a/beta/src/content/learn/manipulating-the-dom-with-refs.md b/beta/src/content/learn/manipulating-the-dom-with-refs.md new file mode 100644 index 000000000..2adee47ac --- /dev/null +++ b/beta/src/content/learn/manipulating-the-dom-with-refs.md @@ -0,0 +1,1202 @@ +--- +title: 'Manipulating the DOM with Refs' +--- + +<Intro> + +React automatically updates the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) to match your render output, so your components won't often need to manipulate it. However, sometimes you might need access to the DOM elements managed by React--for example, to focus a node, scroll to it, or measure its size and position. There is no built-in way to do those things in React, so you will need a *ref* to the DOM node. + +</Intro> + +<YouWillLearn> + +- How to access a DOM node managed by React with the `ref` attribute +- How the `ref` JSX attribute relates to the `useRef` Hook +- How to access another component's DOM node +- In which cases it's safe to modify the DOM managed by React + +</YouWillLearn> + +## Getting a ref to the node {/*getting-a-ref-to-the-node*/} + +To access a DOM node managed by React, first, import the `useRef` Hook: + +```js +import { useRef } from 'react'; +``` + +Then, use it to declare a ref inside your component: + +```js +const myRef = useRef(null); +``` + +Finally, pass it to the DOM node as the `ref` attribute: + +```js +<div ref={myRef}> +``` + +The `useRef` Hook returns an object with a single property called `current`. Initially, `myRef.current` will be `null`. When React creates a DOM node for this `<div>`, React will put a reference to this node into `myRef.current`. You can then access this DOM node from your [event handlers](/learn/responding-to-events) and use the built-in [browser APIs](https://developer.mozilla.org/docs/Web/API/Element) defined on it. + +```js +// You can use any browser APIs, for example: +myRef.current.scrollIntoView(); +``` + +### Example: Focusing a text input {/*example-focusing-a-text-input*/} + +In this example, clicking the button will focus the input: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <input ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +To implement this: + +1. Declare `inputRef` with the `useRef` Hook. +2. Pass it as `<input ref={inputRef}>`. This tells React to **put this `<input>`'s DOM node into `inputRef.current`.** +3. In the `handleClick` function, read the input DOM node from `inputRef.current` and call [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on it with `inputRef.current.focus()`. +4. Pass the `handleClick` event handler to `<button>` with `onClick`. + +While DOM manipulation is the most common use case for refs, the `useRef` Hook can be used for storing other things outside React, like timer IDs. Similarly to state, refs remain between renders. Refs are like state variables that don't trigger re-renders when you set them. For an introduction to refs, see [Referencing Values with Refs.](/learn/referencing-values-with-refs) + +### Example: Scrolling to an element {/*example-scrolling-to-an-element*/} + +You can have more than a single ref in a component. In this example, there is a carousel of three images. Each button centers an image by calling the browser [`scrollIntoView()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) method the corresponding DOM node: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function CatFriends() { + const firstCatRef = useRef(null); + const secondCatRef = useRef(null); + const thirdCatRef = useRef(null); + + function handleScrollToFirstCat() { + firstCatRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }); + } + + function handleScrollToSecondCat() { + secondCatRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }); + } + + function handleScrollToThirdCat() { + thirdCatRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }); + } + + return ( + <> + <nav> + <button onClick={handleScrollToFirstCat}> + Tom + </button> + <button onClick={handleScrollToSecondCat}> + Maru + </button> + <button onClick={handleScrollToThirdCat}> + Jellylorum + </button> + </nav> + <div> + <ul> + <li> + <img + src="https://placekitten.com/g/200/200" + alt="Tom" + ref={firstCatRef} + /> + </li> + <li> + <img + src="https://placekitten.com/g/300/200" + alt="Maru" + ref={secondCatRef} + /> + </li> + <li> + <img + src="https://placekitten.com/g/250/200" + alt="Jellylorum" + ref={thirdCatRef} + /> + </li> + </ul> + </div> + </> + ); +} +``` + +```css +div { + width: 100%; + overflow: hidden; +} + +nav { + text-align: center; +} + +button { + margin: .25rem; +} + +ul, +li { + list-style: none; + white-space: nowrap; +} + +li { + display: inline; + padding: 0.5rem; +} +``` + +</Sandpack> + +<DeepDive> + +#### How to manage a list of refs using a ref callback {/*how-to-manage-a-list-of-refs-using-a-ref-callback*/} + +In the above examples, there is a predefined number of refs. However, sometimes you might need a ref to each item in the list, and you don't know how many you will have. Something like this **wouldn't work**: + +```js +<ul> + {items.map((item) => { + // Doesn't work! + const ref = useRef(null); + return <li ref={ref} />; + })} +</ul> +``` + +This is because **Hooks must only be called at the top-level of your component.** You can't call `useRef` in a loop, in a condition, or inside a `map()` call. + +One possible way around this is to get a single ref to their parent element, and then use DOM manipulation methods like [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) to "find" the individual child nodes from it. However, this is brittle and can break if your DOM structure changes. + +Another solution is to **pass a function to the `ref` attribute.** This is called a [`ref` callback.](/reference/react-dom/components/common#ref-callback) React will call your ref callback with the DOM node when it's time to set the ref, and with `null` when it's time to clear it. This lets you maintain your own array or a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), and access any ref by its index or some kind of ID. + +This example shows how you can use this approach to scroll to an arbitrary node in a long list: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function CatFriends() { + const itemsRef = useRef(null); + + function scrollToId(itemId) { + const map = getMap(); + const node = map.get(itemId); + node.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }); + } + + function getMap() { + if (!itemsRef.current) { + // Initialize the Map on first usage. + itemsRef.current = new Map(); + } + return itemsRef.current; + } + + return ( + <> + <nav> + <button onClick={() => scrollToId(0)}> + Tom + </button> + <button onClick={() => scrollToId(5)}> + Maru + </button> + <button onClick={() => scrollToId(9)}> + Jellylorum + </button> + </nav> + <div> + <ul> + {catList.map(cat => ( + <li + key={cat.id} + ref={(node) => { + const map = getMap(); + if (node) { + map.set(cat.id, node); + } else { + map.delete(cat.id); + } + }} + > + <img + src={cat.imageUrl} + alt={'Cat #' + cat.id} + /> + </li> + ))} + </ul> + </div> + </> + ); +} + +const catList = []; +for (let i = 0; i < 10; i++) { + catList.push({ + id: i, + imageUrl: 'https://placekitten.com/250/200?image=' + i + }); +} + +``` + +```css +div { + width: 100%; + overflow: hidden; +} + +nav { + text-align: center; +} + +button { + margin: .25rem; +} + +ul, +li { + list-style: none; + white-space: nowrap; +} + +li { + display: inline; + padding: 0.5rem; +} +``` + +</Sandpack> + +In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a [Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) from item ID to a DOM node. ([Refs can hold any values!](/learn/referencing-values-with-refs)) The [`ref` callback](/reference/react-dom/components/common#ref-callback) on every list item takes care to update the Map: + +```js +<li + key={cat.id} + ref={node => { + const map = getMap(); + if (node) { + // Add to the Map + map.set(cat.id, node); + } else { + // Remove from the Map + map.delete(cat.id); + } + }} +> +``` + +This lets you read individual DOM nodes from the Map later. + +</DeepDive> + +## Accessing another component's DOM nodes {/*accessing-another-components-dom-nodes*/} + +When you put a ref on a built-in component that outputs a browser element like `<input />`, React will set that ref's `current` property to the corresponding DOM node (such as the actual `<input />` in the browser). + +However, if you try to put a ref on **your own** component, like `<MyInput />`, by default you will get `null`. Here is an example demonstrating it. Notice how clicking the button **does not** focus the input: + +<Sandpack> + +```js +import { useRef } from 'react'; + +function MyInput(props) { + return <input {...props} />; +} + +export default function MyForm() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <MyInput ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +To help you notice the issue, React also prints an error to the console: + +<ConsoleBlock level="error"> + +Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? + +</ConsoleBlock> + +This happens because by default React does not let a component access the DOM nodes of other components. Not even for its own children! This is intentional. Refs are an escape hatch that should be used sparingly. Manually manipulating _another_ component's DOM nodes makes your code even more fragile. + +Instead, components that _want_ to expose their DOM nodes have to **opt in** to that behavior. A component can specify that it "forwards" its ref to one of its children. Here's how `MyInput` can use the `forwardRef` API: + +```js +const MyInput = forwardRef((props, ref) => { + return <input {...props} ref={ref} />; +}); +``` + +This is how it works: + +1. `<MyInput ref={inputRef} />` tells React to put the corresponding DOM node into `inputRef.current`. However, it's up to the `MyInput` component to opt into that--by default, it doesn't. +2. The `MyInput` component is declared using `forwardRef`. **This opts it into receiving the `inputRef` from above as the second `ref` argument** which is declared after `props`. +3. `MyInput` itself passes the `ref` it received to the `<input>` inside of it. + +Now clicking the button to focus the input works: + +<Sandpack> + +```js +import { forwardRef, useRef } from 'react'; + +const MyInput = forwardRef((props, ref) => { + return <input {...props} ref={ref} />; +}); + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <MyInput ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +In design systems, it is a common pattern for low-level components like buttons, inputs, and so on, to forward their refs to their DOM nodes. On the other hand, high-level components like forms, lists, or page sections usually won't expose their DOM nodes to avoid accidental dependencies on the DOM structure. + +<DeepDive> + +#### Exposing a subset of the API with an imperative handle {/*exposing-a-subset-of-the-api-with-an-imperative-handle*/} + +In the above example, `MyInput` exposes the original DOM input element. This lets the parent component call `focus()` on it. However, this also lets the parent component do something else--for example, change its CSS styles. In uncommon cases, you may want to restrict the exposed functionality. You can do that with `useImperativeHandle`: + +<Sandpack> + +```js +import { + forwardRef, + useRef, + useImperativeHandle +} from 'react'; + +const MyInput = forwardRef((props, ref) => { + const realInputRef = useRef(null); + useImperativeHandle(ref, () => ({ + // Only expose focus and nothing else + focus() { + realInputRef.current.focus(); + }, + })); + return <input {...props} ref={realInputRef} />; +}); + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <MyInput ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +Here, `realInputRef` inside `MyInput` holds the actual input DOM node. However, `useImperativeHandle` instructs React to provide your own special object as the value of a ref to the parent component. So `inputRef.current` inside the `Form` component will only have the `focus` method. In this case, the ref "handle" is not the DOM node, but the custom object you create inside `useImperativeHandle` call. + +</DeepDive> + +## When React attaches the refs {/*when-react-attaches-the-refs*/} + +In React, every update is split in [two phases](/learn/render-and-commit#step-3-react-commits-changes-to-the-dom): + +* During **render,** React calls your components to figure out what should be on the screen. +* During **commit,** React applies changes to the DOM. + +In general, you [don't want](/learn/referencing-values-with-refs#best-practices-for-refs) to access refs during rendering. That goes for refs holding DOM nodes as well. During the first render, the DOM nodes have not yet been created, so `ref.current` will be `null`. And during the rendering of updates, the DOM nodes haven't been updated yet. So it's too early to read them. + +React sets `ref.current` during the commit. Before updating the DOM, React sets the affected `ref.current` values to `null`. After updating the DOM, React immediately sets them to the corresponding DOM nodes. + +**Usually, you will access refs from event handlers.** If you want to do something with a ref, but there is no particular event to do it in, you might need an Effect. We will discuss effects on the next pages. + +<DeepDive> + +#### Flushing state updates synchronously with flushSync {/*flushing-state-updates-synchronously-with-flush-sync*/} + +Consider code like this, which adds a new todo and scrolls the screen down to the last child of the list. Notice how, for some reason, it always scrolls to the todo that was *just before* the last added one: + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function TodoList() { + const listRef = useRef(null); + const [text, setText] = useState(''); + const [todos, setTodos] = useState( + initialTodos + ); + + function handleAdd() { + const newTodo = { id: nextId++, text: text }; + setText(''); + setTodos([ ...todos, newTodo]); + listRef.current.lastChild.scrollIntoView({ + behavior: 'smooth', + block: 'nearest' + }); + } + + return ( + <> + <button onClick={handleAdd}> + Add + </button> + <input + value={text} + onChange={e => setText(e.target.value)} + /> + <ul ref={listRef}> + {todos.map(todo => ( + <li key={todo.id}>{todo.text}</li> + ))} + </ul> + </> + ); +} + +let nextId = 0; +let initialTodos = []; +for (let i = 0; i < 20; i++) { + initialTodos.push({ + id: nextId++, + text: 'Todo #' + (i + 1) + }); +} +``` + +</Sandpack> + +The issue is with these two lines: + +```js +setTodos([ ...todos, newTodo]); +listRef.current.lastChild.scrollIntoView(); +``` + +In React, [state updates are queued.](/learn/queueing-a-series-of-state-updates) Usually, this is what you want. However, here it causes a problem because `setTodos` does not immediately update the DOM. So the time you scroll the list to its last element, the todo has not yet been added. This is why scrolling always "lags behind" by one item. + +To fix this issue, you can force React to update ("flush") the DOM synchronously. To do this, import `flushSync` from `react-dom` and **wrap the state update** into a `flushSync` call: + +```js +flushSync(() => { + setTodos([ ...todos, newTodo]); +}); +listRef.current.lastChild.scrollIntoView(); +``` + +This will instruct React to update the DOM synchronously right after the code wrapped in `flushSync` executes. As a result, the last todo will already be in the DOM by the time you try to scroll to it: + +<Sandpack> + +```js +import { useState, useRef } from 'react'; +import { flushSync } from 'react-dom'; + +export default function TodoList() { + const listRef = useRef(null); + const [text, setText] = useState(''); + const [todos, setTodos] = useState( + initialTodos + ); + + function handleAdd() { + const newTodo = { id: nextId++, text: text }; + flushSync(() => { + setText(''); + setTodos([ ...todos, newTodo]); + }); + listRef.current.lastChild.scrollIntoView({ + behavior: 'smooth', + block: 'nearest' + }); + } + + return ( + <> + <button onClick={handleAdd}> + Add + </button> + <input + value={text} + onChange={e => setText(e.target.value)} + /> + <ul ref={listRef}> + {todos.map(todo => ( + <li key={todo.id}>{todo.text}</li> + ))} + </ul> + </> + ); +} + +let nextId = 0; +let initialTodos = []; +for (let i = 0; i < 20; i++) { + initialTodos.push({ + id: nextId++, + text: 'Todo #' + (i + 1) + }); +} +``` + +</Sandpack> + +</DeepDive> + +## Best practices for DOM manipulation with refs {/*best-practices-for-dom-manipulation-with-refs*/} + +Refs are an escape hatch. You should only use them when you have to "step outside React". Common examples of this include managing focus, scroll position, or calling browser APIs that React does not expose. + +If you stick to non-destructive actions like focusing and scrolling, you shouldn't encounter any problems. However, if you try to **modify** the DOM manually, you can risk conflicting with the changes React is making. + +To illustrate this problem, this example includes a welcome message and two buttons. The first button toggles its presence using [conditional rendering](/learn/conditional-rendering) and [state](/learn/state-a-components-memory), as you would usually do in React. The second button uses the [`remove()` DOM API](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) to forcefully remove it from the DOM outside of React's control. + +Try pressing "Toggle with setState" a few times. The message should disappear and appear again. Then press "Remove from the DOM". This will forcefully remove it. Finally, press "Toggle with setState": + +<Sandpack> + +```js +import {useState, useRef} from 'react'; + +export default function Counter() { + const [show, setShow] = useState(true); + const ref = useRef(null); + + return ( + <div> + <button + onClick={() => { + setShow(!show); + }}> + Toggle with setState + </button> + <button + onClick={() => { + ref.current.remove(); + }}> + Remove from the DOM + </button> + {show && <p ref={ref}>Hello world</p>} + </div> + ); +} +``` + +```css +p, +button { + display: block; + margin: 10px; +} +``` + +</Sandpack> + +After you've manually removed the DOM element, trying to use `setState` to show it again will lead to a crash. This is because you've changed the DOM, and React doesn't know how to continue managing it correctly. + +**Avoid changing DOM nodes managed by React.** Modifying, adding children to, or removing children from elements that are managed by React can lead to inconsistent visual results or crashes like above. + +However, this doesn't mean that you can't do it at all. It requires caution. **You can safely modify parts of the DOM that React has _no reason_ to update.** For example, if some `<div>` is always empty in the JSX, React won't have a reason to touch its children list. Therefore, it is safe to manually add or remove elements there. + +<Recap> + +- Refs are a generic concept, but most often you'll use them to hold DOM elements. +- You instruct React to put a DOM node into `myRef.current` by passing `<div ref={myRef}>`. +- Usually, you will use refs for non-destructive actions like focusing, scrolling, or measuring DOM elements. +- A component doesn't expose its DOM nodes by default. You can opt into exposing a DOM node by using `forwardRef` and passing the second `ref` argument down to a specific node. +- Avoid changing DOM nodes managed by React. +- If you do modify DOM nodes managed by React, modify parts that React has no reason to update. + +</Recap> + + + +<Challenges> + +#### Play and pause the video {/*play-and-pause-the-video*/} + +In this example, the button toggles a state variable to switch between a playing and a paused state. However, in order to actually play or pause the video, toggling state is not enough. You also need to call [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) and [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) on the DOM element for the `<video>`. Add a ref to it, and make the button work. + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function VideoPlayer() { + const [isPlaying, setIsPlaying] = useState(false); + + function handleClick() { + const nextIsPlaying = !isPlaying; + setIsPlaying(nextIsPlaying); + } + + return ( + <> + <button onClick={handleClick}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <video width="250"> + <source + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + type="video/mp4" + /> + </video> + </> + ) +} +``` + +```css +button { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +For an extra challenge, keep the "Play" button in sync with whether the video is playing even if the user right-clicks the video and plays it using the built-in browser media controls. You might want to listen to `onPlay` and `onPause` on the video to do that. + +<Solution> + +Declare a ref and put it on the `<video>` element. Then call `ref.current.play()` and `ref.current.pause()` in the event handler depending on the next state. + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function VideoPlayer() { + const [isPlaying, setIsPlaying] = useState(false); + const ref = useRef(null); + + function handleClick() { + const nextIsPlaying = !isPlaying; + setIsPlaying(nextIsPlaying); + + if (nextIsPlaying) { + ref.current.play(); + } else { + ref.current.pause(); + } + } + + return ( + <> + <button onClick={handleClick}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <video + width="250" + ref={ref} + onPlay={() => setIsPlaying(true)} + onPause={() => setIsPlaying(false)} + > + <source + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + type="video/mp4" + /> + </video> + </> + ) +} +``` + +```css +button { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +In order to handle the built-in browser controls, you can add `onPlay` and `onPause` handlers to the `<video>` element and call `setIsPlaying` from them. This way, if the user plays the video using the browser controls, the state will adjust accordingly. + +</Solution> + +#### Focus the search field {/*focus-the-search-field*/} + +Make it so that clicking the "Search" button puts focus into the field. + +<Sandpack> + +```js +export default function Page() { + return ( + <> + <nav> + <button>Search</button> + </nav> + <input + placeholder="Looking for something?" + /> + </> + ); +} +``` + +```css +button { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +<Solution> + +Add a ref to the input, and call `focus()` on the DOM node to focus it: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Page() { + const inputRef = useRef(null); + return ( + <> + <nav> + <button onClick={() => { + inputRef.current.focus(); + }}> + Search + </button> + </nav> + <input + ref={inputRef} + placeholder="Looking for something?" + /> + </> + ); +} +``` + +```css +button { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +</Solution> + +#### Scrolling an image carousel {/*scrolling-an-image-carousel*/} + +This image carousel has a "Next" button that switches the active image. Make the gallery scroll horizontally to the active image on click. You will want to call [`scrollIntoView()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) on the DOM node of the active image: + +```js +node.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' +}); +``` + +<Hint> + +You don't need to have a ref to every image for this exercise. It should be enough to have a ref to the currently active image, or to the list itself. Use `flushSync` to ensure the DOM is updated *before* you scroll. + +</Hint> + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function CatFriends() { + const [index, setIndex] = useState(0); + return ( + <> + <nav> + <button onClick={() => { + if (index < catList.length - 1) { + setIndex(index + 1); + } else { + setIndex(0); + } + }}> + Next + </button> + </nav> + <div> + <ul> + {catList.map((cat, i) => ( + <li key={cat.id}> + <img + className={ + index === i ? + 'active' : + '' + } + src={cat.imageUrl} + alt={'Cat #' + cat.id} + /> + </li> + ))} + </ul> + </div> + </> + ); +} + +const catList = []; +for (let i = 0; i < 10; i++) { + catList.push({ + id: i, + imageUrl: 'https://placekitten.com/250/200?image=' + i + }); +} + +``` + +```css +div { + width: 100%; + overflow: hidden; +} + +nav { + text-align: center; +} + +button { + margin: .25rem; +} + +ul, +li { + list-style: none; + white-space: nowrap; +} + +li { + display: inline; + padding: 0.5rem; +} + +img { + padding: 10px; + margin: -10px; + transition: background 0.2s linear; +} + +.active { + background: rgba(0, 100, 150, 0.4); +} +``` + +</Sandpack> + +<Solution> + +You can declare a `selectedRef`, and then pass it conditionally only to the current image: + +```js +<li ref={index === i ? selectedRef : null}> +``` + +When `index === i`, meaning that the image is the selected one, the `<li>` will receive the `selectedRef`. React will make sure that `selectedRef.current` always points at the correct DOM node. + +Note that the `flushSync` call is necessary to force React to update the DOM before the scroll. Otherwise, `selectedRef.current` would always point at the previously selected item. + +<Sandpack> + +```js +import { useRef, useState } from 'react'; +import { flushSync } from 'react-dom'; + +export default function CatFriends() { + const selectedRef = useRef(null); + const [index, setIndex] = useState(0); + + return ( + <> + <nav> + <button onClick={() => { + flushSync(() => { + if (index < catList.length - 1) { + setIndex(index + 1); + } else { + setIndex(0); + } + }); + selectedRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }); + }}> + Next + </button> + </nav> + <div> + <ul> + {catList.map((cat, i) => ( + <li + key={cat.id} + ref={index === i ? + selectedRef : + null + } + > + <img + className={ + index === i ? + 'active' + : '' + } + src={cat.imageUrl} + alt={'Cat #' + cat.id} + /> + </li> + ))} + </ul> + </div> + </> + ); +} + +const catList = []; +for (let i = 0; i < 10; i++) { + catList.push({ + id: i, + imageUrl: 'https://placekitten.com/250/200?image=' + i + }); +} + +``` + +```css +div { + width: 100%; + overflow: hidden; +} + +nav { + text-align: center; +} + +button { + margin: .25rem; +} + +ul, +li { + list-style: none; + white-space: nowrap; +} + +li { + display: inline; + padding: 0.5rem; +} + +img { + padding: 10px; + margin: -10px; + transition: background 0.2s linear; +} + +.active { + background: rgba(0, 100, 150, 0.4); +} +``` + +</Sandpack> + +</Solution> + +#### Focus the search field with separate components {/*focus-the-search-field-with-separate-components*/} + +Make it so that clicking the "Search" button puts focus into the field. Note that each component is defined in a separate file and shouldn't be moved out of it. How do you connect them together? + +<Hint> + +You'll need `forwardRef` to opt into exposing a DOM node from your own component like `SearchInput`. + +</Hint> + +<Sandpack> + +```js App.js +import SearchButton from './SearchButton.js'; +import SearchInput from './SearchInput.js'; + +export default function Page() { + return ( + <> + <nav> + <SearchButton /> + </nav> + <SearchInput /> + </> + ); +} +``` + +```js SearchButton.js +export default function SearchButton() { + return ( + <button> + Search + </button> + ); +} +``` + +```js SearchInput.js +export default function SearchInput() { + return ( + <input + placeholder="Looking for something?" + /> + ); +} +``` + +```css +button { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +<Solution> + +You'll need to add an `onClick` prop to the `SearchButton`, and make the `SearchButton` pass it down to the browser `<button>`. You'll also pass a ref down to `<SearchInput>`, which will forward it to the real `<input>` and populate it. Finally, in the click handler, you'll call `focus` on the DOM node stored inside that ref. + +<Sandpack> + +```js App.js +import { useRef } from 'react'; +import SearchButton from './SearchButton.js'; +import SearchInput from './SearchInput.js'; + +export default function Page() { + const inputRef = useRef(null); + return ( + <> + <nav> + <SearchButton onClick={() => { + inputRef.current.focus(); + }} /> + </nav> + <SearchInput ref={inputRef} /> + </> + ); +} +``` + +```js SearchButton.js +export default function SearchButton({ onClick }) { + return ( + <button onClick={onClick}> + Search + </button> + ); +} +``` + +```js SearchInput.js +import { forwardRef } from 'react'; + +export default forwardRef( + function SearchInput(props, ref) { + return ( + <input + ref={ref} + placeholder="Looking for something?" + /> + ); + } +); +``` + +```css +button { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/passing-data-deeply-with-context.md b/beta/src/content/learn/passing-data-deeply-with-context.md new file mode 100644 index 000000000..5a8213d67 --- /dev/null +++ b/beta/src/content/learn/passing-data-deeply-with-context.md @@ -0,0 +1,1160 @@ +--- +title: Passing Data Deeply with Context +--- + +<Intro> + +Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information. *Context* lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props. + +</Intro> + +<YouWillLearn> + +- What "prop drilling" is +- How to replace repetitive prop passing with context +- Common use cases for context +- Common alternatives to context + +</YouWillLearn> + +## The problem with passing props {/*the-problem-with-passing-props*/} + +[Passing props](/learn/passing-props-to-a-component) is a great way to explicitly pipe data through your UI tree to the components that use it. + +But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and [lifting state up](/learn/sharing-state-between-components) that high can lead to a situation sometimes called "prop drilling". + +<DiagramGroup> + +<Diagram name="passing_data_lifting_state" height={160} width={608} captionPosition="top" alt="Diagram with a tree of three components. The parent contains a bubble representing a value highlighted in purple. The value flows down to each of the two children, both highlighted in purple." > + +Lifting state up + +</Diagram> +<Diagram name="passing_data_prop_drilling" height={430} width={608} captionPosition="top" alt="Diagram with a tree of ten nodes, each node with two children or less. The root node contains a bubble representing a value highlighted in purple. The value flows down through the two children, each of which pass the value but do not contain it. The left child passes the value down to two children which are both highlighted purple. The right child of the root passes the value through to one of its two children - the right one, which is highlighted purple. That child passed the value through its single child, which passes it down to both of its two children, which are highlighted purple."> + +Prop drilling + +</Diagram> + +</DiagramGroup> + +Wouldn't it be great if there were a way to "teleport" data to the components in the tree that need it without passing props? With React's context feature, there is! + +## Context: an alternative to passing props {/*context-an-alternative-to-passing-props*/} + +Context lets a parent component provide data to the entire tree below it. There are many uses for context. Here is one example. Consider this `Heading` component that accepts a `level` for its size: + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section> + <Heading level={1}>Title</Heading> + <Heading level={2}>Heading</Heading> + <Heading level={3}>Sub-heading</Heading> + <Heading level={4}>Sub-sub-heading</Heading> + <Heading level={5}>Sub-sub-sub-heading</Heading> + <Heading level={6}>Sub-sub-sub-sub-heading</Heading> + </Section> + ); +} +``` + +```js Section.js +export default function Section({ children }) { + return ( + <section className="section"> + {children} + </section> + ); +} +``` + +```js Heading.js +export default function Heading({ level, children }) { + switch (level) { + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +Let's say you want multiple headings within the same `Section` to always have the same size: + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section> + <Heading level={1}>Title</Heading> + <Section> + <Heading level={2}>Heading</Heading> + <Heading level={2}>Heading</Heading> + <Heading level={2}>Heading</Heading> + <Section> + <Heading level={3}>Sub-heading</Heading> + <Heading level={3}>Sub-heading</Heading> + <Heading level={3}>Sub-heading</Heading> + <Section> + <Heading level={4}>Sub-sub-heading</Heading> + <Heading level={4}>Sub-sub-heading</Heading> + <Heading level={4}>Sub-sub-heading</Heading> + </Section> + </Section> + </Section> + </Section> + ); +} +``` + +```js Section.js +export default function Section({ children }) { + return ( + <section className="section"> + {children} + </section> + ); +} +``` + +```js Heading.js +export default function Heading({ level, children }) { + switch (level) { + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +Currently, you pass the `level` prop to each `<Heading>` separately: + +```js +<Section> + <Heading level={3}>About</Heading> + <Heading level={3}>Photos</Heading> + <Heading level={3}>Videos</Heading> +</Section> +``` + +It would be nice if you could pass the `level` prop to the `<Section>` component instead and remove it from the `<Heading>`. This way you could enforce that all headings in the same section have the same size: + +```js +<Section level={3}> + <Heading>About</Heading> + <Heading>Photos</Heading> + <Heading>Videos</Heading> +</Section> +``` + +But how can the `<Heading>` component know the level of its closest `<Section>`? **That would require some way for a child to "ask" for data from somewhere above in the tree.** + +You can't do it with props alone. This is where context comes into play. You will do it in three steps: + +1. **Create** a context. (You can call it `LevelContext`, since it's for the heading level.) +2. **Use** that context from the component that needs the data. (`Heading` will use `LevelContext`.) +3. **Provide** that context from the component that specifies the data. (`Section` will provide `LevelContext`.) + +Context lets a parent--even a distant one!--provide some data to the entire tree inside of it. + +<DiagramGroup> + +<Diagram name="passing_data_context_close" height={160} width={608} captionPosition="top" alt="Diagram with a tree of three components. The parent contains a bubble representing a value highlighted in orange which projects down to the two children, each highlighted in orange." > + +Using context in close children + +</Diagram> + +<Diagram name="passing_data_context_far" height={430} width={608} captionPosition="top" alt="Diagram with a tree of ten nodes, each node with two children or less. The root parent node contains a bubble representing a value highlighted in orange. The value projects down directly to four leaves and one intermediate component in the tree, which are all highlighted in orange. None of the other intermediate components are highlighted."> + +Using context in distant children + +</Diagram> + +</DiagramGroup> + +### Step 1: Create the context {/*step-1-create-the-context*/} + +First, you need to create the context. You'll need to **export it from a file** so that your components can use it: + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section> + <Heading level={1}>Title</Heading> + <Section> + <Heading level={2}>Heading</Heading> + <Heading level={2}>Heading</Heading> + <Heading level={2}>Heading</Heading> + <Section> + <Heading level={3}>Sub-heading</Heading> + <Heading level={3}>Sub-heading</Heading> + <Heading level={3}>Sub-heading</Heading> + <Section> + <Heading level={4}>Sub-sub-heading</Heading> + <Heading level={4}>Sub-sub-heading</Heading> + <Heading level={4}>Sub-sub-heading</Heading> + </Section> + </Section> + </Section> + </Section> + ); +} +``` + +```js Section.js +export default function Section({ children }) { + return ( + <section className="section"> + {children} + </section> + ); +} +``` + +```js Heading.js +export default function Heading({ level, children }) { + switch (level) { + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js LevelContext.js active +import { createContext } from 'react'; + +export const LevelContext = createContext(1); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +The only argument to `createContext` is the _default_ value. Here, `1` refers to the biggest heading level, but you could pass any kind of value (even an object). You will see the significance of the default value in the next step. + +### Step 2: Use the context {/*step-2-use-the-context*/} + +Import the `useContext` Hook from React and your context: + +```js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; +``` + +Currently, the `Heading` component reads `level` from props: + +```js +export default function Heading({ level, children }) { + // ... +} +``` + +Instead, remove the `level` prop and read the value from the context you just imported, `LevelContext`: + +```js {2} +export default function Heading({ children }) { + const level = useContext(LevelContext); + // ... +} +``` + +`useContext` is a Hook. Just like `useState` and `useReducer`, you can only call a Hook at the top level of a React component. **`useContext` tells React that the `Heading` component wants to read the `LevelContext`.** + +Now that the `Heading` component doesn't have a `level` prop, you don't need to pass the level prop to `Heading` in your JSX like this anymore: + +```js +<Section> + <Heading level={4}>Sub-sub-heading</Heading> + <Heading level={4}>Sub-sub-heading</Heading> + <Heading level={4}>Sub-sub-heading</Heading> +</Section> +``` + +Update the JSX so that it's the `Section` that receives it instead: + +```jsx +<Section level={4}> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> +</Section> +``` + +As a reminder, this is the markup that you were trying to get working: + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section level={1}> + <Heading>Title</Heading> + <Section level={2}> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Section level={3}> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Section level={4}> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + </Section> + </Section> + </Section> + </Section> + ); +} +``` + +```js Section.js +export default function Section({ children }) { + return ( + <section className="section"> + {children} + </section> + ); +} +``` + +```js Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(1); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +Notice this example doesn't quite work, yet! All the headings have the same size because **even though you're *using* the context, you have not *provided* it yet.** React doesn't know where to get it! + +If you don't provide the context, React will use the default value you've specified in the previous step. In this example, you specified `1` as the argument to `createContext`, so `useContext(LevelContext)` returns `1`, setting all those headings to `<h1>`. Let's fix this problem by having each `Section` provide its own context. + +### Step 3: Provide the context {/*step-3-provide-the-context*/} + +The `Section` component currently renders its children: + +```js +export default function Section({ children }) { + return ( + <section className="section"> + {children} + </section> + ); +} +``` + +**Wrap them with a context provider** to provide the `LevelContext` to them: + +```js {1,6,8} +import { LevelContext } from './LevelContext.js'; + +export default function Section({ level, children }) { + return ( + <section className="section"> + <LevelContext.Provider value={level}> + {children} + </LevelContext.Provider> + </section> + ); +} +``` + +This tells React: "if any component inside this `<Section>` asks for `LevelContext`, give them this `level`." The component will use the value of the nearest `<LevelContext.Provider>` in the UI tree above it. + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section level={1}> + <Heading>Title</Heading> + <Section level={2}> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Section level={3}> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Section level={4}> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + </Section> + </Section> + </Section> + </Section> + ); +} +``` + +```js Section.js +import { LevelContext } from './LevelContext.js'; + +export default function Section({ level, children }) { + return ( + <section className="section"> + <LevelContext.Provider value={level}> + {children} + </LevelContext.Provider> + </section> + ); +} +``` + +```js Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(1); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +It's the same result as the original code, but you did not need to pass the `level` prop to each `Heading` component! Instead, it "figures out" its heading level by asking the closest `Section` above: + +1. You pass a `level` prop to the `<Section>`. +2. `Section` wraps its children into `<LevelContext.Provider value={level}>`. +3. `Heading` asks the closest value of `LevelContext` above with `useContext(LevelContext)`. + +## Using and providing context from the same component {/*using-and-providing-context-from-the-same-component*/} + +Currently, you still have to specify each section's `level` manually: + +```js +export default function Page() { + return ( + <Section level={1}> + ... + <Section level={2}> + ... + <Section level={3}> + ... +``` + +Since context lets you read information from a component above, each `Section` could read the `level` from the `Section` above, and pass `level + 1` down automatically. Here is how you could do it: + +```js Section.js {5,8} +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children }) { + const level = useContext(LevelContext); + return ( + <section className="section"> + <LevelContext.Provider value={level + 1}> + {children} + </LevelContext.Provider> + </section> + ); +} +``` + +With this change, you don't need to pass the `level` prop *either* to the `<Section>` or to the `<Heading>`: + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section> + <Heading>Title</Heading> + <Section> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Section> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Section> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + </Section> + </Section> + </Section> + </Section> + ); +} +``` + +```js Section.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children }) { + const level = useContext(LevelContext); + return ( + <section className="section"> + <LevelContext.Provider value={level + 1}> + {children} + </LevelContext.Provider> + </section> + ); +} +``` + +```js Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 0: + throw Error('Heading must be inside a Section!'); + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(0); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +Now both `Heading` and `Section` read the `LevelContext` to figure out how "deep" they are. And the `Section` wraps its children into the `LevelContext` to specify that anything inside of it is at a "deeper" level. + +>This example uses heading levels because they show visually how nested components can override context. But context is useful for many other use cases too. You can use it to pass down any information needed by the entire subtree: the current color theme, the currently logged in user, and so on. + +## Context passes through intermediate components {/*context-passes-through-intermediate-components*/} + +You can insert as many components as you like between the component that provides context and the one that uses it. This includes both built-in components like `<div>` and components you might build yourself. + +In this example, the same `Post` component (with a dashed border) is rendered at two different nesting levels. Notice that the `<Heading>` inside of it gets its level automatically from the closest `<Section>`: + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function ProfilePage() { + return ( + <Section> + <Heading>My Profile</Heading> + <Post + title="Hello traveller!" + body="Read about my adventures." + /> + <AllPosts /> + </Section> + ); +} + +function AllPosts() { + return ( + <Section> + <Heading>Posts</Heading> + <RecentPosts /> + </Section> + ); +} + +function RecentPosts() { + return ( + <Section> + <Heading>Recent Posts</Heading> + <Post + title="Flavors of Lisbon" + body="...those pastéis de nata!" + /> + <Post + title="Buenos Aires in the rhythm of tango" + body="I loved it!" + /> + </Section> + ); +} + +function Post({ title, body }) { + return ( + <Section isFancy={true}> + <Heading> + {title} + </Heading> + <p><i>{body}</i></p> + </Section> + ); +} +``` + +```js Section.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children, isFancy }) { + const level = useContext(LevelContext); + return ( + <section className={ + 'section ' + + (isFancy ? 'fancy' : '') + }> + <LevelContext.Provider value={level + 1}> + {children} + </LevelContext.Provider> + </section> + ); +} +``` + +```js Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 0: + throw Error('Heading must be inside a Section!'); + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(0); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} + +.fancy { + border: 4px dashed pink; +} +``` + +</Sandpack> + +You didn't do anything special for this to work. A `Section` specifies the context for the tree inside it, so you can insert a `<Heading>` anywhere, and it will have the correct size. Try it in the sandbox above! + +**Context lets you write components that "adapt to their surroundings" and display themselves differently depending on _where_ (or, in other words, _in which context_) they are being rendered.** + +How context works might remind you of [CSS property inheritance.](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) In CSS, you can specify `color: blue` for a `<div>`, and any DOM node inside of it, no matter how deep, will inherit that color unless some other DOM node in the middle overrides it with `color: green`. Similarly, in React, the only way to override some context coming from above is to wrap children into a context provider with a different value. + +In CSS, different properties like `color` and `background-color` don't override each other. You can set all `<div>`'s `color` to red without impacting `background-color`. Similarly, **different React contexts don't override each other.** Each context that you make with `createContext()` is completely separate from other ones, and ties together components using and providing *that particular* context. One component may use or provide many different contexts without a problem. + +## Before you use context {/*before-you-use-context*/} + +Context is very tempting to use! However, this also means it's too easy to overuse it. **Just because you need to pass some props several levels deep doesn't mean you should put that information into context.** + +Here's a few alternatives you should consider before using context: + +1. **Start by [passing props.](/learn/passing-props-to-a-component)** If your components are not trivial, it's not unusual to pass a dozen props down through a dozen components. It may feel like a slog, but it makes it very clear which components use which data! The person maintaining your code will be glad you've made the data flow explicit with props. +2. **Extract components and [pass JSX as `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) to them.** If you pass some data through many layers of intermediate components that don't use that data (and only pass it further down), this often means that you forgot to extract some components along the way. For example, maybe you pass data props like `posts` to visual components that don't use them directly, like `<Layout posts={posts} />`. Instead, make `Layout` take `children` as a prop, and render `<Layout><Posts posts={posts} /></Layout>`. This reduces the number of layers between the component specifying the data and the one that needs it. + +If neither of these approaches works well for you, consider context. + +## Use cases for context {/*use-cases-for-context*/} + +* **Theming:** If your app lets the user change its appearance (e.g. dark mode), you can put a context provider at the top of your app, and use that context in components that need to adjust their visual look. +* **Current account:** Many components might need to know the currently logged in user. Putting it in context makes it convenient to read it anywhere in the tree. Some apps also let you operate multiple accounts at the same time (e.g. to leave a comment as a different user). In those cases, it can be convenient to wrap a part of the UI into a nested provider with a different current account value. +* **Routing:** Most routing solutions use context internally to hold the current route. This is how every link "knows" whether it's active or not. If you build your own router, you might want to do it too. +* **Managing state:** As your app grows, you might end up with a lot of state closer to the top of your app. Many distant components below may want to change it. It is common to [use a reducer together with context](/learn/scaling-up-with-reducer-and-context) to manage complex state and pass it down to distant components without too much hassle. + +Context is not limited to static values. If you pass a different value on the next render, React will update all the components reading it below! This is why context is often used in combination with state. + +In general, if some information is needed by distant components in different parts of the tree, it's a good indication that context will help you. + +<Recap> + +* Context lets a component provide some information to the entire tree below it. +* To pass context: + 1. Create and export it with `export const MyContext = createContext(defaultValue)`. + 2. Pass it to the `useContext(MyContext)` Hook to read it in any child component, no matter how deep. + 3. Wrap children into `<MyContext.Provider value={...}>` to provide it from a parent. +* Context passes through any components in the middle. +* Context lets you write components that "adapt to their surroundings". +* Before you use context, try passing props or passing JSX as `children`. + +</Recap> + +<Challenges> + +#### Replace prop drilling with context {/*replace-prop-drilling-with-context*/} + +In this example, toggling the checkbox changes the `imageSize` prop passed to each `<PlaceImage>`. The checkbox state is held in the top-level `App` component, but each `<PlaceImage>` needs to be aware of it. + +Currently, `App` passes `imageSize` to `List`, which passes it to each `Place`, which passes it to the `PlaceImage`. Remove the `imageSize` prop, and instead pass it from the `App` component directly to `PlaceImage`. + +You can declare context in `Context.js`. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { places } from './data.js'; +import { getImageUrl } from './utils.js'; + +export default function App() { + const [isLarge, setIsLarge] = useState(false); + const imageSize = isLarge ? 150 : 100; + return ( + <> + <label> + <input + type="checkbox" + checked={isLarge} + onChange={e => { + setIsLarge(e.target.checked); + }} + /> + Use large images + </label> + <hr /> + <List imageSize={imageSize} /> + </> + ) +} + +function List({ imageSize }) { + const listItems = places.map(place => + <li key={place.id}> + <Place + place={place} + imageSize={imageSize} + /> + </li> + ); + return <ul>{listItems}</ul>; +} + +function Place({ place, imageSize }) { + return ( + <> + <PlaceImage + place={place} + imageSize={imageSize} + /> + <p> + <b>{place.name}</b> + {': ' + place.description} + </p> + </> + ); +} + +function PlaceImage({ place, imageSize }) { + return ( + <img + src={getImageUrl(place)} + alt={place.name} + width={imageSize} + height={imageSize} + /> + ); +} +``` + +```js Context.js + +``` + +```js data.js +export const places = [{ + id: 0, + name: 'Bo-Kaap in Cape Town, South Africa', + description: 'The tradition of choosing bright colors for houses began in the late 20th century.', + imageId: 'K9HVAGH' +}, { + id: 1, + name: 'Rainbow Village in Taichung, Taiwan', + description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', + imageId: '9EAYZrt' +}, { + id: 2, + name: 'Macromural de Pachuca, Mexico', + description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', + imageId: 'DgXHVwu' +}, { + id: 3, + name: 'Selarón Staircase in Rio de Janeiro, Brazil', + description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people."', + imageId: 'aeO3rpI' +}, { + id: 4, + name: 'Burano, Italy', + description: 'The houses are painted following a specific color system dating back to 16th century.', + imageId: 'kxsph5C' +}, { + id: 5, + name: 'Chefchaouen, Marocco', + description: 'There are a few theories on why the houses are painted blue, including that the color repells mosquitos or that it symbolizes sky and heaven.', + imageId: 'rTqKo46' +}, { + id: 6, + name: 'Gamcheon Culture Village in Busan, South Korea', + description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', + imageId: 'ZfQOOzf' +}]; +``` + +```js utils.js +export function getImageUrl(place) { + return ( + 'https://i.imgur.com/' + + place.imageId + + 'l.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +``` + +</Sandpack> + +<Solution> + +Remove `imageSize` prop from all the components. + +Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `<ImageSizeContext.Provider value={imageSize}>` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`: + +<Sandpack> + +```js App.js +import { useState, useContext } from 'react'; +import { places } from './data.js'; +import { getImageUrl } from './utils.js'; +import { ImageSizeContext } from './Context.js'; + +export default function App() { + const [isLarge, setIsLarge] = useState(false); + const imageSize = isLarge ? 150 : 100; + return ( + <ImageSizeContext.Provider + value={imageSize} + > + <label> + <input + type="checkbox" + checked={isLarge} + onChange={e => { + setIsLarge(e.target.checked); + }} + /> + Use large images + </label> + <hr /> + <List /> + </ImageSizeContext.Provider> + ) +} + +function List() { + const listItems = places.map(place => + <li key={place.id}> + <Place place={place} /> + </li> + ); + return <ul>{listItems}</ul>; +} + +function Place({ place }) { + return ( + <> + <PlaceImage place={place} /> + <p> + <b>{place.name}</b> + {': ' + place.description} + </p> + </> + ); +} + +function PlaceImage({ place }) { + const imageSize = useContext(ImageSizeContext); + return ( + <img + src={getImageUrl(place)} + alt={place.name} + width={imageSize} + height={imageSize} + /> + ); +} +``` + +```js Context.js +import { createContext } from 'react'; + +export const ImageSizeContext = createContext(500); +``` + +```js data.js +export const places = [{ + id: 0, + name: 'Bo-Kaap in Cape Town, South Africa', + description: 'The tradition of choosing bright colors for houses began in the late 20th century.', + imageId: 'K9HVAGH' +}, { + id: 1, + name: 'Rainbow Village in Taichung, Taiwan', + description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', + imageId: '9EAYZrt' +}, { + id: 2, + name: 'Macromural de Pachuca, Mexico', + description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', + imageId: 'DgXHVwu' +}, { + id: 3, + name: 'Selarón Staircase in Rio de Janeiro, Brazil', + description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people".', + imageId: 'aeO3rpI' +}, { + id: 4, + name: 'Burano, Italy', + description: 'The houses are painted following a specific color system dating back to 16th century.', + imageId: 'kxsph5C' +}, { + id: 5, + name: 'Chefchaouen, Marocco', + description: 'There are a few theories on why the houses are painted blue, including that the color repells mosquitos or that it symbolizes sky and heaven.', + imageId: 'rTqKo46' +}, { + id: 6, + name: 'Gamcheon Culture Village in Busan, South Korea', + description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', + imageId: 'ZfQOOzf' +}]; +``` + +```js utils.js +export function getImageUrl(place) { + return ( + 'https://i.imgur.com/' + + place.imageId + + 'l.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +``` + +</Sandpack> + +Note how components in the middle don't need to pass `imageSize` anymore. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/passing-props-to-a-component.md b/beta/src/content/learn/passing-props-to-a-component.md new file mode 100644 index 000000000..111fa89bd --- /dev/null +++ b/beta/src/content/learn/passing-props-to-a-component.md @@ -0,0 +1,1113 @@ +--- +title: Passing Props to a Component +--- + +<Intro> + +React components use *props* to communicate with each other. Every parent component can pass some information to its child components by giving them props. Props might remind you of HTML attributes, but you can pass any JavaScript value through them, including objects, arrays, and functions. + +</Intro> + +<YouWillLearn> + +* How to pass props to a component +* How to read props from a component +* How to specify default values for props +* How to pass some JSX to a component +* How props change over time + +</YouWillLearn> + +## Familiar props {/*familiar-props*/} + +Props are the information that you pass to a JSX tag. For example, `className`, `src`, `alt`, `width`, and `height` are some of the props you can pass to an `<img>`: + +<Sandpack> + +```js +function Avatar() { + return ( + <img + className="avatar" + src="https://i.imgur.com/1bX5QH6.jpg" + alt="Lin Lanying" + width={100} + height={100} + /> + ); +} + +export default function Profile() { + return ( + <Avatar /> + ); +} +``` + +```css +body { min-height: 120px; } +.avatar { margin: 20px; border-radius: 50%; } +``` + +</Sandpack> + +The props you can pass to an `<img>` tag are predefined (ReactDOM conforms to [the HTML standard](https://www.w3.org/TR/html52/semantics-embedded-content.html#the-img-element)). But you can pass any props to *your own* components, such as `<Avatar>`, to customize them. Here's how! + +## Passing props to a component {/*passing-props-to-a-component*/} + +In this code, the `Profile` component isn't passing any props to its child component, `Avatar`: + +```js +export default function Profile() { + return ( + <Avatar /> + ); +} +``` + +You can give `Avatar` some props in two steps. + +### Step 1: Pass props to the child component {/*step-1-pass-props-to-the-child-component*/} + +First, pass some props to `Avatar`. For example, let's pass two props: `person` (an object), and `size` (a number): + +```js +export default function Profile() { + return ( + <Avatar + person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }} + size={100} + /> + ); +} +``` + +> If double curly braces after `person=` confuse you, remember [they are merely an object](/learn/javascript-in-jsx-with-curly-braces#using-double-curlies-css-and-other-objects-in-jsx) inside the JSX curlies. + +Now you can read these props inside the `Avatar` component. + +### Step 2: Read props inside the child component {/*step-2-read-props-inside-the-child-component*/} + +You can read these props by listing their names `person, size` separated by the commas inside `({` and `})` directly after `function Avatar`. This lets you use them inside the `Avatar` code, like you would with a variable. + +```js +function Avatar({ person, size }) { + // person and size are available here +} +``` + +Add some logic to `Avatar` that uses the `person` and `size` props for rendering, and you're done. + +Now you can configure `Avatar` to render in many different ways with different props. Try tweaking the values! + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js'; + +function Avatar({ person, size }) { + return ( + <img + className="avatar" + src={getImageUrl(person)} + alt={person.name} + width={size} + height={size} + /> + ); +} + +export default function Profile() { + return ( + <div> + <Avatar + size={100} + person={{ + name: 'Katsuko Saruhashi', + imageId: 'YfeOqp2' + }} + /> + <Avatar + size={80} + person={{ + name: 'Aklilu Lemma', + imageId: 'OKS67lh' + }} + /> + <Avatar + size={50} + person={{ + name: 'Lin Lanying', + imageId: '1bX5QH6' + }} + /> + </div> + ); +} +``` + +```js utils.js +export function getImageUrl(person, size = 's') { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +body { min-height: 120px; } +.avatar { margin: 10px; border-radius: 50%; } +``` + +</Sandpack> + +Props let you think about parent and child components independently. For example, you can change the `person` or the `size` props inside `Profile` without having to think about how `Avatar` uses them. Similarly, you can change how the `Avatar` uses these props, without looking at the `Profile`. + +You can think of props like "knobs" that you can adjust. They serve the same role as arguments serve for functions—in fact, props _are_ the only argument to your component! React component functions accept a single argument, a `props` object: + +```js +function Avatar(props) { + let person = props.person; + let size = props.size; + // ... +} +``` + +Usually you don't need the whole `props` object itself, so you destructure it into individual props. + +<Pitfall> + +**Don't miss the pair of `{` and `}` curlies** inside of `(` and `)` when declaring props: + +```js +function Avatar({ person, size }) { + // ... +} +``` + +This syntax is called ["destructuring"](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Unpacking_fields_from_objects_passed_as_a_function_parameter) and is equivalent to reading properties from a function parameter: + +```js +function Avatar(props) { + let person = props.person; + let size = props.size; + // ... +} +``` + +</Pitfall> + +## Specifying a default value for a prop {/*specifying-a-default-value-for-a-prop*/} + +If you want to give a prop a default value to fall back on when no value is specified, you can do it with the destructuring by putting `=` and the default value right after the parameter: + +```js +function Avatar({ person, size = 100 }) { + // ... +} +``` + +Now, if `<Avatar person={...} />` is rendered with no `size` prop, the `size` will be set to `100`. + +The default value is only used if the `size` prop is missing or if you pass `size={undefined}`. But if you pass `size={null}` or `size={0}`, the default value will **not** be used. + +## Forwarding props with the JSX spread syntax {/*forwarding-props-with-the-jsx-spread-syntax*/} + +Sometimes, passing props gets very repetitive: + +```js +function Profile({ person, size, isSepia, thickBorder }) { + return ( + <div className="card"> + <Avatar + person={person} + size={size} + isSepia={isSepia} + thickBorder={thickBorder} + /> + </div> + ); +} +``` + +There's nothing wrong with repetitive code—it can be more legible. But at times you may value conciseness. Some components forward all of their props to their children, like how this `Profile` does with `Avatar`. Because they don't use any of their props directly, it can make sense to use a more concise "spread" syntax: + +```js +function Profile(props) { + return ( + <div className="card"> + <Avatar {...props} /> + </div> + ); +} +``` + +This forwards all of `Profile`'s props to the `Avatar` without listing each of their names. + +**Use spread syntax with restraint.** If you're using it in every other component, something is wrong. Often, it indicates that you should split your components and pass children as JSX. More on that next! + +## Passing JSX as children {/*passing-jsx-as-children*/} + +It is common to nest built-in browser tags: + +```js +<div> + <img /> +</div> +``` + +Sometimes you'll want to nest your own components the same way: + +```js +<Card> + <Avatar /> +</Card> +``` + +When you nest content inside a JSX tag, the parent component will receive that content in a prop called `children`. For example, the `Card` component below will receive a `children` prop set to `<Avatar />` and render it in a wrapper div: + +<Sandpack> + +```js App.js +import Avatar from './Avatar.js'; + +function Card({ children }) { + return ( + <div className="card"> + {children} + </div> + ); +} + +export default function Profile() { + return ( + <Card> + <Avatar + size={100} + person={{ + name: 'Katsuko Saruhashi', + imageId: 'YfeOqp2' + }} + /> + </Card> + ); +} +``` + +```js Avatar.js +import { getImageUrl } from './utils.js'; + +export default function Avatar({ person, size }) { + return ( + <img + className="avatar" + src={getImageUrl(person)} + alt={person.name} + width={size} + height={size} + /> + ); +} +``` + +```js utils.js +export function getImageUrl(person, size = 's') { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.card { + width: fit-content; + margin: 5px; + padding: 5px; + font-size: 20px; + text-align: center; + border: 1px solid #aaa; + border-radius: 20px; + background: #fff; +} +.avatar { + margin: 20px; + border-radius: 50%; +} +``` + +</Sandpack> + +Try replacing the `<Avatar>` inside `<Card>` with some text to see how the `Card` component can wrap any nested content. It doesn't need to "know" what's being rendered inside of it. You will see this flexible pattern in many places. + +You can think of a component with a `children` prop as having a "hole" that can be "filled in" by its parent components with arbitrary JSX. You will often use the `children` prop for visual wrappers: panels, grids, and so on. + +<Illustration src="/images/docs/illustrations/i_children-prop.png" alt='A puzzle-like Card tile with a slot for "children" pieces like text and Avatar' /> + +## How props change over time {/*how-props-change-over-time*/} + +The `Clock` component below receives two props from its parent component: `color` and `time`. (The parent component's code is omitted because it uses [state](/learn/state-a-components-memory), which we won't dive into just yet.) + +Try changing the color in the select box below: + +<Sandpack> + +```js Clock.js active +export default function Clock({ color, time }) { + return ( + <h1 style={{ color: color }}> + {time} + </h1> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import Clock from './Clock.js'; + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} + +export default function App() { + const time = useTime(); + const [color, setColor] = useState('lightcoral'); + return ( + <div> + <p> + Pick a color:{' '} + <select value={color} onChange={e => setColor(e.target.value)}> + <option value="lightcoral">lightcoral</option> + <option value="midnightblue">midnightblue</option> + <option value="rebeccapurple">rebeccapurple</option> + </select> + </p> + <Clock color={color} time={time.toLocaleTimeString()} /> + </div> + ); +} +``` + +</Sandpack> + +This example illustrates that **a component may receive different props over time.** Props are not always static! Here, the `time` prop changes every second, and the `color` prop changes when you select another color. Props reflect a component's data at any point in time, rather than only in the beginning. + +However, props are [immutable](https://en.wikipedia.org/wiki/Immutable_object)—a term from computer science meaning "unchangeable". When a component needs to change its props (for example, in response to a user interaction or new data), it will have to "ask" its parent component to pass it _different props_—a new object! Its old props will then be cast aside, and eventually the JavaScript engine will reclaim the memory taken by them. + +**Don't try to "change props".** When you need to respond to the user input (like changing the selected color), you will need to "set state", which you can learn about in [State: A Component's Memory.](/learn/state-a-components-memory) + +<Recap> + +* To pass props, add them to the JSX, just like you would with HTML attributes. +* To read props, use the `function Avatar({ person, size })` destructuring syntax. +* You can specify a default value like `size = 100`, which is used for missing and `undefined` props. +* You can forward all props with `<Avatar {...props} />` JSX spread syntax, but don't overuse it! +* Nested JSX like `<Card><Avatar /></Card>` will appear as `Card` component's `children` prop. +* Props are read-only snapshots in time: every render receives a new version of props. +* You can't change props. When you need interactivity, you'll need to set state. + +</Recap> + + + +<Challenges> + +#### Extract a component {/*extract-a-component*/} + +This `Gallery` component contains some very similar markup for two profiles. Extract a `Profile` component out of it to reduce the duplication. You'll need to choose what props to pass to it. + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js'; + +export default function Gallery() { + return ( + <div> + <h1>Notable Scientists</h1> + <section className="profile"> + <h2>Maria Skłodowska-Curie</h2> + <img + className="avatar" + src={getImageUrl('szV5sdG')} + alt="Maria Skłodowska-Curie" + width={70} + height={70} + /> + <ul> + <li> + <b>Profession: </b> + physicist and chemist + </li> + <li> + <b>Awards: 4 </b> + (Nobel Prize in Physics, Nobel Prize in Chemistry, Davy Medal, Matteucci Medal) + </li> + <li> + <b>Discovered: </b> + polonium (element) + </li> + </ul> + </section> + <section className="profile"> + <h2>Katsuko Saruhashi</h2> + <img + className="avatar" + src={getImageUrl('YfeOqp2')} + alt="Katsuko Saruhashi" + width={70} + height={70} + /> + <ul> + <li> + <b>Profession: </b> + geochemist + </li> + <li> + <b>Awards: 2 </b> + (Miyake Prize for geochemistry, Tanaka Prize) + </li> + <li> + <b>Discovered: </b> + a method for measuring carbon dioxide in seawater + </li> + </ul> + </section> + </div> + ); +} +``` + +```js utils.js +export function getImageUrl(imageId, size = 's') { + return ( + 'https://i.imgur.com/' + + imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 5px; border-radius: 50%; min-height: 70px; } +.profile { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} +h1, h2 { margin: 5px; } +h1 { margin-bottom: 10px; } +ul { padding: 0px 10px 0px 20px; } +li { margin: 5px; } +``` + +</Sandpack> + +<Hint> + +Start by extracting the markup for one of the scientists. Then find the pieces that don't match it in the second example, and make them configurable by props. + +</Hint> + +<Solution> + +In this solution, the `Profile` component accepts multiple props: `imageId` (a string), `name` (a string), `profession` (a string), `awards` (an array of strings), `discovery` (a string), and `imageSize` (a number). + +Note that the `imageSize` prop has a default value, which is why we don't pass it to the component. + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js'; + +function Profile({ + imageId, + name, + profession, + awards, + discovery, + imageSize = 70 +}) { + return ( + <section className="profile"> + <h2>{name}</h2> + <img + className="avatar" + src={getImageUrl(imageId)} + alt={name} + width={imageSize} + height={imageSize} + /> + <ul> + <li><b>Profession:</b> {profession}</li> + <li> + <b>Awards: {awards.length} </b> + ({awards.join(', ')}) + </li> + <li> + <b>Discovered: </b> + {discovery} + </li> + </ul> + </section> + ); +} + +export default function Gallery() { + return ( + <div> + <h1>Notable Scientists</h1> + <Profile + imageId="szV5sdG" + name="Maria Skłodowska-Curie" + profession="physicist and chemist" + discovery="polonium (chemical element)" + awards={[ + 'Nobel Prize in Physics', + 'Nobel Prize in Chemistry', + 'Davy Medal', + 'Matteucci Medal' + ]} + /> + <Profile + imageId='YfeOqp2' + name='Katsuko Saruhashi' + profession='geochemist' + discovery="a method for measuring carbon dioxide in seawater" + awards={[ + 'Miyake Prize for geochemistry', + 'Tanaka Prize' + ]} + /> + </div> + ); +} +``` + +```js utils.js +export function getImageUrl(imageId, size = 's') { + return ( + 'https://i.imgur.com/' + + imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 5px; border-radius: 50%; min-height: 70px; } +.profile { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} +h1, h2 { margin: 5px; } +h1 { margin-bottom: 10px; } +ul { padding: 0px 10px 0px 20px; } +li { margin: 5px; } +``` + +</Sandpack> + +Note how you don't need a separate `awardCount` prop if `awards` is an array. Then you can use `awards.length` to count the number of awards. Remember that props can take any values, and that includes arrays too! + +Another solution, which is more similar to the earlier examples on this page, is to group all information about a person in a single object, and pass that object as one prop: + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js'; + +function Profile({ person, imageSize = 70 }) { + const imageSrc = getImageUrl(person) + + return ( + <section className="profile"> + <h2>{person.name}</h2> + <img + className="avatar" + src={imageSrc} + alt={person.name} + width={imageSize} + height={imageSize} + /> + <ul> + <li> + <b>Profession:</b> {person.profession} + </li> + <li> + <b>Awards: {person.awards.length} </b> + ({person.awards.join(', ')}) + </li> + <li> + <b>Discovered: </b> + {person.discovery} + </li> + </ul> + </section> + ) +} + +export default function Gallery() { + return ( + <div> + <h1>Notable Scientists</h1> + <Profile person={{ + imageId: 'szV5sdG', + name: 'Maria Skłodowska-Curie', + profession: 'physicist and chemist', + discovery: 'polonium (chemical element)', + awards: [ + 'Nobel Prize in Physics', + 'Nobel Prize in Chemistry', + 'Davy Medal', + 'Matteucci Medal' + ], + }} /> + <Profile person={{ + imageId: 'YfeOqp2', + name: 'Katsuko Saruhashi', + profession: 'geochemist', + discovery: 'a method for measuring carbon dioxide in seawater', + awards: [ + 'Miyake Prize for geochemistry', + 'Tanaka Prize' + ], + }} /> + </div> + ); +} +``` + +```js utils.js +export function getImageUrl(person, size = 's') { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 5px; border-radius: 50%; min-height: 70px; } +.profile { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} +h1, h2 { margin: 5px; } +h1 { margin-bottom: 10px; } +ul { padding: 0px 10px 0px 20px; } +li { margin: 5px; } +``` + +</Sandpack> + +Although the syntax looks slightly different because you're describing properties of a JavaScript object rather than a collection of JSX attributes, these examples are mostly equivalent, and you can pick either approach. + +</Solution> + +#### Adjust the image size based on a prop {/*adjust-the-image-size-based-on-a-prop*/} + +In this example, `Avatar` receives a numeric `size` prop which determines the `<img>` width and height. The `size` prop is set to `40` in this example. However, if you open the image in a new tab, you'll notice that the image itself is larger (`160` pixels). The real image size is determined by which thumbnail size you're requesting. + +Change the `Avatar` component to request the closest image size based on the `size` prop. Specifically, if the `size` is less than `90`, pass `'s'` ("small") rather than `'b'` ("big") to the `getImageUrl` function. Verify that your changes work by rendering avatars with different values of the `size` prop and opening images in a new tab. + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js'; + +function Avatar({ person, size }) { + return ( + <img + className="avatar" + src={getImageUrl(person, 'b')} + alt={person.name} + width={size} + height={size} + /> + ); +} + +export default function Profile() { + return ( + <Avatar + size={40} + person={{ + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP' + }} + /> + ); +} +``` + +```js utils.js +export function getImageUrl(person, size) { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 20px; border-radius: 50%; } +``` + +</Sandpack> + +<Solution> + +Here is how you could go about it: + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js'; + +function Avatar({ person, size }) { + let thumbnailSize = 's'; + if (size > 90) { + thumbnailSize = 'b'; + } + return ( + <img + className="avatar" + src={getImageUrl(person, thumbnailSize)} + alt={person.name} + width={size} + height={size} + /> + ); +} + +export default function Profile() { + return ( + <> + <Avatar + size={40} + person={{ + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP' + }} + /> + <Avatar + size={120} + person={{ + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP' + }} + /> + </> + ); +} +``` + +```js utils.js +export function getImageUrl(person, size) { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 20px; border-radius: 50%; } +``` + +</Sandpack> + +You could also show a sharper image for high DPI screens by taking [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) into account: + +<Sandpack> + +```js App.js +import { getImageUrl } from './utils.js'; + +const ratio = window.devicePixelRatio; + +function Avatar({ person, size }) { + let thumbnailSize = 's'; + if (size * ratio > 90) { + thumbnailSize = 'b'; + } + return ( + <img + className="avatar" + src={getImageUrl(person, thumbnailSize)} + alt={person.name} + width={size} + height={size} + /> + ); +} + +export default function Profile() { + return ( + <> + <Avatar + size={40} + person={{ + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP' + }} + /> + <Avatar + size={70} + person={{ + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP' + }} + /> + <Avatar + size={120} + person={{ + name: 'Gregorio Y. Zara', + imageId: '7vQD0fP' + }} + /> + </> + ); +} +``` + +```js utils.js +export function getImageUrl(person, size) { + return ( + 'https://i.imgur.com/' + + person.imageId + + size + + '.jpg' + ); +} +``` + +```css +.avatar { margin: 20px; border-radius: 50%; } +``` + +</Sandpack> + +Props let you encapsulate logic like this inside the `Avatar` component (and change it later if needed) so that everyone can use the `<Avatar>` component without thinking about how the images are requested and resized. + +</Solution> + +#### Passing JSX in a `children` prop {/*passing-jsx-in-a-children-prop*/} + +Extract a `Card` component from the markup below, and use the `children` prop to pass different JSX to it: + +<Sandpack> + +```js +export default function Profile() { + return ( + <div> + <div className="card"> + <div className="card-content"> + <h1>Photo</h1> + <img + className="avatar" + src="https://i.imgur.com/OKS67lhm.jpg" + alt="Aklilu Lemma" + width={70} + height={70} + /> + </div> + </div> + <div className="card"> + <div className="card-content"> + <h1>About</h1> + <p>Aklilu Lemma was a distinguished Ethiopian scientist who discovered a natural treatment to schistosomiasis.</p> + </div> + </div> + </div> + ); +} +``` + +```css +.card { + width: fit-content; + margin: 20px; + padding: 20px; + border: 1px solid #aaa; + border-radius: 20px; + background: #fff; +} +.card-content { + text-align: center; +} +.avatar { + margin: 10px; + border-radius: 50%; +} +h1 { + margin: 5px; + padding: 0; + font-size: 24px; +} +``` + +</Sandpack> + +<Hint> + +Any JSX you put inside of a component's tag will be passed as the `children` prop to that component. + +</Hint> + +<Solution> + +This is how you can use the `Card` component in both places: + +<Sandpack> + +```js +function Card({ children }) { + return ( + <div className="card"> + <div className="card-content"> + {children} + </div> + </div> + ); +} + +export default function Profile() { + return ( + <div> + <Card> + <h1>Photo</h1> + <img + className="avatar" + src="https://i.imgur.com/OKS67lhm.jpg" + alt="Aklilu Lemma" + width={100} + height={100} + /> + </Card> + <Card> + <h1>About</h1> + <p>Aklilu Lemma was a distinguished Ethiopian scientist who discovered a natural treatment to schistosomiasis.</p> + </Card> + </div> + ); +} +``` + +```css +.card { + width: fit-content; + margin: 20px; + padding: 20px; + border: 1px solid #aaa; + border-radius: 20px; + background: #fff; +} +.card-content { + text-align: center; +} +.avatar { + margin: 10px; + border-radius: 50%; +} +h1 { + margin: 5px; + padding: 0; + font-size: 24px; +} +``` + +</Sandpack> + +You can also make `title` a separate prop if you want every `Card` to always have a title: + +<Sandpack> + +```js +function Card({ children, title }) { + return ( + <div className="card"> + <div className="card-content"> + <h1>{title}</h1> + {children} + </div> + </div> + ); +} + +export default function Profile() { + return ( + <div> + <Card title="Photo"> + <img + className="avatar" + src="https://i.imgur.com/OKS67lhm.jpg" + alt="Aklilu Lemma" + width={100} + height={100} + /> + </Card> + <Card title="About"> + <p>Aklilu Lemma was a distinguished Ethiopian scientist who discovered a natural treatment to schistosomiasis.</p> + </Card> + </div> + ); +} +``` + +```css +.card { + width: fit-content; + margin: 20px; + padding: 20px; + border: 1px solid #aaa; + border-radius: 20px; + background: #fff; +} +.card-content { + text-align: center; +} +.avatar { + margin: 10px; + border-radius: 50%; +} +h1 { + margin: 5px; + padding: 0; + font-size: 24px; +} +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/preserving-and-resetting-state.md b/beta/src/content/learn/preserving-and-resetting-state.md new file mode 100644 index 000000000..22ebfc221 --- /dev/null +++ b/beta/src/content/learn/preserving-and-resetting-state.md @@ -0,0 +1,2196 @@ +--- +title: Preserving and Resetting State +--- + +<Intro> + +State is isolated between components. React keeps track of which state belongs to which component based on their place in the UI tree. You can control when to preserve state and when to reset it between re-renders. + +</Intro> + +<YouWillLearn> + +* How React "sees" component structures +* When React chooses to preserve or reset the state +* How to force React to reset component's state +* How keys and types affect whether the state is preserved + +</YouWillLearn> + +## The UI tree {/*the-ui-tree*/} + +Browsers use many tree structures to model UI. The [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) represents HTML elements, the [CSSOM](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model) does the same for CSS. There's even an [Accessibility tree](https://developer.mozilla.org/docs/Glossary/Accessibility_tree)! + +React also uses tree structures to manage and model the UI you make. React makes **UI trees** from your JSX. Then React DOM updates the browser DOM elements to match that UI tree. (React Native translates these trees into elements specific to mobile platforms.) + +<DiagramGroup> + +<Diagram name="preserving_state_dom_tree" height={193} width={864} alt="Diagram with three sections arranged horizontally. In the first section, there are three rectangles stacked vertically, with labels 'Component A', 'Component B', and 'Component C'. Transitioning to the next pane is an arrow with the React logo on top labeled 'React'. The middle section contains a tree of components, with the root labeled 'A' and two children labeled 'B' and 'C'. The next section is again transitioned using an arrow with the React logo on top labeled 'React'. The third and final section is a wireframe of a browser, containing a tree of 8 nodes, which has only a subset highlighted (indicating the subtree from the middle section)."> + +From components, React creates a UI tree which React DOM uses to render the DOM + +</Diagram> + +</DiagramGroup> + +## State is tied to a position in the tree {/*state-is-tied-to-a-position-in-the-tree*/} + +When you give a component state, you might think the state "lives" inside the component. But the state is actually held inside React. React associates each piece of state it's holding with the correct component by where that component sits in the UI tree. + + +Here, there is only one `<Counter />` JSX tag, but it's rendered at two different positions: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function App() { + const counter = <Counter />; + return ( + <div> + {counter} + {counter} + </div> + ); +} + +function Counter() { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +label { + display: block; + clear: both; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; + float: left; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +Here's how these look as a tree: + +<DiagramGroup> + +<Diagram name="preserving_state_tree" height={248} width={395} alt="Diagram of a tree of React components. The root node is labeled 'div' and has two children. Each of the children are labeled 'Counter' and both contain a state bubble labeled 'count' with value 0."> + +React tree + +</Diagram> + +</DiagramGroup> + +**These are two separate counters because each is rendered at its own position in the tree.** You don't usually have to think about these positions to use React, but it can be useful to understand how it works. + +In React, each component on the screen has fully isolated state. For example, if you render two `Counter` components side by side, each of them will get its own, independent, `score` and `hover` states. + +Try clicking both counters and notice they don't affect each other: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function App() { + return ( + <div> + <Counter /> + <Counter /> + </div> + ); +} + +function Counter() { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; + float: left; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +As you can see, when one counter is updated, only the state for that component is updated: + + +<DiagramGroup> + +<Diagram name="preserving_state_increment" height={248} width={441} alt="Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 1. The state bubble of the right child is highlighted in yellow to indicate its value has updated."> + +Updating state + +</Diagram> + +</DiagramGroup> + + +React will keep the state around for as long as you render the same component at the same position. To see this, increment both counters, then remove the second component by unchecking "Render the second counter" checkbox, and then add it back by ticking it again: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function App() { + const [showB, setShowB] = useState(true); + return ( + <div> + <Counter /> + {showB && <Counter />} + <label> + <input + type="checkbox" + checked={showB} + onChange={e => { + setShowB(e.target.checked) + }} + /> + Render the second counter + </label> + </div> + ); +} + +function Counter() { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +label { + display: block; + clear: both; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; + float: left; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +Notice how the moment you stop rendering the second counter, its state disappears completely. That's because when React removes a component, it destroys its state. + +<DiagramGroup> + +<Diagram name="preserving_state_remove_component" height={253} width={422} alt="Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is missing, and in its place is a yellow 'poof' image, highlighting the component being deleted from the tree."> + +Deleting a component + +</Diagram> + +</DiagramGroup> + +When you tick "Render the second counter", a second `Counter` and its state are initialized from scratch (`score = 0`) and added to the DOM. + +<DiagramGroup> + +<Diagram name="preserving_state_add_component" height={258} width={500} alt="Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The entire right child node is highlighted in yellow, indicating that it was just added to the tree."> + +Adding a component + +</Diagram> + +</DiagramGroup> + +**React preserves a component's state for as long as it's being rendered at its position in the UI tree.** If it gets removed, or a different component gets rendered at the same position, React discards its state. + +## Same component at the same position preserves state {/*same-component-at-the-same-position-preserves-state*/} + +In this example, there are two different `<Counter />` tags: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function App() { + const [isFancy, setIsFancy] = useState(false); + return ( + <div> + {isFancy ? ( + <Counter isFancy={true} /> + ) : ( + <Counter isFancy={false} /> + )} + <label> + <input + type="checkbox" + checked={isFancy} + onChange={e => { + setIsFancy(e.target.checked) + }} + /> + Use fancy styling + </label> + </div> + ); +} + +function Counter({ isFancy }) { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + if (isFancy) { + className += ' fancy'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +label { + display: block; + clear: both; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; + float: left; +} + +.fancy { + border: 5px solid gold; + color: #ff6767; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +When you tick or clear the checkbox, the counter state does not get reset. Whether `isFancy` is `true` or `false`, you always have a `<Counter />` as the first child of the `div` returned from the root `App` component: + +<DiagramGroup> + +<Diagram name="preserving_state_same_component" height={461} width={600} alt="Diagram with two sections separated by an arrow transitioning between them. Each section contains a layout of components with a parent labeled 'App' containing a state bubble labeled isFancy. This component has one child labeled 'div', which leads to a prop bubble containing isFancy (highlighted in purple) passed down to the only child. The last child is labeled 'Counter' and contains a state bubble with label 'count' and value 3 in both diagrams. In the left section of the diagram, nothing is highlighted and the isFancy parent state value is false. In the right section of the diagram, the isFancy parent state value has changed to true and it is highlighted in yellow, and so is the props bubble below, which has also changed its isFancy value to true."> + +Updating the `App` state does not reset the `Counter` because `Counter` stays in the same position + +</Diagram> + +</DiagramGroup> + + +It's the same component at the same position, so from React's perspective, it's the same counter. + +<Pitfall> + +Remember that **it's the position in the UI tree--not in the JSX markup--that matters to React!** This component has two `return` clauses with different `<Counter />` JSX tags inside and outside the `if`: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function App() { + const [isFancy, setIsFancy] = useState(false); + if (isFancy) { + return ( + <div> + <Counter isFancy={true} /> + <label> + <input + type="checkbox" + checked={isFancy} + onChange={e => { + setIsFancy(e.target.checked) + }} + /> + Use fancy styling + </label> + </div> + ); + } + return ( + <div> + <Counter isFancy={false} /> + <label> + <input + type="checkbox" + checked={isFancy} + onChange={e => { + setIsFancy(e.target.checked) + }} + /> + Use fancy styling + </label> + </div> + ); +} + +function Counter({ isFancy }) { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + if (isFancy) { + className += ' fancy'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +label { + display: block; + clear: both; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; + float: left; +} + +.fancy { + border: 5px solid gold; + color: #ff6767; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +You might expect the state to reset when you tick checkbox, but it doesn't! This is because **both of these `<Counter />` tags are rendered at the same position.** React doesn't know where you place the conditions in your function. All it "sees" is the tree you return. In both cases, the `App` component returns a `<div>` with `<Counter />` as a first child. This is why React considers them as _the same_ `<Counter />`. + +You can think of them as having the same "address": the first child of the first child of the root. This is how React matches them up between the previous and next renders, regardless of how you structure your logic. + +</Pitfall> + +## Different components at the same position reset state {/*different-components-at-the-same-position-reset-state*/} + +In this example, ticking the checkbox will replace `<Counter>` with a `<p>`: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function App() { + const [isPaused, setIsPaused] = useState(false); + return ( + <div> + {isPaused ? ( + <p>See you later!</p> + ) : ( + <Counter /> + )} + <label> + <input + type="checkbox" + checked={isPaused} + onChange={e => { + setIsPaused(e.target.checked) + }} + /> + Take a break + </label> + </div> + ); +} + +function Counter() { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +label { + display: block; + clear: both; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; + float: left; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +Here, you switch between _different_ component types at the same position. Initially, the first child of the `<div>` contained a `Counter`. But when you swapped in a `p`, React removed the `Counter` from the UI tree and destroyed its state. + +<DiagramGroup> + +<Diagram name="preserving_state_diff_pt1" height={290} width={753} alt="Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'p', highlighted in yellow."> + +When `Counter` changes to `p`, the `Counter` is deleted and the `p` is added + +</Diagram> + +</DiagramGroup> + +<DiagramGroup> + +<Diagram name="preserving_state_diff_pt2" height={290} width={753} alt="Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'p'. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, highlighted in yellow."> + +When switching back, the `p` is deleted and the `Counter` is added + +</Diagram> + +</DiagramGroup> + +Also, **when you render a different component in the same position, it resets the state of its entire subtree.** To see how this works, increment the counter and then tick the checkbox: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function App() { + const [isFancy, setIsFancy] = useState(false); + return ( + <div> + {isFancy ? ( + <div> + <Counter isFancy={true} /> + </div> + ) : ( + <section> + <Counter isFancy={false} /> + </section> + )} + <label> + <input + type="checkbox" + checked={isFancy} + onChange={e => { + setIsFancy(e.target.checked) + }} + /> + Use fancy styling + </label> + </div> + ); +} + +function Counter({ isFancy }) { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + if (isFancy) { + className += ' fancy'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +label { + display: block; + clear: both; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; + float: left; +} + +.fancy { + border: 5px solid gold; + color: #ff6767; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +The counter state gets reset when you click the checkbox. Although you render a `Counter`, the first child of the `div` changes from a `div` to a `section`. When the child `div` was removed from the DOM, the whole tree below it (including the `Counter` and its state) was destroyed as well. + +<DiagramGroup> + +<Diagram name="preserving_state_diff_same_pt1" height={350} width={794} alt="Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'section', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'div', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow."> + +When `section` changes to `div`, the `section` is deleted and the new `div` is added + +</Diagram> + +</DiagramGroup> + +<DiagramGroup> + +<Diagram name="preserving_state_diff_same_pt2" height={350} width={794} alt="Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'div', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 0. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'section', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow."> + +When switching back, the `div` is deleted and the new `section` is added + +</Diagram> + +</DiagramGroup> + +As a rule of thumb, **if you want to preserve the state between re-renders, the structure of your tree needs to "match up"** from one render to another. If the structure is different, the state gets destroyed because React destroys state when it removes a component from the tree. + +<Pitfall> + +This is why you should not nest component function definitions. + +Here, the `MyTextField` component function is defined *inside* `MyComponent`: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function MyComponent() { + const [counter, setCounter] = useState(0); + + function MyTextField() { + const [text, setText] = useState(''); + + return ( + <input + value={text} + onChange={e => setText(e.target.value)} + /> + ); + } + + return ( + <> + <MyTextField /> + <button onClick={() => { + setCounter(counter + 1) + }}>Clicked {counter} times</button> + </> + ); +} +``` + +</Sandpack> + + +Every time you click the button, the input state disappears! This is because a *different* `MyTextField` function is created for every render of `MyComponent`. You're rendering a *different* component in the same position, so React resets all state below. This leads to bugs and performance problems. To avoid this problem, **always declare component functions at the top level, and don't nest their definitions.** + +</Pitfall> + +## Resetting state at the same position {/*resetting-state-at-the-same-position*/} + +By default, React preserves state of a component while it stays at the same position. Usually, this is exactly what you want, so it makes sense as the default behavior. But sometimes, you may want to reset a component's state. Consider this app that lets two players keep track of their scores during each turn: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Scoreboard() { + const [isPlayerA, setIsPlayerA] = useState(true); + return ( + <div> + {isPlayerA ? ( + <Counter person="Taylor" /> + ) : ( + <Counter person="Sarah" /> + )} + <button onClick={() => { + setIsPlayerA(!isPlayerA); + }}> + Next player! + </button> + </div> + ); +} + +function Counter({ person }) { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{person}'s score: {score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +h1 { + font-size: 18px; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +Currently, when you change the player, the score is preserved. The two `Counter`s appear in the same position, so React sees them as *the same* `Counter` whose `person` prop has changed. + +But conceptually, in this app they should be two separate counters. They might appear in the same place in the UI, but one is a counter for Taylor, and another is a counter for Sarah. + +There are two ways to reset state when switching between them: + +1. Render components in different positions +2. Give each component an explicit identity with `key` + + +### Option 1: Rendering a component in different positions {/*option-1-rendering-a-component-in-different-positions*/} + +If you want these two `Counter`s to be independent, you can render them in two different positions: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Scoreboard() { + const [isPlayerA, setIsPlayerA] = useState(true); + return ( + <div> + {isPlayerA && + <Counter person="Taylor" /> + } + {!isPlayerA && + <Counter person="Sarah" /> + } + <button onClick={() => { + setIsPlayerA(!isPlayerA); + }}> + Next player! + </button> + </div> + ); +} + +function Counter({ person }) { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{person}'s score: {score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +h1 { + font-size: 18px; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +* Initially, `isPlayerA` is `true`. So the first position contains `Counter` state, and the second one is empty. +* When you click the "Next player" button the first position clears but the second one now contains a `Counter`. + +<DiagramGroup> + +<Diagram name="preserving_state_diff_position_p1" height={375} width={504} alt="Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The only child, arranged to the left, is labeled Counter with a state bubble labeled 'count' and value 0. All of the left child is highlighted in yellow, indicating it was added."> + +Initial state + +</Diagram> + +<Diagram name="preserving_state_diff_position_p2" height={375} width={504} alt="Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'false'. The state bubble is highlighted in yellow, indicating that it has changed. The left child is replaced with a yellow 'poof' image indicating that it has been deleted and there is a new child on the right, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0."> + +Clicking "next" + +</Diagram> + +<Diagram name="preserving_state_diff_position_p3" height={375} width={504} alt="Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The state bubble is highlighted in yellow, indicating that it has changed. There is a new child on the left, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is replaced with a yellow 'poof' image indicating that it has been deleted."> + +Clicking "next" again + +</Diagram> + +</DiagramGroup> + +> Each `Counter`'s state gets destroyed each time its removed from the DOM. This is why they reset every time you click the button. + +This solution is convenient when you only have a few independent components rendered in the same place. In this example, you only have two, so it's not a hassle to render both separately in the JSX. + +### Option 2: Resetting state with a key {/*option-2-resetting-state-with-a-key*/} + +There is also another, more generic, way to reset a component's state. + +You might have seen `key`s when [rendering lists.](/learn/rendering-lists#keeping-list-items-in-order-with-key) Keys aren't just for lists! You can use keys to make React distinguish between any components. By default, React uses order within the parent ("first counter", "second counter") to discern between components. But keys let you tell React that this is not just a *first* counter, or a *second* counter, but a specific counter--for example, *Taylor's* counter. This way, React will know *Taylor's* counter wherever it appears in the tree! + +In this example, the two `<Counter />`s don't share state even though they appear in the same place in JSX: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Scoreboard() { + const [isPlayerA, setIsPlayerA] = useState(true); + return ( + <div> + {isPlayerA ? ( + <Counter key="Taylor" person="Taylor" /> + ) : ( + <Counter key="Sarah" person="Sarah" /> + )} + <button onClick={() => { + setIsPlayerA(!isPlayerA); + }}> + Next player! + </button> + </div> + ); +} + +function Counter({ person }) { + const [score, setScore] = useState(0); + const [hover, setHover] = useState(false); + + let className = 'counter'; + if (hover) { + className += ' hover'; + } + + return ( + <div + className={className} + onPointerEnter={() => setHover(true)} + onPointerLeave={() => setHover(false)} + > + <h1>{person}'s score: {score}</h1> + <button onClick={() => setScore(score + 1)}> + Add one + </button> + </div> + ); +} +``` + +```css +h1 { + font-size: 18px; +} + +.counter { + width: 100px; + text-align: center; + border: 1px solid gray; + border-radius: 4px; + padding: 20px; + margin: 0 20px 20px 0; +} + +.hover { + background: #ffffd8; +} +``` + +</Sandpack> + +Switching between Taylor and Sarah does not preserve the state. This is because **you gave them different `key`s:** + +```js +{isPlayerA ? ( + <Counter key="Taylor" person="Taylor" /> +) : ( + <Counter key="Sarah" person="Sarah" /> +)} +``` + +Specifying a `key` tells React to use the `key` itself as part of the position, instead of their order within the parent. This is why, even though you render them in the same place in JSX, from React's perspective, these are two different counters. As a result, they will never share state. Every time a counter appears on the screen, its state is created. Every time it is removed, its state is destroyed. Toggling between them resets their state over and over. + +> Remember that keys are not globally unique. They only specify the position *within the parent*. + +### Resetting a form with a key {/*resetting-a-form-with-a-key*/} + +Resetting state with a key is particularly useful when dealing with forms. + +In this chat app, the `<Chat>` component contains the text input state: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; + +export default function Messenger() { + const [to, setTo] = useState(contacts[0]); + return ( + <div> + <ContactList + contacts={contacts} + selectedContact={to} + onSelect={contact => setTo(contact)} + /> + <Chat contact={to} /> + </div> + ) +} + +const contacts = [ + { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, + { id: 1, name: 'Alice', email: 'alice@mail.com' }, + { id: 2, name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js +export default function ContactList({ + selectedContact, + contacts, + onSelect +}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map(contact => + <li key={contact.id}> + <button onClick={() => { + onSelect(contact); + }}> + {contact.name} + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js Chat.js +import { useState } from 'react'; + +export default function Chat({ contact }) { + const [text, setText] = useState(''); + return ( + <section className="chat"> + <textarea + value={text} + placeholder={'Chat to ' + contact.name} + onChange={e => setText(e.target.value)} + /> + <br /> + <button>Send to {contact.email}</button> + </section> + ); +} +``` + +```css +.chat, .contact-list { + float: left; + margin-bottom: 20px; +} +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +Try entering something into the input, and then press "Alice" or "Bob" to choose a different recipient. You will notice that the input state is preserved because the `<Chat>` is rendered at the same position in the tree. + +**In many apps, this may be the desired behavior, but not in a chat app!** You don't want to let the user send a message they already typed to a wrong person due to an accidental click. To fix it, add a `key`: + +```js +<Chat key={to.id} contact={to} /> +``` + +This ensures that when you select a different recipient, the `Chat` component will be recreated from scratch, including any state in the tree below it. React will also re-create the DOM elements instead of reusing them. + +Now switching the recipient always clears the text field: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Chat from './Chat.js'; +import ContactList from './ContactList.js'; + +export default function Messenger() { + const [to, setTo] = useState(contacts[0]); + return ( + <div> + <ContactList + contacts={contacts} + selectedContact={to} + onSelect={contact => setTo(contact)} + /> + <Chat key={to.id} contact={to} /> + </div> + ) +} + +const contacts = [ + { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, + { id: 1, name: 'Alice', email: 'alice@mail.com' }, + { id: 2, name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js +export default function ContactList({ + selectedContact, + contacts, + onSelect +}) { + return ( + <section className="contact-list"> + <ul> + {contacts.map(contact => + <li key={contact.id}> + <button onClick={() => { + onSelect(contact); + }}> + {contact.name} + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js Chat.js +import { useState } from 'react'; + +export default function Chat({ contact }) { + const [text, setText] = useState(''); + return ( + <section className="chat"> + <textarea + value={text} + placeholder={'Chat to ' + contact.name} + onChange={e => setText(e.target.value)} + /> + <br /> + <button>Send to {contact.email}</button> + </section> + ); +} +``` + +```css +.chat, .contact-list { + float: left; + margin-bottom: 20px; +} +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li button { + width: 100px; + padding: 10px; + margin-right: 10px; +} +textarea { + height: 150px; +} +``` + +</Sandpack> + +<DeepDive> + +#### Preserving state for removed components {/*preserving-state-for-removed-components*/} + +In a real chat app, you'd probably want to recover the input state when the user selects the previous recipient again. There are a few ways to keep the state "alive" for a component that's no longer visible: + +- You could render _all_ chats instead of just the current one, but hide all the others with CSS. The chats would not get removed from the tree, so their local state would be preserved. This solution works great for simple UIs. But it can get very slow if the hidden trees are large and contain a lot of DOM nodes. +- You could [lift the state up](/learn/sharing-state-between-components) and hold the pending message for each recipient in the parent component. This way, when the child components get removed, it doesn't matter, because it's the parent that keeps the important information. This is the most common solution. +- You might also use a different source in addition to React state. For example, you probably want a message draft to persist even if the user accidentally closes the page. To implement this, you could have the `Chat` component initialize its state by reading from the [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), and save the drafts there too. + +No matter which strategy you pick, a chat _with Alice_ is conceptually distinct from a chat _with Bob_, so it makes sense to give a `key` to the `<Chat>` tree based on the current recipient. + +</DeepDive> + +<Recap> + +- React keeps state for as long as the same component is rendered at the same position. +- State is not kept in JSX tags. It's associated with the tree position in which you put that JSX. +- You can force a subtree to reset its state by giving it a different key. +- Don't nest component definitions, or you'll reset state by accident. + +</Recap> + + + +<Challenges> + +#### Fix disappearing input text {/*fix-disappearing-input-text*/} + +This example shows a message when you press the button. However, pressing the button also accidentally resets the input. Why does this happen? Fix it so that pressing the button does not reset the input text. + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +export default function App() { + const [showHint, setShowHint] = useState(false); + if (showHint) { + return ( + <div> + <p><i>Hint: Your favorite city?</i></p> + <Form /> + <button onClick={() => { + setShowHint(false); + }}>Hide hint</button> + </div> + ); + } + return ( + <div> + <Form /> + <button onClick={() => { + setShowHint(true); + }}>Show hint</button> + </div> + ); +} + +function Form() { + const [text, setText] = useState(''); + return ( + <textarea + value={text} + onChange={e => setText(e.target.value)} + /> + ); +} +``` + +```css +textarea { display: block; margin: 10px 0; } +``` + +</Sandpack> + +<Solution> + +The problem is that `Form` is rendered in different positions. In the `if` branch, it is the second child of the `<div>`, but in the `else` branch, it is the first child. Therefore, the component type in each position changes. The first position changes between holding a `p` and a `Form`, while the second position changes between holding a `Form` and a `button`. React resets the state every time the component type changes. + +The easiest solution is to unify the branches so that `Form` always renders in the same position: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +export default function App() { + const [showHint, setShowHint] = useState(false); + return ( + <div> + {showHint && + <p><i>Hint: Your favorite city?</i></p> + } + <Form /> + {showHint ? ( + <button onClick={() => { + setShowHint(false); + }}>Hide hint</button> + ) : ( + <button onClick={() => { + setShowHint(true); + }}>Show hint</button> + )} + </div> + ); +} + +function Form() { + const [text, setText] = useState(''); + return ( + <textarea + value={text} + onChange={e => setText(e.target.value)} + /> + ); +} +``` + +```css +textarea { display: block; margin: 10px 0; } +``` + +</Sandpack> + + +Technically, you could also add `null` before `<Form />` in the `else` branch to match the `if` branch structure: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +export default function App() { + const [showHint, setShowHint] = useState(false); + if (showHint) { + return ( + <div> + <p><i>Hint: Your favorite city?</i></p> + <Form /> + <button onClick={() => { + setShowHint(false); + }}>Hide hint</button> + </div> + ); + } + return ( + <div> + {null} + <Form /> + <button onClick={() => { + setShowHint(true); + }}>Show hint</button> + </div> + ); +} + +function Form() { + const [text, setText] = useState(''); + return ( + <textarea + value={text} + onChange={e => setText(e.target.value)} + /> + ); +} +``` + +```css +textarea { display: block; margin: 10px 0; } +``` + +</Sandpack> + +This way, `Form` is always the second child, so it stays in the same position and keeps its state. But this approach is much less obvious and introduces a risk that someone else will remove that `null`. + +</Solution> + +#### Swap two form fields {/*swap-two-form-fields*/} + +This form lets you enter first and last name. It also has a checkbox controlling which field goes first. When you tick the checkbox, the "Last name" field will appear before the "First name" field. + +It almost works, but there is a bug. If you fill in the "First name" input and tick the checkbox, the text will stay in the first input (which is now "Last name"). Fix it so that the input text *also* moves when you reverse the order. + +<Hint> + +It seems like for these fields, their position within the parent is not enough. Is there some way to tell React how to match up the state between re-renders? + +</Hint> + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +export default function App() { + const [reverse, setReverse] = useState(false); + let checkbox = ( + <label> + <input + type="checkbox" + checked={reverse} + onChange={e => setReverse(e.target.checked)} + /> + Reverse order + </label> + ); + if (reverse) { + return ( + <> + <Field label="Last name" /> + <Field label="First name" /> + {checkbox} + </> + ); + } else { + return ( + <> + <Field label="First name" /> + <Field label="Last name" /> + {checkbox} + </> + ); + } +} + +function Field({ label }) { + const [text, setText] = useState(''); + return ( + <label> + {label}:{' '} + <input + type="text" + value={text} + placeholder={label} + onChange={e => setText(e.target.value)} + /> + </label> + ); +} +``` + +```css +label { display: block; margin: 10px 0; } +``` + +</Sandpack> + +<Solution> + +Give a `key` to both `<Field>` components in both `if` and `else` branches. This tells React how to "match up" the correct state for either `<Field>` even if their order within the parent changes: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +export default function App() { + const [reverse, setReverse] = useState(false); + let checkbox = ( + <label> + <input + type="checkbox" + checked={reverse} + onChange={e => setReverse(e.target.checked)} + /> + Reverse order + </label> + ); + if (reverse) { + return ( + <> + <Field key="lastName" label="Last name" /> + <Field key="firstName" label="First name" /> + {checkbox} + </> + ); + } else { + return ( + <> + <Field key="firstName" label="First name" /> + <Field key="lastName" label="Last name" /> + {checkbox} + </> + ); + } +} + +function Field({ label }) { + const [text, setText] = useState(''); + return ( + <label> + {label}:{' '} + <input + type="text" + value={text} + placeholder={label} + onChange={e => setText(e.target.value)} + /> + </label> + ); +} +``` + +```css +label { display: block; margin: 10px 0; } +``` + +</Sandpack> + +</Solution> + +#### Reset a detail form {/*reset-a-detail-form*/} + +This is an editable contact list. You can edit the selected contact's details and then either press "Save" to update it, or "Reset" to undo your changes. + +When you select a different contact (for example, Alice), the state updates but the form keeps showing the previous contact's details. Fix it so that the form gets reset when the selected contact changes. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ContactList from './ContactList.js'; +import EditContact from './EditContact.js'; + +export default function ContactManager() { + const [ + contacts, + setContacts + ] = useState(initialContacts); + const [ + selectedId, + setSelectedId + ] = useState(0); + const selectedContact = contacts.find(c => + c.id === selectedId + ); + + function handleSave(updatedData) { + const nextContacts = contacts.map(c => { + if (c.id === updatedData.id) { + return updatedData; + } else { + return c; + } + }); + setContacts(nextContacts); + } + + return ( + <div> + <ContactList + contacts={contacts} + selectedId={selectedId} + onSelect={id => setSelectedId(id)} + /> + <hr /> + <EditContact + initialData={selectedContact} + onSave={handleSave} + /> + </div> + ) +} + +const initialContacts = [ + { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, + { id: 1, name: 'Alice', email: 'alice@mail.com' }, + { id: 2, name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js +export default function ContactList({ + contacts, + selectedId, + onSelect +}) { + return ( + <section> + <ul> + {contacts.map(contact => + <li key={contact.id}> + <button onClick={() => { + onSelect(contact.id); + }}> + {contact.id === selectedId ? + <b>{contact.name}</b> : + contact.name + } + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js EditContact.js +import { useState } from 'react'; + +export default function EditContact({ initialData, onSave }) { + const [name, setName] = useState(initialData.name); + const [email, setEmail] = useState(initialData.email); + return ( + <section> + <label> + Name:{' '} + <input + type="text" + value={name} + onChange={e => setName(e.target.value)} + /> + </label> + <label> + Email:{' '} + <input + type="email" + value={email} + onChange={e => setEmail(e.target.value)} + /> + </label> + <button onClick={() => { + const updatedData = { + id: initialData.id, + name: name, + email: email + }; + onSave(updatedData); + }}> + Save + </button> + <button onClick={() => { + setName(initialData.name); + setEmail(initialData.email); + }}> + Reset + </button> + </section> + ); +} +``` + +```css +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li { display: inline-block; } +li button { + padding: 10px; +} +label { + display: block; + margin: 10px 0; +} +button { + margin-right: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +<Solution> + +Give `key={selectedId}` to the `EditContact` component. This way, switching between different contacts will reset the form: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ContactList from './ContactList.js'; +import EditContact from './EditContact.js'; + +export default function ContactManager() { + const [ + contacts, + setContacts + ] = useState(initialContacts); + const [ + selectedId, + setSelectedId + ] = useState(0); + const selectedContact = contacts.find(c => + c.id === selectedId + ); + + function handleSave(updatedData) { + const nextContacts = contacts.map(c => { + if (c.id === updatedData.id) { + return updatedData; + } else { + return c; + } + }); + setContacts(nextContacts); + } + + return ( + <div> + <ContactList + contacts={contacts} + selectedId={selectedId} + onSelect={id => setSelectedId(id)} + /> + <hr /> + <EditContact + key={selectedId} + initialData={selectedContact} + onSave={handleSave} + /> + </div> + ) +} + +const initialContacts = [ + { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, + { id: 1, name: 'Alice', email: 'alice@mail.com' }, + { id: 2, name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js +export default function ContactList({ + contacts, + selectedId, + onSelect +}) { + return ( + <section> + <ul> + {contacts.map(contact => + <li key={contact.id}> + <button onClick={() => { + onSelect(contact.id); + }}> + {contact.id === selectedId ? + <b>{contact.name}</b> : + contact.name + } + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js EditContact.js +import { useState } from 'react'; + +export default function EditContact({ initialData, onSave }) { + const [name, setName] = useState(initialData.name); + const [email, setEmail] = useState(initialData.email); + return ( + <section> + <label> + Name:{' '} + <input + type="text" + value={name} + onChange={e => setName(e.target.value)} + /> + </label> + <label> + Email:{' '} + <input + type="email" + value={email} + onChange={e => setEmail(e.target.value)} + /> + </label> + <button onClick={() => { + const updatedData = { + id: initialData.id, + name: name, + email: email + }; + onSave(updatedData); + }}> + Save + </button> + <button onClick={() => { + setName(initialData.name); + setEmail(initialData.email); + }}> + Reset + </button> + </section> + ); +} +``` + +```css +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li { display: inline-block; } +li button { + padding: 10px; +} +label { + display: block; + margin: 10px 0; +} +button { + margin-right: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +</Solution> + +#### Clear an image while it's loading {/*clear-an-image-while-its-loading*/} + +When you press "Next", the browser starts loading the next image. However, because it's displayed in the same `<img>` tag, by default you would still see the previous image until the next one loads. This may be undesirable if it's important for the text to always match the image. Change it so that the moment you press "Next", the previous image immediately clears. + +<Hint> + +Is there a way to tell React to re-create the DOM instead of reusing it? + +</Hint> + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + const hasNext = index < images.length - 1; + + function handleClick() { + if (hasNext) { + setIndex(index + 1); + } else { + setIndex(0); + } + } + + let image = images[index]; + return ( + <> + <button onClick={handleClick}> + Next + </button> + <h3> + Image {index + 1} of {images.length} + </h3> + <img src={image.src} /> + <p> + {image.place} + </p> + </> + ); +} + +let images = [{ + place: 'Penang, Malaysia', + src: 'https://i.imgur.com/FJeJR8M.jpg' +}, { + place: 'Lisbon, Portugal', + src: 'https://i.imgur.com/dB2LRbj.jpg' +}, { + place: 'Bilbao, Spain', + src: 'https://i.imgur.com/z08o2TS.jpg' +}, { + place: 'Valparaíso, Chile', + src: 'https://i.imgur.com/Y3utgTi.jpg' +}, { + place: 'Schwyz, Switzerland', + src: 'https://i.imgur.com/JBbMpWY.jpg' +}, { + place: 'Prague, Czechia', + src: 'https://i.imgur.com/QwUKKmF.jpg' +}, { + place: 'Ljubljana, Slovenia', + src: 'https://i.imgur.com/3aIiwfm.jpg' +}]; +``` + +```css +img { width: 150px; height: 150px; } +``` + +</Sandpack> + +<Solution> + +You can provide a `key` to the `<img>` tag. When that `key` changes, React will re-create the `<img>` DOM node from scratch. This causes a brief flash when each image loads, so it's not something you'd want to do for every image in your app. But it makes sense if you want to ensure the image always matches the text. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + const hasNext = index < images.length - 1; + + function handleClick() { + if (hasNext) { + setIndex(index + 1); + } else { + setIndex(0); + } + } + + let image = images[index]; + return ( + <> + <button onClick={handleClick}> + Next + </button> + <h3> + Image {index + 1} of {images.length} + </h3> + <img key={image.src} src={image.src} /> + <p> + {image.place} + </p> + </> + ); +} + +let images = [{ + place: 'Penang, Malaysia', + src: 'https://i.imgur.com/FJeJR8M.jpg' +}, { + place: 'Lisbon, Portugal', + src: 'https://i.imgur.com/dB2LRbj.jpg' +}, { + place: 'Bilbao, Spain', + src: 'https://i.imgur.com/z08o2TS.jpg' +}, { + place: 'Valparaíso, Chile', + src: 'https://i.imgur.com/Y3utgTi.jpg' +}, { + place: 'Schwyz, Switzerland', + src: 'https://i.imgur.com/JBbMpWY.jpg' +}, { + place: 'Prague, Czechia', + src: 'https://i.imgur.com/QwUKKmF.jpg' +}, { + place: 'Ljubljana, Slovenia', + src: 'https://i.imgur.com/3aIiwfm.jpg' +}]; +``` + +```css +img { width: 150px; height: 150px; } +``` + +</Sandpack> + +</Solution> + +#### Fix misplaced state in the list {/*fix-misplaced-state-in-the-list*/} + +In this list, each `Contact` has state that determines whether "Show email" has been pressed for it. Press "Show email" for Alice, and then tick the "Show in reverse order" checkbox. You will notice that it's _Taylor's_ email that is expanded now, but Alice's--which has moved to the bottom--appears collapsed. + +Fix it so that the expanded state is associated with each contact, regardless of the chosen ordering. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Contact from './Contact.js'; + +export default function ContactList() { + const [reverse, setReverse] = useState(false); + + const displayedContacts = [...contacts]; + if (reverse) { + displayedContacts.reverse(); + } + + return ( + <> + <label> + <input + type="checkbox" + value={reverse} + onChange={e => { + setReverse(e.target.checked) + }} + />{' '} + Show in reverse order + </label> + <ul> + {displayedContacts.map((contact, i) => + <li key={i}> + <Contact contact={contact} /> + </li> + )} + </ul> + </> + ); +} + +const contacts = [ + { id: 0, name: 'Alice', email: 'alice@mail.com' }, + { id: 1, name: 'Bob', email: 'bob@mail.com' }, + { id: 2, name: 'Taylor', email: 'taylor@mail.com' } +]; +``` + +```js Contact.js +import { useState } from 'react'; + +export default function Contact({ contact }) { + const [expanded, setExpanded] = useState(false); + return ( + <> + <p><b>{contact.name}</b></p> + {expanded && + <p><i>{contact.email}</i></p> + } + <button onClick={() => { + setExpanded(!expanded); + }}> + {expanded ? 'Hide' : 'Show'} email + </button> + </> + ); +} +``` + +```css +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li { + margin-bottom: 20px; +} +label { + display: block; + margin: 10px 0; +} +button { + margin-right: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +<Solution> + +The problem is that this example was using index as a `key`: + +```js +{displayedContacts.map((contact, i) => + <li key={i}> +``` + +However, you want the state to be associated with _each particular contact_. + +Using the contact ID as a `key` instead fixes the issue: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Contact from './Contact.js'; + +export default function ContactList() { + const [reverse, setReverse] = useState(false); + + const displayedContacts = [...contacts]; + if (reverse) { + displayedContacts.reverse(); + } + + return ( + <> + <label> + <input + type="checkbox" + value={reverse} + onChange={e => { + setReverse(e.target.checked) + }} + />{' '} + Show in reverse order + </label> + <ul> + {displayedContacts.map(contact => + <li key={contact.id}> + <Contact contact={contact} /> + </li> + )} + </ul> + </> + ); +} + +const contacts = [ + { id: 0, name: 'Alice', email: 'alice@mail.com' }, + { id: 1, name: 'Bob', email: 'bob@mail.com' }, + { id: 2, name: 'Taylor', email: 'taylor@mail.com' } +]; +``` + +```js Contact.js +import { useState } from 'react'; + +export default function Contact({ contact }) { + const [expanded, setExpanded] = useState(false); + return ( + <> + <p><b>{contact.name}</b></p> + {expanded && + <p><i>{contact.email}</i></p> + } + <button onClick={() => { + setExpanded(!expanded); + }}> + {expanded ? 'Hide' : 'Show'} email + </button> + </> + ); +} +``` + +```css +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li { + margin-bottom: 20px; +} +label { + display: block; + margin: 10px 0; +} +button { + margin-right: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +State is associated with the tree position. A `key` lets you specify a named position instead of relying on order. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/queueing-a-series-of-state-updates.md b/beta/src/content/learn/queueing-a-series-of-state-updates.md new file mode 100644 index 000000000..4144d3623 --- /dev/null +++ b/beta/src/content/learn/queueing-a-series-of-state-updates.md @@ -0,0 +1,599 @@ +--- +title: Queueing a Series of State Updates +--- + +<Intro> + +Setting a state variable will queue another render. But sometimes you might want to perform multiple operations on the value before queueing the next render. To do this, it helps to understand how React batches state updates. + +</Intro> + +<YouWillLearn> + +* What "batching" is and how React uses it to process multiple state updates +* How to apply several updates to the same state variable in a row + +</YouWillLearn> + +## React batches state updates {/*react-batches-state-updates*/} + +You might expect that clicking the "+3" button will increment the counter three times because it calls `setNumber(number + 1)` three times: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> + <h1>{number}</h1> + <button onClick={() => { + setNumber(number + 1); + setNumber(number + 1); + setNumber(number + 1); + }}>+3</button> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } +``` + +</Sandpack> + +However, as you might recall from the previous section, [each render's state values are fixed](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time), so the value of `number` inside the first render's event handler is always `0`, no matter how many times you call `setNumber(1)`: + +```js +setNumber(0 + 1); +setNumber(0 + 1); +setNumber(0 + 1); +``` + +But there is one other factor at work here to discuss. **React waits until *all* code in the event handlers has run before processing your state updates.** This is why the re-render only happens *after* all these `setNumber()` calls. + +This might remind you of a waiter taking an order at the restaurant. A waiter doesn't run to the kitchen at the mention of your first dish! Instead, they let you finish your order, let you make changes to it, and even take orders from other people at the table. + +<Illustration src="/images/docs/illustrations/i_react-batching.png" alt="An elegant cursor at a restaurant places and order multiple times with React, playing the part of the waiter. After she calls setState() multiple times, the waiter writes down the last one she requested as her final order." /> + +This lets you update multiple state variables--even from multiple components--without triggering too many [re-renders.](/learn/render-and-commit#re-renders-when-state-updates) But this also means that the UI won't be updated until _after_ your event handler, and any code in it, completes. This behavior, also known as **batching,** makes your React app run much faster. It also avoids dealing with confusing "half-finished" renders where only some of the variables have been updated. + +**React does not batch across *multiple* intentional events like clicks**--each click is handled separately. Rest assured that React only does batching when it's generally safe to do. This ensures that, for example, if the first button click disables a form, the second click would not submit it again. + +## Updating the same state variable multiple times before the next render {/*updating-the-same-state-variable-multiple-times-before-the-next-render*/} + +It is an uncommon use case, but if you would like to update the same state variable multiple times before the next render, instead of passing the *next state value* like `setNumber(number + 1)`, you can pass a *function* that calculates the next state based on the previous one in the queue, like `setNumber(n => n + 1)`. It is a way to tell React to "do something with the state value" instead of just replacing it. + +Try incrementing the counter now: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> + <h1>{number}</h1> + <button onClick={() => { + setNumber(n => n + 1); + setNumber(n => n + 1); + setNumber(n => n + 1); + }}>+3</button> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } +``` + +</Sandpack> + +Here, `n => n + 1` is called an **updater function.** When you pass it to a state setter: + +1. React queues this function to be processed after all the other code in the event handler has run. +2. During the next render, React goes through the queue and gives you the final updated state. + +```js +setNumber(n => n + 1); +setNumber(n => n + 1); +setNumber(n => n + 1); +``` + +Here's how React works through these lines of code while executing the event handler: + +1. `setNumber(n => n + 1)`: `n => n + 1` is a function. React adds it to a queue. +1. `setNumber(n => n + 1)`: `n => n + 1` is a function. React adds it to a queue. +1. `setNumber(n => n + 1)`: `n => n + 1` is a function. React adds it to a queue. + +When you call `useState` during the next render, React goes through the queue. The previous `number` state was `0`, so that's what React passes to the first updater function as the `n` argument. Then React takes the return value of your previous updater function and passes it to the next updater as `n`, and so on: + +| queued update | `n` | returns | +|--------------|---------|-----| +| `n => n + 1` | `0` | `0 + 1 = 1` | +| `n => n + 1` | `1` | `1 + 1 = 2` | +| `n => n + 1` | `2` | `2 + 1 = 3` | + +React stores `3` as the final result and returns it from `useState`. + +This is why clicking "+3" in the above example correctly increments the value by 3. +### What happens if you update state after replacing it {/*what-happens-if-you-update-state-after-replacing-it*/} + +What about this event handler? What do you think `number` will be in the next render? + +```js +<button onClick={() => { + setNumber(number + 5); + setNumber(n => n + 1); +}}> +``` + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> + <h1>{number}</h1> + <button onClick={() => { + setNumber(number + 5); + setNumber(n => n + 1); + }}>Increase the number</button> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } +``` + +</Sandpack> + +Here's what this event handler tells React to do: + +1. `setNumber(number + 5)`: `number` is `0`, so `setNumber(0 + 5)`. React adds *"replace with `5`"* to its queue. +2. `setNumber(n => n + 1)`: `n => n + 1` is an updater function. React adds *that function* to its queue. + +During the next render, React goes through the state queue: + +| queued update | `n` | returns | +|--------------|---------|-----| +| "replace with `5`" | `0` (unused) | `5` | +| `n => n + 1` | `5` | `5 + 1 = 6` | + +React stores `6` as the final result and returns it from `useState`. + +> You may have noticed that `setState(x)` actually works like `setState(n => x)`, but `n` is unused! + +### What happens if you replace state after updating it {/*what-happens-if-you-replace-state-after-updating-it*/} + +Let's try one more example. What do you think `number` will be in the next render? + +```js +<button onClick={() => { + setNumber(number + 5); + setNumber(n => n + 1); + setNumber(42); +}}> +``` + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> + <h1>{number}</h1> + <button onClick={() => { + setNumber(number + 5); + setNumber(n => n + 1); + setNumber(42); + }}>Increase the number</button> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } +``` + +</Sandpack> + +Here's how React works through these lines of code while executing this event handler: + +1. `setNumber(number + 5)`: `number` is `0`, so `setNumber(0 + 5)`. React adds *"replace with `5`"* to its queue. +2. `setNumber(n => n + 1)`: `n => n + 1` is an updater function. React adds *that function* to its queue. +3. `setNumber(42)`: React adds *"replace with `42`"* to its queue. + +During the next render, React goes through the state queue: + +| queued update | `n` | returns | +|--------------|---------|-----| +| "replace with `5`" | `0` (unused) | `5` | +| `n => n + 1` | `5` | `5 + 1 = 6` | +| "replace with `42`" | `6` (unused) | `42` | + +Then React stores `42` as the final result and returns it from `useState`. + +To summarize, here's how you can think of what you're passing to the `setNumber` state setter: + +* **An updater function** (e.g. `n => n + 1`) gets added to the queue. +* **Any other value** (e.g. number `5`) adds "replace with `5`" to the queue, ignoring what's already queued. + +After the event handler completes, React will trigger a re-render. During the re-render, React will process the queue. Updater functions run during rendering, so **updater functions must be [pure](/learn/keeping-components-pure)** and only *return* the result. Don't try to set state from inside of them or run other side effects. In Strict Mode, React will run each updater function twice (but discard the second result) to help you find mistakes. + +### Naming conventions {/*naming-conventions*/} + +It's common to name the updater function argument by the first letters of the corresponding state variable: + +```js +setEnabled(e => !e); +setLastName(ln => ln.reverse()); +setFriendCount(fc => fc * 2); +``` + +If you prefer more verbose code, another common convention is to repeat the full state variable name, like `setEnabled(enabled => !enabled)`, or to use a prefix like `setEnabled(prevEnabled => !prevEnabled)`. + +<Recap> + +* Setting state does not change the variable in the existing render, but it requests a new render. +* React processes state updates after event handlers have finished running. This is called batching. +* To update some state multiple times in one event, you can use `setNumber(n => n + 1)` updater function. + +</Recap> + + + +<Challenges> + +#### Fix a request counter {/*fix-a-request-counter*/} + +You're working on an art marketplace app that lets the user submit multiple orders for an art item at the same time. Each time the user presses the "Buy" button, the "Pending" counter should increase by one. After three seconds, the "Pending" counter should decrease, and the "Completed" counter should increase. + +However, the "Pending" counter does not behave as intended. When you press "Buy", it decreases to `-1` (which should not be possible!). And if you click fast twice, both counters seem to behave unpredictably. + +Why does this happen? Fix both counters. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function RequestTracker() { + const [pending, setPending] = useState(0); + const [completed, setCompleted] = useState(0); + + async function handleClick() { + setPending(pending + 1); + await delay(3000); + setPending(pending - 1); + setCompleted(completed + 1); + } + + return ( + <> + <h3> + Pending: {pending} + </h3> + <h3> + Completed: {completed} + </h3> + <button onClick={handleClick}> + Buy + </button> + </> + ); +} + +function delay(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} +``` + +</Sandpack> + +<Solution> + +Inside the `handleClick` event handler, the values of `pending` and `completed` correspond to what they were at the time of the click event. For the first render, `pending` was `0`, so `setPending(pending - 1)` becomes `setPending(-1)`, which is wrong. Since you want to *increment* or *decrement* the counters, rather than set them to a concrete value determined during the click, you can instead pass the updater functions: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function RequestTracker() { + const [pending, setPending] = useState(0); + const [completed, setCompleted] = useState(0); + + async function handleClick() { + setPending(p => p + 1); + await delay(3000); + setPending(p => p - 1); + setCompleted(c => c + 1); + } + + return ( + <> + <h3> + Pending: {pending} + </h3> + <h3> + Completed: {completed} + </h3> + <button onClick={handleClick}> + Buy + </button> + </> + ); +} + +function delay(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} +``` + +</Sandpack> + +This ensures that when you increment or decrement a counter, you do it in relation to its *latest* state rather than what the state was at the time of the click. + +</Solution> + +#### Implement the state queue yourself {/*implement-the-state-queue-yourself*/} + +In this challenge, you will reimplement a tiny part of React from scratch! It's not as hard as it sounds. + +Scroll through the sandbox preview. Notice that it shows **four test cases.** They correspond to the examples you've seen earlier on this page. Your task is to implement the `getFinalState` function so that it returns the correct result for each of those cases. If you implement it correctly, all four tests should pass. + +You will receive two arguments: `baseState` is the initial state (like `0`), and the `queue` is an array which contains a mix of numbers (like `5`) and updater functions (like `n => n + 1`) in the order they were added. + +Your task is to return the final state, just like the tables on this page show! + +<Hint> + +If you're feeling stuck, start with this code structure: + +```js +export function getFinalState(baseState, queue) { + let finalState = baseState; + + for (let update of queue) { + if (typeof update === 'function') { + // TODO: apply the updater function + } else { + // TODO: replace the state + } + } + + return finalState; +} +``` + +Fill out the missing lines! + +</Hint> + +<Sandpack> + +```js processQueue.js active +export function getFinalState(baseState, queue) { + let finalState = baseState; + + // TODO: do something with the queue... + + return finalState; +} +``` + +```js App.js +import { getFinalState } from './processQueue.js'; + +function increment(n) { + return n + 1; +} +increment.toString = () => 'n => n+1'; + +export default function App() { + return ( + <> + <TestCase + baseState={0} + queue={[1, 1, 1]} + expected={1} + /> + <hr /> + <TestCase + baseState={0} + queue={[ + increment, + increment, + increment + ]} + expected={3} + /> + <hr /> + <TestCase + baseState={0} + queue={[ + 5, + increment, + ]} + expected={6} + /> + <hr /> + <TestCase + baseState={0} + queue={[ + 5, + increment, + 42, + ]} + expected={42} + /> + </> + ); +} + +function TestCase({ + baseState, + queue, + expected +}) { + const actual = getFinalState(baseState, queue); + return ( + <> + <p>Base state: <b>{baseState}</b></p> + <p>Queue: <b>[{queue.join(', ')}]</b></p> + <p>Expected result: <b>{expected}</b></p> + <p style={{ + color: actual === expected ? + 'green' : + 'red' + }}> + Your result: <b>{actual}</b> + {' '} + ({actual === expected ? + 'correct' : + 'wrong' + }) + </p> + </> + ); +} +``` + +</Sandpack> + +<Solution> + +This is the exact algorithm described on this page that React uses to calculate the final state: + +<Sandpack> + +```js processQueue.js active +export function getFinalState(baseState, queue) { + let finalState = baseState; + + for (let update of queue) { + if (typeof update === 'function') { + // Apply the updater function. + finalState = update(finalState); + } else { + // Replace the next state. + finalState = update; + } + } + + return finalState; +} +``` + +```js App.js +import { getFinalState } from './processQueue.js'; + +function increment(n) { + return n + 1; +} +increment.toString = () => 'n => n+1'; + +export default function App() { + return ( + <> + <TestCase + baseState={0} + queue={[1, 1, 1]} + expected={1} + /> + <hr /> + <TestCase + baseState={0} + queue={[ + increment, + increment, + increment + ]} + expected={3} + /> + <hr /> + <TestCase + baseState={0} + queue={[ + 5, + increment, + ]} + expected={6} + /> + <hr /> + <TestCase + baseState={0} + queue={[ + 5, + increment, + 42, + ]} + expected={42} + /> + </> + ); +} + +function TestCase({ + baseState, + queue, + expected +}) { + const actual = getFinalState(baseState, queue); + return ( + <> + <p>Base state: <b>{baseState}</b></p> + <p>Queue: <b>[{queue.join(', ')}]</b></p> + <p>Expected result: <b>{expected}</b></p> + <p style={{ + color: actual === expected ? + 'green' : + 'red' + }}> + Your result: <b>{actual}</b> + {' '} + ({actual === expected ? + 'correct' : + 'wrong' + }) + </p> + </> + ); +} +``` + +</Sandpack> + +Now you know how this part of React works! + +</Solution> + +</Challenges> \ No newline at end of file diff --git a/beta/src/content/learn/react-developer-tools.md b/beta/src/content/learn/react-developer-tools.md new file mode 100644 index 000000000..89208a6bb --- /dev/null +++ b/beta/src/content/learn/react-developer-tools.md @@ -0,0 +1,76 @@ +--- +title: React Developer Tools +--- + +<Intro> + +Use React Developer Tools to inspect React [components](/learn/your-first-component), edit [props](/learn/passing-props-to-a-component) and [state](/learn/state-a-components-memory), and identify performance problems. + +</Intro> + +<YouWillLearn> + +* How to install React Developer Tools + +</YouWillLearn> + +## Browser extension {/*browser-extension*/} + +The easiest way to debug websites built with React is to install the React Developer Tools browser extension. It is available for several popular browsers: + +* [Install for **Chrome**](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) +* [Install for **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/) +* [Install for **Edge**](https://microsoftedge.microsoft.com/addons/detail/react-developer-tools/gpphkfbcpidddadnkolkpfckpihlkkil) + +Now, if you visit a website **built with React,** you will see the _Components_ and _Profiler_ panels. + +![React Developer Tools extension](/images/docs/react-devtools-extension.png) + +### Safari and other browsers {/*safari-and-other-browsers*/} +For other browsers (for example, Safari), install the [`react-devtools`](https://www.npmjs.com/package/react-devtools) npm package: +```bash +# Yarn +yarn global add react-devtools + +# Npm +npm install -g react-devtools +``` + +Next open the developer tools from the terminal: +```bash +react-devtools +``` + +Then connect your website by adding the following `<script>` tag to the beginning of your website's `<head>`: +```html {3} +<html> + <head> + <script src="http://localhost:8097"></script> +``` + +Reload your website in the browser now to view it in developer tools. + +![React Developer Tools standalone](/images/docs/react-devtools-standalone.png) + +## Mobile (React Native) {/*mobile-react-native*/} +React Developer Tools can be used to inspect apps built with [React Native](https://reactnative.dev/) as well. + +The easiest way to use React Developer Tools is to install it globally: +```bash +# Yarn +yarn global add react-devtools + +# Npm +npm install -g react-devtools +``` + +Next open the developer tools from the terminal. +```bash +react-devtools +``` + +It should connect to any local React Native app that's running. + +> Try reloading the app if developer tools doesn't connect after a few seconds. + +[Learn more about debugging React Native.](https://reactnative.dev/docs/debugging) diff --git a/beta/src/content/learn/reacting-to-input-with-state.md b/beta/src/content/learn/reacting-to-input-with-state.md new file mode 100644 index 000000000..58bda220d --- /dev/null +++ b/beta/src/content/learn/reacting-to-input-with-state.md @@ -0,0 +1,1231 @@ +--- +title: Reacting to Input with State +--- + +<Intro> + +React uses a declarative way to manipulate the UI. Instead of manipulating individual pieces of the UI directly, you describe the different states that your component can be in, and switch between them in response to the user input. This is similar to how designers think about the UI. + +</Intro> + +<YouWillLearn> + +* How declarative UI programming differs from imperative UI programming +* How to enumerate the different visual states your component can be in +* How to trigger the changes between the different visual states from code + +</YouWillLearn> + +## How declarative UI compares to imperative {/*how-declarative-ui-compares-to-imperative*/} + +When you design UI interactions, you probably think about how the UI *changes* in response to user actions. Consider a form that lets the user submit an answer: + +* When you type something into a form, the "Submit" button **becomes enabled.** +* When you press "Submit", both form and the button **become disabled,** and a spinner **appears.** +* If the network request succeeds, the form **gets hidden,** and the "Thank you" message **appears.** +* If the network request fails, an error message **appears,** and the form **becomes enabled** again. + +In **imperative programming,** the above corresponds directly to how you implement interaction. You have to write the exact instructions to manipulate the UI depending on what just happened. Here's another way to think about this: imagine riding next to someone in a car and telling them turn by turn where to go. + +<Illustration src="/images/docs/illustrations/i_imperative-ui-programming.png" alt="In a car driven by an anxious-looking person representing JavaScript, a passenger orders the driver to execute a sequence of complicated turn by turn navigations." /> + +They don't know where you want to go, they just follow your commands. (And if you get the directions wrong, you end up in the wrong place!) It's called *imperative* because you have to "command" each element, from the spinner to the button, telling the computer *how* to update the UI. + +In this example of imperative UI programming, the form is built *without* React. It uses the built-in browser [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model): + +<Sandpack> + +```js index.js active +async function handleFormSubmit(e) { + e.preventDefault(); + disable(textarea); + disable(button); + show(loadingMessage); + hide(errorMessage); + try { + await submitForm(textarea.value); + show(successMessage); + hide(form); + } catch (err) { + show(errorMessage); + errorMessage.textContent = err.message; + } finally { + hide(loadingMessage); + enable(textarea); + enable(button); + } +} + +function handleTextareaChange() { + if (textarea.value.length === 0) { + disable(button); + } else { + enable(button); + } +} + +function hide(el) { + el.style.display = 'none'; +} + +function show(el) { + el.style.display = ''; +} + +function enable(el) { + el.disabled = false; +} + +function disable(el) { + el.disabled = true; +} + +function submitForm(answer) { + // Pretend it's hitting the network. + return new Promise((resolve, reject) => { + setTimeout(() => { + if (answer.toLowerCase() == 'istanbul') { + resolve(); + } else { + reject(new Error('Good guess but a wrong answer. Try again!')); + } + }, 1500); + }); +} + +let form = document.getElementById('form'); +let textarea = document.getElementById('textarea'); +let button = document.getElementById('button'); +let loadingMessage = document.getElementById('loading'); +let errorMessage = document.getElementById('error'); +let successMessage = document.getElementById('success'); +form.onsubmit = handleFormSubmit; +textarea.oninput = handleTextareaChange; +``` + +```js sandbox.config.json hidden +{ + "hardReloadOnChange": true +} +``` + +```html public/index.html +<form id="form"> + <h2>City quiz</h2> + <p> + What city is located on two continents? + </p> + <textarea id="textarea"></textarea> + <br /> + <button id="button" disabled>Submit</button> + <p id="loading" style="display: none">Loading...</p> + <p id="error" style="display: none; color: red;"></p> +</form> +<h1 id="success" style="display: none">That's right!</h1> + +<style> +* { box-sizing: border-box; } +body { font-family: sans-serif; margin: 20px; padding: 0; } +</style> +``` + +</Sandpack> + +Manipulating the UI imperatively works well enough for isolated examples, but it gets exponentially more difficult to manage in more complex systems. Imagine updating a page full of different forms like this one. Adding a new UI element or a new interaction would require carefully checking all existing code to make sure you haven't introduced a bug (for example, forgetting to show or hide something). + +React was built to solve this problem. + +In React, you don't directly manipulate the UI--meaning you don't enable, disable, show, or hide components directly. Instead, you **declare what you want to show,** and React figures out how to update the UI. Think of getting into a taxi and telling the driver where you want to go instead of telling them exactly where to turn. It's the driver's job to get you there, and they might even know some shortcuts you haven't considered! + +<Illustration src="/images/docs/illustrations/i_declarative-ui-programming.png" alt="In a car driven by React, a passenger asks to be taken to a specific place on the map. React figures out how to do that." /> + +## Thinking about UI declaratively {/*thinking-about-ui-declaratively*/} + +You've seen how to implement a form imperatively above. To better understand how to think in React, you'll walk through reimplementing this UI in React below: + +1. **Identify** your component's different visual states +2. **Determine** what triggers those state changes +3. **Represent** the state in memory using `useState` +4. **Remove** any non-essential state variables +5. **Connect** the event handlers to set the state + +### Step 1: Identify your component's different visual states {/*step-1-identify-your-components-different-visual-states*/} + +In computer science, you may hear about a ["state machine"](https://en.wikipedia.org/wiki/Finite-state_machine) being in one of several “states”. If you work with a designer, you may have seen mockups for different "visual states". React stands at the intersection of design and computer science, so both of these ideas are sources of inspiration. + +First, you need to visualize all the different "states" of the UI the user might see: + +* **Empty**: Form has a disabled "Submit" button. +* **Typing**: Form has an enabled "Submit" button. +* **Submitting**: Form is completely disabled. Spinner is shown. +* **Success**: "Thank you" message is shown instead of a form. +* **Error**: Same as Typing state, but with an extra error message. + +Just like a designer, you'll want to "mock up" or create "mocks" for the different states before you add logic. For example, here is a mock for just the visual part of the form. This mock is controlled by a prop called `status` with a default value of `'empty'`: + +<Sandpack> + +```js +export default function Form({ + status = 'empty' +}) { + if (status === 'success') { + return <h1>That's right!</h1> + } + return ( + <> + <h2>City quiz</h2> + <p> + In which city is there a billboard that turns air into drinkable water? + </p> + <form> + <textarea /> + <br /> + <button> + Submit + </button> + </form> + </> + ) +} +``` + +</Sandpack> + +You could call that prop anything you like, the naming is not important. Try editing `status = 'empty'` to `status = 'success'` to see the success message appear. Mocking lets you quickly iterate on the UI before you wire up any logic. Here is a more fleshed out prototype of the same component, still "controlled" by the `status` prop: + +<Sandpack> + +```js +export default function Form({ + // Try 'submitting', 'error', 'success': + status = 'empty' +}) { + if (status === 'success') { + return <h1>That's right!</h1> + } + return ( + <> + <h2>City quiz</h2> + <p> + In which city is there a billboard that turns air into drinkable water? + </p> + <form> + <textarea disabled={ + status === 'submitting' + } /> + <br /> + <button disabled={ + status === 'empty' || + status === 'submitting' + }> + Submit + </button> + {status === 'error' && + <p className="Error"> + Good guess but a wrong answer. Try again! + </p> + } + </form> + </> + ); +} +``` + +```css +.Error { color: red; } +``` + +</Sandpack> + +<DeepDive> + +#### Displaying many visual states at once {/*displaying-many-visual-states-at-once*/} + +If a component has a lot of visual states, it can be convenient to show them all on one page: + +<Sandpack> + +```js App.js active +import Form from './Form.js'; + +let statuses = [ + 'empty', + 'typing', + 'submitting', + 'success', + 'error', +]; + +export default function App() { + return ( + <> + {statuses.map(status => ( + <section key={status}> + <h4>Form ({status}):</h4> + <Form status={status} /> + </section> + ))} + </> + ); +} +``` + +```js Form.js +export default function Form({ status }) { + if (status === 'success') { + return <h1>That's right!</h1> + } + return ( + <form> + <textarea disabled={ + status === 'submitting' + } /> + <br /> + <button disabled={ + status === 'empty' || + status === 'submitting' + }> + Submit + </button> + {status === 'error' && + <p className="Error"> + Good guess but a wrong answer. Try again! + </p> + } + </form> + ); +} +``` + +```css +section { border-bottom: 1px solid #aaa; padding: 20px; } +h4 { color: #222; } +body { margin: 0; } +.Error { color: red; } +``` + +</Sandpack> + +Pages like this are often called "living styleguides" or "storybooks". + +</DeepDive> + +### Step 2: Determine what triggers those state changes {/*step-2-determine-what-triggers-those-state-changes*/} + +You can trigger state updates in response to two kinds of inputs: + +* **Human inputs,** like clicking a button, typing in a field, navigating a link. +* **Computer inputs,** like a network response arriving, a timeout completing, an image loading. + +<IllustrationBlock> + <Illustration caption="Human inputs" alt="A finger." src="/images/docs/illustrations/i_inputs1.png" /> + <Illustration caption="Computer inputs" alt="Ones and zeroes." src="/images/docs/illustrations/i_inputs2.png" /> +</IllustrationBlock> + +In both cases, **you must set [state variables](/learn/state-a-components-memory#anatomy-of-usestate) to update the UI.** For the form you're developing, you will need to change state in response to a few different inputs: + +* **Changing the text input** (human) should switch it from the *Empty* state to the *Typing* state or back, depending on whether the text box is empty or not. +* **Clicking the Submit button** (human) should switch it to the *Submitting* state. +* **Successful network response** (computer) should switch it to the *Success* state. +* **Failed network response** (computer) should switch it to the *Error* state with the matching error message. + +> Notice that human inputs often require [event handlers](/learn/responding-to-events)! + +To help visualize this flow, try drawing each state on paper as a labeled circle, and each change between two states as an arrow. You can sketch out many flows this way and sort out bugs long before implementation. + +<DiagramGroup> + +<Diagram name="responding_to_input_flow" height={350} width={688} alt="Flow chart moving left to right with 5 nodes. The first node labeled 'empty' has one edge labeled 'start typing' connected to a node labeled 'typing'. That node has one edge labeled 'press submit' connected to a node labeled 'submitting', which has two edges. The left edge is labeled 'network error' connecting to a node labeled 'error'. The right edge is labeled 'network success' connecting to a node labeled 'success'."> + +Form states + +</Diagram> + +</DiagramGroup> + +### Step 3: Represent the state in memory with `useState` {/*step-3-represent-the-state-in-memory-with-usestate*/} + +Next you'll need to represent the visual states of your component in memory with [`useState`.](/reference/react/useState) Simplicity is key: each piece of state is a "moving piece", and **you want as few "moving pieces" as possible.** More complexity leads to more bugs! + +Start with the state that *absolutely must* be there. For example, you'll need to store the `answer` for the input, and the `error` (if it exists) to store the last error: + +```js +const [answer, setAnswer] = useState(''); +const [error, setError] = useState(null); +``` + +Then, you'll need a state variable representing which one of the visual states described earlier you want to display. There's usually more than a single way to represent that in memory, so you'll need to experiment with it. + +If you struggle to think of the best way immediately, start by adding enough state that you're *definitely* sure that all the possible visual states are covered: + +```js +const [isEmpty, setIsEmpty] = useState(true); +const [isTyping, setIsTyping] = useState(false); +const [isSubmitting, setIsSubmitting] = useState(false); +const [isSuccess, setIsSuccess] = useState(false); +const [isError, setIsError] = useState(false); +``` + +Your first idea likely won't be the best, but that's ok--refactoring state is a part of the process! + +### Step 4: Remove any non-essential state variables {/*step-4-remove-any-non-essential-state-variables*/} + +You want to avoid duplication in the state content so you're only tracking what is essential. Spending a little time on refactoring your state structure will make your components easier to understand, reduce duplication, and avoid unintended meanings. Your goal is to **prevent the cases where the state in memory doesn't represent any valid UI that you'd want a user to see.** (For example, you never want to show an error message and disable the input at the same time, or the user won't be able to correct the error!) + +Here are some questions you can ask about your state variables: + +* **Does this state cause a paradox?** For example, `isTyping` and `isSubmitting` can't both be `true`. A paradox usually means that the state is not constrained enough. There are four possible combinations of two booleans, but only three correspond to valid states. To remove the "impossible" state, you can combine these into a `status` that must be one of three values: `'typing'`, `'submitting'`, or `'success'`. +* **Is the same information available in another state variable already?** Another paradox: `isEmpty` and `isTyping` can't be `true` at the same time. By making them separate state variables, you risk them going out of sync and causing bugs. Fortunately, you can remove `isEmpty` and instead check `answer.length === 0`. +* **Can you get the same information from the inverse of another state variable?** `isError` is not needed because you can check `error !== null` instead. + +After this clean-up, you're left with 3 (down from 7!) *essential* state variables: + +```js +const [answer, setAnswer] = useState(''); +const [error, setError] = useState(null); +const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success' +``` + +You know they are essential, because you can't remove any of them without breaking the functionality. + +<DeepDive> + +#### Eliminating “impossible” states with a reducer {/*eliminating-impossible-states-with-a-reducer*/} + +These three variables are a good enough representation of this form's state. However, there are still some intermediate states that don't fully make sense. For example, a non-null `error` doesn't make sense when `status` is `'success'`. To model the state more precisely, you can [extract it into a reducer.](/learn/extracting-state-logic-into-a-reducer) Reducers let you unify multiple state variables into a single object and consolidate all the related logic! + +</DeepDive> + +### Step 5: Connect the event handlers to set state {/*step-5-connect-the-event-handlers-to-set-state*/} + +Lastly, create event handlers to set the state variables. Below is the final form, with all event handlers wired up: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [answer, setAnswer] = useState(''); + const [error, setError] = useState(null); + const [status, setStatus] = useState('typing'); + + if (status === 'success') { + return <h1>That's right!</h1> + } + + async function handleSubmit(e) { + e.preventDefault(); + setStatus('submitting'); + try { + await submitForm(answer); + setStatus('success'); + } catch (err) { + setStatus('typing'); + setError(err); + } + } + + function handleTextareaChange(e) { + setAnswer(e.target.value); + } + + return ( + <> + <h2>City quiz</h2> + <p> + In which city is there a billboard that turns air into drinkable water? + </p> + <form onSubmit={handleSubmit}> + <textarea + value={answer} + onChange={handleTextareaChange} + disabled={status === 'submitting'} + /> + <br /> + <button disabled={ + answer.length === 0 || + status === 'submitting' + }> + Submit + </button> + {error !== null && + <p className="Error"> + {error.message} + </p> + } + </form> + </> + ); +} + +function submitForm(answer) { + // Pretend it's hitting the network. + return new Promise((resolve, reject) => { + setTimeout(() => { + let shouldError = answer.toLowerCase() !== 'lima' + if (shouldError) { + reject(new Error('Good guess but a wrong answer. Try again!')); + } else { + resolve(); + } + }, 1500); + }); +} +``` + +```css +.Error { color: red; } +``` + +</Sandpack> + +Although this code is longer than the original imperative example, it is much less fragile. Expressing all interactions as state changes lets you later introduce new visual states without breaking existing ones. It also lets you change what should be displayed in each state without changing the logic of the interaction itself. + +<Recap> + +* Declarative programming means describing the UI for each visual state rather than micromanaging the UI (imperative). +* When developing a component: + 1. Identify all its visual states. + 2. Determine the human and computer triggers for state changes. + 3. Model the state with `useState`. + 4. Remove non-essential state to avoid bugs and paradoxes. + 5. Connect the event handlers to set state. + +</Recap> + + + +<Challenges> + +#### Add and remove a CSS class {/*add-and-remove-a-css-class*/} + +Make it so that clicking on the picture *removes* the `background--active` CSS class from the outer `<div>`, but *adds* the `picture--active` class to the `<img>`. Clicking the background again should restore the original CSS classes. + +Visually, you should expect that clicking on the picture removes the purple background and highlights the picture border. Clicking outside the picture highlights the background, but removes the picture border highlight. + +<Sandpack> + +```js +export default function Picture() { + return ( + <div className="background background--active"> + <img + className="picture" + alt="Rainbow houses in Kampung Pelangi, Indonesia" + src="https://i.imgur.com/5qwVYb1.jpeg" + /> + </div> + ); +} +``` + +```css +body { margin: 0; padding: 0; height: 250px; } + +.background { + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background: #eee; +} + +.background--active { + background: #a6b5ff; +} + +.picture { + width: 200px; + height: 200px; + border-radius: 10px; +} + +.picture--active { + border: 5px solid #a6b5ff; +} +``` + +</Sandpack> + +<Solution> + +This component has two visual states: when the image is active, and when the image is inactive: + +* When the image is active, the CSS classes are `background` and `picture picture--active`. +* When the image is inactive, the CSS classes are `background background--active` and `picture`. + +A single boolean state variable is enough to remember whether the image is active. The original task was to remove or add CSS classes. However, in React you need to *describe* what you want to see rather than *manipulate* the UI elements. So you need to calculate both CSS classes based on the current state. You also need to [stop the propagation](/learn/responding-to-events#stopping-propagation) so that clicking the image doesn't register as a click on the background. + +Verify that this version works by clicking the image and then outside of it: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Picture() { + const [isActive, setIsActive] = useState(false); + + let backgroundClassName = 'background'; + let pictureClassName = 'picture'; + if (isActive) { + pictureClassName += ' picture--active'; + } else { + backgroundClassName += ' background--active'; + } + + return ( + <div + className={backgroundClassName} + onClick={() => setIsActive(false)} + > + <img + onClick={e => { + e.stopPropagation(); + setIsActive(true); + }} + className={pictureClassName} + alt="Rainbow houses in Kampung Pelangi, Indonesia" + src="https://i.imgur.com/5qwVYb1.jpeg" + /> + </div> + ); +} +``` + +```css +body { margin: 0; padding: 0; height: 250px; } + +.background { + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background: #eee; +} + +.background--active { + background: #a6b5ff; +} + +.picture { + width: 200px; + height: 200px; + border-radius: 10px; + border: 5px solid transparent; +} + +.picture--active { + border: 5px solid #a6b5ff; +} +``` + +</Sandpack> + +Alternatively, you could return two separate chunks of JSX: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Picture() { + const [isActive, setIsActive] = useState(false); + if (isActive) { + return ( + <div + className="background" + onClick={() => setIsActive(false)} + > + <img + className="picture picture--active" + alt="Rainbow houses in Kampung Pelangi, Indonesia" + src="https://i.imgur.com/5qwVYb1.jpeg" + onClick={e => e.stopPropagation()} + /> + </div> + ); + } + return ( + <div className="background background--active"> + <img + className="picture" + alt="Rainbow houses in Kampung Pelangi, Indonesia" + src="https://i.imgur.com/5qwVYb1.jpeg" + onClick={() => setIsActive(true)} + /> + </div> + ); +} +``` + +```css +body { margin: 0; padding: 0; height: 250px; } + +.background { + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background: #eee; +} + +.background--active { + background: #a6b5ff; +} + +.picture { + width: 200px; + height: 200px; + border-radius: 10px; + border: 5px solid transparent; +} + +.picture--active { + border: 5px solid #a6b5ff; +} +``` + +</Sandpack> + +Keep in mind that if two different JSX chunks describe the same tree, their nesting (first `<div>` → first `<img>`) has to line up. Otherwise, toggling `isActive` would recreate the whole tree below and [reset its state.](/learn/preserving-and-resetting-state) This is why, if a similar JSX tree gets returned in both cases, it is better to write them as a single piece of JSX. + +</Solution> + +#### Profile editor {/*profile-editor*/} + +Here is a small form implemented with plain JavaScript and DOM. Play with it to understand its behavior: + +<Sandpack> + +```js index.js active +function handleFormSubmit(e) { + e.preventDefault(); + if (editButton.textContent === 'Edit Profile') { + editButton.textContent = 'Save Profile'; + hide(firstNameText); + hide(lastNameText); + show(firstNameInput); + show(lastNameInput); + } else { + editButton.textContent = 'Edit Profile'; + hide(firstNameInput); + hide(lastNameInput); + show(firstNameText); + show(lastNameText); + } +} + +function handleFirstNameChange() { + firstNameText.textContent = firstNameInput.value; + helloText.textContent = ( + 'Hello ' + + firstNameInput.value + ' ' + + lastNameInput.value + '!' + ); +} + +function handleLastNameChange() { + lastNameText.textContent = lastNameInput.value; + helloText.textContent = ( + 'Hello ' + + firstNameInput.value + ' ' + + lastNameInput.value + '!' + ); +} + +function hide(el) { + el.style.display = 'none'; +} + +function show(el) { + el.style.display = ''; +} + +let form = document.getElementById('form'); +let editButton = document.getElementById('editButton'); +let firstNameInput = document.getElementById('firstNameInput'); +let firstNameText = document.getElementById('firstNameText'); +let lastNameInput = document.getElementById('lastNameInput'); +let lastNameText = document.getElementById('lastNameText'); +let helloText = document.getElementById('helloText'); +form.onsubmit = handleFormSubmit; +firstNameInput.oninput = handleFirstNameChange; +lastNameInput.oninput = handleLastNameChange; +``` + +```js sandbox.config.json hidden +{ + "hardReloadOnChange": true +} +``` + +```html public/index.html +<form id="form"> + <label> + First name: + <b id="firstNameText">Jane</b> + <input + id="firstNameInput" + value="Jane" + style="display: none"> + </label> + <label> + Last name: + <b id="lastNameText">Jacobs</b> + <input + id="lastNameInput" + value="Jacobs" + style="display: none"> + </label> + <button type="submit" id="editButton">Edit Profile</button> + <p><i id="helloText">Hello, Jane Jacobs!</i></p> +</form> + +<style> +* { box-sizing: border-box; } +body { font-family: sans-serif; margin: 20px; padding: 0; } +label { display: block; margin-bottom: 20px; } +</style> +``` + +</Sandpack> + +This form switches between two modes: in the editing mode, you see the inputs, and in the viewing mode, you only see the result. The button label changes between "Edit" and "Save" depending on the mode you're in. When you change the inputs, the welcome message at the bottom updates in real time. + +Your task is to reimplement it in React in the sandbox below. For your convenience, the markup was already converted to JSX, but you'll need to make it show and hide the inputs like the original does. + +Make sure that it updates the text at the bottom, too! + +<Sandpack> + +```js +export default function EditProfile() { + return ( + <form> + <label> + First name:{' '} + <b>Jane</b> + <input /> + </label> + <label> + Last name:{' '} + <b>Jacobs</b> + <input /> + </label> + <button type="submit"> + Edit Profile + </button> + <p><i>Hello, Jane Jacobs!</i></p> + </form> + ); +} +``` + +```css +label { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +<Solution> + +You will need two state variables to hold the input values: `firstName` and `lastName`. You're also going to need an `isEditing` state variable that holds whether to display the inputs or not. You should _not_ need a `fullName` variable because the full name can always be calculated from the `firstName` and the `lastName`. + +Finally, you should use [conditional rendering](/learn/conditional-rendering) to show or hide the inputs depending on `isEditing`. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function EditProfile() { + const [isEditing, setIsEditing] = useState(false); + const [firstName, setFirstName] = useState('Jane'); + const [lastName, setLastName] = useState('Jacobs'); + + return ( + <form onSubmit={e => { + e.preventDefault(); + setIsEditing(!isEditing); + }}> + <label> + First name:{' '} + {isEditing ? ( + <input + value={firstName} + onChange={e => { + setFirstName(e.target.value) + }} + /> + ) : ( + <b>{firstName}</b> + )} + </label> + <label> + Last name:{' '} + {isEditing ? ( + <input + value={lastName} + onChange={e => { + setLastName(e.target.value) + }} + /> + ) : ( + <b>{lastName}</b> + )} + </label> + <button type="submit"> + {isEditing ? 'Save' : 'Edit'} Profile + </button> + <p><i>Hello, {firstName} {lastName}!</i></p> + </form> + ); +} +``` + +```css +label { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +Compare this solution to the original imperative code. How are they different? + +</Solution> + +#### Refactor the imperative solution without React {/*refactor-the-imperative-solution-without-react*/} + +Here is the original sandbox from the previous challenge, written imperatively without React: + +<Sandpack> + +```js index.js active +function handleFormSubmit(e) { + e.preventDefault(); + if (editButton.textContent === 'Edit Profile') { + editButton.textContent = 'Save Profile'; + hide(firstNameText); + hide(lastNameText); + show(firstNameInput); + show(lastNameInput); + } else { + editButton.textContent = 'Edit Profile'; + hide(firstNameInput); + hide(lastNameInput); + show(firstNameText); + show(lastNameText); + } +} + +function handleFirstNameChange() { + firstNameText.textContent = firstNameInput.value; + helloText.textContent = ( + 'Hello ' + + firstNameInput.value + ' ' + + lastNameInput.value + '!' + ); +} + +function handleLastNameChange() { + lastNameText.textContent = lastNameInput.value; + helloText.textContent = ( + 'Hello ' + + firstNameInput.value + ' ' + + lastNameInput.value + '!' + ); +} + +function hide(el) { + el.style.display = 'none'; +} + +function show(el) { + el.style.display = ''; +} + +let form = document.getElementById('form'); +let editButton = document.getElementById('editButton'); +let firstNameInput = document.getElementById('firstNameInput'); +let firstNameText = document.getElementById('firstNameText'); +let lastNameInput = document.getElementById('lastNameInput'); +let lastNameText = document.getElementById('lastNameText'); +let helloText = document.getElementById('helloText'); +form.onsubmit = handleFormSubmit; +firstNameInput.oninput = handleFirstNameChange; +lastNameInput.oninput = handleLastNameChange; +``` + +```js sandbox.config.json hidden +{ + "hardReloadOnChange": true +} +``` + +```html public/index.html +<form id="form"> + <label> + First name: + <b id="firstNameText">Jane</b> + <input + id="firstNameInput" + value="Jane" + style="display: none"> + </label> + <label> + Last name: + <b id="lastNameText">Jacobs</b> + <input + id="lastNameInput" + value="Jacobs" + style="display: none"> + </label> + <button type="submit" id="editButton">Edit Profile</button> + <p><i id="helloText">Hello, Jane Jacobs!</i></p> +</form> + +<style> +* { box-sizing: border-box; } +body { font-family: sans-serif; margin: 20px; padding: 0; } +label { display: block; margin-bottom: 20px; } +</style> +``` + +</Sandpack> + +Imagine React didn't exist. Can you refactor this code in a way that makes the logic less fragile and more similar to the React version? What would it look like if the state was explicit, like in React? + +If you're struggling to think where to start, the stub below already has most of the structure in place. If you start here, fill in the missing logic in the `updateDOM` function. (Refer to the original code where needed.) + +<Sandpack> + +```js index.js active +let firstName = 'Jane'; +let lastName = 'Jacobs'; +let isEditing = false; + +function handleFormSubmit(e) { + e.preventDefault(); + setIsEditing(!isEditing); +} + +function handleFirstNameChange(e) { + setFirstName(e.target.value); +} + +function handleLastNameChange(e) { + setLastName(e.target.value); +} + +function setFirstName(value) { + firstName = value; + updateDOM(); +} + +function setLastName(value) { + lastName = value; + updateDOM(); +} + +function setIsEditing(value) { + isEditing = value; + updateDOM(); +} + +function updateDOM() { + if (isEditing) { + editButton.textContent = 'Save Profile'; + // TODO: show inputs, hide content + } else { + editButton.textContent = 'Edit Profile'; + // TODO: hide inputs, show content + } + // TODO: update text labels +} + +function hide(el) { + el.style.display = 'none'; +} + +function show(el) { + el.style.display = ''; +} + +let form = document.getElementById('form'); +let editButton = document.getElementById('editButton'); +let firstNameInput = document.getElementById('firstNameInput'); +let firstNameText = document.getElementById('firstNameText'); +let lastNameInput = document.getElementById('lastNameInput'); +let lastNameText = document.getElementById('lastNameText'); +let helloText = document.getElementById('helloText'); +form.onsubmit = handleFormSubmit; +firstNameInput.oninput = handleFirstNameChange; +lastNameInput.oninput = handleLastNameChange; +``` + +```js sandbox.config.json hidden +{ + "hardReloadOnChange": true +} +``` + +```html public/index.html +<form id="form"> + <label> + First name: + <b id="firstNameText">Jane</b> + <input + id="firstNameInput" + value="Jane" + style="display: none"> + </label> + <label> + Last name: + <b id="lastNameText">Jacobs</b> + <input + id="lastNameInput" + value="Jacobs" + style="display: none"> + </label> + <button type="submit" id="editButton">Edit Profile</button> + <p><i id="helloText">Hello, Jane Jacobs!</i></p> +</form> + +<style> +* { box-sizing: border-box; } +body { font-family: sans-serif; margin: 20px; padding: 0; } +label { display: block; margin-bottom: 20px; } +</style> +``` + +</Sandpack> + +<Solution> + +The missing logic included toggling the display of inputs and content, and updating the labels: + +<Sandpack> + +```js index.js active +let firstName = 'Jane'; +let lastName = 'Jacobs'; +let isEditing = false; + +function handleFormSubmit(e) { + e.preventDefault(); + setIsEditing(!isEditing); +} + +function handleFirstNameChange(e) { + setFirstName(e.target.value); +} + +function handleLastNameChange(e) { + setLastName(e.target.value); +} + +function setFirstName(value) { + firstName = value; + updateDOM(); +} + +function setLastName(value) { + lastName = value; + updateDOM(); +} + +function setIsEditing(value) { + isEditing = value; + updateDOM(); +} + +function updateDOM() { + if (isEditing) { + editButton.textContent = 'Save Profile'; + hide(firstNameText); + hide(lastNameText); + show(firstNameInput); + show(lastNameInput); + } else { + editButton.textContent = 'Edit Profile'; + hide(firstNameInput); + hide(lastNameInput); + show(firstNameText); + show(lastNameText); + } + firstNameText.textContent = firstName; + lastNameText.textContent = lastName; + helloText.textContent = ( + 'Hello ' + + firstName + ' ' + + lastName + '!' + ); +} + +function hide(el) { + el.style.display = 'none'; +} + +function show(el) { + el.style.display = ''; +} + +let form = document.getElementById('form'); +let editButton = document.getElementById('editButton'); +let firstNameInput = document.getElementById('firstNameInput'); +let firstNameText = document.getElementById('firstNameText'); +let lastNameInput = document.getElementById('lastNameInput'); +let lastNameText = document.getElementById('lastNameText'); +let helloText = document.getElementById('helloText'); +form.onsubmit = handleFormSubmit; +firstNameInput.oninput = handleFirstNameChange; +lastNameInput.oninput = handleLastNameChange; +``` + +```js sandbox.config.json hidden +{ + "hardReloadOnChange": true +} +``` + +```html public/index.html +<form id="form"> + <label> + First name: + <b id="firstNameText">Jane</b> + <input + id="firstNameInput" + value="Jane" + style="display: none"> + </label> + <label> + Last name: + <b id="lastNameText">Jacobs</b> + <input + id="lastNameInput" + value="Jacobs" + style="display: none"> + </label> + <button type="submit" id="editButton">Edit Profile</button> + <p><i id="helloText">Hello, Jane Jacobs!</i></p> +</form> + +<style> +* { box-sizing: border-box; } +body { font-family: sans-serif; margin: 20px; padding: 0; } +label { display: block; margin-bottom: 20px; } +</style> +``` + +</Sandpack> + +The `updateDOM` function you wrote shows what React does under the hood when you set the state. (However, React also avoids touching the DOM for properties that have not changed since the last time they were set.) + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/referencing-values-with-refs.md b/beta/src/content/learn/referencing-values-with-refs.md new file mode 100644 index 000000000..f395ab879 --- /dev/null +++ b/beta/src/content/learn/referencing-values-with-refs.md @@ -0,0 +1,662 @@ +--- +title: 'Referencing Values with Refs' +--- + +<Intro> + +When you want a component to "remember" some information, but you don't want that information to [trigger new renders](/learn/render-and-commit), you can use a *ref*. + +</Intro> + +<YouWillLearn> + +- How to add a ref to your component +- How to update a ref's value +- How refs are different from state +- How to use refs safely + +</YouWillLearn> + +## Adding a ref to your component {/*adding-a-ref-to-your-component*/} + +You can add a ref to your component by importing the `useRef` Hook from React: + +```js +import { useRef } from 'react'; +``` + +Inside your component, call the `useRef` Hook and pass the initial value that you want to reference as the only argument. For example, here is a ref to the value `0`: + +```js +const ref = useRef(0); +``` + +`useRef` returns an object like this: + +```js +{ + current: 0 // The value you passed to useRef +} +``` + +<Illustration src="/images/docs/illustrations/i_ref.png" alt="An arrow with 'current' written on it stuffed into a pocket with 'ref' written on it." /> + +You can access the current value of that ref through the `ref.current` property. This value is intentionally mutable, meaning you can both read and write to it. It's like a secret pocket of your component that React doesn't track. (This is what makes it an "escape hatch" from React's one-way data flow--more on that below!) + +Here, a button will increment `ref.current` on every click: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Counter() { + let ref = useRef(0); + + function handleClick() { + ref.current = ref.current + 1; + alert('You clicked ' + ref.current + ' times!'); + } + + return ( + <button onClick={handleClick}> + Click me! + </button> + ); +} +``` + +</Sandpack> + +The ref points to a number, but, like [state](/learn/state-a-components-memory), you could point to anything: a string, an object, or even a function. Unlike state, ref is a plain JavaScript object with the `current` property that you can read and modify. + +Note that **the component doesn't re-render with every increment.** Like state, refs are retained by React between re-renders. However, setting state re-renders a component. Changing a ref does not! + +## Example: building a stopwatch {/*example-building-a-stopwatch*/} + +You can combine refs and state in a single component. For example, let's make a stopwatch that the user can start or stop by pressing a button. In order to display how much time has passed since the user pressed "Start", you will need to keep track of when the Start button was pressed and what the current time is. **This information is used for rendering, so you'll keep it in state:** + +```js +const [startTime, setStartTime] = useState(null); +const [now, setNow] = useState(null); +``` + +When the user presses "Start", you'll use [`setInterval`](https://developer.mozilla.org/docs/Web/API/setInterval) in order to update the time every 10 milliseconds: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Stopwatch() { + const [startTime, setStartTime] = useState(null); + const [now, setNow] = useState(null); + + function handleStart() { + // Start counting. + setStartTime(Date.now()); + setNow(Date.now()); + + setInterval(() => { + // Update the current time every 10ms. + setNow(Date.now()); + }, 10); + } + + let secondsPassed = 0; + if (startTime != null && now != null) { + secondsPassed = (now - startTime) / 1000; + } + + return ( + <> + <h1>Time passed: {secondsPassed.toFixed(3)}</h1> + <button onClick={handleStart}> + Start + </button> + </> + ); +} +``` + +</Sandpack> + +When the "Stop" button is pressed, you need to cancel the existing interval so that it stops updating the `now` state variable. You can do this by calling [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval), but you need to give it the interval ID that was previously returned by the `setInterval` call when the user pressed Start. You need to keep the interval ID somewhere. **Since the interval ID is not used for rendering, you can keep it in a ref:** + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function Stopwatch() { + const [startTime, setStartTime] = useState(null); + const [now, setNow] = useState(null); + const intervalRef = useRef(null); + + function handleStart() { + setStartTime(Date.now()); + setNow(Date.now()); + + clearInterval(intervalRef.current); + intervalRef.current = setInterval(() => { + setNow(Date.now()); + }, 10); + } + + function handleStop() { + clearInterval(intervalRef.current); + } + + let secondsPassed = 0; + if (startTime != null && now != null) { + secondsPassed = (now - startTime) / 1000; + } + + return ( + <> + <h1>Time passed: {secondsPassed.toFixed(3)}</h1> + <button onClick={handleStart}> + Start + </button> + <button onClick={handleStop}> + Stop + </button> + </> + ); +} +``` + +</Sandpack> + +When a piece of information is used for rendering, keep it in state. When a piece of information is only needed by event handlers and changing it doesn't require a re-render, using a ref may be more efficient. + +## Differences between refs and state {/*differences-between-refs-and-state*/} + +Perhaps you're thinking refs seem less "strict" than state—you can mutate them instead of always having to use a state setting function, for instance. But in most cases, you'll want to use state. Refs are an "escape hatch" you won't need often. Here's how state and refs compare: + +| refs | state | +| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `useRef(initialValue)` returns `{ current: initialValue }` | `useState(initialValue)` returns the current value of a state variable and a state setter function ( `[value, setValue]`) | +| Doesn't trigger re-render when you change it. | Triggers re-render when you change it. | +| Mutable—you can modify and update `current`'s value outside of the rendering process. | "Immutable"—you must use the state setting function to modify state variables to queue a re-render. | +| You shouldn't read (or write) the `current` value during rendering. | You can read state at any time. However, each render has its own [snapshot](/learn/state-as-a-snapshot) of state which does not change. + +Here is a counter button that's implemented with state: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ( + <button onClick={handleClick}> + You clicked {count} times + </button> + ); +} +``` + +</Sandpack> + +Because the `count` value is displayed, it makes sense to use a state value for it. When the counter's value is set with `setCount()`, React re-renders the component and the screen updates to reflect the new count. + +If you tried to implement this with a ref, React would never re-render the component, so you'd never see the count change! See how clicking this button **does not update its text**: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Counter() { + let countRef = useRef(0); + + function handleClick() { + // This doesn't re-render the component! + countRef.current = countRef.current + 1; + } + + return ( + <button onClick={handleClick}> + You clicked {countRef.current} times + </button> + ); +} +``` + +</Sandpack> + +This is why reading `ref.current` during render leads to unreliable code. If you need that, use state instead. + +<DeepDive> + +#### How does useRef work inside? {/*how-does-use-ref-work-inside*/} + +Although both `useState` and `useRef` are provided by React, in principle `useRef` could be implemented _on top of_ `useState`. You can imagine that inside of React, `useRef` is implemented like this: + +```js +// Inside of React +function useRef(initialValue) { + const [ref, unused] = useState({ current: initialValue }); + return ref; +} +``` + +During the first render, `useRef` returns `{ current: initialValue }`. This object is stored by React, so during the next render the same object will be returned. Note how the state setter is unused in this example. It is unnecessary because `useRef` always needs to return the same object! + +React provides a built-in version of `useRef` because it is common enough in practice. But you can think of it as a regular state variable without a setter. If you're familiar with object-oriented programming, refs might remind you of instance fields--but instead of `this.something` you write `somethingRef.current`. + +</DeepDive> + +## When to use refs {/*when-to-use-refs*/} + +Typically, you will use a ref when your component needs to "step outside" React and communicate with external APIs—often a browser API that won't impact the appearance of the component. Here are a few of these rare situations: + +- Storing [timeout IDs](https://developer.mozilla.org/docs/Web/API/setTimeout) +- Storing and manipulating [DOM elements](https://developer.mozilla.org/docs/Web/API/Element), which we cover on [the next page](/learn/manipulating-the-dom-with-refs) +- Storing other objects that aren't necessary to calculate the JSX. + +If your component needs to store some value, but it doesn't impact the rendering logic, choose refs. + +## Best practices for refs {/*best-practices-for-refs*/} + +Following these principles will make your components more predictable: + +- **Treat refs as an escape hatch.** Refs are useful when you work with external systems or browser APIs. If much of your application logic and data flow relies on refs, you might want to rethink your approach. +- **Don't read or write `ref.current` during rendering.** If some information is needed during rendering, use [state](/learn/state-a-components-memory) instead. Since React doesn't know when `ref.current` changes, even reading it while rendering makes your component's behavior difficult to predict. (The only exception to this is code like `if (!ref.current) ref.current = new Thing()` which only sets the ref once during the first render.) + +Limitations of React state don't apply to refs. For example, state acts like a [snapshot for every render](/learn/state-as-a-snapshot) and [doesn't update synchronously.](/learn/queueing-a-series-of-state-updates) But when you mutate the current value of a ref, it changes immediately: + +```js +ref.current = 5; +console.log(ref.current); // 5 +``` + +This is because **the ref itself is a regular JavaScript object,** and so it behaves like one. + +You also don't need to worry about [avoiding mutation](/learn/updating-objects-in-state) when you work with a ref. As long as the object you're mutating isn't used for rendering, React doesn't care what you do with the ref or its contents. + +## Refs and the DOM {/*refs-and-the-dom*/} + +You can point a ref to any value. However, the most common use case for a ref is to access a DOM element. For example, this is handy if you want to focus an input programmatically. When you pass a ref to a `ref` attribute in JSX, like `<div ref={myRef}>`, React will put the corresponding DOM element into `myRef.current`. You can read more about this in [Manipulating the DOM with Refs.](/learn/manipulating-the-dom-with-refs) + +<Recap> + +- Refs are an escape hatch to hold onto values that aren't used for rendering. You won't need them often. +- A ref is a plain JavaScript object with a single property called `current`, which you can read or set. +- You can ask React to give you a ref by calling the `useRef` Hook. +- Like state, refs let you retain information between re-renders of a component. +- Unlike state, setting the ref's `current` value does not trigger a re-render. +- Don't read or write `ref.current` during rendering. This makes your component hard to predict. + +</Recap> + + + +<Challenges> + +#### Fix a broken chat input {/*fix-a-broken-chat-input*/} + +Type a message and click "Send". You will notice there is a three second delay before you see the "Sent!" alert. During this delay, you can see an "Undo" button. Click it. This "Undo" button is supposed to stop the "Sent!" message from appearing. It does this by calling [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) for the timeout ID saved during `handleSend`. However, even after "Undo" is clicked, the "Sent!" message still appears. Find why it doesn't work, and fix it. + +<Hint> + +Regular variables like `let timeoutID` don't "survive" between re-renders because every render runs your component (and initializes its variables) from scratch. Should you keep the timeout ID somewhere else? + +</Hint> + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Chat() { + const [text, setText] = useState(''); + const [isSending, setIsSending] = useState(false); + let timeoutID = null; + + function handleSend() { + setIsSending(true); + timeoutID = setTimeout(() => { + alert('Sent!'); + setIsSending(false); + }, 3000); + } + + function handleUndo() { + setIsSending(false); + clearTimeout(timeoutID); + } + + return ( + <> + <input + disabled={isSending} + value={text} + onChange={e => setText(e.target.value)} + /> + <button + disabled={isSending} + onClick={handleSend}> + {isSending ? 'Sending...' : 'Send'} + </button> + {isSending && + <button onClick={handleUndo}> + Undo + </button> + } + </> + ); +} +``` + +</Sandpack> + +<Solution> + +Whenever your component re-renders (such as when you set state), all local variables get initialized from scratch. This is why you can't save the timeout ID in a local variable like `timeoutID` and then expect another event handler to "see" it in the future. Instead, store it in a ref, which React will preserve between renders. + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function Chat() { + const [text, setText] = useState(''); + const [isSending, setIsSending] = useState(false); + const timeoutRef = useRef(null); + + function handleSend() { + setIsSending(true); + timeoutRef.current = setTimeout(() => { + alert('Sent!'); + setIsSending(false); + }, 3000); + } + + function handleUndo() { + setIsSending(false); + clearTimeout(timeoutRef.current); + } + + return ( + <> + <input + disabled={isSending} + value={text} + onChange={e => setText(e.target.value)} + /> + <button + disabled={isSending} + onClick={handleSend}> + {isSending ? 'Sending...' : 'Send'} + </button> + {isSending && + <button onClick={handleUndo}> + Undo + </button> + } + </> + ); +} +``` + +</Sandpack> + +</Solution> + + +#### Fix a component failing to re-render {/*fix-a-component-failing-to-re-render*/} + +This button is supposed to toggle between showing "On" and "Off". However, it always shows "Off". What is wrong with this code? Fix it. + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Toggle() { + const isOnRef = useRef(false); + + return ( + <button onClick={() => { + isOnRef.current = !isOnRef.current; + }}> + {isOnRef.current ? 'On' : 'Off'} + </button> + ); +} +``` + +</Sandpack> + +<Solution> + +In this example, the current value of a ref is used to calculate the rendering output: `{isOnRef.current ? 'On' : 'Off'}`. This is a sign that this information should not be in a ref, and should have instead been put in state. To fix it, remove the ref and use state instead: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Toggle() { + const [isOn, setIsOn] = useState(false); + + return ( + <button onClick={() => { + setIsOn(!isOn); + }}> + {isOn ? 'On' : 'Off'} + </button> + ); +} +``` + +</Sandpack> + +</Solution> + +#### Fix debouncing {/*fix-debouncing*/} + +In this example, all button click handlers are ["debounced".](https://redd.one/blog/debounce-vs-throttle) To see what this means, press one of the buttons. Notice how the message appears a second later. If you press the button while waiting for the message, the timer will reset. So if you keep clicking the same button fast many times, the message won't appear until a second *after* you stop clicking. Debouncing lets you delay some action until the user "stops doing things". + +This example works, but not quite as intended. The buttons are not independent. To see the problem, click one of the buttons, and then immediately click another button. You'd expect that after a delay, you would see both button's messages. But only the last button's message shows up. The first button's message gets lost. + +Why are the buttons interfering with each other? Find and fix the issue. + +<Hint> + +The last timeout ID variable is shared between all `DebouncedButton` components. This is why clicking one button resets another button's timeout. Can you store a separate timeout ID for each button? + +</Hint> + +<Sandpack> + +```js +import { useState } from 'react'; + +let timeoutID; + +function DebouncedButton({ onClick, children }) { + return ( + <button onClick={() => { + clearTimeout(timeoutID); + timeoutID = setTimeout(() => { + onClick(); + }, 1000); + }}> + {children} + </button> + ); +} + +export default function Dashboard() { + return ( + <> + <DebouncedButton + onClick={() => alert('Spaceship launched!')} + > + Launch the spaceship + </DebouncedButton> + <DebouncedButton + onClick={() => alert('Soup boiled!')} + > + Boil the soup + </DebouncedButton> + <DebouncedButton + onClick={() => alert('Lullaby sung!')} + > + Sing a lullaby + </DebouncedButton> + </> + ) +} +``` + +```css +button { display: block; margin: 10px; } +``` + +</Sandpack> + +<Solution> + +A variable like `timeoutID` is shared between all components. This is why clicking on the second button resets the first button's pending timeout. To fix this, you can keep timeout in a ref. Each button will get its own ref, so they won't conflict with each other. Notice how clicking two buttons fast will show both messages. + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +function DebouncedButton({ onClick, children }) { + const timeoutRef = useRef(null); + return ( + <button onClick={() => { + clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + onClick(); + }, 1000); + }}> + {children} + </button> + ); +} + +export default function Dashboard() { + return ( + <> + <DebouncedButton + onClick={() => alert('Spaceship launched!')} + > + Launch the spaceship + </DebouncedButton> + <DebouncedButton + onClick={() => alert('Soup boiled!')} + > + Boil the soup + </DebouncedButton> + <DebouncedButton + onClick={() => alert('Lullaby sung!')} + > + Sing a lullaby + </DebouncedButton> + </> + ) +} +``` + +```css +button { display: block; margin: 10px; } +``` + +</Sandpack> + +</Solution> + +#### Read the latest state {/*read-the-latest-state*/} + +In this example, after you press "Send", there is a small delay before the message is shown. Type "hello", press Send, and then quickly edit the input again. Despite your edits, the alert would still show "hello" (which was the value of state [at the time](/learn/state-as-a-snapshot#state-over-time) the button was clicked). + +Usually, this behavior is what you want in an app. However, there may be occasional cases where you want some asynchronous code to read the *latest* version of some state. Can you think of a way to make the alert show the *current* input text rather than what it was at the time of the click? + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function Chat() { + const [text, setText] = useState(''); + + function handleSend() { + setTimeout(() => { + alert('Sending: ' + text); + }, 3000); + } + + return ( + <> + <input + value={text} + onChange={e => setText(e.target.value)} + /> + <button + onClick={handleSend}> + Send + </button> + </> + ); +} +``` + +</Sandpack> + +<Solution> + +State works [like a snapshot](/learn/state-as-a-snapshot), so you can't read the latest state from an asynchronous operation like a timeout. However, you can keep the latest input text in a ref. A ref is mutable, so you can read the `current` property at any time. Since the current text is also used for rendering, in this example, you will need *both* a state variable (for rendering), *and* a ref (to read it in the timeout). You will need to update the current ref value manually. + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function Chat() { + const [text, setText] = useState(''); + const textRef = useRef(text); + + function handleChange(e) { + setText(e.target.value); + textRef.current = e.target.value; + } + + function handleSend() { + setTimeout(() => { + alert('Sending: ' + textRef.current); + }, 3000); + } + + return ( + <> + <input + value={text} + onChange={handleChange} + /> + <button + onClick={handleSend}> + Send + </button> + </> + ); +} +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/removing-effect-dependencies.md b/beta/src/content/learn/removing-effect-dependencies.md new file mode 100644 index 000000000..e824a5bee --- /dev/null +++ b/beta/src/content/learn/removing-effect-dependencies.md @@ -0,0 +1,2330 @@ +--- +title: 'Removing Effect Dependencies' +--- + +<Intro> + +When you write an Effect, the linter will verify that you've included every reactive value (like props and state) that the Effect reads in the list of your Effect's dependencies. This ensures that your Effect remains synchronized with the latest props and state of your component. Unnecessary dependencies may cause your Effect to run too often, or even create an infinite loop. Follow this guide to review and remove unnecessary dependencies from your Effects. + +</Intro> + +<YouWillLearn> + +- How to fix infinite Effect dependency loops +- What to do when you want to remove a dependency +- How to read a value from your Effect without "reacting" to it +- How and why to avoid object and function dependencies +- Why suppressing the dependency linter is dangerous, and what to do instead + +</YouWillLearn> + +## Dependencies should match the code {/*dependencies-should-match-the-code*/} + +When you write an Effect, you first specify how to [start and stop](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) whatever you want your Effect to be doing: + +```js {5-7} +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + // ... +} +``` + +Then, if you leave the Effect dependencies empty (`[]`), the linter will suggest the correct dependencies: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); // <-- Fix the mistake here! + return <h1>Welcome to the {roomId} room!</h1>; +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Fill them in according to what the linter says: + +```js {6} +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +} +``` + +[Effects "react" to reactive values.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Since `roomId` is a reactive value (it can change due to a re-render), the linter verifies that you've specified it as a dependency. If `roomId` receives a different value, React will re-synchronize your Effect. This ensures that the chat stays connected to the selected room and "reacts" to the dropdown: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + return <h1>Welcome to the {roomId} room!</h1>; +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +### To remove a dependency, prove that it's not a dependency {/*to-remove-a-dependency-prove-that-its-not-a-dependency*/} + +Notice that you can't "choose" the dependencies of your Effect. Every <CodeStep step={2}>reactive value</CodeStep> used by your Effect's code must be declared in your dependency list. Your Effect's dependency list is determined by the surrounding code: + +```js [[2, 3, "roomId"], [2, 5, "roomId"], [2, 8, "roomId"]] +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { // This is a reactive value + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // This Effect reads that reactive value + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ So you must specify that reactive value as a dependency of your Effect + // ... +} +``` + +[Reactive values](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) include props and all variables and functions declared directly inside of your component. Since `roomId` is a reactive value, you can't remove it from the dependency list. The linter wouldn't allow it: + +```js {8} +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); // 🔴 React Hook useEffect has a missing dependency: 'roomId' + // ... +} +``` + +And the linter would be right! Since `roomId` may change over time, this would introduce a bug in your code. + +**To remove a dependency, you need to "prove" to the linter that it *doesn't need* to be a dependency.** For example, you can move `roomId` out of your component to prove that it's not reactive and won't change on re-renders: + +```js {2,9} +const serverUrl = 'https://localhost:1234'; +const roomId = 'music'; // Not a reactive value anymore + +function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); // ✅ All dependencies declared + // ... +} +``` + +Now that `roomId` is not a reactive value (and can't change on a re-render), it doesn't need to be a dependency: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; +const roomId = 'music'; + +export default function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +This is why you could now specify an [empty (`[]`) dependency list.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Your Effect *really doesn't* depend on any reactive value anymore, so it *really doesn't* need to re-run when any of the component's props or state change. + +### To change the dependencies, change the code {/*to-change-the-dependencies-change-the-code*/} + +You might have noticed a pattern in your workflow: + +1. First, you **change the code** of your Effect or how your reactive values are declared. +2. Then, you follow the linter and adjust the dependencies to **match the code you have changed.** +3. If you're not happy with the list of dependencies, you **go back to the first step** (and change the code again). + +The last part is important. **If you want to change the dependencies, change the surrounding code first.** You can think of the dependency list as [a list of all the reactive values used by your Effect's code.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) You don't intentionally *choose* what to put on that list. The list *describes* your code. To change the dependency list, change the code. + +This might feel like solving an equation. You might start with a goal (for example, to remove a dependency), and you need to "find" the exact code matching that goal. Not everyone finds solving equations fun, and the same thing could be said about writing Effects! Luckily, there is a list of common recipes that you can try below. + +<Pitfall> + +If you have an existing codebase, you might have some Effects that suppress the linter like this: + +```js {3-4} +useEffect(() => { + // ... + // 🔴 Avoid suppressing the linter like this: + // eslint-ignore-next-line react-hooks/exhaustive-deps +}, []); +``` + +**When dependencies don't match the code, there is a very high risk of introducing bugs.** By suppressing the linter, you "lie" to React about the values your Effect depends on. Instead, use the techniques below. + +</Pitfall> + +<DeepDive> + +#### Why is suppressing the dependency linter so dangerous? {/*why-is-suppressing-the-dependency-linter-so-dangerous*/} + +Suppressing the linter leads to very unintuitive bugs that are hard to find and fix. Here's one example: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + const [increment, setIncrement] = useState(1); + + function onTick() { + setCount(count + increment); + } + + useEffect(() => { + const id = setInterval(onTick, 1000); + return () => clearInterval(id); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + <h1> + Counter: {count} + <button onClick={() => setCount(0)}>Reset</button> + </h1> + <hr /> + <p> + Every second, increment by: + <button disabled={increment === 0} onClick={() => { + setIncrement(i => i - 1); + }}>–</button> + <b>{increment}</b> + <button onClick={() => { + setIncrement(i => i + 1); + }}>+</button> + </p> + </> + ); +} +``` + +```css +button { margin: 10px; } +``` + +</Sandpack> + +Let's say that you wanted to run the Effect "only on mount". You've read that [empty (`[]`) dependencies](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) do that, so you've decided to ignore the linter, and forcefully specified `[]` as the dependencies. + +This counter was supposed to increment every second by the amount configurable with the two buttons. However, since you "lied" to React that this Effect doesn't depend on anything, React forever keeps using the `onTick` function from the initial render. [During that render,](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` was `0` and `increment` was `1`. This is why `onTick` from that render always calls `setCount(0 + 1)` every second, and you always see `1`. Bugs like this are harder to fix when they're spread across multiple components. + +There's always a better solution than ignoring the linter! To fix this code, you need to add `onTick` to the dependency list. (To ensure the interval is only setup once, [make `onTick` an Effect Event.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)) + +**We recommend to treat the dependency lint error as a compilation error. If you don't suppress it, you will never see bugs like this.** The rest of this page documents the alternatives for this and other cases. + +</DeepDive> + +## Removing unnecessary dependencies {/*removing-unnecessary-dependencies*/} + +Every time you adjust the Effect's dependencies to reflect the code, look at the dependency list. Does it make sense for the Effect to re-run when any of these dependencies change? Sometimes, the answer is "no": + +* Sometimes, you want to re-execute *different parts* of your Effect under different conditions. +* Sometimes, you want to only read the *latest value* of some dependency instead of "reacting" to its changes. +* Sometimes, a dependency may change too often *unintentionally* because it's an object or a function. + +To find the right solution, you'll need to answer a few questions about your Effect. Let's walk through them. + +### Should this code move to an event handler? {/*should-this-code-move-to-an-event-handler*/} + +The first thing you should think about is whether this code should be an Effect at all. + +Imagine a form. On submit, you set the `submitted` state variable to `true`. You need to send a POST request and show a notification. You've decided to put this logic inside an Effect that "reacts" to `submitted` being `true`: + +```js {6-8} +function Form() { + const [submitted, setSubmitted] = useState(false); + + useEffect(() => { + if (submitted) { + // 🔴 Avoid: Event-specific logic inside an Effect + post('/api/register'); + showNotification('Successfully registered!'); + } + }, [submitted]); + + function handleSubmit() { + setSubmitted(true); + } + + // ... +} +``` + +Later, you want to style the notification message according to the current theme, so you read the current theme. Since `theme` is declared in the component body, it is a reactive value, and you must declare it as a dependency: + +```js {3,9,11} +function Form() { + const [submitted, setSubmitted] = useState(false); + const theme = useContext(ThemeContext); + + useEffect(() => { + if (submitted) { + // 🔴 Avoid: Event-specific logic inside an Effect + post('/api/register'); + showNotification('Successfully registered!', theme); + } + }, [submitted, theme]); // ✅ All dependencies declared + + function handleSubmit() { + setSubmitted(true); + } + + // ... +} +``` + +But by doing this, you've introduced a bug. Imagine you submit the form first and then switch between Dark and Light themes. The `theme` will change, the Effect will re-run, and so it will display the same notification again! + +**The problem here is that this shouldn't be an Effect in the first place.** You want to send this POST request and show the notification in response to *submitting the form,* which is a particular interaction. When you want to run some code in response to particular interaction, put that logic directly into the corresponding event handler: + +```js {6-7} +function Form() { + const theme = useContext(ThemeContext); + + function handleSubmit() { + // ✅ Good: Event-specific logic is called from event handlers + post('/api/register'); + showNotification('Successfully registered!', theme); + } + + // ... +} +``` + +Now that the code is in an event handler, it's not reactive--so it will only run when the user submits the form. Read more about [choosing between event handlers and Effects](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) and [how to delete unnecessary Effects.](/learn/you-might-not-need-an-effect) + +### Is your Effect doing several unrelated things? {/*is-your-effect-doing-several-unrelated-things*/} + +The next question you should ask yourself is whether your Effect is doing several unrelated things. + +Imagine you're creating a shipping form where the user needs to choose their city and area. You fetch the list of `cities` from the server according to the selected `country` so that you can show them as dropdown options: + +```js +function ShippingForm({ country }) { + const [cities, setCities] = useState(null); + const [city, setCity] = useState(null); + + useEffect(() => { + let ignore = false; + fetch(`/api/cities?country=${country}`) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setCities(json); + } + }); + return () => { + ignore = true; + }; + }, [country]); // ✅ All dependencies declared + + // ... +``` + +This is a good example of [fetching data in an Effect.](/learn/you-might-not-need-an-effect#fetching-data) You are synchronizing the `cities` state with the network according to the `country` prop. You can't do this in an event handler because you need to fetch as soon as `ShippingForm` is displayed and whenever the `country` changes (no matter which interaction causes it). + +Now let's say you're adding a second select box for city areas, which should fetch the `areas` for the currently selected `city`. You might start by adding a second `fetch` call for the list of areas inside the same Effect: + +```js {15-24,28} +function ShippingForm({ country }) { + const [cities, setCities] = useState(null); + const [city, setCity] = useState(null); + const [areas, setAreas] = useState(null); + + useEffect(() => { + let ignore = false; + fetch(`/api/cities?country=${country}`) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setCities(json); + } + }); + // 🔴 Avoid: A single Effect synchronizes two independent processes + if (city) { + fetch(`/api/areas?city=${city}`) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setAreas(json); + } + }); + } + return () => { + ignore = true; + }; + }, [country, city]); // ✅ All dependencies declared + + // ... +``` + +However, since the Effect now uses the `city` state variable, you've had to add `city` to the list of dependencies. That, in turn, has introduced a problem. Now, whenever the user selects a different city, the Effect will re-run and call `fetchCities(country)`. As a result, you will be unnecessarily refetching the list of cities many times. + +**The problem with this code is that you're synchronizing two different unrelated things:** + +1. You want to synchronize the `cities` state to the network based on the `country` prop. +1. You want to synchronize the `areas` state to the network based on the `city` state. + +Split the logic into two Effects, each of which reacts to the prop that it needs to synchronize with: + +```js {19-33} +function ShippingForm({ country }) { + const [cities, setCities] = useState(null); + useEffect(() => { + let ignore = false; + fetch(`/api/cities?country=${country}`) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setCities(json); + } + }); + return () => { + ignore = true; + }; + }, [country]); // ✅ All dependencies declared + + const [city, setCity] = useState(null); + const [areas, setAreas] = useState(null); + useEffect(() => { + if (city) { + let ignore = false; + fetch(`/api/areas?city=${city}`) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setAreas(json); + } + }); + return () => { + ignore = true; + }; + } + }, [city]); // ✅ All dependencies declared + + // ... +``` + +Now the first Effect only re-runs if the `country` changes, while the second Effect re-runs when the `city` changes. You've separated them by purpose: two different things are synchronized by two separate Effects. Two separate Effects have two separate dependency lists, so they will no longer trigger each other unintentionally. + +The final code is longer than the original, but splitting these Effects is still correct. [Each Effect should represent an independent synchronization process.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) In this example, deleting one Effect doesn't break the other Effect's logic. This is a good indication that they *synchronize different things,* and it made sense to split them up. If the duplication feels concerning, you can further improve this code by [extracting repetitive logic into a custom Hook.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) + +### Are you reading some state to calculate the next state? {/*are-you-reading-some-state-to-calculate-the-next-state*/} + +This Effect updates the `messages` state variable with a newly created array every time a new message arrives: + +```js {2,6-8} +function ChatRoom({ roomId }) { + const [messages, setMessages] = useState([]); + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + setMessages([...messages, receivedMessage]); + }); + // ... +``` + +It uses the `messages` variable to [create a new array](/learn/updating-arrays-in-state) starting with all the existing messages and adds the new message at the end. However, since `messages` is a reactive value read by an Effect, it must be a dependency: + +```js {7,10} +function ChatRoom({ roomId }) { + const [messages, setMessages] = useState([]); + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + setMessages([...messages, receivedMessage]); + }); + return () => connection.disconnect(); + }, [roomId, messages]); // ✅ All dependencies declared + // ... +``` + +And making `messages` a dependency introduces a problem. + +Every time you receive a message, `setMessages()` causes the component to re-render with a new `messages` array that includes the received message. However, since this Effect now depends on `messages`, this will *also* re-synchronize the Effect. So every new message will make the chat re-connect. The user would not like that! + +To fix the issue, don't read `messages` inside the Effect. Instead, pass an [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) to `setMessages`: + +```js {7,10} +function ChatRoom({ roomId }) { + const [messages, setMessages] = useState([]); + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + setMessages(msgs => [...msgs, receivedMessage]); + }); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +``` + +**Notice how your Effect does not read the `messages` variable at all now.** You only need to pass an updater function like `msgs => [...msgs, receivedMessage]`. React [puts your updater function in a queue](/learn/queueing-a-series-of-state-updates) and will provide the `msgs` argument to it during the next render. This is why the Effect itself doesn't need to depend on `messages` anymore. As a result of this fix, receiving a chat message will no longer make the chat re-connect. + +### Do you want to read a value without "reacting" to its changes? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} + +<Wip> + +This section describes an **experimental API that has not yet been added to React,** so you can't use it yet. + +</Wip> + +Suppose that you want to play a sound when the user receives a new message unless `isMuted` is `true`: + +```js {3,10-12} +function ChatRoom({ roomId }) { + const [messages, setMessages] = useState([]); + const [isMuted, setIsMuted] = useState(false); + + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + setMessages(msgs => [...msgs, receivedMessage]); + if (!isMuted) { + playSound(); + } + }); + // ... +``` + +Since your Effect now uses `isMuted` in its code, you have to add it to the dependencies: + +```js {10,15} +function ChatRoom({ roomId }) { + const [messages, setMessages] = useState([]); + const [isMuted, setIsMuted] = useState(false); + + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + setMessages(msgs => [...msgs, receivedMessage]); + if (!isMuted) { + playSound(); + } + }); + return () => connection.disconnect(); + }, [roomId, isMuted]); // ✅ All dependencies declared + // ... +``` + +The problem is that every time `isMuted` changes (for example, when the user presses the "Muted" toggle), the Effect will re-synchronize, and reconnect to the chat server. This is not the desired user experience! (In this example, even disabling the linter would not work--if you do that, `isMuted` would get "stuck" with its old value.) + +To solve this problem, you need to extract the logic that shouldn't be reactive out of the Effect. You don't want this Effect to "react" to the changes in `isMuted`. [Move this non-reactive piece of logic into an Effect Event:](/learn/separating-events-from-effects#declaring-an-effect-event) + +```js {1,7-12,18,21} +import { useState, useEffect, useEffectEvent } from 'react'; + +function ChatRoom({ roomId }) { + const [messages, setMessages] = useState([]); + const [isMuted, setIsMuted] = useState(false); + + const onMessage = useEffectEvent(receivedMessage => { + setMessages(msgs => [...msgs, receivedMessage]); + if (!isMuted) { + playSound(); + } + }); + + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + onMessage(receivedMessage); + }); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +``` + +Effect Events let you split an Effect into reactive parts (which should "react" to reactive values like `roomId` and their changes) and non-reactive parts (which only read their latest values, like `onMessage` reads `isMuted`). **Now that you read `isMuted` inside an Effect Event, it doesn't need to be a dependency of your Effect.** As a result, the chat won't re-connect when you toggle the "Muted" setting on and off, solving the original issue! + +#### Wrapping an event handler from the props {/*wrapping-an-event-handler-from-the-props*/} + +You might run into a similar problem when your component receives an event handler as a prop: + +```js {1,8,11} +function ChatRoom({ roomId, onReceiveMessage }) { + const [messages, setMessages] = useState([]); + + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + onReceiveMessage(receivedMessage); + }); + return () => connection.disconnect(); + }, [roomId, onReceiveMessage]); // ✅ All dependencies declared + // ... +``` + +Suppose that the parent component passes a *different* `onReceiveMessage` function on every render: + +```js {3-5} +<ChatRoom + roomId={roomId} + onReceiveMessage={receivedMessage => { + // ... + }} +/> +``` + +Since `onReceiveMessage` is a dependency of your Effect, it would cause the Effect to re-synchronize after every parent re-render. This would make it re-connect to the chat. To solve this, wrap the call in an Effect Event: + +```js {4-6,12,15} +function ChatRoom({ roomId, onReceiveMessage }) { + const [messages, setMessages] = useState([]); + + const onMessage = useEffectEvent(receivedMessage => { + onReceiveMessage(receivedMessage); + }); + + useEffect(() => { + const connection = createConnection(); + connection.connect(); + connection.on('message', (receivedMessage) => { + onMessage(receivedMessage); + }); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +``` + +Effect Events aren't reactive, so you don't need to specify them as dependencies. As a result, the chat will no longer re-connect even if the parent component passes a function that's different on every re-render. + +#### Separating reactive and non-reactive code {/*separating-reactive-and-non-reactive-code*/} + +In this example, you want to log a visit every time `roomId` changes. You want to include the current `notificationCount` with every log, but you *don't* want a change to `notificationCount` to trigger a log event. + +The solution is again to split out the non-reactive code into an Effect Event: + +```js {2-4,7} +function Chat({ roomId, notificationCount }) { + const onVisit = useEffectEvent(visitedRoomId => { + logVisit(visitedRoomId, notificationCount); + }); + + useEffect(() => { + onVisit(roomId); + }, [roomId]); // ✅ All dependencies declared + // ... +} +``` + +You want your logic to be reactive with regards to `roomId`, so you read `roomId` inside of your Effect. However, you don't want a change to `notificationCount` to log an extra visit, so you read `notificationCount` inside of the Effect Event. [Learn more about reading the latest props and state from Effects using Effect Events.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) + +### Does some reactive value change unintentionally? {/*does-some-reactive-value-change-unintentionally*/} + +Sometimes, you *do* want your Effect to "react" to a certain value, but that value changes more often than you'd like--and might not reflect any actual change from the user's perspective. For example, let's say that you create an `options` object in the body of your component, and then read that object from inside of your Effect: + +```js {3-6,9} +function ChatRoom({ roomId }) { + // ... + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + // ... +``` + +This object is declared in the component body, so it's a [reactive value.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) When you read a reactive value like this inside an Effect, you declare it as a dependency. This ensures your Effect "reacts" to its changes: + +```js {3,6} + // ... + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); // ✅ All dependencies declared + // ... +``` + +It is important to declare it as a dependency! This ensures, for example, that if the `roomId` changes, then your Effect will re-connect to the chat with the new `options`. However, there is also a problem with the code above. To see the problem, try typing into the input in the sandbox below, and watch what happens in the console: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input value={message} onChange={e => setMessage(e.target.value)} /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +In the sandbox above, the input only updates the `message` state variable. From the user's perspective, this should not affect the chat connection. However, every time you update the `message`, your component re-renders. When your component re-renders, the code inside of it runs again from scratch. + +This means that a new `options` object is created from scratch on every re-render of the `ChatRoom` component. React sees that the `options` object is a *different object* from the `options` object created during the last render. This is why it re-synchronizes your Effect (which depends on `options`), and the chat re-connects as you type. + +**This problem affects objects and functions in particular. In JavaScript, each newly created object and function is considered distinct from all the others. It doesn't matter that the contents inside of them may be the same!** + +```js {7-8} +// During the first render +const options1 = { serverUrl: 'https://localhost:1234', roomId: 'music' }; + +// During the next render +const options2 = { serverUrl: 'https://localhost:1234', roomId: 'music' }; + +// These are two different objects! +console.log(Object.is(options1, options2)); // false +```` + +**Object and function dependencies create a risk that your Effect will re-synchronize more often than you need.** + +This is why, whenever possible, you should try to avoid objects and functions as your Effect's dependencies. Instead, try moving them outside the component, inside the Effect, or extracting primitive values out of them. + +#### Move static objects and functions outside your component {/*move-static-objects-and-functions-outside-your-component*/} + +If the object does not depend on any props and state, you can move that object outside your component: + +```js {1-4,13} +const options = { + serverUrl: 'https://localhost:1234', + roomId: 'music' +}; + +function ChatRoom() { + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, []); // ✅ All dependencies declared + // ... +``` + +This way, you *prove* to the linter that it's not reactive. It can't change as a result of a re-render, so it doesn't need to be a dependency of your Effect. Now re-rendering `ChatRoom` won't cause your Effect to re-synchronize. + +This works for functions too: + +```js {1-6,12} +function createOptions() { + return { + serverUrl: 'https://localhost:1234', + roomId: 'music' + }; +} + +function ChatRoom() { + const [message, setMessage] = useState(''); + + useEffect(() => { + const options = createOptions(); + const connection = createConnection(); + connection.connect(); + return () => connection.disconnect(); + }, []); // ✅ All dependencies declared + // ... +``` + +Since `createOptions` is declared outside your component, it's not a reactive value. This is why it doesn't need to be specified in your Effect's dependencies, and why it won't ever cause your Effect to re-synchronize. + +#### Move dynamic objects and functions inside your Effect {/*move-dynamic-objects-and-functions-inside-your-effect*/} + +If your object depends on some reactive value that may change as a result of a re-render, like a `roomId` prop, you can't pull it *outside* your component. You can, however, move its creation *inside* of your Effect's code: + +```js {7-10,11,14} +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +``` + +Now that `options` is declared inside of your Effect, it is no longer a dependency of your Effect. Instead, the only reactive value used by your Effect is `roomId`. Since `roomId` is not an object or function, you can be sure that it won't be *unintentionally* different. In JavaScript, numbers and strings are compared by their content: + +```js {7-8} +// During the first render +const roomId1 = 'music'; + +// During the next render +const roomId2 = 'music'; + +// These two strings are the same! +console.log(Object.is(roomId1, roomId2)); // true +```` + +Thanks to this fix, the chat no longer re-connects if you edit the input: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input value={message} onChange={e => setMessage(e.target.value)} /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +However, it *does* re-connect when you change the `roomId` dropdown, as you would expect. + +This works for functions, too: + +```js {7-12,14} +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + function createOptions() { + return { + serverUrl: serverUrl, + roomId: roomId + }; + } + + const options = createOptions(); + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +``` + +You can write your own functions to group pieces of logic inside your Effect. As long as you also declare them *inside* your Effect, they're not reactive values, and so they don't need to be dependencies of your Effect. + +#### Read primitive values from objects {/*read-primitive-values-from-objects*/} + +Sometimes, you may receive an object from props: + +```js {1,5,8} +function ChatRoom({ options }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); // ✅ All dependencies declared + // ... +``` + +The risk here is that the parent component will create the object during rendering: + +```js {3-6} +<ChatRoom + roomId={roomId} + options={{ + serverUrl: serverUrl, + roomId: roomId + }} +/> +``` + +This would cause your Effect to re-connect every time the parent component re-renders. To fix this, read all the necessary information from the object *outside* the Effect, and avoid having objects and functions dependencies: + +```js {4,7-8,12} +function ChatRoom({ options }) { + const [message, setMessage] = useState(''); + + const { roomId, serverUrl } = options; + useEffect(() => { + const connection = createConnection({ + roomId: roomId, + serverUrl: serverUrl + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, serverUrl]); // ✅ All dependencies declared + // ... +``` + +The logic gets a little repetitive (you read some values from an object outside an Effect, and then create an object with the same values inside the Effect). But it makes it very explicit what information your Effect *actually* depends on. If an object is re-created unintentionally by the parent component, the chat would not re-connect. However, if `options.roomId` or `options.serverUrl` actually change, the chat would re-connect as you'd expect. + +#### Calculate primitive values from functions {/*calculate-primitive-values-from-functions*/} + +The same approach can work for functions. For example, suppose the parent component passes a function: + +```js {3-8} +<ChatRoom + roomId={roomId} + getOptions={() => { + return { + serverUrl: serverUrl, + roomId: roomId + }; + }} +/> +``` + +To avoid making it a dependency (and thus causing it to re-connect on re-renders), call it outside the Effect. This gives you the `roomId` and `serverUrl` values that aren't objects, and that you can read from inside your Effect: + +```js {1,4} +function ChatRoom({ getOptions }) { + const [message, setMessage] = useState(''); + + const { roomId, serverUrl } = getOptions(); + useEffect(() => { + const connection = createConnection({ + roomId: roomId, + serverUrl: serverUrl + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, serverUrl]); // ✅ All dependencies declared + // ... +``` + +This only works for [pure](/learn/keeping-components-pure) functions because they are safe to call during rendering. If your function is an event handler, but you don't want its changes to re-synchronize your Effect, [wrap it into an Effect Event instead.](#do-you-want-to-read-a-value-without-reacting-to-its-changes) + +<Recap> + +- Dependencies should always match the code. +- When you're not happy with your dependencies, what you need to edit is the code. +- Suppressing the linter leads to very confusing bugs, and you should always avoid it. +- To remove a dependency, you need to "prove" to the linter that it's not necessary. +- If the code in your Effect should run in response to a specific interaction, move that code to an event handler. +- If different parts of your Effect should re-run for different reasons, split it into several Effects. +- If you want to update some state based on the previous state, pass an updater function. +- If you want to read the latest value without "reacting" it, extract an Effect Event from your Effect. +- In JavaScript, objects and functions are considered different if they were created at different times. +- Try to avoid object and function dependencies. Move them outside the component or inside the Effect. + +</Recap> + +<Challenges> + +#### Fix a resetting interval {/*fix-a-resetting-interval*/} + +This Effect sets up an interval that ticks every second. You've noticed something strange happening: it seems like the interval gets destroyed and re-created every time it ticks. Fix the code so that the interval doesn't get constantly re-created. + +<Hint> + +It seems like this Effect's code depends on `count`. Is there some way to not need this dependency? There should be a way to update the `count` state based on its previous value without adding a dependency on that value. + +</Hint> + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + + useEffect(() => { + console.log('✅ Creating an interval'); + const id = setInterval(() => { + console.log('⏰ Interval tick'); + setCount(count + 1); + }, 1000); + return () => { + console.log('❌ Clearing an interval'); + clearInterval(id); + }; + }, [count]); + + return <h1>Counter: {count}</h1> +} +``` + +</Sandpack> + +<Solution> + +You want to update the `count` state to be `count + 1` from inside the Effect. However, this makes your Effect depend on `count`, which changes with every tick, and that's why your interval gets re-created on every tick. + +To solve this, use the [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) and write `setCount(c => c + 1)` instead of `setCount(count + 1)`: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + + useEffect(() => { + console.log('✅ Creating an interval'); + const id = setInterval(() => { + console.log('⏰ Interval tick'); + setCount(c => c + 1); + }, 1000); + return () => { + console.log('❌ Clearing an interval'); + clearInterval(id); + }; + }, []); + + return <h1>Counter: {count}</h1> +} +``` + +</Sandpack> + +Instead of reading `count` inside the Effect, you pass a `c => c + 1` instruction ("increment this number!") to React. React will apply it on the next render. And since you don't need to read the value of `count` inside your Effect anymore, so you can keep your Effect's dependencies empty (`[]`). This prevents your Effect from re-creating the interval on every tick. + +</Solution> + +#### Fix a retriggering animation {/*fix-a-retriggering-animation*/} + +In this example, when you press "Show", a welcome message fades in. The animation takes a second. When you press "Remove", the welcome message immediately disappears. The logic for the fade-in animation is implemented in the `animation.js` file as plain JavaScript [animation loop.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) You don't need to change that logic. You can treat it as a third-party library. Your Effect creates an instance of `FadeInAnimation` for the DOM node, and then calls `start(duration)` or `stop()` to control the animation. The `duration` is controlled by a slider. Adjust the slider and see how the animation changes. + +This code already works, but there is something you want to change. Currently, when you move the slider that controls the `duration` state variable, it retriggers the animation. Change the behavior so that the Effect does not "react" to the `duration` variable. When you press "Show", the Effect should use the current `duration` on the slider. However, moving the slider itself should not by itself retrigger the animation. + +<Hint> + +Is there a line of code inside the Effect that should not be reactive? How can you move non-reactive code out of the Effect? + +</Hint> + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect, useRef } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { FadeInAnimation } from './animation.js'; + +function Welcome({ duration }) { + const ref = useRef(null); + + useEffect(() => { + const animation = new FadeInAnimation(ref.current); + animation.start(duration); + return () => { + animation.stop(); + }; + }, [duration]); + + return ( + <h1 + ref={ref} + style={{ + opacity: 0, + color: 'white', + padding: 50, + textAlign: 'center', + fontSize: 50, + backgroundImage: 'radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%)' + }} + > + Welcome + </h1> + ); +} + +export default function App() { + const [duration, setDuration] = useState(1000); + const [show, setShow] = useState(false); + + return ( + <> + <label> + <input + type="range" + min="100" + max="3000" + value={duration} + onChange={e => setDuration(Number(e.target.value))} + /> + <br /> + Fade in duration: {duration} ms + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome duration={duration} />} + </> + ); +} +``` + +```js animation.js +export class FadeInAnimation { + constructor(node) { + this.node = node; + } + start(duration) { + this.duration = duration; + if (this.duration === 0) { + // Jump to end immediately + this.onProgress(1); + } else { + this.onProgress(0); + // Start animating + this.startTime = performance.now(); + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + } + onFrame() { + const timePassed = performance.now() - this.startTime; + const progress = Math.min(timePassed / this.duration, 1); + this.onProgress(progress); + if (progress < 1) { + // We still have more frames to paint + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + } + onProgress(progress) { + this.node.style.opacity = progress; + } + stop() { + cancelAnimationFrame(this.frameId); + this.startTime = null; + this.frameId = null; + this.duration = 0; + } +} +``` + +```css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +``` + +</Sandpack> + +<Solution> + +Your Effect needs to read the latest value of `duration`, but you don't want it to "react" to changes in `duration`. You use `duration` to start the animation, but starting animation isn't reactive. Extract the non-reactive line of code into an Effect Event, and call that function from your Effect. + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect, useRef } from 'react'; +import { FadeInAnimation } from './animation.js'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +function Welcome({ duration }) { + const ref = useRef(null); + + const onAppear = useEffectEvent(animation => { + animation.start(duration); + }); + + useEffect(() => { + const animation = new FadeInAnimation(ref.current); + onAppear(animation); + return () => { + animation.stop(); + }; + }, []); + + return ( + <h1 + ref={ref} + style={{ + opacity: 0, + color: 'white', + padding: 50, + textAlign: 'center', + fontSize: 50, + backgroundImage: 'radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%)' + }} + > + Welcome + </h1> + ); +} + +export default function App() { + const [duration, setDuration] = useState(1000); + const [show, setShow] = useState(false); + + return ( + <> + <label> + <input + type="range" + min="100" + max="3000" + value={duration} + onChange={e => setDuration(Number(e.target.value))} + /> + <br /> + Fade in duration: {duration} ms + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome duration={duration} />} + </> + ); +} +``` + +```js animation.js +export class FadeInAnimation { + constructor(node) { + this.node = node; + } + start(duration) { + this.duration = duration; + this.onProgress(0); + this.startTime = performance.now(); + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + onFrame() { + const timePassed = performance.now() - this.startTime; + const progress = Math.min(timePassed / this.duration, 1); + this.onProgress(progress); + if (progress < 1) { + // We still have more frames to paint + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + } + onProgress(progress) { + this.node.style.opacity = progress; + } + stop() { + cancelAnimationFrame(this.frameId); + this.startTime = null; + this.frameId = null; + this.duration = 0; + } +} +``` + +```css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +``` + +</Sandpack> + +Effect Events like `onAppear` are not reactive, so you can read `duration` inside without retriggering the animation. + +</Solution> + +#### Fix a reconnecting chat {/*fix-a-reconnecting-chat*/} + +In this example, every time you press "Toggle theme", the chat re-connects. Why does this happen? Fix the mistake so that the chat re-connects only when you edit the Server URL or choose a different chat room. + +Treat `chat.js` as an external third-party library: you can consult it to check its API, but don't edit it. + +<Hint> + +There's more than one way to fix this, but ultimately you want to avoid having an object as your dependency. + +</Hint> + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + const [roomId, setRoomId] = useState('general'); + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + + return ( + <div className={isDark ? 'dark' : 'light'}> + <button onClick={() => setIsDark(!isDark)}> + Toggle theme + </button> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom options={options} /> + </div> + ); +} +``` + +```js ChatRoom.js active +import { useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export default function ChatRoom({ options }) { + useEffect(() => { + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [options]); + + return <h1>Welcome to the {options.roomId} room!</h1>; +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +label, button { display: block; margin-bottom: 5px; } +.dark { background: #222; color: #eee; } +``` + +</Sandpack> + +<Solution> + +Your Effect is re-running because it depends on the `options` object. Objects can be re-created unintentionally, you should try to avoid them as dependencies of your Effects whenever possible. + +The least invasive fix is to read `roomId` and `serverUrl` right outside the Effect, and then make the Effect depend on those primitive values (which can't change unintentionally). Inside the Effect, create an object and it pass to `createConnnection`: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + const [roomId, setRoomId] = useState('general'); + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + + return ( + <div className={isDark ? 'dark' : 'light'}> + <button onClick={() => setIsDark(!isDark)}> + Toggle theme + </button> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom options={options} /> + </div> + ); +} +``` + +```js ChatRoom.js active +import { useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export default function ChatRoom({ options }) { + const { roomId, serverUrl } = options; + useEffect(() => { + const connection = createConnection({ + roomId: roomId, + serverUrl: serverUrl + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, serverUrl]); + + return <h1>Welcome to the {options.roomId} room!</h1>; +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +label, button { display: block; margin-bottom: 5px; } +.dark { background: #222; color: #eee; } +``` + +</Sandpack> + +It would be even better to replace the object `options` prop with the more specific `roomId` and `serverUrl` props: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + const [roomId, setRoomId] = useState('general'); + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + return ( + <div className={isDark ? 'dark' : 'light'}> + <button onClick={() => setIsDark(!isDark)}> + Toggle theme + </button> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom + roomId={roomId} + serverUrl={serverUrl} + /> + </div> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export default function ChatRoom({ roomId, serverUrl }) { + useEffect(() => { + const connection = createConnection({ + roomId: roomId, + serverUrl: serverUrl + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, serverUrl]); + + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +label, button { display: block; margin-bottom: 5px; } +.dark { background: #222; color: #eee; } +``` + +</Sandpack> + +Sticking to primitive props where possible makes it easier to optimize your components later. + +</Solution> + +#### Fix a reconnecting chat, again {/*fix-a-reconnecting-chat-again*/} + +This example connects to the chat either with or without encryption. Toggle the checkbox and notice the different messages in the console when the encryption is on and off. Try changing the room. Then, try toggling the theme. When you're connected to a chat room, you will receive new messages every few seconds. Verify that their color matches the theme you've picked. + +In this example, the chat re-connects every time you try to change the theme. Fix this. After the fix, changing the theme should not re-connect the chat, but toggling encryption settings or changing the room should re-connect. + +Don't change any code in `chat.js`. Other than that, you can change any code as long as it results in the same behavior. For example, you may find it helpful to change which props are being passed down. + +<Hint> + +You're passing down two functions: `onMessage` and `createConnection`. Both of them are created from scratch every time `App` re-renders. They are considered to be new values every time, which is why they re-trigger your Effect. + +One of these functions is an event handler. Do you know some way to call an event handler an Effect without "reacting" to the new values of the event handler function? That would come in handy! + +Another of these functions only exists to pass some state to an imported API method. Is this function really necessary? What is the essential information that's being passed down? You might need to move some imports from `App.js` to `ChatRoom.js`. + +</Hint> + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; +import { + createEncryptedConnection, + createUnencryptedConnection, +} from './chat.js'; +import { showNotification } from './notifications.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + const [roomId, setRoomId] = useState('general'); + const [isEncrypted, setIsEncrypted] = useState(false); + + return ( + <> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <label> + <input + type="checkbox" + checked={isEncrypted} + onChange={e => setIsEncrypted(e.target.checked)} + /> + Enable encryption + </label> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom + roomId={roomId} + onMessage={msg => { + showNotification('New message: ' + msg, isDark ? 'dark' : 'light'); + }} + createConnection={() => { + const options = { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + if (isEncrypted) { + return createEncryptedConnection(options); + } else { + return createUnencryptedConnection(options); + } + }} + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export default function ChatRoom({ roomId, createConnection, onMessage }) { + useEffect(() => { + const connection = createConnection(); + connection.on('message', (msg) => onMessage(msg)); + connection.connect(); + return () => connection.disconnect(); + }, [createConnection, onMessage]); + + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +export function createEncryptedConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + let intervalId; + let messageCallback; + return { + connect() { + console.log('✅ 🔐 Connecting to "' + roomId + '" room... (encrypted)'); + clearInterval(intervalId); + intervalId = setInterval(() => { + if (messageCallback) { + if (Math.random() > 0.5) { + messageCallback('hey') + } else { + messageCallback('lol'); + } + } + }, 3000); + }, + disconnect() { + clearInterval(intervalId); + messageCallback = null; + console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); + }, + on(event, callback) { + if (messageCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'message') { + throw Error('Only "message" event is supported.'); + } + messageCallback = callback; + }, + }; +} + +export function createUnencryptedConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + let intervalId; + let messageCallback; + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room (unencrypted)...'); + clearInterval(intervalId); + intervalId = setInterval(() => { + if (messageCallback) { + if (Math.random() > 0.5) { + messageCallback('hey') + } else { + messageCallback('lol'); + } + } + }, 3000); + }, + disconnect() { + clearInterval(intervalId); + messageCallback = null; + console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); + }, + on(event, callback) { + if (messageCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'message') { + throw Error('Only "message" event is supported.'); + } + messageCallback = callback; + }, + }; +} +``` + +```js notifications.js +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label, button { display: block; margin-bottom: 5px; } +``` + +</Sandpack> + +<Solution> + +There's more than one correct way to solve this, but here is one possible solution. + +In the original example, toggling the theme caused different `onMessage` and `createConnection` functions to be created and passed down. Since the Effect depended on these functions, the chat would re-connect every time you toggle the theme. + +To fix the problem with `onMessage`, you needed to wrap it into an Effect Event: + +```js {1,2,6} +export default function ChatRoom({ roomId, createConnection, onMessage }) { + const onReceiveMessage = useEffectEvent(onMessage); + + useEffect(() => { + const connection = createConnection(); + connection.on('message', (msg) => onReceiveMessage(msg)); + // ... +``` + +Unlike the `onMessage` prop, the `onReceiveMessage` Effect Event is not reactive. This is why it doesn't need to be a dependency of your Effect. As a result, changes to `onMessage` won't cause the chat to re-connect. + +You can't do the same with `createConnection` because it *should* be reactive. You *want* the Effect to re-trigger if the user switches between an encrypted and an unencryption connection, or if the user switches the current room. However, because `createConnection` is a function, you can't check whether the information it reads has *actually* changed or not. To solve this, instead of passing `createConnection` down from the `App` component, pass the raw `roomId` and `isEncrypted` values: + +```js {2-3} + <ChatRoom + roomId={roomId} + isEncrypted={isEncrypted} + onMessage={msg => { + showNotification('New message: ' + msg, isDark ? 'dark' : 'light'); + }} + /> +``` + +Now you can move the `createConnection` function *inside* the Effect instead of passing it down from the `App`: + +```js {1-4,6,10-20} +import { + createEncryptedConnection, + createUnencryptedConnection, +} from './chat.js'; + +export default function ChatRoom({ roomId, isEncrypted, onMessage }) { + const onReceiveMessage = useEffectEvent(onMessage); + + useEffect(() => { + function createConnection() { + const options = { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + if (isEncrypted) { + return createEncryptedConnection(options); + } else { + return createUnencryptedConnection(options); + } + } + // ... +``` + +After these two changes, your Effect no longer depends on any function values: + +```js {1,8,10,21} +export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reactive values + const onReceiveMessage = useEffectEvent(onMessage); // Not reactive + + useEffect(() => { + function createConnection() { + const options = { + serverUrl: 'https://localhost:1234', + roomId: roomId // Reading a reactive value + }; + if (isEncrypted) { // Reading a reactive value + return createEncryptedConnection(options); + } else { + return createUnencryptedConnection(options); + } + } + + const connection = createConnection(); + connection.on('message', (msg) => onReceiveMessage(msg)); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, isEncrypted]); // ✅ All dependencies declared +``` + +As a result, the chat re-connects only when something meaningful (`roomId` or `isEncrypted`) changes: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +import { showNotification } from './notifications.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + const [roomId, setRoomId] = useState('general'); + const [isEncrypted, setIsEncrypted] = useState(false); + + return ( + <> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <label> + <input + type="checkbox" + checked={isEncrypted} + onChange={e => setIsEncrypted(e.target.checked)} + /> + Enable encryption + </label> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom + roomId={roomId} + isEncrypted={isEncrypted} + onMessage={msg => { + showNotification('New message: ' + msg, isDark ? 'dark' : 'light'); + }} + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { + createEncryptedConnection, + createUnencryptedConnection, +} from './chat.js'; + +export default function ChatRoom({ roomId, isEncrypted, onMessage }) { + const onReceiveMessage = useEffectEvent(onMessage); + + useEffect(() => { + function createConnection() { + const options = { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + if (isEncrypted) { + return createEncryptedConnection(options); + } else { + return createUnencryptedConnection(options); + } + } + + const connection = createConnection(); + connection.on('message', (msg) => onReceiveMessage(msg)); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, isEncrypted]); + + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +export function createEncryptedConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + let intervalId; + let messageCallback; + return { + connect() { + console.log('✅ 🔐 Connecting to "' + roomId + '" room... (encrypted)'); + clearInterval(intervalId); + intervalId = setInterval(() => { + if (messageCallback) { + if (Math.random() > 0.5) { + messageCallback('hey') + } else { + messageCallback('lol'); + } + } + }, 3000); + }, + disconnect() { + clearInterval(intervalId); + messageCallback = null; + console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); + }, + on(event, callback) { + if (messageCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'message') { + throw Error('Only "message" event is supported.'); + } + messageCallback = callback; + }, + }; +} + +export function createUnencryptedConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + let intervalId; + let messageCallback; + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room (unencrypted)...'); + clearInterval(intervalId); + intervalId = setInterval(() => { + if (messageCallback) { + if (Math.random() > 0.5) { + messageCallback('hey') + } else { + messageCallback('lol'); + } + } + }, 3000); + }, + disconnect() { + clearInterval(intervalId); + messageCallback = null; + console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); + }, + on(event, callback) { + if (messageCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'message') { + throw Error('Only "message" event is supported.'); + } + messageCallback = callback; + }, + }; +} +``` + +```js notifications.js +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label, button { display: block; margin-bottom: 5px; } +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/render-and-commit.md b/beta/src/content/learn/render-and-commit.md new file mode 100644 index 000000000..4ddefc2f5 --- /dev/null +++ b/beta/src/content/learn/render-and-commit.md @@ -0,0 +1,213 @@ +--- +title: Render and Commit +--- + +<Intro> + +Before your components are displayed on screen, they must be rendered by React. Understanding the steps in this process will help you think about how your code executes and explain its behavior. + +</Intro> + +<YouWillLearn> + +* What rendering means in React +* When and why React renders a component +* The steps involved in displaying a component on screen +* Why rendering does not always produce a DOM update + +</YouWillLearn> + +Imagine that your components are cooks in the kitchen, assembling tasty dishes from ingredients. In this scenario, React is the waiter who puts in requests from customers and brings them their orders. This process of requesting and serving UI has three steps: + +1. **Triggering** a render (delivering the guest's order to the kitchen) +2. **Rendering** the component (preparing the order in the kitchen) +3. **Committing** to the DOM (placing the order on the table) + +<IllustrationBlock sequential> + <Illustration caption="Trigger" alt="React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen." src="/images/docs/illustrations/i_render-and-commit1.png" /> + <Illustration caption="Render" alt="The Card Chef gives React a fresh Card component." src="/images/docs/illustrations/i_render-and-commit2.png" /> + <Illustration caption="Commit" alt="React delivers the Card to the user at their table." src="/images/docs/illustrations/i_render-and-commit3.png" /> +</IllustrationBlock> + +## Step 1: Trigger a render {/*step-1-trigger-a-render*/} + +There are two reasons for a component to render: + +1. It's the component's **initial render.** +2. The component's (or one of its ancestors') **state has been updated.** + +### Initial render {/*initial-render*/} + +When your app starts, you need to trigger the initial render. Frameworks and sandboxes sometimes hide this code, but it's done by calling [`createRoot`](https://beta.reactjs.org/apis/react-dom/client/createRoot) with the target DOM node, and then calling its `render` method with your component: + +<Sandpack> + +```js index.js active +import Image from './Image.js'; +import { createRoot } from 'react-dom/client'; + +const root = createRoot(document.getElementById('root')) +root.render(<Image />); +``` + +```js Image.js +export default function Image() { + return ( + <img + src="https://i.imgur.com/ZF6s192.jpg" + alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals" + /> + ); +} +``` + +</Sandpack> + +Try commenting out the `root.render()` call and see the component disappear! + +### Re-renders when state updates {/*re-renders-when-state-updates*/} + +Once the component has been initially rendered, you can trigger further renders by updating its state with the [`set` function.](/reference/react/useState#setstate) Updating your component's state automatically queues a render. (You can imagine these as a restaurant guest ordering tea, dessert, and all sorts of things after putting in their first order, depending on the state of their thirst or hunger.) + +<IllustrationBlock sequential> + <Illustration caption="State update..." alt="React as a server in a restaurant, serving a Card UI to the user, represented as a patron with a cursor for their head. They patron expresses they want a pink card, not a black one!" src="/images/docs/illustrations/i_rerender1.png" /> + <Illustration caption="...triggers..." alt="React returns to the Component Kitchen and tells the Card Chef they need a pink Card." src="/images/docs/illustrations/i_rerender2.png" /> + <Illustration caption="...render!" alt="The Card Chef gives React the pink Card." src="/images/docs/illustrations/i_rerender3.png" /> +</IllustrationBlock> + +## Step 2: React renders your components {/*step-2-react-renders-your-components*/} + +After you trigger a render, React calls your components to figure out what to display on screen. **"Rendering" is React calling your components.** + +* **On initial render,** React will call the root component. +* **For subsequent renders,** React will call the function component whose state update triggered the render. + +This process is recursive: if the updated component returns some other component, React will render _that_ component next, and if that component also returns something, it will render _that_ component next, and so on. The process will continue until there are no more nested components and React knows exactly what should be displayed on screen. + +In the following example, React will call `Gallery()` and `Image()` several times: + +<Sandpack> + +```js Gallery.js active +export default function Gallery() { + return ( + <section> + <h1>Inspiring Sculptures</h1> + <Image /> + <Image /> + <Image /> + </section> + ); +} + +function Image() { + return ( + <img + src="https://i.imgur.com/ZF6s192.jpg" + alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals" + /> + ); +} +``` + +```js index.js +import Gallery from './Gallery.js'; +import { createRoot } from 'react-dom/client'; + +const root = createRoot(document.getElementById('root')) +root.render(<Gallery />); +``` + +```css +img { margin: 0 10px 10px 0; } +``` + +</Sandpack> + +* **During the initial render,** React will [create the DOM nodes](https://developer.mozilla.org/docs/Web/API/Document/createElement) for `<section>`, `<h1>`, and three `<img>` tags. +* **During a re-render,** React will calculate which of their properties, if any, have changed since the previous render. It won't do anything with that information until the next step, the commit phase. + +<Pitfall> + +Rendering must always be a [pure calculation](/learn/keeping-components-pure): + +* **Same inputs, same output.** Given the same inputs, a component should always return the same JSX. (When someone orders a salad with tomatoes, they should not receive a salad with onions!) +* **It minds its own business.** It should not change any objects or variables that existed before rendering. (One order should not change anyone else's order.) + +Otherwise, you can encounter confusing bugs and unpredictable behavior as your codebase grows in complexity. When developing in "Strict Mode", React calls each component's function twice, which can help surface mistakes caused by impure functions. + +</Pitfall> + +<DeepDive> + +#### Optimizing performance {/*optimizing-performance*/} + +The default behavior of rendering all components nested within the updated component is not optimal for performance if the updated component is very high in the tree. If you run into a performance issue, there are several opt-in ways to solve it described in the [Performance](https://reactjs.org/docs/optimizing-performance.html) section. **Don't optimize prematurely!** + +</DeepDive> + +## Step 3: React commits changes to the DOM {/*step-3-react-commits-changes-to-the-dom*/} + +After rendering (calling) your components, React will modify the DOM. + +* **For the initial render,** React will use the [`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild) DOM API to put all the DOM nodes it has created on screen. +* **For re-renders,** React will apply the minimal necessary operations (calculated while rendering!) to make the DOM match the latest rendering output. + +**React only changes the DOM nodes if there's a difference between renders.** For example, here is a component that re-renders with different props passed from its parent every second. Notice how you can add some text into the `<input>`, updating its `value`, but the text doesn't disappear when the component re-renders: + +<Sandpack> + +```js Clock.js active +export default function Clock({ time }) { + return ( + <> + <h1>{time}</h1> + <input /> + </> + ); +} +``` + +```js App.js hidden +import { useState, useEffect } from 'react'; +import Clock from './Clock.js'; + +function useTime() { + const [time, setTime] = useState(() => new Date()); + useEffect(() => { + const id = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => clearInterval(id); + }, []); + return time; +} + +export default function App() { + const time = useTime(); + return ( + <Clock time={time.toLocaleTimeString()} /> + ); +} +``` + +</Sandpack> + +This works because during this last step, React only updates the content of `<h1>` with the new `time`. It sees that the `<input>` appears in the JSX in the same place as last time, so React doesn't touch the `<input>`—or its `value`! +## Epilogue: Browser paint {/*epilogue-browser-paint*/} + +After rendering is done and React updated the DOM, the browser will repaint the screen. Although this process is known as "browser rendering", we'll refer to it as "painting" to avoid confusion in the rest of these docs. + +<Illustration alt="A browser painting 'still life with card element'." src="/images/docs/illustrations/i_browser-paint.png" /> + +<Recap> + +* Any screen update in a React app happens in three steps: + 1. Trigger + 2. Render + 3. Commit +* You can use Strict Mode to find mistakes in your components +* React does not touch the DOM if the rendering result is the same as last time + +</Recap> + diff --git a/beta/src/content/learn/rendering-lists.md b/beta/src/content/learn/rendering-lists.md new file mode 100644 index 000000000..b7c4854b5 --- /dev/null +++ b/beta/src/content/learn/rendering-lists.md @@ -0,0 +1,1261 @@ +--- +title: Rendering Lists +--- + +<Intro> + +You will often want to display multiple similar components from a collection of data. You can use the [JavaScript array methods](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array#) to manipulate an array of data. On this page, you'll use [`filter()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) and [`map()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map) with React to filter and transform your array of data into an array of components. + +</Intro> + +<YouWillLearn> + +* How to render components from an array using JavaScript's `map()` +* How to render only specific components using JavaScript's `filter()` +* When and why to use React keys + +</YouWillLearn> + +## Rendering data from arrays {/*rendering-data-from-arrays*/} + +Say that you have a list of content. + +```js +<ul> + <li>Creola Katherine Johnson: mathematician</li> + <li>Mario José Molina-Pasquel Henríquez: chemist</li> + <li>Mohammad Abdus Salam: physicist</li> + <li>Percy Lavon Julian: chemist</li> + <li>Subrahmanyan Chandrasekhar: astrophysicist</li> +</ul> +``` + +The only difference among those list items is their contents, their data. You will often need to show several instances of the same component using different data when building interfaces: from lists of comments to galleries of profile images. In these situations, you can store that data in JavaScript objects and arrays and use methods like [`map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) and [`filter()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) to render lists of components from them. + +Here’s a short example of how to generate a list of items from an array: + +1. **Move** the data into an array: + +```js +const people = [ + 'Creola Katherine Johnson: mathematician', + 'Mario José Molina-Pasquel Henríquez: chemist', + 'Mohammad Abdus Salam: physicist', + 'Percy Lavon Julian: chemist', + 'Subrahmanyan Chandrasekhar: astrophysicist' +]; +``` + +2. **Map** the `people` members into a new array of JSX nodes, `listItems`: + +```js +const listItems = people.map(person => <li>{person}</li>); +``` + +3. **Return** `listItems` from your component wrapped in a `<ul>`: + +```js +return <ul>{listItems}</ul>; +``` + +Here is the result: + +<Sandpack> + +```js +const people = [ + 'Creola Katherine Johnson: mathematician', + 'Mario José Molina-Pasquel Henríquez: chemist', + 'Mohammad Abdus Salam: physicist', + 'Percy Lavon Julian: chemist', + 'Subrahmanyan Chandrasekhar: astrophysicist' +]; + +export default function List() { + const listItems = people.map(person => + <li>{person}</li> + ); + return <ul>{listItems}</ul>; +} +``` + +```css +li { margin-bottom: 10px; } +``` + +</Sandpack> + +Notice the sandbox above displays a console error: + +<ConsoleBlock level="error"> + +Warning: Each child in a list should have a unique "key" prop. + +</ConsoleBlock> + +You'll learn how to fix this error later on this page. Before we get to that, let's add some structure to your data. + +## Filtering arrays of items {/*filtering-arrays-of-items*/} + +This data can be structured even more. + +```js +const people = [{ + id: 0, + name: 'Creola Katherine Johnson', + profession: 'mathematician', +}, { + id: 1, + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', +}, { + id: 2, + name: 'Mohammad Abdus Salam', + profession: 'physicist', +}, { + name: 'Percy Lavon Julian', + profession: 'chemist', +}, { + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', +}]; +``` + +Let's say you want a way to only show people whose profession is `'chemist'`. You can use JavaScript's `filter()` method to return just those people. This method takes an array of items, passes them through a “test” (a function that returns `true` or `false`), and returns a new array of only those items that passed the test (returned `true`). + +You only want the items where `profession` is `'chemist'`. The "test" function for this looks like `(person) => person.profession === 'chemist'`. Here's how to put it together: + +1. **Create** a new array of just “chemist” people, `chemists`, by calling `filter()` on the `people` filtering by `person.profession === 'chemist'`: + +```js +const chemists = people.filter(person => + person.profession === 'chemist' +); +``` + +2. Now **map** over `chemists`: + +```js {1,13} +const listItems = chemists.map(person => + <li> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> +); +``` + +3. Lastly, **return** the `listItems` from your component: + +```js +return <ul>{listItems}</ul>; +``` + +<Sandpack> + +```js App.js +import { people } from './data.js'; +import { getImageUrl } from './utils.js'; + +export default function List() { + const chemists = people.filter(person => + person.profession === 'chemist' + ); + const listItems = chemists.map(person => + <li> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + ); + return <ul>{listItems}</ul>; +} +``` + +```js data.js +export const people = [{ + id: 0, + name: 'Creola Katherine Johnson', + profession: 'mathematician', + accomplishment: 'spaceflight calculations', + imageId: 'MK3eW3A' +}, { + id: 1, + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', + accomplishment: 'discovery of Arctic ozone hole', + imageId: 'mynHUSa' +}, { + id: 2, + name: 'Mohammad Abdus Salam', + profession: 'physicist', + accomplishment: 'electromagnetism theory', + imageId: 'bE7W1ji' +}, { + id: 3, + name: 'Percy Lavon Julian', + profession: 'chemist', + accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', + imageId: 'IOjWm71' +}, { + id: 4, + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', + accomplishment: 'white dwarf star mass calculations', + imageId: 'lrWQx8l' +}]; +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + 's.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +img { width: 100px; height: 100px; border-radius: 50%; } +``` + +</Sandpack> + +<Pitfall> + +Arrow functions implicitly return the expression right after `=>`, so you didn't need a `return` statement: + +```js +const listItems = chemists.map(person => + <li>...</li> // Implicit return! +); +``` + +However, **you must write `return` explicitly if your `=>` is followed by a `{` curly brace!** + +```js +const listItems = chemists.map(person => { // Curly brace + return <li>...</li>; +}); +``` + +Arrow functions containing `=> {` are said to have a ["block body".](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#function_body) They let you write more than a single line of code, but you *have to* write a `return` statement yourself. If you forget it, nothing gets returned! + +</Pitfall> + +## Keeping list items in order with `key` {/*keeping-list-items-in-order-with-key*/} + +Notice that all the sandboxes above show an error in the console: + +<ConsoleBlock level="error"> + +Warning: Each child in a list should have a unique "key" prop. + +</ConsoleBlock> + +You need to give each array item a `key` -- a string or a number that uniquely identifies it among other items in that array: + +```js +<li key={person.id}>...</li> +``` + +<Note> + +JSX elements directly inside a `map()` call always need keys! + +</Note> + +Keys tell React which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen `key` helps React infer what exactly has happened, and make the correct updates to the DOM tree. + +Rather than generating keys on the fly, you should include them in your data: + +<Sandpack> + +```js App.js +import { people } from './data.js'; +import { getImageUrl } from './utils.js'; + +export default function List() { + const listItems = people.map(person => + <li key={person.id}> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + ); + return <ul>{listItems}</ul>; +} +``` + +```js data.js active +export const people = [{ + id: 0, // Used in JSX as a key + name: 'Creola Katherine Johnson', + profession: 'mathematician', + accomplishment: 'spaceflight calculations', + imageId: 'MK3eW3A' +}, { + id: 1, // Used in JSX as a key + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', + accomplishment: 'discovery of Arctic ozone hole', + imageId: 'mynHUSa' +}, { + id: 2, // Used in JSX as a key + name: 'Mohammad Abdus Salam', + profession: 'physicist', + accomplishment: 'electromagnetism theory', + imageId: 'bE7W1ji' +}, { + id: 3, // Used in JSX as a key + name: 'Percy Lavon Julian', + profession: 'chemist', + accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', + imageId: 'IOjWm71' +}, { + id: 4, // Used in JSX as a key + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', + accomplishment: 'white dwarf star mass calculations', + imageId: 'lrWQx8l' +}]; +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + 's.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +img { width: 100px; height: 100px; border-radius: 50%; } +``` + +</Sandpack> + +<DeepDive> + +#### Displaying several DOM nodes for each list item {/*displaying-several-dom-nodes-for-each-list-item*/} + +What do you do when each item needs to render not one, but several DOM nodes? + +The short [`<>...</>` Fragment](/reference/react/Fragment) syntax won't let you pass a key, so you need to either group them into a single `<div>`, or use the slightly longer and [more explicit `<Fragment>` syntax:](/reference/react/Fragment#rendering-a-list-of-fragments) + +```js +import { Fragment } from 'react'; + +// ... + +const listItems = people.map(person => + <Fragment key={person.id}> + <h1>{person.name}</h1> + <p>{person.bio}</p> + </Fragment> +); +``` + +Fragments disappear from the DOM, so this will produce a flat list of `<h1>`, `<p>`, `<h1>`, `<p>`, and so on. + +</DeepDive> + +### Where to get your `key` {/*where-to-get-your-key*/} + +Different sources of data provide different sources of keys: + +* **Data from a database:** If your data is coming from a database, you can use the database keys/IDs, which are unique by nature. +* **Locally generated data:** If your data is generated and persisted locally (e.g. notes in a note-taking app), use an incrementing counter, [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID) or a package like [`uuid`](https://www.npmjs.com/package/uuid) when creating items. + +### Rules of keys {/*rules-of-keys*/} + +* **Keys must be unique among siblings.** However, it’s okay to use the same keys for JSX nodes in _different_ arrays. +* **Keys must not change** or that defeats their purpose! Don't generate them while rendering. + +### Why does React need keys? {/*why-does-react-need-keys*/} + +Imagine that files on your desktop didn't have names. Instead, you'd refer to them by their order -- the first file, the second file, and so on. You could get used to it, but once you delete a file, it would get confusing. The second file would become the first file, the third file would be the second file, and so on. + +File names in a folder and JSX keys in an array serve a similar purpose. They let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the array. Even if the _position_ changes due to reordering, the `key` lets React identify the item throughout its lifetime. + +<Pitfall> + +You might be tempted to use an item's index in the array as its key. In fact, that's what React will use if you don't specify a `key` at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs. + +Similarly, do not generate keys on the fly, e.g. with `key={Math.random()}`. This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time. Not only is this slow, but it will also lose any user input inside the list items. Instead, use a stable ID based on the data. + +Note that your components won't receive `key` as a prop. It's only used as a hint by React itself. If your component needs an ID, you have to pass it as a separate prop: `<Profile key={id} userId={id} />`. + +</Pitfall> + +<Recap> + +On this page you learned: + +* How to move data out of components and into data structures like arrays and objects. +* How to generate sets of similar components with JavaScript's `map()`. +* How to create arrays of filtered items with JavaScript's `filter()`. +* Why and how to set `key` on each component in a collection so React can keep track of each of them even if their position or data changes. + +</Recap> + + + +<Challenges> + +#### Splitting a list in two {/*splitting-a-list-in-two*/} + +This example shows a list of all people. + +Change it to show two separate lists one after another: **Chemists** and **Everyone Else.** Like previously, you can determine whether a person is a chemist by checking if `person.profession === 'chemist'`. + +<Sandpack> + +```js App.js +import { people } from './data.js'; +import { getImageUrl } from './utils.js'; + +export default function List() { + const listItems = people.map(person => + <li key={person.id}> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + ); + return ( + <article> + <h1>Scientists</h1> + <ul>{listItems}</ul> + </article> + ); +} +``` + +```js data.js +export const people = [{ + id: 0, + name: 'Creola Katherine Johnson', + profession: 'mathematician', + accomplishment: 'spaceflight calculations', + imageId: 'MK3eW3A' +}, { + id: 1, + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', + accomplishment: 'discovery of Arctic ozone hole', + imageId: 'mynHUSa' +}, { + id: 2, + name: 'Mohammad Abdus Salam', + profession: 'physicist', + accomplishment: 'electromagnetism theory', + imageId: 'bE7W1ji' +}, { + id: 3, + name: 'Percy Lavon Julian', + profession: 'chemist', + accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', + imageId: 'IOjWm71' +}, { + id: 4, + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', + accomplishment: 'white dwarf star mass calculations', + imageId: 'lrWQx8l' +}]; +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + 's.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +img { width: 100px; height: 100px; border-radius: 50%; } +``` + +</Sandpack> + +<Solution> + +You could use `filter()` twice, creating two separate arrays, and then `map` over both of them: + +<Sandpack> + +```js App.js +import { people } from './data.js'; +import { getImageUrl } from './utils.js'; + +export default function List() { + const chemists = people.filter(person => + person.profession === 'chemist' + ); + const everyoneElse = people.filter(person => + person.profession !== 'chemist' + ); + return ( + <article> + <h1>Scientists</h1> + <h2>Chemists</h2> + <ul> + {chemists.map(person => + <li key={person.id}> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + )} + </ul> + <h2>Everyone Else</h2> + <ul> + {everyoneElse.map(person => + <li key={person.id}> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + )} + </ul> + </article> + ); +} +``` + +```js data.js +export const people = [{ + id: 0, + name: 'Creola Katherine Johnson', + profession: 'mathematician', + accomplishment: 'spaceflight calculations', + imageId: 'MK3eW3A' +}, { + id: 1, + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', + accomplishment: 'discovery of Arctic ozone hole', + imageId: 'mynHUSa' +}, { + id: 2, + name: 'Mohammad Abdus Salam', + profession: 'physicist', + accomplishment: 'electromagnetism theory', + imageId: 'bE7W1ji' +}, { + id: 3, + name: 'Percy Lavon Julian', + profession: 'chemist', + accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', + imageId: 'IOjWm71' +}, { + id: 4, + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', + accomplishment: 'white dwarf star mass calculations', + imageId: 'lrWQx8l' +}]; +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + 's.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +img { width: 100px; height: 100px; border-radius: 50%; } +``` + +</Sandpack> + +In this solution, the `map` calls are placed directly inline into the parent `<ul>` elements, but you could introduce variables for them if you find that more readable. + +There is still a bit duplication between the rendered lists. You can go further and extract the repetitive parts into a `<ListSection>` component: + +<Sandpack> + +```js App.js +import { people } from './data.js'; +import { getImageUrl } from './utils.js'; + +function ListSection({ title, people }) { + return ( + <> + <h2>{title}</h2> + <ul> + {people.map(person => + <li key={person.id}> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + )} + </ul> + </> + ); +} + +export default function List() { + const chemists = people.filter(person => + person.profession === 'chemist' + ); + const everyoneElse = people.filter(person => + person.profession !== 'chemist' + ); + return ( + <article> + <h1>Scientists</h1> + <ListSection + title="Chemists" + people={chemists} + /> + <ListSection + title="Everyone Else" + people={everyoneElse} + /> + </article> + ); +} +``` + +```js data.js +export const people = [{ + id: 0, + name: 'Creola Katherine Johnson', + profession: 'mathematician', + accomplishment: 'spaceflight calculations', + imageId: 'MK3eW3A' +}, { + id: 1, + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', + accomplishment: 'discovery of Arctic ozone hole', + imageId: 'mynHUSa' +}, { + id: 2, + name: 'Mohammad Abdus Salam', + profession: 'physicist', + accomplishment: 'electromagnetism theory', + imageId: 'bE7W1ji' +}, { + id: 3, + name: 'Percy Lavon Julian', + profession: 'chemist', + accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', + imageId: 'IOjWm71' +}, { + id: 4, + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', + accomplishment: 'white dwarf star mass calculations', + imageId: 'lrWQx8l' +}]; +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + 's.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +img { width: 100px; height: 100px; border-radius: 50%; } +``` + +</Sandpack> + +A very attentive reader might notice that with two `filter` calls, we check each person's profession twice. Checking a property is very fast, so in this example it's fine. If your logic was more expensive than that, you could replace the `filter` calls with a loop that manually constructs the arrays and checks each person once. + +In fact, if `people` never change, you could move this code out of your component. From React's perspective, all that matters is that you give it an array of JSX nodes in the end. It doesn't care how you produce that array: + +<Sandpack> + +```js App.js +import { people } from './data.js'; +import { getImageUrl } from './utils.js'; + +let chemists = []; +let everyoneElse = []; +people.forEach(person => { + if (person.profession === 'chemist') { + chemists.push(person); + } else { + everyoneElse.push(person); + } +}); + +function ListSection({ title, people }) { + return ( + <> + <h2>{title}</h2> + <ul> + {people.map(person => + <li key={person.id}> + <img + src={getImageUrl(person)} + alt={person.name} + /> + <p> + <b>{person.name}:</b> + {' ' + person.profession + ' '} + known for {person.accomplishment} + </p> + </li> + )} + </ul> + </> + ); +} + +export default function List() { + return ( + <article> + <h1>Scientists</h1> + <ListSection + title="Chemists" + people={chemists} + /> + <ListSection + title="Everyone Else" + people={everyoneElse} + /> + </article> + ); +} +``` + +```js data.js +export const people = [{ + id: 0, + name: 'Creola Katherine Johnson', + profession: 'mathematician', + accomplishment: 'spaceflight calculations', + imageId: 'MK3eW3A' +}, { + id: 1, + name: 'Mario José Molina-Pasquel Henríquez', + profession: 'chemist', + accomplishment: 'discovery of Arctic ozone hole', + imageId: 'mynHUSa' +}, { + id: 2, + name: 'Mohammad Abdus Salam', + profession: 'physicist', + accomplishment: 'electromagnetism theory', + imageId: 'bE7W1ji' +}, { + id: 3, + name: 'Percy Lavon Julian', + profession: 'chemist', + accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', + imageId: 'IOjWm71' +}, { + id: 4, + name: 'Subrahmanyan Chandrasekhar', + profession: 'astrophysicist', + accomplishment: 'white dwarf star mass calculations', + imageId: 'lrWQx8l' +}]; +``` + +```js utils.js +export function getImageUrl(person) { + return ( + 'https://i.imgur.com/' + + person.imageId + + 's.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +img { width: 100px; height: 100px; border-radius: 50%; } +``` + +</Sandpack> + +</Solution> + +#### Nested lists in one component {/*nested-lists-in-one-component*/} + +Make a list of recipes from this array! For each recipe in the array, display its name as an `<h2>` and list its ingredients in a `<ul>`. + +<Hint> + +This will require nesting two different `map` calls. + +</Hint> + +<Sandpack> + +```js App.js +import { recipes } from './data.js'; + +export default function RecipeList() { + return ( + <div> + <h1>Recipes</h1> + </div> + ); +} +``` + +```js data.js +export const recipes = [{ + id: 'greek-salad', + name: 'Greek Salad', + ingredients: ['tomatoes', 'cucumber', 'onion', 'olives', 'feta'] +}, { + id: 'hawaiian-pizza', + name: 'Hawaiian Pizza', + ingredients: ['pizza crust', 'pizza sauce', 'mozzarella', 'ham', 'pineapple'] +}, { + id: 'hummus', + name: 'Hummus', + ingredients: ['chickpeas', 'olive oil', 'garlic cloves', 'lemon', 'tahini'] +}]; +``` + +</Sandpack> + +<Solution> + +Here is one way you could go about it: + +<Sandpack> + +```js App.js +import { recipes } from './data.js'; + +export default function RecipeList() { + return ( + <div> + <h1>Recipes</h1> + {recipes.map(recipe => + <div key={recipe.id}> + <h2>{recipe.name}</h2> + <ul> + {recipe.ingredients.map(ingredient => + <li key={ingredient}> + {ingredient} + </li> + )} + </ul> + </div> + )} + </div> + ); +} +``` + +```js data.js +export const recipes = [{ + id: 'greek-salad', + name: 'Greek Salad', + ingredients: ['tomatoes', 'cucumber', 'onion', 'olives', 'feta'] +}, { + id: 'hawaiian-pizza', + name: 'Hawaiian Pizza', + ingredients: ['pizza crust', 'pizza sauce', 'mozzarella', 'ham', 'pineapple'] +}, { + id: 'hummus', + name: 'Hummus', + ingredients: ['chickpeas', 'olive oil', 'garlic cloves', 'lemon', 'tahini'] +}]; +``` + +</Sandpack> + +Each of the `recipes` already includes an `id` field, so that's what the outer loop uses for its `key`. There is no ID you could use to loop over ingredients. However, it's reasonable to assume that the same ingredient won't be listed twice within the same recipe, so its name can serve as a `key`. Alternatively, you could change the data structure to add IDs, or use index as a `key` (with the caveat that you can't safely reorder ingredients). + +</Solution> + +#### Extracting a list item component {/*extracting-a-list-item-component*/} + +This `RecipeList` component contains two nested `map` calls. To simplify it, extract a `Recipe` component from it which will accept `id`, `name`, and `ingredients` props. Where do you place the outer `key` and why? + +<Sandpack> + +```js App.js +import { recipes } from './data.js'; + +export default function RecipeList() { + return ( + <div> + <h1>Recipes</h1> + {recipes.map(recipe => + <div key={recipe.id}> + <h2>{recipe.name}</h2> + <ul> + {recipe.ingredients.map(ingredient => + <li key={ingredient}> + {ingredient} + </li> + )} + </ul> + </div> + )} + </div> + ); +} +``` + +```js data.js +export const recipes = [{ + id: 'greek-salad', + name: 'Greek Salad', + ingredients: ['tomatoes', 'cucumber', 'onion', 'olives', 'feta'] +}, { + id: 'hawaiian-pizza', + name: 'Hawaiian Pizza', + ingredients: ['pizza crust', 'pizza sauce', 'mozzarella', 'ham', 'pineapple'] +}, { + id: 'hummus', + name: 'Hummus', + ingredients: ['chickpeas', 'olive oil', 'garlic cloves', 'lemon', 'tahini'] +}]; +``` + +</Sandpack> + +<Solution> + +You can copy-paste the JSX from the outer `map` into a new `Recipe` component and return that JSX. Then you can change `recipe.name` to `name`, `recipe.id` to `id`, and so on, and pass them as props to the `Recipe`: + +<Sandpack> + +```js +import { recipes } from './data.js'; + +function Recipe({ id, name, ingredients }) { + return ( + <div> + <h2>{name}</h2> + <ul> + {ingredients.map(ingredient => + <li key={ingredient}> + {ingredient} + </li> + )} + </ul> + </div> + ); +} + +export default function RecipeList() { + return ( + <div> + <h1>Recipes</h1> + {recipes.map(recipe => + <Recipe {...recipe} key={recipe.id} /> + )} + </div> + ); +} +``` + +```js data.js +export const recipes = [{ + id: 'greek-salad', + name: 'Greek Salad', + ingredients: ['tomatoes', 'cucumber', 'onion', 'olives', 'feta'] +}, { + id: 'hawaiian-pizza', + name: 'Hawaiian Pizza', + ingredients: ['pizza crust', 'pizza sauce', 'mozzarella', 'ham', 'pineapple'] +}, { + id: 'hummus', + name: 'Hummus', + ingredients: ['chickpeas', 'olive oil', 'garlic cloves', 'lemon', 'tahini'] +}]; +``` + +</Sandpack> + +Here, `<Recipe {...recipe} key={recipe.id} />` is a syntax shortcut saying "pass all properties of the `recipe` object as props to the `Recipe` component". You could also write each prop explicitly: `<Recipe id={recipe.id} name={recipe.name} ingredients={recipe.ingredients} key={recipe.id} />`. + +**Note that the `key` is specified on the `<Recipe>` itself rather than on the root `<div>` returned from `Recipe`.** This is because this `key` is needed directly within the context of the surrounding array. Previously, you had an array of `<div>`s so each of them needed a `key`, but now you have an array of `<Recipe>`s. In other words, when you extract a component, don't forget to leave the `key` outside the JSX you copy and paste. + +</Solution> + +#### List with a separator {/*list-with-a-separator*/} + +This example renders a famous haiku by Katsushika Hokusai, with each line wrapped in a `<p>` tag. Your job is to insert an `<hr />` separator between each paragraph. Your resulting structure should look like this: + +```js +<article> + <p>I write, erase, rewrite</p> + <hr /> + <p>Erase again, and then</p> + <hr /> + <p>A poppy blooms.</p> +</article> +``` + +A haiku only contains three lines, but your solution should work with any number of lines. Note that `<hr />` elements only appear *between* the `<p>` elements, not in the beginning or the end! + +<Sandpack> + +```js +const poem = { + lines: [ + 'I write, erase, rewrite', + 'Erase again, and then', + 'A poppy blooms.' + ] +}; + +export default function Poem() { + return ( + <article> + {poem.lines.map((line, index) => + <p key={index}> + {line} + </p> + )} + </article> + ); +} +``` + +```css +body { + text-align: center; +} +p { + font-family: Georgia, serif; + font-size: 20px; + font-style: italic; +} +hr { + margin: 0 120px 0 120px; + border: 1px dashed #45c3d8; +} +``` + +</Sandpack> + +(This is a rare case where index as a key is acceptable because a poem's lines will never reorder.) + +<Hint> + +You'll either need to convert `map` to a manual loop, or use a fragment. + +</Hint> + +<Solution> + +You can write a manual loop, inserting `<hr />` and `<p>...</p>` into the output array as you go: + +<Sandpack> + +```js +const poem = { + lines: [ + 'I write, erase, rewrite', + 'Erase again, and then', + 'A poppy blooms.' + ] +}; + +export default function Poem() { + let output = []; + + // Fill the output array + poem.lines.forEach((line, i) => { + output.push( + <hr key={i + '-separator'} /> + ); + output.push( + <p key={i + '-text'}> + {line} + </p> + ); + }); + // Remove the first <hr /> + output.shift(); + + return ( + <article> + {output} + </article> + ); +} +``` + +```css +body { + text-align: center; +} +p { + font-family: Georgia, serif; + font-size: 20px; + font-style: italic; +} +hr { + margin: 0 120px 0 120px; + border: 1px dashed #45c3d8; +} +``` + +</Sandpack> + +Using the original line index as a `key` doesn't work anymore because each separator and paragraph are now in the same array. However, you can give each of them a distinct key using a suffix, e.g. `key={i + '-text'}`. + +Alternatively, you could render a collection of fragments which contain `<hr />` and `<p>...</p>`. However, the `<>...</>` shorthand syntax doesn't support passing keys, so you'd have to write `<Fragment>` explicitly: + +<Sandpack> + +```js +import React, { Fragment } from 'react'; + +const poem = { + lines: [ + 'I write, erase, rewrite', + 'Erase again, and then', + 'A poppy blooms.' + ] +}; + +export default function Poem() { + return ( + <article> + {poem.lines.map((line, i) => + <Fragment key={i}> + {i > 0 && <hr />} + <p>{line}</p> + </Fragment> + )} + </article> + ); +} +``` + +```css +body { + text-align: center; +} +p { + font-family: Georgia, serif; + font-size: 20px; + font-style: italic; +} +hr { + margin: 0 120px 0 120px; + border: 1px dashed #45c3d8; +} +``` + +</Sandpack> + +Remember, fragments (often written as `<> </>`) let you group JSX nodes without adding extra `<div>`s! + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/responding-to-events.md b/beta/src/content/learn/responding-to-events.md new file mode 100644 index 000000000..1f3fa9233 --- /dev/null +++ b/beta/src/content/learn/responding-to-events.md @@ -0,0 +1,735 @@ +--- +title: Responding to Events +--- + +<Intro> + +React lets you add *event handlers* to your JSX. Event handlers are your own functions that will be triggered in response to interactions like clicking, hovering, focusing form inputs, and so on. + +</Intro> + +<YouWillLearn> + +* Different ways to write an event handler +* How to pass event handling logic from a parent component +* How events propagate and how to stop them + +</YouWillLearn> + +## Adding event handlers {/*adding-event-handlers*/} + +To add an event handler, you will first define a function and then [pass it as a prop](/learn/passing-props-to-a-component) to the appropriate JSX tag. For example, here is a button that doesn't do anything yet: + +<Sandpack> + +```js +export default function Button() { + return ( + <button> + I don't do anything + </button> + ); +} +``` + +</Sandpack> + +You can make it show a message when a user clicks by following these three steps: + +1. Declare a function called `handleClick` *inside* your `Button` component. +2. Implement the logic inside that function (use `alert` to show the message). +3. Add `onClick={handleClick}` to the `<button>` JSX. + +<Sandpack> + +```js +export default function Button() { + function handleClick() { + alert('You clicked me!'); + } + + return ( + <button onClick={handleClick}> + Click me + </button> + ); +} +``` + +```css +button { margin-right: 10px; } +``` + +</Sandpack> + +You defined the `handleClick` function and then [passed it as a prop](/learn/passing-props-to-a-component) to `<button>`. `handleClick` is an **event handler.** Event handler functions: + +* Are usually defined *inside* your components. +* Have names that start with `handle`, followed by the name of the event. + +By convention, it is common to name event handlers as `handle` followed by the event name. You'll often see `onClick={handleClick}`, `onMouseEnter={handleMouseEnter}`, and so on. + +Alternatively, you can define an event handler inline in the JSX: + +```jsx +<button onClick={function handleClick() { + alert('You clicked me!'); +}}> +``` + +Or, more concisely, using an arrow function: + +```jsx +<button onClick={() => { + alert('You clicked me!'); +}}> +``` + +All of these styles are equivalent. Inline event handlers are convenient for short functions. + +<Pitfall> + +Functions passed to event handlers must be passed, not called. For example: + +| passing a function (correct) | calling a function (incorrect) | +| -------------------------------- | ---------------------------------- | +| `<button onClick={handleClick}>` | `<button onClick={handleClick()}>` | + +The difference is subtle. In the first example, the `handleClick` function is passed as an `onClick` event handler. This tells React to remember it and only call your function when the user clicks the button. + +In the second example, the `()` at the end of `handleClick()` fires the function *immediately* during [rendering](/learn/render-and-commit), without any clicks. This is because JavaScript inside the [JSX `{` and `}`](/learn/javascript-in-jsx-with-curly-braces) executes right away. + +When you write code inline, the same pitfall presents itself in a different way: + +| passing a function (correct) | calling a function (incorrect) | +| --------------------------------------- | --------------------------------- | +| `<button onClick={() => alert('...')}>` | `<button onClick={alert('...')}>` | + + +Passing inline code like this won't fire on click—it fires every time the component renders: + +```jsx +// This alert fires when the component renders, not when clicked! +<button onClick={alert('You clicked me!')}> +``` + +If you want to define your event handler inline, wrap it in an anonymous function like so: + +```jsx +<button onClick={() => alert('You clicked me!')}> +``` + +Rather than executing the code inside with every render, this creates a function to be called later. + +In both cases, what you want to pass is a function: + +* `<button onClick={handleClick}>` passes the `handleClick` function. +* `<button onClick={() => alert('...')}>` passes the `() => alert('...')` function. + +[Read more about arrow functions.](https://javascript.info/arrow-functions-basics) + +</Pitfall> + +### Reading props in event handlers {/*reading-props-in-event-handlers*/} + +Because event handlers are declared inside of a component, they have access to the component's props. Here is a button that, when clicked, shows an alert with its `message` prop: + +<Sandpack> + +```js +function AlertButton({ message, children }) { + return ( + <button onClick={() => alert(message)}> + {children} + </button> + ); +} + +export default function Toolbar() { + return ( + <div> + <AlertButton message="Playing!"> + Play Movie + </AlertButton> + <AlertButton message="Uploading!"> + Upload Image + </AlertButton> + </div> + ); +} +``` + +```css +button { margin-right: 10px; } +``` + +</Sandpack> + +This lets these two buttons show different messages. Try changing the messages passed to them. + +### Passing event handlers as props {/*passing-event-handlers-as-props*/} + +Often you'll want the parent component to specify a child's event handler. Consider buttons: depending on where you're using a `Button` component, you might want to execute a different function—perhaps one plays a movie and another uploads an image. + +To do this, pass a prop the component receives from its parent as the event handler like so: + +<Sandpack> + +```js +function Button({ onClick, children }) { + return ( + <button onClick={onClick}> + {children} + </button> + ); +} + +function PlayButton({ movieName }) { + function handlePlayClick() { + alert(`Playing ${movieName}!`); + } + + return ( + <Button onClick={handlePlayClick}> + Play "{movieName}" + </Button> + ); +} + +function UploadButton() { + return ( + <Button onClick={() => alert('Uploading!')}> + Upload Image + </Button> + ); +} + +export default function Toolbar() { + return ( + <div> + <PlayButton movieName="Kiki's Delivery Service" /> + <UploadButton /> + </div> + ); +} +``` + +```css +button { margin-right: 10px; } +``` + +</Sandpack> + +Here, the `Toolbar` component renders a `PlayButton` and an `UploadButton`: + +- `PlayButton` passes `handlePlayClick` as the `onClick` prop to the `Button` inside. +- `UploadButton` passes `() => alert('Uploading!')` as the `onClick` prop to the `Button` inside. + +Finally, your `Button` component accepts a prop called `onClick`. It passes that prop directly to the built-in browser `<button>` with `onClick={onClick}`. This tells React to call the passed function on click. + +If you use a [design system](https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969), it's common for components like buttons to contain styling but not specify behavior. Instead, components like `PlayButton` and `UploadButton` will pass event handlers down. + +### Naming event handler props {/*naming-event-handler-props*/} + +Built-in components like `<button>` and `<div>` only support [browser event names](/reference/react-dom/components/common#common-props) like `onClick`. However, when you're building your own components, you can name their event handler props any way that you like. + +By convention, event handler props should start with `on`, followed by a capital letter. + +For example, the `Button` component's `onClick` prop could have been called `onSmash`: + +<Sandpack> + +```js +function Button({ onSmash, children }) { + return ( + <button onClick={onSmash}> + {children} + </button> + ); +} + +export default function App() { + return ( + <div> + <Button onSmash={() => alert('Playing!')}> + Play Movie + </Button> + <Button onSmash={() => alert('Uploading!')}> + Upload Image + </Button> + </div> + ); +} +``` + +```css +button { margin-right: 10px; } +``` + +</Sandpack> + +In this example, `<button onClick={onSmash}>` shows that the browser `<button>` (lowercase) still needs a prop called `onClick`, but the prop name received by your custom `Button` component is up to you! + +When your component supports multiple interactions, you might name event handler props for app-specific concepts. For example, this `Toolbar` component receives `onPlayMovie` and `onUploadImage` event handlers: + +<Sandpack> + +```js +export default function App() { + return ( + <Toolbar + onPlayMovie={() => alert('Playing!')} + onUploadImage={() => alert('Uploading!')} + /> + ); +} + +function Toolbar({ onPlayMovie, onUploadImage }) { + return ( + <div> + <Button onClick={onPlayMovie}> + Play Movie + </Button> + <Button onClick={onUploadImage}> + Upload Image + </Button> + </div> + ); +} + +function Button({ onClick, children }) { + return ( + <button onClick={onClick}> + {children} + </button> + ); +} +``` + +```css +button { margin-right: 10px; } +``` + +</Sandpack> + +Notice how the `App` component does not need to know *what* `Toolbar` will do with `onPlayMovie` or `onUploadImage`. That's an implementation detail of the `Toolbar`. Here, `Toolbar` passes them down as `onClick` handlers to its `Button`s, but it could later also trigger them on a keyboard shortcut. Naming props after app-specific interactions like `onPlayMovie` gives you the flexibility to change how they're used later. + +## Event propagation {/*event-propagation*/} + +Event handlers will also catch events from any children your component might have. We say that an event "bubbles" or "propagates" up the tree: it starts with where the event happened, and then goes up the tree. + +This `<div>` contains two buttons. Both the `<div>` *and* each button have their own `onClick` handlers. Which handlers do you think will fire when you click a button? + +<Sandpack> + +```js +export default function Toolbar() { + return ( + <div className="Toolbar" onClick={() => { + alert('You clicked on the toolbar!'); + }}> + <button onClick={() => alert('Playing!')}> + Play Movie + </button> + <button onClick={() => alert('Uploading!')}> + Upload Image + </button> + </div> + ); +} +``` + +```css +.Toolbar { + background: #aaa; + padding: 5px; +} +button { margin: 5px; } +``` + +</Sandpack> + +If you click on either button, its `onClick` will run first, followed by the parent `<div>`'s `onClick`. So two messages will appear. If you click the toolbar itself, only the parent `<div>`'s `onClick` will run. + +<Pitfall> + +All events propagate in React except `onScroll`, which only works on the JSX tag you attach it to. + +</Pitfall> + +### Stopping propagation {/*stopping-propagation*/} + +Event handlers receive an **event object** as their only argument. By convention, it's usually called `e`, which stands for "event". You can use this object to read information about the event. + +That event object also lets you stop the propagation. If you want to prevent an event from reaching parent components, you need to call `e.stopPropagation()` like this `Button` component does: + +<Sandpack> + +```js +function Button({ onClick, children }) { + return ( + <button onClick={e => { + e.stopPropagation(); + onClick(); + }}> + {children} + </button> + ); +} + +export default function Toolbar() { + return ( + <div className="Toolbar" onClick={() => { + alert('You clicked on the toolbar!'); + }}> + <Button onClick={() => alert('Playing!')}> + Play Movie + </Button> + <Button onClick={() => alert('Uploading!')}> + Upload Image + </Button> + </div> + ); +} +``` + +```css +.Toolbar { + background: #aaa; + padding: 5px; +} +button { margin: 5px; } +``` + +</Sandpack> + +When you click on a button: + +1. React calls the `onClick` handler passed to `<button>`. +2. That handler, defined in `Button`, does the following: + * Calls `e.stopPropagation()`, preventing the event from bubbling further. + * Calls the `onClick` function, which is a prop passed from the `Toolbar` component. +3. That function, defined in the `Toolbar` component, displays the button's own alert. +4. Since the propagation was stopped, the parent `<div>`'s `onClick` handler does *not* run. + +As a result of `e.stopPropagation()`, clicking on the buttons now only shows a single alert (from the `<button>`) rather than the two of them (from the `<button>` and the parent toolbar `<div>`). Clicking a button is not the same thing as clicking the surrounding toolbar, so stopping the propagation makes sense for this UI. + +<DeepDive> + +#### Capture phase events {/*capture-phase-events*/} + +In rare cases, you might need to catch all events on child elements, *even if they stopped propagation*. For example, maybe you want to log every click to analytics, regardless of the propagation logic. You can do this by adding `Capture` at the end of the event name: + +```js +<div onClickCapture={() => { /* this runs first */ }}> + <button onClick={e => e.stopPropagation()} /> + <button onClick={e => e.stopPropagation()} /> +</div> +``` + +Each event propagates in three phases: + +1. It travels down, calling all `onClickCapture` handlers. +2. It runs the clicked element's `onClick` handler. +3. It travels upwards, calling all `onClick` handlers. + +Capture events are useful for code like routers or analytics, but you probably won't use them in app code. + +</DeepDive> + +### Passing handlers as alternative to propagation {/*passing-handlers-as-alternative-to-propagation*/} + +Notice how this click handler runs a line of code _and then_ calls the `onClick` prop passed by the parent: + +```js {4,5} +function Button({ onClick, children }) { + return ( + <button onClick={e => { + e.stopPropagation(); + onClick(); + }}> + {children} + </button> + ); +} +``` + +You could add more code to this handler before calling the parent `onClick` event handler, too. This pattern provides an *alternative* to propagation. It lets the child component handle the event, while also letting the parent component specify some additional behavior. Unlike propagation, it's not automatic. But the benefit of this pattern is that you can clearly follow the whole chain code that executes as a result of some event. + +If you rely on propagation and it's difficult to trace which handlers execute and why, try this approach instead. + +### Preventing default behavior {/*preventing-default-behavior*/} + +Some browser events have default behavior associated with them. For example, a `<form>` submit event, which happens when a button inside of it is clicked, will reload the whole page by default: + +<Sandpack> + +```js +export default function Signup() { + return ( + <form onSubmit={() => alert('Submitting!')}> + <input /> + <button>Send</button> + </form> + ); +} +``` + +```css +button { margin-left: 5px; } +``` + +</Sandpack> + +You can call `e.preventDefault()` on the event object to stop this from happening: + +<Sandpack> + +```js +export default function Signup() { + return ( + <form onSubmit={e => { + e.preventDefault(); + alert('Submitting!'); + }}> + <input /> + <button>Send</button> + </form> + ); +} +``` + +```css +button { margin-left: 5px; } +``` + +</Sandpack> + +Don't confuse `e.stopPropagation()` and `e.preventDefault()`. They are both useful, but are unrelated: + +* [`e.stopPropagation()`](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation) stops the event handlers attached to the tags above from firing. +* [`e.preventDefault()` ](https://developer.mozilla.org/docs/Web/API/Event/preventDefault) prevents the default browser behavior for the few events that have it. + +## Can event handlers have side effects? {/*can-event-handlers-have-side-effects*/} + +Absolutely! Event handlers are the best place for side effects. + +Unlike rendering functions, event handlers don't need to be [pure](/learn/keeping-components-pure), so it's a great place to *change* something—for example, change an input's value in response to typing, or change a list in response to a button press. However, in order to change some information, you first need some way to store it. In React, this is done by using [state, a component's memory.](/learn/state-a-components-memory) You will learn all about it on the next page. + +<Recap> + +* You can handle events by passing a function as a prop to an element like `<button>`. +* Event handlers must be passed, **not called!** `onClick={handleClick}`, not `onClick={handleClick()}`. +* You can define an event handler function separately or inline. +* Event handlers are defined inside a component, so they can access props. +* You can declare an event handler in a parent and pass it as a prop to a child. +* You can define your own event handler props with application-specific names. +* Events propagate upwards. Call `e.stopPropagation()` on the first argument to prevent that. +* Events may have unwanted default browser behavior. Call `e.preventDefault()` to prevent that. +* Explicitly calling an event handler prop from a child handler is a good alternative to propagation. + +</Recap> + + + +<Challenges> + +#### Fix an event handler {/*fix-an-event-handler*/} + +Clicking this button is supposed to switch the page background between white and black. However, nothing happens when you click it. Fix the problem. (Don't worry about the logic inside `handleClick`—that part is fine.) + +<Sandpack> + +```js +export default function LightSwitch() { + function handleClick() { + let bodyStyle = document.body.style; + if (bodyStyle.backgroundColor === 'black') { + bodyStyle.backgroundColor = 'white'; + } else { + bodyStyle.backgroundColor = 'black'; + } + } + + return ( + <button onClick={handleClick()}> + Toggle the lights + </button> + ); +} +``` + +</Sandpack> + +<Solution> + +The problem is that `<button onClick={handleClick()}>` _calls_ the `handleClick` function while rendering instead of _passing_ it. Removing the `()` call so that it's `<button onClick={handleClick}>` fixes the issue: + +<Sandpack> + +```js +export default function LightSwitch() { + function handleClick() { + let bodyStyle = document.body.style; + if (bodyStyle.backgroundColor === 'black') { + bodyStyle.backgroundColor = 'white'; + } else { + bodyStyle.backgroundColor = 'black'; + } + } + + return ( + <button onClick={handleClick}> + Toggle the lights + </button> + ); +} +``` + +</Sandpack> + +Alternatively, you could wrap the call into another function, like `<button onClick={() => handleClick()}>`: + +<Sandpack> + +```js +export default function LightSwitch() { + function handleClick() { + let bodyStyle = document.body.style; + if (bodyStyle.backgroundColor === 'black') { + bodyStyle.backgroundColor = 'white'; + } else { + bodyStyle.backgroundColor = 'black'; + } + } + + return ( + <button onClick={() => handleClick()}> + Toggle the lights + </button> + ); +} +``` + +</Sandpack> + +</Solution> + +#### Wire up the events {/*wire-up-the-events*/} + +This `ColorSwitch` component renders a button. It's supposed to change the page color. Wire it up to the `onChangeColor` event handler prop it receives from the parent so that clicking the button changes the color. + +After you do this, notice that clicking the button also increments the page click counter. Your colleague who wrote the parent component insists that `onChangeColor` does not increment any counters. What else might be happening? Fix it so that clicking the button *only* changes the color, and does _not_ increment the counter. + +<Sandpack> + +```js ColorSwitch.js active +export default function ColorSwitch({ + onChangeColor +}) { + return ( + <button> + Change color + </button> + ); +} +``` + +```js App.js hidden +import { useState } from 'react'; +import ColorSwitch from './ColorSwitch.js'; + +export default function App() { + const [clicks, setClicks] = useState(0); + + function handleClickOutside() { + setClicks(c => c + 1); + } + + function getRandomLightColor() { + let r = 150 + Math.round(100 * Math.random()); + let g = 150 + Math.round(100 * Math.random()); + let b = 150 + Math.round(100 * Math.random()); + return `rgb(${r}, ${g}, ${b})`; + } + + function handleChangeColor() { + let bodyStyle = document.body.style; + bodyStyle.backgroundColor = getRandomLightColor(); + } + + return ( + <div style={{ width: '100%', height: '100%' }} onClick={handleClickOutside}> + <ColorSwitch onChangeColor={handleChangeColor} /> + <br /> + <br /> + <h2>Clicks on the page: {clicks}</h2> + </div> + ); +} +``` + +</Sandpack> + +<Solution> + +First, you need to add the event handler, like `<button onClick={onChangeColor}>`. + +However, this introduces the problem of the incrementing counter. If `onChangeColor` does not do this, as your colleague insists, then the problem is that this event propagates up, and some handler above does it. To solve this problem, you need to stop the propagation. But don't forget that you should still call `onChangeColor`. + +<Sandpack> + +```js ColorSwitch.js active +export default function ColorSwitch({ + onChangeColor +}) { + return ( + <button onClick={e => { + e.stopPropagation(); + onChangeColor(); + }}> + Change color + </button> + ); +} +``` + +```js App.js hidden +import { useState } from 'react'; +import ColorSwitch from './ColorSwitch.js'; + +export default function App() { + const [clicks, setClicks] = useState(0); + + function handleClickOutside() { + setClicks(c => c + 1); + } + + function getRandomLightColor() { + let r = 150 + Math.round(100 * Math.random()); + let g = 150 + Math.round(100 * Math.random()); + let b = 150 + Math.round(100 * Math.random()); + return `rgb(${r}, ${g}, ${b})`; + } + + function handleChangeColor() { + let bodyStyle = document.body.style; + bodyStyle.backgroundColor = getRandomLightColor(); + } + + return ( + <div style={{ width: '100%', height: '100%' }} onClick={handleClickOutside}> + <ColorSwitch onChangeColor={handleChangeColor} /> + <br /> + <br /> + <h2>Clicks on the page: {clicks}</h2> + </div> + ); +} +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/reusing-logic-with-custom-hooks.md b/beta/src/content/learn/reusing-logic-with-custom-hooks.md new file mode 100644 index 000000000..d4a138ed7 --- /dev/null +++ b/beta/src/content/learn/reusing-logic-with-custom-hooks.md @@ -0,0 +1,2492 @@ +--- +title: 'Reusing Logic with Custom Hooks' +--- + +<Intro> + +React comes with several built-in Hooks like `useState`, `useContext`, and `useEffect`. Sometimes, you'll wish that there was a Hook for some more specific purpose: for example, to fetch data, to keep track of whether the user is online, or to connect to a chat room. You might not find these Hooks in React, but you can create your own Hooks for your application's needs. + +</Intro> + +<YouWillLearn> + +- What custom Hooks are, and how to write your own +- How to reuse logic between components +- How to name and structure your custom Hooks +- When and why to extract custom Hooks + +</YouWillLearn> + +## Custom Hooks: Sharing logic between components {/*custom-hooks-sharing-logic-between-components*/} + +Imagine you're developing an app that heavily relies on the network (as most apps do). You want to warn the user if their network connection has accidentally gone off while they were using your app. How would you go about it? + +It seems like you'll need two things in your component: + +1. A piece of state that tracks whether the network is online. +2. An Effect that subscribes to the global [`online`](https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event) and [`offline`](https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event) events, and updates that state. + +This will keep your component [synchronized](/learn/synchronizing-with-effects) with the network status. You might start with something like this: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function StatusBar() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function handleOnline() { + setIsOnline(true); + } + function handleOffline() { + setIsOnline(false); + } + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} +``` + +</Sandpack> + +Try turning your network on and off, and notice how this `StatusBar` updates in response to your actions. + +Now imagine you *also* want to use the same logic in a different component. You want to implement a Save button that will become disabled and show "Reconnecting..." instead of "Save" while the network is off. + +To start, you can copy and paste the `isOnline` state and the Effect into `SaveButton`: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function SaveButton() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function handleOnline() { + setIsOnline(true); + } + function handleOffline() { + setIsOnline(false); + } + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + + function handleSaveClick() { + console.log('✅ Progress saved'); + } + + return ( + <button disabled={!isOnline} onClick={handleSaveClick}> + {isOnline ? 'Save progress' : 'Reconnecting...'} + </button> + ); +} +``` + +</Sandpack> + +Verify that, if you turn off the network, the button will change its appearance. + +These two components work fine, but the duplication in logic between them is unfortunate. It seems like even though they have different *visual appearance,* you want to reuse the logic between them. + +### Extracting your own custom Hook from a component {/*extracting-your-own-custom-hook-from-a-component*/} + +Imagine for a moment that, similar to [`useState`](/reference/react/useState) and [`useEffect`](/reference/react/useEffect), there was a built-in `useOnlineStatus` Hook. Then both of these components could be simplified and you could remove the duplication between them: + +```js {2,7} +function StatusBar() { + const isOnline = useOnlineStatus(); + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + + function handleSaveClick() { + console.log('✅ Progress saved'); + } + + return ( + <button disabled={!isOnline} onClick={handleSaveClick}> + {isOnline ? 'Save progress' : 'Reconnecting...'} + </button> + ); +} +``` + +Although there is no such built-in Hook, you can write it yourself. Declare a function called `useOnlineStatus` and move all the duplicated code into it from the components you wrote earlier: + +```js {2-16} +function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function handleOnline() { + setIsOnline(true); + } + function handleOffline() { + setIsOnline(false); + } + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + return isOnline; +} +``` + +At the end of the function, return `isOnline`. This lets your components read that value: + +<Sandpack> + +```js +import { useOnlineStatus } from './useOnlineStatus.js'; + +function StatusBar() { + const isOnline = useOnlineStatus(); + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + + function handleSaveClick() { + console.log('✅ Progress saved'); + } + + return ( + <button disabled={!isOnline} onClick={handleSaveClick}> + {isOnline ? 'Save progress' : 'Reconnecting...'} + </button> + ); +} + +export default function App() { + return ( + <> + <SaveButton /> + <StatusBar /> + </> + ); +} +``` + +```js useOnlineStatus.js +import { useState, useEffect } from 'react'; + +export function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function handleOnline() { + setIsOnline(true); + } + function handleOffline() { + setIsOnline(false); + } + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + return isOnline; +} +``` + +</Sandpack> + +Verify that switching the network on and off updates both components. + +Now your components don't have as much repetitive logic. **More importantly, the code inside them describes *what they want to do* (use the online status!) rather than *how to do it* (by subscribing to the browser events).** + +When you extract logic into custom Hooks, you can hide the gnarly details of how you deal with some external system or a browser API. The code of your components expresses your intent, not the implementation. + +### Hook names always start with `use` {/*hook-names-always-start-with-use*/} + +React applications are built from components. Components are built from Hooks, whether built-in or custom. You'll likely often use custom Hooks created by others, but occasionally you might write one yourself! + +You must follow these naming conventions: + +1. **React component names must start with a capital letter,** like `StatusBar` and `SaveButton`. React components also need to return something that React knows how to display, like a piece of JSX. +2. **Hook names must start with `use` followed by a capital letter,** like [`useState`](/reference/react/useState) (built-in) or `useOnlineStatus` (custom, like earlier on the page). Hooks may return arbitrary values. + +This convention guarantees that you can always look at a component and know where its state, Effects, and other React features might "hide". For example, if you see a `getColor()` function call inside your component, you can be sure that it can't possibly contain React state inside because its name doesn't start with `use`. However, a function call like `useOnlineStatus()` will most likely contain calls to other Hooks inside! + +<Note> + +If your linter is [configured for React,](/learn/editor-setup#linting) it will enforce this naming convention. Scroll up to the sandbox above and rename `useOnlineStatus` to `getOnlineStatus`. Notice that the linter won't allow you to call `useState` or `useEffect` inside of it anymore. Only Hooks and components can call other Hooks! + +</Note> + +<DeepDive> + +#### Should all functions called during rendering start with the use prefix? {/*should-all-functions-called-during-rendering-start-with-the-use-prefix*/} + +No. Functions that don't *call* Hooks don't need to *be* Hooks. + +If your function doesn't call any Hooks, avoid the `use` prefix. Instead, write it as a regular function *without* the `use` prefix. For example, `useSorted` below doesn't call Hooks, so call it `getSorted` instead: + +```js +// 🔴 Avoid: A Hook that doesn't use Hooks +function useSorted(items) { + return items.slice().sort(); +} + +// ✅ Good: A regular function that doesn't use Hooks +function getSorted(items) { + return items.slice().sort(); +} +``` + +This ensures that your code can call this regular function anywhere, including conditions: + +```js +function List({ items, shouldSort }) { + let displayedItems = items; + if (shouldSort) { + // ✅ It's ok to call getSorted() conditionally because it's not a Hook + displayedItems = getSorted(items); + } + // ... +} +``` + +You should give `use` prefix to a function (and thus make it a Hook) if it uses at least one Hook inside of it: + +```js +// ✅ Good: A Hook that uses other Hooks +function useAuth() { + return useContext(Auth); +} +``` + +Technically, this isn't enforced by React. In principle, you could make a Hook that doesn't call other Hooks. This is often confusing and limiting so it's best to avoid that pattern. However, there may be rare cases where it is helpful. For example, maybe your function doesn't use any Hooks right now, but you plan to add some Hook calls to it in the future. Then it makes sense to name it with the `use` prefix: + +```js {3-4} +// ✅ Good: A Hook that will likely use some other Hooks later +function useAuth() { + // TODO: Replace with this line when authentication is implemented: + // return useContext(Auth); + return TEST_USER; +} +``` + +Then components won't be able to call it conditionally. This will become important when you actually add Hook calls inside. If you don't plan to use Hooks inside it (now or later), don't make it a Hook. + +</DeepDive> + +### Custom Hooks let you share stateful logic, not state itself {/*custom-hooks-let-you-share-stateful-logic-not-state-itself*/} + +In the earlier example, when you turned the network on and off, both components updated together. However, it's wrong to think that a single `isOnline` state variable is shared between them. Look at this code: + +```js {2,7} +function StatusBar() { + const isOnline = useOnlineStatus(); + // ... +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + // ... +} +``` + +It works the same way as before you extracted the duplication: + +```js {2-5,10-13} +function StatusBar() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + // ... + }, []); + // ... +} + +function SaveButton() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + // ... + }, []); + // ... +} +``` + +These are two completely independent state variables and Effects! They only happened to have the same value at the same time because you synchronized them with the same external value (whether the network is on). + +To better illustrate this, we'll need a different example. Consider this `Form` component: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [firstName, setFirstName] = useState('Mary'); + const [lastName, setLastName] = useState('Poppins'); + + function handleFirstNameChange(e) { + setFirstName(e.target.value); + } + + function handleLastNameChange(e) { + setLastName(e.target.value); + } + + return ( + <> + <label> + First name: + <input value={firstName} onChange={handleFirstNameChange} /> + </label> + <label> + Last name: + <input value={lastName} onChange={handleLastNameChange} /> + </label> + <p><b>Good morning, {firstName} {lastName}.</b></p> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 10px; } +``` + +</Sandpack> + +There's some repetitive logic for each form field: + +1. There's a piece of state (`firstName` and `lastName`). +1. There's a change handler (`handleFirstNameChange` and `handleLastNameChange`). +1. There's a piece of JSX that specifies the `value` and `onChange` attributes for that input. + +You can extract the repetitive logic into this `useFormInput` custom Hook: + +<Sandpack> + +```js +import { useFormInput } from './useFormInput.js'; + +export default function Form() { + const firstNameProps = useFormInput('Mary'); + const lastNameProps = useFormInput('Poppins'); + + return ( + <> + <label> + First name: + <input {...firstNameProps} /> + </label> + <label> + Last name: + <input {...lastNameProps} /> + </label> + <p><b>Good morning, {firstNameProps.value} {lastNameProps.value}.</b></p> + </> + ); +} +``` + +```js useFormInput.js active +import { useState } from 'react'; + +export function useFormInput(initialValue) { + const [value, setValue] = useState(initialValue); + + function handleChange(e) { + setValue(e.target.value); + } + + const inputProps = { + value: value, + onChange: handleChange + }; + + return inputProps; +} +``` + +```css +label { display: block; } +input { margin-left: 10px; } +``` + +</Sandpack> + +Notice that it only declares *one* state variable called `value`. + +However, the `Form` component calls `useFormInput` *two times:* + +```js +function Form() { + const firstNameProps = useFormInput('Mary'); + const lastNameProps = useFormInput('Poppins'); + // ... +``` + +This is why it works like declaring two separate state variables! + +**Custom Hooks let you share *stateful logic* but not *state itself.* Each call to a Hook is completely independent from every other call to the same Hook.** This is why the two sandboxes above are completely equivalent. If you'd like, scroll back up and compare them. The behavior before and after extracting a custom Hook is identical. + +When you need to share the state itself between multiple components, [lift it up and pass it down](/learn/sharing-state-between-components) instead. + +## Passing reactive values between Hooks {/*passing-reactive-values-between-hooks*/} + +The code inside your custom Hooks will re-run during every re-render of your component. This is why, like components, custom Hooks [need to be pure.](/learn/keeping-components-pure) Think of custom Hooks' code as part of your component's body! + +Because custom Hooks re-render together with your component, they always receive the latest props and state. To see what this means, consider this chat room example. Change the server URL or the selected chat room: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom + roomId={roomId} + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; +import { showNotification } from './notifications.js'; + +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.on('message', (msg) => { + showNotification('New message: ' + msg); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, serverUrl]); + + return ( + <> + <label> + Server URL: + <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + let intervalId; + let messageCallback; + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + clearInterval(intervalId); + intervalId = setInterval(() => { + if (messageCallback) { + if (Math.random() > 0.5) { + messageCallback('hey') + } else { + messageCallback('lol'); + } + } + }, 3000); + }, + disconnect() { + clearInterval(intervalId); + messageCallback = null; + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl + ''); + }, + on(event, callback) { + if (messageCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'message') { + throw Error('Only "message" event is supported.'); + } + messageCallback = callback; + }, + }; +} +``` + +```js notifications.js +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme = 'dark') { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +When you change `serverUrl` or `roomId`, the Effect ["reacts" to your changes](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) and re-synchronizes. You can tell by the console messages that the chat re-connects every time that you change your Effect's dependencies. + +Now move the Effect's code into a custom Hook: + +```js {2-13} +export function useChatRoom({ serverUrl, roomId }) { + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + connection.on('message', (msg) => { + showNotification('New message: ' + msg); + }); + return () => connection.disconnect(); + }, [roomId, serverUrl]); +} +``` + +This lets your `ChatRoom` component call your custom Hook without worrying about how it works inside: + +```js {4-7} +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl + }); + + return ( + <> + <label> + Server URL: + <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} +``` + +This looks much simpler! (But it does the same thing.) + +Notice that the logic *still responds* to prop and state changes. Try editing the server URL or the selected room: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom + roomId={roomId} + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState } from 'react'; +import { useChatRoom } from './useChatRoom.js'; + +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl + }); + + return ( + <> + <label> + Server URL: + <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} +``` + +```js useChatRoom.js +import { useEffect } from 'react'; +import { createConnection } from './chat.js'; +import { showNotification } from './notifications.js'; + +export function useChatRoom({ serverUrl, roomId }) { + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + connection.on('message', (msg) => { + showNotification('New message: ' + msg); + }); + return () => connection.disconnect(); + }, [roomId, serverUrl]); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + let intervalId; + let messageCallback; + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + clearInterval(intervalId); + intervalId = setInterval(() => { + if (messageCallback) { + if (Math.random() > 0.5) { + messageCallback('hey') + } else { + messageCallback('lol'); + } + } + }, 3000); + }, + disconnect() { + clearInterval(intervalId); + messageCallback = null; + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl + ''); + }, + on(event, callback) { + if (messageCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'message') { + throw Error('Only "message" event is supported.'); + } + messageCallback = callback; + }, + }; +} +``` + +```js notifications.js +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme = 'dark') { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Notice how you're taking the return value of one Hook: + +```js {2} +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl + }); + // ... +``` + +and pass it as an input to another Hook: + +```js {6} +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl + }); + // ... +``` + +Every time your `ChatRoom` component re-renders, it passes the latest `roomId` and `serverUrl` to your Hook. This is why your Effect re-connects to the chat whenever their values are different after a re-render. (If you ever worked with music processing software, chaining Hooks like this might remind you of chaining multiple audio effects, like adding reverb or chorus. It's as if the output of `useState` "feeds into" the input of the `useChatRoom`.) + +### Passing event handlers to custom Hooks {/*passing-event-handlers-to-custom-hooks*/} + +<Wip> + +This section describes an **experimental API that has not yet been added to React,** so you can't use it yet. + +</Wip> + +As you start using `useChatRoom` in more components, you might want to let different components customize its behavior. For example, currently, the logic for what to do when a message arrives is hardcoded inside the Hook: + +```js {9-11} +export function useChatRoom({ serverUrl, roomId }) { + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + connection.on('message', (msg) => { + showNotification('New message: ' + msg); + }); + return () => connection.disconnect(); + }, [roomId, serverUrl]); +} +``` + +Let's say you want to move this logic back to your component: + +```js {7-9} +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl, + onReceiveMessage(msg) { + showNotification('New message: ' + msg); + } + }); + // ... +``` + +To make this work, change your custom Hook to take `onReceiveMessage` as one of its named options: + +```js {1,10,13} +export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) { + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + connection.on('message', (msg) => { + onReceiveMessage(msg); + }); + return () => connection.disconnect(); + }, [roomId, serverUrl, onReceiveMessage]); // ✅ All dependencies declared +} +``` + +This will work, but there's one more improvement you can do when your custom Hook accepts event handlers. + +Adding a dependency on `onReceiveMessage` is not ideal because it will cause the chat to re-connect every time the component re-renders. [Wrap this event handler into an Effect Event to remove it from the dependencies:](/learn/removing-effect-dependencies#wrapping-an-event-handler-from-the-props) + +```js {1,4,5,15,18} +import { useEffect, useEffectEvent } from 'react'; +// ... + +export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) { + const onMessage = useEffectEvent(onReceiveMessage); + + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + connection.on('message', (msg) => { + onMessage(msg); + }); + return () => connection.disconnect(); + }, [roomId, serverUrl]); // ✅ All dependencies declared +} +``` + +Now the chat won't re-connect every time that the `ChatRoom` component re-renders. Here is a fully working demo of passing an event handler to a custom Hook that you can play with: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom + roomId={roomId} + /> + </> + ); +} +``` + +```js ChatRoom.js active +import { useState } from 'react'; +import { useChatRoom } from './useChatRoom.js'; +import { showNotification } from './notifications.js'; + +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl, + onReceiveMessage(msg) { + showNotification('New message: ' + msg); + } + }); + + return ( + <> + <label> + Server URL: + <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} +``` + +```js useChatRoom.js +import { useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { createConnection } from './chat.js'; + +export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) { + const onMessage = useEffectEvent(onReceiveMessage); + + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + connection.on('message', (msg) => { + onMessage(msg); + }); + return () => connection.disconnect(); + }, [roomId, serverUrl]); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + if (typeof serverUrl !== 'string') { + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + } + if (typeof roomId !== 'string') { + throw Error('Expected roomId to be a string. Received: ' + roomId); + } + let intervalId; + let messageCallback; + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + clearInterval(intervalId); + intervalId = setInterval(() => { + if (messageCallback) { + if (Math.random() > 0.5) { + messageCallback('hey') + } else { + messageCallback('lol'); + } + } + }, 3000); + }, + disconnect() { + clearInterval(intervalId); + messageCallback = null; + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl + ''); + }, + on(event, callback) { + if (messageCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'message') { + throw Error('Only "message" event is supported.'); + } + messageCallback = callback; + }, + }; +} +``` + +```js notifications.js +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme = 'dark') { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Notice how you no longer need to know *how* `useChatRoom` works in order to use it. You could add it to any other component, pass any other options, and it would work the same way. That's the power of custom Hooks. + +## When to use custom Hooks {/*when-to-use-custom-hooks*/} + +You don't need to extract a custom Hook for every little duplicated bit of code. Some duplication is fine. For example, extracting a `useFormInput` Hook to wrap a single `useState` call like earlier is probably unnecessary. + +However, whenever you write an Effect, consider whether it would be clearer to also wrap it in a custom Hook. [You shouldn't need Effects very often,](/learn/you-might-not-need-an-effect) so if you're writing one, it means that you need to "step outside React" to synchronize with some external system or to do something that React doesn't have a built-in API for. Wrapping your Effect into a custom Hook lets you precisely communicate your intent and how the data flows through it. + +For example, consider a `ShippingForm` component that displays two dropdowns: one shows the list of cities, and another shows the list of areas in the selected city. You might start with some code that looks like this: + +```js {3-16,20-35} +function ShippingForm({ country }) { + const [cities, setCities] = useState(null); + // This Effect fetches cities for a country + useEffect(() => { + let ignore = false; + fetch(`/api/cities?country=${country}`) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setCities(json); + } + }); + return () => { + ignore = true; + }; + }, [country]); + + const [city, setCity] = useState(null); + const [areas, setAreas] = useState(null); + // This Effect fetches areas for the selected city + useEffect(() => { + if (city) { + let ignore = false; + fetch(`/api/areas?city=${city}`) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setAreas(json); + } + }); + return () => { + ignore = true; + }; + } + }, [city]); + + // ... +``` + +Although this code is quite repetitive, [it's correct to keep these Effects separate from each other.](/learn/removing-effect-dependencies#is-your-effect-doing-several-unrelated-things) They synchronize two different things, so you shouldn't merge them into one Effect. Instead, you can simplify the `ShippingForm` component above by extracting the common logic between them into your own `useData` Hook: + +```js {2-18} +function useData(url) { + const [data, setData] = useState(null); + useEffect(() => { + if (url) { + let ignore = false; + fetch(url) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setData(json); + } + }); + return () => { + ignore = true; + }; + } + }, [url]); + return data; +} +``` + +Now you can replace both Effects in the `ShippingForm` components with calls to `useData`: + +```js {2,4} +function ShippingForm({ country }) { + const cities = useData(`/api/cities?country=${country}`); + const [city, setCity] = useState(null); + const areas = useData(city ? `/api/areas?city=${city}` : null); + // ... +``` + +Extracting a custom Hook makes the data flow explicit. You feed the `url` in and you get the `data` out. By "hiding" your Effect inside `useData`, you also prevent someone working on the `ShippingForm` component from adding [unnecessary dependencies](/learn/removing-effect-dependencies) to it. Ideally, with time, most of your app's Effects will be in custom Hooks. + +<DeepDive> + +#### Keep your custom Hooks focused on concrete high-level use cases {/*keep-your-custom-hooks-focused-on-concrete-high-level-use-cases*/} + +Start by choosing your custom Hook's name. If you struggle to pick a clear name, it might mean that your Effect is too coupled to the rest of your component's logic, and is not yet ready to be extracted. + +Ideally, your custom Hook's name should be clear enough that even a person who doesn't write code often could have a good guess about what your custom Hook does, what it takes, and what it returns: + +* ✅ `useData(url)` +* ✅ `useImpressionLog(eventName, extraData)` +* ✅ `useChatRoom(options)` + +When you synchronize with an external system, your custom Hook name may be more technical and use jargon specific to that system. It's good as long as it would be clear to a person familiar with that system: + +* ✅ `useMediaQuery(query)` +* ✅ `useSocket(url)` +* ✅ `useIntersectionObserver(ref, options)` + +**Keep custom Hooks focused on concrete high-level use cases.** Avoid creating and using custom "lifecycle" Hooks that act as alternatives and convenience wrappers for the `useEffect` API itself: + +* 🔴 `useMount(fn)` +* 🔴 `useEffectOnce(fn)` +* 🔴 `useUpdateEffect(fn)` + +For example, this `useMount` Hook tries to ensure some code only runs "on mount": + +```js {4-5,14-15} +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + // 🔴 Avoid: using custom "lifecycle" Hooks + useMount(() => { + const connection = createConnection({ roomId, serverUrl }); + connection.connect(); + + post('/analytics/event', { eventName: 'visit_chat' }); + }); + // ... +} + +// 🔴 Avoid: creating custom "lifecycle" Hooks +function useMount(fn) { + useEffect(() => { + fn(); + }, []); // 🔴 React Hook useEffect has a missing dependency: 'fn' +} +``` + +**Custom "lifecycle" Hooks like `useMount` don't fit well into the React paradigm.** For example, this code example has a mistake (it doesn't "react" to `roomId` or `serverUrl` changes), but the linter won't warn you about it because the linter only checks direct `useEffect` calls. It won't know about your Hook. + +If you're writing an Effect, start by using the React API directly: + +```js +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + // ✅ Good: two raw Effects separated by purpose + + useEffect(() => { + const connection = createConnection({ serverUrl, roomId }); + connection.connect(); + return () => connection.disconnect(); + }, [serverUrl, roomId]); + + useEffect(() => { + post('/analytics/event', { eventName: 'visit_chat', roomId }); + }, [roomId]); + + // ... +} +``` + +Then, you can (but don't have to) extract custom Hooks for different high-level use cases: + +```js +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + // ✅ Great: custom Hooks named after their purpose + useChatRoom({ serverUrl, roomId }); + useImpressionLog('visit_chat', { roomId }); + // ... +} +``` + +**A good custom Hook makes the calling code more declarative by constraining what it does.** For example, `useChatRoom(options)` can only connect to the chat room, while `useImpressionLog(eventName, extraData)` can only send an impression log to the analytics. If your custom Hook API doesn't constrain the use cases and is very abstract, in the long run it's likely to introduce more problems than it solves. + +</DeepDive> + +### Custom Hooks help you migrate to better patterns {/*custom-hooks-help-you-migrate-to-better-patterns*/} + +Effects are an ["escape hatch"](/learn/escape-hatches): you use them when you need to "step outside React" and when there is no better built-in solution for your use case. With time, the React team's goal is to reduce the number of the Effects in your app to the minimum by providing more specific solutions to more specific problems. Wrapping Effects in custom Hooks makes it easier to upgrade your code when these solutions become available. Let's return to this example: + +<Sandpack> + +```js +import { useOnlineStatus } from './useOnlineStatus.js'; + +function StatusBar() { + const isOnline = useOnlineStatus(); + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + + function handleSaveClick() { + console.log('✅ Progress saved'); + } + + return ( + <button disabled={!isOnline} onClick={handleSaveClick}> + {isOnline ? 'Save progress' : 'Reconnecting...'} + </button> + ); +} + +export default function App() { + return ( + <> + <SaveButton /> + <StatusBar /> + </> + ); +} +``` + +```js useOnlineStatus.js active +import { useState, useEffect } from 'react'; + +export function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function handleOnline() { + setIsOnline(true); + } + function handleOffline() { + setIsOnline(false); + } + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + return isOnline; +} +``` + +</Sandpack> + +In the above example, `useOnlineStatus` is implemented with a pair of [`useState`](/reference/react/useState) and [`useEffect`.](/reference/react/useEffect) However, this isn't the best possible solution. There is a number of edge cases it doesn't consider. For example, it assumes that when the component mounts, `isOnline` is already `true`, but this may be wrong if the network already went offline. You can use the browser [`navigator.onLine`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine) API to check for that, but using it directly would break if you run your React app on the server to generate the initial HTML. In short, this code could be improved. + +Luckily, React 18 includes a dedicated API called [`useSyncExternalStore`](/reference/react/useSyncExternalStore) which takes care of all of these problems for you. Here is how your `useOnlineStatus` Hook, rewritten to take advantage of this new API: + +<Sandpack> + +```js +import { useOnlineStatus } from './useOnlineStatus.js'; + +function StatusBar() { + const isOnline = useOnlineStatus(); + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + + function handleSaveClick() { + console.log('✅ Progress saved'); + } + + return ( + <button disabled={!isOnline} onClick={handleSaveClick}> + {isOnline ? 'Save progress' : 'Reconnecting...'} + </button> + ); +} + +export default function App() { + return ( + <> + <SaveButton /> + <StatusBar /> + </> + ); +} +``` + +```js useOnlineStatus.js active +import { useSyncExternalStore } from 'react'; + +function subscribe(callback) { + window.addEventListener('online', callback); + window.addEventListener('offline', callback); + return () => { + window.removeEventListener('online', callback); + window.removeEventListener('offline', callback); + }; +} + +export function useOnlineStatus() { + return useSyncExternalStore( + subscribe, + () => navigator.onLine, // How to get the value on the client + () => true // How to get the value on the server + ); +} + +``` + +</Sandpack> + +Notice how **you didn't need to change any of the components** to make this migration: + +```js {2,7} +function StatusBar() { + const isOnline = useOnlineStatus(); + // ... +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + // ... +} +``` + +This is another reason for why wrapping Effects in custom Hooks is often beneficial: + +1. You make the data flow to and from your Effects very explicit. +2. You let your components focus on the intent rather than on the exact implementation of your Effects. +3. When React adds new features, you can remove those Effects without changing any of your components. + +Similar to a [design system,](https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969) you might find it helpful to start extracting common idioms from your app's components into custom Hooks. This will keep your components' code focused on the intent, and let you avoid writing raw Effects very often. There are also many excellent custom Hooks maintained by the React community. + +<DeepDive> + +#### Will React provide any built-in solution for data fetching? {/*will-react-provide-any-built-in-solution-for-data-fetching*/} + +We're still working out the details, but we expect that in the future, you'll write data fetching like this: + +```js {1,4,6} +import { use } from 'react'; // Not available yet! + +function ShippingForm({ country }) { + const cities = use(fetch(`/api/cities?country=${country}`)); + const [city, setCity] = useState(null); + const areas = city ? use(fetch(`/api/areas?city=${city}`)) : null; + // ... +``` + +If you use custom Hooks like `useData` above in your app, it will require fewer changes to migrate to the eventually recommended approach than if you write raw Effects in every component manually. However, the old approach will still work fine, so if you feel happy writing raw Effects, you can continue to do that. + +</DeepDive> + +### There is more than one way to do it {/*there-is-more-than-one-way-to-do-it*/} + +Let's say you want to implement a fade-in animation *from scratch* using the browser [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) API. You might start with an Effect that sets up an animation loop. During each frame of the animation, you could change the opacity of the DOM node you [hold in a ref](/learn/manipulating-the-dom-with-refs) until it reaches `1`. Your code might start like this: + +<Sandpack> + +```js +import { useState, useEffect, useRef } from 'react'; + +function Welcome() { + const ref = useRef(null); + + useEffect(() => { + const duration = 1000; + const node = ref.current; + + let startTime = performance.now(); + let frameId = null; + + function onFrame(now) { + const timePassed = now - startTime; + const progress = Math.min(timePassed / duration, 1); + onProgress(progress); + if (progress < 1) { + // We still have more frames to paint + frameId = requestAnimationFrame(onFrame); + } + } + + function onProgress(progress) { + node.style.opacity = progress; + } + + function start() { + onProgress(0); + startTime = performance.now(); + frameId = requestAnimationFrame(onFrame); + } + + function stop() { + cancelAnimationFrame(frameId); + startTime = null; + frameId = null; + } + + start(); + return () => stop(); + }, []); + + return ( + <h1 className="welcome" ref={ref}> + Welcome + </h1> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome />} + </> + ); +} +``` + +```css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +.welcome { + opacity: 0; + color: white; + padding: 50px; + text-align: center; + font-size: 50px; + background-image: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%); +} +``` + +</Sandpack> + +To make the component more readable, you might extract the logic into a `useFadeIn` custom Hook: + +<Sandpack> + +```js +import { useState, useEffect, useRef } from 'react'; +import { useFadeIn } from './useFadeIn.js'; + +function Welcome() { + const ref = useRef(null); + + useFadeIn(ref, 1000); + + return ( + <h1 className="welcome" ref={ref}> + Welcome + </h1> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome />} + </> + ); +} +``` + +```js useFadeIn.js +import { useEffect } from 'react'; + +export function useFadeIn(ref, duration) { + useEffect(() => { + const node = ref.current; + + let startTime = performance.now(); + let frameId = null; + + function onFrame(now) { + const timePassed = now - startTime; + const progress = Math.min(timePassed / duration, 1); + onProgress(progress); + if (progress < 1) { + // We still have more frames to paint + frameId = requestAnimationFrame(onFrame); + } + } + + function onProgress(progress) { + node.style.opacity = progress; + } + + function start() { + onProgress(0); + startTime = performance.now(); + frameId = requestAnimationFrame(onFrame); + } + + function stop() { + cancelAnimationFrame(frameId); + startTime = null; + frameId = null; + } + + start(); + return () => stop(); + }, [ref, duration]); +} +``` + +```css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +.welcome { + opacity: 0; + color: white; + padding: 50px; + text-align: center; + font-size: 50px; + background-image: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%); +} +``` + +</Sandpack> + +You could keep the `useFadeIn` code as is, but you could also refactor it more. For example, you could extract the logic for setting up the animation loop out of `useFadeIn` into a new custom Hook called `useAnimationLoop`: + +<Sandpack> + +```js +import { useState, useEffect, useRef } from 'react'; +import { useFadeIn } from './useFadeIn.js'; + +function Welcome() { + const ref = useRef(null); + + useFadeIn(ref, 1000); + + return ( + <h1 className="welcome" ref={ref}> + Welcome + </h1> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome />} + </> + ); +} +``` + +```js useFadeIn.js active +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export function useFadeIn(ref, duration) { + const [isRunning, setIsRunning] = useState(true); + + useAnimationLoop(isRunning, (timePassed) => { + const progress = Math.min(timePassed / duration, 1); + ref.current.style.opacity = progress; + if (progress === 1) { + setIsRunning(false); + } + }); +} + +function useAnimationLoop(isRunning, drawFrame) { + const onFrame = useEffectEvent(drawFrame); + + useEffect(() => { + if (!isRunning) { + return; + } + + const startTime = performance.now(); + let frameId = null; + + function tick(now) { + const timePassed = now - startTime; + onFrame(timePassed); + frameId = requestAnimationFrame(tick); + } + + tick(); + return () => cancelAnimationFrame(frameId); + }, [isRunning]); +} +``` + +```css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +.welcome { + opacity: 0; + color: white; + padding: 50px; + text-align: center; + font-size: 50px; + background-image: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +However, you didn't *have to* do that. As with regular functions, ultimately you decide where to draw the boundaries between different parts of your code. For example, you could also take a very different approach. Instead of keeping the logic in the Effect, you could move most of the imperative logic inside a JavaScript [class:](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) + +<Sandpack> + +```js +import { useState, useEffect, useRef } from 'react'; +import { useFadeIn } from './useFadeIn.js'; + +function Welcome() { + const ref = useRef(null); + + useFadeIn(ref, 1000); + + return ( + <h1 className="welcome" ref={ref}> + Welcome + </h1> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome />} + </> + ); +} +``` + +```js useFadeIn.js active +import { useState, useEffect } from 'react'; +import { FadeInAnimation } from './animation.js'; + +export function useFadeIn(ref, duration) { + useEffect(() => { + const animation = new FadeInAnimation(ref.current); + animation.start(duration); + return () => { + animation.stop(); + }; + }, [ref, duration]); +} +``` + +```js animation.js +export class FadeInAnimation { + constructor(node) { + this.node = node; + } + start(duration) { + this.duration = duration; + this.onProgress(0); + this.startTime = performance.now(); + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + onFrame() { + const timePassed = performance.now() - this.startTime; + const progress = Math.min(timePassed / this.duration, 1); + this.onProgress(progress); + if (progress === 1) { + this.stop(); + } else { + // We still have more frames to paint + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + } + onProgress(progress) { + this.node.style.opacity = progress; + } + stop() { + cancelAnimationFrame(this.frameId); + this.startTime = null; + this.frameId = null; + this.duration = 0; + } +} +``` + +```css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +.welcome { + opacity: 0; + color: white; + padding: 50px; + text-align: center; + font-size: 50px; + background-image: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%); +} +``` + +</Sandpack> + +Effects let you connect React to external systems. The more coordination between Effects is needed (for example, to chain multiple animations), the more it makes sense to extract that logic out of Effects and Hooks *completely* like in the sandbox above. Then, the code you extracted *becomes* the "external system". This lets your Effects stay simple because they only need to send messages to the system you've moved outside React. + +The examples above assume that the fade-in logic needs to be written in JavaScript. However, this particular fade-in animation is both simpler and much more efficient to implement with a plain [CSS Animation:](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations) + +<Sandpack> + +```js +import { useState, useEffect, useRef } from 'react'; +import './welcome.css'; + +function Welcome() { + return ( + <h1 className="welcome"> + Welcome + </h1> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome />} + </> + ); +} +``` + +```css styles.css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +``` + +```css welcome.css active +.welcome { + color: white; + padding: 50px; + text-align: center; + font-size: 50px; + background-image: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%); + + animation: fadeIn 1000ms; +} + +@keyframes fadeIn { + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +``` + +</Sandpack> + +Sometimes, you don't even need a Hook! + +<Recap> + +- Custom Hooks let you share logic between components. +- Custom Hooks must be named starting with `use` followed by a capital letter. +- Custom Hooks only share stateful logic, not state itself. +- You can pass reactive values from one Hook to another, and they stay up-to-date. +- All Hooks re-run every time your component re-renders. +- The code of your custom Hooks should be pure, like your component's code. +- Wrap event handlers received by custom Hooks into Effect Events. +- Don't create custom Hooks like `useMount`. Keep their purpose specific. +- It's up to you how and where to choose the boundaries of your code. + +</Recap> + +<Challenges> + +#### Extract a `useCounter` Hook {/*extract-a-usecounter-hook*/} + +This component uses a state variable and an Effect to display a number that increments every second. Extract this logic into a custom Hook called `useCounter`. Your goal is to make the `Counter` component implementation look exactly like this: + +```js +export default function Counter() { + const count = useCounter(); + return <h1>Seconds passed: {count}</h1>; +} +``` + +You'll need to write your custom Hook in `useCounter.js` and import it into the `Counter.js` file. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Counter() { + const [count, setCount] = useState(0); + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(id); + }, []); + return <h1>Seconds passed: {count}</h1>; +} +``` + +```js useCounter.js +// Write your custom Hook in this file! +``` + +</Sandpack> + +<Solution> + +Your code should look like this: + +<Sandpack> + +```js +import { useCounter } from './useCounter.js'; + +export default function Counter() { + const count = useCounter(); + return <h1>Seconds passed: {count}</h1>; +} +``` + +```js useCounter.js +import { useState, useEffect } from 'react'; + +export function useCounter() { + const [count, setCount] = useState(0); + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(id); + }, []); + return count; +} +``` + +</Sandpack> + +Notice that `App.js` doesn't need to import `useState` or `useEffect` anymore. + +</Solution> + +#### Make the counter delay configurable {/*make-the-counter-delay-configurable*/} + +In this example, there is a `delay` state variable controlled by a slider, but its value is not used. Pass the `delay` value to your custom `useCounter` Hook, and change the `useCounter` Hook to use the passed `delay` instead of hardcoding `1000` ms. + +<Sandpack> + +```js +import { useState } from 'react'; +import { useCounter } from './useCounter.js'; + +export default function Counter() { + const [delay, setDelay] = useState(1000); + const count = useCounter(); + return ( + <> + <label> + Tick duration: {delay} ms + <br /> + <input + type="range" + value={delay} + min="10" + max="2000" + onChange={e => setDelay(Number(e.target.value))} + /> + </label> + <hr /> + <h1>Ticks: {count}</h1> + </> + ); +} +``` + +```js useCounter.js +import { useState, useEffect } from 'react'; + +export function useCounter() { + const [count, setCount] = useState(0); + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(id); + }, []); + return count; +} +``` + +</Sandpack> + +<Solution> + +Pass the `delay` to your Hook with `useCounter(delay)`. Then, inside the Hook, use `delay` instead of the hardcoded `1000` value. You'll need to add `delay` to your Effect's dependencies. This ensures that a change in `delay` will reset the interval. + +<Sandpack> + +```js +import { useState } from 'react'; +import { useCounter } from './useCounter.js'; + +export default function Counter() { + const [delay, setDelay] = useState(1000); + const count = useCounter(delay); + return ( + <> + <label> + Tick duration: {delay} ms + <br /> + <input + type="range" + value={delay} + min="10" + max="2000" + onChange={e => setDelay(Number(e.target.value))} + /> + </label> + <hr /> + <h1>Ticks: {count}</h1> + </> + ); +} +``` + +```js useCounter.js +import { useState, useEffect } from 'react'; + +export function useCounter(delay) { + const [count, setCount] = useState(0); + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + 1); + }, delay); + return () => clearInterval(id); + }, [delay]); + return count; +} +``` + +</Sandpack> + +</Solution> + +#### Extract `useInterval` out of `useCounter` {/*extract-useinterval-out-of-usecounter*/} + +Currently, your `useCounter` Hook does two things. It sets up an interval, and it also increments a state variable on every interval tick. Split out the logic that sets up the interval into a separate Hook called `useInterval`. It should take two arguments: the `onTick` callback, and the `delay`. After this change, your `useCounter` implementation should look like this: + +```js +export function useCounter(delay) { + const [count, setCount] = useState(0); + useInterval(() => { + setCount(c => c + 1); + }, delay); + return count; +} +``` + +Write `useInterval` in the `useInterval.js` file and import it into the `useCounter.js` file. + +<Sandpack> + +```js +import { useState } from 'react'; +import { useCounter } from './useCounter.js'; + +export default function Counter() { + const count = useCounter(1000); + return <h1>Seconds passed: {count}</h1>; +} +``` + +```js useCounter.js +import { useState, useEffect } from 'react'; + +export function useCounter(delay) { + const [count, setCount] = useState(0); + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + 1); + }, delay); + return () => clearInterval(id); + }, [delay]); + return count; +} +``` + +```js useInterval.js +// Write your Hook here! +``` + +</Sandpack> + +<Solution> + +The logic inside `useInterval` should set up and clear the interval. It doesn't need to do anything else. + +<Sandpack> + +```js +import { useCounter } from './useCounter.js'; + +export default function Counter() { + const count = useCounter(1000); + return <h1>Seconds passed: {count}</h1>; +} +``` + +```js useCounter.js +import { useState } from 'react'; +import { useInterval } from './useInterval.js'; + +export function useCounter(delay) { + const [count, setCount] = useState(0); + useInterval(() => { + setCount(c => c + 1); + }, delay); + return count; +} +``` + +```js useInterval.js active +import { useEffect } from 'react'; + +export function useInterval(onTick, delay) { + useEffect(() => { + const id = setInterval(onTick, delay); + return () => clearInterval(id); + }, [onTick, delay]); +} +``` + +</Sandpack> + +Note that there is a bit of a problem with this solution, which you'll solve in the next challenge. + +</Solution> + +#### Fix a resetting interval {/*fix-a-resetting-interval*/} + +In this example, there are *two* separate intervals. + +The `App` component calls `useCounter`, which calls `useInterval` to update the counter every second. But the `App` component *also* calls `useInterval` to randomly update the page background color every two seconds. + +For some reason, the callback that updates the page background never runs. Add some logs inside `useInterval`: + +```js {2,5} + useEffect(() => { + console.log('✅ Setting up an interval with delay ', delay) + const id = setInterval(onTick, delay); + return () => { + console.log('❌ Clearing an interval with delay ', delay) + clearInterval(id); + }; + }, [onTick, delay]); +``` + +Do the logs match what you expect to happen? If some of your Effects seem to re-synchronize unnecessarily, can you guess which dependency is causing that to happen? Is there some way to [remove that dependency](/learn/removing-effect-dependencies) from your Effect? + +After you fix the issue, you should expect the page background to update every two seconds. + +<Hint> + +It looks like your `useInterval` Hook accepts an event listener as an argument. Can you think of some way to wrap that event listener so that it doesn't need to be a dependency of your Effect? + +</Hint> + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useCounter } from './useCounter.js'; +import { useInterval } from './useInterval.js'; + +export default function Counter() { + const count = useCounter(1000); + + useInterval(() => { + const randomColor = `hsla(${Math.random() * 360}, 100%, 50%, 0.2)`; + document.body.style.backgroundColor = randomColor; + }, 2000); + + return <h1>Seconds passed: {count}</h1>; +} +``` + +```js useCounter.js +import { useState } from 'react'; +import { useInterval } from './useInterval.js'; + +export function useCounter(delay) { + const [count, setCount] = useState(0); + useInterval(() => { + setCount(c => c + 1); + }, delay); + return count; +} +``` + +```js useInterval.js +import { useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export function useInterval(onTick, delay) { + useEffect(() => { + const id = setInterval(onTick, delay); + return () => { + clearInterval(id); + }; + }, [onTick, delay]); +} +``` + +</Sandpack> + +<Solution> + +Inside `useInterval`, wrap the tick callback into an Effect Event, as you did [earlier on this page.](/learn/reusing-logic-with-custom-hooks#passing-event-handlers-to-custom-hooks) + +This will allow you to omit `onTick` from dependencies of your Effect. The Effect won't re-synchronize on every re-render of the component, so the page background color change interval won't get reset every second before it has a chance to fire. + +With this change, both intervals work as expected and don't interfere with each other: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + + +```js +import { useCounter } from './useCounter.js'; +import { useInterval } from './useInterval.js'; + +export default function Counter() { + const count = useCounter(1000); + + useInterval(() => { + const randomColor = `hsla(${Math.random() * 360}, 100%, 50%, 0.2)`; + document.body.style.backgroundColor = randomColor; + }, 2000); + + return <h1>Seconds passed: {count}</h1>; +} +``` + +```js useCounter.js +import { useState } from 'react'; +import { useInterval } from './useInterval.js'; + +export function useCounter(delay) { + const [count, setCount] = useState(0); + useInterval(() => { + setCount(c => c + 1); + }, delay); + return count; +} +``` + +```js useInterval.js active +import { useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export function useInterval(callback, delay) { + const onTick = useEffectEvent(callback); + useEffect(() => { + const id = setInterval(onTick, delay); + return () => clearInterval(id); + }, [delay]); +} +``` + +</Sandpack> + +</Solution> + +#### Implement a staggering movement {/*implement-a-staggering-movement*/} + +In this example, the `usePointerPosition()` Hook tracks the current pointer position. Try moving your cursor or your finger over the preview area and see the red dot follow your movement. Its position is saved in the `pos1` variable. + +In fact, there are five (!) different red dots being rendered. You don't see them because currently they all appear at the same position. This is what you need to fix. What you want to implement instead is a "staggered" movement: each dot should "follow" the previous dot's path. For example, if you quickly move your cursor, the first dot should follow it immediately, the second dot should follow the first dot with a small delay, the third dot should follow the second dot, and so on. + +You need to implement the `useDelayedValue` custom Hook. Its current implementation returns the `value` provided to it. Instead, you want to return the value back from `delay` milliseconds ago. You might need some state and an Effect to do this. + +After you implement `useDelayedValue`, you should see the dots move following one another. + +<Hint> + +You'll need to store the `delayedValue` as a state variable inside your custom Hook. When the `value` changes, you'll want to run an Effect. This Effect should update `delayedValue` after the `delay`. You might find it helpful to call `setTimeout`. + +Does this Effect need cleanup? Why or why not? + +</Hint> + +<Sandpack> + +```js +import { usePointerPosition } from './usePointerPosition.js'; + +function useDelayedValue(value, delay) { + // TODO: Implement this Hook + return value; +} + +export default function Canvas() { + const pos1 = usePointerPosition(); + const pos2 = useDelayedValue(pos1, 100); + const pos3 = useDelayedValue(pos2, 200); + const pos4 = useDelayedValue(pos3, 100); + const pos5 = useDelayedValue(pos3, 50); + return ( + <> + <Dot position={pos1} opacity={1} /> + <Dot position={pos2} opacity={0.8} /> + <Dot position={pos3} opacity={0.6} /> + <Dot position={pos4} opacity={0.4} /> + <Dot position={pos5} opacity={0.2} /> + </> + ); +} + +function Dot({ position, opacity }) { + return ( + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + ); +} +``` + +```js usePointerPosition.js +import { useState, useEffect } from 'react'; + +export function usePointerPosition() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + useEffect(() => { + function handleMove(e) { + setPosition({ x: e.clientX, y: e.clientY }); + } + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + }, []); + return position; +} +``` + +```css +body { min-height: 300px; } +``` + +</Sandpack> + +<Solution> + +Here is a working version. You keep the `delayedValue` as a state variable. When `value` updates, your Effect schedules a timeout to update the `delayedValue`. This is why the `delayedValue` always "lags behind" the actual `value`. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { usePointerPosition } from './usePointerPosition.js'; + +function useDelayedValue(value, delay) { + const [delayedValue, setDelayedValue] = useState(value); + + useEffect(() => { + setTimeout(() => { + setDelayedValue(value); + }, delay); + }, [value, delay]); + + return delayedValue; +} + +export default function Canvas() { + const pos1 = usePointerPosition(); + const pos2 = useDelayedValue(pos1, 100); + const pos3 = useDelayedValue(pos2, 200); + const pos4 = useDelayedValue(pos3, 100); + const pos5 = useDelayedValue(pos3, 50); + return ( + <> + <Dot position={pos1} opacity={1} /> + <Dot position={pos2} opacity={0.8} /> + <Dot position={pos3} opacity={0.6} /> + <Dot position={pos4} opacity={0.4} /> + <Dot position={pos5} opacity={0.2} /> + </> + ); +} + +function Dot({ position, opacity }) { + return ( + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + ); +} +``` + +```js usePointerPosition.js +import { useState, useEffect } from 'react'; + +export function usePointerPosition() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + useEffect(() => { + function handleMove(e) { + setPosition({ x: e.clientX, y: e.clientY }); + } + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + }, []); + return position; +} +``` + +```css +body { min-height: 300px; } +``` + +</Sandpack> + +Note that this Effect *does not* need cleanup. If you called `clearTimeout` in the cleanup function, then each time the `value` changes, it would reset the already scheduled timeout. To keep the movement continuous, you want all the timeouts to fire. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/scaling-up-with-reducer-and-context.md b/beta/src/content/learn/scaling-up-with-reducer-and-context.md new file mode 100644 index 000000000..b37f60b42 --- /dev/null +++ b/beta/src/content/learn/scaling-up-with-reducer-and-context.md @@ -0,0 +1,1362 @@ +--- +title: Scaling Up with Reducer and Context +--- + +<Intro> + +Reducers let you consolidate a component's state update logic. Context lets you pass information deep down to other components. You can combine reducers and context together to manage state of a complex screen. + +</Intro> + +<YouWillLearn> + +* How to combine a reducer with context +* How to avoid passing state and dispatch through props +* How to keep context and state logic in a separate file + +</YouWillLearn> + +## Combining a reducer with context {/*combining-a-reducer-with-context*/} + +In this example from [the introduction to reducers](/learn/extracting-state-logic-into-a-reducer), the state is managed by a reducer. The reducer function contains all of the state update logic and is declared at the bottom of this file: + +<Sandpack> + +```js App.js +import { useReducer } from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId + }); + } + + return ( + <> + <h1>Day off in Kyoto</h1> + <AddTask + onAddTask={handleAddTask} + /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +let nextId = 3; +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js AddTask.js +import { useState } from 'react'; + +export default function AddTask({ onAddTask }) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + onAddTask(text); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + tasks, + onChangeTask, + onDeleteTask +}) { + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task + task={task} + onChange={onChangeTask} + onDelete={onDeleteTask} + /> + </li> + ))} + </ul> + ); +} + +function Task({ task, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + onChange({ + ...task, + text: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + onChange({ + ...task, + done: e.target.checked + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +A reducer helps keep the event handlers short and concise. However, as your app grows, you might run into another difficulty. **Currently, the `tasks` state and the `dispatch` function are only available in the top-level `TaskApp` component.** To let other components read the list of tasks or change it, you have to explicitly [pass down](/learn/passing-props-to-a-component) the current state and the event handlers that change it as props. + +For example, `TaskApp` passes a list of tasks and the event handlers to `TaskList`: + +```js +<TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} +/> +``` + +And `TaskList` passes the event handlers to `Task`: + +```js +<Task + task={task} + onChange={onChangeTask} + onDelete={onDeleteTask} +/> +``` + +In a small example like this, this works well, but if you have tens or hundreds of components in the middle, passing down all state and functions can be quite frustrating! + +This is why, as an alternative to passing them through props, you might want to put both the `tasks` state and the `dispatch` function [into context.](/learn/passing-data-deeply-with-context) **This way, any component below `TaskApp` in the tree can read the tasks and dispatch actions without the repetitive "prop drilling".** + +Here is how you can combine a reducer with context: + +1. **Create** the context. +2. **Put** state and dispatch into context. +3. **Use** context anywhere in the tree. + +### Step 1: Create the context {/*step-1-create-the-context*/} + +The `useReducer` Hook returns the current `tasks` and the `dispatch` function that lets you update them: + +```js +const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); +``` + +To pass them down the tree, you will [create](/learn/passing-data-deeply-with-context#step-2-use-the-context) two separate contexts: + +- `TasksContext` provides the current list of tasks. +- `TasksDispatchContext` provides the function that lets components dispatch actions. + +Export them from a separate file so that you can later import them from other files: + +<Sandpack> + +```js App.js +import { useReducer } from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId + }); + } + + return ( + <> + <h1>Day off in Kyoto</h1> + <AddTask + onAddTask={handleAddTask} + /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +let nextId = 3; +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js TasksContext.js active +import { createContext } from 'react'; + +export const TasksContext = createContext(null); +export const TasksDispatchContext = createContext(null); +``` + +```js AddTask.js +import { useState } from 'react'; + +export default function AddTask({ onAddTask }) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + onAddTask(text); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + tasks, + onChangeTask, + onDeleteTask +}) { + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task + task={task} + onChange={onChangeTask} + onDelete={onDeleteTask} + /> + </li> + ))} + </ul> + ); +} + +function Task({ task, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + onChange({ + ...task, + text: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + onChange({ + ...task, + done: e.target.checked + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +Here, you're passing `null` as the default value to both contexts. The actual values will be provided by the `TaskApp` component. + +### Step 2: Put state and dispatch into context {/*step-2-put-state-and-dispatch-into-context*/} + +Now you can import both contexts in your `TaskApp` component. Take the `tasks` and `dispatch` returned by `useReducer()` and [provide them](/learn/passing-data-deeply-with-context#step-3-provide-the-context) to the entire tree below: + +```js {4,7-8} +import { TasksContext, TasksDispatchContext } from './TasksContext.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); + // ... + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + ... + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} +``` + +For now, you pass the information both via props and in context: + +<Sandpack> + +```js App.js +import { useReducer } from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; +import { TasksContext, TasksDispatchContext } from './TasksContext.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId + }); + } + + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + <h1>Day off in Kyoto</h1> + <AddTask + onAddTask={handleAddTask} + /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +let nextId = 3; +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js TasksContext.js +import { createContext } from 'react'; + +export const TasksContext = createContext(null); +export const TasksDispatchContext = createContext(null); +``` + +```js AddTask.js +import { useState } from 'react'; + +export default function AddTask({ onAddTask }) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + onAddTask(text); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + tasks, + onChangeTask, + onDeleteTask +}) { + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task + task={task} + onChange={onChangeTask} + onDelete={onDeleteTask} + /> + </li> + ))} + </ul> + ); +} + +function Task({ task, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + onChange({ + ...task, + text: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + onChange({ + ...task, + done: e.target.checked + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +In the next step, you will remove prop passing. + +### Step 3: Use context anywhere in the tree {/*step-3-use-context-anywhere-in-the-tree*/} + +Now you don't need to pass the list of tasks or the event handlers down the tree: + +```js {4-5} +<TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + <h1>Day off in Kyoto</h1> + <AddTask /> + <TaskList /> + </TasksDispatchContext.Provider> +</TasksContext.Provider> +``` + +Instead, any component that needs the task list can read it from the `TaskContext`: + +```js {2} +export default function TaskList() { + const tasks = useContext(TasksContext); + // ... +``` + +To update the task list, any component can read the `dispatch` function from context and call it: + +```js {3,9-13} +export default function AddTask() { + const [text, setText] = useState(''); + const dispatch = useContext(TasksDispatchContext); + // ... + return ( + // ... + <button onClick={() => { + setText(''); + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + }}>Add</button> + // ... +``` + +**The `TaskApp` component does not pass any event handlers down, and the `TaskList` does not pass any event handlers to the `Task` component either.** Each component reads the context that it needs: + +<Sandpack> + +```js App.js +import { useReducer } from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; +import { TasksContext, TasksDispatchContext } from './TasksContext.js'; + +export default function TaskApp() { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + <h1>Day off in Kyoto</h1> + <AddTask /> + <TaskList /> + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js TasksContext.js +import { createContext } from 'react'; + +export const TasksContext = createContext(null); +export const TasksDispatchContext = createContext(null); +``` + +```js AddTask.js +import { useState, useContext } from 'react'; +import { TasksDispatchContext } from './TasksContext.js'; + +export default function AddTask() { + const [text, setText] = useState(''); + const dispatch = useContext(TasksDispatchContext); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + }}>Add</button> + </> + ); +} + +let nextId = 3; +``` + +```js TaskList.js active +import { useState, useContext } from 'react'; +import { TasksContext, TasksDispatchContext } from './TasksContext.js'; + +export default function TaskList() { + const tasks = useContext(TasksContext); + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task task={task} /> + </li> + ))} + </ul> + ); +} + +function Task({ task }) { + const [isEditing, setIsEditing] = useState(false); + const dispatch = useContext(TasksDispatchContext); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + text: e.target.value + } + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + done: e.target.checked + } + }); + }} + /> + {taskContent} + <button onClick={() => { + dispatch({ + type: 'deleted', + id: task.id + }); + }}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +**The state still "lives" in the top-level `TaskApp` component, managed with `useReducer`.** But its `tasks` and `dispatch` are now available to every component below in the tree by importing and using these contexts. + +## Moving all wiring into a single file {/*moving-all-wiring-into-a-single-file*/} + +You don't have to do this, but you could further declutter the components by moving both reducer and context into a single file. Currently, `TasksContext.js` contains only two context declarations: + +```js +import { createContext } from 'react'; + +export const TasksContext = createContext(null); +export const TasksDispatchContext = createContext(null); +``` + +This file is about to get crowded! You'll move the reducer into that same file. Then you'll declare a new `TasksProvider` component in the same file. This component will tie all the pieces together: + +1. It will manage the state with a reducer. +2. It will provide both contexts to components below. +3. It will [take `children` as a prop](/learn/passing-props-to-a-component#passing-jsx-as-children) so you can pass JSX to it. + +```js +export function TasksProvider({ children }) { + const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); + + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + {children} + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} +``` + +**This removes all the complexity and wiring from your `TaskApp` component:** + +<Sandpack> + +```js App.js +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; +import { TasksProvider } from './TasksContext.js'; + +export default function TaskApp() { + return ( + <TasksProvider> + <h1>Day off in Kyoto</h1> + <AddTask /> + <TaskList /> + </TasksProvider> + ); +} +``` + +```js TasksContext.js +import { createContext, useReducer } from 'react'; + +export const TasksContext = createContext(null); +export const TasksDispatchContext = createContext(null); + +export function TasksProvider({ children }) { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + {children} + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js AddTask.js +import { useState, useContext } from 'react'; +import { TasksDispatchContext } from './TasksContext.js'; + +export default function AddTask() { + const [text, setText] = useState(''); + const dispatch = useContext(TasksDispatchContext); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + }}>Add</button> + </> + ); +} + +let nextId = 3; +``` + +```js TaskList.js +import { useState, useContext } from 'react'; +import { TasksContext, TasksDispatchContext } from './TasksContext.js'; + +export default function TaskList() { + const tasks = useContext(TasksContext); + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task task={task} /> + </li> + ))} + </ul> + ); +} + +function Task({ task }) { + const [isEditing, setIsEditing] = useState(false); + const dispatch = useContext(TasksDispatchContext); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + text: e.target.value + } + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + done: e.target.checked + } + }); + }} + /> + {taskContent} + <button onClick={() => { + dispatch({ + type: 'deleted', + id: task.id + }); + }}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +You can also export functions that _use_ the context from `TasksContext.js`: + +```js +export function useTasks() { + return useContext(TasksContext); +} + +export function useTasksDispatch() { + return useContext(TasksDispatchContext); +} +``` + +When a component needs to read context, it can do it through these functions: + +```js +const tasks = useTasks(); +const dispatch = useTasksDispatch(); +``` + +This doesn't change the behavior in any way, but it lets you later split these contexts further or add some logic to these functions. **Now all of the context and reducer wiring is in `TasksContext.js`. This keeps the components clean and uncluttered, focused on what they display rather than where they get the data:** + +<Sandpack> + +```js App.js +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; +import { TasksProvider } from './TasksContext.js'; + +export default function TaskApp() { + return ( + <TasksProvider> + <h1>Day off in Kyoto</h1> + <AddTask /> + <TaskList /> + </TasksProvider> + ); +} +``` + +```js TasksContext.js +import { createContext, useContext, useReducer } from 'react'; + +const TasksContext = createContext(null); + +const TasksDispatchContext = createContext(null); + +export function TasksProvider({ children }) { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + {children} + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} + +export function useTasks() { + return useContext(TasksContext); +} + +export function useTasksDispatch() { + return useContext(TasksDispatchContext); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js AddTask.js +import { useState } from 'react'; +import { useTasksDispatch } from './TasksContext.js'; + +export default function AddTask() { + const [text, setText] = useState(''); + const dispatch = useTasksDispatch(); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + }}>Add</button> + </> + ); +} + +let nextId = 3; +``` + +```js TaskList.js active +import { useState } from 'react'; +import { useTasks, useTasksDispatch } from './TasksContext.js'; + +export default function TaskList() { + const tasks = useTasks(); + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task task={task} /> + </li> + ))} + </ul> + ); +} + +function Task({ task }) { + const [isEditing, setIsEditing] = useState(false); + const dispatch = useTasksDispatch(); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + text: e.target.value + } + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + done: e.target.checked + } + }); + }} + /> + {taskContent} + <button onClick={() => { + dispatch({ + type: 'deleted', + id: task.id + }); + }}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +You can think of `TasksProvider` as a part of the screen that knows how to deal with tasks, `useTasks` as a way to read them, and `useTasksDispatch` as a way to update them from any component below in the tree. + +> Functions like `useTasks` and `useTasksDispatch` are called **[Custom Hooks.](/learn/reusing-logic-with-custom-hooks)** Your function is considered a custom Hook if its name starts with `use`. This lets you use other Hooks, like `useContext`, inside it. + +As your app grows, you may have many context-reducer pairs like this. This is a powerful way to scale your app and [lift state up](/learn/sharing-state-between-components) without too much work whenever you want to access the data deep in the tree. + +<Recap> + +- You can combine reducer with context to let any component read and update state above it. +- To provide state and the dispatch function to components below: + 1. Create two contexts (for state and for dispatch functions). + 2. Provide both contexts from the component that uses the reducer. + 3. Use either context from components that need to read them. +- You can further declutter the components by moving all wiring into one file. + - You can export a component like `TasksProvider` that provides context. + - You can also export custom Hooks like `useTasks` and `useTasksDispatch` to read it. +- You can have many context-reducer pairs like this in your app. + +</Recap> + diff --git a/beta/src/content/learn/separating-events-from-effects.md b/beta/src/content/learn/separating-events-from-effects.md new file mode 100644 index 000000000..ba1029d8c --- /dev/null +++ b/beta/src/content/learn/separating-events-from-effects.md @@ -0,0 +1,1842 @@ +--- +title: 'Separating Events from Effects' +--- + +<Intro> + +Event handlers only re-run when you perform the same interaction again. Unlike event handlers, Effects re-synchronize if some value they read, like a prop or a state variable, is different from what it was during the last render. Sometimes, you also want a mix of both behaviors: an Effect that re-runs in response to some values but not others. This page will teach you how to do that. + +</Intro> + +<YouWillLearn> + +- How to choose between an event handler and an Effect +- Why Effects are reactive, and event handlers are not +- What to do when you want a part of your Effect's code to not be reactive +- What Effect Events are, and how to extract them from your Effects +- How to read the latest props and state from Effects using Effect Events + +</YouWillLearn> + +## Choosing between event handlers and Effects {/*choosing-between-event-handlers-and-effects*/} + +First, let's recap the difference between event handlers and Effects. + +Imagine you're implementing a chat room component. Your requirements look like this: + +1. Your component should automatically connect to the selected chat room. +1. When you click the "Send" button, it should send a message to the chat. + +Let's say you've already implemented the code for them, but you're not sure where to put it. Should you use event handlers or Effects? Every time you need to answer this question, consider [*why* the code needs to run.](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events) + +### Event handlers run in response to specific interactions {/*event-handlers-run-in-response-to-specific-interactions*/} + +From the user's perspective, sending a message should happen *because* the particular "Send" button was clicked. The user will get rather upset if you send their message at any other time or for any other reason. This is why sending a message should be an event handler. Event handlers let you handle specific interactions like clicks: + +```js {4-6} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + // ... + function handleSendClick() { + sendMessage(message); + } + // ... + return ( + <> + <input value={message} onChange={e => setMessage(e.target.value)} /> + <button onClick={handleSendClick}>Send</button>; + </> + ); +} +``` + +With an event handler, you can be sure that `sendMessage(message)` will *only* run if the user presses the button. + +### Effects run whenever synchronization is needed {/*effects-run-whenever-synchronization-is-needed*/} + +Recall that you also need to keep the component connected to the chat room. Where does that code go? + +The *reason* to run this code is not some particular interaction. It doesn't matter why or how the user navigated to the chat room screen. Now that they're looking at it and could interact with it, the component needs to stay connected to the selected chat server. Even if the chat room component was the initial screen of your app, and the user has not performed any interactions at all, you would *still* need to connect. This is why it's an Effect: + +```js {3-9} +function ChatRoom({ roomId }) { + // ... + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId]); + // ... +} +``` + +With this code, you can be sure that there is always an active connection to the currently selected chat server, *regardless* of the specific interactions performed by the user. Whether the user has only opened your app, selected a different room, or navigated to another screen and back, your Effect will ensure that the component will *remain synchronized* with the currently selected room, and will [re-connect whenever it's necessary.](/learn/lifecycle-of-reactive-effects#why-synchronization-may-need-to-happen-more-than-once) + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection, sendMessage } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + function handleSendClick() { + sendMessage(message); + } + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input value={message} onChange={e => setMessage(e.target.value)} /> + <button onClick={handleSendClick}>Send</button> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js chat.js +export function sendMessage(message) { + console.log('🔵 You sent: ' + message); +} + +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input, select { margin-right: 20px; } +``` + +</Sandpack> + +## Reactive values and reactive logic {/*reactive-values-and-reactive-logic*/} + +Intuitively, you could say that event handlers are always triggered "manually", for example by clicking a button. Effects, on the other hand, are "automatic": they run and re-run as often as it's needed to stay synchronized. + +There is a more precise way to think about this. + +Props, state, and variables declared inside your component's body are called <CodeStep step={2}>reactive values</CodeStep>. In this example, `serverUrl` is not a reactive value, but `roomId` and `message` are. They participate in the rendering data flow: + +```js [[2, 3, "roomId"], [2, 4, "message"]] +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + // ... +} +``` + +Reactive values like these can change due to a re-render. For example, the user may edit the `message` or choose a different `roomId` in a dropdown. Event handlers and Effects are different in how they respond to changes: + +- **Logic inside event handlers is *not reactive.*** It will not run again unless the user performs the same interaction (for example, a click) again. Event handlers can read reactive values, but they don't "react" to their changes. +- **Logic inside Effects is *reactive.*** If your Effect reads a reactive value, [you have to specify it as a dependency.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Then, if a re-render causes that value to change, React will re-run your Effect's logic again with the new value. + +Let's revisit the previous example to illustrate this difference. + +### Logic inside event handlers is not reactive {/*logic-inside-event-handlers-is-not-reactive*/} + +Take a look at this line of code. Should this logic be reactive or not? + +```js [[2, 2, "message"]] + // ... + sendMessage(message); + // ... +``` + +From the user's perspective, **a change to the `message` does _not_ mean that they want to send a message.** It only means that the user is typing. In other words, the logic that sends a message should not be reactive. It should not run again only because the <CodeStep step={2}>reactive value</CodeStep> has changed. That's why you placed this logic in the event handler: + +```js {2} + function handleSendClick() { + sendMessage(message); + } +``` + +Event handlers aren't reactive, so `sendMessage(message)` will only run when the user clicks the Send button. + +### Logic inside Effects is reactive {/*logic-inside-effects-is-reactive*/} + +Now let's return to these lines: + +```js [[2, 2, "roomId"]] + // ... + const connection = createConnection(serverUrl, roomId); + connection.connect(); + // ... +``` + +From the user's perspective, **a change to the `roomId` *does* mean that they want to connect to a different room.** In other words, the logic for connecting to the room should be reactive. You *want* these lines of code to "keep up" with the <CodeStep step={2}>reactive value</CodeStep>, and to run again if that value is different. That's why you put this logic inside an Effect: + +```js {2-3} + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect() + }; + }, [roomId]); +``` + +Effects are reactive, so `createConnection(serverUrl, roomId)` and `connection.connect()` will run for every distinct value of `roomId`. Your Effect keeps the chat connection synchronized to the currently selected room. + +## Extracting non-reactive logic out of Effects {/*extracting-non-reactive-logic-out-of-effects*/} + +Things get more tricky when you want to mix reactive logic with non-reactive logic. + +For example, imagine that you want to show a notification when the user connects to the chat. You read the current theme (dark or light) from the props so that you can show the notification in the correct color: + +```js {1,4-6} +function ChatRoom({ roomId, theme }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + showNotification('Connected!', theme); + }); + connection.connect(); + // ... +```` + +However, `theme` is a reactive value (it can change as a result of re-rendering), and [every reactive value read by an Effect must be declared as its dependency.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) So now you have to specify `theme` as a dependency of your Effect: + +```js {5,11} +function ChatRoom({ roomId, theme }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + showNotification('Connected!', theme); + }); + connection.connect(); + return () => { + connection.disconnect() + }; + }, [roomId, theme]); // ✅ All dependencies declared + // ... +```` + +Play with this example and see if you can spot the problem with this user experience: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { createConnection, sendMessage } from './chat.js'; +import { showNotification } from './notifications.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId, theme }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + showNotification('Connected!', theme); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, theme]); + + return <h1>Welcome to the {roomId} room!</h1> +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <hr /> + <ChatRoom + roomId={roomId} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + let connectedCallback; + let timeout; + return { + connect() { + timeout = setTimeout(() => { + if (connectedCallback) { + connectedCallback(); + } + }, 100); + }, + on(event, callback) { + if (connectedCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'connected') { + throw Error('Only "connected" event is supported.'); + } + connectedCallback = callback; + }, + disconnect() { + clearTimeout(timeout); + } + }; +} +``` + +```js notifications.js +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label { display: block; margin-top: 10px; } +``` + +</Sandpack> + +When the `roomId` changes, the chat re-connects as you would expect. But since `theme` is also a dependency, the chat *also* re-connects every time you switch between the dark and the light theme. That's not great! + +In other words, you *don't* want this line to be reactive, even though it is inside an Effect (which is reactive): + +```js + // ... + showNotification('Connected!', theme); + // ... +``` + +You need a way to separate this non-reactive logic from the reactive Effect around it. + +### Declaring an Effect Event {/*declaring-an-effect-event*/} + +<Wip> + +This section describes an **experimental API that has not yet been added to React,** so you can't use it yet. + +</Wip> + +Use a special Hook called [`useEffectEvent`](/reference/react/useEffectEvent) to extract this non-reactive logic out of your Effect: + +```js {1,4-6} +import { useEffect, useEffectEvent } from 'react'; + +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Connected!', theme); + }); + // ... +```` + +Here, `onConnected` is called an *Effect Event.* It's a part of your Effect logic, but it behaves a lot more like an event handler. The logic inside it is not reactive, and it always "sees" the latest values of your props and state. + +Now you can call the `onConnected` Effect Event from inside your Effect: + +```js {2-4,9,13} +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Connected!', theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + onConnected(); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +``` + +This solves the problem. Note that you had to *remove* `onConnected` from the list of your Effect's dependencies. **Effect Events are not reactive and must be omitted from dependencies. The linter will error if you include them.** + +Verify that the new behavior works as you would expect: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { createConnection, sendMessage } from './chat.js'; +import { showNotification } from './notifications.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Connected!', theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + onConnected(); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1> +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <hr /> + <ChatRoom + roomId={roomId} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + let connectedCallback; + let timeout; + return { + connect() { + timeout = setTimeout(() => { + if (connectedCallback) { + connectedCallback(); + } + }, 100); + }, + on(event, callback) { + if (connectedCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'connected') { + throw Error('Only "connected" event is supported.'); + } + connectedCallback = callback; + }, + disconnect() { + clearTimeout(timeout); + } + }; +} +``` + +```js notifications.js hidden +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label { display: block; margin-top: 10px; } +``` + +</Sandpack> + +You can think of Effect Events as being very similar to event handlers. The main difference is that event handlers run in response to a user interactions, whereas Effect Events are triggered by you from Effects. Effect Events let you "break the chain" between the reactivity of Effects and some code that should not be reactive. + +### Reading latest props and state with Effect Events {/*reading-latest-props-and-state-with-effect-events*/} + +<Wip> + +This section describes an **experimental API that has not yet been added to React,** so you can't use it yet. + +</Wip> + +Effect Events let you fix many patterns where you might be tempted to suppress the dependency linter. + +For example, say you have an Effect to log the page visits: + +```js +function Page() { + useEffect(() => { + logVisit(); + }, []); + // ... +} +``` + +Later, you add multiple routes to your site. Now your `Page` component receives a `url` prop with the current path. You want to pass the `url` as a part of your `logVisit` call, but the dependency linter complains: + +```js {1,3} +function Page({ url }) { + useEffect(() => { + logVisit(url); + }, []); // 🔴 React Hook useEffect has a missing dependency: 'url' + // ... +} +``` + +Think about what you want the code to do. You *want* to log a separate visit for different URLs since each URL represents a different page. In other words, this `logVisit` call *should* be reactive with respect to the `url`. This is why, in this case, it makes sense to follow the dependency linter, and add `url` as a dependency: + +```js {4} +function Page({ url }) { + useEffect(() => { + logVisit(url); + }, [url]); // ✅ All dependencies declared + // ... +} +``` + +Now let's say you want to include the number of items in the shopping cart together with every page visit: + +```js {2-3,6} +function Page({ url }) { + const { items } = useContext(ShoppingCartContext); + const numberOfItems = items.length; + + useEffect(() => { + logVisit(url, numberOfItems); + }, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems' + // ... +} +``` + +You used `numberOfItems` inside the Effect, so the linter asks you to add it as a dependency. However, you *don't* want the `logVisit` call to be reactive with respect to `numberOfItems`. If the user puts something into the shopping cart, and the `numberOfItems` changes, this *does not mean* that the user visited the page again. In other words, *visiting the page* feels similar to an event. You want to be very precise about *when* you say it's happened. + +Split the code in two parts: + +```js {5-7,10} +function Page({ url }) { + const { items } = useContext(ShoppingCartContext); + const numberOfItems = items.length; + + const onVisit = useEffectEvent(visitedUrl => { + logVisit(visitedUrl, numberOfItems); + }); + + useEffect(() => { + onVisit(url); + }, [url]); // ✅ All dependencies declared + // ... +} +``` + +Here, `onVisit` is an Effect Event. The code inside it isn't reactive. This is why you can use `numberOfItems` (or any other reactive value!) without worrying that it will cause the surrounding code to re-execute on changes. + +On the other hand, the Effect itself remains reactive. Code inside the Effect uses the `url` prop, so the Effect will re-run after every re-render with a different `url`. This, in turn, will call the `onVisit` Effect Event. + +As a result, you will call `logVisit` for every change to the `url`, and always read the latest `numberOfItems`. However, if `numberOfItems` changes on its own, this will not cause any of the code to re-run. + +<Note> + +You might be wondering if you could call `onVisit()` with no arguments, and read the `url` inside it: + +```js {2,6} + const onVisit = useEffectEvent(() => { + logVisit(url, numberOfItems); + }); + + useEffect(() => { + onVisit(); + }, [url]); +``` + +This would work, but it's better to pass this `url` to the Effect Event explicitly. **By passing `url` as an argument to your Effect Event, you are saying that visiting a page with a different `url` constitutes a separate "event" from the user's perspective.** The `visitedUrl` is a *part* of the "event" that happened: + +```js {1-2,6} + const onVisit = useEffectEvent(visitedUrl => { + logVisit(visitedUrl, numberOfItems); + }); + + useEffect(() => { + onVisit(url); + }, [url]); +``` + +Since your Effect Event explicitly "asks" for the `visitedUrl`, now you can't accidentally remove `url` from the Effect's dependencies. If you remove the `url` dependency (causing distinct page visits to be counted as one), the linter will warn you about it. You want `onVisit` to be reactive with regards to the `url`, so instead of reading the `url` inside (where it wouldn't be reactive), you pass it *from* your Effect. + +This becomes especially important if there is some asynchronous logic inside the Effect: + +```js {6,8} + const onVisit = useEffectEvent(visitedUrl => { + logVisit(visitedUrl, numberOfItems); + }); + + useEffect(() => { + setTimeout(() => { + onVisit(url); + }, 5000); // Delay logging visits + }, [url]); +``` + +In this example, `url` inside `onVisit` corresponds to the *latest* `url` (which could have already changed), but `visitedUrl` corresponds to the `url` that originally caused this Effect (and this `onVisit` call) to run. + +</Note> + +<DeepDive> + +#### Is it okay to suppress the dependency linter instead? {/*is-it-okay-to-suppress-the-dependency-linter-instead*/} + +In the existing codebases, you may sometimes see the lint rule suppressed like this: + +```js {7-9} +function Page({ url }) { + const { items } = useContext(ShoppingCartContext); + const numberOfItems = items.length; + + useEffect(() => { + logVisit(url, numberOfItems); + // 🔴 Avoid suppressing the linter like this: + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [url]); + // ... +} +``` + +After `useEffectEvent` becomes a stable part of React, we recommend to **never suppress the linter** like this. + +The first downside of suppressing the rule is that React will no longer warn you when your Effect needs to "react" to a new reactive dependency you've introduced to your code. For example, in the earlier example, you added `url` to the dependencies *because* React reminded you to do it. You will no longer get such reminders for any future edits to that Effect if you disable the linter. This leads to bugs. + +Here is an example of a confusing bug caused by suppressing the linter. In this example, the `handleMove` function is supposed to read the current `canMove` state variable value in order to decide whether the dot should follow the cursor. However, `canMove` is always `true` inside `handleMove`. Can you see why? + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + function handleMove(e) { + if (canMove) { + setPosition({ x: e.clientX, y: e.clientY }); + } + } + + useEffect(() => { + window.addEventListener('pointermove', handleMove); + return () => window.removeEventListener('pointermove', handleMove); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + + +The problem with the this code is in suppressing the dependency linter. If you remove the suppression, you'll see that this Effect should depend on the `handleMove` function. This makes sense: `handleMove` is declared inside the component body, which makes it a reactive value. Every reactive value must be specified as a dependency, or it can potentially get stale over time! + +The author of the original code has "lied" to React by saying that the Effect does not depend (`[]`) on any reactive values. This is why React did not re-synchronize the Effect after `canMove` has changed (and `handleMove` with it). Because React did not re-synchronize the Effect, the `handleMove` attached as a listener is the `handleMove` function created during the initial render. During the initial render, `canMove` was `true`, which is why `handleMove` from the initial render will forever see that value. + +**If you never suppress the linter, you will never see problems with stale values.** + +With `useEffectEvent`, there is no need to "lie" to the linter, and the code works as you would expect: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [canMove, setCanMove] = useState(true); + + const onMove = useEffectEvent(e => { + if (canMove) { + setPosition({ x: e.clientX, y: e.clientY }); + } + }); + + useEffect(() => { + window.addEventListener('pointermove', onMove); + return () => window.removeEventListener('pointermove', onMove); + }, []); + + return ( + <> + <label> + <input type="checkbox" + checked={canMove} + onChange={e => setCanMove(e.target.checked)} + /> + The dot is allowed to move + </label> + <hr /> + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + </> + ); +} +``` + +```css +body { + height: 200px; +} +``` + +</Sandpack> + +This doesn't mean that `useEffectEvent` is *always* the correct solution. You should only apply it to the lines of code that you don't want to be reactive. For example, in the above sandbox, you didn't want the Effect's code to be reactive with regards to `canMove`. That's why it made sense to extract an Effect Event. + +Read [Removing Effect Dependencies](/learn/removing-effect-dependencies) for other correct alternatives to suppressing the linter. + +</DeepDive> + +### Limitations of Effect Events {/*limitations-of-effect-events*/} + +<Wip> + +This section describes an **experimental API that has not yet been added to React,** so you can't use it yet. + +</Wip> + +Effect Events are very limited in how you can use them: + +* **Only call them from inside Effects.** +* **Never pass them to other components or Hooks.** + +For example, don't declare and pass an Effect Event like this: + +```js {4-6,8} +function Timer() { + const [count, setCount] = useState(0); + + const onTick = useEffectEvent(() => { + setCount(count + 1); + }); + + useTimer(onTick, 1000); // 🔴 Avoid: Passing Effect Events + + return <h1>{count}</h1> +} + +function useTimer(callback, delay) { + useEffect(() => { + const id = setInterval(() => { + callback(); + }, delay); + return () => { + clearInterval(id); + }; + }, [delay, callback]); // Need to specify "callback" in dependencies +} +``` + +Instead, always declare Effect Events directly next to the Effects that use them: + +```js {10-12,16,21} +function Timer() { + const [count, setCount] = useState(0); + useTimer(() => { + setCount(count + 1); + }, 1000); + return <h1>{count}</h1> +} + +function useTimer(callback, delay) { + const onTick = useEffectEvent(() => { + callback(); + }); + + useEffect(() => { + const id = setInterval(() => { + onTick(); // ✅ Good: Only called locally inside an Effect + }, delay); + return () => { + clearInterval(id); + }; + }, [delay]); // No need to specify "onTick" (an Effect Event) as a dependency +} +``` + +Effect Events are non-reactive "pieces" of your Effect code. They should be next to the Effect using them. + +<Recap> + +- Event handlers run in response to specific interactions. +- Effects run whenever synchronization is needed. +- Logic inside event handlers is not reactive. +- Logic inside Effects is reactive. +- You can move non-reactive logic from Effects into Effect Events. +- Only call Effect Events from inside Effects. +- Don't pass Effect Events to other components or Hooks. + +</Recap> + +<Challenges> + +#### Fix a variable that doesn't update {/*fix-a-variable-that-doesnt-update*/} + +This `Timer` component keeps a `count` state variable which increases every second. The value by which it's increasing is stored in the `increment` state variable. You can control the `increment` variable with the plus and minus buttons. + +However, no matter how many times you click the plus button, the counter is still incremented by one every second. What's wrong with this code? Why is `increment` always equal to `1` inside the Effect's code? Find the mistake and fix it. + +<Hint> + +To fix this code, it's enough to follow the rules. + +</Hint> + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + const [increment, setIncrement] = useState(1); + + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + increment); + }, 1000); + return () => { + clearInterval(id); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + <h1> + Counter: {count} + <button onClick={() => setCount(0)}>Reset</button> + </h1> + <hr /> + <p> + Every second, increment by: + <button disabled={increment === 0} onClick={() => { + setIncrement(i => i - 1); + }}>–</button> + <b>{increment}</b> + <button onClick={() => { + setIncrement(i => i + 1); + }}>+</button> + </p> + </> + ); +} +``` + +```css +button { margin: 10px; } +``` + +</Sandpack> + +<Solution> + +As usual, when you're looking for bugs in Effects, start by searching for linter suppressions. + +If you remove the suppression comment, React will tell you that this Effect's code depends on `increment`, but you "lied" to React by claiming that this Effect does not depend on any reactive values (`[]`). Add `increment` to the dependency array: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + const [increment, setIncrement] = useState(1); + + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + increment); + }, 1000); + return () => { + clearInterval(id); + }; + }, [increment]); + + return ( + <> + <h1> + Counter: {count} + <button onClick={() => setCount(0)}>Reset</button> + </h1> + <hr /> + <p> + Every second, increment by: + <button disabled={increment === 0} onClick={() => { + setIncrement(i => i - 1); + }}>–</button> + <b>{increment}</b> + <button onClick={() => { + setIncrement(i => i + 1); + }}>+</button> + </p> + </> + ); +} +``` + +```css +button { margin: 10px; } +``` + +</Sandpack> + +Now, when `increment` changes, React will re-synchronize your Effect, which will restart the interval. + +</Solution> + +#### Fix a freezing counter {/*fix-a-freezing-counter*/} + +This `Timer` component keeps a `count` state variable which increases every second. The value by which it's increasing is stored in the `increment` state variable, which you can control it with the plus and minus buttons. For example, try pressing the plus button nine times, and notice that the `count` now increases each second by ten rather than by one. + +There is a small issue with this user interface. You might notice that if you keep pressing the plus or minus buttons faster than once per second, the timer itself seems to pause. It only resumes after a second passes since the last time you've pressed either button. Find why this is happening, and fix the issue so that the timer ticks on *every* second without interruptions. + +<Hint> + +It seems like the Effect which sets up the timer "reacts" to the `increment` value. Does the line that uses the current `increment` value in order to call `setCount` really need to be reactive? + +</Hint> + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + const [increment, setIncrement] = useState(1); + + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + increment); + }, 1000); + return () => { + clearInterval(id); + }; + }, [increment]); + + return ( + <> + <h1> + Counter: {count} + <button onClick={() => setCount(0)}>Reset</button> + </h1> + <hr /> + <p> + Every second, increment by: + <button disabled={increment === 0} onClick={() => { + setIncrement(i => i - 1); + }}>–</button> + <b>{increment}</b> + <button onClick={() => { + setIncrement(i => i + 1); + }}>+</button> + </p> + </> + ); +} +``` + +```css +button { margin: 10px; } +``` + +</Sandpack> + +<Solution> + +The issue is that the code inside the Effect uses the `increment` state variable. Since it's a dependency of your Effect, every change to `increment` causes the Effect to re-synchronize, which causes the interval to clear. If you keep clearing the interval every time before it has a chance to fire, it will appear as if the timer has stalled. + +To solve the issue, extract an `onTick` Effect Event from the Effect: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + const [increment, setIncrement] = useState(1); + + const onTick = useEffectEvent(() => { + setCount(c => c + increment); + }); + + useEffect(() => { + const id = setInterval(() => { + onTick(); + }, 1000); + return () => { + clearInterval(id); + }; + }, []); + + return ( + <> + <h1> + Counter: {count} + <button onClick={() => setCount(0)}>Reset</button> + </h1> + <hr /> + <p> + Every second, increment by: + <button disabled={increment === 0} onClick={() => { + setIncrement(i => i - 1); + }}>–</button> + <b>{increment}</b> + <button onClick={() => { + setIncrement(i => i + 1); + }}>+</button> + </p> + </> + ); +} +``` + + +```css +button { margin: 10px; } +``` + +</Sandpack> + +Since `onTick` is an Effect Event, the code inside it isn't reactive. The change to `increment` does not trigger any Effects. + +</Solution> + +#### Fix a non-adjustable delay {/*fix-a-non-adjustable-delay*/} + +In this example, you can customize the interval delay. It's stored in a `delay` state variable which is updated by two buttons. However, even if you press the "plus 100 ms" button until the `delay` is 1000 milliseconds (that is, a second), you'll notice that the timer still increments very fast (every 100 ms). It's as if your changes to the `delay` are ignored. Find and fix the bug. + +<Hint> + +Code inside Effect Events is not reactive. Are there cases in which you would _want_ the `setInterval` call to re-run? + +</Hint> + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + const [increment, setIncrement] = useState(1); + const [delay, setDelay] = useState(100); + + const onTick = useEffectEvent(() => { + setCount(c => c + increment); + }); + + const onMount = useEffectEvent(() => { + return setInterval(() => { + onTick(); + }, delay); + }); + + useEffect(() => { + const id = onMount(); + return () => { + clearInterval(id); + } + }, []); + + return ( + <> + <h1> + Counter: {count} + <button onClick={() => setCount(0)}>Reset</button> + </h1> + <hr /> + <p> + Increment by: + <button disabled={increment === 0} onClick={() => { + setIncrement(i => i - 1); + }}>–</button> + <b>{increment}</b> + <button onClick={() => { + setIncrement(i => i + 1); + }}>+</button> + </p> + <p> + Increment delay: + <button disabled={delay === 100} onClick={() => { + setDelay(d => d - 100); + }}>–100 ms</button> + <b>{delay} ms</b> + <button onClick={() => { + setDelay(d => d + 100); + }}>+100 ms</button> + </p> + </> + ); +} +``` + + +```css +button { margin: 10px; } +``` + +</Sandpack> + +<Solution> + +The problem with the above example is that it extracted an Effect Event called `onMount` without considering what the code should actually be doing. You should only extract Effect Events for a specific reason: when you want to make a part of your code non-reactive. However, the `setInterval` call *should* be reactive with respect to the `delay` state variable. If the `delay` changes, you want to set up the interval from scratch! To fix this code, pull all the reactive code back inside the Effect: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; + +export default function Timer() { + const [count, setCount] = useState(0); + const [increment, setIncrement] = useState(1); + const [delay, setDelay] = useState(100); + + const onTick = useEffectEvent(() => { + setCount(c => c + increment); + }); + + useEffect(() => { + const id = setInterval(() => { + onTick(); + }, delay); + return () => { + clearInterval(id); + } + }, [delay]); + + return ( + <> + <h1> + Counter: {count} + <button onClick={() => setCount(0)}>Reset</button> + </h1> + <hr /> + <p> + Increment by: + <button disabled={increment === 0} onClick={() => { + setIncrement(i => i - 1); + }}>–</button> + <b>{increment}</b> + <button onClick={() => { + setIncrement(i => i + 1); + }}>+</button> + </p> + <p> + Increment delay: + <button disabled={delay === 100} onClick={() => { + setDelay(d => d - 100); + }}>–100 ms</button> + <b>{delay} ms</b> + <button onClick={() => { + setDelay(d => d + 100); + }}>+100 ms</button> + </p> + </> + ); +} +``` + +```css +button { margin: 10px; } +``` + +</Sandpack> + +In general, you should be suspicious of functions like `onMount` that focus on the *timing* rather than the *purpose* of a piece of code. It may feel "more descriptive" at first but it obscures your intent. As a rule of thumb, Effect Events should correspond to something that happens from the *user's* perspective. For example, `onMessage`, `onTick`, `onVisit`, or `onConnected` are good Effect Event names. Code inside them would likely not need to be reactive. On the other hand, `onMount`, `onUpdate`, `onUnmount`, or `onAfterRender` are so generic that it's easy to accidentally put code that *should* be reactive into them. This is why you should name your Effect Events after *what the user thinks has happened,* not when some code happened to run. + +</Solution> + +#### Fix a delayed notification {/*fix-a-delayed-notification*/} + +When you join a chat room, this component shows a notification. However, it doesn't show the notification immediately. Instead, the notification is artificially delayed by two seconds so that the user has a chance to look around the UI. + +This almost works, but there is a bug. Try changing the dropdown from "general" to "travel" and then to "music" very quickly. If you do it fast enough, you will see two notifications (as expected!) but they will *both* say "Welcome to music". + +Fix it so that when you switch from "general" to "travel" and then to "music" very quickly, you see two notifications, the first one being "Welcome to travel" and the second one being "Welcome to music". (For an additional challenge, assuming you've *already* made the notifications show the correct rooms, change the code so that only the latter notification is displayed.) + +<Hint> + +Your Effect knows which room it connected to. Is there any information that you might want to pass to your Effect Event? + +</Hint> + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { createConnection, sendMessage } from './chat.js'; +import { showNotification } from './notifications.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(() => { + showNotification('Welcome to ' + roomId, theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + setTimeout(() => { + onConnected(); + }, 2000); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1> +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <hr /> + <ChatRoom + roomId={roomId} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + let connectedCallback; + let timeout; + return { + connect() { + timeout = setTimeout(() => { + if (connectedCallback) { + connectedCallback(); + } + }, 100); + }, + on(event, callback) { + if (connectedCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'connected') { + throw Error('Only "connected" event is supported.'); + } + connectedCallback = callback; + }, + disconnect() { + clearTimeout(timeout); + } + }; +} +``` + +```js notifications.js hidden +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label { display: block; margin-top: 10px; } +``` + +</Sandpack> + +<Solution> + +Inside your Effect Event, `roomId` is the value *at the time Effect Event was called.* + +Your Effect Event is called with a two second delay. If you're quickly switching from the travel to the music room, by the time the travel room's notification shows, `roomId` is already `"music"`. This is why both notifications say "Welcome to music". + +To fix the issue, instead of reading the *latest* `roomId` inside the Effect Event, make it a parameter of your Effect Event, like `connectedRoomId` below. Then pass `roomId` from your Effect by calling `onConnected(roomId)`: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { createConnection, sendMessage } from './chat.js'; +import { showNotification } from './notifications.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(connectedRoomId => { + showNotification('Welcome to ' + connectedRoomId, theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.on('connected', () => { + setTimeout(() => { + onConnected(roomId); + }, 2000); + }); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1> +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <hr /> + <ChatRoom + roomId={roomId} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + let connectedCallback; + let timeout; + return { + connect() { + timeout = setTimeout(() => { + if (connectedCallback) { + connectedCallback(); + } + }, 100); + }, + on(event, callback) { + if (connectedCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'connected') { + throw Error('Only "connected" event is supported.'); + } + connectedCallback = callback; + }, + disconnect() { + clearTimeout(timeout); + } + }; +} +``` + +```js notifications.js hidden +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label { display: block; margin-top: 10px; } +``` + +</Sandpack> + +The Effect that had `roomId` set to `"travel"` (so it connected to the `"travel"` room) will show the notification for `"travel"`. The Effect that had `roomId` set to `"music"` (so it connected to the `"music"` room) will show the notification for `"music"`. In other words, `connectedRoomId` comes from your Effect (which is reactive), while `theme` always uses the latest value. + +To solve the additional challenge, save the notification timeout ID and clear it in the cleanup function of your Effect: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js +import { useState, useEffect } from 'react'; +import { experimental_useEffectEvent as useEffectEvent } from 'react'; +import { createConnection, sendMessage } from './chat.js'; +import { showNotification } from './notifications.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId, theme }) { + const onConnected = useEffectEvent(connectedRoomId => { + showNotification('Welcome to ' + connectedRoomId, theme); + }); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + let notificationTimeoutId; + connection.on('connected', () => { + notificationTimeoutId = setTimeout(() => { + onConnected(roomId); + }, 2000); + }); + connection.connect(); + return () => { + connection.disconnect(); + if (notificationTimeoutId !== undefined) { + clearTimeout(notificationTimeoutId); + } + }; + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1> +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Use dark theme + </label> + <hr /> + <ChatRoom + roomId={roomId} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + let connectedCallback; + let timeout; + return { + connect() { + timeout = setTimeout(() => { + if (connectedCallback) { + connectedCallback(); + } + }, 100); + }, + on(event, callback) { + if (connectedCallback) { + throw Error('Cannot add the handler twice.'); + } + if (event !== 'connected') { + throw Error('Only "connected" event is supported.'); + } + connectedCallback = callback; + }, + disconnect() { + clearTimeout(timeout); + } + }; +} +``` + +```js notifications.js hidden +import Toastify from 'toastify-js'; +import 'toastify-js/src/toastify.css'; + +export function showNotification(message, theme) { + Toastify({ + text: message, + duration: 2000, + gravity: 'top', + position: 'right', + style: { + background: theme === 'dark' ? 'black' : 'white', + color: theme === 'dark' ? 'white' : 'black', + }, + }).showToast(); +} +``` + +```css +label { display: block; margin-top: 10px; } +``` + +</Sandpack> + +This ensures that already scheduled (but not yet displayed) notifications get cancelled when you change rooms. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/sharing-state-between-components.md b/beta/src/content/learn/sharing-state-between-components.md new file mode 100644 index 000000000..cc98d8f82 --- /dev/null +++ b/beta/src/content/learn/sharing-state-between-components.md @@ -0,0 +1,624 @@ +--- +title: Sharing State Between Components +--- + +<Intro> + +Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as *lifting state up,* and it's one of the most common things you will do writing React code. + +</Intro> + +<YouWillLearn> + +- How to share state between components by lifting it up +- What are controlled and uncontrolled components + +</YouWillLearn> + +## Lifting state up by example {/*lifting-state-up-by-example*/} + +In this example, a parent `Accordion` component renders two separate `Panel`s: + +* `Accordion` + - `Panel` + - `Panel` + +Each `Panel` component has a boolean `isActive` state that determines whether its content is visible. + +Press the Show button for both panels: + +<Sandpack> + +```js +import { useState } from 'react'; + +function Panel({ title, children }) { + const [isActive, setIsActive] = useState(false); + return ( + <section className="panel"> + <h3>{title}</h3> + {isActive ? ( + <p>{children}</p> + ) : ( + <button onClick={() => setIsActive(true)}> + Show + </button> + )} + </section> + ); +} + +export default function Accordion() { + return ( + <> + <h2>Almaty, Kazakhstan</h2> + <Panel title="About"> + With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. + </Panel> + <Panel title="Etymology"> + The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. + </Panel> + </> + ); +} +``` + +```css +h3, p { margin: 5px 0px; } +.panel { + padding: 10px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +Notice how pressing one panel's button does not affect the other panel--they are independent. + +<DiagramGroup> + +<Diagram name="sharing_state_child" height={367} width={477} alt="Diagram showing a tree of three components, one parent labeled Accordion and two children labeled Panel. Both Panel components contain isActive with value false."> + +Initially, each `Panel`'s `isActive` state is `false`, so they both appear collapsed + +</Diagram> + +<Diagram name="sharing_state_child_clicked" height={367} width={480} alt="The same diagram as the previous, with the isActive of the first child Panel component highlighted indicating a click with the isActive value set to true. The second Panel component still contains value false." > + +Clicking either `Panel`'s button will only update that `Panel`'s `isActive` state alone + +</Diagram> + +</DiagramGroup> + +**But now let's say you want to change it so that only one panel is expanded at any given time.** With that design, expanding the second panel should collapse the first one. How would you do that? + +To coordinate these two panels, you need to "lift their state up" to a parent component in three steps: + +1. **Remove** state from the child components. +2. **Pass** hardcoded data from the common parent. +3. **Add** state to the common parent and pass it down together with the event handlers. + +This will allow the `Accordion` component to coordinate both `Panel`s and only expand one at a time. + +### Step 1: Remove state from the child components {/*step-1-remove-state-from-the-child-components*/} + +You will give control of the `Panel`'s `isActive` to its parent component. This means that the parent component will pass `isActive` to `Panel` as a prop instead. Start by **removing this line** from the `Panel` component: + +```js +const [isActive, setIsActive] = useState(false); +``` + +And instead, add `isActive` to the `Panel`'s list of props: + +```js +function Panel({ title, children, isActive }) { +``` + +Now the `Panel`'s parent component can *control* `isActive` by [passing it down as a prop.](/learn/passing-props-to-a-component) Conversely, the `Panel` component now has *no control* over the value of `isActive`--it's now up to the parent component! + +### Step 2: Pass hardcoded data from the common parent {/*step-2-pass-hardcoded-data-from-the-common-parent*/} + +To lift state up, you must locate the closest common parent component of *both* of the child components that you want to coordinate: + +* `Accordion` *(closest common parent)* + - `Panel` + - `Panel` + +In this example, it's the `Accordion` component. Since it's above both panels and can control their props, it will become the "source of truth" for which panel is currently active. Make the `Accordion` component pass a hardcoded value of `isActive` (for example, `true`) to both panels: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Accordion() { + return ( + <> + <h2>Almaty, Kazakhstan</h2> + <Panel title="About" isActive={true}> + With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. + </Panel> + <Panel title="Etymology" isActive={true}> + The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. + </Panel> + </> + ); +} + +function Panel({ title, children, isActive }) { + return ( + <section className="panel"> + <h3>{title}</h3> + {isActive ? ( + <p>{children}</p> + ) : ( + <button onClick={() => setIsActive(true)}> + Show + </button> + )} + </section> + ); +} +``` + +```css +h3, p { margin: 5px 0px; } +.panel { + padding: 10px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +Try editing the hardcoded `isActive` values in the `Accordion` component and see the result on the screen. + +### Step 3: Add state to the common parent {/*step-3-add-state-to-the-common-parent*/} + +Lifting state up often changes the nature of what you're storing as state. + +In this case, only one panel should be active at a time. This means that the `Accordion` common parent component needs to keep track of *which* panel is the active one. Instead of a `boolean` value, it could use a number as the index of the active `Panel` for the state variable: + +```js +const [activeIndex, setActiveIndex] = useState(0); +``` + +When the `activeIndex` is `0`, the first panel is active, and when it's `1`, it's the second one. + +Clicking the "Show" button in either `Panel` needs to change the active index in `Accordion`. A `Panel` can't set the `activeIndex` state directly because it's defined inside the `Accordion`. The `Accordion` component needs to *explicitly allow* the `Panel` component to change its state by [passing an event handler down as a prop](/learn/responding-to-events#passing-event-handlers-as-props): + +```js +<> + <Panel + isActive={activeIndex === 0} + onShow={() => setActiveIndex(0)} + > + ... + </Panel> + <Panel + isActive={activeIndex === 1} + onShow={() => setActiveIndex(1)} + > + ... + </Panel> +</> +``` + +The `<button>` inside the `Panel` will now use the `onShow` prop as its click event handler: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Accordion() { + const [activeIndex, setActiveIndex] = useState(0); + return ( + <> + <h2>Almaty, Kazakhstan</h2> + <Panel + title="About" + isActive={activeIndex === 0} + onShow={() => setActiveIndex(0)} + > + With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. + </Panel> + <Panel + title="Etymology" + isActive={activeIndex === 1} + onShow={() => setActiveIndex(1)} + > + The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. + </Panel> + </> + ); +} + +function Panel({ + title, + children, + isActive, + onShow +}) { + return ( + <section className="panel"> + <h3>{title}</h3> + {isActive ? ( + <p>{children}</p> + ) : ( + <button onClick={onShow}> + Show + </button> + )} + </section> + ); +} +``` + +```css +h3, p { margin: 5px 0px; } +.panel { + padding: 10px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +This completes lifting state up! Moving state into the common parent component allowed you to coordinate the two panels. Using the active index instead of two "is shown" flags ensured that only one panel is active at a given time. And passing down the event handler to the child allowed the child to change the parent's state. + +<DiagramGroup> + +<Diagram name="sharing_state_parent" height={385} width={487} alt="Diagram showing a tree of three components, one parent labeled Accordion and two children labeled Panel. Accordion contains an activeIndex value of zero which turns into isActive value of true passed to the first Panel, and isActive value of false passed to the second Panel." > + +Initially, `Accordion`'s `activeIndex` is `0`, so the first `Panel` receives `isActive = true` + +</Diagram> + +<Diagram name="sharing_state_parent_clicked" height={385} width={521} alt="The same diagram as the previous, with the activeIndex value of the parent Accordion component highlighted indicating a click with the value changed to one. The flow to both of the children Panel components is also highlighted, and the isActive value passed to each child is set to the opposite: false for the first Panel and true for the second one." > + +When `Accordion`'s `activeIndex` state changes to `1`, the second `Panel` receives `isActive = true` instead + +</Diagram> + +</DiagramGroup> + +<DeepDive> + +#### Controlled and uncontrolled components {/*controlled-and-uncontrolled-components*/} + +It is common to call a component with some local state "uncontrolled". For example, the original `Panel` component with an `isActive` state variable is uncontrolled because its parent cannot influence whether the panel is active or not. + +In contrast, you might say a component is "controlled" when the important information in it is driven by props rather than its own local state. This lets the parent component fully specify its behavior. The final `Panel` component with the `isActive` prop is controlled by the `Accordion` component. + +Uncontrolled components are easier to use within their parents because they require less configuration. But they're less flexible when you want to coordinate them together. Controlled components are maximally flexible, but they require the parent components to fully configure them with props. + +In practice, "controlled" and "uncontrolled" aren't strict technical terms--each component usually has some mix of both local state and props. However, this is a useful way to talk about how components are designed and what capabilities they offer. + +When writing a component, consider which information in it should be controlled (via props), and which information should be uncontrolled (via state). But you can always change your mind and refactor later. + +</DeepDive> + +## A single source of truth for each state {/*a-single-source-of-truth-for-each-state*/} + +In a React application, many components will have their own state. Some state may "live" close to the leaf components (components at the bottom of the tree) like inputs. Other state may "live" closer to the top of the app. For example, even client-side routing libraries are usually implemented by storing the current route in the React state, and passing it down by props! + +**For each unique piece of state, you will choose the component that "owns" it.** This principle is also known as having a ["single source of truth".](https://en.wikipedia.org/wiki/Single_source_of_truth) It doesn't mean that all state lives in one place--but that for _each_ piece of state, there is a _specific_ component that holds that piece of information. Instead of duplicating shared state between components, you will *lift it up* to their common shared parent, and *pass it down* to the children that need it. + +Your app will change as you work on it. It is common that you will move state down or back up while you're still figuring out where each piece of the state "lives". This is all part of the process! + +To see what this feels like in practice with a few more components, read [Thinking in React.](/learn/thinking-in-react) + +<Recap> + +* When you want to coordinate two components, move their state to their common parent. +* Then pass the information down through props from their common parent. +* Finally, pass the event handlers down so that the children can change the parent's state. +* It's useful to consider components as "controlled" (driven by props) or "uncontrolled" (driven by state). + +</Recap> + +<Challenges> + +#### Synced inputs {/*synced-inputs*/} + +These two inputs are independent. Make them stay in sync: editing one input should update the other input with the same text, and vice versa. + +<Hint> + +You'll need to lift their state up into the parent component. + +</Hint> + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function SyncedInputs() { + return ( + <> + <Input label="First input" /> + <Input label="Second input" /> + </> + ); +} + +function Input({ label }) { + const [text, setText] = useState(''); + + function handleChange(e) { + setText(e.target.value); + } + + return ( + <label> + {label} + {' '} + <input + value={text} + onChange={handleChange} + /> + </label> + ); +} +``` + +```css +input { margin: 5px; } +label { display: block; } +``` + +</Sandpack> + +<Solution> + +Move the `text` state variable into the parent component along with the `handleChange` handler. Then pass them down as props to both of the `Input` components. This will keep them in sync. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function SyncedInputs() { + const [text, setText] = useState(''); + + function handleChange(e) { + setText(e.target.value); + } + + return ( + <> + <Input + label="First input" + value={text} + onChange={handleChange} + /> + <Input + label="Second input" + value={text} + onChange={handleChange} + /> + </> + ); +} + +function Input({ label, value, onChange }) { + return ( + <label> + {label} + {' '} + <input + value={value} + onChange={onChange} + /> + </label> + ); +} +``` + +```css +input { margin: 5px; } +label { display: block; } +``` + +</Sandpack> + +</Solution> + +#### Filtering a list {/*filtering-a-list*/} + +In this example, the `SearchBar` has its own `query` state that controls the text input. Its parent `FilterableList` component displays a `List` of items, but it doesn't take the search query into account. + +Use the `filterItems(foods, query)` function to filter the list according to the search query. To test your changes, verify that typing "s" into the input filters down the list to "Sushi", "Shish kebab", and "Dim sum". + +Note that `filterItems` is already implemented and imported so you don't need to write it yourself! + +<Hint> + +You will want to remove the `query` state and the `handleChange` handler from the `SearchBar`, and move them to the `FilterableList`. Then pass them down to `SearchBar` as `query` and `onChange` props. + +</Hint> + +<Sandpack> + +```js +import { useState } from 'react'; +import { foods, filterItems } from './data.js'; + +export default function FilterableList() { + return ( + <> + <SearchBar /> + <hr /> + <List items={foods} /> + </> + ); +} + +function SearchBar() { + const [query, setQuery] = useState(''); + + function handleChange(e) { + setQuery(e.target.value); + } + + return ( + <label> + Search:{' '} + <input + value={query} + onChange={handleChange} + /> + </label> + ); +} + +function List({ items }) { + return ( + <table> + <tbody> + {items.map(food => ( + <tr key={food.id}> + <td>{food.name}</td> + <td>{food.description}</td> + </tr> + ))} + </tbody> + </table> + ); +} +``` + +```js data.js +export function filterItems(items, query) { + query = query.toLowerCase(); + return items.filter(item => + item.name.split(' ').some(word => + word.toLowerCase().startsWith(query) + ) + ); +} + +export const foods = [{ + id: 0, + name: 'Sushi', + description: 'Sushi is a traditional Japanese dish of prepared vinegared rice' +}, { + id: 1, + name: 'Dal', + description: 'The most common way of preparing dal is in the form of a soup to which onions, tomatoes and various spices may be added' +}, { + id: 2, + name: 'Pierogi', + description: 'Pierogi are filled dumplings made by wrapping unleavened dough around a savoury or sweet filling and cooking in boiling water' +}, { + id: 3, + name: 'Shish kebab', + description: 'Shish kebab is a popular meal of skewered and grilled cubes of meat.' +}, { + id: 4, + name: 'Dim sum', + description: 'Dim sum is a large range of small dishes that Cantonese people traditionally enjoy in restaurants for breakfast and lunch' +}]; +``` + +</Sandpack> + +<Solution> + +Lift the `query` state up into the `FilterableList` component. Call `filterItems(foods, query)` to get the filtered list and pass it down to the `List`. Now changing the query input is reflected in the list: + +<Sandpack> + +```js +import { useState } from 'react'; +import { foods, filterItems } from './data.js'; + +export default function FilterableList() { + const [query, setQuery] = useState(''); + const results = filterItems(foods, query); + + function handleChange(e) { + setQuery(e.target.value); + } + + return ( + <> + <SearchBar + query={query} + onChange={handleChange} + /> + <hr /> + <List items={results} /> + </> + ); +} + +function SearchBar({ query, onChange }) { + return ( + <label> + Search:{' '} + <input + value={query} + onChange={onChange} + /> + </label> + ); +} + +function List({ items }) { + return ( + <table> + <tbody> + {items.map(food => ( + <tr key={food.id}> + <td>{food.name}</td> + <td>{food.description}</td> + </tr> + ))} + </tbody> + </table> + ); +} +``` + +```js data.js +export function filterItems(items, query) { + query = query.toLowerCase(); + return items.filter(item => + item.name.split(' ').some(word => + word.toLowerCase().startsWith(query) + ) + ); +} + +export const foods = [{ + id: 0, + name: 'Sushi', + description: 'Sushi is a traditional Japanese dish of prepared vinegared rice' +}, { + id: 1, + name: 'Dal', + description: 'The most common way of preparing dal is in the form of a soup to which onions, tomatoes and various spices may be added' +}, { + id: 2, + name: 'Pierogi', + description: 'Pierogi are filled dumplings made by wrapping unleavened dough around a savoury or sweet filling and cooking in boiling water' +}, { + id: 3, + name: 'Shish kebab', + description: 'Shish kebab is a popular meal of skewered and grilled cubes of meat.' +}, { + id: 4, + name: 'Dim sum', + description: 'Dim sum is a large range of small dishes that Cantonese people traditionally enjoy in restaurants for breakfast and lunch' +}]; +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/start-a-new-react-project.md b/beta/src/content/learn/start-a-new-react-project.md new file mode 100644 index 000000000..3e1d44d36 --- /dev/null +++ b/beta/src/content/learn/start-a-new-react-project.md @@ -0,0 +1,83 @@ +--- +title: Start a New React Project +--- + +<Intro> + +If you're starting a new project, we recommend to use a toolchain or a framework. These tools provide a comfortable development environment but require a local Node.js installation. + +</Intro> + +<YouWillLearn> + +* How toolchains are different from frameworks +* How to start a project with a minimal toolchain +* How to start a project with a fully-featured framework +* What's inside popular toolchains and frameworks + +</YouWillLearn> + +## Choose your own adventure {/*choose-your-own-adventure*/} + +React is a library that lets you organize UI code by breaking it apart into pieces called components. React doesn't take care of routing or data management. This means there are several ways to start a new React project: + +* [Start with an **HTML file and a script tag.**](/learn/add-react-to-a-website) This doesn't require Node.js setup but offers limited features. +* Start with a **minimal toolchain,** adding more features to your project as you go. (Great for learning!) +* Start with an **opinionated framework** that has common features like data fetching and routing built-in. + +## Getting started with a minimal toolchain {/*getting-started-with-a-minimal-toolchain*/} + +If you're **learning React,** we recommend [Create React App.](https://create-react-app.dev/) It is the most popular way to try out React and build a new single-page, client-side application. It's made for React but isn't opinionated about routing or data fetching. + +First, install [Node.js.](https://nodejs.org/en/) Then open your terminal and run this line to create a project: + +<TerminalBlock> + +npx create-react-app my-app + +</TerminalBlock> + +Now you can run your app with: + +<TerminalBlock> + +cd my-app +npm start + +</TerminalBlock> + +For more information, [check out the official guide.](https://create-react-app.dev/docs/getting-started) + +> Create React App doesn't handle backend logic or databases. You can use it with any backend. When you build a project, you'll get a folder with static HTML, CSS and JS. Because Create React App can't take advantage of the server, it doesn't provide the best performance. If you're looking for faster loading times and built-in features like routing and server-side logic, we recommend using a framework instead. + +### Popular alternatives {/*toolkit-popular-alternatives*/} + +* [Vite](https://vitejs.dev/guide/) +* [Parcel](https://parceljs.org/getting-started/webapp/) + +## Building with a full-featured framework {/*building-with-a-full-featured-framework*/} + +If you're looking to **start a production-ready project,** [Next.js](https://nextjs.org/) is a great place to start. Next.js is a popular, lightweight framework for static and server‑rendered applications built with React. It comes pre-packaged with features like routing, styling, and server-side rendering, getting your project up and running quickly. + +The [Next.js Foundations](https://nextjs.org/learn/foundations/about-nextjs) tutorial is a great introduction to building with React and Next.js. + +### Popular alternatives {/*framework-popular-alternatives*/} + +* [Gatsby](https://www.gatsbyjs.org/) +* [Remix](https://remix.run/) +* [Razzle](https://razzlejs.org/) + +## Custom toolchains {/*custom-toolchains*/} + +You may prefer to create and configure your own toolchain. A toolchain typically consists of: + +* A **package manager** lets you install, update, and manage third-party packages. Popular package managers: [npm](https://www.npmjs.com/) (built into Node.js), [Yarn](https://yarnpkg.com/), [pnpm.](https://pnpm.io/) +* A **compiler** lets you compile modern language features and additional syntax like JSX or type annotations for the browsers. Popular compilers: [Babel](https://babeljs.io/), [TypeScript](https://www.typescriptlang.org/), [swc.](https://swc.rs/) +* A **bundler** lets you write modular code and bundle it together into small packages to optimize load time. Popular bundlers: [webpack](https://webpack.js.org/), [Parcel](https://parceljs.org/), [esbuild](https://esbuild.github.io/), [swc.](https://swc.rs/) +* A **minifier** makes your code more compact so that it loads faster. Popular minifiers: [Terser](https://terser.org/), [swc.](https://swc.rs/) +* A **server** handles server requests so that you can render components to HTML. Popular servers: [Express.](https://expressjs.com/) +* A **linter** checks your code for common mistakes. Popular linters: [ESLint.](https://eslint.org/) +* A **test runner** lets you run tests against your code. Popular test runners: [Jest.](https://jestjs.io/) + +If you prefer to set up your own JavaScript toolchain from scratch, [check out this guide](https://blog.usejournal.com/creating-a-react-app-from-scratch-f3c693b84658) that re-creates some of the Create React App functionality. A framework will usually also provide a routing and a data fetching solution. In a larger project, you might also want to manage multiple packages in a single repository with a tool like [Nx](https://nx.dev/react) or [Turborepo.](https://turborepo.org/) + diff --git a/beta/src/content/learn/state-a-components-memory.md b/beta/src/content/learn/state-a-components-memory.md new file mode 100644 index 000000000..e67cf0a51 --- /dev/null +++ b/beta/src/content/learn/state-a-components-memory.md @@ -0,0 +1,1509 @@ +--- +title: "State: A Component's Memory" +--- + +<Intro> + +Components often need to change what's on the screen as a result of an interaction. Typing into the form should update the input field, clicking "next" on an image carousel should change which image is displayed, clicking "buy" should put a product in the shopping cart. Components need to "remember" things: the current input value, the current image, the shopping cart. In React, this kind of component-specific memory is called *state*. + +</Intro> + +<YouWillLearn> + +* How to add a state variable with the [`useState`](/reference/react/useState) Hook +* What pair of values the `useState` Hook returns +* How to add more than one state variable +* Why state is called local + +</YouWillLearn> + +## When a regular variable isn’t enough {/*when-a-regular-variable-isnt-enough*/} + +Here's a component that renders a sculpture image. Clicking the "Next" button should show the next sculpture by changing the `index` to `1`, then `2`, and so on. However, this **won't work** (you can try it!): + +<Sandpack> + +```js +import { sculptureList } from './data.js'; + +export default function Gallery() { + let index = 0; + + function handleClick() { + index = index + 1; + } + + let sculpture = sculptureList[index]; + return ( + <> + <button onClick={handleClick}> + Next + </button> + <h2> + <i>{sculpture.name} </i> + by {sculpture.artist} + </h2> + <h3> + ({index + 1} of {sculptureList.length}) + </h3> + <img + src={sculpture.url} + alt={sculpture.alt} + /> + <p> + {sculpture.description} + </p> + </> + ); +} +``` + +```js data.js +export const sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; +``` + +```css +h2 { margin-top: 10px; margin-bottom: 0; } +h3 { + margin-top: 5px; + font-weight: normal; + font-size: 100%; +} +img { width: 120px; height: 120px; } +button { + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +The `handleClick` event handler is updating a local variable, `index`. But two things prevent that change from being visible: + +1. **Local variables don't persist between renders.** When React renders this component a second time, it renders it from scratch—it doesn't consider any changes to the local variables. +2. **Changes to local variables won't trigger renders.** React doesn't realize it needs to render the component again with the new data. + +To update a component with new data, two things need to happen: + +1. **Retain** the data between renders. +2. **Trigger** React to render the component with new data (re-rendering). + +The [`useState`](/reference/react/useState) Hook provides those two things: + +1. A **state variable** to retain the data between renders. +2. A **state setter function** to update the variable and trigger React to render the component again. + +## Adding a state variable {/*adding-a-state-variable*/} + +To add a state variable, import `useState` from React at the top of the file: + +```js +import { useState } from 'react'; +``` + +Then, replace this line: + +```js +let index = 0; +``` + +with + +```js +const [index, setIndex] = useState(0); +``` + +`index` is a state variable and `setIndex` is the setter function. + +> The `[` and `]` syntax here is called [array destructuring](https://javascript.info/destructuring-assignment) and it lets you read values from an array. The array returned by `useState` always has exactly two items. + +This is how they work together in `handleClick`: + +```js +function handleClick() { + setIndex(index + 1); +} +``` + +Now clicking the "Next" button switches the current sculpture: + +<Sandpack> + +```js +import { useState } from 'react'; +import { sculptureList } from './data.js'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + + function handleClick() { + setIndex(index + 1); + } + + let sculpture = sculptureList[index]; + return ( + <> + <button onClick={handleClick}> + Next + </button> + <h2> + <i>{sculpture.name} </i> + by {sculpture.artist} + </h2> + <h3> + ({index + 1} of {sculptureList.length}) + </h3> + <img + src={sculpture.url} + alt={sculpture.alt} + /> + <p> + {sculpture.description} + </p> + </> + ); +} +``` + +```js data.js +export const sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; +``` + +```css +h2 { margin-top: 10px; margin-bottom: 0; } +h3 { + margin-top: 5px; + font-weight: normal; + font-size: 100%; +} +img { width: 120px; height: 120px; } +button { + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +### Meet your first Hook {/*meet-your-first-hook*/} + +In React, `useState`, as well as any other function starting with "`use`", is called a Hook. + +*Hooks* are special functions that are only available while React is [rendering](/learn/render-and-commit#step-1-trigger-a-render) (which we'll get into in more detail on the next page). They let you "hook into" different React features. + +State is just one of those features, but you will meet the other Hooks later. + +<Pitfall> + +**Hooks—functions starting with `use`—can only be called at the top level of your components or [your own Hooks.](/learn/reusing-logic-with-custom-hooks)** You can't call Hooks inside conditions, loops, or other nested functions. Hooks are functions, but it's helpful to think of them as unconditional declarations about your component's needs. You "use" React features at the top of your component similar to how you "import" modules at the top of your file. + +</Pitfall> + +### Anatomy of `useState` {/*anatomy-of-usestate*/} + +When you call [`useState`](/reference/react/useState), you are telling React that you want this component to remember something: + +```js +const [index, setIndex] = useState(0); +``` + +In this case, you want React to remember `index`. + +> The convention is to name this pair like `const [something, setSomething]`. You could name it anything you like, but conventions make things easier to understand across projects. + +The only argument to `useState` is the **initial value** of your state variable. In this example, the `index`'s initial value is set to `0` with `useState(0)`. + +Every time your component renders, `useState` gives you an array containing two values: + +1. The **state variable** (`index`) with the value you stored. +2. The **state setter function** (`setIndex`) which can update the state variable and trigger React to render the component again. + +Here's how that happens in action: + +```js +const [index, setIndex] = useState(0); +``` + +1. **Your component renders the first time.** Because you passed `0` to `useState` as the initial value for `index`, it will return `[0, setIndex]`. React remembers `0` is the latest state value. +2. **You update the state.** When a user clicks the button, it calls `setIndex(index + 1)`. `index` is `0`, so it's `setIndex(1)`. This tells React to remember `index` is `1` now and triggers another render. +3. **Your component's second render.** React still sees `useState(0)`, but because React *remembers* that you set `index` to `1`, it returns `[1, setIndex]` instead. +4. And so on! + +## Giving a component multiple state variables {/*giving-a-component-multiple-state-variables*/} + +You can have as many state variables of as many types as you like in one component. This component has two state variables, a number `index` and a boolean `showMore` that's toggled when you click "Show details": + +<Sandpack> + +```js +import { useState } from 'react'; +import { sculptureList } from './data.js'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + const [showMore, setShowMore] = useState(false); + + function handleNextClick() { + setIndex(index + 1); + } + + function handleMoreClick() { + setShowMore(!showMore); + } + + let sculpture = sculptureList[index]; + return ( + <> + <button onClick={handleNextClick}> + Next + </button> + <h2> + <i>{sculpture.name} </i> + by {sculpture.artist} + </h2> + <h3> + ({index + 1} of {sculptureList.length}) + </h3> + <button onClick={handleMoreClick}> + {showMore ? 'Hide' : 'Show'} details + </button> + {showMore && <p>{sculpture.description}</p>} + <img + src={sculpture.url} + alt={sculpture.alt} + /> + </> + ); +} +``` + +```js data.js +export const sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; +``` + +```css +h2 { margin-top: 10px; margin-bottom: 0; } +h3 { + margin-top: 5px; + font-weight: normal; + font-size: 100%; +} +img { width: 120px; height: 120px; } +button { + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +It is a good idea to have multiple state variables if their state is unrelated, like `index` and `showMore` in this example. But if you find that you often change two state variables together, it might be better to combine them into a single one. For example, if you have a form with many fields, it's more convenient to have a single state variable that holds an object than state variable per field. [Choosing the State Structure](/learn/choosing-the-state-structure) has more tips on this. + +<DeepDive> + +#### How does React know which state to return? {/*how-does-react-know-which-state-to-return*/} + +You might have noticed that the `useState` call does not receive any information about *which* state variable it refers to. There is no "identifier" that is passed to `useState`, so how does it know which of the state variables to return? Does it rely on some magic like parsing your functions? The answer is no. + +Instead, to enable their concise syntax, Hooks **rely on a stable call order on every render of the same component.** This works well in practice because if you follow the rule above ("only call Hooks at the top level"), Hooks will always be called in the same order. Additionally, a [linter plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks) catches most mistakes. + +Internally, React holds an array of state pairs for every component. It also maintains the current pair index, which is set to `0` before rendering. Each time you call `useState`, React gives you the next state pair and increments the index. You can read more about this mechanism in [React Hooks: Not Magic, Just Arrays.](https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e) + +This example **doesn't use React** but it gives you an idea of how `useState` works internally: + +<Sandpack> + +```js index.js active +let componentHooks = []; +let currentHookIndex = 0; + +// How useState works inside React (simplified). +function useState(initialState) { + let pair = componentHooks[currentHookIndex]; + if (pair) { + // This is not the first render, + // so the state pair already exists. + // Return it and prepare for next Hook call. + currentHookIndex++; + return pair; + } + + // This is the first time we're rendering, + // so create a state pair and store it. + pair = [initialState, setState]; + + function setState(nextState) { + // When the user requests a state change, + // put the new value into the pair. + pair[0] = nextState; + updateDOM(); + } + + // Store the pair for future renders + // and prepare for the next Hook call. + componentHooks[currentHookIndex] = pair; + currentHookIndex++; + return pair; +} + +function Gallery() { + // Each useState() call will get the next pair. + const [index, setIndex] = useState(0); + const [showMore, setShowMore] = useState(false); + + function handleNextClick() { + setIndex(index + 1); + } + + function handleMoreClick() { + setShowMore(!showMore); + } + + let sculpture = sculptureList[index]; + // This example doesn't use React, so + // return an output object instead of JSX. + return { + onNextClick: handleNextClick, + onMoreClick: handleMoreClick, + header: `${sculpture.name} by ${sculpture.artist}`, + counter: `${index + 1} of ${sculptureList.length}`, + more: `${showMore ? 'Hide' : 'Show'} details`, + description: showMore ? sculpture.description : null, + imageSrc: sculpture.url, + imageAlt: sculpture.alt + }; +} + +function updateDOM() { + // Reset the current Hook index + // before rendering the component. + currentHookIndex = 0; + let output = Gallery(); + + // Update the DOM to match the output. + // This is the part React does for you. + nextButton.onclick = output.onNextClick; + header.textContent = output.header; + moreButton.onclick = output.onMoreClick; + moreButton.textContent = output.more; + image.src = output.imageSrc; + image.alt = output.imageAlt; + if (output.description !== null) { + description.textContent = output.description; + description.style.display = ''; + } else { + description.style.display = 'none'; + } +} + +let nextButton = document.getElementById('nextButton'); +let header = document.getElementById('header'); +let moreButton = document.getElementById('moreButton'); +let description = document.getElementById('description'); +let image = document.getElementById('image'); +let sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; + +// Make UI match the initial state. +updateDOM(); +``` + +```html public/index.html +<button id="nextButton"> + Next +</button> +<h3 id="header"></h3> +<button id="moreButton"></button> +<p id="description"></p> +<img id="image"> + +<style> +* { box-sizing: border-box; } +body { font-family: sans-serif; margin: 20px; padding: 0; } +button { display: block; margin-bottom: 10px; } +</style> +``` + +```css +button { display: block; margin-bottom: 10px; } +``` + +</Sandpack> + +You don't have to understand it to use React, but you might find this a helpful mental model. + +</DeepDive> + +## State is isolated and private {/*state-is-isolated-and-private*/} + +State is local to a component instance on the screen. In other words, **if you render the same component twice, each copy will have completely isolated state!** Changing one of them will not affect the other. + +In this example, the `Gallery` component from earlier is rendered twice with no changes to its logic. Try clicking the buttons inside each of the galleries. Notice that their state is independent: + +<Sandpack> + +```js +import Gallery from './Gallery.js'; + +export default function Page() { + return ( + <div className="Page"> + <Gallery /> + <Gallery /> + </div> + ); +} + +``` + +```js Gallery.js +import { useState } from 'react'; +import { sculptureList } from './data.js'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + const [showMore, setShowMore] = useState(false); + + function handleNextClick() { + setIndex(index + 1); + } + + function handleMoreClick() { + setShowMore(!showMore); + } + + let sculpture = sculptureList[index]; + return ( + <section> + <button onClick={handleNextClick}> + Next + </button> + <h2> + <i>{sculpture.name} </i> + by {sculpture.artist} + </h2> + <h3> + ({index + 1} of {sculptureList.length}) + </h3> + <button onClick={handleMoreClick}> + {showMore ? 'Hide' : 'Show'} details + </button> + {showMore && <p>{sculpture.description}</p>} + <img + src={sculpture.url} + alt={sculpture.alt} + /> + </section> + ); +} +``` + +```js data.js +export const sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; +``` + +```css +button { display: block; margin-bottom: 10px; } +.Page > * { + float: left; + width: 50%; + padding: 10px; +} +h2 { margin-top: 10px; margin-bottom: 0; } +h3 { + margin-top: 5px; + font-weight: normal; + font-size: 100%; +} +img { width: 120px; height: 120px; } +button { + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +This is what makes state different from regular variables that you might declare at the top of your module. State is not tied to a particular function call or a place in the code, but it's "local" to the specific place on the screen. You rendered two `<Gallery />` components, so their state is stored separately. + +Also notice how the `Page` component doesn't "know" anything about the `Gallery` state or even whether it has any. Unlike props, **state is fully private to the component declaring it.** The parent component can't change it. This lets you add state to any component or remove it without impacting the rest of the components. + +What if you wanted both galleries to keep their states in sync? The right way to do it in React is to *remove* state from child components and add it to their closest shared parent. The next few pages will focus on organizing state of a single component, but we will return to this topic in [Sharing State Between Components.](/learn/sharing-state-between-components) + +<Recap> + +* Use a state variable when a component needs to "remember" some information between renders. +* State variables are declared by calling the `useState` Hook. +* Hooks are special functions that start with `use`. They let you "hook into" React features like state. +* Hooks might remind you of imports: they need to be called unconditionally. Calling Hooks, including `useState`, is only valid at the top level of a component or another Hook. +* The `useState` Hook returns a pair of values: the current state and the function to update it. +* You can have more than one state variable. Internally, React matches them up by their order. +* State is private to the component. If you render it in two places, each copy gets its own state. + +</Recap> + + + +<Challenges> + +#### Complete the gallery {/*complete-the-gallery*/} + +When you press "Next" on the last sculpture, the code crashes. Fix the logic to prevent the crash. You may do this by adding extra logic to event handler or by disabling the button when the action is not possible. + +After fixing the crash, add a "Previous" button that shows the previous sculpture. It shouldn't crash on the first sculpture. + +<Sandpack> + +```js +import { useState } from 'react'; +import { sculptureList } from './data.js'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + const [showMore, setShowMore] = useState(false); + + function handleNextClick() { + setIndex(index + 1); + } + + function handleMoreClick() { + setShowMore(!showMore); + } + + let sculpture = sculptureList[index]; + return ( + <> + <button onClick={handleNextClick}> + Next + </button> + <h2> + <i>{sculpture.name} </i> + by {sculpture.artist} + </h2> + <h3> + ({index + 1} of {sculptureList.length}) + </h3> + <button onClick={handleMoreClick}> + {showMore ? 'Hide' : 'Show'} details + </button> + {showMore && <p>{sculpture.description}</p>} + <img + src={sculpture.url} + alt={sculpture.alt} + /> + </> + ); +} +``` + +```js data.js +export const sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; +``` + +```css +button { display: block; margin-bottom: 10px; } +.Page > * { + float: left; + width: 50%; + padding: 10px; +} +h2 { margin-top: 10px; margin-bottom: 0; } +h3 { + margin-top: 5px; + font-weight: normal; + font-size: 100%; +} +img { width: 120px; height: 120px; } +``` + +</Sandpack> + +<Solution> + +This adds a guarding condition inside both event handlers and disables the buttons when needed: + +<Sandpack> + +```js +import { useState } from 'react'; +import { sculptureList } from './data.js'; + +export default function Gallery() { + const [index, setIndex] = useState(0); + const [showMore, setShowMore] = useState(false); + + let hasPrev = index > 0; + let hasNext = index < sculptureList.length - 1; + + function handlePrevClick() { + if (hasPrev) { + setIndex(index - 1); + } + } + + function handleNextClick() { + if (hasNext) { + setIndex(index + 1); + } + } + + function handleMoreClick() { + setShowMore(!showMore); + } + + let sculpture = sculptureList[index]; + return ( + <> + <button + onClick={handlePrevClick} + disabled={!hasPrev} + > + Previous + </button> + <button + onClick={handleNextClick} + disabled={!hasNext} + > + Next + </button> + <h2> + <i>{sculpture.name} </i> + by {sculpture.artist} + </h2> + <h3> + ({index + 1} of {sculptureList.length}) + </h3> + <button onClick={handleMoreClick}> + {showMore ? 'Hide' : 'Show'} details + </button> + {showMore && <p>{sculpture.description}</p>} + <img + src={sculpture.url} + alt={sculpture.alt} + /> + </> + ); +} +``` + +```js data.js hidden +export const sculptureList = [{ + name: 'Homenaje a la Neurocirugía', + artist: 'Marta Colvin Andrade', + description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', + url: 'https://i.imgur.com/Mx7dA2Y.jpg', + alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' +}, { + name: 'Floralis Genérica', + artist: 'Eduardo Catalano', + description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', + url: 'https://i.imgur.com/ZF6s192m.jpg', + alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' +}, { + name: 'Eternal Presence', + artist: 'John Woodrow Wilson', + description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', + url: 'https://i.imgur.com/aTtVpES.jpg', + alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' +}, { + name: 'Moai', + artist: 'Unknown Artist', + description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', + url: 'https://i.imgur.com/RCwLEoQm.jpg', + alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' +}, { + name: 'Blue Nana', + artist: 'Niki de Saint Phalle', + description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', + url: 'https://i.imgur.com/Sd1AgUOm.jpg', + alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' +}, { + name: 'Ultimate Form', + artist: 'Barbara Hepworth', + description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', + url: 'https://i.imgur.com/2heNQDcm.jpg', + alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' +}, { + name: 'Cavaliere', + artist: 'Lamidi Olonade Fakeye', + description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", + url: 'https://i.imgur.com/wIdGuZwm.png', + alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' +}, { + name: 'Big Bellies', + artist: 'Alina Szapocznikow', + description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", + url: 'https://i.imgur.com/AlHTAdDm.jpg', + alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' +}, { + name: 'Terracotta Army', + artist: 'Unknown Artist', + description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', + url: 'https://i.imgur.com/HMFmH6m.jpg', + alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' +}, { + name: 'Lunar Landscape', + artist: 'Louise Nevelson', + description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', + url: 'https://i.imgur.com/rN7hY6om.jpg', + alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' +}, { + name: 'Aureole', + artist: 'Ranjani Shettar', + description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', + url: 'https://i.imgur.com/okTpbHhm.jpg', + alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' +}, { + name: 'Hippos', + artist: 'Taipei Zoo', + description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', + url: 'https://i.imgur.com/6o5Vuyu.jpg', + alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' +}]; +``` + +```css +button { display: block; margin-bottom: 10px; } +.Page > * { + float: left; + width: 50%; + padding: 10px; +} +h2 { margin-top: 10px; margin-bottom: 0; } +h3 { + margin-top: 5px; + font-weight: normal; + font-size: 100%; +} +img { width: 120px; height: 120px; } +``` + +</Sandpack> + +Notice how `hasPrev` and `hasNext` are used *both* for the returned JSX and inside the event handlers! This handy pattern works because event handler functions ["close over"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) any variables declared while rendering. + +</Solution> + +#### Fix stuck form inputs {/*fix-stuck-form-inputs*/} + +When you type into the input fields, nothing appears. It's like the input values are "stuck" with empty strings. The `value` of the first `<input>` is set to always match the `firstName` variable, and the `value` for the second `<input>` is set to always match the `lastName` variable. This is correct. Both inputs have `onChange` event handlers, which try to update the variables based on the latest user input (`e.target.value`). However, the variables don't seem to "remember" their values between re-renders. Fix this by using state variables instead. + +<Sandpack> + +```js +export default function Form() { + let firstName = ''; + let lastName = ''; + + function handleFirstNameChange(e) { + firstName = e.target.value; + } + + function handleLastNameChange(e) { + lastName = e.target.value; + } + + function handleReset() { + firstName = ''; + lastName = ''; + } + + return ( + <form onSubmit={e => e.preventDefault()}> + <input + placeholder="First name" + value={firstName} + onChange={handleFirstNameChange} + /> + <input + placeholder="Last name" + value={lastName} + onChange={handleLastNameChange} + /> + <h1>Hi, {firstName} {lastName}</h1> + <button onClick={handleReset}>Reset</button> + </form> + ); +} +``` + +```css +h1 { margin-top: 10px; } +``` + +</Sandpack> + +<Solution> + +First, import `useState` from React. Then replace `firstName` and `lastName` with state variables declared by calling `useState`. Finally, replace every `firstName = ...` assignment with `setFirstName(...)`, and do the same for `lastName`. Don't forget to update `handleReset` too so that the reset button works. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + + function handleFirstNameChange(e) { + setFirstName(e.target.value); + } + + function handleLastNameChange(e) { + setLastName(e.target.value); + } + + function handleReset() { + setFirstName(''); + setLastName(''); + } + + return ( + <form onSubmit={e => e.preventDefault()}> + <input + placeholder="First name" + value={firstName} + onChange={handleFirstNameChange} + /> + <input + placeholder="Last name" + value={lastName} + onChange={handleLastNameChange} + /> + <h1>Hi, {firstName} {lastName}</h1> + <button onClick={handleReset}>Reset</button> + </form> + ); +} +``` + +```css +h1 { margin-top: 10px; } +``` + +</Sandpack> + +</Solution> + +#### Fix a crash {/*fix-a-crash*/} + +Here is a small form that is supposed to let the user leave some feedback. When the feedback is submitted, it's supposed to display a thank-you message. However, it crashes with an error message saying "Rendered fewer hooks than expected". Can you spot the mistake and fix it? + +<Hint> + +Are there any limitations on _where_ Hooks may be called? Does this component break any rules? Check if there are any comments disabling the linter checks--this is where the bugs often hide! + +</Hint> + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FeedbackForm() { + const [isSent, setIsSent] = useState(false); + if (isSent) { + return <h1>Thank you!</h1>; + } else { + // eslint-disable-next-line + const [message, setMessage] = useState(''); + return ( + <form onSubmit={e => { + e.preventDefault(); + alert(`Sending: "${message}"`); + setIsSent(true); + }}> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <br /> + <button type="submit">Send</button> + </form> + ); + } +} +``` + +</Sandpack> + +<Solution> + +Hooks can only be called at the top level of the component function. Here, the first `isSent` definition follows this rule, but the `message` definition is nested in a condition. + +Move it out of the condition to fix the issue: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FeedbackForm() { + const [isSent, setIsSent] = useState(false); + const [message, setMessage] = useState(''); + + if (isSent) { + return <h1>Thank you!</h1>; + } else { + return ( + <form onSubmit={e => { + e.preventDefault(); + alert(`Sending: "${message}"`); + setIsSent(true); + }}> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <br /> + <button type="submit">Send</button> + </form> + ); + } +} +``` + +</Sandpack> + +Remember, Hooks must be called unconditionally and always in the same order! + +You could also remove the unnecessary `else` branch to reduce the nesting. However, it's still important that all calls to Hooks happen *before* the first `return`. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FeedbackForm() { + const [isSent, setIsSent] = useState(false); + const [message, setMessage] = useState(''); + + if (isSent) { + return <h1>Thank you!</h1>; + } + + return ( + <form onSubmit={e => { + e.preventDefault(); + alert(`Sending: "${message}"`); + setIsSent(true); + }}> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <br /> + <button type="submit">Send</button> + </form> + ); +} +``` + +</Sandpack> + +Try moving the second `useState` call after the `if` condition and notice how this breaks it again. + +If your linter is [configured for React](/learn/editor-setup#linting), you should see a lint error when you make a mistake like this. If you don't see an error when you try the faulty code locally, you need to set up linting for your project. + +</Solution> + +#### Remove unnecessary state {/*remove-unnecessary-state*/} + +When the button is clicked, this example should ask for the user's name and then display an alert greeting them. You tried to use state to keep the name, but for some reason it always shows "Hello, !". + +To fix this code, remove the unnecessary state variable. (We will discuss about [why this didn't work](/learn/state-as-a-snapshot) later.) + +Can you explain why this state variable was unnecessary? + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FeedbackForm() { + const [name, setName] = useState(''); + + function handleClick() { + setName(prompt('What is your name?')); + alert(`Hello, ${name}!`); + } + + return ( + <button onClick={handleClick}> + Greet + </button> + ); +} +``` + +</Sandpack> + +<Solution> + +Here is a fixed version that uses a regular `name` variable declared in the function that needs it: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FeedbackForm() { + function handleClick() { + const name = prompt('What is your name?'); + alert(`Hello, ${name}!`); + } + + return ( + <button onClick={handleClick}> + Greet + </button> + ); +} +``` + +</Sandpack> + +A state variable is only necessary to keep information between re-renders of a component. Within a single event handler, a regular variable will do fine. Don't introduce state variables when a regular variable works well. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/state-as-a-snapshot.md b/beta/src/content/learn/state-as-a-snapshot.md new file mode 100644 index 000000000..556553d33 --- /dev/null +++ b/beta/src/content/learn/state-as-a-snapshot.md @@ -0,0 +1,435 @@ +--- +title: State as a Snapshot +--- + +<Intro> + +State variables might look like regular JavaScript variables that you can read and write to. However, state behaves more like a snapshot. Setting it does not change the state variable you already have, but instead triggers a re-render. + +</Intro> + +<YouWillLearn> + +* How setting state triggers re-renders +* When and how state updates +* Why state does not update immediately after you set it +* How event handlers access a "snapshot" of the state + +</YouWillLearn> + +## Setting state triggers renders {/*setting-state-triggers-renders*/} + +You might think of your user interface as changing directly in response to the user event like a click. In React, it works a little differently from this mental model. On the previous page, you saw that [setting state requests a re-render](/learn/render-and-commit#step-1-trigger-a-render) from React. This means that for an interface to react to the event, you need to *update the state*. + +In this example, when you press "send", `setIsSent(true)` tells React to re-render the UI: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [isSent, setIsSent] = useState(false); + const [message, setMessage] = useState('Hi!'); + if (isSent) { + return <h1>Your message is on its way!</h1> + } + return ( + <form onSubmit={(e) => { + e.preventDefault(); + setIsSent(true); + sendMessage(message); + }}> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <button type="submit">Send</button> + </form> + ); +} + +function sendMessage(message) { + // ... +} +``` + +```css +label, textarea { margin-bottom: 10px; display: block; } +``` + +</Sandpack> + +Here's what happens when you click the button: + +1. The `onSubmit` event handler executes. +2. `setIsSent(true)` sets `isSent` to `true` and queues a new render. +3. React re-renders the component according to the new `isSent` value. + +Let's take a closer look at the relationship between state and rendering. + +## Rendering takes a snapshot in time {/*rendering-takes-a-snapshot-in-time*/} + +["Rendering"](/learn/render-and-commit#step-2-react-renders-your-components) means that React is calling your component, which is a function. The JSX you return from that function is like a snapshot of the UI in time. Its props, event handlers, and local variables were all calculated **using its state at the time of the render.** + +Unlike a photograph or a movie frame, the UI "snapshot" you return is interactive. It includes logic like event handlers that specify what happens in response to inputs. React then updates the screen to match this snapshot and connects the event handlers. As a result, pressing a button will trigger the click handler from your JSX. + +When React re-renders a component: + +1. React calls your function again. +2. Your function returns a new JSX snapshot. +3. React then updates the screen to match the snapshot you've returned. + +<IllustrationBlock sequential> + <Illustration caption="React executing the function" src="/images/docs/illustrations/i_render1.png" /> + <Illustration caption="Calculating the snapshot" src="/images/docs/illustrations/i_render2.png" /> + <Illustration caption="Updating the DOM tree" src="/images/docs/illustrations/i_render3.png" /> +</IllustrationBlock> + +As a component's memory, state is not like a regular variable that disappears after your function returns. State actually "lives" in React itself--as if on a shelf!--outside of your function. When React calls your component, it gives you a snapshot of the state for that particular render. Your component returns a snapshot of the UI with a fresh set of props and event handlers in its JSX, all calculated **using the state values from that render!** + +<IllustrationBlock sequential> + <Illustration caption="You tell React to update the state" src="/images/docs/illustrations/i_state-snapshot1.png" /> + <Illustration caption="React updates the state value" src="/images/docs/illustrations/i_state-snapshot2.png" /> + <Illustration caption="React passes a snapshot of the state value into the component" src="/images/docs/illustrations/i_state-snapshot3.png" /> +</IllustrationBlock> + +Here's a little experiment to show you how this works. In this example, you might expect that clicking the "+3" button would increment the counter three times because it calls `setNumber(number + 1)` three times. + +See what happens when you click the "+3" button: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> + <h1>{number}</h1> + <button onClick={() => { + setNumber(number + 1); + setNumber(number + 1); + setNumber(number + 1); + }}>+3</button> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } +``` + +</Sandpack> + +Notice that `number` only increments once per click! + +**Setting state only changes it for the *next* render.** During the first render, `number` was `0`. This is why, in *that render's* `onClick` handler, the value of `number` is still `0` even after `setNumber(number + 1)` was called: + +```js +<button onClick={() => { + setNumber(number + 1); + setNumber(number + 1); + setNumber(number + 1); +}}>+3</button> +``` + +Here is what this button's click handler tells React to do: + +1. `setNumber(number + 1)`: `number` is `0` so `setNumber(0 + 1)`. + - React prepares to change `number` to `1` on the next render. +2. `setNumber(number + 1)`: `number` is `0` so `setNumber(0 + 1)`. + - React prepares to change `number` to `1` on the next render. +3. `setNumber(number + 1)`: `number` is `0` so `setNumber(0 + 1)`. + - React prepares to change `number` to `1` on the next render. + +Even though you called `setNumber(number + 1)` three times, in *this render's* event handler `number` is always `0`, so you set the state to `1` three times. This is why, after your event handler finishes, React re-renders the component with `number` equal to `1` rather than `3`. + +You can also visualize this by mentally substituting state variables with their values in your code. Since the `number` state variable is `0` for *this render*, its event handler looks like this: + +```js +<button onClick={() => { + setNumber(0 + 1); + setNumber(0 + 1); + setNumber(0 + 1); +}}>+3</button> +``` + +For the next render, `number` is `1`, so *that render's* click handler looks like this: + +```js +<button onClick={() => { + setNumber(1 + 1); + setNumber(1 + 1); + setNumber(1 + 1); +}}>+3</button> +``` + +This is why clicking the button again will set the counter to `2`, then to `3` on the next click, and so on. + +## State over time {/*state-over-time*/} + +Well, that was fun. Try to guess what clicking this button will alert: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> + <h1>{number}</h1> + <button onClick={() => { + setNumber(number + 5); + alert(number); + }}>+5</button> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } +``` + +</Sandpack> + +If you use the substitution method from before, you can guess that the alert shows "0": + +```js +setNumber(0 + 5); +alert(0); +``` + +But what if you put a timer on the alert, so it only fires _after_ the component re-rendered? Would it say "0" or "5"? Have a guess! + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> + <h1>{number}</h1> + <button onClick={() => { + setNumber(number + 5); + setTimeout(() => { + alert(number); + }, 3000); + }}>+5</button> + </> + ) +} +``` + +```css +button { display: inline-block; margin: 10px; font-size: 20px; } +h1 { display: inline-block; margin: 10px; width: 30px; text-align: center; } +``` + +</Sandpack> + +Surprised? If you use the substitution method, you can see the "snapshot" of the state passed to the alert. + +```js +setNumber(0 + 5); +setTimeout(() => { + alert(0); +}, 3000); +``` + +The state stored in React may have changed by the time the alert runs, but it was scheduled using a snapshot of the state at the time the user interacted with it! + +**A state variable's value never changes within a render,** even if its event handler's code is asynchronous. Inside *that render's* `onClick`, the value of `number` continues to be `0` even after `setNumber(number + 5)` was called. Its value was "fixed" when React "took the snapshot" of the UI by calling your component. + +Here is an example of how that makes your event handlers less prone to timing mistakes. Below is a form that sends a message with a five-second delay. Imagine this scenario: + +1. You press the "Send" button, sending "Hello" to Alice. +2. Before the five-second delay ends, you change the value of the "To" field to "Bob". + +What do you expect the `alert` to display? Would it display, "You said Hello to Alice"? Or would it display, "You said Hello to Bob"? Make a guess based on what you know, and then try it: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [to, setTo] = useState('Alice'); + const [message, setMessage] = useState('Hello'); + + function handleSubmit(e) { + e.preventDefault(); + setTimeout(() => { + alert(`You said ${message} to ${to}`); + }, 5000); + } + + return ( + <form onSubmit={handleSubmit}> + <label> + To:{' '} + <select + value={to} + onChange={e => setTo(e.target.value)}> + <option value="Alice">Alice</option> + <option value="Bob">Bob</option> + </select> + </label> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <button type="submit">Send</button> + </form> + ); +} +``` + +```css +label, textarea { margin-bottom: 10px; display: block; } +``` + +</Sandpack> + +**React keeps the state values "fixed" within one render's event handlers.** You don't need to worry whether the state has changed while the code is running. + +But what if you wanted to read the latest state before a re-render? You'll want to use a [state updater function](/learn/queueing-a-series-of-state-updates), covered on the next page! + +<Recap> + +* Setting state requests a new render. +* React stores state outside of your component, as if on a shelf. +* When you call `useState`, React gives you a snapshot of the state *for that render*. +* Variables and event handlers don't "survive" re-renders. Every render has its own event handlers. +* Every render (and functions inside it) will always "see" the snapshot of the state that React gave to *that* render. +* You can mentally substitute state in event handlers, similarly to how you think about the rendered JSX. +* Event handlers created in the past have the state values from the render in which they were created. + +</Recap> + + + +<Challenges> + +#### Implement a traffic light {/*implement-a-traffic-light*/} + +Here is a crosswalk light component that toggles on when the button is pressed: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function TrafficLight() { + const [walk, setWalk] = useState(true); + + function handleClick() { + setWalk(!walk); + } + + return ( + <> + <button onClick={handleClick}> + Change to {walk ? 'Stop' : 'Walk'} + </button> + <h1 style={{ + color: walk ? 'darkgreen' : 'darkred' + }}> + {walk ? 'Walk' : 'Stop'} + </h1> + </> + ); +} +``` + +```css +h1 { margin-top: 20px; } +``` + +</Sandpack> + +Add an `alert` to the click handler. When the light is green and says "Walk", clicking the button should say "Stop is next". When the light is red and says "Stop", clicking the button should say "Walk is next". + +Does it make a difference whether you put the `alert` before or after the `setWalk` call? + +<Solution> + +Your `alert` should look like this: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function TrafficLight() { + const [walk, setWalk] = useState(true); + + function handleClick() { + setWalk(!walk); + alert(walk ? 'Stop is next' : 'Walk is next'); + } + + return ( + <> + <button onClick={handleClick}> + Change to {walk ? 'Stop' : 'Walk'} + </button> + <h1 style={{ + color: walk ? 'darkgreen' : 'darkred' + }}> + {walk ? 'Walk' : 'Stop'} + </h1> + </> + ); +} +``` + +```css +h1 { margin-top: 20px; } +``` + +</Sandpack> + +Whether you put it before or after the `setWalk` call makes no difference. That render's value of `walk` is fixed. Calling `setWalk` will only change it for the *next* render, but will not affect the event handler from the previous render. + +This line might seem counter-intuitive at first: + +```js +alert(walk ? 'Stop is next' : 'Walk is next'); +``` + +But it makes sense if you read it as: "If the traffic light shows 'Walk now', the message should say 'Stop is next.'" The `walk` variable inside your event handler matches that render's value of `walk` and does not change. + +You can verify that this is correct by applying the substitution method. When `walk` is `true`, you get: + +```js +<button onClick={() => { + setWalk(false); + alert('Stop is next'); +}}> + Change to Stop +</button> +<h1 style={{color: 'darkgreen'}}> + Walk +</h1> +``` + +So clicking "Change to Stop" queues a render with `walk` set to `false`, and alerts "Stop is next". + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/synchronizing-with-effects.md b/beta/src/content/learn/synchronizing-with-effects.md new file mode 100644 index 000000000..74b642435 --- /dev/null +++ b/beta/src/content/learn/synchronizing-with-effects.md @@ -0,0 +1,1579 @@ +--- +title: 'Synchronizing with Effects' +--- + +<Intro> + +Some components need to synchronize with external systems. For example, you might want to control a non-React component based on the React state, set up a server connection, or send an analytics log when a component appears on the screen. *Effects* let you run some code after rendering so that you can synchronize your component with some system outside of React. + +</Intro> + +<YouWillLearn> + +- What Effects are +- How Effects are different from events +- How to declare an Effect in your component +- How to skip re-running an Effect unnecessarily +- Why Effects run twice in development and how to fix them + +</YouWillLearn> + +## What are Effects and how are they different from events? {/*what-are-effects-and-how-are-they-different-from-events*/} + +Before getting to Effects, you need to be familiar with two types of logic inside React components: + +- **Rendering code** (introduced in [Describing the UI](/learn/describing-the-ui)) lives at the top level of your component. This is where you take the props and state, transform them, and return the JSX you want to see on the screen. [Rendering code must be pure.](/learn/keeping-components-pure) Like a math formula, it should only _calculate_ the result, but not do anything else. + +- **Event handlers** (introduced in [Adding Interactivity](/learn/adding-interactivity)) are nested functions inside your components that *do* things rather than just calculate them. An event handler might update an input field, submit an HTTP POST request to buy a product, or navigate the user to another screen. Event handlers contain ["side effects"](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) (they change the program's state) and are caused by a specific user action (for example, a button click or typing). + +Sometimes this isn't enough. Consider a `ChatRoom` component that must connect to the chat server whenever it's visible on the screen. Connecting to a server is not a pure calculation (it's a side effect) so it can't happen during rendering. However, there is no single particular event like a click that causes `ChatRoom` to be displayed. + +***Effects* let you specify side effects that are caused by rendering itself, rather than by a particular event.** Sending a message in the chat is an *event* because it is directly caused by the user clicking a specific button. However, setting up a server connection is an *Effect* because it needs to happen regardless of which interaction caused the component to appear. Effects run at the end of the [rendering process](/learn/render-and-commit) after the screen updates. This is a good time to synchronize the React components with some external system (like network or a third-party library). + +<Note> + +Here and later in this text, capitalized "Effect" refers to the React-specific definition above, i.e. a side effect caused by rendering. To refer to the broader programming concept, we'll say "side effect". + +</Note> + + +## You might not need an Effect {/*you-might-not-need-an-effect*/} + +**Don't rush to add Effects to your components.** Keep in mind that Effects are typically used to "step out" of your React code and synchronize with some *external* system. This includes browser APIs, third-party widgets, network, and so on. If your Effect only adjusts some state based on other state, [you might not need an Effect.](/learn/you-might-not-need-an-effect) + +## How to write an Effect {/*how-to-write-an-effect*/} + +To write an Effect, follow these three steps: + +1. **Declare an Effect.** By default, your Effect will run after every render. +2. **Specify the Effect dependencies.** Most Effects should only re-run *when needed* rather than after every render. For example, a fade-in animation should only trigger when a component appears. Connecting and disconnecting to a chat room should only happen when the component appears and disappears, or when the chat room changes. You will learn how to control this by specifying *dependencies.* +3. **Add cleanup if needed.** Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, "connect" needs "disconnect", "subscribe" needs "unsubscribe", and "fetch" needs either "cancel" or "ignore". You will learn how to do this by returning a *cleanup function*. + +Let's look at each of these steps in detail. + +### Step 1: Declare an Effect {/*step-1-declare-an-effect*/} + +To declare an Effect in your component, import the [`useEffect` Hook](/reference/react/useEffect) from React: + +```js +import { useEffect } from 'react'; +``` + +Then, call it at the top level of your component and put some code inside your Effect: + +```js {2-4} +function MyComponent() { + useEffect(() => { + // Code here will run after *every* render + }); + return <div />; +} +``` + +Every time your component renders, React will update the screen *and then* run the code inside `useEffect`. In other words, **`useEffect` "delays" a piece of code from running until that render is reflected on the screen.** + +Let's see how you can use an Effect to synchronize with an external system. Consider a `<VideoPlayer>` React component. It would be nice to control whether it's playing or paused by passing an `isPlaying` prop to it: + +```js +<VideoPlayer isPlaying={isPlaying} />; +``` + +Your custom `VideoPlayer` component renders the built-in browser [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) tag: + +```js +function VideoPlayer({ src, isPlaying }) { + // TODO: do something with isPlaying + return <video src={src} />; +} +``` + +However, the browser `<video>` tag does not have an `isPlaying` prop. The only way to control it is to manually call the [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) and [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) methods on the DOM element. **You need to synchronize the value of `isPlaying` prop, which tells whether the video _should_ currently be playing, with imperative calls like `play()` and `pause()`.** + +We'll need to first [get a ref](/learn/manipulating-the-dom-with-refs) to the `<video>` DOM node. + +You might be tempted to try to call `play()` or `pause()` during rendering, but that isn't correct: + +<Sandpack> + +```js +import { useState, useRef, useEffect } from 'react'; + +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + + if (isPlaying) { + ref.current.play(); // Calling these while rendering isn't allowed. + } else { + ref.current.pause(); // Also, this crashes. + } + + return <video ref={ref} src={src} loop playsInline />; +} + +export default function App() { + const [isPlaying, setIsPlaying] = useState(false); + return ( + <> + <button onClick={() => setIsPlaying(!isPlaying)}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <VideoPlayer + isPlaying={isPlaying} + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + /> + </> + ); +} +``` + +```css +button { display: block; margin-bottom: 20px; } +video { width: 250px; } +``` + +</Sandpack> + +The reason this code isn't correct is that it tries to do something with the DOM node during rendering. In React, [rendering should be a pure calculation](/learn/keeping-components-pure) of JSX and should not contain side effects like modifying the DOM. + +Moreover, when `VideoPlayer` is called for the first time, its DOM does not exist yet! There isn't a DOM node yet to call `play()` or `pause()` on, because React doesn't know what DOM to create until after you return the JSX. + +The solution here is to **wrap the side effect with `useEffect` to move it out of the rendering calculation:** + +```js {6,12} +import { useEffect, useRef } from 'react'; + +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + + useEffect(() => { + if (isPlaying) { + ref.current.play(); + } else { + ref.current.pause(); + } + }); + + return <video ref={ref} src={src} loop playsInline />; +} +``` + +By wrapping the DOM update in an Effect, you let React update the screen first. Then your Effect runs. + +When your `VideoPlayer` component renders (either the first time or if it re-renders), a few things will happen. First, React will update the screen, ensuring the `<video>` tag is in the DOM with the right props. Then React will run your Effect. Finally, your Effect will call `play()` or `pause()` depending on the value of `isPlaying` prop. + +Press Play/Pause multiple times and see how the video player stays synchronized to the `isPlaying` value: + +<Sandpack> + +```js +import { useState, useRef, useEffect } from 'react'; + +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + + useEffect(() => { + if (isPlaying) { + ref.current.play(); + } else { + ref.current.pause(); + } + }); + + return <video ref={ref} src={src} loop playsInline />; +} + +export default function App() { + const [isPlaying, setIsPlaying] = useState(false); + return ( + <> + <button onClick={() => setIsPlaying(!isPlaying)}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <VideoPlayer + isPlaying={isPlaying} + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + /> + </> + ); +} +``` + +```css +button { display: block; margin-bottom: 20px; } +video { width: 250px; } +``` + +</Sandpack> + +In this example, the "external system" you synchronized to React state was the browser media API. You can use a similar approach to wrap legacy non-React code (like jQuery plugins) into declarative React components. + +Note that controlling a video player is much more complex in practice. Calling `play()` may fail, the user might play or pause using the built-in browser controls, and so on. This example is very simplified and incomplete. + +<Pitfall> + +By default, Effects run after *every* render. This is why code like this will **produce an infinite loop:** + +```js +const [count, setCount] = useState(0); +useEffect(() => { + setCount(count + 1); +}); +``` + +Effects run as a *result* of rendering. Setting state *triggers* rendering. Setting state immediately in an Effect is like plugging a power outlet into itself. The Effect runs, it sets the state, which causes a re-render, which causes the Effect to run, it sets the state again, this causes another re-render, and so on. + +Effects should usually synchronize your components with an *external* system. If there's no external system and you only want to adjust some state based on other state, [you might not need an Effect.](/learn/you-might-not-need-an-effect) + +</Pitfall> + +### Step 2: Specify the Effect dependencies {/*step-2-specify-the-effect-dependencies*/} + +By default, Effects run after *every* render. Often, this is **not what you want:** + +- Sometimes, it's slow. Synchronizing with an external system is not always instant, so you might want to skip doing it unless it's necessary. For example, you don't want to reconnect to the chat server on every keystroke. +- Sometimes, it's wrong. For example, you don't want to trigger a component fade-in animation on every keystroke. The animation should only play once when the component appears for the first time. + +To demonstrate the issue, here is the previous example with a few `console.log` calls and a text input that updates the parent component's state. Notice how typing causes the Effect to re-run: + +<Sandpack> + +```js +import { useState, useRef, useEffect } from 'react'; + +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + + useEffect(() => { + if (isPlaying) { + console.log('Calling video.play()'); + ref.current.play(); + } else { + console.log('Calling video.pause()'); + ref.current.pause(); + } + }); + + return <video ref={ref} src={src} loop playsInline />; +} + +export default function App() { + const [isPlaying, setIsPlaying] = useState(false); + const [text, setText] = useState(''); + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={() => setIsPlaying(!isPlaying)}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <VideoPlayer + isPlaying={isPlaying} + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + /> + </> + ); +} +``` + +```css +input, button { display: block; margin-bottom: 20px; } +video { width: 250px; } +``` + +</Sandpack> + +You can tell React to **skip unnecessarily re-running the Effect** by specifying an array of *dependencies* as the second argument to the `useEffect` call. Start by adding an empty `[]` array to the above example on line 14: + +```js {3} + useEffect(() => { + // ... + }, []); +``` + +You should see an error saying `React Hook useEffect has a missing dependency: 'isPlaying'`: + +<Sandpack> + +```js +import { useState, useRef, useEffect } from 'react'; + +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + + useEffect(() => { + if (isPlaying) { + console.log('Calling video.play()'); + ref.current.play(); + } else { + console.log('Calling video.pause()'); + ref.current.pause(); + } + }, []); // This causes an error + + return <video ref={ref} src={src} loop playsInline />; +} + +export default function App() { + const [isPlaying, setIsPlaying] = useState(false); + const [text, setText] = useState(''); + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={() => setIsPlaying(!isPlaying)}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <VideoPlayer + isPlaying={isPlaying} + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + /> + </> + ); +} +``` + +```css +input, button { display: block; margin-bottom: 20px; } +video { width: 250px; } +``` + +</Sandpack> + +The problem is that the code inside of your Effect *depends on* the `isPlaying` prop to decide what to do, but this dependency was not explicitly declared. To fix this issue, add `isPlaying` to the dependency array: + +```js {2,7} + useEffect(() => { + if (isPlaying) { // It's used here... + // ... + } else { + // ... + } + }, [isPlaying]); // ...so it must be declared here! +``` + +Now all dependencies are declared, so there is no error. Specifying `[isPlaying]` as the dependency array tells React that it should skip re-running your Effect if `isPlaying` is the same as it was during the previous render. With this change, typing into the input doesn't cause the Effect to re-run, but pressing Play/Pause does: + +<Sandpack> + +```js +import { useState, useRef, useEffect } from 'react'; + +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + + useEffect(() => { + if (isPlaying) { + console.log('Calling video.play()'); + ref.current.play(); + } else { + console.log('Calling video.pause()'); + ref.current.pause(); + } + }, [isPlaying]); + + return <video ref={ref} src={src} loop playsInline />; +} + +export default function App() { + const [isPlaying, setIsPlaying] = useState(false); + const [text, setText] = useState(''); + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={() => setIsPlaying(!isPlaying)}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <VideoPlayer + isPlaying={isPlaying} + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + /> + </> + ); +} +``` + +```css +input, button { display: block; margin-bottom: 20px; } +video { width: 250px; } +``` + +</Sandpack> + +The dependency array can contain multiple dependencies. React will only skip re-running the Effect if *all* of the dependencies you specify have exactly the same values as they had during the previous render. React compares the dependency values using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. See the [`useEffect` API reference](/reference/react/useEffect#reference) for more details. + +**Notice that you can't "choose" your dependencies.** You will get a lint error if the dependencies you specified don't match what React expects based on the code inside your Effect. This helps catch many bugs in your code. If your Effect uses some value but you *don't* want to re-run the Effect when it changes, you'll need to [*edit the Effect code itself* to not "need" that dependency.](/learn/lifecycle-of-reactive-effects#what-to-do-when-you-dont-want-to-re-synchronize) + +<Pitfall> + +The behaviors *without* the dependency array and with an *empty* `[]` dependency array are very different: + +```js {3,7,11} +useEffect(() => { + // This runs after every render +}); + +useEffect(() => { + // This runs only on mount (when the component appears) +}, []); + +useEffect(() => { + // This runs on mount *and also* if either a or b have changed since the last render +}, [a, b]); +``` + +We'll take a close look at what "mount" means in the next step. + +</Pitfall> + +<DeepDive> + +#### Why was the ref omitted from the dependency array? {/*why-was-the-ref-omitted-from-the-dependency-array*/} + +This Effect uses _both_ `ref` and `isPlaying`, but only `isPlaying` is declared as a dependency: + +```js {9} +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + useEffect(() => { + if (isPlaying) { + ref.current.play(); + } else { + ref.current.pause(); + } + }, [isPlaying]); +``` + +This is because the `ref` object has a *stable identity:* React guarantees [you'll always get the same object](/reference/react/useRef#returns) from the same `useRef` call on every render. It never changes, so it will never by itself cause the Effect to re-run. Therefore, it does not matter whether you include it or not. Including it is fine too: + +```js {9} +function VideoPlayer({ src, isPlaying }) { + const ref = useRef(null); + useEffect(() => { + if (isPlaying) { + ref.current.play(); + } else { + ref.current.pause(); + } + }, [isPlaying, ref]); +``` + +The [`set` functions](/reference/react/useState#setstate) returned by `useState` also have stable identity, so you will often see them omitted from the dependencies too. If the linter lets you omit a dependency without errors, it is safe to do. + +Omitting always-stable dependencies only works when the linter can "see" that the object is stable. For example, if `ref` was passed from a parent component, you would have to specify it in the dependency array. However, this is good because you can't know whether the parent component always passes the same ref, or passes one of several refs conditionally. So your Effect _would_ depend on which ref is passed. + +</DeepDive> + +### Step 3: Add cleanup if needed {/*step-3-add-cleanup-if-needed*/} + +Consider a different example. You're writing a `ChatRoom` component that needs to connect to the chat server when it appears. You are given a `createConnection()` API that returns an object with `connect()` and `disconnect()` methods. How do you keep the component connected while it is displayed to the user? + +Start by writing the Effect logic: + +```js +useEffect(() => { + const connection = createConnection(); + connection.connect(); +}); +``` + +It would be slow to connect to the chat after every re-render, so you add the dependency array: + +```js {4} +useEffect(() => { + const connection = createConnection(); + connection.connect(); +}, []); +``` + +**The code inside the Effect does not use any props or state, so your dependency array is `[]` (empty). This tells React to only run this code when the component "mounts", i.e. appears on the screen for the first time.** + +Let's try running this code: + +<Sandpack> + +```js +import { useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export default function ChatRoom() { + useEffect(() => { + const connection = createConnection(); + connection.connect(); + }, []); + return <h1>Welcome to the chat!</h1>; +} +``` + +```js chat.js +export function createConnection() { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting...'); + }, + disconnect() { + console.log('❌ Disconnected.'); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +This Effect only runs on mount, so you might expect `"✅ Connecting..."` to be printed once in the console. **However, if you check the console, `"✅ Connecting..."` gets printed twice. Why does it happen?** + +Imagine the `ChatRoom` component is a part of a larger app with many different screens. The user starts their journey on the `ChatRoom` page. The component mounts and calls `connection.connect()`. Then imagine the user navigates to another screen--for example, to the Settings page. The `ChatRoom` component unmounts. Finally, the user clicks Back and `ChatRoom` mounts again. This would set up a second connection--but the first connection was never destroyed! As the user navigates across the app, the connections would keep piling up. + +Bugs like this are easy to miss without extensive manual testing. To help you spot them quickly, in development React remounts every component once immediately after its initial mount. **Seeing the `"✅ Connecting..."` log twice helps you notice the real issue: your code doesn't close the connection when the component unmounts.** + +To fix the issue, return a *cleanup function* from your Effect: + +```js {4-6} + useEffect(() => { + const connection = createConnection(); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, []); +``` + +React will call your cleanup function each time before the Effect runs again, and one final time when the component unmounts (gets removed). Let's see what happens when the cleanup function is implemented: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export default function ChatRoom() { + useEffect(() => { + const connection = createConnection(); + connection.connect(); + return () => connection.disconnect(); + }, []); + return <h1>Welcome to the chat!</h1>; +} +``` + +```js chat.js +export function createConnection() { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting...'); + }, + disconnect() { + console.log('❌ Disconnected.'); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +Now you get three console logs in development: + +1. `"✅ Connecting..."` +2. `"❌ Disconnected."` +3. `"✅ Connecting..."` + +**This is the correct behavior in development.** By remounting your component, React verifies that navigating away and back would not break your code. Disconnecting and then connecting again is exactly what should happen! When you implement the cleanup well, there should be no user-visible difference between running the Effect once vs running it, cleaning it up, and running it again. There's an extra connect/disconnect call pair because React is probing your code for bugs in development. This is normal and you shouldn't try to make it go away. + +**In production, you would only see `"✅ Connecting..."` printed once.** Remounting components only happens in development to help you find Effects that need cleanup. You can turn off [Strict Mode](/reference/react/StrictMode) to opt out of the development behavior, but we recommend keeping it on. This lets you find many bugs like the one above. + +## How to handle the Effect firing twice in development? {/*how-to-handle-the-effect-firing-twice-in-development*/} + +React intentionally remounts your components in development to help you find bugs like in the last example. **The right question isn't "how to run an Effect once", but "how to fix my Effect so that it works after remounting".** + +Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn't be able to distinguish between the Effect running once (as in production) and a _setup → cleanup → setup_ sequence (as you'd see in development). + +Most of the Effects you'll write will fit into one of the common patterns below. + +### Controlling non-React widgets {/*controlling-non-react-widgets*/} + +Sometimes you need to add UI widgets that aren't written to React. For example, let's say you're adding a map component to your page. It has a `setZoomLevel()` method, and you'd like to keep the zoom level in sync with a `zoomLevel` state variable in your React code. Your Effect would look like similar to this: + +```js +useEffect(() => { + const map = mapRef.current; + map.setZoomLevel(zoomLevel); +}, [zoomLevel]); +``` + +Note that there is no cleanup needed in this case. In development, React will call the Effect twice, but this is not a problem because calling `setZoomLevel` twice with the same value does not do anything. It may be slightly slower, but this doesn't matter because the remounting is development-only and won't happen in production. + +Some APIs may not allow you to call them twice in a row. For example, the [`showModal`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) method of the built-in [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement) element throws if you call it twice. Implement the cleanup function and make it close the dialog: + +```js {4} +useEffect(() => { + const dialog = dialogRef.current; + dialog.showModal(); + return () => dialog.close(); +}, []); +``` + +In development, your Effect will call `showModal()`, then immediately `close()`, and then `showModal()` again. This has the same user-visible behavior as calling `showModal()` once, as you would see in production. + +### Subscribing to events {/*subscribing-to-events*/} + +If your Effect subscribes to something, the cleanup function should unsubscribe: + +```js {6} +useEffect(() => { + function handleScroll(e) { + console.log(e.clientX, e.clientY); + } + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); +}, []); +``` + +In development, your Effect will call `addEventListener()`, then immediately `removeEventListener()`, and then `addEventListener()` again with the same handler. So there would be only one active subscription at a time. This has the same user-visible behavior as calling `addEventListener()` once, as you would see in production. + +### Triggering animations {/*triggering-animations*/} + +If your Effect animates something in, the cleanup function should reset the animation to the initial values: + +```js {4-6} +useEffect(() => { + const node = ref.current; + node.style.opacity = 1; // Trigger the animation + return () => { + node.style.opacity = 0; // Reset to the initial value + }; +}, []); +``` + +In development, opacity will be set to `1`, then to `0`, and then to `1` again. This should have the same user-visible behavior as setting it to `1` directly, which is what would happen in production. If you use a third-party animation library with support for tweening, your cleanup function should reset the tween's timeline to its initial state. + +### Fetching data {/*fetching-data*/} + +If your Effect fetches something, the cleanup function should either [abort the fetch](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) or ignore its result: + +```js {2,6,13-15} +useEffect(() => { + let ignore = false; + + async function startFetching() { + const json = await fetchTodos(userId); + if (!ignore) { + setTodos(json); + } + } + + startFetching(); + + return () => { + ignore = true; + }; +}, [userId]); +``` + +You can't "undo" a network request that already happened, but your cleanup function should ensure that the fetch that's _not relevant anymore_ does not keep affecting your application. For example, if the `userId` changes from `'Alice'` to `'Bob'`, cleanup ensures that the `'Alice'` response is ignored even if it arrives after `'Bob'`. + +**In development, you will see two fetches in the Network tab.** There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned up so its copy of the `ignore` variable will be set to `true`. So even though there is an extra request, it won't affect the state thanks to the `if (!ignore)` check. + +**In production, there will only be one request.** If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components: + +```js +function TodoList() { + const todos = useSomeDataLibrary(`/api/user/${userId}/todos`); + // ... +``` + +This will not only improve the development experience, but also make your application feel faster. For example, the user pressing the Back button won't have to wait for some data to load again because it will be cached. You can either build such a cache yourself or use one of the many existing alternatives to manual fetching in Effects. + +<DeepDive> + +#### What are good alternatives to data fetching in Effects? {/*what-are-good-alternatives-to-data-fetching-in-effects*/} + +Writing `fetch` calls inside Effects is a [popular way to fetch data](https://www.robinwieruch.de/react-hooks-fetch-data/), especially in fully client-side apps. This is, however, a very manual approach and it has significant downsides: + +- **Effects don't run on the server.** This means that the initial server-rendered HTML will only include a loading state with no data. The client computer will have to download all JavaScript and render your app only to discover that now it needs to load the data. This is not very efficient. +- **Fetching directly in Effects makes it easy to create "network waterfalls".** You render the parent component, it fetches some data, renders the child components, and then they start fetching their data. If the network is not very fast, this is significantly slower than fetching all data in parallel. +- **Fetching directly in Effects usually means you don't preload or cache data.** For example, if the component unmounts and then mounts again, it would have to fetch the data again. +- **It's not very ergonomic.** There's quite a bit of boilerplate code involved when writing `fetch` calls in a way that doesn't suffer from bugs like [race conditions.](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect) + +This list of downsides is not specific to React. It applies to fetching data on mount with any library. Like with routing, data fetching is not trivial to do well, so we recommend the following approaches: + +- **If you use a [framework](/learn/start-a-new-react-project#building-with-a-full-featured-framework), use its built-in data fetching mechanism.** Modern React frameworks have integrated data fetching mechanisms that are efficient and don't suffer from the above pitfalls. +- **Otherwise, consider using or building a client-side cache.** Popular open source solutions include [React Query](https://tanstack.com/query/latest), [useSWR](https://swr.vercel.app/), and [React Router 6.4+.](https://beta.reactrouter.com/en/main/start/overview) You can build your own solution too, in which case you would use Effects under the hood but also add logic for deduplicating requests, caching responses, and avoiding network waterfalls (by preloading data or hoisting data requirements to routes). + +You can continue fetching data directly in Effects if neither of these approaches suit you. + +</DeepDive> + +### Sending analytics {/*sending-analytics*/} + +Consider this code that sends an analytics event on the page visit: + +```js +useEffect(() => { + logVisit(url); // Sends a POST request +}, [url]); +``` + +In development, `logVisit` will be called twice for every URL, so you might be tempted to try to work around it. **We recommend to keep this code as is.** Like with earlier examples, there is no *user-visible* behavior difference between running it once and running it twice. From a practical point of view, `logVisit` should not do anything in development because you don't want the logs from the development machines to skew the production metrics. Your component remounts every time you save its file, so it would send extra visits during development anyway. + +**In production, there will be no duplicate visit logs.** + +To debug the analytics events you're sending, you can deploy your app to a staging environment (which runs in production mode) or temporarily opt out of [Strict Mode](/reference/react/StrictMode) and its development-only remounting checks. You may also send analytics from the route change event handlers instead of Effects. For even more precise analytics, [intersection observers](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) can help track which components are in the viewport and how long they remain visible. + +### Not an Effect: Initializing the application {/*not-an-effect-initializing-the-application*/} + +Some logic should only run once when the application starts. You can put it outside your components: + +```js {2-3} +if (typeof window !== 'undefined') { // Check if we're running in the browser. + checkAuthToken(); + loadDataFromLocalStorage(); +} + +function App() { + // ... +} +``` + +This guarantees that such logic only runs once after the browser loads the page. + +### Not an Effect: Buying a product {/*not-an-effect-buying-a-product*/} + +Sometimes, even if you write a cleanup function, there's no way to prevent user-visible consequences of running the Effect twice. For example, maybe your Effect sends a POST request like buying a product: + +```js {2-3} +useEffect(() => { + // 🔴 Wrong: This Effect fires twice in development, exposing a problem in the code. + fetch('/api/buy', { method: 'POST' }); +}, []); +``` + +You wouldn't want to buy the product twice. However, this is also why you shouldn't put this logic in an Effect. What if the user goes to another page and then presses Back? Your Effect would run again. You don't want to buy the product when the user *visits* a page; you want to buy it when the user *clicks* the Buy button. + +Buying is not caused by rendering; it's caused by a specific interaction. It only runs once because the interaction (a click) happens once. **Delete the Effect and move your `/api/buy` request into the Buy button event handler:** + +```js {2-3} + function handleClick() { + // ✅ Buying is an event because it is caused by a particular interaction. + fetch('/api/buy', { method: 'POST' }); + } +``` + +**This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs.** From the user's perspective, visiting a page shouldn't be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don't break this principle by remounting them once in development. + +## Putting it all together {/*putting-it-all-together*/} + +This playground can help you "get a feel" for how Effects work in practice. + +This example uses [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) to schedule a console log with the input text to appear three seconds after the Effect runs. The cleanup function cancels the pending timeout. Start by pressing "Mount the component": + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +function Playground() { + const [text, setText] = useState('a'); + + useEffect(() => { + function onTimeout() { + console.log('⏰ ' + text); + } + + console.log('🔵 Schedule "' + text + '" log'); + const timeoutId = setTimeout(onTimeout, 3000); + + return () => { + console.log('🟡 Cancel "' + text + '" log'); + clearTimeout(timeoutId); + }; + }, [text]); + + return ( + <> + <label> + What to log:{' '} + <input + value={text} + onChange={e => setText(e.target.value)} + /> + </label> + <h1>{text}</h1> + </> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Unmount' : 'Mount'} the component + </button> + {show && <hr />} + {show && <Playground />} + </> + ); +} +``` + +</Sandpack> + +You will see three logs at first: `Schedule "a" log`, `Cancel "a" log`, and `Schedule "a" log` again. Three second later there will also be a log saying `a`. As you learned earlier on this page, the extra schedule/cancel pair is because **React remounts the component once in development to verify that you've implemented cleanup well.** + +Now edit the input to say `abc`. If you do it fast enough, you'll see `Schedule "ab" log` immediately followed by `Cancel "ab" log` and `Schedule "abc" log`. **React always cleans up the previous render's Effect before the next render's Effect.** This is why even if you type into the input fast, there is at most one timeout scheduled at a time. Edit the input a few times and watch the console to get a feel for how Effects get cleaned up. + +Type something into the input and then immediately press "Unmount the component". **Notice how unmounting cleans up the last render's Effect.** In this example, it clears the last timeout before it has a chance to fire. + +Finally, edit the component above and **comment out the cleanup function** so that the timeouts don't get cancelled. Try typing `abcde` fast. What do you expect to happen in three seconds? Will `console.log(text)` inside the timeout print the *latest* `text` and produce five `abcde` logs? Give it a try to check your intuition! + +Three seconds later, you should see a sequence of logs (`a`, `ab`, `abc`, `abcd`, and `abcde`) rather than five `abcde` logs. **Each Effect "captures" the `text` value from its corresponding render.** It doesn't matter that the `text` state changed: an Effect from the render with `text = 'ab'` will always see `'ab'`. In other words, Effects from each render are isolated from each other. If you're curious how this works, you can read about [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures). + +<DeepDive> + +#### Each render has its own Effects {/*each-render-has-its-own-effects*/} + +You can think of `useEffect` as "attaching" a piece of behavior to the render output. Consider this Effect: + +```js +export default function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return <h1>Welcome to {roomId}!</h1>; +} +``` + +Let's see what exactly happens as the user navigates around the app. + +#### Initial render {/*initial-render*/} + +The user visits `<ChatRoom roomId="general" />`. Let's [mentally substitute](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `roomId` with `'general'`: + +```js + // JSX for the first render (roomId = "general") + return <h1>Welcome to general!</h1>; +``` + +**The Effect is *also* a part of the rendering output.** The first render's Effect becomes: + +```js + // Effect for the first render (roomId = "general") + () => { + const connection = createConnection('general'); + connection.connect(); + return () => connection.disconnect(); + }, + // Dependencies for the first render (roomId = "general") + ['general'] +``` + +React runs this Effect, which connects to the `'general'` chat room. + +#### Re-render with same dependencies {/*re-render-with-same-dependencies*/} + +Let's say `<ChatRoom roomId="general" />` re-renders. The JSX output is the same: + +```js + // JSX for the second render (roomId = "general") + return <h1>Welcome to general!</h1>; +``` + +React sees that the rendering output has not changed, so it doesn't update the DOM. + +The Effect from the second render looks like this: + +```js + // Effect for the second render (roomId = "general") + () => { + const connection = createConnection('general'); + connection.connect(); + return () => connection.disconnect(); + }, + // Dependencies for the second render (roomId = "general") + ['general'] +``` + +React compares `['general']` from the second render with `['general']` from the first render. **Because all dependencies are the same, React *ignores* the Effect from the second render.** It never gets called. + +#### Re-render with different dependencies {/*re-render-with-different-dependencies*/} + +Then, the user visits `<ChatRoom roomId="travel" />`. This time, the component returns different JSX: + +```js + // JSX for the third render (roomId = "travel") + return <h1>Welcome to travel!</h1>; +``` + +React updates the DOM to change `"Welcome to general"` into `"Welcome to travel"`. + +The Effect from the third render looks like this: + +```js + // Effect for the third render (roomId = "travel") + () => { + const connection = createConnection('travel'); + connection.connect(); + return () => connection.disconnect(); + }, + // Dependencies for the third render (roomId = "travel") + ['travel'] +``` + +React compares `['travel']` from the third render with `['general']` from the second render. One dependency is different: `Object.is('travel', 'general')` is `false`. The Effect can't be skipped. + +**Before React can apply the Effect from the third render, it needs to clean up the last Effect that _did_ run.** The second render's Effect was skipped, so React needs to clean up the first render's Effect. If you scroll up to the first render, you'll see that its cleanup calls `disconnect()` on the connection that was created with `createConnection('general')`. This disconnects the app from the `'general'` chat room. + +After that, React runs the third render's Effect. It connects to the `'travel'` chat room. + +#### Unmount {/*unmount*/} + +Finally, let's say the user navigates away, and the `ChatRoom` component unmounts. React runs the last Effect's cleanup function. The last Effect was from the third render. The third render's cleanup destroys the `createConnection('travel')` connection. So the app disconnects from the `'travel'` room. + +#### Development-only behaviors {/*development-only-behaviors*/} + +When [Strict Mode](/reference/react/StrictMode) is on, React remounts every component once after mount (state and DOM are preserved). This [helps you find Effects that need cleanup](#step-3-add-cleanup-if-needed) and exposes bugs like race conditions early. Additionally, React will remount the Effects whenever you save a file in development. Both of these behaviors are development-only. + +</DeepDive> + +<Recap> + +- Unlike events, Effects are caused by rendering itself rather than a particular interaction. +- Effects let you synchronize a component with some external system (third-party API, network, etc). +- By default, Effects run after every render (including the initial one). +- React will skip the Effect if all of its dependencies have the same values as during the last render. +- You can't "choose" your dependencies. They are determined by the code inside the Effect. +- An empty dependency array (`[]`) corresponds to the component "mounting", i.e. being added to the screen. +- When Strict Mode is on, React mounts components twice (in development only!) to stress-test your Effects. +- If your Effect breaks because of remounting, you need to implement a cleanup function. +- React will call your cleanup function before the Effect runs next time, and during the unmount. + +</Recap> + +<Challenges> + +#### Focus a field on mount {/*focus-a-field-on-mount*/} + +In this example, the form renders a `<MyInput />` component. + +Use the input's [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) method to make `MyInput` automatically focus when it appears on the screen. There is already a commented out implementation, but it doesn't quite work. Figure out why it doesn't work, and fix it. (If you're familiar with the `autoFocus` attribute, pretend that it does not exist: we are reimplementing the same functionality from scratch.) + +<Sandpack> + +```js MyInput.js active +import { useEffect, useRef } from 'react'; + +export default function MyInput({ value, onChange }) { + const ref = useRef(null); + + // TODO: This doesn't quite work. Fix it. + // ref.current.focus() + + return ( + <input + ref={ref} + value={value} + onChange={onChange} + /> + ); +} +``` + +```js App.js hidden +import { useState } from 'react'; +import MyInput from './MyInput.js'; + +export default function Form() { + const [show, setShow] = useState(false); + const [name, setName] = useState('Taylor'); + const [upper, setUpper] = useState(false); + return ( + <> + <button onClick={() => setShow(s => !s)}>{show ? 'Hide' : 'Show'} form</button> + <br /> + <hr /> + {show && ( + <> + <label> + Enter your name: + <MyInput + value={name} + onChange={e => setName(e.target.value)} + /> + </label> + <label> + <input + type="checkbox" + checked={upper} + onChange={e => setUpper(e.target.checked)} + /> + Make it uppercase + </label> + <p>Hello, <b>{upper ? name.toUpperCase() : name}</b></p> + </> + )} + </> + ); +} +``` + +```css +label { + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + +body { + min-height: 150px; +} +``` + +</Sandpack> + + +To verify that your solution works, press "Show form" and verify that the input receives focus (becomes highlighted and the cursor is placed inside). Press "Hide form" and "Show form" again. Verify the input is highlighted again. + +`MyInput` should only focus _on mount_ rather than after every render. To verify that the behavior is right, press "Show form" and then repeatedly press the "Make it uppercase" checkbox. Clicking the checkbox should _not_ focus the input above it. + +<Solution> + +Calling `ref.current.focus()` during render is wrong because it is a *side effect*. Side effects should either be placed inside an event handler or be declared with `useEffect`. In this case, the side effect is _caused_ by the component appearing rather than by any specific interaction, so it makes sense to put it in an Effect. + +To fix the mistake, wrap the `ref.current.focus()` call into an Effect declaration. Then, to ensure that this Effect runs only on mount rather than after every render, add the empty `[]` dependencies to it. + +<Sandpack> + +```js MyInput.js active +import { useEffect, useRef } from 'react'; + +export default function MyInput({ value, onChange }) { + const ref = useRef(null); + + useEffect(() => { + ref.current.focus(); + }, []); + + return ( + <input + ref={ref} + value={value} + onChange={onChange} + /> + ); +} +``` + +```js App.js hidden +import { useState } from 'react'; +import MyInput from './MyInput.js'; + +export default function Form() { + const [show, setShow] = useState(false); + const [name, setName] = useState('Taylor'); + const [upper, setUpper] = useState(false); + return ( + <> + <button onClick={() => setShow(s => !s)}>{show ? 'Hide' : 'Show'} form</button> + <br /> + <hr /> + {show && ( + <> + <label> + Enter your name: + <MyInput + value={name} + onChange={e => setName(e.target.value)} + /> + </label> + <label> + <input + type="checkbox" + checked={upper} + onChange={e => setUpper(e.target.checked)} + /> + Make it uppercase + </label> + <p>Hello, <b>{upper ? name.toUpperCase() : name}</b></p> + </> + )} + </> + ); +} +``` + +```css +label { + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + +body { + min-height: 150px; +} +``` + +</Sandpack> + +</Solution> + +#### Focus a field conditionally {/*focus-a-field-conditionally*/} + +This form renders two `<MyInput />` components. + +Press "Show form" and notice that the second field automatically gets focused. This is because both of the `<MyInput />` components try to focus the field inside. When you call `focus()` for two input fields in a row, the last one always "wins". + +Let's say you want to focus the first field. The first `MyInput` component now receives a boolean `shouldFocus` prop set to `true`. Change the logic so that `focus()` is only called if the `shouldFocus` prop received by `MyInput` is `true`. + +<Sandpack> + +```js MyInput.js active +import { useEffect, useRef } from 'react'; + +export default function MyInput({ shouldFocus, value, onChange }) { + const ref = useRef(null); + + // TODO: call focus() only if shouldFocus is true. + useEffect(() => { + ref.current.focus(); + }, []); + + return ( + <input + ref={ref} + value={value} + onChange={onChange} + /> + ); +} +``` + +```js App.js hidden +import { useState } from 'react'; +import MyInput from './MyInput.js'; + +export default function Form() { + const [show, setShow] = useState(false); + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + const [upper, setUpper] = useState(false); + const name = firstName + ' ' + lastName; + return ( + <> + <button onClick={() => setShow(s => !s)}>{show ? 'Hide' : 'Show'} form</button> + <br /> + <hr /> + {show && ( + <> + <label> + Enter your first name: + <MyInput + value={firstName} + onChange={e => setFirstName(e.target.value)} + shouldFocus={true} + /> + </label> + <label> + Enter your last name: + <MyInput + value={lastName} + onChange={e => setLastName(e.target.value)} + shouldFocus={false} + /> + </label> + <p>Hello, <b>{upper ? name.toUpperCase() : name}</b></p> + </> + )} + </> + ); +} +``` + +```css +label { + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + +body { + min-height: 150px; +} +``` + +</Sandpack> + +To verify your solution, press "Show form" and "Hide form" repeatedly. When the form appears, only the *first* input should get focused. This is because the parent component renders the first input with `shouldFocus={true}` and the second input with `shouldFocus={false}`. Also check that both inputs still work and you can type into both of them. + +<Hint> + +You can't declare an Effect conditionally, but your Effect can include conditional logic. + +</Hint> + +<Solution> + +Put the conditional logic inside the Effect. You will need to specify `shouldFocus` as a dependency because you are using it inside the Effect. (This means that if some input's `shouldFocus` changes from `false` to `true`, it will focus after mount.) + +<Sandpack> + +```js MyInput.js active +import { useEffect, useRef } from 'react'; + +export default function MyInput({ shouldFocus, value, onChange }) { + const ref = useRef(null); + + useEffect(() => { + if (shouldFocus) { + ref.current.focus(); + } + }, [shouldFocus]); + + return ( + <input + ref={ref} + value={value} + onChange={onChange} + /> + ); +} +``` + +```js App.js hidden +import { useState } from 'react'; +import MyInput from './MyInput.js'; + +export default function Form() { + const [show, setShow] = useState(false); + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + const [upper, setUpper] = useState(false); + const name = firstName + ' ' + lastName; + return ( + <> + <button onClick={() => setShow(s => !s)}>{show ? 'Hide' : 'Show'} form</button> + <br /> + <hr /> + {show && ( + <> + <label> + Enter your first name: + <MyInput + value={firstName} + onChange={e => setFirstName(e.target.value)} + shouldFocus={true} + /> + </label> + <label> + Enter your last name: + <MyInput + value={lastName} + onChange={e => setLastName(e.target.value)} + shouldFocus={false} + /> + </label> + <p>Hello, <b>{upper ? name.toUpperCase() : name}</b></p> + </> + )} + </> + ); +} +``` + +```css +label { + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + +body { + min-height: 150px; +} +``` + +</Sandpack> + +</Solution> + +#### Fix an interval that fires twice {/*fix-an-interval-that-fires-twice*/} + +This `Counter` component displays a counter that should increment every second. On mount, it calls [`setInterval`.](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) This causes `onTick` to run every second. The `onTick` function increments the counter. + +However, instead of incrementing once per second, it increments twice. Why is that? Find the cause of the bug and fix it. + +<Hint> + +Keep in mind that `setInterval` returns an interval ID, which you can pass to [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) to stop the interval. + +</Hint> + +<Sandpack> + +```js Counter.js active +import { useState, useEffect } from 'react'; + +export default function Counter() { + const [count, setCount] = useState(0); + + useEffect(() => { + function onTick() { + setCount(c => c + 1); + } + + setInterval(onTick, 1000); + }, []); + + return <h1>{count}</h1>; +} +``` + +```js App.js hidden +import { useState } from 'react'; +import Counter from './Counter.js'; + +export default function Form() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(s => !s)}>{show ? 'Hide' : 'Show'} counter</button> + <br /> + <hr /> + {show && <Counter />} + </> + ); +} +``` + +```css +label { + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + +body { + min-height: 150px; +} +``` + +</Sandpack> + +<Solution> + +When [Strict Mode](/reference/react/StrictMode) is on (like in the sandboxes on this site), React remounts each component once in development. This causes the interval to be set up twice, and this is why each second the counter increments twice. + +However, React's behavior is not the *cause* of the bug: the bug already exists in the code. React's behavior makes the bug more noticeable. The real cause is that this Effect starts a process but doesn't provide a way to clean it up. + +To fix this code, save the interval ID returned by `setInterval`, and implement a cleanup function with [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval): + +<Sandpack> + +```js Counter.js active +import { useState, useEffect } from 'react'; + +export default function Counter() { + const [count, setCount] = useState(0); + + useEffect(() => { + function onTick() { + setCount(c => c + 1); + } + + const intervalId = setInterval(onTick, 1000); + return () => clearInterval(intervalId); + }, []); + + return <h1>{count}</h1>; +} +``` + +```js App.js hidden +import { useState } from 'react'; +import Counter from './Counter.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(s => !s)}>{show ? 'Hide' : 'Show'} counter</button> + <br /> + <hr /> + {show && <Counter />} + </> + ); +} +``` + +```css +label { + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + +body { + min-height: 150px; +} +``` + +</Sandpack> + +In development, React will still remount your component once to verify that you've implemented cleanup well. So there will be a `setInterval` call, immediately followed by `clearInterval`, and `setInterval` again. In production, there will be only one `setInterval` call. The user-visible behavior in both cases is the same: the counter increments once per second. + +</Solution> + +#### Fix fetching inside an Effect {/*fix-fetching-inside-an-effect*/} + +This component shows the biography for the selected person. It loads the biography by calling an asynchronous function `fetchBio(person)` on mount and whenever `person` changes. That asynchronous function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which eventually resolves to a string. When fetching is done, it calls `setBio` to display that string under the select box. + +<Sandpack> + +```js App.js +import { useState, useEffect } from 'react'; +import { fetchBio } from './api.js'; + +export default function Page() { + const [person, setPerson] = useState('Alice'); + const [bio, setBio] = useState(null); + + useEffect(() => { + setBio(null); + fetchBio(person).then(result => { + setBio(result); + }); + }, [person]); + + return ( + <> + <select value={person} onChange={e => { + setPerson(e.target.value); + }}> + <option value="Alice">Alice</option> + <option value="Bob">Bob</option> + <option value="Taylor">Taylor</option> + </select> + <hr /> + <p><i>{bio ?? 'Loading...'}</i></p> + </> + ); +} +``` + +```js api.js hidden +export async function fetchBio(person) { + const delay = person === 'Bob' ? 2000 : 200; + return new Promise(resolve => { + setTimeout(() => { + resolve('This is ' + person + '’s bio.'); + }, delay); + }) +} + +``` + +</Sandpack> + + +There is a bug in this code. Start by selecting "Alice". Then select "Bob" and then immediately after that select "Taylor". If you do this fast enough, you will notice that bug: Taylor is selected, but the paragraph below says "This is Bob's bio." + +Why does this happen? Fix the bug inside this Effect. + +<Hint> + +If an Effect fetches something asynchronously, it usually needs cleanup. + +</Hint> + +<Solution> + +To trigger the bug, things need to happen in this order: + +- Selecting `'Bob'` triggers `fetchBio('Bob')` +- Selecting `'Taylor'` triggers `fetchBio('Taylor')` +- **Fetching `'Taylor'` completes *before* fetching `'Bob'`** +- The Effect from the `'Taylor'` render calls `setBio('This is Taylor’s bio')` +- Fetching `'Bob'` completes +- The Effect from the `'Bob'` render calls `setBio('This is Bob’s bio')` + +This is why you see Bob's bio even though Taylor is selected. Bugs like this are called [race conditions](https://en.wikipedia.org/wiki/Race_condition) because two asynchronous operations are "racing" with each other, and they might arrive in an unexpected order. + +To fix this race condition, add a cleanup function: + +<Sandpack> + +```js App.js +import { useState, useEffect } from 'react'; +import { fetchBio } from './api.js'; + +export default function Page() { + const [person, setPerson] = useState('Alice'); + const [bio, setBio] = useState(null); + useEffect(() => { + let ignore = false; + setBio(null); + fetchBio(person).then(result => { + if (!ignore) { + setBio(result); + } + }); + return () => { + ignore = true; + } + }, [person]); + + return ( + <> + <select value={person} onChange={e => { + setPerson(e.target.value); + }}> + <option value="Alice">Alice</option> + <option value="Bob">Bob</option> + <option value="Taylor">Taylor</option> + </select> + <hr /> + <p><i>{bio ?? 'Loading...'}</i></p> + </> + ); +} +``` + +```js api.js hidden +export async function fetchBio(person) { + const delay = person === 'Bob' ? 2000 : 200; + return new Promise(resolve => { + setTimeout(() => { + resolve('This is ' + person + '’s bio.'); + }, delay); + }) +} + +``` + +</Sandpack> + +Each render's Effect has its own `ignore` variable. Initially, the `ignore` variable is set to `false`. However, if an Effect gets cleaned up (such as when you select a different person), its `ignore` variable becomes `true`. So now it doesn't matter in which order the requests complete. Only the last person's Effect will have `ignore` set to `false`, so it will call `setBio(result)`. Past Effects have been cleaned up, so the `if (!ignore)` check will prevent them from calling `setBio`: + +- Selecting `'Bob'` triggers `fetchBio('Bob')` +- Selecting `'Taylor'` triggers `fetchBio('Taylor')` **and cleans up the previous (Bob's) Effect** +- Fetching `'Taylor'` completes *before* fetching `'Bob'` +- The Effect from the `'Taylor'` render calls `setBio('This is Taylor’s bio')` +- Fetching `'Bob'` completes +- The Effect from the `'Bob'` render **does not do anything because its `ignore` flag was set to `true`** + +In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problems. + +</Solution> + +</Challenges> + diff --git a/beta/src/content/learn/thinking-in-react.md b/beta/src/content/learn/thinking-in-react.md new file mode 100644 index 000000000..82dc4c0fc --- /dev/null +++ b/beta/src/content/learn/thinking-in-react.md @@ -0,0 +1,649 @@ +--- +title: Thinking in React +--- + +<Intro> + +React can change how you think about the designs you look at and the apps you build. When you build a user interface with React, you will first break it apart into pieces called *components.* Then, you will describe the different visual states for each of your components. Finally, you will connect your components together so that the data flows through them. In this tutorial, we'll guide you through the thought process of building a searchable product data table with React. + +</Intro> + +## Start with the mockup {/*start-with-the-mockup*/} + +Imagine that you already have a JSON API and a mockup from a designer. + +The JSON API returns some data that looks like this: + +```json +[ + { category: "Fruits", price: "$1", stocked: true, name: "Apple" }, + { category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" }, + { category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" }, + { category: "Vegetables", price: "$2", stocked: true, name: "Spinach" }, + { category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" }, + { category: "Vegetables", price: "$1", stocked: true, name: "Peas" } +] +``` + +The mockup looks like this: + +<img src="/images/docs/s_thinking-in-react_ui.png" width="300" style={{margin: '0 auto'}} /> + +To implement a UI in React, you will usually follow the same five steps. + +## Step 1: Break the UI into a component hierarchy {/*step-1-break-the-ui-into-a-component-hierarchy*/} + +Start by drawing boxes around every component and subcomponent in the mockup and naming them. If you work with a designer, they may have already named these components in their design tool. Check in with them! + +Depending on your background, you can think about splitting up a design into components in different ways: + +* **Programming**--use the same techniques for deciding if you should create a new function or object. One such technique is the [single responsibility principle](https://en.wikipedia.org/wiki/Single_responsibility_principle), that is, a component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller subcomponents. +* **CSS**--consider what you would make class selectors for. (However, components are a bit less granular.) +* **Design**--consider how you would organize the design's layers. + +If your JSON is well-structured, you'll often find that it naturally maps to the component structure of your UI. That's because UI and data models often have the same information architecture--that is, the same shape. Separate your UI into components, where each component matches one piece of your data model. + +There are five components on this screen: + +<FullWidth> + +<CodeDiagram flip> + +<img src="/images/docs/s_thinking-in-react_ui_outline.png" width="500" style={{margin: '0 auto'}} /> + +1. `FilterableProductTable` (grey) contains the entire app. +2. `SearchBar` (blue) receives the user input. +3. `ProductTable` (lavender) displays and filters the list according to the user input. +4. `ProductCategoryRow` (green) displays a heading for each category. +5. `ProductRow` (yellow) displays a row for each product. + +</CodeDiagram> + +</FullWidth> + +If you look at `ProductTable` (lavender), you'll see that the table header (containing the "Name" and "Price" labels) isn't its own component. This is a matter of preference, and you could go either way. For this example, it is a part of `ProductTable` because it appears inside the `ProductTable`'s list. However, if this header grows to be complex (e.g., if you add sorting), it would make sense to make this its own `ProductTableHeader` component. + +Now that you've identified the components in the mockup, arrange them into a hierarchy. Components that appear within another component in the mockup should appear as a child in the hierarchy: + +* `FilterableProductTable` + * `SearchBar` + * `ProductTable` + * `ProductCategoryRow` + * `ProductRow` + +## Step 2: Build a static version in React {/*step-2-build-a-static-version-in-react*/} + +Now that you have your component hierarchy, it's time to implement your app. The most straightforward approach is to build a version that renders the UI from your data model without adding any interactivity... yet! It's often easier to build the static version first and then add interactivity separately. Building a static version requires a lot of typing and no thinking, but adding interactivity requires a lot of thinking and not a lot of typing. + +To build a static version of your app that renders your data model, you'll want to build [components](/learn/your-first-component) that reuse other components and pass data using [props.](/learn/passing-props-to-a-component) Props are a way of passing data from parent to child. (If you're familiar with the concept of [state](/learn/state-a-components-memory), don't use state at all to build this static version. State is reserved only for interactivity, that is, data that changes over time. Since this is a static version of the app, you don't need it.) + +You can either build "top down" by starting with building the components higher up in the hierarchy (like `FilterableProductTable`) or "bottom up" by working from components lower down (like `ProductRow`). In simpler examples, it’s usually easier to go top-down, and on larger projects, it’s easier to go bottom-up. + +<Sandpack> + +```jsx App.js +function ProductCategoryRow({ category }) { + return ( + <tr> + <th colSpan="2"> + {category} + </th> + </tr> + ); +} + +function ProductRow({ product }) { + const name = product.stocked ? product.name : + <span style={{ color: 'red' }}> + {product.name} + </span>; + + return ( + <tr> + <td>{name}</td> + <td>{product.price}</td> + </tr> + ); +} + +function ProductTable({ products }) { + const rows = []; + let lastCategory = null; + + products.forEach((product) => { + if (product.category !== lastCategory) { + rows.push( + <ProductCategoryRow + category={product.category} + key={product.category} /> + ); + } + rows.push( + <ProductRow + product={product} + key={product.name} /> + ); + lastCategory = product.category; + }); + + return ( + <table> + <thead> + <tr> + <th>Name</th> + <th>Price</th> + </tr> + </thead> + <tbody>{rows}</tbody> + </table> + ); +} + +function SearchBar() { + return ( + <form> + <input type="text" placeholder="Search..." /> + <label> + <input type="checkbox" /> + {' '} + Only show products in stock + </label> + </form> + ); +} + +function FilterableProductTable({ products }) { + return ( + <div> + <SearchBar /> + <ProductTable products={products} /> + </div> + ); +} + +const PRODUCTS = [ + {category: "Fruits", price: "$1", stocked: true, name: "Apple"}, + {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"}, + {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"}, + {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"}, + {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"}, + {category: "Vegetables", price: "$1", stocked: true, name: "Peas"} +]; + +export default function App() { + return <FilterableProductTable products={PRODUCTS} />; +} +``` + +```css +body { + padding: 5px +} +label { + display: block; + margin-top: 5px; + margin-bottom: 5px; +} +th { + padding-top: 10px; +} +td { + padding: 2px; + padding-right: 40px; +} +``` + +</Sandpack> + +(If this code looks intimidating, go through the [Quick Start](/learn/) first!) + +After building your components, you'll have a library of reusable components that render your data model. Because this is a static app, the components will only return JSX. The component at the top of the hierarchy (`FilterableProductTable`) will take your data model as a prop. This is called _one-way data flow_ because the data flows down from the top-level component to the ones at the bottom of the tree. + +<Pitfall> + +At this point, you should not be using any state values. That’s for the next step! + +</Pitfall> + +## Step 3: Find the minimal but complete representation of UI state {/*step-3-find-the-minimal-but-complete-representation-of-ui-state*/} + +To make the UI interactive, you need to let users change your underlying data model. You will use *state* for this. + +Think of state as the minimal set of changing data that your app needs to remember. The most important principle for structuring state is to keep it [DRY (Don't Repeat Yourself).](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) Figure out the absolute minimal representation of the state your application needs and compute everything else on-demand. For example, if you're building a shopping list, you can store the items as an array in state. If you want to also display the number of items in the list, don't store the number of items as another state value--instead, read the length of your array. + +Now think of all of the pieces of data in this example application: + +1. The original list of products +2. The search text the user has entered +3. The value of the checkbox +4. The filtered list of products + +Which of these are state? Identify the ones that are not: + +* Does it **remain unchanged** over time? If so, it isn't state. +* Is it **passed in from a parent** via props? If so, it isn't state. +* **Can you compute it** based on existing state or props in your component? If so, it *definitely* isn't state! + +What's left is probably state. + +Let's go through them one by one again: + +1. The original list of products is **passed in as props, so it's not state.** +2. The search text seems to be state since it changes over time and can't be computed from anything. +3. The value of the checkbox seems to be state since it changes over time and can't be computed from anything. +4. The filtered list of products **isn't state because it can be computed** by taking the original list of products and filtering it according to the search text and value of the checkbox. + +This means only the search text and the value of the checkbox are state! Nicely done! + +<DeepDive> + +#### Props vs State {/*props-vs-state*/} + +There are two types of "model" data in React: props and state. The two are very different: + +* [**Props** are like arguments you pass](/learn/passing-props-to-a-component) to a function. They let a parent component pass data to a child component and customize its appearance. For example, a `Form` can pass a `color` prop to a `Button`. +* [**State** is like a component’s memory.](/learn/state-a-components-memory) It lets a component keep track of some information and change it in response to interactions. For example, a `Button` might keep track of `isHovered` state. + +Props and state are different, but they work together. A parent component will often keep some information in state (so that it can change it), and *pass it down* to child components as their props. It's okay if the difference still feels fuzzy on the first read. It takes a bit of practice for it to really stick! + +</DeepDive> + +## Step 4: Identify where your state should live {/*step-4-identify-where-your-state-should-live*/} + +After identifying your app’s minimal state data, you need to identify which component is responsible for changing this state, or *owns* the state. Remember: React uses one-way data flow, passing data down the component hierarchy from parent to child component. It may not be immediately clear which component should own what state. This can be challenging if you’re new to this concept, but you can figure it out by following these steps! + +For each piece of state in your application: + +1. Identify *every* component that renders something based on that state. +2. Find their closest common parent component--a component above them all in the hierarchy. +3. Decide where the state should live: + 1. Often, you can put the state directly into their common parent. + 2. You can also put the state into some component above their common parent. + 3. If you can't find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common parent component. + +In the previous step, you found two pieces of state in this application: the search input text, and the value of the checkbox. In this example, they always appear together, so it is easier to think of them as a single piece of state. + +Now let's run through our strategy for this state: + +1. **Identify components that use state:** + * `ProductTable` needs to filter the product list based on that state (search text and checkbox value). + * `SearchBar` needs to display that state (search text and checkbox value). +1. **Find their common parent:** The first parent component both components share is `FilterableProductTable`. +2. **Decide where the state lives**: We'll keep the filter text and checked state values in `FilterableProductTable`. + +So the state values will live in `FilterableProductTable`. + +Add state to the component with the [`useState()` Hook.](/reference/react/useState) Hooks let you "hook into" a component's [render cycle.](/learn/render-and-commit) Add two state variables at the top of `FilterableProductTable` and specify the initial state of your application: + +```js +function FilterableProductTable({ products }) { + const [filterText, setFilterText] = useState(''); + const [inStockOnly, setInStockOnly] = useState(false); +``` + +Then, pass `filterText` and `inStockOnly` to `ProductTable` and `SearchBar` as props: + +```js +<div> + <SearchBar + filterText={filterText} + inStockOnly={inStockOnly} /> + <ProductTable + products={products} + filterText={filterText} + inStockOnly={inStockOnly} /> +</div> +``` + +You can start seeing how your application will behave. Edit the `filterText` initial value from `useState('')` to `useState('fruit')` in the sandbox code below. You'll see both the search input text and the table update: + +<Sandpack> + +```jsx App.js +import { useState } from 'react'; + +function FilterableProductTable({ products }) { + const [filterText, setFilterText] = useState(''); + const [inStockOnly, setInStockOnly] = useState(false); + + return ( + <div> + <SearchBar + filterText={filterText} + inStockOnly={inStockOnly} /> + <ProductTable + products={products} + filterText={filterText} + inStockOnly={inStockOnly} /> + </div> + ); +} + +function ProductCategoryRow({ category }) { + return ( + <tr> + <th colSpan="2"> + {category} + </th> + </tr> + ); +} + +function ProductRow({ product }) { + const name = product.stocked ? product.name : + <span style={{ color: 'red' }}> + {product.name} + </span>; + + return ( + <tr> + <td>{name}</td> + <td>{product.price}</td> + </tr> + ); +} + +function ProductTable({ products, filterText, inStockOnly }) { + const rows = []; + let lastCategory = null; + + products.forEach((product) => { + if ( + product.name.toLowerCase().indexOf( + filterText.toLowerCase() + ) === -1 + ) { + return; + } + if (inStockOnly && !product.stocked) { + return; + } + if (product.category !== lastCategory) { + rows.push( + <ProductCategoryRow + category={product.category} + key={product.category} /> + ); + } + rows.push( + <ProductRow + product={product} + key={product.name} /> + ); + lastCategory = product.category; + }); + + return ( + <table> + <thead> + <tr> + <th>Name</th> + <th>Price</th> + </tr> + </thead> + <tbody>{rows}</tbody> + </table> + ); +} + +function SearchBar({ filterText, inStockOnly }) { + return ( + <form> + <input + type="text" + value={filterText} + placeholder="Search..."/> + <label> + <input + type="checkbox" + checked={inStockOnly} /> + {' '} + Only show products in stock + </label> + </form> + ); +} + +const PRODUCTS = [ + {category: "Fruits", price: "$1", stocked: true, name: "Apple"}, + {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"}, + {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"}, + {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"}, + {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"}, + {category: "Vegetables", price: "$1", stocked: true, name: "Peas"} +]; + +export default function App() { + return <FilterableProductTable products={PRODUCTS} />; +} +``` + +```css +body { + padding: 5px +} +label { + display: block; + margin-top: 5px; + margin-bottom: 5px; +} +th { + padding-top: 5px; +} +td { + padding: 2px; +} +``` + +</Sandpack> + +Notice that editing the form doesn't work yet. There is a console error in the sandbox above explaining why: + +<ConsoleBlock level="error"> + +You provided a \`value\` prop to a form field without an \`onChange\` handler. This will render a read-only field. + +</ConsoleBlock> + +In the sandbox above, `ProductTable` and `SearchBar` read the `filterText` and `inStockOnly` props to render the table, the input, and the checkbox. For example, here is how `SearchBar` populates the input value: + +```js {1,6} +function SearchBar({ filterText, inStockOnly }) { + return ( + <form> + <input + type="text" + value={filterText} + placeholder="Search..."/> +``` + +However, you haven't added any code to respond to the user actions like typing yet. This will be your final step. + + +## Step 5: Add inverse data flow {/*step-5-add-inverse-data-flow*/} + +Currently your app renders correctly with props and state flowing down the hierarchy. But to change the state according to user input, you will need to support data flowing the other way: the form components deep in the hierarchy need to update the state in `FilterableProductTable`. + +React makes this data flow explicit, but it requires a little more typing than two-way data binding. If you try to type or check the box in the example above, you'll see that React ignores your input. This is intentional. By writing `<input value={filterText} />`, you've set the `value` prop of the `input` to always be equal to the `filterText` state passed in from `FilterableProductTable`. Since `filterText` state is never set, the input never changes. + +You want to make it so whenever the user changes the form inputs, the state updates to reflect those changes. The state is owned by `FilterableProductTable`, so only it can call `setFilterText` and `setInStockOnly`. To let `SearchBar` update the `FilterableProductTable`'s state, you need to pass these functions down to `SearchBar`: + +```js {2,3,10,11} +function FilterableProductTable({ products }) { + const [filterText, setFilterText] = useState(''); + const [inStockOnly, setInStockOnly] = useState(false); + + return ( + <div> + <SearchBar + filterText={filterText} + inStockOnly={inStockOnly} + onFilterTextChange={setFilterText} + onInStockOnlyChange={setInStockOnly} /> +``` + +Inside the `SearchBar`, you will add the `onChange` event handlers and set the parent state from them: + +```js {5} +<input + type="text" + value={filterText} + placeholder="Search..." + onChange={(e) => onFilterTextChange(e.target.value)} /> +``` + +Now the application fully works! + +<Sandpack> + +```jsx App.js +import { useState } from 'react'; + +function FilterableProductTable({ products }) { + const [filterText, setFilterText] = useState(''); + const [inStockOnly, setInStockOnly] = useState(false); + + return ( + <div> + <SearchBar + filterText={filterText} + inStockOnly={inStockOnly} + onFilterTextChange={setFilterText} + onInStockOnlyChange={setInStockOnly} /> + <ProductTable + products={products} + filterText={filterText} + inStockOnly={inStockOnly} /> + </div> + ); +} + +function ProductCategoryRow({ category }) { + return ( + <tr> + <th colSpan="2"> + {category} + </th> + </tr> + ); +} + +function ProductRow({ product }) { + const name = product.stocked ? product.name : + <span style={{ color: 'red' }}> + {product.name} + </span>; + + return ( + <tr> + <td>{name}</td> + <td>{product.price}</td> + </tr> + ); +} + +function ProductTable({ products, filterText, inStockOnly }) { + const rows = []; + let lastCategory = null; + + products.forEach((product) => { + if ( + product.name.toLowerCase().indexOf( + filterText.toLowerCase() + ) === -1 + ) { + return; + } + if (inStockOnly && !product.stocked) { + return; + } + if (product.category !== lastCategory) { + rows.push( + <ProductCategoryRow + category={product.category} + key={product.category} /> + ); + } + rows.push( + <ProductRow + product={product} + key={product.name} /> + ); + lastCategory = product.category; + }); + + return ( + <table> + <thead> + <tr> + <th>Name</th> + <th>Price</th> + </tr> + </thead> + <tbody>{rows}</tbody> + </table> + ); +} + +function SearchBar({ + filterText, + inStockOnly, + onFilterTextChange, + onInStockOnlyChange +}) { + return ( + <form> + <input + type="text" + value={filterText} placeholder="Search..." + onChange={(e) => onFilterTextChange(e.target.value)} /> + <label> + <input + type="checkbox" + checked={inStockOnly} + onChange={(e) => onInStockOnlyChange(e.target.checked)} /> + {' '} + Only show products in stock + </label> + </form> + ); +} + +const PRODUCTS = [ + {category: "Fruits", price: "$1", stocked: true, name: "Apple"}, + {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"}, + {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"}, + {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"}, + {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"}, + {category: "Vegetables", price: "$1", stocked: true, name: "Peas"} +]; + +export default function App() { + return <FilterableProductTable products={PRODUCTS} />; +} +``` + +```css +body { + padding: 5px +} +label { + display: block; + margin-top: 5px; + margin-bottom: 5px; +} +th { + padding: 4px; +} +td { + padding: 2px; +} +``` + +</Sandpack> + +You can learn all about handling events and updating state in the [Adding Interactivity](/learn/adding-interactivity) section. + +## Where to go from here {/*where-to-go-from-here*/} + +This was a very brief introduction to how to think about building components and applications with React. You can [start a React project](/learn/installation) right now or [dive deeper on all the syntax](/learn/describing-the-ui) used in this tutorial. diff --git a/beta/src/content/learn/tutorial-tic-tac-toe.md b/beta/src/content/learn/tutorial-tic-tac-toe.md new file mode 100644 index 000000000..47a3b50b7 --- /dev/null +++ b/beta/src/content/learn/tutorial-tic-tac-toe.md @@ -0,0 +1,2918 @@ +--- +title: 'Tutorial: Tic-Tac-Toe' +--- + +<Intro> + +You will build a small tic-tac-toe game during this tutorial. This tutorial does not assume any existing React knowledge. The techniques you'll learn in the tutorial are fundamental to building any React app, and fully understanding it will give you a deep understanding of React. + +</Intro> + +<Note> + +This tutorial is designed for people who prefer to **learn by doing** and want to quickly try making something tangible. If you prefer learning each concept step by step, start with [Describing the UI.](/learn/describing-the-ui) + +</Note> + +The tutorial is divided into several sections: + +- [Setup for the tutorial](#setup-for-the-tutorial) will give you **a starting point** to follow the tutorial. +- [Overview](#overview) will teach you **the fundamentals** of React: components, props, and state. +- [Completing the game](#completing-the-game) will teach you **the most common techniques** in React development. +- [Adding time travel](#adding-time-travel) will give you **a deeper insight** into the unique strengths of React. + +### What are you building? {/*what-are-you-building*/} + +In this tutorial, you'll build an interactive tic-tac-toe game with React. + +You can see what it will look like when you're finished here: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({ value, onSquareClick }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + onPlay(nextSquares); + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( + <> + <div className="status">{status}</div> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} + +export default function Game() { + const [history, setHistory] = useState([Array(9).fill(null)]); + const [currentMove, setCurrentMove] = useState(0); + const xIsNext = currentMove % 2 === 0; + const currentSquares = history[currentMove]; + + function handlePlay(nextSquares) { + const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; + setHistory(nextHistory); + setCurrentMove(nextHistory.length - 1); + } + + function jumpTo(nextMove) { + setCurrentMove(nextMove); + } + + const moves = history.map((squares, move) => { + let description; + if (move > 0) { + description = 'Go to move #' + move; + } else { + description = 'Go to game start'; + } + return ( + <li key={move}> + <button onClick={() => jumpTo(move)}>{description}</button> + </li> + ); + }); + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + </div> + <div className="game-info"> + <ol>{moves}</ol> + </div> + </div> + ); +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +If the code doesn't make sense to you yet, or if you are unfamiliar with the code's syntax, don't worry! The goal of this tutorial is to help you understand React and its syntax. + +We recommend that you check out the tic-tac-toe game above before continuing with the tutorial. One of the features that you'll notice is that there is a numbered list to the right of the game's board. This list gives you a history of all of the moves that have occurred in the game, and it is updated as the game progresses. + +Once you've played around with the finished tic-tac-toe game, keep scrolling. You'll start with a simpler template in this tutorial. Our next step is to set you up so that you can start building the game. + +## Setup for the tutorial {/*setup-for-the-tutorial*/} + +In the live code editor below, click **Fork** in the top-right corner to open the editor in a new tab using the website CodeSandbox. CodeSandbox allows you to write code in your browser and immediately view how your users will see the app you've created. The new tab should display an empty square and the starter code for this tutorial. + +<Sandpack> + +```js App.js +export default function Square() { + return <button className="square">X</button>; +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +<Note> + +You can also follow this tutorial using your local development environment. To do this, you need to: + +1. Install [Node.js](https://nodejs.org/en/) +1. In the CodeSandbox tab you opened earlier, press the top-left corner button to open the menu, and then choose **File > Export to ZIP** in that menu to download an archive of the files locally +1. Unzip the archive, then open a terminal and `cd` to the directory you unzipped +1. Install the dependencies with `npm install` +1. Run `npm start` to start a local server and follow the prompts to view the code running in a browser + +If you get stuck, don't let this stop you! Follow along online instead and try a local setup again later. + +</Note> + +## Overview {/*overview*/} + +Now that you're set up, let's get an overview of React! + +### Inspecting the starter code {/*inspecting-the-starter-code*/} + +In CodeSandbox you'll see three main sections: + +![CodeSandbox with starter code](../images/tutorial/react-starter-code-codesandbox.png) + +1. The _Files_ section with a list of files like `App.js`, `index.js`, `styles.css` and a folder called `public` +1. The _code editor_ where you'll see the source code of your selected file +1. The _browser_ section where you'll see how the code you've written will be displayed + +The `App.js` file should be selected in the _Files_ section. The contents of that file in the _code editor_ should be: + +```jsx +export default function Square() { + return <button className="square">X</button>; +} +``` + +The _browser_ section should be displaying a square with a X in it like this: + +![x-filled square](../images/tutorial/x-filled-square.png) + +Now let's have a look at the files in the starter code. + +#### `App.js` {/*appjs*/} + +The code in `App.js` creates a _component_. In React, a component is a piece of reusable code that represents a part of a user interface. Components are used to render, manage, and update the UI elements in your application. Let's look at the component line by line to see what's going on: + +```js {1} +export default function Square() { + return <button className="square">X</button>; +} +``` + +The first line defines a function called `Square`. The `export` JavaScript keyword makes this function accessible outside of this file. The `default` keyword tells other files using your code that it's the main function in your file. + +```js {2} +export default function Square() { + return <button className="square">X</button>; +} +``` + +The second line returns a button. The `return` JavaScript keyword means whatever comes after is returned as a value to the caller of the function. `<button>` is a *JSX element*. A JSX element is a combination of JavaScript code and HTML tags that describes what you'd like to display. `className="square"` is a button property or *prop* that tells CSS how to style the button. `X` is the text displayed inside of the button and `</button>` closes the JSX element to indicate that any following content shouldn't be placed inside the button. + +#### `styles.css` {/*stylescss*/} + +Click on the file labeled `styles.css` in the _Files_ section of CodeSandbox. This file defines the styles for your React app. The first two _CSS selectors_ (`*` and `body`) define the style of large parts of your app while the `.square` selector defines the style of any component where the `className` property is set to `square`. In your code, that would match the button from your Square component in the `App.js` file. + +#### `index.js` {/*indexjs*/} + +Click on the file labeled `index.js` in the _Files_ section of CodeSandbox. You won't be editing this file during the tutorial but it is the bridge between the component you created in the `App.js` file and the web browser. + +```jsx +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; +import './styles.css'; + +import App from './App'; +``` + +Lines 1-5 brings all the necessary pieces together: + +* React +* React's library to talk to web browsers (React DOM) +* the styles for your components +* the component you created in `App.js`. + +The remainder of the file brings all the pieces together and injects the final product into `index.html` in the `public` folder. + +### Building the board {/*building-the-board*/} + +Let's get back to `App.js`. This is where you'll spend the rest of the tutorial. + +Currently the board is only a single square, but you need nine! If you just try and copy paste your square to make two squares like this: + +```js {2} +export default function Square() { + return <button className="square">X</button><button className="square">X</button>; +} +``` + +You'll get this error: + +<ConsoleBlock level="error"> + +/src/App.js: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment `<>...</>`? + +</ConsoleBlock> + +React components need to return a single JSX element and not multiple adjacent JSX elements like two buttons. To fix this you can use *fragments* (`<>` and `</>`) to wrap multiple adjacent JSX elements like this: + +```js {3-6} +export default function Square() { + return ( + <> + <button className="square">X</button> + <button className="square">X</button> + </> + ); +} +``` + +Now you should see: + +![two x-filled squares](../images/tutorial/two-x-filled-squares.png) + +Great! Now you just need to copy-paste a few times to add nine squares and... + +![nine x-filled squares in a line](../images/tutorial/nine-x-filled-squares.png) + +Oh no! The squares are all in a single line, not in a grid like you need for our board. To fix this you'll need to group your squares into rows with `div`s and add some CSS classes. While you're at it, you'll give each square a number to make sure you know where each square is displayed. + +In the `App.js` file, update the `Square` component to look like this: + +```js {3-19} +export default function Square() { + return ( + <> + <div className="board-row"> + <button className="square">1</button> + <button className="square">2</button> + <button className="square">3</button> + </div> + <div className="board-row"> + <button className="square">4</button> + <button className="square">5</button> + <button className="square">6</button> + </div> + <div className="board-row"> + <button className="square">7</button> + <button className="square">8</button> + <button className="square">9</button> + </div> + </> + ); +} +``` + +The CSS defined in `styles.css` styles the divs with the `className` of `board-row`. Now that you've grouped your components into rows with the styled `div`s you have your tic-tac-toe board: + +![tic-tac-toe board filled with numbers 1 through 9](../images/tutorial/number-filled-board.png) + +But you now have a problem. Your component named `Square`, really isn't a square anymore. Let's fix that by changing the name to `Board`: + +```js {1} +export default function Board() { + //... +} +``` + +At this point your code should look something like this: + +<Sandpack> + +```js +export default function Board() { + return ( + <> + <div className="board-row"> + <button className="square">1</button> + <button className="square">2</button> + <button className="square">3</button> + </div> + <div className="board-row"> + <button className="square">4</button> + <button className="square">5</button> + <button className="square">6</button> + </div> + <div className="board-row"> + <button className="square">7</button> + <button className="square">8</button> + <button className="square">9</button> + </div> + </> + ); +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +<Note> + +Psssst... That's a lot to type! It's okay to copy and paste code from this page. However, if you're up for a little challenge, we recommend to only copy the code that you've manually typed at least once yourself. + +</Note> + +### Passing data through props {/*passing-data-through-props*/} + +Next, you'll want to change the value of a square from empty to "X" when the user clicks on the square. With how you've built the board so far you would need to copy-paste the code that updates the square nine times (once for each square you have)! Instead of copy-pasting, React's component architecture allows you to create a reusable component to avoid messy, duplicated code. + +First, you are going to copy the line defining your first square (`<button className="square">1</button>`) from your `Board` component into a new `Square` component: + +```js {1-3} +function Square() { + return <button className="square">1</button>; +} + +export default function Board() { + // ... +} +``` + +Then you'll update the Board component to render that `Square` component using JSX syntax: + +```js {5-19} +// ... +export default function Board() { + return ( + <> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + </> + ); +} +``` + +Notice how unlike the browser `div`s, your own components `Board` and `Square` must start with a capital letter. + +Let's take a look: + +![one-filled board](../images/tutorial/board-filled-with-ones.png) + +Oh no! You lost the numbered squares you had before. Now each square says "1". To fix this, you will use *props* to pass the value each square should have from the parent component (`Board`) to the child component (`Square`). + +Update the `Square` component to read the `value` prop that you'll pass from the `Board`: + +```js {1} +function Square({ value }) { + return <button className="square">1</button>; +} +``` + +`function Square({ value })` indicates the Square component can be passed a prop called `value`. + +Now you want to display that `value` instead of `1` inside every square. Try doing it like this: + +```js {2} +function Square({ value }) { + return <button className="square">value</button>; +} +``` + +Oops, this is not what you wanted: + +![value-filled board](../images/tutorial/board-filled-with-value.png) + +You wanted to render the JavaScript variable called `value` from your component, not the word "value". To "escape into JavaScript" from JSX, you need curly braces. Add curly braces around `value` in JSX like so: + +```js {2} +function Square({ value }) { + return <button className="square">{value}</button>; +} +``` + +For now, you should see an empty board: + +![empty board](../images/tutorial/empty-board.png) + +This is because the `Board` component hasn't passed the `value` prop to each `Square` component it renders yet. To fix it you'll add the `value` prop to each `Square` component rendered by the `Board` component: + +```js {5-7,10-12,15-17} +export default function Board() { + return ( + <> + <div className="board-row"> + <Square value="1" /> + <Square value="2" /> + <Square value="3" /> + </div> + <div className="board-row"> + <Square value="4" /> + <Square value="5" /> + <Square value="6" /> + </div> + <div className="board-row"> + <Square value="7" /> + <Square value="8" /> + <Square value="9" /> + </div> + </> + ); +} +``` + +Now you should see a grid of numbers again: + +![tic-tac-toe board filled with numbers 1 through 9](../images/tutorial/number-filled-board.png) + +Your updated code should look like this: + +<Sandpack> + +```js App.js +function Square({ value }) { + return <button className="square">{value}</button>; +} + +export default function Board() { + return ( + <> + <div className="board-row"> + <Square value="1" /> + <Square value="2" /> + <Square value="3" /> + </div> + <div className="board-row"> + <Square value="4" /> + <Square value="5" /> + <Square value="6" /> + </div> + <div className="board-row"> + <Square value="7" /> + <Square value="8" /> + <Square value="9" /> + </div> + </> + ); +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +### Making an interactive component {/*making-an-interactive-component*/} + +Let's fill the `Square` component with an `X` when you click it. Declare a function called `handleClick` inside of the `Square`. Then, add `onClick` to the props of the button JSX element returned from the `Square` component: + +```js {2-4,9} +function Square({ value }) { + function handleClick() { + console.log('clicked!'); + } + + return ( + <button + className="square" + onClick={handleClick} + > + {value} + </button> + ); +} +``` + +If you click on a square now, you should see a log saying `"clicked!"` in the _Console_ tab at the bottom of the _Browser_ section in CodeSandbox. Clicking the square more than once will log `"clicked!"` again. Repeated console logs with the same message will not create more lines in the console. Instead, you will see an incrementing counter next to your first `"clicked!"` log. + +<Note> + +If you are following this tutorial using your local development environment, you need to open your browser's Console. For example, if you use the Chrome browser, you can view the Console with the keyboard shortcut **Shift + Ctrl + J** (on Windows/Linux) or **Option + ⌘ + J** (on macOS). + +</Note> + +As a next step, you want the Square component to "remember" that it got clicked, and fill it with an "X" mark. To "remember" things, components use *state*. + +React provides a special function called `useState` that you can call from your component to let it "remember" things. Let's store the current value of the `Square` in state, and change it when the `Square` is clicked. + +Import `useState` at the top of the file. Remove the `value` prop from the Square component. Instead, add a new line at the start of the `Square` component that calls `useState`. Have it return a state variable called `value`: + +```js {1,3,4} +import { useState } from 'react'; + +function Square() { + const [value, setValue] = useState(null); + + function handleClick() { + //... +``` + +`value` stores the value and `setValue` is a function that can be used to change the value. The `null` passed to `useState` is used as the initial value for this state variable, so `value` here starts off equal to `null`. + +Since the `Square` component no longer accepts props anymore, you'll remove the `value` prop from all nine of the Square components created by the Board component: + +```js {6-8,11-13,16-18} +// ... +export default function Board() { + return ( + <> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + </> + ); +} +``` + +Now you'll change `Square` to display an "X" when clicked. Replace the `console.log("clicked!");` event handler with `setValue('X');`. Now your `Square` component looks like this: + +```js {5} +function Square() { + const [value, setValue] = useState(null); + + function handleClick() { + setValue('X'); + } + + return ( + <button + className="square" + onClick={handleClick} + > + {value} + </button> + ); +} +``` + +By calling this `set` function from an `onClick` handler, you're telling React to re-render that `Square` whenever its `<button>` is clicked. After the update, the `Square`'s `value` will be `'X'`, so you'll see the "X" on the game board. + +If you click on any Square, an "X" should show up: + +![adding xes to board](../images/tutorial/tictac-adding-x-s.gif) + +Note that each Square has its own state: the `value` stored in each Square is completely independent of the others. When you call a `set` function in a component, React automatically updates the child components inside of it too. + +After you've made the above changes, your code will look like this: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square() { + const [value, setValue] = useState(null); + + function handleClick() { + setValue('X'); + } + + return ( + <button + className="square" + onClick={handleClick} + > + {value} + </button> + ); +} + +export default function Board() { + return ( + <> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + <div className="board-row"> + <Square /> + <Square /> + <Square /> + </div> + </> + ); +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +### React Developer Tools {/*react-developer-tools*/} + +React DevTools let you check the props and the state of your React components. You can find the React DevTools tab at the bottom of the _browser_ section in CodeSandbox: + +![React DevTools in CodeSandbox](../images/tutorial/codesandbox-devtools.png) + +To inspect a particular component on the screen, use the button in the top left corner of React DevTools: + +![Selecting components on the page with React DevTools](../images/tutorial/devtools-select.gif) + +<Note> + +For local development, React DevTools is available as a [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en), [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/), and [Edge](https://microsoftedge.microsoft.com/addons/detail/react-developer-tools/gpphkfbcpidddadnkolkpfckpihlkkil) browser extension. After installing it, the *Components* tab will appear in your browser Developer Tools for sites using React. + +</Note> + +## Completing the game {/*completing-the-game*/} + +By this point, you have all the basic building blocks for your tic-tac-toe game. To have a complete game, you now need to alternate placing "X"s and "O"s on the board, and you need a way to determine a winner. + +### Lifting state up {/*lifting-state-up*/} + +Currently, each `Square` component maintains a part of the game's state. To check for a winner in a tic-tac-toe game, the `Board` would need to somehow know the state of each of the 9 `Square` components. + +How would you approach that? At first, you might guess that the `Board` needs to "ask" each `Square` for that `Square`'s state. Although this approach is technically possible in React, we discourage it because the code becomes difficult to understand, susceptible to bugs, and hard to refactor. Instead, the best approach is to store the game's state in the parent `Board` component instead of in each `Square`. The `Board` component can tell each `Square` what to display by passing a prop, like you did when you passed a number to each Square. + +**To collect data from multiple children, or to have two child components communicate with each other, declare the shared state in their parent component instead. The parent component can pass that state back down to the children via props. This keeps the child components in sync with each other and with the parent component.** + +Lifting state into a parent component is common when React components are refactored. + +Let's take this opportunity to try it out. Edit the `Board` component so that it declares a state variable named `squares` that defaults to an array of 9 nulls corresponding to the 9 squares: + +```js {3} +// ... +export default function Board() { + const [squares, setSquares] = useState(Array(9).fill(null)); + return ( + // ... + ); +} +``` + +`Array(9).fill(null)` creates an array with nine elements and sets each of them to `null`. The `useState()` call around it declares a `squares` state variable that's initially set to that array. Each entry in the array corresponds to the value of a square. When you fill the board in later, the `squares` array will look something like this: + +```jsx +['O', null, 'X', 'X', 'X', 'O', 'O', null, null] +``` + +Now your `Board` component needs to pass the `value` prop down to each of the `Square` components it renders: + +```js {6-8,11-13,16-18} +export default function Board() { + const [squares, setSquares] = useState(Array(9).fill(null)); + return ( + <> + <div className="board-row"> + <Square value={squares[0]} /> + <Square value={squares[1]} /> + <Square value={squares[2]} /> + </div> + <div className="board-row"> + <Square value={squares[3]} /> + <Square value={squares[4]} /> + <Square value={squares[5]} /> + </div> + <div className="board-row"> + <Square value={squares[6]} /> + <Square value={squares[7]} /> + <Square value={squares[8]} /> + </div> + </> + ); +} +``` + +Next, you'll edit the `Square` component to receive the `value` prop from the Board component. This will require removing the Square component's own stateful tracking of `value` and the button's `onClick` prop: + +```js {1,2} +function Square({value}) { + return <button className="square">{value}</button>; +} +``` + +At this point you should see an empty tic-tac-toe board: + +![empty board](../images/tutorial/empty-board.png) + +And your code should look like this: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({ value }) { + return <button className="square">{value}</button>; +} + +export default function Board() { + const [squares, setSquares] = useState(Array(9).fill(null)); + return ( + <> + <div className="board-row"> + <Square value={squares[0]} /> + <Square value={squares[1]} /> + <Square value={squares[2]} /> + </div> + <div className="board-row"> + <Square value={squares[3]} /> + <Square value={squares[4]} /> + <Square value={squares[5]} /> + </div> + <div className="board-row"> + <Square value={squares[6]} /> + <Square value={squares[7]} /> + <Square value={squares[8]} /> + </div> + </> + ); +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +Each Square will now receive a `value` prop that will either be `'X'`, `'O'`, or `null` for empty squares. + +Next, you need to change what happens when a `Square` is clicked. The `Board` component now maintains which squares are filled. You'll need to create a way for the `Square` to update the `Board`'s state. Since state is private to a component that defines it, you cannot update the `Board`'s state directly from `Square`. + +Instead, you'll pass down a function from the `Board` component to the `Square` component, and you'll have `Square` call that function when a square is clicked. You'll start with the function that the `Square` component will call when it is clicked. You'll call that function `onSquareClick`: + +```js {3} +function Square({ value }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} +``` + +Next, you'll add the `onSquareClick` function to the `Square` component's props: + +```js {1} +function Square({ value, onSquareClick }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} +``` + +Now you'll connect the `onSquareClick` prop to a function in the `Board` component that you'll name `handleClick`. To connect `onSquareClick` to `handleClick` you'll pass a function to the `onSquareClick` prop of the first `Square` component: + +```js {7} +export default function Board() { + const [squares, setSquares] = useState(Array(9).fill(null)); + + return ( + <> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={handleClick} /> + //... + ); +} +``` + +Lastly, you will define the `handleClick` function inside the Board component to update the `squares` array holding your board's state: + +```js {4-8} +export default function Board() { + const [squares, setSquares] = useState(Array(9).fill(null)); + + function handleClick() { + const nextSquares = squares.slice(); + nextSquares[0] = "X"; + setSquares(nextSquares); + } + + return ( + // ... + ) +} +``` + +The `handleClick` function creates a copy of the `squares` array (`nextSquares`) with the JavaScript `slice()` Array method. Then, `handleClick` updates the `nextSquares` array to add `X` to the first (`[0]` index) square. + +Calling the `setSquares` function lets React know the state of the component has changed. This will trigger a re-render of the components that use the `squares` state (`Board`) as well as its child components (the `Square` components that make up the board). + +<Note> + +JavaScript supports [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) which means an inner function (e.g. `handleClick`) has access to variables and functions defined in a outer function (e.g. `Board`). The `handleClick` function can read the `squares` state and call the `setSquares` method because they are both defined inside of the `Board` function. + +</Note> + +Now you can add X's to the board... but only to the upper left square. Your `handleClick` function is hardcoded to update the index for the upper left square (`0`). Let's update `handleClick` to be able to update any square. Add a argument `i` to the `handleClick` function that takes the index of the square that should be updated: + +```js {4,6} +export default function Board() { + const [squares, setSquares] = useState(Array(9).fill(null)); + + function handleClick(i) { + const nextSquares = squares.slice(); + nextSquares[i] = "X"; + setSquares(nextSquares); + } + + return ( + // ... + ) +} +``` + +Now there is a new problem! + +Try setting the `onSquareClick` prop of square to be `handleClick(0)` directly in the JSX like this: + +```jsx +<Square value={squares[0]} onSquareClick={handleClick(0)} /> +``` + +The `handleClick(0)` call will be a part of rendering the board component. Because `handleClick(0)` alters the state of the board component by calling `setSquares`, your entire board component will be re-rendered again. But `handleClick(0)` is now a part of rendering of the board component, and so you've created an infinite loop: + +<ConsoleBlock level="error"> + +Too many re-renders. React limits the number of renders to prevent an infinite loop. + +</ConsoleBlock> + +Why didn't this problem happen earlier? + +When you were passing `onSquareClick={handleClick}`, you were passing the `handleClick` function down as a prop. You were not calling it! But now you are *calling* that function right away--notice the parentheses in `handleClick(0)`--and that's why it runs too early. You don't *want* to call `handleClick` until the user clicks! + +To fix this, you could create a function like `handleFirstSquareClick` that calls `handleClick(0)`, a function like `handleSecondSquareClick` that calls `handleClick(1)`, and so on. Instead of calling them, you would pass these functions down as props like `onSquareClick={handleFirstSquareClick}`. This would solve the infinite loop. + +However, defining nine different functions and giving each of them a name is too verbose. Instead, let's do this: + +```js {6} +export default function Board() { + // ... + return ( + <> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + // ... + ); +} +``` + +Notice the new `() =>` syntax. Here, `() => handleClick(0)` is an *arrow function,* which is a shorter way to define functions. When the square is clicked, the code after the `=>` "arrow" will run, calling `handleClick(0)`. + +Now you need to update the other eight squares to call `handleClick` from the arrow functions you pass. Make sure that the argument for each call of the `handleClick` corresponds to the index of the correct square: + +```js {6-8,11-13,16-18} +export default function Board() { + // ... + return ( + <> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +}; +``` + +Now you can again add X's to any square on the board by clicking on them: + +![filling the board with X](../images/tutorial/tictac-adding-x-s.gif) + +But this time all the state management is handled by the `Board` component! + +This is what your code should look like: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({ value, onSquareClick }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +export default function Board() { + const [squares, setSquares] = useState(Array(9).fill(null)); + + function handleClick(i) { + const nextSquares = squares.slice(); + nextSquares[i] = 'X'; + setSquares(nextSquares); + } + + return ( + <> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +Now that your state handling is in the `Board` component, the parent `Board` component passes props to the child `Square` components so that they can be displayed correctly. When clicking on a `Square`, the child `Square` component now asks the parent `Board` component to update the state of the board. When the `Board`'s state changes, both the `Board` component and every child `Square` component re-renders automatically. Keeping the state of all squares in the `Board` component will allow it to determine the winner in the future. + +Let's recap what happens when a user clicks the top left square on your board to add an `X` to it: + +1. Clicking on the upper left square runs the function that the `button` received as its `onClick` prop from the `Square`. The `Square` component received that function as its `onSquareClick` prop from the `Board`. The `Board` component defined that function directly in the JSX. It calls `handleClick` with an argument of `0`. +1. `handleClick` uses the argument (`0`) to update the first element of the `squares` array from `null` to `X`. +1. The `squares` state of the `Board` component was updated, so the `Board` and all of its children re-render. This causes the `value` prop of the `Square` component with index `0` to change from `null` to `X`. + +In the end the user sees that the upper left square has changed from empty to having a `X` after clicking it. + +<Note> + +The DOM `<button>` element's `onClick` attribute has a special meaning to React because it is a built-in component. For custom components like Square, the naming is up to you. You could give any name to the `Square`'s `onSquareClick` prop or `Board`'s `handleClick` function, and the code would work the same. In React, it's conventional to use `on[Event]` names for props which represent events and `handle[Event]` for the function definitions which handle the events. + +</Note> + +### Why immutability is important {/*why-immutability-is-important*/} + +Note how in `handleClick`, you call `.slice()` to create a copy of the `squares` array instead of modifying the existing array. To explain why, we need to discuss immutability and why immutability is important to learn. + +There are generally two approaches to changing data. The first approach is to _mutate_ the data by directly changing the data's values. The second approach is to replace the data with a new copy which has the desired changes. Here is what it would look like if you mutated the `squares` array: + +```jsx +const squares = [null, null, null, null, null, null, null, null, null]; +squares[0] = 'X'; +// Now `squares` is ["X", null, null, null, null, null, null, null, null]; +``` + +And here is what it would look like if you changed data without mutating the `squares` array: + +```jsx +const squares = [null, null, null, null, null, null, null, null, null]; +const nextSquares = ['X', null, null, null, null, null, null, null, null]; +// Now `squares` is unchanged, but `nextSquares` first element is 'X' rather than `null` +``` + +The end result is the same but by not mutating (changing the underlying data) directly, you gain several benefits. + +Immutability makes complex features much easier to implement. Later in this tutorial, you will implement a "time travel" feature that lets you review the game's history and "jump back" to past moves. This functionality isn't specific to games--an ability to undo and redo certain actions is a common requirement for apps. Avoiding direct data mutation lets you keep previous versions of the data intact, and reuse them (or reset to them) later. + +There is also another benefit of immutability. By default, all child components re-render automatically when the state of a parent component changes. This includes even the child components that weren't affected by the change. Although re-rendering is not by itself noticeable to the user (you shouldn't actively try to avoid it!), you might want to skip re-rendering a part of the tree that clearly wasn't affected by it for performance reasons. Immutability makes it very cheap for components to compare whether their data has changed or not. You can learn more about how React chooses when to re-render a component in [the `memo` API reference](/reference/react/memo) documentation. + +### Taking turns {/*taking-turns*/} + +It's now time to fix a major defect in this tic-tac-toe game: the "O"s cannot be marked on the board. + +You'll set the first move to be "X" by default. Let's keep track of this by adding another piece of state to the Board component: + +```js {2} +function Board() { + const [xIsNext, setXIsNext] = useState(true); + const [squares, setSquares] = useState(Array(9).fill(null)); + + // ... +} +``` + +Each time a player moves, `xIsNext` (a boolean) will be flipped to determine which player goes next and the game's state will be saved. You'll update the `Board`'s `handleClick` function to flip the value of `xIsNext`: + +```js {7,8,9,10,11,13} +export default function Board() { + const [xIsNext, setXIsNext] = useState(true); + const [squares, setSquares] = useState(Array(9).fill(null)); + + function handleClick(i) { + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = "X"; + } else { + nextSquares[i] = "O"; + } + setSquares(nextSquares); + setXIsNext(!xIsNext); + } + + return ( + //... + ); +} +``` + +Now, as you click on different squares, they will alternate between `X` and `O`, as they should! + +But wait, there's a problem. Try clicking on the same square multiple times: + +![O overwriting an X](../images/tutorial/o-replaces-x.gif) + +The `X` is overwritten by an `O`! While this would add a very interesting twist to the game, we're going to stick to the original rules for now. + +When you mark a square with a `X` or a `O` you aren't first checking to see if the square already has a `X` or `O` value. You can fix this by *returning early*. You'll check to see if the square already has a `X` or and `O`. If the square is already filled, you will `return` in the `handleClick` function early--before it tries to update the board state. + +```js {2,3,4} +function handleClick(i) { + if (squares[i]) { + return; + } + const nextSquares = squares.slice(); + //... +} +``` + +Now you can only add `X`'s or `O`'s to empty squares! Here is what your code should look like at this point: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({value, onSquareClick}) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +export default function Board() { + const [xIsNext, setXIsNext] = useState(true); + const [squares, setSquares] = useState(Array(9).fill(null)); + + function handleClick(i) { + if (squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + setSquares(nextSquares); + setXIsNext(!xIsNext); + } + + return ( + <> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +### Declaring a winner {/*declaring-a-winner*/} + +Now that you show which player's turn is next, you should also show when the game is won and there are no more turns to make. To do this you'll add a helper function called `calculateWinner` that takes an array of 9 squares, checks for a winner and returns `'X'`, `'O'`, or `null` as appropriate. Don't worry too much about the `calculateWinner` function; it's not specific to React: + +```js App.js +export default function Board() { + //... +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6] + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} +``` + +<Note> + +It does not matter whether you define `calculateWinner` before or after the `Board`. Let's put it at the end so that you don't have to scroll past it every time you edit your components. + +</Note> + +You will call `calculateWinner(squares)` in the `Board` component's `handleClick` function to check if a player has won. You can perform this check at the same time you check if a user has clicked a square that already has a `X` or and `O`. We'd like to return early in both cases: + +```js {2} +function handleClick(i) { + if (squares[i] || calculateWinner(squares)) { + return; + } + const nextSquares = squares.slice(); + //... +} +``` + +To let the players know when the game is over, you can display text such as "Winner: X" or "Winner: O". To do that you'll add a `status` section to the `Board` component. The status will display the winner if the game is over and if the game is ongoing you'll display which player's turn is next: + +```js {3-9,13} +export default function Board() { + // ... + const winner = calculateWinner(squares); + let status; + if (winner) { + status = "Winner: " + winner; + } else { + status = "Next player: " + (xIsNext ? "X" : "O"); + } + + return ( + <div> + <div className="status">{status}</div> + <div className="board-row"> + // ... + ) +} +``` + +Congratulations! You now have a working tic-tac-toe game. And you've just learned the basics of React too. So _you_ are the real winner here. Here is what the code should look like: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({value, onSquareClick}) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +export default function Board() { + const [xIsNext, setXIsNext] = useState(true); + const [squares, setSquares] = useState(Array(9).fill(null)); + + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + setSquares(nextSquares); + setXIsNext(!xIsNext); + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( + <> + <div className="status">{status}</div> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +## Adding time travel {/*adding-time-travel*/} + +As a final exercise, let's make it possible to "go back in time" to the previous moves in the game. + +### Storing a history of moves {/*storing-a-history-of-moves*/} + +If you mutated the `squares` array, implementing time travel would be very difficult. + +However, you used `slice()` to create a new copy of the `squares` array after every move, and treated it as immutable. This will allow you to store every past version of the `squares` array, and navigate between the turns that have already happened. + +You'll store the past `squares` arrays in another array called `history`, which you'll store as a new state variable. The `history` array represents all board states, from the first to the last move, and has a shape like this: + +```jsx +[ + // Before first move + [null, null, null, null, null, null, null, null, null], + // After first move + [null, null, null, null, 'X', null, null, null, null], + // After second move + [null, null, null, null, 'X', null, null, null, 'O'], + // ... +] +``` + +### Lifting state up, again {/*lifting-state-up-again*/} + +You will now write a new top-level component called `Game` to display a list of past moves. That's where you will place the `history` state that contains the entire game history. + +Placing the `history` state into the `Game` component will let you remove the `squares` state from its child `Board` component. Just like you "lifted state up" from the `Square` component into the `Board` component, you will now lift it up from the `Board` into the top-level `Game` component. This gives the `Game` component full control over the `Board`'s data and lets it instruct the `Board` to render previous turns from the `history`. + +First, add a `Game` component with `export default`. Have it render the `Board` component inside some markup: + +```js {1,5-16} +function Board() { + // ... +} + +export default function Game() { + return ( + <div className="game"> + <div className="game-board"> + <Board /> + </div> + <div className="game-info"> + <ol>{/*TODO*/}</ol> + </div> + </div> + ); +} +``` + +Note that you are removing the `export default` keywords before the `function Board() {` declaration and adding them before the `function Game() {` declaration. This tells your `index.js` file to use the `Game` component as the top-level component instead of your `Board` component. The additional `div`s returned by the `Game` component are making room for the game information you'll add to the board later. + +Add some state to the `Game` component to track which player is next and the history of moves: + +```js {2-3} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + // ... +``` + +Notice how `[Array(9).fill(null)]` is an array with a single item, which itself is an array of 9 `null`s. + +To render the squares for the current move, you'll want to read the last squares array from the `history`. You don't need `useState` for this--you already have enough information to calculate it during rendering: + +```js {4} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const currentSquares = history[history.length - 1]; + // ... +``` + +Next, create a `handlePlay` function inside the `Game` component that will be called by the `Board` component to update the game. Pass `xIsNext`, `currentSquares` and `handlePlay` as props to the `Board` component: + +```js {6-8,13} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const currentSquares = history[history.length - 1]; + + function handlePlay(nextSquares) { + // TODO + } + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + //... + ) +} +``` + +Let's make the `Board` component fully controlled by the props it receives. Change the `Board` component to take three props: `xIsNext`, `squares`, and a new `onPlay` function that `Board` can call with the updated squares array whenever a player makes a move. Next, remove the first two lines of the `Board` function that call `useState`: + +```js {1} +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + //... + } + // ... +} +``` + +Now you'll replace the `setSquares` and `setXIsNext` calls in `handleClick` in the `Board` component with a single call to your new `onPlay` function so the `Game` component can update the `Board` when the user clicks a square: + +```js {12} +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = "X"; + } else { + nextSquares[i] = "O"; + } + onPlay(nextSquares); + } + //... +} +``` + +The `Board` component is fully controlled by the props passed to it by the `Game` component. You need to implement the `handlePlay` function in the `Game` component to get the game working again. + +What should `handlePlay` do when called? Remember that Board used to call `setSquares` with an updated array; now it passes the updated `squares` array to `onPlay`. + +The `handlePlay` function needs to update `Game`'s state to trigger a re-render, but you don't have a `setSquares` function that you can call any more--you're now using the `history` state variable to store this information. You'll want to update `history` by appending the updated `squares` array as a new history entry. You also want to toggle `xIsNext`, just as Board used to do: + +```js {4-5} +export default function Game() { + //... + function handlePlay(nextSquares) { + setHistory([...history, nextSquares]); + setXIsNext(!xIsNext); + } + //... +} +``` + +Here, `[...history, nextSquares]` creates a new array that contains all the items in `history`, followed by `nextSquares`. (You can read the `...history` [*spread syntax*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) as "enumerate all the items in `history`".) + +For example, if `history` is `[[null,null,null], ["X",null,null]]` and `nextSquares` is `["X",null,"O"]`, then the new `[...history, nextSquares]` array will be `[[null,null,null], ["X",null,null], ["X",null,"O"]]`. + +At this point, you've moved the state to live in the `Game` component, and the UI should be fully working, just as it was before the refactor. Here is what the code should look like at this point: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({ value, onSquareClick }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + onPlay(nextSquares); + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( + <> + <div className="status">{status}</div> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} + +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const currentSquares = history[history.length - 1]; + + function handlePlay(nextSquares) { + setHistory([...history, nextSquares]); + setXIsNext(!xIsNext); + } + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + </div> + <div className="game-info"> + <ol>{/*TODO*/}</ol> + </div> + </div> + ); +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +### Showing the past moves {/*showing-the-past-moves*/} + +Since you are recording the tic-tac-toe game's history, you can now display it to the player as a list of past moves. + +React elements like `<button>` are regular JavaScript objects; you can pass them around in your application. To render multiple items in React, you can use an array of React elements. + +You already have an array of `history` moves in state, so now you need to transform it to an array of React elements. In JavaScript, to transform one array into another, you can use the [array `map` method:](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) + +```jsx +[1, 2, 3].map((x) => x * 2) // [2, 4, 6] +``` + +You'll use `map` to transform your `history` of moves into React elements representing buttons on the screen, and you'll display a list of buttons to "jump" to past moves. Let's `map` over the `history` in the Game component: + +```js {11-13,15-27,35} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const currentSquares = history[history.length - 1]; + + function handlePlay(nextSquares) { + setHistory([...history, nextSquares]); + setXIsNext(!xIsNext); + } + + function jumpTo(nextMove) { + // TODO + } + + const moves = history.map((squares, move) => { + let description; + if (move > 0) { + description = 'Go to move #' + move; + } else { + description = 'Go to game start'; + } + return ( + <li> + <button onClick={() => jumpTo(move)}>{description}</button> + </li> + ); + }); + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + </div> + <div className="game-info"> + <ol>{moves}</ol> + </div> + </div> + ); +} +``` + +You can see what your code should look like below. Note that you should see an error in the developer tools console that says: ``Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `Game`.`` You'll fix this error in the next section. + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({ value, onSquareClick }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + onPlay(nextSquares); + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( + <> + <div className="status">{status}</div> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} + +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const currentSquares = history[history.length - 1]; + + function handlePlay(nextSquares) { + setHistory([...history, nextSquares]); + setXIsNext(!xIsNext); + } + + function jumpTo(nextMove) { + // TODO + } + + const moves = history.map((squares, move) => { + let description; + if (move > 0) { + description = 'Go to move #' + move; + } else { + description = 'Go to game start'; + } + return ( + <li> + <button onClick={() => jumpTo(move)}>{description}</button> + </li> + ); + }); + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + </div> + <div className="game-info"> + <ol>{moves}</ol> + </div> + </div> + ); +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} + +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +As you iterate through `history` array inside the function you passed to `map`, the `squares` argument goes through each element of `history`, and the `move` argument goes through each array index: `0`, `1`, `2`, …. (In most cases, you'd need the actual array elements, but in this case you don't use `squares` to render a list of moves.) + +For each move in the tic-tac-toe game's history, you create a list item `<li>` which contains a button `<button>`. The button has an `onClick` handler which calls a function called `jumpTo` (that you haven't implemented yet). + +For now, you should see a list of the moves that occurred in the game and an error in the developer tools console. + +Let's discuss what the "key" error means. + +### Picking a key {/*picking-a-key*/} + +When you render a list, React stores some information about each rendered list item. When you update a list, React needs to determine what has changed. You could have added, removed, re-arranged, or updated the list's items. + +Imagine transitioning from + +```html +<li>Alexa: 7 tasks left</li> +<li>Ben: 5 tasks left</li> +``` + +to + +```html +<li>Ben: 9 tasks left</li> +<li>Claudia: 8 tasks left</li> +<li>Alexa: 5 tasks left</li> +``` + +In addition to the updated counts, a human reading this would probably say that you swapped Alexa and Ben's ordering and inserted Claudia between Alexa and Ben. However, React is a computer program and can't know what you intended, so you need to specify a _key_ property for each list item to differentiate each list item from its siblings. If you were displaying data from a database, Alexa, Ben, and Claudia's database IDs could be used as keys. + +```js {1} +<li key={user.id}> + {user.name}: {user.taskCount} tasks left +</li> +``` + +When a list is re-rendered, React takes each list item's key and searches the previous list's items for a matching key. If the current list has a key that didn't exist before, React creates a component. If the current list is missing a key that existed in the previous list, React destroys the previous component. If two keys match, the corresponding component is moved. + +Keys tell React about the identity of each component, which allows React to maintain state between re-renders. If a component's key changes, the component will be destroyed and re-created with a new state. + +`key` is a special and reserved property in React. When an element is created, React extracts the `key` property and stores the key directly on the returned element. Even though `key` may look like it is passed as props, React automatically uses `key` to decide which components to update. There's no way for a component to ask what `key` its parent specified. + +**It's strongly recommended that you assign proper keys whenever you build dynamic lists.** If you don't have an appropriate key, you may want to consider restructuring your data so that you do. + +If no key is specified, React will report an error and use the array index as a key by default. Using the array index as a key is problematic when trying to re-order a list's items or inserting/removing list items. Explicitly passing `key={i}` silences the error but has the same problems as array indices and is not recommended in most cases. + +Keys do not need to be globally unique; they only need to be unique between components and their siblings. + +### Implementing time travel {/*implementing-time-travel*/} + +In the tic-tac-toe game's history, each past move has a unique ID associated with it: it's the sequential number of the move. Moves will never be re-ordered, deleted, or inserted in the middle, so it's safe to use the move index as a key. + +In the `Game` function, you can add the key as `<li key={move}>`, and if you reload the rendered game, React's "key" error should disappear: + +```js {4} +const moves = history.map((squares, move) => { + //... + return ( + <li key={move}> + <button onClick={() => jumpTo(move)}>{description}</button> + </li> + ); +}); +``` + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({ value, onSquareClick }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + onPlay(nextSquares); + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( + <> + <div className="status">{status}</div> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} + +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const currentSquares = history[history.length - 1]; + + function handlePlay(nextSquares) { + setHistory([...history, nextSquares]); + setXIsNext(!xIsNext); + } + + function jumpTo(nextMove) { + // TODO + } + + const moves = history.map((squares, move) => { + let description; + if (move > 0) { + description = 'Go to move #' + move; + } else { + description = 'Go to game start'; + } + return ( + <li key={move}> + <button onClick={() => jumpTo(move)}>{description}</button> + </li> + ); + }); + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + </div> + <div className="game-info"> + <ol>{moves}</ol> + </div> + </div> + ); +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} + +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} + +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +Before you can implement `jumpTo`, you need the `Game` component to keep track of which step the user is currently viewing. To do this, define a new state variable called `currentMove`, defaulting to `0`: + +```js {4} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const [currentMove, setCurrentMove] = useState(0); + const currentSquares = history[history.length - 1]; + //... +} +``` + +Next, update the `jumpTo` function inside `Game` to update that `currentMove`. You'll also set `xIsNext` to `true` if the number that you're changing `currentMove` to is even. + +```js {4-5} +export default function Game() { + // ... + function jumpTo(nextMove) { + setCurrentMove(nextMove); + setXIsNext(nextMove % 2 === 0); + } + //... +} +``` + +You will now make two changes to the `Game`'s `handlePlay` function which is called when you click on a square. + +- If you "go back in time" and then make a new move from that point, you only want to keep the history up to that point. Instead of adding `nextSquares` after all items (`...` spread syntax) in `history`, you'll add it after all items in `history.slice(0, currentMove + 1)` so that you're only keeping that portion of the old history. +- Each time a move is made, you need to update `currentMove` to point to the latest history entry. + +```js {2-4} +function handlePlay(nextSquares) { + const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; + setHistory(nextHistory); + setCurrentMove(nextHistory.length - 1); + setXIsNext(!xIsNext); +} +``` + +Finally, you will modify the `Game` component to render the currently selected move, instead of always rendering the final move: + +```js {5} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const [currentMove, setCurrentMove] = useState(0); + const currentSquares = history[currentMove]; + + // ... +} +``` + +If you click on any step in the game's history, the tic-tac-toe board should immediately update to show what the board looked like after that step occurred. + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({value, onSquareClick}) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + onPlay(nextSquares); + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( + <> + <div className="status">{status}</div> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} + +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const [currentMove, setCurrentMove] = useState(0); + const currentSquares = history[currentMove]; + + function handlePlay(nextSquares) { + const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; + setHistory(nextHistory); + setCurrentMove(nextHistory.length - 1); + setXIsNext(!xIsNext); + } + + function jumpTo(nextMove) { + setCurrentMove(nextMove); + setXIsNext(nextMove % 2 === 0); + } + + const moves = history.map((squares, move) => { + let description; + if (move > 0) { + description = 'Go to move #' + move; + } else { + description = 'Go to game start'; + } + return ( + <li key={move}> + <button onClick={() => jumpTo(move)}>{description}</button> + </li> + ); + }); + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + </div> + <div className="game-info"> + <ol>{moves}</ol> + </div> + </div> + ); +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +### Final cleanup {/*final-cleanup*/} + +If you look at the code very closely, you may notice that `xIsNext === true` when `currentMove` is even and `xIsNext === false` when `currentMove` is odd. In other words, if you know the value of `currentMove`, then you can always figure out what `xIsNext` should be. + +There's no reason for you to store both of these in state. In fact, always try to avoid redundant state. Simplifying what you store in state helps reduce bugs and make your code easier to understand. Change `Game` so that it no longer stores `xIsNext` as a separate state variable and instead figures it out based on the `currentMove`: + +```js {4,11,15} +export default function Game() { + const [history, setHistory] = useState([Array(9).fill(null)]); + const [currentMove, setCurrentMove] = useState(0); + const xIsNext = currentMove % 2 === 0; + const currentSquares = history[currentMove]; + + function handlePlay(nextSquares) { + const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; + setHistory(nextHistory); + setCurrentMove(nextHistory.length - 1); + } + + function jumpTo(nextMove) { + setCurrentMove(nextMove); + } + // ... +} +``` + +You no longer need the `xIsNext` state declaration or the calls to `setXIsNext`. Now, there's no chance for `xIsNext` to get out of sync with `currentMove`, even if you make a mistake while coding the components. + +### Wrapping up {/*wrapping-up*/} + +Congratulations! You've created a tic-tac-toe game that: + +- Lets you play tic-tac-toe, +- Indicates when a player has won the game, +- Stores a game's history as a game progresses, +- Allows players to review a game's history and see previous versions of a game's board. + +Nice work! We hope you now feel like you have a decent grasp of how React works. + +Check out the final result here: + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +function Square({ value, onSquareClick }) { + return ( + <button className="square" onClick={onSquareClick}> + {value} + </button> + ); +} + +function Board({ xIsNext, squares, onPlay }) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const nextSquares = squares.slice(); + if (xIsNext) { + nextSquares[i] = 'X'; + } else { + nextSquares[i] = 'O'; + } + onPlay(nextSquares); + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( + <> + <div className="status">{status}</div> + <div className="board-row"> + <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> + <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> + <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> + </div> + <div className="board-row"> + <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> + <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> + <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> + </div> + <div className="board-row"> + <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> + <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> + <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> + </div> + </> + ); +} + +export default function Game() { + const [history, setHistory] = useState([Array(9).fill(null)]); + const [currentMove, setCurrentMove] = useState(0); + const xIsNext = currentMove % 2 === 0; + const currentSquares = history[currentMove]; + + function handlePlay(nextSquares) { + const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; + setHistory(nextHistory); + setCurrentMove(nextHistory.length - 1); + } + + function jumpTo(nextMove) { + setCurrentMove(nextMove); + } + + const moves = history.map((squares, move) => { + let description; + if (move > 0) { + description = 'Go to move #' + move; + } else { + description = 'Go to game start'; + } + return ( + <li key={move}> + <button onClick={() => jumpTo(move)}>{description}</button> + </li> + ); + }); + + return ( + <div className="game"> + <div className="game-board"> + <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> + </div> + <div className="game-info"> + <ol>{moves}</ol> + </div> + </div> + ); +} + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} +``` + +```css styles.css +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +</Sandpack> + +If you have extra time or want to practice your new React skills, here are some ideas for improvements that you could make to the tic-tac-toe game, listed in order of increasing difficulty: + +1. For the current move only, show "You are at move #..." instead of a button +1. Rewrite `Board` to use two loops to make the squares instead of hardcoding them. +1. Add a toggle button that lets you sort the moves in either ascending or descending order. +1. When someone wins, highlight the three squares that caused the win (and when no one wins, display a message about the result being a draw). +1. Display the location for each move in the format (col, row) in the move history list. + +Throughout this tutorial, you've touched on React concepts including elements, components, props, and state. Now that you've seen how these concepts work when building a game, check out [Thinking in React](/learn/thinking-in-react) to see how the same React concepts work when build an app's UI. diff --git a/beta/src/content/learn/updating-arrays-in-state.md b/beta/src/content/learn/updating-arrays-in-state.md new file mode 100644 index 000000000..eae89facf --- /dev/null +++ b/beta/src/content/learn/updating-arrays-in-state.md @@ -0,0 +1,1975 @@ +--- +title: Updating Arrays in State +--- + +<Intro> + +Arrays are mutable in JavaScript, but you should treat them as immutable when you store them in state. Just like with objects, when you want to update an array stored in state, you need to create a new one (or make a copy of an existing one), and then set state to use the new array. + +</Intro> + +<YouWillLearn> + +- How to add, remove, or change items in an array in React state +- How to update an object inside of an array +- How to make array copying less repetitive with Immer + +</YouWillLearn> + +## Updating arrays without mutation {/*updating-arrays-without-mutation*/} + +In JavaScript, arrays are just another kind of object. [Like with objects](/learn/updating-objects-in-state), **you should treat arrays in React state as read-only.** This means that you shouldn't reassign items inside an array like `arr[0] = 'bird'`, and you also shouldn't use methods that mutate the array, such as `push()` and `pop()`. + +Instead, every time you want to update an array, you'll want to pass a *new* array to your state setting function. To do that, you can create a new array from the original array in your state by calling its non-mutating methods like `filter()` and `map()`. Then you can set your state to the resulting new array. + +Here is a reference table of common array operations. When dealing with arrays inside React state, you will need to avoid the methods in the left column, and instead prefer the methods in the right column: + +| | avoid (mutates the array) | prefer (returns a new array) | +| --------- | ----------------------------------- | ------------------------------------------------------------------- | +| adding | `push`, `unshift` | `concat`, `[...arr]` spread syntax ([example](#adding-to-an-array)) | +| removing | `pop`, `shift`, `splice` | `filter`, `slice` ([example](#removing-from-an-array)) | +| replacing | `splice`, `arr[i] = ...` assignment | `map` ([example](#replacing-items-in-an-array)) | +| sorting | `reverse`, `sort` | copy the array first ([example](#making-other-changes-to-an-array)) | + +Alternatively, you can [use Immer](#write-concise-update-logic-with-immer) which lets you use methods from both columns. + +<Pitfall> + +Unfortunately, [`slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) and [`splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) are named similarly but are very different: + +* `slice` lets you copy an array or a part of it. +* `splice` **mutates** the array (to insert or delete items). + +In React, you will be using `slice` (no `p`!) a lot more often because you don't want to mutate objects or arrays in state. [Updating Objects](/learn/updating-objects-in-state) explains what mutation is and why it's not recommended for state. + +</Pitfall> + +### Adding to an array {/*adding-to-an-array*/} + +`push()` will mutate an array, which you don't want: + +<Sandpack> + +```js +import { useState } from 'react'; + +let nextId = 0; + +export default function List() { + const [name, setName] = useState(''); + const [artists, setArtists] = useState([]); + + return ( + <> + <h1>Inspiring sculptors:</h1> + <input + value={name} + onChange={e => setName(e.target.value)} + /> + <button onClick={() => { + setName(''); + artists.push({ + id: nextId++, + name: name, + }); + }}>Add</button> + <ul> + {artists.map(artist => ( + <li key={artist.id}>{artist.name}</li> + ))} + </ul> + </> + ); +} +``` + +```css +button { margin-left: 5px; } +``` + +</Sandpack> + +Instead, create a *new* array which contains the existing items *and* a new item at the end. There are multiple ways to do this, but the easiest one is to use the `...` [array spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_array_literals) syntax: + +```js +setArtists( // Replace the state + [ // with a new array + ...artists, // that contains all the old items + { id: nextId++, name: name } // and one new item at the end + ] +); +``` + +Now it works correctly: + +<Sandpack> + +```js +import { useState } from 'react'; + +let nextId = 0; + +export default function List() { + const [name, setName] = useState(''); + const [artists, setArtists] = useState([]); + + return ( + <> + <h1>Inspiring sculptors:</h1> + <input + value={name} + onChange={e => setName(e.target.value)} + /> + <button onClick={() => { + setName(''); + setArtists([ + ...artists, + { id: nextId++, name: name } + ]); + }}>Add</button> + <ul> + {artists.map(artist => ( + <li key={artist.id}>{artist.name}</li> + ))} + </ul> + </> + ); +} +``` + +```css +button { margin-left: 5px; } +``` + +</Sandpack> + +The array spread syntax also lets you prepend an item by placing it *before* the original `...artists`: + +```js +setArtists([ + { id: nextId++, name: name }, + ...artists // Put old items at the end +]); +``` + +In this way, spread can do the job of both `push()` by adding to the end of an array and `unshift()` by adding to the beginning of an array. Try it in the sandbox above! + +### Removing from an array {/*removing-from-an-array*/} + +The easiest way to remove an item from an array is to *filter it out*. In other words, you will produce a new array that will not contain that item. To do this, use the `filter` method, for example: + +<Sandpack> + +```js +import { useState } from 'react'; + +let initialArtists = [ + { id: 0, name: 'Marta Colvin Andrade' }, + { id: 1, name: 'Lamidi Olonade Fakeye'}, + { id: 2, name: 'Louise Nevelson'}, +]; + +export default function List() { + const [artists, setArtists] = useState( + initialArtists + ); + + return ( + <> + <h1>Inspiring sculptors:</h1> + <ul> + {artists.map(artist => ( + <li key={artist.id}> + {artist.name}{' '} + <button onClick={() => { + setArtists( + artists.filter(a => + a.id !== artist.id + ) + ); + }}> + Delete + </button> + </li> + ))} + </ul> + </> + ); +} +``` + +</Sandpack> + +Click the "Delete" button a few times, and look at its click handler. + +```js +setArtists( + artists.filter(a => a.id !== artist.id) +); +``` + +Here, `artists.filter(a => a.id !== artist.id)` means "create an array that consists of those `artists` whose IDs are different from `artist.id`". In other words, each artist's "Delete" button will filter _that_ artist out of the array, and then request a re-render with the resulting array. Note that `filter` does not modify the original array. + +### Transforming an array {/*transforming-an-array*/} + +If you want to change some or all items of the array, you can use `map()` to create a **new** array. The function you will pass to `map` can decide what to do with each item, based on its data or its index (or both). + +In this example, an array holds coordinates of two circles and a square. When you press the button, it moves only the circles down by 50 pixels. It does this by producing a new array of data using `map()`: + +<Sandpack> + +```js +import { useState } from 'react'; + +let initialShapes = [ + { id: 0, type: 'circle', x: 50, y: 100 }, + { id: 1, type: 'square', x: 150, y: 100 }, + { id: 2, type: 'circle', x: 250, y: 100 }, +]; + +export default function ShapeEditor() { + const [shapes, setShapes] = useState( + initialShapes + ); + + function handleClick() { + const nextShapes = shapes.map(shape => { + if (shape.type === 'square') { + // No change + return shape; + } else { + // Return a new circle 50px below + return { + ...shape, + y: shape.y + 50, + }; + } + }); + // Re-render with the new array + setShapes(nextShapes); + } + + return ( + <> + <button onClick={handleClick}> + Move circles down! + </button> + {shapes.map(shape => ( + <div + key={shape.id} + style={{ + background: 'purple', + position: 'absolute', + left: shape.x, + top: shape.y, + borderRadius: + shape.type === 'circle' + ? '50%' : '', + width: 20, + height: 20, + }} /> + ))} + </> + ); +} +``` + +```css +body { height: 300px; } +``` + +</Sandpack> + +### Replacing items in an array {/*replacing-items-in-an-array*/} + +It is particularly common to want to replace one or more items in an array. Assignments like `arr[0] = 'bird'` are mutating the original array, so instead you'll want to use `map` for this as well. + +To replace an item, create a new array with `map`. Inside your `map` call, you will receive the item index as the second argument. Use it to decide whether to return the original item (the first argument) or something else: + +<Sandpack> + +```js +import { useState } from 'react'; + +let initialCounters = [ + 0, 0, 0 +]; + +export default function CounterList() { + const [counters, setCounters] = useState( + initialCounters + ); + + function handleIncrementClick(index) { + const nextCounters = counters.map((c, i) => { + if (i === index) { + // Increment the clicked counter + return c + 1; + } else { + // The rest haven't changed + return c; + } + }); + setCounters(nextCounters); + } + + return ( + <ul> + {counters.map((counter, i) => ( + <li key={i}> + {counter} + <button onClick={() => { + handleIncrementClick(i); + }}>+1</button> + </li> + ))} + </ul> + ); +} +``` + +```css +button { margin: 5px; } +``` + +</Sandpack> + +### Inserting into an array {/*inserting-into-an-array*/} + +Sometimes, you may want to insert an item at a particular position that's neither at the beginning nor at the end. To do this, you can use the `...` array spread syntax together with the `slice()` method. The `slice()` method lets you cut a "slice" of the array. To insert an item, you will create an array that spreads the slice _before_ the insertion point, then the new item, and then the rest of the original array. + +In this example, the Insert button always inserts at the index `1`: + +<Sandpack> + +```js +import { useState } from 'react'; + +let nextId = 3; +const initialArtists = [ + { id: 0, name: 'Marta Colvin Andrade' }, + { id: 1, name: 'Lamidi Olonade Fakeye'}, + { id: 2, name: 'Louise Nevelson'}, +]; + +export default function List() { + const [name, setName] = useState(''); + const [artists, setArtists] = useState( + initialArtists + ); + + function handleClick() { + const insertAt = 1; // Could be any index + const nextArtists = [ + // Items before the insertion point: + ...artists.slice(0, insertAt), + // New item: + { id: nextId++, name: name }, + // Items after the insertion point: + ...artists.slice(insertAt) + ]; + setArtists(nextArtists); + setName(''); + } + + return ( + <> + <h1>Inspiring sculptors:</h1> + <input + value={name} + onChange={e => setName(e.target.value)} + /> + <button onClick={handleClick}> + Insert + </button> + <ul> + {artists.map(artist => ( + <li key={artist.id}>{artist.name}</li> + ))} + </ul> + </> + ); +} +``` + +```css +button { margin-left: 5px; } +``` + +</Sandpack> + +### Making other changes to an array {/*making-other-changes-to-an-array*/} + +There are some things you can't do with the spread syntax and non-mutating methods like `map()` and `filter()` alone. For example, you may want to reverse or sort an array. The JavaScript `reverse()` and `sort()` methods are mutating the original array, so you can't use them directly. + +**However, you can copy the array first, and then make changes to it.** + +For example: + +<Sandpack> + +```js +import { useState } from 'react'; + +let nextId = 3; +const initialList = [ + { id: 0, title: 'Big Bellies' }, + { id: 1, title: 'Lunar Landscape' }, + { id: 2, title: 'Terracotta Army' }, +]; + +export default function List() { + const [list, setList] = useState(initialList); + + function handleClick() { + const nextList = [...list]; + nextList.reverse(); + setList(nextList); + } + + return ( + <> + <button onClick={handleClick}> + Reverse + </button> + <ul> + {list.map(artwork => ( + <li key={artwork.id}>{artwork.title}</li> + ))} + </ul> + </> + ); +} +``` + +</Sandpack> + +Here, you use the `[...list]` spread syntax to create a copy of the original array first. Now that you have a copy, you can use mutating methods like `nextList.reverse()` or `nextList.sort()`, or even assign individual items with `nextList[0] = "something"`. + +However, **even if you copy an array, you can't mutate existing items _inside_ of it directly.** This is because copying is shallow--the new array will contain the same items as the original one. So if you modify an object inside the copied array, you are mutating the existing state. For example, code like this is a problem. + +```js +const nextList = [...list]; +nextList[0].seen = true; // Problem: mutates list[0] +setList(nextList); +``` + +Although `nextList` and `list` are two different arrays, **`nextList[0]` and `list[0]` point to the same object.** So by changing `nextList[0].seen`, you are also changing `list[0].seen`. This is a state mutation, which you should avoid! You can solve this issue in a similar way to [updating nested JavaScript objects](/learn/updating-objects-in-state#updating-a-nested-object)--by copying individual items you want to change instead of mutating them. Here's how. + +## Updating objects inside arrays {/*updating-objects-inside-arrays*/} + +Objects are not _really_ located "inside" arrays. They might appear to be "inside" in code, but each object in an array is a separate value, to which the array "points". This is why you need to be careful when changing nested fields like `list[0]`. Another person's artwork list may point to the same element of the array! + +**When updating nested state, you need to create copies from the point where you want to update, and all the way up to the top level.** Let's see how this works. + +In this example, two separate artwork lists have the same initial state. They are supposed to be isolated, but because of a mutation, their state is accidentally shared, and checking a box in one list affects the other list: + +<Sandpack> + +```js +import { useState } from 'react'; + +let nextId = 3; +const initialList = [ + { id: 0, title: 'Big Bellies', seen: false }, + { id: 1, title: 'Lunar Landscape', seen: false }, + { id: 2, title: 'Terracotta Army', seen: true }, +]; + +export default function BucketList() { + const [myList, setMyList] = useState(initialList); + const [yourList, setYourList] = useState( + initialList + ); + + function handleToggleMyList(artworkId, nextSeen) { + const myNextList = [...myList]; + const artwork = myNextList.find( + a => a.id === artworkId + ); + artwork.seen = nextSeen; + setMyList(myNextList); + } + + function handleToggleYourList(artworkId, nextSeen) { + const yourNextList = [...yourList]; + const artwork = yourNextList.find( + a => a.id === artworkId + ); + artwork.seen = nextSeen; + setYourList(yourNextList); + } + + return ( + <> + <h1>Art Bucket List</h1> + <h2>My list of art to see:</h2> + <ItemList + artworks={myList} + onToggle={handleToggleMyList} /> + <h2>Your list of art to see:</h2> + <ItemList + artworks={yourList} + onToggle={handleToggleYourList} /> + </> + ); +} + +function ItemList({ artworks, onToggle }) { + return ( + <ul> + {artworks.map(artwork => ( + <li key={artwork.id}> + <label> + <input + type="checkbox" + checked={artwork.seen} + onChange={e => { + onToggle( + artwork.id, + e.target.checked + ); + }} + /> + {artwork.title} + </label> + </li> + ))} + </ul> + ); +} +``` + +</Sandpack> + +The problem is in code like this: + +```js +const myNextList = [...myList]; +const artwork = myNextList.find(a => a.id === artworkId); +artwork.seen = nextSeen; // Problem: mutates an existing item +setMyList(myNextList); +``` + +Although the `myNextList` array itself is new, the *items themselves* are the same as in the original `myList` array. So changing `artwork.seen` changes the *original* artwork item. That artwork item is also in `yourArtworks`, which causes the bug. Bugs like this can be difficult to think about, but thankfully they disappear if you avoid mutating state. + +**You can use `map` to substitute an old item with its updated version without mutation.** + +```js +setMyList(myList.map(artwork => { + if (artwork.id === artworkId) { + // Create a *new* object with changes + return { ...artwork, seen: nextSeen }; + } else { + // No changes + return artwork; + } +}); +``` + +Here, `...` is the object spread syntax used to [create a copy of an object.](/learn/updating-objects-in-state#copying-objects-with-the-spread-syntax) + +With this approach, none of the existing state items are being mutated, and the bug is fixed: + +<Sandpack> + +```js +import { useState } from 'react'; + +let nextId = 3; +const initialList = [ + { id: 0, title: 'Big Bellies', seen: false }, + { id: 1, title: 'Lunar Landscape', seen: false }, + { id: 2, title: 'Terracotta Army', seen: true }, +]; + +export default function BucketList() { + const [myList, setMyList] = useState(initialList); + const [yourList, setYourList] = useState( + initialList + ); + + function handleToggleMyList(artworkId, nextSeen) { + setMyList(myList.map(artwork => { + if (artwork.id === artworkId) { + // Create a *new* object with changes + return { ...artwork, seen: nextSeen }; + } else { + // No changes + return artwork; + } + })); + } + + function handleToggleYourList(artworkId, nextSeen) { + setYourList(yourList.map(artwork => { + if (artwork.id === artworkId) { + // Create a *new* object with changes + return { ...artwork, seen: nextSeen }; + } else { + // No changes + return artwork; + } + })); + } + + return ( + <> + <h1>Art Bucket List</h1> + <h2>My list of art to see:</h2> + <ItemList + artworks={myList} + onToggle={handleToggleMyList} /> + <h2>Your list of art to see:</h2> + <ItemList + artworks={yourList} + onToggle={handleToggleYourList} /> + </> + ); +} + +function ItemList({ artworks, onToggle }) { + return ( + <ul> + {artworks.map(artwork => ( + <li key={artwork.id}> + <label> + <input + type="checkbox" + checked={artwork.seen} + onChange={e => { + onToggle( + artwork.id, + e.target.checked + ); + }} + /> + {artwork.title} + </label> + </li> + ))} + </ul> + ); +} +``` + +</Sandpack> + +In general, **you should only mutate objects that you have just created.** If you were inserting a *new* artwork, you could mutate it, but if you're dealing with something that's already in state, you need to make a copy. + +### Write concise update logic with Immer {/*write-concise-update-logic-with-immer*/} + +Updating nested arrays without mutation can get a little bit repetitive. [Just as with objects](/learn/updating-objects-in-state#write-concise-update-logic-with-immer): + +- Generally, you shouldn't need to update state more than a couple of levels deep. If your state objects are very deep, you might want to [restructure them differently](/learn/choosing-the-state-structure#avoid-deeply-nested-state) so that they are flat. +- If you don't want to change your state structure, you might prefer to use [Immer](https://github.com/immerjs/use-immer), which lets you write using the convenient but mutating syntax and takes care of producing the copies for you. + +Here is the Art Bucket List example rewritten with Immer: + +<Sandpack> + +```js +import { useState } from 'react'; +import { useImmer } from 'use-immer'; + +let nextId = 3; +const initialList = [ + { id: 0, title: 'Big Bellies', seen: false }, + { id: 1, title: 'Lunar Landscape', seen: false }, + { id: 2, title: 'Terracotta Army', seen: true }, +]; + +export default function BucketList() { + const [myList, updateMyList] = useImmer( + initialList + ); + const [yourArtworks, updateYourList] = useImmer( + initialList + ); + + function handleToggleMyList(id, nextSeen) { + updateMyList(draft => { + const artwork = draft.find(a => + a.id === id + ); + artwork.seen = nextSeen; + }); + } + + function handleToggleYourList(artworkId, nextSeen) { + updateYourList(draft => { + const artwork = draft.find(a => + a.id === artworkId + ); + artwork.seen = nextSeen; + }); + } + + return ( + <> + <h1>Art Bucket List</h1> + <h2>My list of art to see:</h2> + <ItemList + artworks={myList} + onToggle={handleToggleMyList} /> + <h2>Your list of art to see:</h2> + <ItemList + artworks={yourArtworks} + onToggle={handleToggleYourList} /> + </> + ); +} + +function ItemList({ artworks, onToggle }) { + return ( + <ul> + {artworks.map(artwork => ( + <li key={artwork.id}> + <label> + <input + type="checkbox" + checked={artwork.seen} + onChange={e => { + onToggle( + artwork.id, + e.target.checked + ); + }} + /> + {artwork.title} + </label> + </li> + ))} + </ul> + ); +} +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +Note how with Immer, **mutation like `artwork.seen = nextSeen` is now okay:** + +```js +updateMyTodos(draft => { + const artwork = draft.find(a => a.id === artworkId); + artwork.seen = nextSeen; +}); +``` + +This is because you're not mutating the _original_ state, but you're mutating a special `draft` object provided by Immer. Similarly, you can apply mutating methods like `push()` and `pop()` to the content of the `draft`. + +Behind the scenes, Immer always constructs the next state from scratch according to the changes that you've done to the `draft`. This keeps your event handlers very concise without ever mutating state. + +<Recap> + +- You can put arrays into state, but you can't change them. +- Instead of mutating an array, create a *new* version of it, and update the state to it. +- You can use the `[...arr, newItem]` array spread syntax to create arrays with new items. +- You can use `filter()` and `map()` to create new arrays with filtered or transformed items. +- You can use Immer to keep your code concise. + +</Recap> + + + +<Challenges> + +#### Update an item in the shopping cart {/*update-an-item-in-the-shopping-cart*/} + +Fill in the `handleIncreaseClick` logic so that pressing "+" increases the corresponding number: + +<Sandpack> + +```js +import { useState } from 'react'; + +const initialProducts = [{ + id: 0, + name: 'Baklava', + count: 1, +}, { + id: 1, + name: 'Cheese', + count: 5, +}, { + id: 2, + name: 'Spaghetti', + count: 2, +}]; + +export default function ShoppingCart() { + const [ + products, + setProducts + ] = useState(initialProducts) + + function handleIncreaseClick(productId) { + + } + + return ( + <ul> + {products.map(product => ( + <li key={product.id}> + {product.name} + {' '} + (<b>{product.count}</b>) + <button onClick={() => { + handleIncreaseClick(product.id); + }}> + + + </button> + </li> + ))} + </ul> + ); +} +``` + +```css +button { margin: 5px; } +``` + +</Sandpack> + +<Solution> + +You can use the `map` function to create a new array, and then use the `...` object spread syntax to create a copy of the changed object for the new array: + +<Sandpack> + +```js +import { useState } from 'react'; + +const initialProducts = [{ + id: 0, + name: 'Baklava', + count: 1, +}, { + id: 1, + name: 'Cheese', + count: 5, +}, { + id: 2, + name: 'Spaghetti', + count: 2, +}]; + +export default function ShoppingCart() { + const [ + products, + setProducts + ] = useState(initialProducts) + + function handleIncreaseClick(productId) { + setProducts(products.map(product => { + if (product.id === productId) { + return { + ...product, + count: product.count + 1 + }; + } else { + return product; + } + })) + } + + return ( + <ul> + {products.map(product => ( + <li key={product.id}> + {product.name} + {' '} + (<b>{product.count}</b>) + <button onClick={() => { + handleIncreaseClick(product.id); + }}> + + + </button> + </li> + ))} + </ul> + ); +} +``` + +```css +button { margin: 5px; } +``` + +</Sandpack> + +</Solution> + +#### Remove an item from the shopping cart {/*remove-an-item-from-the-shopping-cart*/} + +This shopping cart has a working "+" button, but the "–" button doesn't do anything. You need to add an event handler to it so that pressing it decreases the `count` of the corresponding product. If you press "–" when the count is 1, the product should automatically get removed from the cart. Make sure it never shows 0. + +<Sandpack> + +```js +import { useState } from 'react'; + +const initialProducts = [{ + id: 0, + name: 'Baklava', + count: 1, +}, { + id: 1, + name: 'Cheese', + count: 5, +}, { + id: 2, + name: 'Spaghetti', + count: 2, +}]; + +export default function ShoppingCart() { + const [ + products, + setProducts + ] = useState(initialProducts) + + function handleIncreaseClick(productId) { + setProducts(products.map(product => { + if (product.id === productId) { + return { + ...product, + count: product.count + 1 + }; + } else { + return product; + } + })) + } + + return ( + <ul> + {products.map(product => ( + <li key={product.id}> + {product.name} + {' '} + (<b>{product.count}</b>) + <button onClick={() => { + handleIncreaseClick(product.id); + }}> + + + </button> + <button> + – + </button> + </li> + ))} + </ul> + ); +} +``` + +```css +button { margin: 5px; } +``` + +</Sandpack> + +<Solution> + +You can first use `map` to produce a new array, and then `filter` to remove products with a `count` set to `0`: + +<Sandpack> + +```js +import { useState } from 'react'; + +const initialProducts = [{ + id: 0, + name: 'Baklava', + count: 1, +}, { + id: 1, + name: 'Cheese', + count: 5, +}, { + id: 2, + name: 'Spaghetti', + count: 2, +}]; + +export default function ShoppingCart() { + const [ + products, + setProducts + ] = useState(initialProducts) + + function handleIncreaseClick(productId) { + setProducts(products.map(product => { + if (product.id === productId) { + return { + ...product, + count: product.count + 1 + }; + } else { + return product; + } + })) + } + + function handleDecreaseClick(productId) { + let nextProducts = products.map(product => { + if (product.id === productId) { + return { + ...product, + count: product.count - 1 + }; + } else { + return product; + } + }); + nextProducts = nextProducts.filter(p => + p.count > 0 + ); + setProducts(nextProducts) + } + + return ( + <ul> + {products.map(product => ( + <li key={product.id}> + {product.name} + {' '} + (<b>{product.count}</b>) + <button onClick={() => { + handleIncreaseClick(product.id); + }}> + + + </button> + <button onClick={() => { + handleDecreaseClick(product.id); + }}> + – + </button> + </li> + ))} + </ul> + ); +} +``` + +```css +button { margin: 5px; } +``` + +</Sandpack> + +</Solution> + +#### Fix the mutations using non-mutative methods {/*fix-the-mutations-using-non-mutative-methods*/} + +In this example, all of the event handlers in `App.js` use mutation. As a result, editing and deleting todos doesn't work. Rewrite `handleAddTodo`, `handleChangeTodo`, and `handleDeleteTodo` to use the non-mutative methods: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AddTodo from './AddTodo.js'; +import TaskList from './TaskList.js'; + +let nextId = 3; +const initialTodos = [ + { id: 0, title: 'Buy milk', done: true }, + { id: 1, title: 'Eat tacos', done: false }, + { id: 2, title: 'Brew tea', done: false }, +]; + +export default function TaskApp() { + const [todos, setTodos] = useState( + initialTodos + ); + + function handleAddTodo(title) { + todos.push({ + id: nextId++, + title: title, + done: false + }); + } + + function handleChangeTodo(nextTodo) { + const todo = todos.find(t => + t.id === nextTodo.id + ); + todo.title = nextTodo.title; + todo.done = nextTodo.done; + } + + function handleDeleteTodo(todoId) { + const index = todos.findIndex(t => + t.id === todoId + ); + todos.splice(index, 1); + } + + return ( + <> + <AddTodo + onAddTodo={handleAddTodo} + /> + <TaskList + todos={todos} + onChangeTodo={handleChangeTodo} + onDeleteTodo={handleDeleteTodo} + /> + </> + ); +} +``` + +```js AddTodo.js +import { useState } from 'react'; + +export default function AddTodo({ onAddTodo }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add todo" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddTodo(title); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + todos, + onChangeTodo, + onDeleteTodo +}) { + return ( + <ul> + {todos.map(todo => ( + <li key={todo.id}> + <Task + todo={todo} + onChange={onChangeTodo} + onDelete={onDeleteTodo} + /> + </li> + ))} + </ul> + ); +} + +function Task({ todo, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let todoContent; + if (isEditing) { + todoContent = ( + <> + <input + value={todo.title} + onChange={e => { + onChange({ + ...todo, + title: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + todoContent = ( + <> + {todo.title} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={todo.done} + onChange={e => { + onChange({ + ...todo, + done: e.target.checked + }); + }} + /> + {todoContent} + <button onClick={() => onDelete(todo.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +<Solution> + +In `handleAddTodo`, you can use the array spread syntax. In `handleChangeTodo`, you can create a new array with `map`. In `handleDeleteTodo`, you can create a new array with `filter`. Now the list works correctly: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AddTodo from './AddTodo.js'; +import TaskList from './TaskList.js'; + +let nextId = 3; +const initialTodos = [ + { id: 0, title: 'Buy milk', done: true }, + { id: 1, title: 'Eat tacos', done: false }, + { id: 2, title: 'Brew tea', done: false }, +]; + +export default function TaskApp() { + const [todos, setTodos] = useState( + initialTodos + ); + + function handleAddTodo(title) { + setTodos([ + ...todos, + { + id: nextId++, + title: title, + done: false + } + ]); + } + + function handleChangeTodo(nextTodo) { + setTodos(todos.map(t => { + if (t.id === nextTodo.id) { + return nextTodo; + } else { + return t; + } + })); + } + + function handleDeleteTodo(todoId) { + setTodos( + todos.filter(t => t.id !== todoId) + ); + } + + return ( + <> + <AddTodo + onAddTodo={handleAddTodo} + /> + <TaskList + todos={todos} + onChangeTodo={handleChangeTodo} + onDeleteTodo={handleDeleteTodo} + /> + </> + ); +} +``` + +```js AddTodo.js +import { useState } from 'react'; + +export default function AddTodo({ onAddTodo }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add todo" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddTodo(title); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + todos, + onChangeTodo, + onDeleteTodo +}) { + return ( + <ul> + {todos.map(todo => ( + <li key={todo.id}> + <Task + todo={todo} + onChange={onChangeTodo} + onDelete={onDeleteTodo} + /> + </li> + ))} + </ul> + ); +} + +function Task({ todo, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let todoContent; + if (isEditing) { + todoContent = ( + <> + <input + value={todo.title} + onChange={e => { + onChange({ + ...todo, + title: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + todoContent = ( + <> + {todo.title} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={todo.done} + onChange={e => { + onChange({ + ...todo, + done: e.target.checked + }); + }} + /> + {todoContent} + <button onClick={() => onDelete(todo.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +</Solution> + + +#### Fix the mutations using Immer {/*fix-the-mutations-using-immer*/} + +This is the same example as in the previous challenge. This time, fix the mutations by using Immer. For your convenience, `useImmer` is already imported, so you need to change the `todos` state variable to use it. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { useImmer } from 'use-immer'; +import AddTodo from './AddTodo.js'; +import TaskList from './TaskList.js'; + +let nextId = 3; +const initialTodos = [ + { id: 0, title: 'Buy milk', done: true }, + { id: 1, title: 'Eat tacos', done: false }, + { id: 2, title: 'Brew tea', done: false }, +]; + +export default function TaskApp() { + const [todos, setTodos] = useState( + initialTodos + ); + + function handleAddTodo(title) { + todos.push({ + id: nextId++, + title: title, + done: false + }); + } + + function handleChangeTodo(nextTodo) { + const todo = todos.find(t => + t.id === nextTodo.id + ); + todo.title = nextTodo.title; + todo.done = nextTodo.done; + } + + function handleDeleteTodo(todoId) { + const index = todos.findIndex(t => + t.id === todoId + ); + todos.splice(index, 1); + } + + return ( + <> + <AddTodo + onAddTodo={handleAddTodo} + /> + <TaskList + todos={todos} + onChangeTodo={handleChangeTodo} + onDeleteTodo={handleDeleteTodo} + /> + </> + ); +} +``` + +```js AddTodo.js +import { useState } from 'react'; + +export default function AddTodo({ onAddTodo }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add todo" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddTodo(title); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + todos, + onChangeTodo, + onDeleteTodo +}) { + return ( + <ul> + {todos.map(todo => ( + <li key={todo.id}> + <Task + todo={todo} + onChange={onChangeTodo} + onDelete={onDeleteTodo} + /> + </li> + ))} + </ul> + ); +} + +function Task({ todo, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let todoContent; + if (isEditing) { + todoContent = ( + <> + <input + value={todo.title} + onChange={e => { + onChange({ + ...todo, + title: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + todoContent = ( + <> + {todo.title} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={todo.done} + onChange={e => { + onChange({ + ...todo, + done: e.target.checked + }); + }} + /> + {todoContent} + <button onClick={() => onDelete(todo.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +<Solution> + +With Immer, you can write code in the mutative fashion, as long as you're only mutating parts of the `draft` that Immer gives you. Here, all mutations are performed on the `draft` so the code works: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { useImmer } from 'use-immer'; +import AddTodo from './AddTodo.js'; +import TaskList from './TaskList.js'; + +let nextId = 3; +const initialTodos = [ + { id: 0, title: 'Buy milk', done: true }, + { id: 1, title: 'Eat tacos', done: false }, + { id: 2, title: 'Brew tea', done: false }, +]; + +export default function TaskApp() { + const [todos, updateTodos] = useImmer( + initialTodos + ); + + function handleAddTodo(title) { + updateTodos(draft => { + draft.push({ + id: nextId++, + title: title, + done: false + }); + }); + } + + function handleChangeTodo(nextTodo) { + updateTodos(draft => { + const todo = draft.find(t => + t.id === nextTodo.id + ); + todo.title = nextTodo.title; + todo.done = nextTodo.done; + }); + } + + function handleDeleteTodo(todoId) { + updateTodos(draft => { + const index = draft.findIndex(t => + t.id === todoId + ); + draft.splice(index, 1); + }); + } + + return ( + <> + <AddTodo + onAddTodo={handleAddTodo} + /> + <TaskList + todos={todos} + onChangeTodo={handleChangeTodo} + onDeleteTodo={handleDeleteTodo} + /> + </> + ); +} +``` + +```js AddTodo.js +import { useState } from 'react'; + +export default function AddTodo({ onAddTodo }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add todo" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddTodo(title); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + todos, + onChangeTodo, + onDeleteTodo +}) { + return ( + <ul> + {todos.map(todo => ( + <li key={todo.id}> + <Task + todo={todo} + onChange={onChangeTodo} + onDelete={onDeleteTodo} + /> + </li> + ))} + </ul> + ); +} + +function Task({ todo, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let todoContent; + if (isEditing) { + todoContent = ( + <> + <input + value={todo.title} + onChange={e => { + onChange({ + ...todo, + title: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + todoContent = ( + <> + {todo.title} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={todo.done} + onChange={e => { + onChange({ + ...todo, + done: e.target.checked + }); + }} + /> + {todoContent} + <button onClick={() => onDelete(todo.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +You can also mix and match the mutative and non-mutative approaches with Immer. + +For example, in this version `handleAddTodo` is implemented by mutating the Immer `draft`, while `handleChangeTodo` and `handleDeleteTodo` use the non-mutative `map` and `filter` methods: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { useImmer } from 'use-immer'; +import AddTodo from './AddTodo.js'; +import TaskList from './TaskList.js'; + +let nextId = 3; +const initialTodos = [ + { id: 0, title: 'Buy milk', done: true }, + { id: 1, title: 'Eat tacos', done: false }, + { id: 2, title: 'Brew tea', done: false }, +]; + +export default function TaskApp() { + const [todos, updateTodos] = useImmer( + initialTodos + ); + + function handleAddTodo(title) { + updateTodos(draft => { + draft.push({ + id: nextId++, + title: title, + done: false + }); + }); + } + + function handleChangeTodo(nextTodo) { + updateTodos(todos.map(todo => { + if (todo.id === nextTodo.id) { + return nextTodo; + } else { + return todo; + } + })); + } + + function handleDeleteTodo(todoId) { + updateTodos( + todos.filter(t => t.id !== todoId) + ); + } + + return ( + <> + <AddTodo + onAddTodo={handleAddTodo} + /> + <TaskList + todos={todos} + onChangeTodo={handleChangeTodo} + onDeleteTodo={handleDeleteTodo} + /> + </> + ); +} +``` + +```js AddTodo.js +import { useState } from 'react'; + +export default function AddTodo({ onAddTodo }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add todo" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddTodo(title); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + todos, + onChangeTodo, + onDeleteTodo +}) { + return ( + <ul> + {todos.map(todo => ( + <li key={todo.id}> + <Task + todo={todo} + onChange={onChangeTodo} + onDelete={onDeleteTodo} + /> + </li> + ))} + </ul> + ); +} + +function Task({ todo, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let todoContent; + if (isEditing) { + todoContent = ( + <> + <input + value={todo.title} + onChange={e => { + onChange({ + ...todo, + title: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + todoContent = ( + <> + {todo.title} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={todo.done} + onChange={e => { + onChange({ + ...todo, + done: e.target.checked + }); + }} + /> + {todoContent} + <button onClick={() => onDelete(todo.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +With Immer, you can pick the style that feels the most natural for each separate case. + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/updating-objects-in-state.md b/beta/src/content/learn/updating-objects-in-state.md new file mode 100644 index 000000000..1e23c8d3d --- /dev/null +++ b/beta/src/content/learn/updating-objects-in-state.md @@ -0,0 +1,1624 @@ +--- +title: Updating Objects in State +--- + +<Intro> + +State can hold any kind of JavaScript value, including objects. But you shouldn't change objects that you hold in the React state directly. Instead, when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use that copy. + +</Intro> + +<YouWillLearn> + +- How to correctly update an object in React state +- How to update a nested object without mutating it +- What immutability is, and how not to break it +- How to make object copying less repetitive with Immer + +</YouWillLearn> + +## What's a mutation? {/*whats-a-mutation*/} + +You can store any kind of JavaScript value in state. + +```js +const [x, setX] = useState(0); +``` + +So far you've been working with numbers, strings, and booleans. These kinds of JavaScript values are "immutable", meaning unchangeable or "read-only". You can trigger a re-render to _replace_ a value: + +```js +setX(5); +``` + +The `x` state changed from `0` to `5`, but the _number `0` itself_ did not change. It's not possible to make any changes to the built-in primitive values like numbers, strings, and booleans in JavaScript. + +Now consider an object in state: + +```js +const [position, setPosition] = useState({ x: 0, y: 0 }); +``` + +Technically, it is possible to change the contents of _the object itself_. **This is called a mutation:** + +```js +position.x = 5; +``` + +However, although objects in React state are technically mutable, you should treat them **as if** they were immutable--like numbers, booleans, and strings. Instead of mutating them, you should always replace them. + +## Treat state as read-only {/*treat-state-as-read-only*/} + +In other words, you should **treat any JavaScript object that you put into state as read-only.** + +This example holds an object in state to represent the current pointer position. The red dot is supposed to move when you touch or move the cursor over the preview area. But the dot stays in the initial position: + +<Sandpack> + +```js +import { useState } from 'react'; +export default function MovingDot() { + const [position, setPosition] = useState({ + x: 0, + y: 0 + }); + return ( + <div + onPointerMove={e => { + position.x = e.clientX; + position.y = e.clientY; + }} + style={{ + position: 'relative', + width: '100vw', + height: '100vh', + }}> + <div style={{ + position: 'absolute', + backgroundColor: 'red', + borderRadius: '50%', + transform: `translate(${position.x}px, ${position.y}px)`, + left: -10, + top: -10, + width: 20, + height: 20, + }} /> + </div> + ); +} +``` + +```css +body { margin: 0; padding: 0; height: 250px; } +``` + +</Sandpack> + +The problem is with this bit of code. + +```js +onPointerMove={e => { + position.x = e.clientX; + position.y = e.clientY; +}} +``` + +This code modifies the object assigned to `position` from [the previous render.](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) But without using the state setting function, React has no idea that object has changed. So React does not do anything in response. It's like trying to change the order after you've already eaten the meal. While mutating state can work in some cases, we don't recommend it. You should treat the state value you have access to in a render as read-only. + +To actually [trigger a re-render](/learn/state-as-a-snapshot#setting-state-triggers-renders) in this case, **create a *new* object and pass it to the state setting function:** + +```js +onPointerMove={e => { + setPosition({ + x: e.clientX, + y: e.clientY + }); +}} +``` + +With `setPosition`, you're telling React: + +* Replace `position` with this new object +* And render this component again + +Notice how the red dot now follows your pointer when you touch or hover over the preview area: + +<Sandpack> + +```js +import { useState } from 'react'; +export default function MovingDot() { + const [position, setPosition] = useState({ + x: 0, + y: 0 + }); + return ( + <div + onPointerMove={e => { + setPosition({ + x: e.clientX, + y: e.clientY + }); + }} + style={{ + position: 'relative', + width: '100vw', + height: '100vh', + }}> + <div style={{ + position: 'absolute', + backgroundColor: 'red', + borderRadius: '50%', + transform: `translate(${position.x}px, ${position.y}px)`, + left: -10, + top: -10, + width: 20, + height: 20, + }} /> + </div> + ); +} +``` + +```css +body { margin: 0; padding: 0; height: 250px; } +``` + +</Sandpack> + +<DeepDive> + +#### Local mutation is fine {/*local-mutation-is-fine*/} + +Code like this is a problem because it modifies an *existing* object in state: + +```js +position.x = e.clientX; +position.y = e.clientY; +``` + +But code like this is **absolutely fine** because you're mutating a fresh object you have *just created*: + +```js +const nextPosition = {}; +nextPosition.x = e.clientX; +nextPosition.y = e.clientY; +setPosition(nextPosition); +```` + +In fact, it is completely equivalent to writing this: + +```js +setPosition({ + x: e.clientX, + y: e.clientY +}); +``` + +Mutation is only a problem when you change *existing* objects that are already in state. Mutating an object you've just created is okay because *no other code references it yet.* Changing it isn't going to accidentally impact something that depends on it. This is called a "local mutation". You can even do local mutation [while rendering.](/learn/keeping-components-pure#local-mutation-your-components-little-secret) Very convenient and completely okay! + +</DeepDive> + +## Copying objects with the spread syntax {/*copying-objects-with-the-spread-syntax*/} + +In the previous example, the `position` object is always created fresh from the current cursor position. But often, you will want to include *existing* data as a part of the new object you're creating. For example, you may want to update *only one* field in a form, but keep the previous values for all other fields. + +These input fields don't work because the `onChange` handlers mutate the state: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [person, setPerson] = useState({ + firstName: 'Barbara', + lastName: 'Hepworth', + email: 'bhepworth@sculpture.com' + }); + + function handleFirstNameChange(e) { + person.firstName = e.target.value; + } + + function handleLastNameChange(e) { + person.lastName = e.target.value; + } + + function handleEmailChange(e) { + person.email = e.target.value; + } + + return ( + <> + <label> + First name: + <input + value={person.firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name: + <input + value={person.lastName} + onChange={handleLastNameChange} + /> + </label> + <label> + Email: + <input + value={person.email} + onChange={handleEmailChange} + /> + </label> + <p> + {person.firstName}{' '} + {person.lastName}{' '} + ({person.email}) + </p> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +``` + +</Sandpack> + +For example, this line mutates the state from a past render: + +```js +person.firstName = e.target.value; +``` + +The reliable way to get the behavior you're looking for is to create a new object and pass it to `setPerson`. But here, you want to also **copy the existing data into it** because only one of the fields has changed: + +```js +setPerson({ + firstName: e.target.value, // New first name from the input + lastName: person.lastName, + email: person.email +}); +``` + +You can use the `...` [object spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals) syntax so that you don't need to copy every property separately. + +```js +setPerson({ + ...person, // Copy the old fields + firstName: e.target.value // But override this one +}); +``` + +Now the form works! + +Notice how you didn't declare a separate state variable for each input field. For large forms, keeping all data grouped in an object is very convenient--as long as you update it correctly! + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [person, setPerson] = useState({ + firstName: 'Barbara', + lastName: 'Hepworth', + email: 'bhepworth@sculpture.com' + }); + + function handleFirstNameChange(e) { + setPerson({ + ...person, + firstName: e.target.value + }); + } + + function handleLastNameChange(e) { + setPerson({ + ...person, + lastName: e.target.value + }); + } + + function handleEmailChange(e) { + setPerson({ + ...person, + email: e.target.value + }); + } + + return ( + <> + <label> + First name: + <input + value={person.firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name: + <input + value={person.lastName} + onChange={handleLastNameChange} + /> + </label> + <label> + Email: + <input + value={person.email} + onChange={handleEmailChange} + /> + </label> + <p> + {person.firstName}{' '} + {person.lastName}{' '} + ({person.email}) + </p> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +``` + +</Sandpack> + +Note that the `...` spread syntax is "shallow"--it only copies things one level deep. This makes it fast, but it also means that if you want to update a nested property, you'll have to use it more than once. + +<DeepDive> + +#### Using a single event handler for multiple fields {/*using-a-single-event-handler-for-multiple-fields*/} + +You can also use the `[` and `]` braces inside your object definition to specify a property with dynamic name. Here is the same example, but with a single event handler instead of three different ones: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [person, setPerson] = useState({ + firstName: 'Barbara', + lastName: 'Hepworth', + email: 'bhepworth@sculpture.com' + }); + + function handleChange(e) { + setPerson({ + ...person, + [e.target.name]: e.target.value + }); + } + + return ( + <> + <label> + First name: + <input + name="firstName" + value={person.firstName} + onChange={handleChange} + /> + </label> + <label> + Last name: + <input + name="lastName" + value={person.lastName} + onChange={handleChange} + /> + </label> + <label> + Email: + <input + name="email" + value={person.email} + onChange={handleChange} + /> + </label> + <p> + {person.firstName}{' '} + {person.lastName}{' '} + ({person.email}) + </p> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +``` + +</Sandpack> + +Here, `e.target.name` refers to the `name` property given to the `<input>` DOM element. + +</DeepDive> + +## Updating a nested object {/*updating-a-nested-object*/} + +Consider a nested object structure like this: + +```js +const [person, setPerson] = useState({ + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } +}); +``` + +If you wanted to update `person.artwork.city`, it's clear how to do it with mutation: + +```js +person.artwork.city = 'New Delhi'; +``` + +But in React, you treat state as immutable! In order to change `city`, you would first need to produce the new `artwork` object (pre-populated with data from the previous one), and then produce the new `person` object which points at the new `artwork`: + +```js +const nextArtwork = { ...person.artwork, city: 'New Delhi' }; +const nextPerson = { ...person, artwork: nextArtwork }; +setPerson(nextPerson); +``` + +Or, written as a single function call: + +```js +setPerson({ + ...person, // Copy other fields + artwork: { // but replace the artwork + ...person.artwork, // with the same one + city: 'New Delhi' // but in New Delhi! + } +}); +``` + +This gets a bit wordy, but it works fine for many cases: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [person, setPerson] = useState({ + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } + }); + + function handleNameChange(e) { + setPerson({ + ...person, + name: e.target.value + }); + } + + function handleTitleChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + title: e.target.value + } + }); + } + + function handleCityChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + city: e.target.value + } + }); + } + + function handleImageChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + image: e.target.value + } + }); + } + + return ( + <> + <label> + Name: + <input + value={person.name} + onChange={handleNameChange} + /> + </label> + <label> + Title: + <input + value={person.artwork.title} + onChange={handleTitleChange} + /> + </label> + <label> + City: + <input + value={person.artwork.city} + onChange={handleCityChange} + /> + </label> + <label> + Image: + <input + value={person.artwork.image} + onChange={handleImageChange} + /> + </label> + <p> + <i>{person.artwork.title}</i> + {' by '} + {person.name} + <br /> + (located in {person.artwork.city}) + </p> + <img + src={person.artwork.image} + alt={person.artwork.title} + /> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +img { width: 200px; height: 200px; } +``` + +</Sandpack> + +<DeepDive> + +#### Objects are not really nested {/*objects-are-not-really-nested*/} + +An object like this appears "nested" in code: + +```js +let obj = { + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } +}; +``` + +However, "nesting" is an inaccurate way to think about how objects behave. When the code executes, there is no such thing as a "nested" object. You are really looking at two different objects: + +```js +let obj1 = { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', +}; + +let obj2 = { + name: 'Niki de Saint Phalle', + artwork: obj1 +}; +``` + +The `obj1` object is not "inside" `obj2`. For example, `obj3` could "point" at `obj1` too: + +```js +let obj1 = { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', +}; + +let obj2 = { + name: 'Niki de Saint Phalle', + artwork: obj1 +}; + +let obj3 = { + name: 'Copycat', + artwork: obj1 +}; +``` + +If you were to mutate `obj3.artwork.city`, it would affect both `obj2.artwork.city` and `obj1.city`. This is because `obj3.artwork`, `obj2.artwork`, and `obj1` are the same object. This is difficult to see when you think of objects as "nested". Instead, they are separate objects "pointing" at each other with properties. + +</DeepDive> + +### Write concise update logic with Immer {/*write-concise-update-logic-with-immer*/} + +If your state is deeply nested, you might want to consider [flattening it.](/learn/choosing-the-state-structure#avoid-deeply-nested-state) But, if you don't want to change your state structure, you might prefer a shortcut to nested spreads. [Immer](https://github.com/immerjs/use-immer) is a popular library that lets you write using the convenient but mutating syntax and takes care of producing the copies for you. With Immer, the code you write looks like you are "breaking the rules" and mutating an object: + +```js +updatePerson(draft => { + draft.artwork.city = 'Lagos'; +}); +``` + +But unlike a regular mutation, it doesn't overwrite the past state! + +<DeepDive> + +#### How does Immer work? {/*how-does-immer-work*/} + +The `draft` provided by Immer is a special type of object, called a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), that "records" what you do with it. This is why you can mutate it freely as much as you like! Under the hood, Immer figures out which parts of the `draft` have been changed, and produces a completely new object that contains your edits. + +</DeepDive> + +To try Immer: + +1. Run `npm install use-immer` to add Immer as a dependency +2. Then replace `import { useState } from 'react'` with `import { useImmer } from 'use-immer'` + +Here is the above example converted to Immer: + +<Sandpack> + +```js +import { useImmer } from 'use-immer'; + +export default function Form() { + const [person, updatePerson] = useImmer({ + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } + }); + + function handleNameChange(e) { + updatePerson(draft => { + draft.name = e.target.value; + }); + } + + function handleTitleChange(e) { + updatePerson(draft => { + draft.artwork.title = e.target.value; + }); + } + + function handleCityChange(e) { + updatePerson(draft => { + draft.artwork.city = e.target.value; + }); + } + + function handleImageChange(e) { + updatePerson(draft => { + draft.artwork.image = e.target.value; + }); + } + + return ( + <> + <label> + Name: + <input + value={person.name} + onChange={handleNameChange} + /> + </label> + <label> + Title: + <input + value={person.artwork.title} + onChange={handleTitleChange} + /> + </label> + <label> + City: + <input + value={person.artwork.city} + onChange={handleCityChange} + /> + </label> + <label> + Image: + <input + value={person.artwork.image} + onChange={handleImageChange} + /> + </label> + <p> + <i>{person.artwork.title}</i> + {' by '} + {person.name} + <br /> + (located in {person.artwork.city}) + </p> + <img + src={person.artwork.image} + alt={person.artwork.title} + /> + </> + ); +} +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +img { width: 200px; height: 200px; } +``` + +</Sandpack> + +Notice how much more concise the event handlers have become. You can mix and match `useState` and `useImmer` in a single component as much as you like. Immer is a great way to keep the update handlers concise, especially if there's nesting in your state, and copying objects leads to repetitive code. + +<DeepDive> + +#### Why is mutating state not recommended in React? {/*why-is-mutating-state-not-recommended-in-react*/} + +There are a few reasons: + +* **Debugging:** If you use `console.log` and don't mutate state, your past logs won't get clobbered by the more recent state changes. So you can clearly see how state has changed between renders. +* **Optimizations:** Common React [optimization strategies](/reference/react/memo) rely on skipping work if previous props or state are the same as the next ones. If you never mutate state, it is very fast to check whether there were any changes. If `prevObj === obj`, you can be sure that nothing could have changed inside of it. +* **New Features:** The new React features we're building rely on state being [treated like a snapshot.](/learn/state-as-a-snapshot) If you're mutating past versions of state, that may prevent you from using the new features. +* **Requirement Changes:** Some application features, like implementing Undo/Redo, showing a history of changes, or letting the user reset a form to earlier values, are easier to do when nothing is mutated. This is because you can keep past copies of state in memory, and reuse them when appropriate. If you start with a mutative approach, features like this can be difficult to add later on. +* **Simpler Implementation:** Because React does not rely on mutation, it does not need to do anything special with your objects. It does not need to hijack their properties, always wrap them into Proxies, or do other work at initialization as many "reactive" solutions do. This is also why React lets you put any object into state--no matter how large--without additional performance or correctness pitfalls. + +In practice, you can often "get away" with mutating state in React, but we strongly advise you not to do that so that you can use new React features developed with this approach in mind. Future contributors and perhaps even your future self will thank you! + +</DeepDive> + +<Recap> + +* Treat all state in React as immutable. +* When you store objects in state, mutating them will not trigger renders and will change the state in previous render "snapshots". +* Instead of mutating an object, create a *new* version of it, and trigger a re-render by setting state to it. +* You can use the `{...obj, something: 'newValue'}` object spread syntax to create copies of objects. +* Spread syntax is shallow: it only copies one level deep. +* To update a nested object, you need to create copies all the way up from the place you're updating. +* To reduce repetitive copying code, use Immer. + +</Recap> + + + +<Challenges> + +#### Fix incorrect state updates {/*fix-incorrect-state-updates*/} + +This form has a few bugs. Click the button that increases the score a few times. Notice that it does not increase. Then edit the first name, and notice that the score has suddenly "caught up" with your changes. Finally, edit the last name, and notice that the score has disappeared completely. + +Your task is to fix all of these bugs. As you fix them, explain why each of them happens. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Scoreboard() { + const [player, setPlayer] = useState({ + firstName: 'Ranjani', + lastName: 'Shettar', + score: 10, + }); + + function handlePlusClick() { + player.score++; + } + + function handleFirstNameChange(e) { + setPlayer({ + ...player, + firstName: e.target.value, + }); + } + + function handleLastNameChange(e) { + setPlayer({ + lastName: e.target.value + }); + } + + return ( + <> + <label> + Score: <b>{player.score}</b> + {' '} + <button onClick={handlePlusClick}> + +1 + </button> + </label> + <label> + First name: + <input + value={player.firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name: + <input + value={player.lastName} + onChange={handleLastNameChange} + /> + </label> + </> + ); +} +``` + +```css +label { display: block; margin-bottom: 10px; } +input { margin-left: 5px; margin-bottom: 5px; } +``` + +</Sandpack> + +<Solution> + +Here is a version with both bugs fixed: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Scoreboard() { + const [player, setPlayer] = useState({ + firstName: 'Ranjani', + lastName: 'Shettar', + score: 10, + }); + + function handlePlusClick() { + setPlayer({ + ...player, + score: player.score + 1, + }); + } + + function handleFirstNameChange(e) { + setPlayer({ + ...player, + firstName: e.target.value, + }); + } + + function handleLastNameChange(e) { + setPlayer({ + ...player, + lastName: e.target.value + }); + } + + return ( + <> + <label> + Score: <b>{player.score}</b> + {' '} + <button onClick={handlePlusClick}> + +1 + </button> + </label> + <label> + First name: + <input + value={player.firstName} + onChange={handleFirstNameChange} + /> + </label> + <label> + Last name: + <input + value={player.lastName} + onChange={handleLastNameChange} + /> + </label> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +``` + +</Sandpack> + +The problem with `handlePlusClick` was that it mutated the `player` object. As a result, React did not know that there's a reason to re-render, and did not update the score on the screen. This is why, when you edited the first name, the state got updated, triggering a re-render which _also_ updated the score on the screen. + +The problem with `handleLastNameChange` was that it did not copy the existing `...player` fields into the new object. This is why the score got lost after you edited the last name. + +</Solution> + +#### Find and fix the mutation {/*find-and-fix-the-mutation*/} + +There is a draggable box on a static background. You can change the box's color using the select input. + +But there is a bug. If you move the box first, and then change its color, the background (which isn't supposed to move!) will "jump" to the box position. But this should not happen: the `Background`'s `position` prop is set to `initialPosition`, which is `{ x: 0, y: 0 }`. Why is the background moving after the color change? + +Find the bug and fix it. + +<Hint> + +If something unexpected changes, there is a mutation. Find the mutation in `App.js` and fix it. + +</Hint> + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Background from './Background.js'; +import Box from './Box.js'; + +const initialPosition = { + x: 0, + y: 0 +}; + +export default function Canvas() { + const [shape, setShape] = useState({ + color: 'orange', + position: initialPosition + }); + + function handleMove(dx, dy) { + shape.position.x += dx; + shape.position.y += dy; + } + + function handleColorChange(e) { + setShape({ + ...shape, + color: e.target.value + }); + } + + return ( + <> + <select + value={shape.color} + onChange={handleColorChange} + > + <option value="orange">orange</option> + <option value="lightpink">lightpink</option> + <option value="aliceblue">aliceblue</option> + </select> + <Background + position={initialPosition} + /> + <Box + color={shape.color} + position={shape.position} + onMove={handleMove} + > + Drag me! + </Box> + </> + ); +} +``` + +```js Box.js +import { useState } from 'react'; + +export default function Box({ + children, + color, + position, + onMove +}) { + const [ + lastCoordinates, + setLastCoordinates + ] = useState(null); + + function handlePointerDown(e) { + e.target.setPointerCapture(e.pointerId); + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + } + + function handlePointerMove(e) { + if (lastCoordinates) { + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + const dx = e.clientX - lastCoordinates.x; + const dy = e.clientY - lastCoordinates.y; + onMove(dx, dy); + } + } + + function handlePointerUp(e) { + setLastCoordinates(null); + } + + return ( + <div + onPointerDown={handlePointerDown} + onPointerMove={handlePointerMove} + onPointerUp={handlePointerUp} + style={{ + width: 100, + height: 100, + cursor: 'grab', + backgroundColor: color, + position: 'absolute', + border: '1px solid black', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + }} + >{children}</div> + ); +} +``` + +```js Background.js +export default function Background({ + position +}) { + return ( + <div style={{ + position: 'absolute', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + width: 250, + height: 250, + backgroundColor: 'rgba(200, 200, 0, 0.2)', + }} /> + ); +}; +``` + +```css +body { height: 280px; } +select { margin-bottom: 10px; } +``` + +</Sandpack> + +<Solution> + +The problem was in the mutation inside `handleMove`. It mutated `shape.position`, but that's the same object that `initialPosition` points at. This is why both the shape and the background move. (It's a mutation, so the change doesn't reflect on the screen until an unrelated update--the color change--triggers a re-render.) + +The fix is to remove the mutation from `handleMove`, and use the spread syntax to copy the shape. Note that `+=` is a mutation, so you need to rewrite it to use a regular `+` operation. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import Background from './Background.js'; +import Box from './Box.js'; + +const initialPosition = { + x: 0, + y: 0 +}; + +export default function Canvas() { + const [shape, setShape] = useState({ + color: 'orange', + position: initialPosition + }); + + function handleMove(dx, dy) { + setShape({ + ...shape, + position: { + x: shape.position.x + dx, + y: shape.position.y + dy, + } + }); + } + + function handleColorChange(e) { + setShape({ + ...shape, + color: e.target.value + }); + } + + return ( + <> + <select + value={shape.color} + onChange={handleColorChange} + > + <option value="orange">orange</option> + <option value="lightpink">lightpink</option> + <option value="aliceblue">aliceblue</option> + </select> + <Background + position={initialPosition} + /> + <Box + color={shape.color} + position={shape.position} + onMove={handleMove} + > + Drag me! + </Box> + </> + ); +} +``` + +```js Box.js +import { useState } from 'react'; + +export default function Box({ + children, + color, + position, + onMove +}) { + const [ + lastCoordinates, + setLastCoordinates + ] = useState(null); + + function handlePointerDown(e) { + e.target.setPointerCapture(e.pointerId); + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + } + + function handlePointerMove(e) { + if (lastCoordinates) { + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + const dx = e.clientX - lastCoordinates.x; + const dy = e.clientY - lastCoordinates.y; + onMove(dx, dy); + } + } + + function handlePointerUp(e) { + setLastCoordinates(null); + } + + return ( + <div + onPointerDown={handlePointerDown} + onPointerMove={handlePointerMove} + onPointerUp={handlePointerUp} + style={{ + width: 100, + height: 100, + cursor: 'grab', + backgroundColor: color, + position: 'absolute', + border: '1px solid black', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + }} + >{children}</div> + ); +} +``` + +```js Background.js +export default function Background({ + position +}) { + return ( + <div style={{ + position: 'absolute', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + width: 250, + height: 250, + backgroundColor: 'rgba(200, 200, 0, 0.2)', + }} /> + ); +}; +``` + +```css +body { height: 280px; } +select { margin-bottom: 10px; } +``` + +</Sandpack> + +</Solution> + +#### Update an object with Immer {/*update-an-object-with-immer*/} + +This is the same buggy example as in the previous challenge. This time, fix the mutation by using Immer. For your convenience, `useImmer` is already imported, so you need to change the `shape` state variable to use it. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { useImmer } from 'use-immer'; +import Background from './Background.js'; +import Box from './Box.js'; + +const initialPosition = { + x: 0, + y: 0 +}; + +export default function Canvas() { + const [shape, setShape] = useState({ + color: 'orange', + position: initialPosition + }); + + function handleMove(dx, dy) { + shape.position.x += dx; + shape.position.y += dy; + } + + function handleColorChange(e) { + setShape({ + ...shape, + color: e.target.value + }); + } + + return ( + <> + <select + value={shape.color} + onChange={handleColorChange} + > + <option value="orange">orange</option> + <option value="lightpink">lightpink</option> + <option value="aliceblue">aliceblue</option> + </select> + <Background + position={initialPosition} + /> + <Box + color={shape.color} + position={shape.position} + onMove={handleMove} + > + Drag me! + </Box> + </> + ); +} +``` + +```js Box.js +import { useState } from 'react'; + +export default function Box({ + children, + color, + position, + onMove +}) { + const [ + lastCoordinates, + setLastCoordinates + ] = useState(null); + + function handlePointerDown(e) { + e.target.setPointerCapture(e.pointerId); + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + } + + function handlePointerMove(e) { + if (lastCoordinates) { + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + const dx = e.clientX - lastCoordinates.x; + const dy = e.clientY - lastCoordinates.y; + onMove(dx, dy); + } + } + + function handlePointerUp(e) { + setLastCoordinates(null); + } + + return ( + <div + onPointerDown={handlePointerDown} + onPointerMove={handlePointerMove} + onPointerUp={handlePointerUp} + style={{ + width: 100, + height: 100, + cursor: 'grab', + backgroundColor: color, + position: 'absolute', + border: '1px solid black', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + }} + >{children}</div> + ); +} +``` + +```js Background.js +export default function Background({ + position +}) { + return ( + <div style={{ + position: 'absolute', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + width: 250, + height: 250, + backgroundColor: 'rgba(200, 200, 0, 0.2)', + }} /> + ); +}; +``` + +```css +body { height: 280px; } +select { margin-bottom: 10px; } +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +<Solution> + +This is the solution rewritten with Immer. Notice how the event handlers are written in a mutating fashion, but the bug does not occur. This is because under the hood, Immer never mutates the existing objects. + +<Sandpack> + +```js App.js +import { useImmer } from 'use-immer'; +import Background from './Background.js'; +import Box from './Box.js'; + +const initialPosition = { + x: 0, + y: 0 +}; + +export default function Canvas() { + const [shape, updateShape] = useImmer({ + color: 'orange', + position: initialPosition + }); + + function handleMove(dx, dy) { + updateShape(draft => { + draft.position.x += dx; + draft.position.y += dy; + }); + } + + function handleColorChange(e) { + updateShape(draft => { + draft.color = e.target.value; + }); + } + + return ( + <> + <select + value={shape.color} + onChange={handleColorChange} + > + <option value="orange">orange</option> + <option value="lightpink">lightpink</option> + <option value="aliceblue">aliceblue</option> + </select> + <Background + position={initialPosition} + /> + <Box + color={shape.color} + position={shape.position} + onMove={handleMove} + > + Drag me! + </Box> + </> + ); +} +``` + +```js Box.js +import { useState } from 'react'; + +export default function Box({ + children, + color, + position, + onMove +}) { + const [ + lastCoordinates, + setLastCoordinates + ] = useState(null); + + function handlePointerDown(e) { + e.target.setPointerCapture(e.pointerId); + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + } + + function handlePointerMove(e) { + if (lastCoordinates) { + setLastCoordinates({ + x: e.clientX, + y: e.clientY, + }); + const dx = e.clientX - lastCoordinates.x; + const dy = e.clientY - lastCoordinates.y; + onMove(dx, dy); + } + } + + function handlePointerUp(e) { + setLastCoordinates(null); + } + + return ( + <div + onPointerDown={handlePointerDown} + onPointerMove={handlePointerMove} + onPointerUp={handlePointerUp} + style={{ + width: 100, + height: 100, + cursor: 'grab', + backgroundColor: color, + position: 'absolute', + border: '1px solid black', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + }} + >{children}</div> + ); +} +``` + +```js Background.js +export default function Background({ + position +}) { + return ( + <div style={{ + position: 'absolute', + transform: `translate( + ${position.x}px, + ${position.y}px + )`, + width: 250, + height: 250, + backgroundColor: 'rgba(200, 200, 0, 0.2)', + }} /> + ); +}; +``` + +```css +body { height: 280px; } +select { margin-bottom: 10px; } +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/writing-markup-with-jsx.md b/beta/src/content/learn/writing-markup-with-jsx.md new file mode 100644 index 000000000..335f192af --- /dev/null +++ b/beta/src/content/learn/writing-markup-with-jsx.md @@ -0,0 +1,353 @@ +--- +title: Writing Markup with JSX +--- + +<Intro> + +*JSX* is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file. Although there are other ways to write components, most React developers prefer the conciseness of JSX, and most codebases use it. + +</Intro> + +<YouWillLearn> + +* Why React mixes markup with rendering logic +* How JSX is different from HTML +* How to display information with JSX + +</YouWillLearn> + +## JSX: Putting markup into JavaScript {/*jsx-putting-markup-into-javascript*/} + +The Web has been built on HTML, CSS, and JavaScript. For many years, web developers kept content in HTML, design in CSS, and logic in JavaScript—often in separate files! Content was marked up inside HTML while the page's logic lived separately in JavaScript: + +<DiagramGroup> + +<Diagram name="writing_jsx_html" height={237} width={325} alt="HTML markup with purple background and a div with two child tags: p and form. "> + +HTML + +</Diagram> + +<Diagram name="writing_jsx_js" height={237} width={325} alt="Three JavaScript handlers with yellow background: onSubmit, onLogin, and onClick."> + +JavaScript + +</Diagram> + +</DiagramGroup> + +But as the Web became more interactive, logic increasingly determined content. JavaScript was in charge of the HTML! This is why **in React, rendering logic and markup live together in the same place—components.** + +<DiagramGroup> + +<Diagram name="writing_jsx_sidebar" height={330} width={325} alt="React component with HTML and JavaScript from previous examples mixed. Function name is Sidebar which calls the function isLoggedIn, highlighted in yellow. Nested inside the function highlighted in purple is the p tag from before, and a Form tag referencing the component shown in the next diagram."> + +`Sidebar.js` React component + +</Diagram> + +<Diagram name="writing_jsx_form" height={330} width={325} alt="React component with HTML and JavaScript from previous examples mixed. Function name is Form containing two handlers onClick and onSubmit highlighted in yellow. Following the handlers is HTML highlighted in purple. The HTML contains a form element with a nested input element, each with an onClick prop."> + +`Form.js` React component + +</Diagram> + +</DiagramGroup> + +Keeping a button's rendering logic and markup together ensures that they stay in sync with each other on every edit. Conversely, details that are unrelated, such as the button's markup and a sidebar's markup, are isolated from each other, making it safer to change either of them on their own. + +Each React component is a JavaScript function that may contain some markup that React renders into the browser. React components use a syntax extension called JSX to represent that markup. JSX looks a lot like HTML, but it is a bit stricter and can display dynamic information. The best way to understand this is to convert some HTML markup to JSX markup. + +<Note> + +JSX and React are two separate things. They're often used together, but you *can* [use them independently](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#whats-a-jsx-transform) of each other. JSX is a syntax extension, while React is a JavaScript library. + +</Note> + +## Converting HTML to JSX {/*converting-html-to-jsx*/} + +Suppose that you have some (perfectly valid) HTML: + +```html +<h1>Hedy Lamarr's Todos</h1> +<img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + class="photo" +> +<ul> + <li>Invent new traffic lights + <li>Rehearse a movie scene + <li>Improve the spectrum technology +</ul> +``` + +And you want to put it into your component: + +```js +export default function TodoList() { + return ( + // ??? + ) +} +``` + +If you copy and paste it as is, it will not work: + + +<Sandpack> + +```js +export default function TodoList() { + return ( + // This doesn't quite work! + <h1>Hedy Lamarr's Todos</h1> + <img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + class="photo" + > + <ul> + <li>Invent new traffic lights + <li>Rehearse a movie scene + <li>Improve the spectrum technology + </ul> + ); +} +``` + +```css +img { height: 90px } +``` + +</Sandpack> + +This is because JSX is stricter and has a few more rules than HTML! If you read the error messages above, they'll guide you to fix the markup, or you can follow the guide below. + +<Note> + +Most of the times, React's on-screen error messages will help you find where the problem is. Give them a read if you get stuck! + +</Note> + +## The Rules of JSX {/*the-rules-of-jsx*/} + +### 1. Return a single root element {/*1-return-a-single-root-element*/} + +To return multiple elements from a component, **wrap them with a single parent tag.** + +For example, you can use a `<div>`: + +```js {1,11} +<div> + <h1>Hedy Lamarr's Todos</h1> + <img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + class="photo" + > + <ul> + ... + </ul> +</div> +``` + + +If you don't want to add an extra `<div>` to your markup, you can write `<>` and `</>` instead: + +```js {1,11} +<> + <h1>Hedy Lamarr's Todos</h1> + <img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + class="photo" + > + <ul> + ... + </ul> +</> +``` + +This empty tag is called a *[Fragment.](/reference/react/Fragment)* Fragments let you group things without leaving any trace in the browser HTML tree. + +<DeepDive> + +#### Why do multiple JSX tags need to be wrapped? {/*why-do-multiple-jsx-tags-need-to-be-wrapped*/} + +JSX looks like HTML, but under the hood it is transformed into plain JavaScript objects. You can't return two objects from a function without wrapping them into an array. This explains why you also can't return two JSX tags without wrapping them into another tag or a Fragment. + +</DeepDive> + +### 2. Close all the tags {/*2-close-all-the-tags*/} + +JSX requires tags to be explicitly closed: self-closing tags like `<img>` must become `<img />`, and wrapping tags like `<li>oranges` must be written as `<li>oranges</li>`. + +This is how Hedy Lamarr's image and list items look closed: + +```js {2-6,8-10} +<> + <img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + class="photo" + /> + <ul> + <li>Invent new traffic lights</li> + <li>Rehearse a movie scene</li> + <li>Improve the spectrum technology</li> + </ul> +</> +``` + +### 3. camelCase <s>all</s> most of the things! {/*3-camelcase-salls-most-of-the-things*/} + +JSX turns into JavaScript and attributes written in JSX become keys of JavaScript objects. In your own components, you will often want to read those attributes into variables. But JavaScript has limitations on variable names. For example, their names can't contain dashes or be reserved words like `class`. + +This is why, in React, many HTML and SVG attributes are written in camelCase. For example, instead of `stroke-width` you use `strokeWidth`. Since `class` is a reserved word, in React you write `className` instead, named after the [corresponding DOM property](https://developer.mozilla.org/en-US/docs/Web/API/Element/className): + +```js {4} +<img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + className="photo" +/> +``` + +You can [find all these attributes in the list of DOM component props.](/reference/react-dom/components/common) If you get one wrong, don't worry—React will print a message with a possible correction to the [browser console.](https://developer.mozilla.org/docs/Tools/Browser_Console) + +<Pitfall> + +For historical reasons, [`aria-*`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA) and [`data-*`](https://developer.mozilla.org/docs/Learn/HTML/Howto/Use_data_attributes) attributes are written as in HTML with dashes. + +</Pitfall> + +### Pro-tip: Use a JSX Converter {/*pro-tip-use-a-jsx-converter*/} + +Converting all these attributes in existing markup can be tedious! We recommend using a [converter](https://transform.tools/html-to-jsx) to translate your existing HTML and SVG to JSX. Converters are very useful in practice, but it's still worth understanding what is going on so that you can comfortably write JSX on your own. + +Here is your final result: + +<Sandpack> + +```js +export default function TodoList() { + return ( + <> + <h1>Hedy Lamarr's Todos</h1> + <img + src="https://i.imgur.com/yXOvdOSs.jpg" + alt="Hedy Lamarr" + className="photo" + /> + <ul> + <li>Invent new traffic lights</li> + <li>Rehearse a movie scene</li> + <li>Improve the spectrum technology</li> + </ul> + </> + ); +} +``` + +```css +img { height: 90px } +``` + +</Sandpack> + +<Recap> + +Now you know why JSX exists and how to use it in components: + +* React components group rendering logic together with markup because they are related. +* JSX is similar to HTML, with a few differences. You can use a [converter](https://transform.tools/html-to-jsx) if you need to. +* Error messages will often point you in the right direction to fixing your markup. + +</Recap> + + + +<Challenges> + +#### Convert some HTML to JSX {/*convert-some-html-to-jsx*/} + +This HTML was pasted into a component, but it's not valid JSX. Fix it: + +<Sandpack> + +```js +export default function Bio() { + return ( + <div class="intro"> + <h1>Welcome to my website!</h1> + </div> + <p class="summary"> + You can find my thoughts here. + <br><br> + <b>And <i>pictures</b></i> of scientists! + </p> + ); +} +``` + +```css +.intro { + background-image: linear-gradient(to left, violet, indigo, blue, green, yellow, orange, red); + background-clip: text; + color: transparent; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.summary { + padding: 20px; + border: 10px solid gold; +} +``` + +</Sandpack> + +Whether to do it by hand or using the converter is up to you! + +<Solution> + +<Sandpack> + +```js +export default function Bio() { + return ( + <div> + <div className="intro"> + <h1>Welcome to my website!</h1> + </div> + <p className="summary"> + You can find my thoughts here. + <br /><br /> + <b>And <i>pictures</i></b> of scientists! + </p> + </div> + ); +} +``` + +```css +.intro { + background-image: linear-gradient(to left, violet, indigo, blue, green, yellow, orange, red); + background-clip: text; + color: transparent; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.summary { + padding: 20px; + border: 10px solid gold; +} +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/you-might-not-need-an-effect.md b/beta/src/content/learn/you-might-not-need-an-effect.md new file mode 100644 index 000000000..6b4f75931 --- /dev/null +++ b/beta/src/content/learn/you-might-not-need-an-effect.md @@ -0,0 +1,1730 @@ +--- +title: 'You Might Not Need an Effect' +--- + +<Intro> + +Effects are an escape hatch from the React paradigm. They let you "step outside" of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM. If there is no external system involved (for example, if you want to update a component's state when some props or state change), you shouldn't need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone. + +</Intro> + +<YouWillLearn> + +* Why and how to remove unnecessary Effects from your components +* How to cache expensive computations without Effects +* How to reset and adjust component state without Effects +* How to share logic between event handlers +* Which logic should be moved to event handlers +* How to notify parent components about changes + +</YouWillLearn> + +## How to remove unnecessary Effects {/*how-to-remove-unnecessary-effects*/} + +There are two common cases in which you don't need Effects: + +* **You don't need Effects to transform data for rendering.** For example, let's say you want to filter a list before displaying it. You might feel tempted to write an Effect that updates a state variable when the list changes. However, this is inefficient. When you update your component's state, React will first call your component functions to calculate what should be on the screen. Then React will ["commit"](/learn/render-and-commit) these changes to the DOM, updating the screen. Then React will run your Effects. If your Effect *also* immediately updates the state, this restarts the whole process from scratch! To avoid the unnecessary render passes, transform all the data at the top level of your components. That code will automatically re-run whenever your props or state change. +* **You don't need Effects to handle user events.** For example, let's say you want to send an `/api/buy` POST request and show a notification when the user buys a product. In the Buy button click event handler, you know exactly what happened. By the time an Effect runs, you don't know *what* the user did (for example, which button was clicked). This is why you'll usually handle user events in the corresponding event handlers. + +You *do* need Effects to [synchronize](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events) with external systems. For example, you can write an Effect that keeps a jQuery widget synchronized with the React state. You can also fetch data with Effects: for example, you can synchronize the search results with the current search query. Keep in mind that modern [frameworks](/learn/start-a-new-react-project#building-with-a-full-featured-framework) provide more efficient built-in data fetching mechanisms than writing Effects directly in your components. + +To help you gain the right intuition, let's look at some common concrete examples! + +### Updating state based on props or state {/*updating-state-based-on-props-or-state*/} + +Suppose you have a component with two state variables: `firstName` and `lastName`. You want to calculate a `fullName` from them by concatenating them. Moreover, you'd like `fullName` to update whenever `firstName` or `lastName` change. Your first instinct might be to add a `fullName` state variable and update it in an Effect: + +```js {5-9} +function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(firstName + ' ' + lastName); + }, [firstName, lastName]); + // ... +} +``` + +This is more complicated than necessary. It is inefficient too: it does an entire render pass with a stale value for `fullName`, then immediately re-renders with the updated value. Remove both the state variable and the Effect: + +```js {4-5} +function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + // ✅ Good: calculated during rendering + const fullName = firstName + ' ' + lastName; + // ... +} +``` + +**When something can be calculated from the existing props or state, [don't put it in state.](/learn/choosing-the-state-structure#avoid-redundant-state) Instead, calculate it during rendering.** This makes your code faster (you avoid the extra "cascading" updates), simpler (you remove some code), and less error-prone (you avoid bugs caused by different state variables getting out of sync with each other). If this approach feels new to you, [Thinking in React](/learn/thinking-in-react#step-3-find-the-minimal-but-complete-representation-of-ui-state) has some guidance on what should go into state. + +### Caching expensive calculations {/*caching-expensive-calculations*/} + +This component computes `visibleTodos` by taking the `todos` it receives by props and filtering them according to the `filter` prop. You might feel tempted to store the result in a state variable and update it in an Effect: + +```js {4-8} +function TodoList({ todos, filter }) { + const [newTodo, setNewTodo] = useState(''); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [visibleTodos, setVisibleTodos] = useState([]); + useEffect(() => { + setVisibleTodos(getFilteredTodos(todos, filter)); + }, [todos, filter]); + + // ... +} +``` + +Like in the earlier example, this is both unnecessary and inefficient. First, remove the state and the Effect: + +```js {3-4} +function TodoList({ todos, filter }) { + const [newTodo, setNewTodo] = useState(''); + // ✅ This is fine if getFilteredTodos() is not slow. + const visibleTodos = getFilteredTodos(todos, filter); + // ... +} +``` + +In many cases, this code is fine! But maybe `getFilteredTodos()` is slow or you have a lot of `todos`. In that case you don't want to recalculate `getFilteredTodos()` if some unrelated state variable like `newTodo` has changed. + +You can cache (or ["memoize"](https://en.wikipedia.org/wiki/Memoization)) an expensive calculation by wrapping it in a [`useMemo`](/reference/react/useMemo) Hook: + +```js {5-8} +import { useMemo, useState } from 'react'; + +function TodoList({ todos, filter }) { + const [newTodo, setNewTodo] = useState(''); + const visibleTodos = useMemo(() => { + // ✅ Does not re-run unless todos or filter change + return getFilteredTodos(todos, filter); + }, [todos, filter]); + // ... +} +``` + +Or, written as a single line: + +```js {5-6} +import { useMemo, useState } from 'react'; + +function TodoList({ todos, filter }) { + const [newTodo, setNewTodo] = useState(''); + // ✅ Does not re-run getFilteredTodos() unless todos or filter change + const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]); + // ... +} +``` + +**This tells React that you don't want the inner function to re-run unless either `todos` or `filter` have changed.** React will remember the return value of `getFilteredTodos()` during the initial render. During the next renders, it will check if `todos` or `filter` are different. If they're the same as last time, `useMemo` will return the last result it has stored. But if they are different, React will call the wrapped function again (and store _that_ result instead). + +The function you wrap in [`useMemo`](/reference/react/useMemo) runs during rendering, so this only works for [pure calculations.](/learn/keeping-components-pure) + +<DeepDive> + +#### How to tell if a calculation is expensive? {/*how-to-tell-if-a-calculation-is-expensive*/} + +In general, unless you're creating or looping over thousands of objects, it's probably not expensive. If you want to get more confidence, you can add a console log to measure the time spent in a piece of code: + +```js {1,3} +console.time('filter array'); +const visibleTodos = getFilteredTodos(todos, filter); +console.timeEnd('filter array'); +``` + +Perform the interaction you're measuring (for example, typing into the input). You will then see logs like `filter array: 0.15ms` in your console. If the overall logged time adds up to a significant amount (say, `1ms` or more), it might make sense to memoize that calculation. As an experiment, you can then wrap the calculation in `useMemo` to verify whether the total logged time has decreased for that interaction or not: + +```js +console.time('filter array'); +const visibleTodos = useMemo(() => { + return getFilteredTodos(todos, filter); // Skipped if todos and filter haven't changed +}, [todos, filter]); +console.timeEnd('filter array'); +``` + +`useMemo` won't make the *first* render faster. It only helps you skip unnecessary work on updates. + +Keep in mind that your machine is probably faster than your users' so it's a good idea to test the performance with an artificial slowdown. For example, Chrome offers a [CPU Throttling](https://developer.chrome.com/blog/new-in-devtools-61/#throttling) option for this. + +Also note that measuring performance in development will not give you the most accurate results. (For example, when [Strict Mode](/reference/react/StrictMode) is on, you will see each component render twice rather than once.) To get the most accurate timings, build your app for production and test it on a device like your users have. + +</DeepDive> + +### Resetting all state when a prop changes {/*resetting-all-state-when-a-prop-changes*/} + +This `ProfilePage` component receives a `userId` prop. The page contains a comment input, and you use a `comment` state variable to hold its value. One day, you notice a problem: when you navigate from one profile to another, the `comment` state does not get reset. As a result, it's easy to accidentally post a comment on a wrong user's profile. To fix the issue, you want to clear out the `comment` state variable whenever the `userId` changes: + +```js {4-7} +export default function ProfilePage({ userId }) { + const [comment, setComment] = useState(''); + + // 🔴 Avoid: Resetting state on prop change in an Effect + useEffect(() => { + setComment(''); + }, [userId]); + // ... +} +``` + +This is inefficient because `ProfilePage` and its children will first render with the stale value, and then render again. It is also complicated because you'd need to do this in *every* component that has some state inside `ProfilePage`. For example, if the comment UI is nested, you'd want to clear out nested comment state too. + +Instead, you can tell React that each user's profile is conceptually a _different_ profile by giving it an explicit key. Split your component in two and pass a `key` attribute from the outer component to the inner one: + +```js {5,11-12} +export default function ProfilePage({ userId }) { + return ( + <Profile + userId={userId} + key={userId} + /> + ); +} + +function Profile({ userId }) { + // ✅ This and any other state below will reset on key change automatically + const [comment, setComment] = useState(''); + // ... +} +``` + +Normally, React preserves the state when the same component is rendered in the same spot. **By passing `userId` as a `key` to the `Profile` component, you're asking React to treat two `Profile` components with different `userId` as two different components that should not share any state.** Whenever the key (which you've set to `userId`) changes, React will recreate the DOM and [reset the state](/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key) of the `Profile` component and all of its children. As a result, the `comment` field will clear out automatically when navigating between profiles. + +Note that in this example, only the outer `ProfilePage` component is exported and visible to other files in the project. Components rendering `ProfilePage` don't need to pass the key to it: they pass `userId` as a regular prop. The fact `ProfilePage` passes it as a `key` to the inner `Profile` component is an implementation detail. + +### Adjusting some state when a prop changes {/*adjusting-some-state-when-a-prop-changes*/} + +Sometimes, you might want to reset or adjust a part of the state on a prop change, but not all of it. + +This `List` component receives a list of `items` as a prop, and maintains the selected item in the `selection` state variable. You want to reset the `selection` to `null` whenever the `items` prop receives a different array: + +```js {5-8} +function List({ items }) { + const [isReverse, setIsReverse] = useState(false); + const [selection, setSelection] = useState(null); + + // 🔴 Avoid: Adjusting state on prop change in an Effect + useEffect(() => { + setSelection(null); + }, [items]); + // ... +} +``` + +This, too, is not ideal. Every time the `items` change, the `List` and its child components will render with a stale `selection` value at first. Then React will update the DOM and run the Effects. Finally, the `setSelection(null)` call will cause another re-render of the `List` and its child components, restarting this whole process again. + +Start by deleting the Effect. Instead, adjust the state directly during rendering: + +```js {5-11} +function List({ items }) { + const [isReverse, setIsReverse] = useState(false); + const [selection, setSelection] = useState(null); + + // Better: Adjust the state while rendering + const [prevItems, setPrevItems] = useState(items); + if (items !== prevItems) { + setPrevItems(items); + setSelection(null); + } + // ... +} +``` + +[Storing information from previous renders](/reference/react/useState#storing-information-from-previous-renders) like this can be hard to understand, but it’s better than updating the same state in an Effect. In the above example, `setSelection` is called directly during a render. React will re-render the `List` *immediately* after it exits with a `return` statement. By that point, React hasn't rendered the `List` children or updated the DOM yet, so this lets the `List` children skip rendering the stale `selection` value. + +When you update a component during rendering, React throws away the returned JSX and immediately retries rendering. To avoid very slow cascading retries, React only lets you update the *same* component's state during a render. If you update another component's state during a render, you'll see an error. A condition like `items !== prevItems` is necessary to avoid loops. You may adjust state like this, but any other side effects (like changing the DOM or setting a timeout) should remain in event handlers or Effects to [keep your components predictable.](/learn/keeping-components-pure) + +**Although this pattern is more efficient than an Effect, most components shouldn't need it either.** No matter how you do it, adjusting state based on props or other state makes your data flow more difficult to understand and debug. Always check whether you can [reset all state with a key](#resetting-all-state-when-a-prop-changes) or [calculate everything during rendering](#updating-state-based-on-props-or-state) instead. For example, instead of storing (and resetting) the selected *item*, you can store the selected *item ID:* + +```js {3-5} +function List({ items }) { + const [isReverse, setIsReverse] = useState(false); + const [selectedId, setSelectedId] = useState(null); + // ✅ Best: Calculate everything during rendering + const selection = items.find(item => item.id === selectedId) ?? null; + // ... +} +``` + +Now there is no need to "adjust" the state at all. If the item with the selected ID is in the list, it remains selected. If it's not, the `selection` calculated during rendering will be `null` because no matching item was found. This behavior is a bit different, but arguably it's better because most changes to `items` now preserve the selection. However, you'd need to use `selection` in all the logic below because an item with `selectedId` might not exist. + +### Sharing logic between event handlers {/*sharing-logic-between-event-handlers*/} + +Let's say you have a product page with two buttons (Buy and Checkout) that both let you buy that product. You want to show a notification whenever the user puts the product in the cart. Adding the `showNotification()` call to both buttons' click handlers feels repetitive so you might be tempted to place this logic in an Effect: + +```js {2-7} +function ProductPage({ product, addToCart }) { + // 🔴 Avoid: Event-specific logic inside an Effect + useEffect(() => { + if (product.isInCart) { + showNotification(`Added ${product.name} to the shopping cart!`); + } + }, [product]); + + function handleBuyClick() { + addToCart(product); + } + + function handleCheckoutClick() { + addToCart(product); + navigateTo('/checkout'); + } + // ... +} +``` + +This Effect is unnecessary. It will also most likely cause bugs. For example, let's say that your app "remembers" the shopping cart between the page reloads. If you add a product to the cart once and refresh the page, the notification will appear again. It will keep appearing every time you refresh that product's page. This is because `product.isInCart` will already be `true` on the page load, so the Effect above will call `showNotification()`. + +**When you're not sure whether some code should be in an Effect or in an event handler, ask yourself *why* this code needs to run. Use Effects only for code that should run *because* the component was displayed to the user.** In this example, the notification should appear because the user *pressed the button*, not because the page was displayed! Delete the Effect and put the shared logic into a function that you call from both event handlers: + +```js {2-6,9,13} +function ProductPage({ product, addToCart }) { + // ✅ Good: Event-specific logic is called from event handlers + function buyProduct() { + addToCart(product); + showNotification(`Added ${product.name} to the shopping cart!`); + } + + function handleBuyClick() { + buyProduct(); + } + + function handleCheckoutClick() { + buyProduct(); + navigateTo('/checkout'); + } + // ... +} +``` + +This both removes the unnecessary Effect and fixes the bug. + +### Sending a POST request {/*sending-a-post-request*/} + +This `Form` component sends two kinds of POST requests. It sends an analytics event when it mounts. When you fill in the form and click the Submit button, it will send a POST request to the `/api/register` endpoint: + +```js {5-8,10-16} +function Form() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + + // ✅ Good: This logic should run because the component was displayed + useEffect(() => { + post('/analytics/event', { eventName: 'visit_form' }); + }, []); + + // 🔴 Avoid: Event-specific logic inside an Effect + const [jsonToSubmit, setJsonToSubmit] = useState(null); + useEffect(() => { + if (jsonToSubmit !== null) { + post('/api/register', jsonToSubmit); + } + }, [jsonToSubmit]); + + function handleSubmit(e) { + e.preventDefault(); + setJsonToSubmit({ firstName, lastName }); + } + // ... +} +``` + +Let's apply the same criteria as in the example before. + +The analytics POST request should remain in an Effect. This is because the _reason_ to send the analytics event is that the form was displayed. (It would fire twice in development, but [see here](/learn/synchronizing-with-effects#sending-analytics) for how to deal with that.) + +However, the `/api/register` POST request is not caused by the form being _displayed_. You only want to send the request at one specific moment in time: when the user presses the button. It should only ever happen _on that particular interaction_. Delete the second Effect and move that POST request into the event handler: + +```js {12-13} +function Form() { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + + // ✅ Good: This logic runs because the component was displayed + useEffect(() => { + post('/analytics/event', { eventName: 'visit_form' }); + }, []); + + function handleSubmit(e) { + e.preventDefault(); + // ✅ Good: Event-specific logic is in the event handler + post('/api/register', { firstName, lastName }); + } + // ... +} +``` + +When you choose whether to put some logic into an event handler or an Effect, the main question you need to answer is _what kind of logic_ it is from the user's perspective. If this logic is caused by a particular interaction, keep it in the event handler. If it's caused by the user _seeing_ the component on the screen, keep it in the Effect. + +### Chains of computations {/*chains-of-computations*/} + +Sometimes you might feel tempted to chain Effects that each adjust a piece of state based on other state: + +```js {7-29} +function Game() { + const [card, setCard] = useState(null); + const [goldCardCount, setGoldCardCount] = useState(0); + const [round, setRound] = useState(1); + const [isGameOver, setIsGameOver] = useState(false); + + // 🔴 Avoid: Chains of Effects that adjust the state solely to trigger each other + useEffect(() => { + if (card !== null && card.gold) { + setGoldCardCount(c => c + 1); + } + }, [card]); + + useEffect(() => { + if (goldCardCount > 3) { + setRound(r => r + 1) + setGoldCardCount(0); + } + }, [goldCardCount]); + + useEffect(() => { + if (round > 5) { + setIsGameOver(true); + } + }, [round]); + + useEffect(() => { + alert('Good game!'); + }, [isGameOver]); + + function handlePlaceCard(nextCard) { + if (isGameOver) { + throw Error('Game already ended.'); + } else { + setCard(nextCard); + } + } + + // ... +``` + +There are two problems with this code. + +One problem is that it is very inefficient: the component (and its children) have to re-render between each `set` call in the chain. In the example above, in the worst case (`setCard` → render → `setGoldCardCount` → render → `setRound` → render → `setIsGameOver` → render) there are three unnecessary re-renders of the tree below. + +Even if it weren't slow, as your code evolves, you will run into cases where the "chain" you wrote doesn't fit the new requirements. Imagine you are adding a way to step through the history of the game moves. You'd do it by updating each state variable to a value from the past. However, setting the `card` state to a value from the past would trigger the Effect chain again and change the data you're showing. Code like this is often rigid and fragile. + +In this case, it's better to calculate what you can during rendering, and adjust the state in the event handler: + +```js {6-7,14-26} +function Game() { + const [card, setCard] = useState(null); + const [goldCardCount, setGoldCardCount] = useState(0); + const [round, setRound] = useState(1); + + // ✅ Calculate what you can during rendering + const isGameOver = round > 5; + + function handlePlaceCard(nextCard) { + if (isGameOver) { + throw Error('Game already ended.'); + } + + // ✅ Calculate all the next state in the event handler + setCard(nextCard); + if (nextCard.gold) { + if (goldCardCount <= 3) { + setGoldCardCount(goldCardCount + 1); + } else { + setGoldCardCount(0); + setRound(round + 1); + if (round === 5) { + alert('Good game!'); + } + } + } + } + + // ... +``` + +This is a lot more efficient. Also, if you implement a way to view game history, now you will be able to set each state variable to a move from the past without triggering the Effect chain that adjusts every other value. If you need to reuse logic between several event handlers, you can [extract a function](#sharing-logic-between-event-handlers) and call it from those handlers. + +Remember that inside event handlers, [state behaves like a snapshot.](/learn/state-as-a-snapshot) For example, even after you call `setRound(round + 1)`, the `round` variable will reflect the value at the time the user clicked the button. If you need to use the next value for calculations, define it manually like `const nextRound = round + 1`. + +In some cases, you *can't* calculate the next state directly in the event handler. For example, imagine a form with multiple dropdowns where the options of the next dropdown depend on the selected value of the previous dropdown. Then, a chain of Effects fetching data is appropriate because you are synchronizing with network. + +### Initializing the application {/*initializing-the-application*/} + +Some logic should only run once when the app loads. You might place it in an Effect in the top-level component: + +```js {2-6} +function App() { + // 🔴 Avoid: Effects with logic that should only ever run once + useEffect(() => { + loadDataFromLocalStorage(); + checkAuthToken(); + }, []); + // ... +} +``` + +However, you'll quickly discover that it [runs twice in development.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) This can cause issues--for example, maybe it invalidates the authentication token because the function wasn't designed to be called twice. In general, your components should be resilient to being remounted. This includes your top-level `App` component. Although it may not ever get remounted in practice in production, following the same constraints in all components makes it easier to move and reuse code. If some logic must run *once per app load* rather than *once per component mount*, you can add a top-level variable to track whether it has already executed, and always skip re-running it: + +```js {1,5-6,10} +let didInit = false; + +function App() { + useEffect(() => { + if (!didInit) { + didInit = true; + // ✅ Only runs once per app load + loadDataFromLocalStorage(); + checkAuthToken(); + } + }, []); + // ... +} +``` + +You can also run it during module initialization and before the app renders: + +```js {1,5} +if (typeof window !== 'undefined') { // Check if we're running in the browser. + // ✅ Only runs once per app load + checkAuthToken(); + loadDataFromLocalStorage(); +} + +function App() { + // ... +} +``` + +Code at the top level runs once when your component is imported--even if it doesn't end up being rendered. To avoid slowdown or surprising behavior when importing arbitrary components, don't overuse this pattern. Keep app-wide initialization logic to root component modules like `App.js` or in your application's entry point module. + +### Notifying parent components about state changes {/*notifying-parent-components-about-state-changes*/} + +Let's say you're writing a `Toggle` component with an internal `isOn` state which can be either `true` or `false`. There are a few different ways to toggle it (by clicking or dragging). You want to notify the parent component whenever the `Toggle` internal state changes, so you expose an `onChange` event and call it from an Effect: + +```js {4-7} +function Toggle({ onChange }) { + const [isOn, setIsOn] = useState(false); + + // 🔴 Avoid: The onChange handler runs too late + useEffect(() => { + onChange(isOn); + }, [isOn, onChange]) + + function handleClick() { + setIsOn(!isOn); + } + + function handleDragEnd(e) { + if (isCloserToRightEdge(e)) { + setIsOn(true); + } else { + setIsOn(false); + } + } + + // ... +} +``` + +Like earlier, this is not ideal. The `Toggle` updates its state first, and React updates the screen. Then React runs the Effect, which calls the `onChange` function passed from a parent component. Now the parent component will update its own state, starting another render pass. It would be better to do everything in a single pass instead. + +Delete the Effect and instead update the state of *both* components within the same event handler: + +```js {5-7,11,16,18} +function Toggle({ onChange }) { + const [isOn, setIsOn] = useState(false); + + function updateToggle(nextIsOn) { + // ✅ Good: Perform all updates during the event that caused them + setIsOn(nextIsOn); + onChange(nextIsOn); + } + + function handleClick() { + updateToggle(!isOn); + } + + function handleDragEnd(e) { + if (isCloserToRightEdge(e)) { + updateToggle(true); + } else { + updateToggle(false); + } + } + + // ... +} +``` + +With this approach, both the `Toggle` component and its parent component update their state during the event. React [batches updates](/learn/queueing-a-series-of-state-updates) from different components together, so there will only be one render pass as a result. + +You might also be able to remove the state altogether, and instead receive `isOn` from the parent component: + +```js {1,2} +// ✅ Also good: the component is fully controlled by its parent +function Toggle({ isOn, onChange }) { + function handleClick() { + onChange(!isOn); + } + + function handleDragEnd(e) { + if (isCloserToRightEdge(e)) { + onChange(true); + } else { + onChange(false); + } + } + + // ... +} +``` + +["Lifting state up"](/learn/sharing-state-between-components) lets the parent component fully control the `Toggle` by toggling the parent's own state. This means the parent component will have to contain more logic, but there will be less state overall to worry about. Whenever you try to keep two different state variables synchronized, it's a sign to try lifting state up instead! + +### Passing data to the parent {/*passing-data-to-the-parent*/} + +This `Child` component fetches some data and then passes it to the `Parent` component in an Effect: + +```js {9-14} +function Parent() { + const [data, setData] = useState(null); + // ... + return <Child onFetched={setData} />; +} + +function Child({ onFetched }) { + const data = useSomeAPI(); + // 🔴 Avoid: Passing data to the parent in an Effect + useEffect(() => { + if (data) { + onFetched(data); + } + }, [onFetched, data]); + // ... +} +``` + +In React, data flows from the parent components to their children. When you see something wrong on the screen, you can trace where the information comes from by going up the component chain until you find which component passes the wrong prop or has the wrong state. When child components update the state of their parent components in Effects, the data flow becomes very difficult to trace. Since both the child and the parent component need the same data, let the parent component fetch that data, and *pass it down* to the child instead: + +```js {4-5} +function Parent() { + const data = useSomeAPI(); + // ... + // ✅ Good: Passing data down to the child + return <Child data={data} />; +} + +function Child({ data }) { + // ... +} +``` + +This is simpler and keeps the data flow predictable: the data flows down from the parent to the child. + +### Subscribing to an external store {/*subscribing-to-an-external-store*/} + +Sometimes, your components may need to subscribe to some data outside of the React state. This data could be from a third-party library or a built-in browser API. Since this data can change without React's knowledge, you need to manually subscribe your components to it. This is often done with an Effect, for example: + +```js {2-17} +function useOnlineStatus() { + // Not ideal: Manual store subscription in an Effect + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function updateState() { + setIsOnline(navigator.onLine); + } + + updateState(); + + window.addEventListener('online', updateState); + window.addEventListener('offline', updateState); + return () => { + window.removeEventListener('online', updateState); + window.removeEventListener('offline', updateState); + }; + }, []); + return isOnline; +} + +function ChatIndicator() { + const isOnline = useOnlineStatus(); + // ... +} +``` + +Here, the component subscribes to an external data store (in this case, the browser `navigator.onLine` API). Since this API does not exist on the server (so it can't be used to generate the initial HTML), initially the state is set to `true`. Whenever the value of that data store changes in the browser, the component updates its state. + +Although it's common to use Effects for this, React has a purpose-built Hook for subscribing to an external store that is preferred instead. Delete the Effect and replace it with a call to [`useSyncExternalStore`](/reference/react/useSyncExternalStore): + +```js {11-16} +function subscribe(callback) { + window.addEventListener('online', callback); + window.addEventListener('offline', callback); + return () => { + window.removeEventListener('online', callback); + window.removeEventListener('offline', callback); + }; +} + +function useOnlineStatus() { + // ✅ Good: Subscribing to an external store with a built-in Hook + return useSyncExternalStore( + subscribe, // React won't resubscribe for as long as you pass the same function + () => navigator.onLine, // How to get the value on the client + () => true // How to get the value on the server + ); +} + +function ChatIndicator() { + const isOnline = useOnlineStatus(); + // ... +} +``` + +This approach is less error-prone than manually syncing mutable data to React state with an Effect. Typically, you'll write a custom Hook like `useOnlineStatus()` above so that you don't need to repeat this code in the individual components. [Read more about subscribing to external stores from React components.](/reference/react/useSyncExternalStore) + +### Fetching data {/*fetching-data*/} + +Many apps use Effects to kick off data fetching. It is quite common to write a data fetching Effect like this: + +```js {5-10} +function SearchResults({ query }) { + const [results, setResults] = useState([]); + const [page, setPage] = useState(1); + + useEffect(() => { + // 🔴 Avoid: Fetching without cleanup logic + fetchResults(query, page).then(json => { + setResults(json); + }); + }, [query, page]); + + function handleNextPageClick() { + setPage(page + 1); + } + // ... +} +``` + +You *don't* need to move this fetch to an event handler. + +This might seem like a contradiction with the earlier examples where you needed to put the logic into the event handlers! However, consider that it's not *the typing event* that's the main reason to fetch. Search inputs are often prepopulated from the URL, and the user might navigate Back and Forward without touching the input. It doesn't matter where `page` and `query` come from. While this component is visible, you want to keep `results` [synchronized](/learn/synchronizing-with-effects) with data from the network according to the current `page` and `query`. This is why it's an Effect. + +However, the code above has a bug. Imagine you type `"hello"` fast. Then the `query` will change from `"h"`, to `"he"`, `"hel"`, `"hell"`, and `"hello"`. This will kick off separate fetches, but there is no guarantee about which order the responses will arrive in. For example, the `"hell"` response may arrive *after* the `"hello"` response. Since it will call `setResults()` last, you will be displaying the wrong search results. This is called a ["race condition"](https://en.wikipedia.org/wiki/Race_condition): two different requests "raced" against each other and came in a different order than you expected. + +**To fix the race condition, you need to [add a cleanup function](/learn/synchronizing-with-effects#fetching-data) to ignore stale responses:** + +```js {5,7,9,11-13} +function SearchResults({ query }) { + const [results, setResults] = useState([]); + const [page, setPage] = useState(1); + useEffect(() => { + let ignore = false; + fetchResults(query, page).then(json => { + if (!ignore) { + setResults(json); + } + }); + return () => { + ignore = true; + }; + }, [query, page]); + + function handleNextPageClick() { + setPage(page + 1); + } + // ... +} +``` + +This ensures that when your Effect fetches data, all responses except the last requested one will be ignored. + +Handling race conditions is not the only difficulty with implementing data fetching. You might also want to think about how to cache the responses (so that the user can click Back and see the previous screen instantly instead of a spinner), how to fetch them on the server (so that the initial server-rendered HTML contains the fetched content instead of a spinner), and how to avoid network waterfalls (so that a child component that needs to fetch data doesn't have to wait for every parent above it to finish fetching their data before it can start). **These issues apply to any UI library, not just React. Solving them is not trivial, which is why modern [frameworks](/learn/start-a-new-react-project#building-with-a-full-featured-framework) provide more efficient built-in data fetching mechanisms than writing Effects directly in your components.** + +If you don't use a framework (and don't want to build your own) but would like to make data fetching from Effects more ergonomic, consider extracting your fetching logic into a custom Hook like in this example: + +```js {4} +function SearchResults({ query }) { + const [page, setPage] = useState(1); + const params = new URLSearchParams({ query, page }); + const results = useData(`/api/search?${params}`); + + function handleNextPageClick() { + setPage(page + 1); + } + // ... +} + +function useData(url) { + const [data, setData] = useState(null); + useEffect(() => { + let ignore = false; + fetch(url) + .then(response => response.json()) + .then(json => { + if (!ignore) { + setData(json); + } + }); + return () => { + ignore = true; + }; + }, [url]); + return data; +} +``` + +You'll likely also want to add some logic for error handling and to track whether the content is loading. You can build a Hook like this yourself or use one of the many solutions already available in the React ecosystem. **Although this alone won't be as efficient as using a framework's built-in data fetching mechanism, moving the data fetching logic into a custom Hook will make it easier to adopt an efficient data fetching strategy later.** + +In general, whenever you have to resort to writing Effects, keep an eye out for when you can extract a piece of functionality into a custom Hook with a more declarative and purpose-built API like `useData` above. The fewer raw `useEffect` calls you have in your components, the easier you will find to maintain your application. + +<Recap> + +- If you can calculate something during render, you don't need an Effect. +- To cache expensive calculations, add `useMemo` instead of `useEffect`. +- To reset the state of an entire component tree, pass a different `key` to it. +- To reset a particular bit of state in response to a prop change, set it during rendering. +- Code that needs to run because a component was *displayed* should be in Effects, the rest should be in events. +- If you need to update the state of several components, it's better to do it during a single event. +- Whenever you try to synchronize state variables in different components, consider lifting state up. +- You can fetch data with Effects, but you need to implement cleanup to avoid race conditions. + +</Recap> + +<Challenges> + +#### Transform data without Effects {/*transform-data-without-effects*/} + +The `TodoList` below displays a list of todos. When the "Show only active todos" checkbox is ticked, completed todos are not displayed in the list. Regardless of which todos are visible, the footer displays the count of todos that are not yet completed. + +Simplify this component by removing all the unnecessary state and Effects. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { initialTodos, createTodo } from './todos.js'; + +export default function TodoList() { + const [todos, setTodos] = useState(initialTodos); + const [showActive, setShowActive] = useState(false); + const [activeTodos, setActiveTodos] = useState([]); + const [visibleTodos, setVisibleTodos] = useState([]); + const [footer, setFooter] = useState(null); + + useEffect(() => { + setActiveTodos(todos.filter(todo => !todo.completed)); + }, [todos]); + + useEffect(() => { + setVisibleTodos(showActive ? activeTodos : todos); + }, [showActive, todos, activeTodos]); + + useEffect(() => { + setFooter( + <footer> + {activeTodos.length} todos left + </footer> + ); + }, [activeTodos]); + + return ( + <> + <label> + <input + type="checkbox" + checked={showActive} + onChange={e => setShowActive(e.target.checked)} + /> + Show only active todos + </label> + <NewTodo onAdd={newTodo => setTodos([...todos, newTodo])} /> + <ul> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? <s>{todo.text}</s> : todo.text} + </li> + ))} + </ul> + {footer} + </> + ); +} + +function NewTodo({ onAdd }) { + const [text, setText] = useState(''); + + function handleAddClick() { + setText(''); + onAdd(createTodo(text)); + } + + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={handleAddClick}> + Add + </button> + </> + ); +} +``` + +```js todos.js +let nextId = 0; + +export function createTodo(text, completed = false) { + return { + id: nextId++, + text, + completed + }; +} + +export const initialTodos = [ + createTodo('Get apples', true), + createTodo('Get oranges', true), + createTodo('Get carrots'), +]; +``` + +```css +label { display: block; } +input { margin-top: 10px; } +``` + +</Sandpack> + +<Hint> + +If you can calculate something during rendering, you don't need state or an Effect that updates it. + +</Hint> + +<Solution> + +There are only two essential pieces of state in this example: the list of `todos` and the `showActive` state variable which represents whether the checkbox is ticked. All of the other state variables are [redundant](/learn/choosing-the-state-structure#avoid-redundant-state) and can be calculated during rendering instead. This includes the `footer` which you can move directly into the surrounding JSX. + +Your result should end up looking like this: + +<Sandpack> + +```js +import { useState } from 'react'; +import { initialTodos, createTodo } from './todos.js'; + +export default function TodoList() { + const [todos, setTodos] = useState(initialTodos); + const [showActive, setShowActive] = useState(false); + const activeTodos = todos.filter(todo => !todo.completed); + const visibleTodos = showActive ? activeTodos : todos; + + return ( + <> + <label> + <input + type="checkbox" + checked={showActive} + onChange={e => setShowActive(e.target.checked)} + /> + Show only active todos + </label> + <NewTodo onAdd={newTodo => setTodos([...todos, newTodo])} /> + <ul> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? <s>{todo.text}</s> : todo.text} + </li> + ))} + </ul> + <footer> + {activeTodos.length} todos left + </footer> + </> + ); +} + +function NewTodo({ onAdd }) { + const [text, setText] = useState(''); + + function handleAddClick() { + setText(''); + onAdd(createTodo(text)); + } + + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={handleAddClick}> + Add + </button> + </> + ); +} +``` + +```js todos.js +let nextId = 0; + +export function createTodo(text, completed = false) { + return { + id: nextId++, + text, + completed + }; +} + +export const initialTodos = [ + createTodo('Get apples', true), + createTodo('Get oranges', true), + createTodo('Get carrots'), +]; +``` + +```css +label { display: block; } +input { margin-top: 10px; } +``` + +</Sandpack> + +</Solution> + +#### Cache a calculation without Effects {/*cache-a-calculation-without-effects*/} + +In this example, filtering the todos was extracted into a separate function called `getVisibleTodos()`. This function contains a `console.log()` call inside of it which helps you notice when it's being called. Toggle "Show only active todos" and notice that it causes `getVisibleTodos()` to re-run. This is expected because visible todos change when you toggle which ones to display. + +Your task is to remove the Effect that recomputes the `visibleTodos` list in the `TodoList` component. However, you need to make sure that `getVisibleTodos()` does *not* re-run (and so does not print any logs) when you type into the input. + +<Hint> + +One solution is to add a `useMemo` call to cache the visible todos. There is also another, less obvious solution. + +</Hint> + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { initialTodos, createTodo, getVisibleTodos } from './todos.js'; + +export default function TodoList() { + const [todos, setTodos] = useState(initialTodos); + const [showActive, setShowActive] = useState(false); + const [text, setText] = useState(''); + const [visibleTodos, setVisibleTodos] = useState([]); + + useEffect(() => { + setVisibleTodos(getVisibleTodos(todos, showActive)); + }, [todos, showActive]); + + function handleAddClick() { + setText(''); + setTodos([...todos, createTodo(text)]); + } + + return ( + <> + <label> + <input + type="checkbox" + checked={showActive} + onChange={e => setShowActive(e.target.checked)} + /> + Show only active todos + </label> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={handleAddClick}> + Add + </button> + <ul> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? <s>{todo.text}</s> : todo.text} + </li> + ))} + </ul> + </> + ); +} +``` + +```js todos.js +let nextId = 0; +let calls = 0; + +export function getVisibleTodos(todos, showActive) { + console.log(`getVisibleTodos() was called ${++calls} times`); + const activeTodos = todos.filter(todo => !todo.completed); + const visibleTodos = showActive ? activeTodos : todos; + return visibleTodos; +} + +export function createTodo(text, completed = false) { + return { + id: nextId++, + text, + completed + }; +} + +export const initialTodos = [ + createTodo('Get apples', true), + createTodo('Get oranges', true), + createTodo('Get carrots'), +]; +``` + +```css +label { display: block; } +input { margin-top: 10px; } +``` + +</Sandpack> + +<Solution> + +Remove the state variable and the Effect, and instead add a `useMemo` call to cache the result of calling `getVisibleTodos()`: + +<Sandpack> + +```js +import { useState, useMemo } from 'react'; +import { initialTodos, createTodo, getVisibleTodos } from './todos.js'; + +export default function TodoList() { + const [todos, setTodos] = useState(initialTodos); + const [showActive, setShowActive] = useState(false); + const [text, setText] = useState(''); + const visibleTodos = useMemo( + () => getVisibleTodos(todos, showActive), + [todos, showActive] + ); + + function handleAddClick() { + setText(''); + setTodos([...todos, createTodo(text)]); + } + + return ( + <> + <label> + <input + type="checkbox" + checked={showActive} + onChange={e => setShowActive(e.target.checked)} + /> + Show only active todos + </label> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={handleAddClick}> + Add + </button> + <ul> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? <s>{todo.text}</s> : todo.text} + </li> + ))} + </ul> + </> + ); +} +``` + +```js todos.js +let nextId = 0; +let calls = 0; + +export function getVisibleTodos(todos, showActive) { + console.log(`getVisibleTodos() was called ${++calls} times`); + const activeTodos = todos.filter(todo => !todo.completed); + const visibleTodos = showActive ? activeTodos : todos; + return visibleTodos; +} + +export function createTodo(text, completed = false) { + return { + id: nextId++, + text, + completed + }; +} + +export const initialTodos = [ + createTodo('Get apples', true), + createTodo('Get oranges', true), + createTodo('Get carrots'), +]; +``` + +```css +label { display: block; } +input { margin-top: 10px; } +``` + +</Sandpack> + +With this change, `getVisibleTodos()` will be called only if `todos` or `showActive` change. Typing into the input only changes the `text` state variable, so it does not trigger a call to `getVisibleTodos()`. + +There is also another solution which does not need `useMemo`. Since the `text` state variable can't possibly affect the list of todos, you can extract the `NewTodo` form into a separate component, and move the `text` state variable inside of it: + +<Sandpack> + +```js +import { useState, useMemo } from 'react'; +import { initialTodos, createTodo, getVisibleTodos } from './todos.js'; + +export default function TodoList() { + const [todos, setTodos] = useState(initialTodos); + const [showActive, setShowActive] = useState(false); + const visibleTodos = getVisibleTodos(todos, showActive); + + return ( + <> + <label> + <input + type="checkbox" + checked={showActive} + onChange={e => setShowActive(e.target.checked)} + /> + Show only active todos + </label> + <NewTodo onAdd={newTodo => setTodos([...todos, newTodo])} /> + <ul> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? <s>{todo.text}</s> : todo.text} + </li> + ))} + </ul> + </> + ); +} + +function NewTodo({ onAdd }) { + const [text, setText] = useState(''); + + function handleAddClick() { + setText(''); + onAdd(createTodo(text)); + } + + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <button onClick={handleAddClick}> + Add + </button> + </> + ); +} +``` + +```js todos.js +let nextId = 0; +let calls = 0; + +export function getVisibleTodos(todos, showActive) { + console.log(`getVisibleTodos() was called ${++calls} times`); + const activeTodos = todos.filter(todo => !todo.completed); + const visibleTodos = showActive ? activeTodos : todos; + return visibleTodos; +} + +export function createTodo(text, completed = false) { + return { + id: nextId++, + text, + completed + }; +} + +export const initialTodos = [ + createTodo('Get apples', true), + createTodo('Get oranges', true), + createTodo('Get carrots'), +]; +``` + +```css +label { display: block; } +input { margin-top: 10px; } +``` + +</Sandpack> + +This approach satisfies the requirements too. When you type into the input, only the `text` state variable updates. Since the `text` state variable is in the child `NewTodo` component, the parent `TodoList` component won't get re-rendered. This is why `getVisibleTodos()` doesn't get called when you type. (It would still be called if the `TodoList` re-renders for another reason.) + +</Solution> + +#### Reset state without Effects {/*reset-state-without-effects*/} + +This `EditContact` component receives a contact object shaped like `{ id, name, email }` as the `savedContact` prop. Try editing the name and email input fields. When you press Save, the contact's button above the form updates to the edited name. When you press Reset, any pending changes in the form are discarded. Play around with this UI to get a feel for it. + +When you select a contact with the buttons at the top, the form resets to reflect that contact's details. This is done with an Effect inside `EditContact.js`. Remove this Effect. Find another way to reset the form when `savedContact.id` changes. + +<Sandpack> + +```js App.js hidden +import { useState } from 'react'; +import ContactList from './ContactList.js'; +import EditContact from './EditContact.js'; + +export default function ContactManager() { + const [ + contacts, + setContacts + ] = useState(initialContacts); + const [ + selectedId, + setSelectedId + ] = useState(0); + const selectedContact = contacts.find(c => + c.id === selectedId + ); + + function handleSave(updatedData) { + const nextContacts = contacts.map(c => { + if (c.id === updatedData.id) { + return updatedData; + } else { + return c; + } + }); + setContacts(nextContacts); + } + + return ( + <div> + <ContactList + contacts={contacts} + selectedId={selectedId} + onSelect={id => setSelectedId(id)} + /> + <hr /> + <EditContact + savedContact={selectedContact} + onSave={handleSave} + /> + </div> + ) +} + +const initialContacts = [ + { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, + { id: 1, name: 'Alice', email: 'alice@mail.com' }, + { id: 2, name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js hidden +export default function ContactList({ + contacts, + selectedId, + onSelect +}) { + return ( + <section> + <ul> + {contacts.map(contact => + <li key={contact.id}> + <button onClick={() => { + onSelect(contact.id); + }}> + {contact.id === selectedId ? + <b>{contact.name}</b> : + contact.name + } + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js EditContact.js active +import { useState, useEffect } from 'react'; + +export default function EditContact({ savedContact, onSave }) { + const [name, setName] = useState(savedContact.name); + const [email, setEmail] = useState(savedContact.email); + + useEffect(() => { + setName(savedContact.name); + setEmail(savedContact.email); + }, [savedContact]); + + return ( + <section> + <label> + Name:{' '} + <input + type="text" + value={name} + onChange={e => setName(e.target.value)} + /> + </label> + <label> + Email:{' '} + <input + type="email" + value={email} + onChange={e => setEmail(e.target.value)} + /> + </label> + <button onClick={() => { + const updatedData = { + id: savedContact.id, + name: name, + email: email + }; + onSave(updatedData); + }}> + Save + </button> + <button onClick={() => { + setName(savedContact.name); + setEmail(savedContact.email); + }}> + Reset + </button> + </section> + ); +} +``` + +```css +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li { display: inline-block; } +li button { + padding: 10px; +} +label { + display: block; + margin: 10px 0; +} +button { + margin-right: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +<Hint> + +It would be nice if there was a way to tell React that when `savedContact.id` is different, the `EditContact` form is conceptually a _different contact's form_ and should not preserve state. Do you recall any such way? + +</Hint> + +<Solution> + +Split the `EditContact` component in two. Move all the form state into the inner `EditForm` component. Export the outer `EditContact` component, and make it pass `savedContact.id` as the `key` to the inner `EditContact` component. As a result, the inner `EditForm` component resets all of the form state and recreates the DOM whenever you select a different contact. + +<Sandpack> + +```js App.js hidden +import { useState } from 'react'; +import ContactList from './ContactList.js'; +import EditContact from './EditContact.js'; + +export default function ContactManager() { + const [ + contacts, + setContacts + ] = useState(initialContacts); + const [ + selectedId, + setSelectedId + ] = useState(0); + const selectedContact = contacts.find(c => + c.id === selectedId + ); + + function handleSave(updatedData) { + const nextContacts = contacts.map(c => { + if (c.id === updatedData.id) { + return updatedData; + } else { + return c; + } + }); + setContacts(nextContacts); + } + + return ( + <div> + <ContactList + contacts={contacts} + selectedId={selectedId} + onSelect={id => setSelectedId(id)} + /> + <hr /> + <EditContact + savedContact={selectedContact} + onSave={handleSave} + /> + </div> + ) +} + +const initialContacts = [ + { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, + { id: 1, name: 'Alice', email: 'alice@mail.com' }, + { id: 2, name: 'Bob', email: 'bob@mail.com' } +]; +``` + +```js ContactList.js hidden +export default function ContactList({ + contacts, + selectedId, + onSelect +}) { + return ( + <section> + <ul> + {contacts.map(contact => + <li key={contact.id}> + <button onClick={() => { + onSelect(contact.id); + }}> + {contact.id === selectedId ? + <b>{contact.name}</b> : + contact.name + } + </button> + </li> + )} + </ul> + </section> + ); +} +``` + +```js EditContact.js active +import { useState } from 'react'; + +export default function EditContact(props) { + return ( + <EditForm + {...props} + key={props.savedContact.id} + /> + ); +} + +function EditForm({ savedContact, onSave }) { + const [name, setName] = useState(savedContact.name); + const [email, setEmail] = useState(savedContact.email); + + return ( + <section> + <label> + Name:{' '} + <input + type="text" + value={name} + onChange={e => setName(e.target.value)} + /> + </label> + <label> + Email:{' '} + <input + type="email" + value={email} + onChange={e => setEmail(e.target.value)} + /> + </label> + <button onClick={() => { + const updatedData = { + id: savedContact.id, + name: name, + email: email + }; + onSave(updatedData); + }}> + Save + </button> + <button onClick={() => { + setName(savedContact.name); + setEmail(savedContact.email); + }}> + Reset + </button> + </section> + ); +} +``` + +```css +ul, li { + list-style: none; + margin: 0; + padding: 0; +} +li { display: inline-block; } +li button { + padding: 10px; +} +label { + display: block; + margin: 10px 0; +} +button { + margin-right: 10px; + margin-bottom: 10px; +} +``` + +</Sandpack> + +</Solution> + +#### Submit a form without Effects {/*submit-a-form-without-effects*/} + +This `Form` component lets you send a message to a friend. When you submit the form, the `showForm` state variable is set to `false`. This triggers an Effect calling `sendMessage(message)`, which sends the message (you can see it in the console). After the message is sent, you see a "Thank you" dialog with an "Open chat" button that lets you get back to the form. + +Your app's users are sending way too many messages. To make chatting a little bit more difficult, you've decided to show the "Thank you" dialog *first* rather than the form. Change the `showForm` state variable to initialize to `false` instead of `true`. As soon as you make that change, the console will show that an empty message was sent. Something in this logic is wrong! + +What's the root cause of this problem? And how can you fix it? + +<Hint> + +Should the message be sent _because_ the user saw the "Thank you" dialog? Or is it the other way around? + +</Hint> + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Form() { + const [showForm, setShowForm] = useState(true); + const [message, setMessage] = useState(''); + + useEffect(() => { + if (!showForm) { + sendMessage(message); + } + }, [showForm, message]); + + function handleSubmit(e) { + e.preventDefault(); + setShowForm(false); + } + + if (!showForm) { + return ( + <> + <h1>Thanks for using our services!</h1> + <button onClick={() => { + setMessage(''); + setShowForm(true); + }}> + Open chat + </button> + </> + ); + } + + return ( + <form onSubmit={handleSubmit}> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <button type="submit" disabled={message === ''}> + Send + </button> + </form> + ); +} + +function sendMessage(message) { + console.log('Sending message: ' + message); +} +``` + +```css +label, textarea { margin-bottom: 10px; display: block; } +``` + +</Sandpack> + +<Solution> + +The `showForm` state variable determines whether to show the form or the "Thank you" dialog. However, you aren't sending the message because the "Thank you" dialog was _displayed_. You want to send the message because the user has _submitted the form._ Delete the misleading Effect and move the `sendMessage` call inside the `handleSubmit` event handler: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Form() { + const [showForm, setShowForm] = useState(true); + const [message, setMessage] = useState(''); + + function handleSubmit(e) { + e.preventDefault(); + setShowForm(false); + sendMessage(message); + } + + if (!showForm) { + return ( + <> + <h1>Thanks for using our services!</h1> + <button onClick={() => { + setMessage(''); + setShowForm(true); + }}> + Open chat + </button> + </> + ); + } + + return ( + <form onSubmit={handleSubmit}> + <textarea + placeholder="Message" + value={message} + onChange={e => setMessage(e.target.value)} + /> + <button type="submit" disabled={message === ''}> + Send + </button> + </form> + ); +} + +function sendMessage(message) { + console.log('Sending message: ' + message); +} +``` + +```css +label, textarea { margin-bottom: 10px; display: block; } +``` + +</Sandpack> + +Notice how in this version, only _submitting the form_ (which is an event) causes the message to be sent. It works equally well regardless of whether `showForm` is initially set to `true` or `false`. (Set it to `false` and notice no extra console messages.) + +</Solution> + +</Challenges> diff --git a/beta/src/content/learn/your-first-component.md b/beta/src/content/learn/your-first-component.md new file mode 100644 index 000000000..0cb88bbb1 --- /dev/null +++ b/beta/src/content/learn/your-first-component.md @@ -0,0 +1,461 @@ +--- +title: Your First Component +--- + +<Intro> + +*Components* are one of the core concepts of React. They are the foundation upon which you build user interfaces (UI), which makes them the perfect place to start your React journey! + +</Intro> + +<YouWillLearn> + +* What a component is +* What role components play in a React application +* How to write your first React component + +</YouWillLearn> + +## Components: UI building blocks {/*components-ui-building-blocks*/} + +On the Web, HTML lets us create rich structured documents with its built-in set of tags like `<h1>` and `<li>`: + +```html +<article> + <h1>My First Component</h1> + <ol> + <li>Components: UI Building Blocks</li> + <li>Defining a Component</li> + <li>Using a Component</li> + </ol> +</article> +``` + +This markup represents this article `<article>`, its heading `<h1>`, and an (abbreviated) table of contents as an ordered list `<ol>`. Markup like this, combined with CSS for style, and JavaScript for interactivity, lies behind every sidebar, avatar, modal, dropdown—every piece of UI you see on the Web. + +React lets you combine your markup, CSS, and JavaScript into custom "components", **reusable UI elements for your app.** The table of contents code you saw above could be turned into a `<TableOfContents />` component you could render on every page. Under the hood, it still uses the same HTML tags like `<article>`, `<h1>`, etc. + +Just like with HTML tags, you can compose, order and nest components to design whole pages. For example, the documentation page you're reading is made out of React components: + +```js +<PageLayout> + <NavigationHeader> + <SearchBar /> + <Link to="/docs">Docs</Link> + </NavigationHeader> + <Sidebar /> + <PageContent> + <TableOfContents /> + <DocumentationText /> + </PageContent> +</PageLayout> +``` + +As your project grows, you will notice that many of your designs can be composed by reusing components you already wrote, speeding up your development. Our table of contents above could be added to any screen with `<TableOfContents />`! You can even jumpstart your project with the thousands of components shared by the React open source community like [Chakra UI](https://chakra-ui.com/) and [Material UI.](https://material-ui.com/) + +## Defining a component {/*defining-a-component*/} + +Traditionally when creating web pages, web developers marked up their content and then added interaction by sprinkling on some JavaScript. This worked great when interaction was a nice-to-have on the web. Now it is expected for many sites and all apps. React puts interactivity first while still using the same technology: **a React component is a JavaScript function that you can _sprinkle with markup_.** Here's what that looks like (you can edit the example below): + +<Sandpack> + +```js +export default function Profile() { + return ( + <img + src="https://i.imgur.com/MK3eW3Am.jpg" + alt="Katherine Johnson" + /> + ) +} +``` + +```css +img { height: 200px; } +``` + +</Sandpack> + +And here's how to build a component: + +### Step 1: Export the component {/*step-1-export-the-component*/} + +The `export default` prefix is a [standard JavaScript syntax](https://developer.mozilla.org/docs/web/javascript/reference/statements/export) (not specific to React). It lets you mark the main function in a file so that you can later import it from other files. (More on importing in [Importing and Exporting Components](/learn/importing-and-exporting-components)!) + +### Step 2: Define the function {/*step-2-define-the-function*/} + +With `function Profile() { }` you define a JavaScript function with the name `Profile`. + +<Pitfall> + +React components are regular JavaScript functions, but **their names must start with a capital letter** or they won't work! + +</Pitfall> + +### Step 3: Add markup {/*step-3-add-markup*/} + +The component returns an `<img />` tag with `src` and `alt` attributes. `<img />` is written like HTML, but it is actually JavaScript under the hood! This syntax is called [JSX](/learn/writing-markup-with-jsx), and it lets you embed markup inside JavaScript. + +Return statements can be written all on one line, as in this component: + +```js +return <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />; +``` + +But if your markup isn't all on the same line as the `return` keyword, you must wrap it in a pair of parentheses like this: + +```js +return ( + <div> + <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" /> + </div> +); +``` + +<Pitfall> + +Without parentheses, any code on the lines after `return` [will be ignored](https://stackoverflow.com/questions/2846283/what-are-the-rules-for-javascripts-automatic-semicolon-insertion-asi)! + +</Pitfall> + +## Using a component {/*using-a-component*/} + +Now that you've defined your `Profile` component, you can nest it inside other components. For example, you can export a `Gallery` component that uses multiple `Profile` components: + +<Sandpack> + +```js +function Profile() { + return ( + <img + src="https://i.imgur.com/MK3eW3As.jpg" + alt="Katherine Johnson" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +### What the browser sees {/*what-the-browser-sees*/} + +Notice the difference in casing: + +* `<section>` is lowercase, so React knows we refer to an HTML tag. +* `<Profile />` starts with a capital `P`, so React knows that we want to use our component called `Profile`. + +And `Profile` contains even more HTML: `<img />`. In the end, this is what the browser sees: + +```html +<section> + <h1>Amazing scientists</h1> + <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" /> + <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" /> + <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" /> +</section> +``` + +### Nesting and organizing components {/*nesting-and-organizing-components*/} + +Components are regular JavaScript functions, so you can keep multiple components in the same file. This is convenient when components are relatively small or tightly related to each other. If this file gets crowded, you can always move `Profile` to a separate file. You will learn how to do this shortly on the [page about imports.](/learn/importing-and-exporting-components) + +Because the `Profile` components are rendered inside `Gallery`—even several times!—we can say that `Gallery` is a **parent component,** rendering each `Profile` as a "child". This is part of the magic of React: you can define a component once, and then use it in as many places and as many times as you like. + +<Pitfall> + +Components can render other components, but **you must never nest their definitions:** + +```js {2-5} +export default function Gallery() { + // 🔴 Never define a component inside another component! + function Profile() { + // ... + } + // ... +} +``` + +The snippet above is [very slow and causes bugs.](/learn/preserving-and-resetting-state#different-components-at-the-same-position-reset-state) Instead, define every component at the top level: + +```js {5-8} +export default function Gallery() { + // ... +} + +// ✅ Declare components at the top level +function Profile() { + // ... +} +``` + +When a child component needs some data from a parent, [pass it by props](/learn/passing-props-to-a-component) instead of nesting definitions. + +</Pitfall> + +<DeepDive> + +#### Components all the way down {/*components-all-the-way-down*/} + +Your React application begins at a "root" component. Usually, it is created automatically when you start a new project. For example, if you use [CodeSandbox](https://codesandbox.io/) or [Create React App](https://create-react-app.dev/), the root component is defined in `src/App.js`. If you use the framework [Next.js](https://nextjs.org/), the root component is defined in `pages/index.js`. In these examples, you've been exporting root components. + +Most React apps use components all the way down. This means that you won't only use components for reusable pieces like buttons, but also for larger pieces like sidebars, lists, and ultimately, complete pages! Components are a handy way to organize UI code and markup, even if some of them are only used once. + +Frameworks like Next.js take this a step further. Instead of using an empty HTML file and letting React "take over" managing the page with JavaScript, they *also* generate the HTML automatically from your React components. This allows your app to show some content before the JavaScript code loads. + +Still, many websites only use React to [add "sprinkles of interactivity".](/learn/add-react-to-a-website) They have many root components instead of a single one for the entire page. You can use as much—or as little—React as you need. + +</DeepDive> + +<Recap> + +You've just gotten your first taste of React! Let's recap some key points. + +* React lets you create components, **reusable UI elements for your app.** +* In a React app, every piece of UI is a component. +* React components are regular JavaScript functions except: + + 1. Their names always begin with a capital letter. + 2. They return JSX markup. + +</Recap> + + + +<Challenges> + +#### Export the component {/*export-the-component*/} + +This sandbox doesn't work because the root component is not exported: + +<Sandpack> + +```js +function Profile() { + return ( + <img + src="https://i.imgur.com/lICfvbD.jpg" + alt="Aklilu Lemma" + /> + ); +} +``` + +```css +img { height: 181px; } +``` + +</Sandpack> + +Try to fix it yourself before looking at the solution! + +<Solution> + +Add `export default` before the function definition like so: + +<Sandpack> + +```js +export default function Profile() { + return ( + <img + src="https://i.imgur.com/lICfvbD.jpg" + alt="Aklilu Lemma" + /> + ); +} +``` + +```css +img { height: 181px; } +``` + +</Sandpack> + +You might be wondering why writing `export` alone is not enough to fix this example. You can learn the difference between `export` and `export default` in [Importing and Exporting Components.](/learn/importing-and-exporting-components) + +</Solution> + +#### Fix the return statement {/*fix-the-return-statement*/} + +Something isn't right about this `return` statement. Can you fix it? + +<Hint> + +You may get an "Unexpected token" error while trying to fix this. In that case, check that the semicolon appears *after* the closing parenthesis. Leaving a semicolon inside `return ( )` will cause an error. + +</Hint> + + +<Sandpack> + +```js +export default function Profile() { + return + <img src="https://i.imgur.com/jA8hHMpm.jpg" alt="Katsuko Saruhashi" />; +} +``` + +```css +img { height: 180px; } +``` + +</Sandpack> + +<Solution> + +You can fix this component by moving the return statement to one line like so: + +<Sandpack> + +```js +export default function Profile() { + return <img src="https://i.imgur.com/jA8hHMpm.jpg" alt="Katsuko Saruhashi" />; +} +``` + +```css +img { height: 180px; } +``` + +</Sandpack> + +Or by wrapping the returned JSX markup in parentheses that open right after `return`: + +<Sandpack> + +```js +export default function Profile() { + return ( + <img + src="https://i.imgur.com/jA8hHMpm.jpg" + alt="Katsuko Saruhashi" + /> + ); +} +``` + +```css +img { height: 180px; } +``` + +</Sandpack> + +</Solution> + +#### Spot the mistake {/*spot-the-mistake*/} + +Something's wrong with how the `Profile` component is declared and used. Can you spot the mistake? (Try to remember how React distinguishes components from the regular HTML tags!) + +<Sandpack> + +```js +function profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <profile /> + <profile /> + <profile /> + </section> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; height: 90px; } +``` + +</Sandpack> + +<Solution> + +React component names must start with a capital letter. + +Change `function profile()` to `function Profile()`, and then change every `<profile />` to `<Profile />`: + +<Sandpack> + +```js +function Profile() { + return ( + <img + src="https://i.imgur.com/QIrZWGIs.jpg" + alt="Alan L. Hart" + /> + ); +} + +export default function Gallery() { + return ( + <section> + <h1>Amazing scientists</h1> + <Profile /> + <Profile /> + <Profile /> + </section> + ); +} +``` + +```css +img { margin: 0 10px 10px 0; } +``` + +</Sandpack> + +</Solution> + +#### Your own component {/*your-own-component*/} + +Write a component from scratch. You can give it any valid name and return any markup. If you're out of ideas, you can write a `Congratulations` component that shows `<h1>Good job!</h1>`. Don't forget to export it! + +<Sandpack> + +```js +// Write your component below! + +``` + +</Sandpack> + +<Solution> + +<Sandpack> + +```js +export default function Congratulations() { + return ( + <h1>Good job!</h1> + ); +} +``` + +</Sandpack> + +</Solution> + +</Challenges> diff --git a/beta/src/content/reference/react-dom/client/createRoot.md b/beta/src/content/reference/react-dom/client/createRoot.md new file mode 100644 index 000000000..bee136acc --- /dev/null +++ b/beta/src/content/reference/react-dom/client/createRoot.md @@ -0,0 +1,426 @@ +--- +title: createRoot +--- + +<Intro> + +`createRoot` lets you create a root to display React components inside a browser DOM node. + +```js +const root = createRoot(domNode, options?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `createRoot(domNode, options?)` {/*createroot*/} + +Call `createRoot` to create a React root for displaying content inside a browser DOM element. + +```js +import { createRoot } from 'react-dom/client'; + +const domNode = document.getElementById('root'); +const root = createRoot(domNode); +``` + +React will create a root for the `domNode`, and take over managing the DOM inside it. After you've created a root, you need to call [`root.render`](#root-render) to display a React component inside of it: + +```js +root.render(<App />); +``` + +An app fully built with React will usually only have one `createRoot` call for its root component. A page that uses "sprinkles" of React for parts of the page may have as many separate roots as needed. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `domNode`: A [DOM element.](https://developer.mozilla.org/en-US/docs/Web/API/Element) React will create a root for this DOM element and allow you to call functions on the root, such as `render` to display rendered React content. + +* **optional** `options`: An object with options for this React root. + + * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. + +#### Returns {/*returns*/} + +`createRoot` returns an object with two methods: [`render`](#root-render) and [`unmount`.](#root-unmount) + +#### Caveats {/*caveats*/} +* If your app is server-rendered, using `createRoot()` is not supported. Use [`hydrateRoot()`](/reference/react-dom/client/hydrateRoot) instead. +* You'll likely have only one `createRoot` call in your app. If you use a framework, it might do this call for you. +* When you want to render a piece of JSX in a different part of the DOM tree that isn't a child of your component (for example, a modal or a tooltip), use [`createPortal`](/reference/react-dom/createPortal) instead of `createRoot`. + +--- + +### `root.render(reactNode)` {/*root-render*/} + +Call `root.render` to display a piece of [JSX](/learn/writing-markup-with-jsx) ("React node") into the React root's browser DOM node. + +```js +root.render(<App />); +``` + +React will display `<App />` in the `root`, and take over managing the DOM inside it. + +[See more examples below.](#usage) + +#### Parameters {/*root-render-parameters*/} + +* `reactNode`: A *React node* that you want to display. This will usually be a piece of JSX like `<App />`, but you can also pass a React element constructed with [`createElement()`](/reference/react/createElement), a string, a number, `null`, or `undefined`. + + +#### Returns {/*root-render-returns*/} + +`root.render` returns `undefined`. + +#### Caveats {/*root-render-caveats*/} + +* The first time you call `root.render`, React will clear all the existing HTML content inside the React root before rendering the React component into it. + +* If your root's DOM node contains HTML generated by React on the server or during the build, use [`hydrateRoot()`](/reference/react-dom/client/hydrateRoot) instead, which attaches the event handlers to the existing HTML. + +* If you call `render` on the same root more than once, React will update the DOM as necessary to reflect the latest JSX you passed. React will decide which parts of the DOM can be reused and which need to be recreated by ["matching it up"](/learn/preserving-and-resetting-state) with the previously rendered tree. Calling `render` on the same root again is similar to calling the [`set` function](/reference/react/useState#setstate) on the root component: React avoids unnecessary DOM updates. + +--- + +### `root.unmount()` {/*root-unmount*/} + +Call `root.unmount` to destroy a rendered tree inside a React root. + +```js +root.unmount(); +``` + +An app fully built with React will usually not have any calls to `root.unmount`. + +This is mostly useful if your React root's DOM node (or any of its ancestors) may get removed from the DOM by some other code. For example, imagine a jQuery tab panel that removes inactive tabs from the DOM. If a tab gets removed, everything inside it (including the React roots inside) would get removed from the DOM as well. In that case, you need to tell React to "stop" managing the removed root's content by calling `root.unmount`. Otherwise, the components inside the removed root won't know to clean up and free up global resources like subscriptions. + +Calling `root.unmount` will unmount all the components in the root and "detach" React from the root DOM node, including removing any event handlers or state in the tree. + + +#### Parameters {/*root-unmount-parameters*/} + +`root.unmount` does not accept any parameters. + + +#### Returns {/*root-unmount-returns*/} + +`root.unmount` returns `undefined`. + +#### Caveats {/*root-unmount-caveats*/} + +* Calling `root.unmount` will unmount all the components in the tree and "detach" React from the root DOM node. + +* Once you call `root.unmount` you cannot call `root.render` again on the same root. Attempting to call `root.render` on an unmounted root will throw a "Cannot update an unmounted root" error. However, you can create a new root for the same DOM node after the previous root for that node has been unmounted. + +--- + +## Usage {/*usage*/} + +### Rendering an app fully built with React {/*rendering-an-app-fully-built-with-react*/} + +If your app is fully built with React, create a single root for your entire app. + +```js [[1, 3, "document.getElementById('root')"], [2, 4, "<App />"]] +import { createRoot } from 'react-dom/client'; + +const root = createRoot(document.getElementById('root')); +root.render(<App />); +```` + +Usually, you only need to run this code once at startup. It will: + +1. Find the <CodeStep step={1}>browser DOM node</CodeStep> defined in your HTML. +2. Display the <CodeStep step={2}>React component</CodeStep> for your app inside. + +<Sandpack> + +```html index.html +<!DOCTYPE html> +<html> + <head><title>My app</title></head> + <body> + <!-- This is the DOM node --> + <div id="root"></div> + </body> +</html> +``` + +```js index.js active +import { createRoot } from 'react-dom/client'; +import App from './App.js'; +import './styles.css'; + +const root = createRoot(document.getElementById('root')); +root.render(<App />); +``` + +```js App.js +import { useState } from 'react'; + +export default function App() { + return ( + <> + <h1>Hello, world!</h1> + <Counter /> + </> + ); +} + +function Counter() { + const [count, setCount] = useState(0); + return ( + <button onClick={() => setCount(count + 1)}> + You clicked me {count} times + </button> + ); +} +``` + +</Sandpack> + +**If your app is fully built with React, you shouldn't need to create any more roots, or to call [`root.render`](#root-render) again.** + +From this point on, React will manage the DOM of your entire app. To add more components, [nest them inside the `App` component.](/learn/importing-and-exporting-components) When you need to update the UI, each of your components can do this by [using state.](/reference/react/useState) When you need to display extra content like a modal or a tooltip outside the DOM node, [render it with a portal.](/reference/react-dom/createPortal) + +<Note> + +When your HTML is empty, the user sees a blank page until the app's JavaScript code loads and runs: + +```html +<div id="root"></div> +``` + +This can feel very slow! To solve this, you can generate the initial HTML from your components [on the server or during the build.](/reference/react-dom/server) Then your visitors can read text, see images, and click links before any of the JavaScript code loads. We recommend to [use a framework](/learn/start-a-new-react-project#building-with-a-full-featured-framework) that does this optimization out of the box. Depending on when it runs, this is called *server-side rendering (SSR)* or *static site generation (SSG).* + +</Note> + +<Pitfall> + +**Apps using server rendering or static generation must call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) instead of `createRoot`.** React will then *hydrate* (reuse) the DOM nodes from your HTML instead of destroying and re-creating them. + +</Pitfall> + +--- + +### Rendering a page partially built with React {/*rendering-a-page-partially-built-with-react*/} + +If your page [isn't fully built with React](/learn/add-react-to-a-website), you can call `createRoot` multiple times to create a root for each top-level piece of UI managed by React. You can display different content in each root by calling [`root.render`.](#root-render) + +Here, two different React components are rendered into two DOM nodes defined in the `index.html` file: + +<Sandpack> + +```html public/index.html +<nav id="navigation"></nav> +<main> + <p>This paragraph is not rendered by React (open index.html to verify).</p> + <section id="comments"></section> +</main> +``` + +```js index.js active +import './styles.css'; +import { createRoot } from 'react-dom/client'; +import { Comments, Navigation } from './Components.js'; + +const navDomNode = document.getElementById('navigation'); +const navRoot = createRoot(navDomNode); +navRoot.render(<Navigation />); + +const commentDomNode = document.getElementById('comments'); +const commentRoot = createRoot(commentDomNode); +commentRoot.render(<Comments />); +``` + +```js Components.js +export function Navigation() { + return ( + <ul> + <NavLink href="/">Home</NavLink> + <NavLink href="/about">About</NavLink> + </ul> + ); +} + +function NavLink({ href, children }) { + return ( + <li> + <a href={href}>{children}</a> + </li> + ); +} + +export function Comments() { + return ( + <> + <h2>Comments</h2> + <Comment text="Hello!" author="Sophie" /> + <Comment text="How are you?" author="Sunil" /> + </> + ); +} + +function Comment({ text, author }) { + return ( + <p>{text} — <i>{author}</i></p> + ); +} +``` + +```css +nav ul { padding: 0; margin: 0; } +nav ul li { display: inline-block; margin-right: 20px; } +``` + +</Sandpack> + +You could also create a new DOM node with [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) and add it to the document manually. + +```js +const domNode = document.createElement('div'); +const root = createRoot(domNode); +root.render(<Comment />); +document.body.appendChild(domNode); // You can add it anywhere in the document +``` + +To remove the React tree from the DOM node and clean up all the resources used by it, call [`root.unmount`.](#root-unmount) + +```js +root.unmount(); +``` + +This is mostly useful if your React components are inside an app written in a different framework. + +--- + +### Updating a root component {/*updating-a-root-component*/} + +You can call `render` more than once on the same root. As long as the component tree structure matches up with what was previously rendered, React will [preserve the state.](/learn/preserving-and-resetting-state) Notice how you can type in the input, which means that the updates from repeated `render` calls every second in this example are not destructive: + +<Sandpack> + +```js index.js active +import { createRoot } from 'react-dom/client'; +import './styles.css'; +import App from './App.js'; + +const root = createRoot(document.getElementById('root')); + +let i = 0; +setInterval(() => { + root.render(<App counter={i} />); + i++; +}, 1000); +``` + +```js App.js +export default function App({counter}) { + return ( + <> + <h1>Hello, world! {counter}</h1> + <input placeholder="Type something here" /> + </> + ); +} +``` + +</Sandpack> + +It is uncommon to call `render` multiple times. Usually, you'll [update state](/reference/react/useState) inside one of the components instead. + +--- +## Troubleshooting {/*troubleshooting*/} + +### I've created a root, but nothing is displayed {/*ive-created-a-root-but-nothing-is-displayed*/} + +Make sure you haven't forgotten to actually *render* your app into the root: + +```js {5} +import { createRoot } from 'react-dom/client'; +import App from './App.js'; + +const root = createRoot(document.getElementById('root')); +root.render(<App />); +``` + +Until you do that, nothing is displayed. + +--- + +### I'm getting an error: "Target container is not a DOM element" {/*im-getting-an-error-target-container-is-not-a-dom-element*/} + +This error means that whatever you're passing to `createRoot` is not a DOM node. + +If you're not sure what's happening, try logging it: + +```js {2} +const domNode = document.getElementById('root'); +console.log(domNode); // ??? +const root = createRoot(domNode); +root.render(<App />); +``` + +For example, if `domNode` is `null`, it means that [`getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) returned `null`. This will happen if there is no node in the document with the given ID at the time of your call. There may be a few reasons for it: + +1. The ID you're looking for might differ from the ID you used in the HTML file. Check for typos! +2. Your bundle's `<script>` tag cannot "see" any DOM nodes that appear *after* it in the HTML. + +If you can't get it working, check out [Adding React to a Website](/learn/add-react-to-a-website) for a working example. + +Another common way to get this error is to write `createRoot(<App />)` instead of `createRoot(domNode)`. + +--- + +### I'm getting an error: "Functions are not valid as a React child." {/*im-getting-an-error-functions-are-not-valid-as-a-react-child*/} + +This error means that whatever you're passing to `root.render` is not a React component. + +This may happen if you call `root.render` with `Component` instead of `<Component />`: + +```js {2,5} +// 🚩 Wrong: App is a function, not a Component. +root.render(App); + +// ✅ Correct: <App /> is a component. +root.render(<App />); +```` + +Or if you pass a function to `root.render`, instead of the result of calling it: + +```js {2,5} +// 🚩 Wrong: createApp is a function, not a component. +root.render(createApp); + +// ✅ Correct: call createApp to return a component. +root.render(createApp()); +``` + +If you can't get it working, check out [Adding React to a Website](/learn/add-react-to-a-website) for a working example. + +--- + +### My server-rendered HTML gets re-created from scratch {/*my-server-rendered-html-gets-re-created-from-scratch*/} + +If your app is server-rendered and includes the initial HTML generated by React, you might notice that creating a root and calling `root.render` deletes all that HTML, and then re-creates all the DOM nodes from scratch. This can be slower, resets focus and scroll positions, and may lose other user input. + +Server-rendered apps must use [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) instead of `createRoot`: + +```js {1,4-7} +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot( + document.getElementById('root'), + <App /> +); +``` + +Note that its API is different. In particular, usually there will be no further `root.render` call. diff --git a/beta/src/content/reference/react-dom/client/hydrateRoot.md b/beta/src/content/reference/react-dom/client/hydrateRoot.md new file mode 100644 index 000000000..bff1a17f9 --- /dev/null +++ b/beta/src/content/reference/react-dom/client/hydrateRoot.md @@ -0,0 +1,373 @@ +--- +title: hydrateRoot +--- + +<Intro> + +`hydrateRoot` lets you display React components inside a browser DOM node whose HTML content was previously generated by [`react-dom/server`.](/reference/react-dom/server) + +```js +const root = hydrateRoot(domNode, reactNode, options?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `hydrateRoot(domNode, options?)` {/*hydrateroot*/} + +Call `hydrateRoot` to “attach” React to existing HTML that was already rendered by React in a server environment. + +```js +import { hydrateRoot } from 'react-dom/client'; + +const domNode = document.getElementById('root'); +const root = hydrateRoot(domNode, reactNode); +``` + +React will attach to the HTML that exists inside the `domNode`, and take over managing the DOM inside it. An app fully built with React will usually only have one `hydrateRoot` call with its root component. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `domNode`: A [DOM element](https://developer.mozilla.org/en-US/docs/Web/API/Element) that was rendered as the root element on the server. + +* `reactNode`: The "React node" used to render the existing HTML. This will usually be a piece of JSX like `<App />` which was rendered with a `ReactDOM Server` method such as `renderToPipeableStream(<App />)`. + +* **optional** `options`: An object with options for this React root. + + * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as used on the server. + * **optional** `nonce`: + +#### Returns {/*returns*/} + +`hydrateRoot` returns an object with two methods: [`render`](#root-render) and [`unmount`.](#root-unmount) + +#### Caveats {/*caveats*/} + +* `hydrateRoot()` expects the rendered content to be identical with the server-rendered content. You should treat mismatches as bugs and fix them. +* In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive. +* You'll likely have only one `hydrateRoot` call in your app. If you use a framework, it might do this call for you. +* If your app is client-rendered with no HTML rendered already, using `hydrateRoot()` is not supported. Use [`createRoot()`](/reference/react-dom/client/createRoot) instead. + +--- + +### `root.render(reactNode)` {/*root-render*/} + +Call `root.render` to update a React component inside a hydrated React root for a browser DOM element. + +```js +root.render(<App />); +``` + +React will update `<App />` in the hydrated `root`. + +[See more examples below.](#usage) + +#### Parameters {/*root-render-parameters*/} + +* `reactNode`: A "React node" that you want to update. This will usually be a piece of JSX like `<App />`, but you can also pass a React element constructed with [`createElement()`](/reference/react/createElement), a string, a number, `null`, or `undefined`. + + +#### Returns {/*root-render-returns*/} + +`root.render` returns `undefined`. + +#### Caveats {/*root-render-caveats*/} + +* If you call `root.render` before the root has finished hydrating, React will clear the existing server-rendered HTML content and switch the entire root to client rendering. + +--- + +### `root.unmount()` {/*root-unmount*/} + +Call `root.unmount` to destroy a rendered tree inside a React root. + +```js +root.unmount(); +``` + +An app fully built with React will usually not have any calls to `root.unmount`. + +This is mostly useful if your React root's DOM node (or any of its ancestors) may get removed from the DOM by some other code. For example, imagine a jQuery tab panel that removes inactive tabs from the DOM. If a tab gets removed, everything inside it (including the React roots inside) would get removed from the DOM as well. In that case, you need to tell React to "stop" managing the removed root's content by calling `root.unmount`. Otherwise, the components inside the removed root won't know to clean up and free up global resources like subscriptions. + +Calling `root.unmount` will unmount all the components in the root and "detach" React from the root DOM node, including removing any event handlers or state in the tree. + + +#### Parameters {/*root-unmount-parameters*/} + +`root.unmount` does not accept any parameters. + + +#### Returns {/*root-unmount-returns*/} + +`render` returns `null`. + +#### Caveats {/*root-unmount-caveats*/} + +* Calling `root.unmount` will unmount all the components in the tree and "detach" React from the root DOM node. + +* Once you call `root.unmount` you cannot call `root.render` again on the root. Attempting to call `root.render` on an unmounted root will throw a "Cannot update an unmounted root" error. + +--- + +## Usage {/*usage*/} + +### Hydrating server-rendered HTML {/*hydrating-server-rendered-html*/} + +If your app's HTML was generated by [`react-dom/server`](/reference/react-dom/client/createRoot), you need to *hydrate* it on the client. + +```js [[1, 3, "document.getElementById('root')"], [2, 3, "<App />"]] +import { hydrateRoot } from 'react-dom/client'; + +hydrateRoot(document.getElementById('root'), <App />); +```` + +This will hydrate the server HTML inside the <CodeStep step={1}>browser DOM node</CodeStep> with the <CodeStep step={2}>React component</CodeStep> for your app. Usually, you will do it once at startup. If you use a framework, it might do this behind the scenes for you. + +To hydrate your app, React will "attach" your components' logic to the initial generated HTML from the server. Hydration turns the initial HTML snapshot from the server into a fully interactive app that runs in the browser. + +<Sandpack> + +```html public/index.html +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><h1>Hello, world!</h1><button>You clicked me <!-- -->0<!-- --> times</button></div> +``` + +```js index.js active +import './styles.css'; +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot( + document.getElementById('root'), + <App /> +); +``` + +```js App.js +import { useState } from 'react'; + +export default function App() { + return ( + <> + <h1>Hello, world!</h1> + <Counter /> + </> + ); +} + +function Counter() { + const [count, setCount] = useState(0); + return ( + <button onClick={() => setCount(count + 1)}> + You clicked me {count} times + </button> + ); +} +``` + +</Sandpack> + +You shouldn't need to call `hydrateRoot` again or to call it in more places. From this point on, React will be managing the DOM of your application. If you want to update the UI, your components can do this by [using state.](/reference/react/useState) + +<Pitfall> + +The React tree you pass to `hydrateRoot` needs to produce **the same output** as it did on the server. + +This is important for the user experience. The user will spend some time looking at the server-generated HTML before your JavaScript code loads. Server rendering creates an illusion that the app loads faster by showing the HTML snapshot of its output. Suddenly showing different content breaks that illusion. This is why the server render output must match the initial render output on the client during hydration. + +The most common causes leading to hydration errors include: + +* Extra whitespace (like newlines) around the React-generated HTML inside the root node. +* Using checks like `typeof window !== 'undefined'` in your rendering logic. +* Using browser-only APIs like [`window.matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) in your rendering logic. +* Rendering different data on the server and the client. + +React can recover from some hydration errors, but **you must fix them like other bugs.** In the best case, they'll lead to a slower app; in the worst case, event handlers would get attached to the wrong elements. + +</Pitfall> + +--- + +### Hydrating an entire document {/*hydrating-an-entire-document*/} + +Apps fully built with React can render the entire document from the root component, including the [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html) tag: + +```js {3,13} +function App() { + return ( + <html> + <head> + <meta charSet="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/styles.css"></link> + <title>My app</title> + </head> + <body> + <Router /> + </body> + </html> + ); +} +``` + +To hydrate the entire document, pass the [`document`](https://developer.mozilla.org/en-US/docs/Web/API/Window/document) global as the first argument to `hydrateRoot`: + +```js {4} +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, <App />); +``` + +--- + +### Suppressing unavoidable hydration mismatch errors {/*suppressing-unavoidable-hydration-mismatch-errors*/} + +If a single element’s attribute or text content is unavoidably different between the server and the client (for example, a timestamp), you may silence the hydration mismatch warning. + +To silence hydration warnings on an element, add `suppressHydrationWarning={true}`: + +<Sandpack> + +```html public/index.html +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><h1>Current Date: <!-- -->01/01/2020</h1></div> +``` + +```js index.js +import './styles.css'; +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document.getElementById('root'), <App />); +``` + +```js App.js active +export default function App() { + return ( + <h1 suppressHydrationWarning={true}> + Current Date: {new Date().toLocaleDateString()} + </h1> + ); +} +``` + +</Sandpack> + +This only works one level deep, and is intended to be an escape hatch. Don’t overuse it. Unless it’s text content, React still won’t attempt to patch it up, so it may remain inconsistent until future updates. + +--- + +### Handling different client and server content {/*handling-different-client-and-server-content*/} + +If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a [state variable](/reference/react/useState) like `isClient`, which you can set to `true` in an [Effect](/reference/react/useEffect): + +<Sandpack> + +```html public/index.html +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><h1>Is Server</h1></div> +``` + +```js index.js +import './styles.css'; +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document.getElementById('root'), <App />); +``` + +```js App.js active +import { useState, useEffect } from "react"; + +export default function App() { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + return ( + <h1> + {isClient ? 'Is Client' : 'Is Server'} + </h1> + ); +} +``` + +</Sandpack> + +This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. + +<Pitfall> + +This approach makes hydration slower because your components have to render twice. Be mindful of the user experience on slow connections. The JavaScript code may load significantly later than the initial HTML render, so rendering a different UI immediately after hydration may also feel jarring to the user. + +</Pitfall> + +--- + +### Updating a hydrated root component {/*updating-a-hydrated-root-component*/} + +After the root has finished hydrating, you can call [`root.render`](#root-render) to update the root React component. **Unlike with [`createRoot`](/reference/react-dom/client/createRoot), you don't usually need to do this because the initial content was already rendered as HTML.** + +If you call `root.render` at some point after hydration, and the component tree structure matches up with what was previously rendered, React will [preserve the state.](/learn/preserving-and-resetting-state) Notice how you can type in the input, which means that the updates from repeated `render` calls every second in this example are not destructive: + +<Sandpack> + +```html public/index.html +<!-- + All HTML content inside <div id="root">...</div> was + generated by rendering <App /> with react-dom/server. +--> +<div id="root"><h1>Hello, world! <!-- -->0</h1><input placeholder="Type something here"/></div> +``` + +```js index.js active +import { hydrateRoot } from 'react-dom/client'; +import './styles.css'; +import App from './App.js'; + +const root = hydrateRoot( + document.getElementById('root'), + <App counter={0} /> +); + +let i = 0; +setInterval(() => { + root.render(<App counter={i} />); + i++; +}, 1000); +``` + +```js App.js +export default function App({counter}) { + return ( + <> + <h1>Hello, world! {counter}</h1> + <input placeholder="Type something here" /> + </> + ); +} +``` + +</Sandpack> + +It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually, you'll [update state](/reference/react/useState) inside one of the components instead. diff --git a/beta/src/content/reference/react-dom/client/index.md b/beta/src/content/reference/react-dom/client/index.md new file mode 100644 index 000000000..4e588b3a2 --- /dev/null +++ b/beta/src/content/reference/react-dom/client/index.md @@ -0,0 +1,22 @@ +--- +title: Client React DOM APIs +--- + +<Intro> + +The `react-dom/client` APIs let you render React components on the client (in the browser). These APIs are typically used at the top level of your app to initialize your React tree. A [framework](/learn/start-a-new-react-project#building-with-a-full-featured-framework) may call them for you. Most of your components don't need to import or use them. + +</Intro> + +--- + +## Client APIs {/*client-apis*/} + +* [`createRoot`](/reference/react-dom/client/createRoot) lets you create a root to display React components inside a browser DOM node. +* [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) lets you display React components inside a browser DOM node whose HTML content was previously generated by [`react-dom/server`.](/reference/react-dom/server) + +--- + +## Browser support {/*browser-support*/} + +React supports all popular browsers, including Internet Explorer 9 and above. Some polyfills are required for older browsers such as IE 9 and IE 10. \ No newline at end of file diff --git a/beta/src/content/reference/react-dom/components/common.md b/beta/src/content/reference/react-dom/components/common.md new file mode 100644 index 000000000..d640248c8 --- /dev/null +++ b/beta/src/content/reference/react-dom/components/common.md @@ -0,0 +1,1184 @@ +--- +title: "Common components (e.g. <div>)" +--- + +<Intro> + +All built-in browser components, such as [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div), support some common props and events. + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### Common components (e.g. `<div>`) {/*common*/} + +```js +<div className="wrapper">Some content</div> +``` + +[See more examples below.](#usage) + +#### Props {/*common-props*/} + +These special React props are supported for all built-in components: + +* `children`: A React node (an element, a string, a number, [a portal,](/reference/react-dom/createPortal) an empty node like `null`, `undefined` and booleans, or an array of other React nodes). Specifies the content inside the component. When you use JSX, you will usually specify the `children` prop implicitly by nesting tags like `<div><span /></div>`. + +* `dangerouslySetInnerHTML`: An object of the form `{ __html: '<p>some html</p>' }` with a raw HTML string inside. Overrides the [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property of the DOM node and displays the passed HTML inside. This should be used with extreme caution! If the HTML inside isn't trusted (for example, if it's based on user data), you risk introducing an [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerability. [Read more about using `dangerouslySetInnerHTML`.](#dangerously-setting-the-inner-html) + +* `ref`: A ref object from [`useRef`](/reference/react/useRef) or [`createRef`](/reference/react/createRef), or a [`ref` callback function,](#ref-callback) or a string for [legacy refs.](https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs) Your ref will be filled with the DOM element for this node. [Read more about manipulating the DOM with refs.](#manipulating-a-dom-node-with-a-ref) + +* `suppressContentEditableWarning`: A boolean. If `true`, suppresses the warning that React shows for elements that both have `children` and `contentEditable={true}` (which normally do not work together). Use this if you're building a text input library that manages the `contentEditable` content manually. + +* `suppressHydrationWarning`: A boolean. If you use [server rendering,](/reference/react-dom/server) normally there is a warning when the server and the client render different content. In some rare cases (like timestamps), it is very hard or impossible to guarantee an exact match. If you set `suppressHydrationWarning` to `true`, React will not warn you about mismatches in the attributes and the content of that element. It only works one level deep, and is intended to be used as an escape hatch. Don't overuse it. [Read more about suppressing hydration errors.](/reference/react-dom/client/hydrateRoot#suppressing-unavoidable-hydration-mismatch-errors) + +* `style`: An object with CSS styles, for example `{ fontWeight: 'bold', margin: 20 }`. Similarly to the DOM [`style`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) property, the CSS property names need to be written as `camelCase`, for example `fontWeight` instead of `font-weight`. You can pass strings or numbers as values. If you pass a number, like `width: 100`, React will automatically append `px` ("pixels") to the value unless it's a [unitless property.](https://github.com/facebook/react/blob/81d4ee9ca5c405dce62f64e61506b8e155f38d8d/packages/react-dom-bindings/src/shared/CSSProperty.js#L8-L57) We recommend using `style` only for dynamic styles where you don't know the style values ahead of time. In other cases, applying plain CSS classes with `className` is more efficient. [Read more about applying CSS with `className` and `styles`.](#applying-css-styles) + +These standard DOM props are also supported for all built-in components: + +* [`accessKey`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey): A string. Specifies a keyboard shortcut for the element. [Not generally recommended.](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey#accessibility_concerns) +* [`aria-*`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes): ARIA attributes let you specify the accessibility tree information for this element. See [ARIA attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes) for a complete reference. In React, all ARIA attribute names are exactly the same as in HTML. +* [`autoCapitalize`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize): A string. Specifies whether and how the user input should be capitalized. +* [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className): A string. Specifies the element's CSS class name. [Read more about applying CSS styles.](#applying-css-styles) +* [`contentEditable`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable): A boolean. If `true`, the browser lets the user edit the rendered element directly. This is used to implement rich text input libraries like [Lexical.](https://lexical.dev/) React warns if you try to pass React children to an element with `contentEditable={true}` because React will not be able to update its content after user edits. +* [`data-*`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*): Data attributes let you attach some string data to the element, for example `data-fruit="banana"`. In React, they are not commonly used because you would usually read data from props or state instead. +* [`dir`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir): Either `'ltr'` or `'rtl'`. Specifies the text direction of the element. +* [`draggable`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable): A boolean. Specifies whether the element is draggable. Part of [HTML Drag and Drop API.](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) +* [`enterKeyHint`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/enterKeyHint): A string. Specifies which action to present for the enter key on virtual keyboards. +* [`htmlFor`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/htmlFor): A string. For [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) and [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output), lets you [associate the label with some control.](/reference/react-dom/components/input#providing-a-label-for-an-input) Same as [`for` HTML attribute.](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/for) React uses the standard DOM property names (`htmlFor`) instead of HTML attribute names. +* [`hidden`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden): A boolean or a string. Specifies whether the element should be hidden. +* [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id): A string. Specifies a unique identifier for this element, which can be used to find it later or connect it with other elements. Generate it with [`useId`](/reference/react/useId) to avoid clashes between multiple instances of the same component. +* [`is`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/is): A string. If specified, the component will behave like a [custom element.](/reference/react-dom/components#custom-html-elements) +* [`inputMode`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode): A string. Specifies what kind of keyboard to display (for example, text, number or telephone). +* [`itemProp`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemprop): A string. Specifies which property the element represents for structured data crawlers. +* [`lang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang): A string. Specifies the language of the element. +* [`onAnimationEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event): An [`AnimationEvent` handler](#animationevent-handler) function. Fires when a CSS animation completes. +* `onAnimationEndCapture`: A version of `onAnimationEnd` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onAnimationIteration`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationiteration_event): An [`AnimationEvent` handler](#animationevent-handler) function. Fires when an iteration of a CSS animation ends, and another one begins. +* `onAnimationIterationCapture`: A version of `onAnimationIteration` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onAnimationStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event): An [`AnimationEvent` handler](#animationevent-handler) function. Fires when a CSS animation starts. +* `onAnimationStartCapture`: `onAnimationStart`, but fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onAuxClick`](https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when a non-primary pointer button was clicked. +* `onAuxClickCapture`: A version of `onAuxClick` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* `onBeforeInput`: An [`InputEvent` handler](#inputevent-handler) function. Fires before the value of an editable element is modified. React does *not* yet use the native [`beforeinput`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event) event, and instead attempts to polyfill it using other events. +* `onBeforeInputCapture`: A version of `onBeforeInput` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* `onBlur`: A [`FocusEvent` handler](#focusevent-handler) function. Fires when an element lost focus. Unlike the built-in browser [`blur`](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) event, in React the `onBlur` event bubbles. +* `onBlurCapture`: A version of `onBlur` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onClick`](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the primary button was clicked on the pointing device. +* `onClickCapture`: A version of `onClick` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onCompositionStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event): A [`CompositionEvent` handler](#compositionevent-handler) function. Fires when an [input method editor](https://developer.mozilla.org/en-US/docs/Glossary/Input_method_editor) starts a new composition session. +* `onCompositionStartCapture`: A version of `onCompositionStart` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onCompositionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event): A [`CompositionEvent` handler](#compositionevent-handler) function. Fires when an [input method editor](https://developer.mozilla.org/en-US/docs/Glossary/Input_method_editor) completes or cancels a composition session. +* `onCompositionEndCapture`: A version of `onCompositionEnd` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onCompositionUpdate`](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionupdate_event): A [`CompositionEvent` handler](#compositionevent-handler) function. Fires when an [input method editor](https://developer.mozilla.org/en-US/docs/Glossary/Input_method_editor) receives a new character. +* `onCompositionUpdateCapture`: A version of `onCompositionUpdate` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onContextMenu`](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the user tries to open a context menu. +* `onContextMenuCapture`: A version of `onContextMenu` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onCopy`](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event): A [`ClipboardEvent` handler](#clipboardevent-handler) function. Fires when the user tries to copy something into the clipboard. +* `onCopyCapture`: A version of `onCopy` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onCut`](https://developer.mozilla.org/en-US/docs/Web/API/Element/cut_event): A [`ClipboardEvent` handler](#clipboardevent-handler) function. Fires when the user tries to cut something into the clipboard. +* `onCutCapture`: A version of `onCut` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* `onDoubleClick`: A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the user clicks twice. Corresponds to the browser [`dblclick` event.](https://developer.mozilla.org/en-US/docs/Web/API/Element/dblclick_event) +* `onDoubleClickCapture`: A version of `onDoubleClick` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onDrag`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drag_event): A [`DragEvent` handler](#dragevent-handler) function. Fires while the user is dragging something. +* `onDragCapture`: A version of `onDrag` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onDragEnd`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragend_event): A [`DragEvent` handler](#dragevent-handler) function. Fires when the user stops dragging something. +* `onDragEndCapture`: A version of `onDragEnd` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onDragEnter`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragenter_event): A [`DragEvent` handler](#dragevent-handler) function. Fires when the dragged content enters a valid drop target. +* `onDragEnterCapture`: A version of `onDragEnter` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onDragOver`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragover_event): A [`DragEvent` handler](#dragevent-handler) function. Fires on a valid drop target while the dragged content is dragged over it. You must call `e.preventDefault()` here to allow dropping. +* `onDragOverCapture`: A version of `onDragOver` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onDragStart`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragstart_event): A [`DragEvent` handler](#dragevent-handler) function. Fires when the user starts dragging an element. +* `onDragStartCapture`: A version of `onDragStart` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onDrop`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event): A [`DragEvent` handler](#dragevent-handler) function. Fires when something is dropped on a valid drop target. +* `onDropCapture`: A version of `onDrop` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* `onFocus`: A [`FocusEvent` handler](#focusevent-handler) function. Fires when an element lost focus. Unlike the built-in browser [`focus`](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event) event, in React the `onFocus` event bubbles. +* `onFocusCapture`: A version of `onFocus` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onGotPointerCapture`](https://developer.mozilla.org/en-US/docs/Web/API/Element/gotpointercapture_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when an element programmatically captures a pointer. +* `onGotPointerCaptureCapture`: A version of `onGotPointerCapture` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onKeyDown`](https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event): A [`KeyboardEvent` handler](#pointerevent-handler) function. Fires when a key is pressed. +* `onKeyDownCapture`: A version of `onKeyDown` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onKeyPress`](https://developer.mozilla.org/en-US/docs/Web/API/Element/keypress_event): A [`KeyboardEvent` handler](#pointerevent-handler) function. Deprecated. Use `onKeyDown` or `onBeforeInput` instead. +* `onKeyPressCapture`: A version of `onKeyPress` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onKeyUp`](https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event): A [`KeyboardEvent` handler](#pointerevent-handler) function. Fires when a key is released. +* `onKeyUpCapture`: A version of `onKeyUp` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onLostPointerCapture`](https://developer.mozilla.org/en-US/docs/Web/API/Element/lostpointercapture_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when an element stops capturing a pointer. +* `onLostPointerCaptureCapture`: A version of `onLostPointerCapture` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onMouseDown`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousedown_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the pointer is pressed down. +* `onMouseDownCapture`: A version of `onMouseDown` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onMouseEnter`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseenter_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the pointer moves inside an element. Does not have a capture phase. Instead, `onMouseLeave` and `onMouseEnter` propagate from the element being left to the one being entered. +* [`onMouseLeave`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseleave_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the pointer moves outside an element. Does not have a capture phase. Instead, `onMouseLeave` and `onMouseEnter` propagate from the element being left to the one being entered. +* [`onMouseMove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the pointer changes coordinates. +* `onMouseMoveCapture`: A version of `onMouseMove` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onMouseOut`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseout_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the pointer moves outside an element, or if it moves into a child element. +* `onMouseOutCapture`: A version of `onMouseOut` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onMouseUp`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseup_event): A [`MouseEvent` handler](#mouseevent-handler) function. Fires when the pointer is released. +* `onMouseUpCapture`: A version of `onMouseUp` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPointerCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointercancel_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when the browser cancels a pointer interaction. +* `onPointerCancelCapture`: A version of `onPointerCancel` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPointerDown`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerdown_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when a pointer becomes active. +* `onPointerDownCapture`: A version of `onPointerDown` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPointerEnter`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerenter_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when a pointer moves inside an element. Does not have a capture phase. Instead, `onPointerLeave` and `onPointerEnter` propagate from the element being left to the one being entered. +* [`onPointerLeave`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerleave_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when a pointer moves outside an element. Does not have a capture phase. Instead, `onPointerLeave` and `onPointerEnter` propagate from the element being left to the one being entered. +* [`onPointerMove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when a pointer changes coordinates. +* `onPointerMoveCapture`: A version of `onPointerMove` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPointerOut`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerout_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when a pointer moves outside an element, if the pointer interaction is cancelled, and [a few other reasons.](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerout_event) +* `onPointerOutCapture`: A version of `onPointerOut` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPointerUp`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerup_event): A [`PointerEvent` handler](#pointerevent-handler) function. Fires when a pointer is no longer active. +* `onPointerUpCapture`: A version of `onPointerUp` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPaste`](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event): A [`ClipboardEvent` handler](#clipboardevent-handler) function. Fires when the user tries to paste something from the clipboard. +* `onPasteCapture`: A version of `onPaste` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onScroll`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event): An [`Event` handler](#event-handler) function. Fires when an element has been scrolled. This event does not bubble. +* `onScrollCapture`: A version of `onScroll` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onSelect`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select_event): An [`Event` handler](#event-handler) function. Fires after the selection inside an editable element like an input changes. React extends the `onSelect` event to work for `contentEditable={true}` elements as well. In addition, React extends it to fire for empty selection and on edits (which may affect the selection). +* `onSelectCapture`: A version of `onSelect` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onTouchCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchcancel_event): A [`TouchEvent` handler](#touchevent-handler) function. Fires when the browser cancels a touch interaction. +* `onTouchCancelCapture`: A version of `onTouchCancel` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onTouchEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchend_event): A [`TouchEvent` handler](#touchevent-handler) function. Fires when one or more touch points are removed. +* `onTouchEndCapture`: A version of `onTouchEnd` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onTouchMove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchmove_event): A [`TouchEvent` handler](#touchevent-handler) function. Fires one or more touch points are moved. +* `onTouchMoveCapture`: A version of `onTouchMove` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onTouchStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event): A [`TouchEvent` handler](#touchevent-handler) function. Fires when one or more touch points are placed. +* `onTouchStartCapture`: A version of `onTouchStart` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onTransitionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event): A [`TransitionEvent` handler](#transitionevent-handler) function. Fires when a CSS transition completes. +* `onTransitionEndCapture`: A version of `onTransitionEnd` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onWheel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event): A [`WheelEvent` handler](#wheelevent-handler) function. Fires when the user rotates a wheel button. +* `onWheelCapture`: A version of `onWheel` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`role`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles): A string. Specifies the element role explicitly for assistive technologies. +nt. +* [`slot`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles): A string. Specifies the slot name when using shadow DOM. In React, an equivalent pattern is typically achieved by passing JSX as props, for example `<Layout left={<Sidebar />} right={<Content />} />`. +* [`spellCheck`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck): A boolean or null. If explicitly set to `true` or `false`, enables or disables spellchecking. +* [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex): A number. Overrides the default Tab button behavior. [Avoid using values other than `-1` and `0`.](https://www.tpgi.com/using-the-tabindex-attribute/) +* [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title): A string. Specifies the tooltip text for the element. +* [`translate`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/translate): Either `'yes'` or `'no'`. Passing `'no'` excludes the element content from being translated. + +You can also pass custom attributes as props, for example `mycustomprop="someValue".` This can be useful when integrating with third-party libraries. The custom attribute name must be lowercase and must not start with `on`. The value will be converted to a string. If you pass `null` or `undefined`, the custom attribute will be removed. + +These events fire only for the [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) elements: + +* [`onReset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset_event): An [`Event` handler](#event-handler) function. Fires when a form gets reset. +* `onResetCapture`: A version of `onReset` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onSubmit`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event): An [`Event` handler](#event-handler) function. Fires when a form gets submitted. +* `onSubmitCapture`: A version of `onSubmit` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) + +These events fire only for the [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) elements. Unlike browser events, they bubble in React: + +* [`onCancel`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/cancel_event): An [`Event` handler](#event-handler) function. Fires when the user tries to dismiss the dialog. +* `onCancelCapture`: A version of `onCancel` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +capture-phase-events) +* [`onClose`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close_event): An [`Event` handler](#event-handler) function. Fires when a dialog has been closed. +* `onCloseCapture`: A version of `onClose` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) + +These events fire only for the [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) elements. Unlike browser events, they bubble in React: + +* [`onToggle`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDetailsElement/toggle_event): An [`Event` handler](#event-handler) function. Fires when the user toggles the details. +* `onToggleCapture`: A version of `onToggle` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +capture-phase-events) + +These events fire for [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img), [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object), [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed), [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link), and [SVG `<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/SVG_Image_Tag) elements. Unlike browser events, they bubble in React: + +* `onLoad`: An [`Event` handler](#event-handler) function. Fires when the resource has loaded. +* `onLoadCapture`: A version of `onLoad` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onError`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/error_event): An [`Event` handler](#event-handler) function. Fires when the resource could not be loaded. +* `onErrorCapture`: A version of `onError` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) + +These events fire for resources like [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio) and [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video). Unlike browser events, they bubble in React: + +* [`onAbort`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/abort_event): An [`Event` handler](#event-handler) function. Fires when the resource has not fully loaded, but not due to an error. +* `onAbortCapture`: A version of `onAbort` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onCanPlay`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event): An [`Event` handler](#event-handler) function. Fires when there's enough data to start playing, but not enough to play to the end without buffering. +* `onCanPlayCapture`: A version of `onCanPlay` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onCanPlayThrough`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplaythrough_event): An [`Event` handler](#event-handler) function. Fires when there's enough data that it's likely possible to start playing without buffering until the end. +* `onCanPlayThroughCapture`: A version of `onCanPlayThrough` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onDurationChange`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/durationchange_event): An [`Event` handler](#event-handler) function. Fires when the media duration has updated. +* `onDurationChangeCapture`: A version of `onDurationChange` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onEmptied`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/emptied_event): An [`Event` handler](#event-handler) function. Fires when the media has become empty. +* `onEmptiedCapture`: A version of `onEmptied` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onEncrypted`](https://w3c.github.io/encrypted-media/#dom-evt-encrypted): An [`Event` handler](#event-handler) function. Fires when the browser encounters encrypted media. +* `onEncryptedCapture`: A version of `onEncrypted` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onEnded`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended_event): An [`Event` handler](#event-handler) function. Fires when the playback stops because there's nothing left to play. +* `onEndedCapture`: A version of `onEnded` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onError`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/error_event): An [`Event` handler](#event-handler) function. Fires when the resource could not be loaded. +* `onErrorCapture`: A version of `onError` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onLoadedData`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadeddata_event): An [`Event` handler](#event-handler) function. Fires when the current playback frame has loaded. +* `onLoadedDataCapture`: A version of `onLoadedData` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onLoadedMetadata`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event): An [`Event` handler](#event-handler) function. Fires when metadata has loaded. +* `onLoadedMetadataCapture`: A version of `onLoadedMetadata` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onLoadStart`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadstart_event): An [`Event` handler](#event-handler) function. Fires when the browser started loading the resource. +* `onLoadStartCapture`: A version of `onLoadStart` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPause`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause_event): An [`Event` handler](#event-handler) function. Fires when the media was paused. +* `onPauseCapture`: A version of `onPause` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPlay`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play_event): An [`Event` handler](#event-handler) function. Fires when the media is no longer paused. +* `onPlayCapture`: A version of `onPlay` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onPlaying`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playing_event): An [`Event` handler](#event-handler) function. Fires when the media starts or restarts playing. +* `onPlayingCapture`: A version of `onPlaying` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onProgress`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/progress_event): An [`Event` handler](#event-handler) function. Fires periodically while the resource is loading. +* `onProgressCapture`: A version of `onProgress` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onRateChange`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ratechange_event): An [`Event` handler](#event-handler) function. Fires when playback rate changes. +* `onRateChangeCapture`: A version of `onRateChange` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* `onResize`: An [`Event` handler](#event-handler) function. Fires when video changes size. +* `onResizeCapture`: A version of `onResize` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onSeeked`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeked_event): An [`Event` handler](#event-handler) function. Fires when a seek operation completes. +* `onSeekedCapture`: A version of `onSeeked` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onSeeking`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeking_event): An [`Event` handler](#event-handler) function. Fires when a seek operation starts. +* `onSeekingCapture`: A version of `onSeeking` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onStalled`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/stalled_event): An [`Event` handler](#event-handler) function. Fires when the browser is waiting for data but it keeps not loading. +* `onStalledCapture`: A version of `onStalled` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onSuspend`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/suspend_event): An [`Event` handler](#event-handler) function. Fires when loading the resource was suspended. +* `onSuspendCapture`: A version of `onSuspend` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onTimeUpdate`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/timeupdate_event): An [`Event` handler](#event-handler) function. Fires when the current playback time updates. +* `onTimeUpdateCapture`: A version of `onTimeUpdate` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onVolumeChange`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volumechange_event): An [`Event` handler](#event-handler) function. Fires when the volume has changed. +* `onVolumeChangeCapture`: A version of `onVolumeChange` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onWaiting`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/waiting_event): An [`Event` handler](#event-handler) function. Fires when the playback stopped due to temporary lack of data. +* `onWaitingCapture`: A version of `onWaiting` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) + +#### Caveats {/*common-caveats*/} + +- You cannot pass both `children` and `dangerouslySetInnerHTML` at the same time. +- Some events (like `onAbort` and `onLoad`) don't bubble in the browser, but bubble in React. + +--- + +### `ref` callback function {/*ref-callback*/} + +Instead of a ref object (like the one returned by [`useRef`](/reference/react/useRef#manipulating-the-dom-with-a-ref)), you may pass a function to the `ref` attribute. + +```js +<div ref={(node) => console.log(node)} /> +``` + +[See an example of using the `ref` callback.](/learn/manipulating-the-dom-with-refs#how-to-manage-a-list-of-refs-using-a-ref-callback) + +When the `<div>` DOM node is added to the screen, React will call your `ref` callback with the DOM `node` as the argument. When that `<div>` DOM node is removed, React will call your `ref` callback with `null`. + +React will also call your `ref` callback whenever you pass a *different* `ref` callback. In the above example, `(node) => { ... }` is a different function on every render. This is why, when your component re-renders, the *previous* function will be called with `null` as the argument, and the *next* function will be called with the DOM node. + +#### Parameters {/*ref-callback-parameters*/} + +* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the ref gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during ever re-render of the component. + +#### Returns {/*returns*/} + +Do not return anything from the `ref` callback. + +--- + +### React event object {/*react-event-object*/} + +Your event handlers will receive a *React event object.* It is also sometimes known as a "synthetic event". + +```js +<button onClick={e => { + console.log(e); // React event object +}} /> +``` + +It conforms to the same standard as the underlying DOM events, but fixes some browser inconsistencies. + +Some React events do not map directly to the browser's native events. For example in `onMouseLeave`, `e.nativeEvent` will point to a `mouseout` event. The specific mapping is not part of the public API and may change in the future. If you need the underlying browser event for some reason, read it from `e.nativeEvent`. + +#### Properties {/*react-event-object-properties*/} + +React event objects implement some of the standard [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) properties: + +* [`bubbles`](https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles): A boolean. Returns whether the event bubbles through the DOM. +* [`cancelable`](https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelable): A boolean. Returns whether the event can be canceled. +* [`currentTarget`](https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget): A DOM node. Returns the node to which the current handler is attached in the React tree. +* [`defaultPrevented`](https://developer.mozilla.org/en-US/docs/Web/API/Event/defaultPrevented): A boolean. Returns whether `preventDefault` was called. +* [`eventPhase`](https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase): A number. Returns which phase the event is currently in. +* [`isTrusted`](https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted): A boolean. Returns whether the event was initiated by user. +* [`target`](https://developer.mozilla.org/en-US/docs/Web/API/Event/target): A DOM node. Returns the node on which the event has occurred (which could be a distant child). +* [`timeStamp`](https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp): A number. Returns the time when the event occurred. + +Additionally, React event objects provide these properties: + +* `nativeEvent`: A DOM [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). The original browser event object. + +#### Methods {/*react-event-object-methods*/} + +React event objects implement some of the standard [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) methods: + +* [`preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault): Prevents the default browser action for the event. +* [`stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation): Stops the event propagation through the React tree. + +Additionally, React event objects provide these methods: + +* `isDefaultPrevented()`: Returns a boolean value indicating whether `preventDefault` was called. +* `isPropagationStopped()`: Returns a boolean value indicating whether `stopPropagation` was called. +* `persist()`: Not used with React DOM. With React Native, call this to read event's properties after the event. +* `isPersistent()`: Not used with React DOM. With React Native, returns whether `persist` has been called. + +#### Caveats {/*react-event-object-caveats*/} + +* The values of `currentTarget`, `eventPhase`, `target`, and `type` reflect the values your React code expects. Under the hood, React attaches event handlers at the root, but this is not reflected in React event objects. For example, `e.currentTarget` may not be the same as the underlying `e.nativeEvent.currentTarget`. For polyfilled events, `e.type` (React event type) may differ from `e.nativeEvent.type` (underlying type). + +--- + +### `AnimationEvent` handler function {/*animationevent-handler*/} + +An event handler type for the [CSS animation](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations) events. + +```js +<div + onAnimationStart={e => console.log('onAnimationStart')} + onAnimationIteration={e => console.log('onAnimationStart')} + onAnimationEnd={e => console.log('onAnimationStart')} +/> +``` + +#### Parameters {/*animationevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`AnimationEvent`](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent) properties: + * [`animationName`](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent/animationName) + * [`elapsedTime`](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent/elapsedTime) + * [`pseudoElement`](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent) + +--- + +### `ClipboardEvent` handler function {/*clipboadevent-handler*/} + +An event handler type for the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) events. + +```js +<input + onCopy={e => console.log('onCopy')} + onCut={e => console.log('onCut')} + onPaste={e => console.log('onPaste')} +/> +``` + +#### Parameters {/*clipboadevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`ClipboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) properties: + + * [`clipboardData`](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent/clipboardData) + +--- + +### `CompositionEvent` handler function {/*compositionevent-handler*/} + +An event handler type for the [input method editor (IME)](https://developer.mozilla.org/en-US/docs/Glossary/Input_method_editor) events. + +```js +<input + onCompositionStart={e => console.log('onCompositionStart')} + onCompositionUpdate={e => console.log('onCompositionUpdate')} + onCompositionEnd={e => console.log('onCompositionEnd')} +/> +``` + +#### Parameters {/*compositionevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`CompositionEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent) properties: + * [`data`](https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent/data) + +--- + +### `DragEvent` handler function {/*dragevent-handler*/} + +An event handler type for the [HTML Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) events. + +```js +<> + <div + draggable={true} + onDragStart={e => console.log('onDragStart')} + onDragEnd={e => console.log('onDragEnd')} + > + Drag source + </div> + + <div + onDragEnter={e => console.log('onDragEnter')} + onDragLeave={e => console.log('onDragLeave')} + onDragOver={e => { e.preventDefault(); console.log('onDragOver'); }} + onDrop={e => console.log('onDrop')} + > + Drop target + </div> +</> +``` + +#### Parameters {/*dragevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`DragEvent`](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent) properties: + * [`dataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/dataTransfer) + + It also includes the inherited [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) properties: + + * [`altKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/altKey) + * [`button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button) + * [`buttons`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons) + * [`ctrlKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/ctrlKey) + * [`clientX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) + * [`clientY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY) + * [`getModifierState(key)`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/getModifierState) + * [`metaKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/metaKey) + * [`movementX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX) + * [`movementY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementY) + * [`pageX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX) + * [`pageY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY) + * [`relatedTarget`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/relatedTarget) + * [`screenX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenX) + * [`screenY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenY) + * [`shiftKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/shiftKey) + + It also includes the inherited [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +### `FocusEvent` handler function {/*focusevent-handler*/} + +An event handler type for the focus events. + +```js +<input + onFocus={e => console.log('onFocus')} + onBlur={e => console.log('onBlur')} +/> +``` + +[See an example.](#handling-focus-events) + +#### Parameters {/*focusevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`FocusEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent) properties: + * [`relatedTarget`](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/relatedTarget) + + It also includes the inherited [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +### `Event` handler function {/*event-handler*/} + +An event handler type for generic events. + +#### Parameters {/*event-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with no additional properties. + +--- + +### `InputEvent` handler function {/*inputevent-handler*/} + +An event handler type for the `onBeforeInput` event. + +```js +<input onBeforeInput={e => console.log('onBeforeInput')} /> +``` + +#### Parameters {/*inputevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent) properties: + * [`data`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data) + +--- + +### `KeyboardEvent` handler function {/*keyboardevent-handler*/} + +An event handler type for keyboard events. + +```js +<input + onKeyDown={e => console.log('onKeyDown')} + onKeyUp={e => console.log('onKeyUp')} +/> +``` + +[See an example.](#handling-keyboard-events) + +#### Parameters {/*keyboardevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) properties: + * [`altKey`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/altKey) + * [`charCode`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/charCode) + * [`code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) + * [`ctrlKey`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/ctrlKey) + * [`getModifierState(key)`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState) + * [`key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) + * [`keyCode`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode) + * [`locale`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/locale) + * [`metaKey`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/metaKey) + * [`location`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/location) + * [`repeat`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat) + * [`shiftKey`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/shiftKey) + * [`which`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/which) + + It also includes the inherited [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +### `MouseEvent` handler function {/*mouseevent-handler*/} + +An event handler type for mouse events. + +```js +<div + onClick={e => console.log('onClick')} + onMouseEnter={e => console.log('onMouseEnter')} + onMouseOver={e => console.log('onMouseOver')} + onMouseDown={e => console.log('onMouseDown')} + onMouseUp={e => console.log('onMouseUp')} + onMouseLeave={e => console.log('onMouseLeave')} +/> +``` + +[See an example.](#handling-mouse-events) + +#### Parameters {/*mouseevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) properties: + * [`altKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/altKey) + * [`button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button) + * [`buttons`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons) + * [`ctrlKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/ctrlKey) + * [`clientX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) + * [`clientY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY) + * [`getModifierState(key)`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/getModifierState) + * [`metaKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/metaKey) + * [`movementX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX) + * [`movementY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementY) + * [`pageX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX) + * [`pageY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY) + * [`relatedTarget`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/relatedTarget) + * [`screenX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenX) + * [`screenY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenY) + * [`shiftKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/shiftKey) + + It also includes the inherited [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +### `PointerEvent` handler function {/*pointerevent-handler*/} + +An event handler type for [pointer events.](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) + +```js +<div + onPointerEnter={e => console.log('onPointerEnter')} + onPointerMove={e => console.log('onPointerMove')} + onPointerDown={e => console.log('onPointerDown')} + onPointerUp={e => console.log('onPointerUp')} + onPointerLeave={e => console.log('onPointerLeave')} +/> +``` + +[See an example.](#handling-pointer-events) + +#### Parameters {/*pointerevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`PointerEvent`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) properties: + * [`height`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height) + * [`isPrimary`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary) + * [`pointerId`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId) + * [`pointerType`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType) + * [`pressure`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure) + * [`tangentialPressure`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tangentialPressure) + * [`tiltX`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX) + * [`tiltY`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY) + * [`twist`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/twist) + * [`width`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width) + + It also includes the inherited [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) properties: + + * [`altKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/altKey) + * [`button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button) + * [`buttons`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons) + * [`ctrlKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/ctrlKey) + * [`clientX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) + * [`clientY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY) + * [`getModifierState(key)`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/getModifierState) + * [`metaKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/metaKey) + * [`movementX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX) + * [`movementY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementY) + * [`pageX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX) + * [`pageY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY) + * [`relatedTarget`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/relatedTarget) + * [`screenX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenX) + * [`screenY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenY) + * [`shiftKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/shiftKey) + + It also includes the inherited [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +### `TouchEvent` handler function {/*touchevent-handler*/} + +An event handler type for [touch events.](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events) + +```js +<div + onTouchStart={e => console.log('onTouchStart')} + onTouchMove={e => console.log('onTouchMove')} + onTouchEnd={e => console.log('onTouchEnd')} + onTouchCancel={e => console.log('onTouchCancel')} +/> +``` + +#### Parameters {/*touchevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`TouchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent) properties: + * [`altKey`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/altKey) + * [`ctrlKey`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/ctrlKey) + * [`changedTouches`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/changedTouches) + * [`getModifierState(key)`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/getModifierState) + * [`metaKey`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/metaKey) + * [`shiftKey`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/shiftKey) + * [`touches`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/touches) + * [`targetTouches`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/targetTouches) + + It also includes the inherited [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +### `TransitionEvent` handler function {/*transitionevent-handler*/} + +An event handler type for the CSS transition events. + +```js +<div + onTransitionEnd={e => console.log('onTransitionEnd')} +/> +``` + +#### Parameters {/*transitionevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`TransitionEvent`](https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent) properties: + * [`elapsedTime`](https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent/elapsedTime) + * [`propertyName`](https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent/propertyName) + * [`pseudoElement`](https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent/pseudoElement) + +--- + +### `UIEvent` handler function {/*uievent-handler*/} + +An event handler type for generic UI events. + +```js +<div + onScroll={e => console.log('onScroll')} +/> +``` + +#### Parameters {/*uievent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +### `WheelEvent` handler function {/*wheelevent-handler*/} + +An event handler type for the `onWheel` event. + +```js +<div + onScroll={e => console.log('onScroll')} +/> +``` + +#### Parameters {/*wheelevent-handler-parameters*/} + +* `e`: A [React event object](#react-event-object) with these extra [`WheelEvent`](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent) properties: + * [`deltaMode`](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode) + * [`deltaX`](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaX) + * [`deltaY`](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaY) + * [`deltaZ`](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaZ) + + + It also includes the inherited [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) properties: + + * [`altKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/altKey) + * [`button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button) + * [`buttons`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons) + * [`ctrlKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/ctrlKey) + * [`clientX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) + * [`clientY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY) + * [`getModifierState(key)`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/getModifierState) + * [`metaKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/metaKey) + * [`movementX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX) + * [`movementY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementY) + * [`pageX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX) + * [`pageY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY) + * [`relatedTarget`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/relatedTarget) + * [`screenX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenX) + * [`screenY`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenY) + * [`shiftKey`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/shiftKey) + + It also includes the inherited [`UIEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) properties: + + * [`detail`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail) + * [`view`](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/view) + +--- + +## Usage {/*usage*/} + +### Applying CSS styles {/*applying-css-styles*/} + +In React, you specify a CSS class with [`className`.](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) It works like the `class` attribute in HTML: + +```js +<img className="avatar" /> +``` + +Then you write the CSS rules for it in a separate CSS file: + +```css +/* In your CSS */ +.avatar { + border-radius: 50%; +} +``` + +React does not prescribe how you add CSS files. In the simplest case, you'll add a [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) tag to your HTML. If you use a build tool or a framework, consult its documentation to learn how to add a CSS file to your project. + +Sometimes, the style values depend on data. Use the `style` attribute to pass some styles dynamically: + +```js {3-6} +<img + className="avatar" + style={{ + width: user.imageSize, + height: user.imageSize + }} +/> +``` + + +In the above example, `style={{}}` is not a special syntax, but a regular `{}` object inside the `style={ }` [JSX curly braces.](/learn/javascript-in-jsx-with-curly-braces) We recommend to only use the `style` attribute when your styles depend on JavaScript variables. + +<Sandpack> + +```js App.js +import Avatar from './Avatar.js'; + +const user = { + name: 'Hedy Lamarr', + imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg', + imageSize: 90, +}; + +export default function App() { + return <Avatar user={user} />; +} +``` + +```js Avatar.js active +export default function Avatar({ user }) { + return ( + <img + src={user.imageUrl} + alt={'Photo of ' + user.name} + className="avatar" + style={{ + width: user.imageSize, + height: user.imageSize + }} + /> + ); +} +``` + +```css styles.css +.avatar { + border-radius: 50%; +} +``` + +</Sandpack> + +<DeepDive> + +#### How to apply multiple CSS classes conditionally? {/*how-to-apply-multiple-css-classes-conditionally*/} + +To apply CSS classes conditionally, you need to produce the `className` string yourself using JavaScript. + +For example, `className={'row ' + (isSelected ? 'selected': '')}` will produce either `className="row"` or `className="row selected"` depending on whether `isSelected` is `true`. + +To make this more readable, you can use a tiny helper library like [`classnames`:](https://github.com/JedWatson/classnames) + +```js +import cn from 'classnames'; + +function Row({ isSelected }) { + return ( + <div className={cn('row', isSelected && 'selected')}> + ... + </div> + ); +} +``` + +It is especially convenient if you have multiple conditional classes: + +```js +import cn from 'classnames'; + +function Row({ isSelected, size }) { + return ( + <div className={cn('row', { + selected: isSelected, + large: size === 'large', + small: size === 'small', + })}> + ... + </div> + ); +} +``` + +</DeepDive> + +--- + +### Manipulating a DOM node with a ref {/*manipulating-a-dom-node-with-a-ref*/} + +Sometimes, you'll need to get the browser DOM node associated with a tag in JSX. For example, if you want to focus an `<input>` when a button is clicked, you need to call [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the browser `<input>` DOM node. + +To obtain the browser DOM node for a tag, [declare a ref](/reference/react/useRef) and pass it as the `ref` attribute to that tag: + +```js {7} +import { useRef } from 'react'; + +export default function Form() { + const inputRef = useRef(null); + // ... + return ( + <input ref={inputRef} /> + // ... +``` + +React will put the DOM node into `inputRef.current` after it's been rendered to the screen. + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <input ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +Read more about [manipulating DOM with refs](/learn/manipulating-the-dom-with-refs) and [check out more examples.](/reference/react/useRef#examples-dom) + +For more advanced use cases, the `ref` attribute also accepts a [callback function.](#ref-callback) + +--- + +### Dangerously setting the inner HTML {/*dangerously-setting-the-inner-html*/} + +You can pass a raw HTML string to an element like so: + +```js +const markup = { __html: '<p>some raw html</p>' }; +return <div dangerouslySetInnerHTML={markup} />; +``` + +**This is dangerous. As with the underlying DOM [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property, you must exercise extreme caution! Unless the markup is coming from a completely trusted source, it is trivial to introduce an [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerability this way.** + +For example, if you use a Markdown library that converts Markdown to HTML, you trust that its parser doesn't contain bugs, and the user only sees their own input, you can display the resulting HTML like this: + +<Sandpack> + +```js +import { useState } from 'react'; +import MarkdownPreview from './MarkdownPreview.js'; + +export default function MarkdownEditor() { + const [postContent, setPostContent] = useState('_Hello,_ **Markdown**!'); + return ( + <> + <label> + Enter some markdown: + <textarea + value={postContent} + onChange={e => setPostContent(e.target.value)} + /> + </label> + <hr /> + <MarkdownPreview markdown={postContent} /> + </> + ); +} +``` + +```js MarkdownPreview.js active +import { Remarkable } from 'remarkable'; + +const md = new Remarkable(); + +function renderMarkdownToHTML(markdown) { + // This is ONLY safe because the output HTML + // is shown to the same user, and because you + // trust this Markdown parser to not have bugs. + const renderedHTML = md.render(markdown); + return {__html: renderedHTML}; +} + +export default function MarkdownPreview({ markdown }) { + const markup = renderMarkdownToHTML(markdown); + return <div dangerouslySetInnerHTML={markup} />; +} +``` + +```json package.json +{ + "dependencies": { + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "remarkable": "2.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +textarea { display: block; margin-top: 5px; margin-bottom: 10px; } +``` + +</Sandpack> + +To see why rendering arbitrary HTML is dangerous, replace the code above with this: + +```js {1-4,7,8} +const post = { + // Imagine this content is stored in the database. + content: `<img src="" onerror='alert("you were hacked")'>` +}; + +export default function MarkdownPreview() { + // 🔴 SECURITY HOLE: passing untrusted input to dangerouslySetInnerHTML + const markup = { __html: post.content }; + return <div dangerouslySetInnerHTML={markup} />; +} +``` + +The code embedded in the HTML will run. A hacker could use this security hole to steal user information or to perform actions on their behalf. **Only use `dangerouslySetInnerHTML` with trusted and sanitized data.** + +--- + +### Handling mouse events {/*handling-mouse-events*/} + +This example shows some common [mouse events](#mouseevent-handler) and when they fire. + +<Sandpack> + +```js +export default function MouseExample() { + return ( + <div + onMouseEnter={e => console.log('onMouseEnter (parent)')} + onMouseLeave={e => console.log('onMouseLeave (parent)')} + > + <button + onClick={e => console.log('onClick (first button)')} + onMouseDown={e => console.log('onMouseDown (first button)')} + onMouseEnter={e => console.log('onMouseEnter (first button)')} + onMouseLeave={e => console.log('onMouseLeave (first button)')} + onMouseOver={e => console.log('onMouseOver (first button)')} + onMouseUp={e => console.log('onMouseUp (first button)')} + > + First button + </button> + <button + onClick={e => console.log('onClick (second button)')} + onMouseDown={e => console.log('onMouseDown (second button)')} + onMouseEnter={e => console.log('onMouseEnter (second button)')} + onMouseLeave={e => console.log('onMouseLeave (second button)')} + onMouseOver={e => console.log('onMouseOver (second button)')} + onMouseUp={e => console.log('onMouseUp (second button)')} + > + Second button + </button> + </div> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 10px; } +``` + +</Sandpack> + +--- + +### Handling pointer events {/*handling-pointer-events*/} + +This example shows some common [pointer events](#pointerevent-handler) and when they fire. + +<Sandpack> + +```js +export default function PointerExample() { + return ( + <div + onPointerEnter={e => console.log('onPointerEnter (parent)')} + onPointerLeave={e => console.log('onPointerLeave (parent)')} + style={{ padding: 20, backgroundColor: '#ddd' }} + > + <div + onPointerDown={e => console.log('onPointerDown (first child)')} + onPointerEnter={e => console.log('onPointerEnter (first child)')} + onPointerLeave={e => console.log('onPointerLeave (first child)')} + onPointerMove={e => console.log('onPointerMove (first child)')} + onPointerUp={e => console.log('onPointerUp (first child)')} + style={{ padding: 20, backgroundColor: 'lightyellow' }} + > + First child + </div> + <div + onPointerDown={e => console.log('onPointerDown (second child)')} + onPointerEnter={e => console.log('onPointerEnter (second child)')} + onPointerLeave={e => console.log('onPointerLeave (second child)')} + onPointerMove={e => console.log('onPointerMove (second child)')} + onPointerUp={e => console.log('onPointerUp (second child)')} + style={{ padding: 20, backgroundColor: 'lightblue' }} + > + Second child + </div> + </div> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 10px; } +``` + +</Sandpack> + +--- + +### Handling focus events {/*handling-focus-events*/} + +In React, [focus events](#focusevent-handler) bubble. You can use the `currentTarget` and `relatedTarget` to differentiate if the focusing or blurring events originated from outside of the parent element. The example shows how to detect focusing a child, focusing the parent element, and how to detect focus entering or leaving the whole subtree. + +<Sandpack> + +```js +export default function FocusExample() { + return ( + <div + tabIndex={1} + onFocus={(e) => { + if (e.currentTarget === e.target) { + console.log('focused parent'); + } else { + console.log('focused child', e.target.name); + } + if (!e.currentTarget.contains(e.relatedTarget)) { + // Not triggered when swapping focus between children + console.log('focus entered parent'); + } + }} + onBlur={(e) => { + if (e.currentTarget === e.target) { + console.log('unfocused parent'); + } else { + console.log('unfocused child', e.target.name); + } + if (!e.currentTarget.contains(e.relatedTarget)) { + // Not triggered when swapping focus between children + console.log('focus left parent'); + } + }} + > + <label> + First name: + <input name="firstName" /> + </label> + <label> + Last name: + <input name="lastName" /> + </label> + </div> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 10px; } +``` + +</Sandpack> + +--- + +### Handling keyboard events {/*handling-keyboard-events*/} + +This example shows some common [keyboard events](#keyboardevent-handler) and when they fire. + +<Sandpack> + +```js +export default function KeyboardExample() { + return ( + <label> + First name: + <input + name="firstName" + onKeyDown={e => console.log('onKeyDown:', e.key, e.code)} + onKeyUp={e => console.log('onKeyUp:', e.key, e.code)} + /> + </label> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 10px; } +``` + +</Sandpack> diff --git a/beta/src/content/reference/react-dom/components/index.md b/beta/src/content/reference/react-dom/components/index.md new file mode 100644 index 000000000..877ff97fc --- /dev/null +++ b/beta/src/content/reference/react-dom/components/index.md @@ -0,0 +1,261 @@ +--- +title: "React DOM Components" +--- + +<Intro> + +React supports all of the browser built-in [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) and [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Element) components. + +</Intro> + +--- + +## Common components {/*common-components*/} + +All of the built-in browser components support some props and events. + +* [Common components (e.g. `<div>`)](/reference/react-dom/components/common) + +This includes React-specific props like `ref` and `dangerouslySetInnerHTML`. + +--- + +## Form components {/*form-components*/} + +These built-in browser components accept user input: + +* [`<input>`](/reference/react-dom/components/input) +* [`<select>`](/reference/react-dom/components/select) +* [`<textarea>`](/reference/react-dom/components/textarea) + +They are special in React because passing the `value` prop to them makes them *[controlled.](/reference/react-dom/components/input#controlling-an-input-with-a-state-variable)* + +--- + +## All HTML components {/*all-html-components*/} + +React supports all built-in browser HTML components. This includes: + +* [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside) +* [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio) +* [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b) +* [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) +* [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi) +* [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo) +* [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote) +* [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body) +* [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br) +* [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button) +* [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas) +* [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption) +* [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite) +* [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code) +* [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col) +* [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup) +* [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data) +* [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist) +* [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd) +* [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del) +* [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) +* [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn) +* [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) +* [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div) +* [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl) +* [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt) +* [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em) +* [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed) +* [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset) +* [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption) +* [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure) +* [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer) +* [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) +* [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1) +* [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) +* [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header) +* [`<hgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup) +* [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr) +* [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html) +* [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i) +* [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) +* [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) +* [`<input>`](/reference/react-dom/components/input) +* [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins) +* [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd) +* [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) +* [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend) +* [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li) +* [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) +* [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main) +* [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map) +* [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark) +* [`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu) +* [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) +* [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter) +* [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav) +* [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript) +* [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object) +* [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol) +* [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup) +* [`<option>`](/reference/react-dom/components/option) +* [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output) +* [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p) +* [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) +* [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre) +* [`<progress>`](/reference/react-dom/components/progress) +* [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q) +* [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp) +* [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt) +* [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby) +* [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s) +* [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp) +* [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) +* [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section) +* [`<select>`](/reference/react-dom/components/select) +* [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) +* [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small) +* [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source) +* [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span) +* [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong) +* [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style) +* [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub) +* [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) +* [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup) +* [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table) +* [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody) +* [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td) +* [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) +* [`<textarea>`](/reference/react-dom/components/textarea) +* [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot) +* [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th) +* [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead) +* [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time) +* [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title) +* [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr) +* [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track) +* [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u) +* [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul) +* [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var) +* [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) +* [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr) + +<Note> + +Similar to the [DOM standard,](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) React uses a `camelCase` convention for prop names. For example, you'll write `tabIndex` instead of `tabindex`. You can convert existing HTML to JSX with an [online converter.](https://transform.tools/html-to-jsx) + +</Note> + +--- + +### Custom HTML elements {/*custom-html-elements*/} + +If you render a tag with a dash, like `<my-element>`, React will assume you want to render a [custom HTML element.](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) In React, rendering custom elements works differently from rendering built-in browser tags: + +- All custom element props are serialized to strings and are always set using attributes. +- Custom elements accept `class` rather than `className`, and `for` rather than `htmlFor`. + +If you render a built-in browser HTML element with an [`is`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/is) attribute, it will also be treated as a custom element. + +<Note> + +[A future version of React will include more comprehensive support for custom elements.](https://github.com/facebook/react/issues/11347#issuecomment-1122275286) + +You can try it by upgrading React packages to the most recent experimental version: + +- `react@experimental` +- `react-dom@experimental` + +Experimental versions of React may contain bugs. Don't use them in production. + +</Note> +--- + +## All SVG components {/*all-svg-components*/} + +React supports all built-in browser SVG components. This includes: + +* [`<a>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a) +* [`<animate>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate) +* [`<animateMotion>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateMotion) +* [`<animateTransform>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateTransform) +* [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle) +* [`<clipPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath) +* [`<defs>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs) +* [`<desc>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc) +* [`<discard>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/discard) +* [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse) +* [`<feBlend>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend) +* [`<feColorMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix) +* [`<feComponentTransfer>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer) +* [`<feComposite>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite) +* [`<feConvolveMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix) +* [`<feDiffuseLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDiffuseLighting) +* [`<feDisplacementMap>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap) +* [`<feDistantLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDistantLight) +* [`<feDropShadow>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow) +* [`<feFlood>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood) +* [`<feFuncA>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncA) +* [`<feFuncB>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncB) +* [`<feFuncG>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncG) +* [`<feFuncR>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncR) +* [`<feGaussianBlur>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur) +* [`<feImage>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feImage) +* [`<feMerge>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMerge) +* [`<feMergeNode>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMergeNode) +* [`<feMorphology>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMorphology) +* [`<feOffset>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feOffset) +* [`<fePointLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/fePointLight) +* [`<feSpecularLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpecularLighting) +* [`<feSpotLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpotLight) +* [`<feTile>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile) +* [`<feTurbulence>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTurbulence) +* [`<filter>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter) +* [`<foreignObject>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject) +* [`<g>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g) +* `<hatch>` +* `<hatchpath>` +* [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image) +* [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line) +* [`<linearGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient) +* [`<marker>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker) +* [`<mask>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mask) +* [`<metadata>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/metadata) +* [`<mpath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mpath) +* [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path) +* [`<pattern>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/pattern) +* [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon) +* [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline) +* [`<radialGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient) +* [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect) +* [`<script>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script) +* [`<set>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/set) +* [`<stop>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop) +* [`<style>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/style) +* [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg) +* [`<switch>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/switch) +* [`<symbol>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/symbol) +* [`<text>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text) +* [`<textPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath) +* [`<title>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title) +* [`<tspan>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan) +* [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use) +* [`<view>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/view) + +<Note> + +Similar to the [DOM standard,](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) React uses a `camelCase` convention for prop names. For example, you'll write `tabIndex` instead of `tabindex`. You can convert existing SVG to JSX with an [online converter.](https://transform.tools/) + +Namespaced attributes also have to be written without the colon: + +* `xlink:actuate` becomes `xlinkActuate`. +* `xlink:arcrole` becomes `xlinkArcrole`. +* `xlink:href` becomes `xlinkHref`. +* `xlink:role` becomes `xlinkRole`. +* `xlink:show` becomes `xlinkShow`. +* `xlink:title` becomes `xlinkTitle`. +* `xlink:type` becomes `xlinkType`. +* `xml:base` becomes `xmlBase`. +* `xml:lang` becomes `xmlLang`. +* `xml:space` becomes `xmlSpace`. +* `xmlns:xlink` becomes `xmlnsXlink`. + +</Note> diff --git a/beta/src/content/reference/react-dom/components/input.md b/beta/src/content/reference/react-dom/components/input.md new file mode 100644 index 000000000..497713c07 --- /dev/null +++ b/beta/src/content/reference/react-dom/components/input.md @@ -0,0 +1,595 @@ +--- +title: "<input>" +--- + +<Intro> + +The [built-in browser `<input>` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) lets you render different kinds of form inputs. + +```js +<input /> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<input>` {/*input*/} + +To display an input, render the [built-in browser `<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) component. + +```js +<input name="myInput" /> +``` + +[See more examples below.](#usage) + +#### Props {/*props*/} + +`<input>` supports all [common element props.](/reference/react-dom/components/common#props) + +You can [make an input controlled](#controlling-an-input-with-a-state-variable) by passing one of these props: + +* [`checked`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#checked): A boolean. For a checkbox input or a radio button, controls whether it is selected. +* [`value`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#value): A string. For a text input, controls its text. (For a radio button, specifies its form data.) + +When you pass either of them, you must also pass an `onChange` handler that updates the passed value. + +These `<input>` props are only relevant for uncontrolled inputs: + +* [`defaultChecked`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#defaultChecked): A boolean. Specifies [the initial value](#providing-an-initial-value-for-an-input) for `type="checkbox"` and `type="radio"` inputs. +* [`defaultValue`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#defaultValue): A string. Specifies [the initial value](#providing-an-initial-value-for-an-input) for a text input. + +These `<input>` props are relevant both for uncontrolled and controlled inputs: + +* [`accept`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#accept): A string. Specifies which filetypes are accepted by a `type="file"` input. +* [`alt`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#alt): A string. Specifies the alternative image text for a `type="image"` input. +* [`capture`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#capture): A string. Specifies the media (microphone, video, or camera) captured by a `type="file"` input. +* [`autoComplete`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#autocomplete): A string. Specifies one of the possible [autocomplete behaviors.](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values) +* [`autoFocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#autofocus): A boolean. If `true`, React will focus the element on mount. +* [`dirname`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#dirname): A string. Specifies the form field name for the element's directionality. +* [`disabled`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#disabled): A boolean. If `true`, the input will not be interactive and will appear dimmed. +* `children`: `<input>` does not accept children. +* [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#form): A string. Specifies the `id` of the `<form>` this input belongs to. If omitted, it's the closest parent form. +* [`formAction`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#formaction): A string. Overrides the parent `<form action>` for `type="submit"` and `type="image"`. +* [`formEnctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#formenctype): A string. Overrides the parent `<form enctype>` for `type="submit"` and `type="image"`. +* [`formMethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#formmethod): A string. Overrides the parent `<form method>` for `type="submit"` and `type="image"`. +* [`formNoValidate`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#formnovalidate): A string. Overrides the parent `<form noValidate>` for `type="submit"` and `type="image"`. +* [`formTarget`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#formtarget): A string. Overrides the parent `<form target>` for `type="submit"` and `type="image"`. +* [`height`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#height): A string. Specifies the image height for `type="image"`. +* [`list`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#list): A string. Specifies the `id` of the `<datalist>` with the autocomplete options. +* [`max`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#max): A number. Specifies the maximum value of numerical and datetime inputs. +* [`maxLength`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#maxlength): A number. Specifies the maximum length of text and other inputs. +* [`min`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#min): A number. Specifies the minimum value of numerical and datetime inputs. +* [`minLength`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#minlength): A number. Specifies the minimum length of text and other inputs. +* [`multiple`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#multiple): A boolean. Specifies whether multiple values are allowed for `<type="file"` and `type="email"`. +* [`name`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#name): A string. Specifies the name for this input that's [submitted with the form.](#reading-the-input-values-when-submitting-a-form) +* `onChange`: An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Required for [controlled inputs.](#controlling-an-input-with-a-state-variable) Fires immediately when the input's value is changed by the user (for example, it fires on every keystroke). Behaves like the browser [`input` event.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event) +* `onChangeCapture`: A version of `onChange` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onInput`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires immediately when the value is changed by the user. For historical reasons, in React it is idiomatic to use `onChange` instead which works similarly. +* `onInputCapture`: A version of `onInput` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onInvalid`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/invalid_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires if an input fails validation on form submit. Unlike the built-in `invalid` event, the React `onInvalid` event bubbles. +* `onInvalidCapture`: A version of `onInvalid` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onSelect`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires after the selection inside the `<input>` changes. React extends the `onSelect` event to also fire for empty selection and on edits (which may affect the selection). +* `onSelectCapture`: A version of `onSelect` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`pattern`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#pattern): A string. Specifies the pattern that the `value` must match. +* [`placeholder`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#placeholder): A string. Displayed in a dimmed color when the input value is empty. +* [`readOnly`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#readonly): A boolean. If `true`, the input is not editable by the user. +* [`required`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#required): A boolean. If `true`, the value must be provided for the form to submit. +* [`size`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#size): A number. Similar to setting width, but the unit depends on the control. +* [`src`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#src): A string. Specifies the image source for a `type="image"` input. +* [`step`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step): A positive number or an `'any'` string. Specifies the distance between valid values. +* [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#type): A string. One of the [input types.](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types) +* [`width`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#width): A string. Specifies the image width for a `type="image"` input. + +#### Caveats {/*caveats*/} + +- Checkboxes need `checked` (or `defaultChecked`), not `value` (or `defaultValue`). +- If a text input receives a string `value` prop, it will be [treated as controlled.](#controlling-an-input-with-a-state-variable) +- If a checkbox or a radio button receives a boolean `checked` prop, it will be [treated as controlled.](#controlling-an-input-with-a-state-variable) +- An input can't be both controlled and uncontrolled at the same time. +- An input cannot switch between being controlled or uncontrolled over its lifetime. +- Every controlled input needs an `onChange` event handler that synchronously updates its backing value. + +--- + +## Usage {/*usage*/} + +### Displaying inputs of different types {/*displaying-inputs-of-different-types*/} + +To display an input, render an `<input>` component. By default, it will be a text input. You can pass `type="checkbox"` for a checkbox, `type="radio"` for a radio button, [or one of the other input types.](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types) + +<Sandpack> + +```js +export default function MyForm() { + return ( + <> + <label> + Text input: <input name="myInput" /> + </label> + <hr /> + <label> + Checkbox: <input type="checkbox" name="myCheckbox" /> + </label> + <hr /> + <p> + Radio buttons: + <label> + <input type="radio" name="myRadio" value="option1" /> + Option 1 + </label> + <label> + <input type="radio" name="myRadio" value="option2" /> + Option 2 + </label> + <label> + <input type="radio" name="myRadio" value="option3" /> + Option 3 + </label> + </p> + </> + ); +} +``` + +```css +label { display: block; } +input { margin: 5px; } +``` + +</Sandpack> + +--- + +### Providing a label for an input {/*providing-a-label-for-an-input*/} + +Typically, you will place every `<input>` inside a [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) tag. This tells the browser that this label is associated with that input. When the user clicks the label, the browser will automatically focus the input. It's also essential for accessibility: a screen reader will announce the label caption when the user focuses the associated input. + +If you can't nest `<input>` into a `<label>`, associate them by passing the same ID to `<input id>` and [`<label htmlFor>`.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/htmlFor) To avoid conflicts between multiple instances of one component, generate such an ID with [`useId`.](/reference/react/useId) + +<Sandpack> + +```js +import { useId } from 'react'; + +export default function Form() { + const ageInputId = useId(); + return ( + <> + <label> + Your first name: + <input name="firstName" /> + </label> + <hr /> + <label htmlFor={ageInputId}>Your age:</label> + <input id={ageInputId} name="age" type="number" /> + </> + ); +} +``` + +```css +input { margin: 5px; } +``` + +</Sandpack> + +--- + +### Providing an initial value for an input {/*providing-an-initial-value-for-an-input*/} + +You can optionally specify the initial value for any input. Pass it as the `defaultValue` string for text inputs. Checkboxes and radio buttons should specify the initial value with the `defaultChecked` boolean instead. + +<Sandpack> + +```js +export default function MyForm() { + return ( + <> + <label> + Text input: <input name="myInput" defaultValue="Some initial value" /> + </label> + <hr /> + <label> + Checkbox: <input type="checkbox" name="myCheckbox" defaultChecked={true} /> + </label> + <hr /> + <p> + Radio buttons: + <label> + <input type="radio" name="myRadio" value="option1" /> + Option 1 + </label> + <label> + <input + type="radio" + name="myRadio" + value="option2" + defaultChecked={true} + /> + Option 2 + </label> + <label> + <input type="radio" name="myRadio" value="option3" /> + Option 3 + </label> + </p> + </> + ); +} +``` + +```css +label { display: block; } +input { margin: 5px; } +``` + +</Sandpack> + +--- + +### Reading the input values when submitting a form {/*reading-the-input-values-when-submitting-a-form*/} + +Add a [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) around your inputs with a [`<button type="submit">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button) inside. It will call your `<form onSubmit>` event handler. By default, the browser will send the form data to the current URL and refresh the page. You can override that behavior by calling `e.preventDefault()`. To read the form data, use [`new FormData(e.target)`](https://developer.mozilla.org/en-US/docs/Web/API/FormData). +<Sandpack> + +```js +export default function MyForm() { + function handleSubmit(e) { + // Prevent the browser from reloading the page + e.preventDefault(); + + // Read the form data + const form = e.target; + const formData = new FormData(form); + + // You can pass formData as a fetch body directly: + fetch('/some-api', { method: form.method, body: formData }); + + // Or you can work with it as a plain object: + const formJson = Object.fromEntries(formData.entries()); + console.log(formJson); + } + + return ( + <form method="post" onSubmit={handleSubmit}> + <label> + Text input: <input name="myInput" defaultValue="Some initial value" /> + </label> + <hr /> + <label> + Checkbox: <input type="checkbox" name="myCheckbox" defaultChecked={true} /> + </label> + <hr /> + <p> + Radio buttons: + <label><input type="radio" name="myRadio" value="option1" /> Option 1</label> + <label><input type="radio" name="myRadio" value="option2" defaultChecked={true} /> Option 2</label> + <label><input type="radio" name="myRadio" value="option3" /> Option 3</label> + </p> + <hr /> + <button type="reset">Reset form</button> + <button type="submit">Submit form</button> + </form> + ); +} +``` + +```css +label { display: block; } +input { margin: 5px; } +``` + +</Sandpack> + +<Note> + +Give a `name` to every `<input>`, for example `<input name="firstName" defaultValue="Taylor" />`. The `name` you specified will be used as a key in the form data, for example `{ firstName: "Taylor" }`. + +</Note> + +<Pitfall> + +By default, *any* `<button>` inside a `<form>` will submit it. This can be surprising! If you have your own custom `Button` React component, consider returning [`<button type="button">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/button) instead of `<button>`. Then, to be explicit, use `<button type="submit">` for buttons that *are* supposed to submit the form. + +</Pitfall> + +--- + +### Controlling an input with a state variable {/*controlling-an-input-with-a-state-variable*/} + +An input like `<input />` is *uncontrolled.* Even if you [pass an initial value](#providing-an-initial-value-for-an-input) like `<input defaultValue="Initial text" />`, your JSX only specifies the initial value. It does not control what the value should be right now. + +**To render a _controlled_ input, pass the `value` prop to it (or `checked` for checkboxes and radios).** React will force the input to always have the `value` you passed. Typically, you will control an input by declaring a [state variable:](/reference/react/useState) + +```js {2,6,7} +function Form() { + const [firstName, setFirstName] = useState(''); // Declare a state variable... + // ... + return ( + <input + value={firstName} // ...force the input's value to match the state variable... + onChange={e => setFirstName(e.target.value)} // ... and update the state variable on any edits! + /> + ); +} +``` + +A controlled input makes sense if you needed state anyway--for example, to re-render your UI on every edit: + +```js {2,9} +function Form() { + const [firstName, setFirstName] = useState(''); + return ( + <> + <label> + First name: + <input value={firstName} onChange={e => setFirstName(e.target.value)} /> + </label> + {firstName !== '' && <p>Your name is {firstName}.</p>} + ... +``` + +It's also useful if you want to offer multiple ways to adjust the input state (for example, by clicking a button): + +```js {3-4,10-11,14} +function Form() { + // ... + const [age, setAge] = useState(''); + const ageAsNumber = Number(age); + return ( + <> + <label> + Age: + <input + value={age} + onChange={e => setAge(e.target.value)} + type="number" + /> + <button onClick={() => setAge(ageAsNumber + 10)}> + Add 10 years + </button> +``` + +The `value` you pass to controlled components should not be `undefined` or `null`. If you need the initial value to be empty (such as with the `firstName` field below), initialize your state variable to an empty string (`''`). + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [firstName, setFirstName] = useState(''); + const [age, setAge] = useState('20'); + const ageAsNumber = Number(age); + return ( + <> + <label> + First name: + <input + value={firstName} + onChange={e => setFirstName(e.target.value)} + /> + </label> + <label> + Age: + <input + value={age} + onChange={e => setAge(e.target.value)} + type="number" + /> + <button onClick={() => setAge(ageAsNumber + 10)}> + Add 10 years + </button> + </label> + {firstName !== '' && + <p>Your name is {firstName}.</p> + } + {ageAsNumber > 0 && + <p>Your age is {ageAsNumber}.</p> + } + </> + ); +} +``` + +```css +label { display: block; } +input { margin: 5px; } +p { font-weight: bold; } +``` + +</Sandpack> + +<Pitfall> + +**If you pass `value` without `onChange`, it will be impossible to type into the input.** When you control an input by passing some `value` to it, you *force* it to always have the value you passed. So if you pass a state variable as a `value` but forget to update that state variable synchronously during the `onChange` event handler, React will revert the input after every keystroke back to the `value` that you specified. + +</Pitfall> + +--- + +### Optimizing re-rendering on every keystroke {/*optimizing-re-rendering-on-every-keystroke*/} + +When you use a controlled input, you set the state on every keystroke. If the component containing your state re-renders a large tree, this can get slow. There's a few ways you can optimize re-rendering performance. + +For example, suppose you start with a form that re-renders all page content on every keystroke: + +```js {5-8} +function App() { + const [firstName, setFirstName] = useState(''); + return ( + <> + <form> + <input value={firstName} onChange={e => setFirstName(e.target.value)} /> + </form> + <PageContent /> + </> + ); +} +``` + +Since `<PageContent />` doesn't rely on the input state, you can move the input state into its own component: + +```js {4,10-17} +function App() { + return ( + <> + <SignupForm /> + <PageContent /> + </> + ); +} + +function SignupForm() { + const [firstName, setFirstName] = useState(''); + return ( + <form> + <input value={firstName} onChange={e => setFirstName(e.target.value)} /> + </form> + ); +} +``` + +This significantly improves performance because now only `SignupForm` re-renders on every keystroke. + +If there is no way to avoid re-rendering (for example, if `PageContent` depends on the search input's value), [`useDeferredValue`](/reference/react/useDeferredValue#deferring-re-rendering-for-a-part-of-the-ui) lets you keep the controlled input responsive even in the middle of a large re-render. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My text input doesn't update when I type into it {/*my-text-input-doesnt-update-when-i-type-into-it*/} + +If you render an input with `value` but no `onChange`, you will see an error in the console: + +```js +// 🔴 Bug: controlled text input with no onChange handler +<input value={something} /> +``` + +<ConsoleBlock level="error"> + +You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`. + +</ConsoleBlock> + +As the error message suggests, if you only wanted to [specify the *initial* value,](#providing-an-initial-value-for-an-input) pass `defaultValue` instead: + +```js +// ✅ Good: uncontrolled input with an initial value +<input defaultValue={something} /> +``` + +If you want [to control this input with a state variable,](#controlling-an-input-with-a-state-variable) specify an `onChange` handler: + +```js +// ✅ Good: controlled input with onChange +<input value={something} onChange={e => setSomething(e.target.value)} /> +``` + +If the value is intentionally read-only, add a `readOnly` prop to suppress the error: + +```js +// ✅ Good: readonly controlled input without on change +<input value={something} readOnly={true} /> +``` + +--- + +### My checkbox doesn't update when I click on it {/*my-checkbox-doesnt-update-when-i-click-on-it*/} + +If you render a checkbox with `checked` but no `onChange`, you will see an error in the console: + +```js +// 🔴 Bug: controlled checkbox with no onChange handler +<input type="checkbox" checked={something} /> +``` + +<ConsoleBlock level="error"> + +You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`. + +</ConsoleBlock> + +As the error message suggests, if you only wanted to [specify the *initial* value,](#providing-an-initial-value-for-an-input) pass `defaultChecked` instead: + +```js +// ✅ Good: uncontrolled checkbox with an initial value +<input type="checkbox" defaultChecked={something} /> +``` + +If you want [to control this checkbox with a state variable,](#controlling-an-input-with-a-state-variable) specify an `onChange` handler: + +```js +// ✅ Good: controlled checkbox with onChange +<input type="checkbox" checked={something} onChange={e => setSomething(e.target.checked)} /> +``` + +<Pitfall> + +You need to read `e.target.checked` rather than `e.target.value` for checkboxes. + +</Pitfall> + +If the checkbox is intentionally read-only, add a `readOnly` prop to suppress the error: + +```js +// ✅ Good: readonly controlled input without on change +<input type="checkbox" checked={something} readOnly={true} /> +``` + +--- + +### My input caret jumps to the beginning on every keystroke {/*my-input-caret-jumps-to-the-beginning-on-every-keystroke*/} + +If you [control an input,](#controlling-an-input-with-a-state-variable) you must update its state variable to the input's value from the DOM during `onChange`. + +You can't update it to something other than `e.target.value` (or `e.target.checked` for checkboxes): + +```js +function handleChange(e) { + // 🔴 Bug: updating an input to something other than e.target.value + setFirstName(e.target.value.toUpperCase()); +} +``` + +You also can't update it asynchronously: + +```js +function handleChange(e) { + // 🔴 Bug: updating an input asynchronously + setTimeout(() => { + setFirstName(e.target.value); + }, 100); +} +``` + +To fix your code, update it synchronously to `e.target.value`: + +```js +function handleChange(e) { + // ✅ Updating a controlled input to e.target.value synchronously + setFirstName(e.target.value); +} +``` + +If this doesn't fix the problem, it's possible that the input gets removed and re-added from the DOM on every keystroke. This can happen if you're accidentally [resetting state](/learn/preserving-and-resetting-state) on every re-render. For example, this can happen if the input or one of its parents always receives a different `key` attribute, or if you nest component definitions (which is not allowed in React and causes the "inner" component to always be considered a different tree). + +--- + +### I'm getting an error: "A component is changing an uncontrolled input to be controlled" {/*im-getting-an-error-a-component-is-changing-an-uncontrolled-input-to-be-controlled*/} + + +If you provide a `value` to the component, it must remain a string throughout its lifetime. + +You cannot pass `value={undefined}` first and later pass `value="some string"` because React won't know whether you want the component to be uncontrolled or controlled. A controlled component should always receive a string `value`, not `null` or `undefined`. + +If your `value` is coming from an API or a state variable, it might be initialized to `null` or `undefined`. In that case, either set it to an empty string (`''`) initially, or pass `value={someValue ?? ''}` to ensure `value` is a string. + +Similarly, if you pass `checked` to a checkbox, ensure it's always a boolean. diff --git a/beta/src/content/reference/react-dom/components/option.md b/beta/src/content/reference/react-dom/components/option.md new file mode 100644 index 000000000..be1db0d61 --- /dev/null +++ b/beta/src/content/reference/react-dom/components/option.md @@ -0,0 +1,83 @@ +--- +title: "<option>" +--- + +<Intro> + +The [built-in browser `<option>` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option) lets you render an option inside a [`<select>`](/reference/react-dom/components/select) box. + +```js +<select> + <option value="someOption">Some option</option> + <option value="otherOption">Other option</option> +</select> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<option>` {/*option*/} + +The [built-in browser `<option>` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) lets you render an option inside a [`<select>`](/reference/react-dom/components/select) box. + +```js +<select> + <option value="someOption">Some option</option> + <option value="otherOption">Other option</option> +</select> +``` + +[See more examples below.](#usage) + +#### Props {/*props*/} + +`<option>` supports all [common element props.](/reference/react-dom/components/common#props) + +Additionally, `<option>` supports these props: + +* [`disabled`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#disabled): A boolean. If `true`, the option will not be selectable and will appear dimmed. +* [`label`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#label): A string. Specifies the meaning of the option. If not specified, the text inside the option is used. +* [`value`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#value): The value to be used [when submitting the parent `<select>` in a form](/reference/react-dom/components/select#reading-the-select-box-value-when-submitting-a-form) if this option is selected. + +#### Caveats {/*caveats*/} + +* React does not support the `selected` attribute on `<option>`. Instead, pass this option's `value` to the parent [`<select defaultValue>`](/reference/react-dom/components/select#providing-an-initially-selected-option) for an uncontrolled select box, or [`<select value>`](/reference/react-dom/components/select#controlling-a-select-box-with-a-state-variable) for a controlled select box. + +--- + +## Usage {/*usage*/} + +### Displaying a select box with options {/*displaying-a-select-box-with-options*/} + +Render a `<select>` with a list of `<option>` components inside to display a select box. Give each `<option>` a `value` representing the data to be submitted with the form. + +[Read more about displaying a `<select>` with a list of `<option>` components.](/reference/react-dom/components/select) + +<Sandpack> + +```js +export default function FruitPicker() { + return ( + <label> + Pick a fruit: + <select name="selectedFruit"> + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + </label> + ); +} +``` + +```css +select { margin: 5px; } +``` + +</Sandpack> + diff --git a/beta/src/content/reference/react-dom/components/progress.md b/beta/src/content/reference/react-dom/components/progress.md new file mode 100644 index 000000000..fd6c96a1e --- /dev/null +++ b/beta/src/content/reference/react-dom/components/progress.md @@ -0,0 +1,71 @@ +--- +title: "<progress>" +--- + +<Intro> + +The [built-in browser `<progress>` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress) lets you render a progress indicator. + +```js +<progress value={0.5} /> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<progress>` {/*progress*/} + +To display a progress indicator, render the [built-in browser `<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress) component. + +```js +<progress value={0.5} /> +``` + +[See more examples below.](#usage) + +#### Props {/*props*/} + +`<progress>` supports all [common element props.](/reference/react-dom/components/common#props) + +Additionally, `<progress>` supports these props: + +* [`max`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress#attr-max): A number. Specifies the maximum `value`. Defaults to `1`. +* [`value`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress#attr-value): A number between `0` and `max`, or `null` for intermedinate progress. Specifies how much was done. + +--- + +## Usage {/*usage*/} + +### Controlling a progress indicator {/*controlling-a-progress-indicator*/} + +To display a progress indicator, render a `<progress>` component. You can pass a number `value` between `0` and the `max` value you specify. If you don't pass a `max` value, it will assumed to be `1` by default. + +If the operation is not ongoing, pass `value={null}` to put the progress indicator into an indeterminate state. + +<Sandpack> + +```js +export default function App() { + return ( + <> + <progress value={0} /> + <progress value={0.5} /> + <progress value={0.7} /> + <progress value={75} max={100} /> + <progress value={1} /> + <progress value={null} /> + </> + ); +} +``` + +```css +progress { display: block; } +``` + +</Sandpack> diff --git a/beta/src/content/reference/react-dom/components/select.md b/beta/src/content/reference/react-dom/components/select.md new file mode 100644 index 000000000..de351d394 --- /dev/null +++ b/beta/src/content/reference/react-dom/components/select.md @@ -0,0 +1,384 @@ +--- +title: "<select>" +--- + +<Intro> + +The [built-in browser `<select>` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) lets you render a select box with options. + +```js +<select> + <option value="someOption">Some option</option> + <option value="otherOption">Other option</option> +</select> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<select>` {/*select*/} + +To display a select box, render the [built-in browser `<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) component. + +```js +<select> + <option value="someOption">Some option</option> + <option value="otherOption">Other option</option> +</select> +``` + +[See more examples below.](#usage) + +#### Props {/*props*/} + +`<select>` supports all [common element props.](/reference/react-dom/components/common#props) + +You can [make a select box controlled](#controlling-a-select-box-with-a-state-variable) by passing a `value` prop: + +* `value`: A string (or an array of strings for [`multiple={true}`](#enabling-multiple-selection)). Controls which option is selected. Every value string match the `value` of some `<option>` nested inside the `<select>`. + +When you pass `value`, you must also pass an `onChange` handler that updates the passed value. + +If your `<select>` is uncontrolled, you may pass the `defaultValue` prop instead: + +* `defaultValue`: A string (or an array of strings for [`multiple={true}`](#enabling-multiple-selection)). Specifies [the initially selected option.](#providing-an-initially-selected-option) + +These `<select>` props are relevant both for uncontrolled and controlled select boxs: + +* [`autoComplete`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-autocomplete): A string. Specifies one of the possible [autocomplete behaviors.](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values) +* [`autoFocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-autofocus): A boolean. If `true`, React will focus the element on mount. +* `children`: `<select>` accepts [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option), [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup), and [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup) components as children. You can also pass your own components as long as they eventually render one of the allowed components. If you pass your own components that eventually render `<option>` tags, each `<option>` you render must have a `value`. +* [`disabled`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-disabled): A boolean. If `true`, the select box will not be interactive and will appear dimmed. +* [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-form): A string. Specifies the `id` of the `<form>` this select box belongs to. If omitted, it's the closest parent form. +* [`multiple`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple): A boolean. If `true`, the browser allows [multiple selection.](#enabling-multiple-selection) +* [`name`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-name): A string. Specifies the name for this select box that's [submitted with the form.](#reading-the-select-box-value-when-submitting-a-form) +* `onChange`: An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Required for [controlled select boxes.](#controlling-a-select-box-with-a-state-variable) Fires immediately when the user picks a different option. Behaves like the browser [`input` event.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event) +* `onChangeCapture`: A version of `onChange` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onInput`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires immediately when the value is changed by the user. For historical reasons, in React it is idiomatic to use `onChange` instead which works similarly. +* `onInputCapture`: A version of `onInput` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onInvalid`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/invalid_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires if an input fails validation on form submit. Unlike the built-in `invalid` event, the React `onInvalid` event bubbles. +* `onInvalidCapture`: A version of `onInvalid` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`required`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-required): A boolean. If `true`, the value must be provided for the form to submit. +* [`size`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-size): A number. For `multiple={true}` selects, specifies the preferred number of initially visible items. + +#### Caveats {/*caveats*/} + +- Unlike in HTML, passing a `selected` attribute to `<option>` is not supported. Instead, use [`<select defaultValue>`](#providing-an-initially-selected-option) for uncontrolled select boxes and [`<select value>`](#controlling-a-select-box-with-a-state-variable) for controlled select boxes. +- If a select box receives a `value` prop, it will be [treated as controlled.](#controlling-a-select-box-with-a-state-variable) +- A select box can't be both controlled and uncontrolled at the same time. +- A select box cannot switch between being controlled or uncontrolled over its lifetime. +- Every controlled select box needs an `onChange` event handler that synchronously updates its backing value. + +--- + +## Usage {/*usage*/} + +### Displaying a select box with options {/*displaying-a-select-box-with-options*/} + +Render a `<select>` with a list of `<option>` components inside to display a select box. Give each `<option>` a `value` representing the data to be submitted with the form. + +<Sandpack> + +```js +export default function FruitPicker() { + return ( + <label> + Pick a fruit: + <select name="selectedFruit"> + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + </label> + ); +} +``` + +```css +select { margin: 5px; } +``` + +</Sandpack> + +--- + +### Providing a label for a select box {/*providing-a-label-for-a-select-box*/} + +Typically, you will place every `<select>` inside a [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) tag. This tells the browser that this label is associated with that select box. When the user clicks the label, the browser will automatically focus the select box. It's also essential for accessibility: a screen reader will announce the label caption when the user focuses the select box. + +If you can't nest `<select>` into a `<label>`, associate them by passing the same ID to `<select id>` and [`<label htmlFor>`.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/htmlFor) To avoid conflicts between multiple instances of one component, generate such an ID with [`useId`.](/reference/react/useId) + +<Sandpack> + +```js +import { useId } from 'react'; + +export default function Form() { + const vegetableSelectId = useId(); + return ( + <> + <label> + Pick a fruit: + <select name="selectedFruit"> + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + </label> + <hr /> + <label htmlFor={vegetableSelectId}> + Pick a vegetable: + </label> + <select id={vegetableSelectId} name="selectedVegetable"> + <option value="cucumber">Cucumber</option> + <option value="corn">Corn</option> + <option value="tomato">Tomato</option> + </select> + </> + ); +} +``` + +```css +select { margin: 5px; } +``` + +</Sandpack> + + +--- + +### Providing an initially selected option {/*providing-an-initially-selected-option*/} + +By default, the browser will select the first `<option>` in the list. To select a different option by default, pass that `<option>`'s `value` as the `defaultValue` to the `<select>` element. + +<Sandpack> + +```js +export default function FruitPicker() { + return ( + <label> + Pick a fruit: + <select name="selectedFruit" defaultValue="orange"> + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + </label> + ); +} +``` + +```css +select { margin: 5px; } +``` + +</Sandpack> + +<Pitfall> + +Unlike in HTML, passing a `selected` attribute to an individual `<option>` is not supported. + +</Pitfall> + +--- + +### Enabling multiple selection {/*enabling-multiple-selection*/} + +Pass `multiple={true}` to the `<select>` to let the user select multiple options. In that case, if you also specify `defaultValue` to choose the initially selected options, it must be an array. + +<Sandpack> + +```js +export default function FruitPicker() { + return ( + <label> + Pick some fruits: + <select + name="selectedFruit" + defaultValue={['orange', 'banana']} + multiple={true} + > + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + </label> + ); +} +``` + +```css +select { display: block; margin-top: 10px; width: 200px; } +``` + +</Sandpack> + +--- + +### Reading the select box value when submitting a form {/*reading-the-select-box-value-when-submitting-a-form*/} + +Add a [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) around your select box with a [`<button type="submit">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button) inside. It will call your `<form onSubmit>` event handler. By default, the browser will send the form data to the current URL and refresh the page. You can override that behavior by calling `e.preventDefault()`. To read the form data, use [`new FormData(e.target)`](https://developer.mozilla.org/en-US/docs/Web/API/FormData). +<Sandpack> + +```js +export default function EditPost() { + function handleSubmit(e) { + // Prevent the browser from reloading the page + e.preventDefault(); + // Read the form data + const form = e.target; + const formData = new FormData(form); + // You can pass formData as a fetch body directly: + fetch('/some-api', { method: form.method, body: formData }); + // You can generate a URL out of it, as the browser does by default: + console.log(new URLSearchParams(formData).toString()); + // You can work with it as a plain object. + const formJson = Object.fromEntries(formData.entries()); + console.log(formJson); // (!) This doesn't include multiple select values + // Or you can get an array of name-value pairs. + console.log([...formData.entries()]); + } + + return ( + <form method="post" onSubmit={handleSubmit}> + <label> + Pick your favorite fruit: + <select name="selectedFruit" defaultValue="orange"> + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + </label> + <label> + Pick all your favorite vegetables: + <select + name="selectedVegetables" + multiple={true} + defaultValue={['corn', 'tomato']} + > + <option value="cucumber">Cucumber</option> + <option value="corn">Corn</option> + <option value="tomato">Tomato</option> + </select> + </label> + <hr /> + <button type="reset">Reset</button> + <button type="submit">Submit</button> + </form> + ); +} +``` + +```css +label, select { display: block; } +label { margin-bottom: 20px; } +``` + +</Sandpack> + +<Note> + +Give a `name` to your `<select>`, for example `<select name="selectedFruit" />`. The `name` you specified will be used as a key in the form data, for example `{ selectedFruit: "orange" }`. + +If you use `<select multiple={true}>`, the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) you'll read from the form will include each selected value as a separate name-value pair. Look closely at the console logs in the example above. + +</Note> + +<Pitfall> + +By default, *any* `<button>` inside a `<form>` will submit it. This can be surprising! If you have your own custom `Button` React component, consider returning [`<button type="button">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/button) instead of `<button>`. Then, to be explicit, use `<button type="submit">` for buttons that *are* supposed to submit the form. + +</Pitfall> + +--- + +### Controlling a select box with a state variable {/*controlling-a-select-box-with-a-state-variable*/} + +A select box like `<select />` is *uncontrolled.* Even if you [pass an initially selected value](#providing-an-initially-selected-option) like `<select defaultValue="orange" />`, your JSX only specifies the initial value, not the value right now. + +**To render a _controlled_ select box, pass the `value` prop to it.** React will force the select box to always have the `value` you passed. Typically, you will control a select box by declaring a [state variable:](/reference/react/useState) + +```js {2,6,7} +function FruitPicker() { + const [selectedFruit, setSelectedFruit] = useState('orange'); // Declare a state variable... + // ... + return ( + <select + value={selectedFruit} // ...force the select's value to match the state variable... + onChange={e => setSelectedFruit(e.target.value)} // ... and update the state variable on any change! + > + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + ); +} +``` + +This is useful if you want to re-render some part of the UI in response to every selection. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function FruitPicker() { + const [selectedFruit, setSelectedFruit] = useState('orange'); + const [selectedVegs, setSelectedVegs] = useState(['corn', 'tomato']); + return ( + <> + <label> + Pick a fruit: + <select + value={selectedFruit} + onChange={e => setSelectedFruit(e.target.value)} + > + <option value="apple">Apple</option> + <option value="banana">Banana</option> + <option value="orange">Orange</option> + </select> + </label> + <hr /> + <label> + Pick all your favorite vegetables: + <select + multiple={true} + value={selectedVegs} + onChange={e => { + const options = [...e.target.selectedOptions]; + const values = options.map(option => option.value); + setSelectedVegs(values); + }} + > + <option value="cucumber">Cucumber</option> + <option value="corn">Corn</option> + <option value="tomato">Tomato</option> + </select> + </label> + <hr /> + <p>Your favorite fruit: {selectedFruit}</p> + <p>Your favorite vegetables: {selectedVegs.join(', ')}</p> + </> + ); +} +``` + +```css +select { margin-bottom: 10px; display: block; } +``` + +</Sandpack> + +<Pitfall> + +**If you pass `value` without `onChange`, it will be impossible to select an option.** When you control a select box by passing some `value` to it, you *force* it to always have the value you passed. So if you pass a state variable as a `value` but forget to update that state variable synchronously during the `onChange` event handler, React will revert the select box after every keystroke back to the `value` that you specified. + +Unlike in HTML, passing a `selected` attribute to an individual `<option>` is not supported. + +</Pitfall> diff --git a/beta/src/content/reference/react-dom/components/textarea.md b/beta/src/content/reference/react-dom/components/textarea.md new file mode 100644 index 000000000..833fedf5c --- /dev/null +++ b/beta/src/content/reference/react-dom/components/textarea.md @@ -0,0 +1,423 @@ +--- +title: "<textarea>" +--- + +<Intro> + +The [built-in browser `<textarea>` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) lets you render a multiline text input. + +```js +<textarea /> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<textarea>` {/*textarea*/} + +To display a text area, render the [built-in browser `<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) component. + +```js +<textarea name="postContent" /> +``` + +[See more examples below.](#usage) + +#### Props {/*props*/} + +`<textarea>` supports all [common element props.](/reference/react-dom/components/common#props) + +You can [make a text area controlled](#controlling-a-text-area-with-a-state-variable) by passing a `value` prop: + +* `value`: A string. Controls the text inside the text area. + +When you pass `value`, you must also pass an `onChange` handler that updates the passed value. + +If your `<textarea>` is uncontrolled, you may pass the `defaultValue` prop instead: + +* `defaultValue`: A string. Specifies [the initial value](#providing-an-initial-value-for-a-text-area) for a text area. + +These `<textarea>` props are relevant both for uncontrolled and controlled text areas: + +* [`autoComplete`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-autocomplete): Either `'on'` or `'off'`. Specifies the autocomplete behavior. +* [`autoFocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-autofocus): A boolean. If `true`, React will focus the element on mount. +* `children`: `<textarea>` does not accept children. To set the initial value, use `defaultValue`. +* [`cols`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-cols): A number. Specifies the default width in average character widths. Defaults to `20`. +* [`disabled`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-disabled): A boolean. If `true`, the input will not be interactive and will appear dimmed. +* [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-form): A string. Specifies the `id` of the `<form>` this input belongs to. If omitted, it's the closest parent form. +* [`maxLength`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-maxlength): A number. Specifies the maximum length of text. +* [`minLength`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-minlength): A number. Specifies the minimum length of text. +* [`name`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#name): A string. Specifies the name for this input that's [submitted with the form.](#reading-the-textarea-value-when-submitting-a-form) +* `onChange`: An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Required for [controlled text areas.](#controlling-a-text-area-with-a-state-variable) Fires immediately when the input's value is changed by the user (for example, it fires on every keystroke). Behaves like the browser [`input` event.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event) +* `onChangeCapture`: A version of `onChange` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onInput`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. function. Fires immediately when the value is changed by the user. For historical reasons, in React it is idiomatic to use `onChange` instead which works similarly. +* `onInputCapture`: A version of `onInput` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onInvalid`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/invalid_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires if an input fails validation on form submit. Unlike the built-in `invalid` event, the React `onInvalid` event bubbles. +* `onInvalidCapture`: A version of `onInvalid` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`onSelect`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement/select_event): An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires after the selection inside the `<textarea>` changes. React extends the `onSelect` event to also fire for empty selection and on edits (which may affect the selection). +* `onSelectCapture`: A version of `onSelect` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) +* [`placeholder`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-placeholder): A string. Displayed in a dimmed color when the text area value is empty. +* [`readOnly`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-readonly): A boolean. If `true`, the text area is not editable by the user. +* [`required`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-required): A boolean. If `true`, the value must be provided for the form to submit. +* [`rows`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-rows): A number. Specifies the default height in average character heights. Defaults to `2`. +* [`wrap`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-wrap): Either `'hard'`, `'soft'`, or `'off'`. Specifies how the text should be wrapped when submitting a form. + +#### Caveats {/*caveats*/} + +- Passing children like `<textarea>something</textarea>` is not allowed. [Use `defaultValue` for initial content.](#providing-an-initial-value-for-a-text-area) +- If a text area receives a string `value` prop, it will be [treated as controlled.](#controlling-a-text-area-with-a-state-variable) +- A text area can't be both controlled and uncontrolled at the same time. +- A text area cannot switch between being controlled or uncontrolled over its lifetime. +- Every controlled text area needs an `onChange` event handler that synchronously updates its backing value. + +--- + +## Usage {/*usage*/} + +### Displaying a text area {/*displaying-a-text-area*/} + +Render `<textarea>` to display a text area. You can specify its default size with the [`rows`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#rows) and [`cols`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#cols) attributes, but by default the user will be able to resize it. To disable resizing, you can specify `resize: none` in the CSS. + +<Sandpack> + +```js +export default function NewPost() { + return ( + <label> + Write your post: + <textarea name="postContent" rows={4} cols={40} /> + </label> + ); +} +``` + +```css +input { margin-left: 5px; } +textarea { margin-top: 10px; } +label { margin: 10px; } +label, textarea { display: block; } +``` + +</Sandpack> + +--- + +### Providing a label for a text area {/*providing-a-label-for-a-text-area*/} + +Typically, you will place every `<textarea>` inside a [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) tag. This tells the browser that this label is associated with that text area. When the user clicks the label, the browser will focus the text area. It's also essential for accessibility: a screen reader will announce the label caption when the user focuses the text area. + +If you can't nest `<textarea>` into a `<label>`, associate them by passing the same ID to `<textarea id>` and [`<label htmlFor>`.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/htmlFor) To avoid conflicts between instances of one component, generate such an ID with [`useId`.](/reference/react/useId) + +<Sandpack> + +```js +import { useId } from 'react'; + +export default function Form() { + const postTextAreaId = useId(); + return ( + <> + <label htmlFor={postTextAreaId}> + Write your post: + </label> + <textarea + id={postTextAreaId} + name="postContent" + rows={4} + cols={40} + /> + </> + ); +} +``` + +```css +input { margin: 5px; } +``` + +</Sandpack> + +--- + +### Providing an initial value for a text area {/*providing-an-initial-value-for-a-text-area*/} + +You can optionally specify the initial value for the text area. Pass it as the `defaultValue` string. + +<Sandpack> + +```js +export default function EditPost() { + return ( + <label> + Edit your post: + <textarea + name="postContent" + defaultValue="I really enjoyed biking yesterday!" + rows={4} + cols={40} + /> + </label> + ); +} +``` + +```css +input { margin-left: 5px; } +textarea { margin-top: 10px; } +label { margin: 10px; } +label, textarea { display: block; } +``` + +</Sandpack> + +<Pitfall> + +Unlike in HTML, passing initial text like `<textarea>Some content</textarea>` is not supported. + +</Pitfall> + +--- + +### Reading the text area value when submitting a form {/*reading-the-text-area-value-when-submitting-a-form*/} + +Add a [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) around your textarea with a [`<button type="submit">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button) inside. It will call your `<form onSubmit>` event handler. By default, the browser will send the form data to the current URL and refresh the page. You can override that behavior by calling `e.preventDefault()`. To read the form data, use [`new FormData(e.target)`](https://developer.mozilla.org/en-US/docs/Web/API/FormData). +<Sandpack> + +```js +export default function EditPost() { + function handleSubmit(e) { + // Prevent the browser from reloading the page + e.preventDefault(); + + // Read the form data + const form = e.target; + const formData = new FormData(form); + + // You can pass formData as a fetch body directly: + fetch('/some-api', { method: form.method, body: formData }); + + // Or you can work with it as a plain object: + const formJson = Object.fromEntries(formData.entries()); + console.log(formJson); + } + + return ( + <form method="post" onSubmit={handleSubmit}> + <label> + Post title: <input name="postTitle" defaultValue="Biking" /> + </label> + <label> + Edit your post: + <textarea + name="postContent" + defaultValue="I really enjoyed biking yesterday!" + rows={4} + cols={40} + /> + </label> + <hr /> + <button type="reset">Reset edits</button> + <button type="submit">Save post</button> + </form> + ); +} +``` + +```css +label { display: block; } +input { margin: 5px; } +``` + +</Sandpack> + +<Note> + +Give a `name` to your `<textarea>`, for example `<textarea name="postContent" />`. The `name` you specified will be used as a key in the form data, for example `{ postContent: "Your post" }`. + +</Note> + +<Pitfall> + +By default, *any* `<button>` inside a `<form>` will submit it. This can be surprising! If you have your own custom `Button` React component, consider returning [`<button type="button">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/button) instead of `<button>`. Then, to be explicit, use `<button type="submit">` for buttons that *are* supposed to submit the form. + +</Pitfall> + +--- + +### Controlling a text area with a state variable {/*controlling-a-text-area-with-a-state-variable*/} + +A text area like `<textarea />` is *uncontrolled.* Even if you [pass an initial value](#providing-an-initial-value-for-a-text-area) like `<textarea defaultValue="Initial text" />`, your JSX only specifies the initial value, not the value right now. + +**To render a _controlled_ text area, pass the `value` prop to it.** React will force the text area to always have the `value` you passed. Typically, you will control a text area by declaring a [state variable:](/reference/react/useState) + +```js {2,6,7} +function NewPost() { + const [postContent, setPostContent] = useState(''); // Declare a state variable... + // ... + return ( + <textarea + value={postContent} // ...force the input's value to match the state variable... + onChange={e => setPostContent(e.target.value)} // ... and update the state variable on any edits! + /> + ); +} +``` + +This is useful if you want to re-render some part of the UI in response to every keystroke. + +<Sandpack> + +```js +import { useState } from 'react'; +import MarkdownPreview from './MarkdownPreview.js'; + +export default function MarkdownEditor() { + const [postContent, setPostContent] = useState('_Hello,_ **Markdown**!'); + return ( + <> + <label> + Enter some markdown: + <textarea + value={postContent} + onChange={e => setPostContent(e.target.value)} + /> + </label> + <hr /> + <MarkdownPreview markdown={postContent} /> + </> + ); +} +``` + +```js MarkdownPreview.js +import { Remarkable } from 'remarkable'; + +const md = new Remarkable(); + +export default function MarkdownPreview({ markdown }) { + const renderedHTML = md.render(markdown); + return <div dangerouslySetInnerHTML={{__html: renderedHTML}} />; +} +``` + +```json package.json +{ + "dependencies": { + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "remarkable": "2.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +textarea { display: block; margin-top: 5px; margin-bottom: 10px; } +``` + +</Sandpack> + +<Pitfall> + +**If you pass `value` without `onChange`, it will be impossible to type into the text area.** When you control an text area by passing some `value` to it, you *force* it to always have the value you passed. So if you pass a state variable as a `value` but forget to update that state variable synchronously during the `onChange` event handler, React will revert the text area after every keystroke back to the `value` that you specified. + +</Pitfall> + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My text area doesn't update when I type into it {/*my-text-area-doesnt-update-when-i-type-into-it*/} + +If you render a text area with `value` but no `onChange`, you will see an error in the console: + +```js +// 🔴 Bug: controlled text area with no onChange handler +<textarea value={something} /> +``` + +<ConsoleBlock level="error"> + +You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`. + +</ConsoleBlock> + +As the error message suggests, if you only wanted to [specify the *initial* value,](#providing-an-initial-value-for-a-text-area) pass `defaultValue` instead: + +```js +// ✅ Good: uncontrolled text area with an initial value +<textarea defaultValue={something} /> +``` + +If you want [to control this text area with a state variable,](#controlling-a-text-area-with-a-state-variable) specify an `onChange` handler: + +```js +// ✅ Good: controlled text area with onChange +<textarea value={something} onChange={e => setSomething(e.target.value)} /> +``` + +If the value is intentionally read-only, add a `readOnly` prop to suppress the error: + +```js +// ✅ Good: readonly controlled text area without on change +<textarea value={something} readOnly={true} /> +``` + +--- + +### My text area caret jumps to the beginning on every keystroke {/*my-text-area-caret-jumps-to-the-beginning-on-every-keystroke*/} + +If you [control a text area,](#controlling-a-text-area-with-a-state-variable) you must update its state variable to the text area's value from the DOM during `onChange`. + +You can't update it to something other than `e.target.value`: + +```js +function handleChange(e) { + // 🔴 Bug: updating an input to something other than e.target.value + setFirstName(e.target.value.toUpperCase()); +} +``` + +You also can't update it asynchronously: + +```js +function handleChange(e) { + // 🔴 Bug: updating an input asynchronously + setTimeout(() => { + setFirstName(e.target.value); + }, 100); +} +``` + +To fix your code, update it synchronously to `e.target.value`: + +```js +function handleChange(e) { + // ✅ Updating a controlled input to e.target.value synchronously + setFirstName(e.target.value); +} +``` + +If this doesn't fix the problem, it's possible that the text area gets removed and re-added from the DOM on every keystroke. This can happen if you're accidentally [resetting state](/learn/preserving-and-resetting-state) on every re-render. For example, this can happen if the text area or one of its parents always receives a different `key` attribute, or if you nest component definitions (which is not allowed in React and causes the "inner" component to remount on every render). + +--- + +### I'm getting an error: "A component is changing an uncontrolled input to be controlled" {/*im-getting-an-error-a-component-is-changing-an-uncontrolled-input-to-be-controlled*/} + + +If you provide a `value` to the component, it must remain a string throughout its lifetime. + +You cannot pass `value={undefined}` first and later pass `value="some string"` because React won't know whether you want the component to be uncontrolled or controlled. A controlled component should always receive a string `value`, not `null` or `undefined`. + +If your `value` is coming from an API or a state variable, it might be initialized to `null` or `undefined`. In that case, either set it to an empty string (`''`) initially, or pass `value={someValue ?? ''}` to ensure `value` is a string. diff --git a/beta/src/content/reference/react-dom/createPortal.md b/beta/src/content/reference/react-dom/createPortal.md new file mode 100644 index 000000000..f5209b174 --- /dev/null +++ b/beta/src/content/reference/react-dom/createPortal.md @@ -0,0 +1,457 @@ +--- +title: createPortal +--- + +<Intro> + +`createPortal` lets you render some children into a different part of the DOM. + + +```js +<div> + <SomeComponent /> + {createPortal(children, domNode)} +</div> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `createPortal(children, domNode)` {/*createportal*/} + +To create a portal, call `createPortal`, passing some JSX, and the DOM node where it should be rendered: + +```js +import { createPortal } from 'react-dom'; + +// ... + +<div> + <p>This child is placed in the parent div.</p> + {createPortal( + <p>This child is placed in the document body.</p>, + document.body + )} +</div> +``` + +[See more examples below.](#usage) + +A portal only changes the physical placement of the DOM node. In every other way, the JSX you render into a portal acts as a child node of the React component that renders it. For example, the child can access the context provided by the parent tree, and events still bubble up from children to parents according to the React tree. + +#### Parameters {/*parameters*/} + +* `children`: Anything that can be rendered with React, such as a piece of JSX (e.g. `<div />` or `<SomeComponent />`), a [Fragment](/reference/react/Fragment) (`<>...</>`), a string or a number, or an array of these. + +* `domNode`: Some DOM node, such as those returned by `document.getElementById()`. The node must already exist. Passing a different DOM node during an update will cause the portal content to be recreated. + +#### Returns {/*returns*/} + +`createPortal` returns a React node that can be included into JSX or returned from a React component. If React encounters it in the render output, it will place the provided `children` inside the provided `domNode`. + +#### Caveats {/*caveats*/} + +* Events from portals propagate according to the React tree rather than the DOM tree. For example, if you click inside a portal, and the portal is wrapped in `<div onClick>`, that `onClick` handler will fire. If this causes issues, either stop the event propagation from inside the portal, or move the portal itself up in the React tree. + +--- + +## Usage {/*usage*/} + +### Rendering to a different part of the DOM {/*rendering-to-a-different-part-of-the-dom*/} + +*Portals* let your components render some of their children into a different place in the DOM. This lets a part of your component "escape" from whatever containers it may be in. For example, a component can display a modal dialog or a tooltip that appears above and outside of the rest of the page. + +To create a portal, render the result of `createPortal` with <CodeStep step={1}>some JSX</CodeStep> and the <CodeStep step={2}>DOM node where it should go</CodeStep>: + +```js [[1, 8, "<p>This child is placed in the document body.</p>"], [2, 9, "document.body"]] +import { createPortal } from 'react-dom'; + +function MyComponent() { + return ( + <div style={{ border: '2px solid black' }}> + <p>This child is placed in the parent div.</p> + {createPortal( + <p>This child is placed in the document body.</p>, + document.body + )} + </div> + ); +} +``` + +React will put the DOM nodes for <CodeStep step={1}>the JSX you passed</CodeStep> inside of the <CodeStep step={2}>DOM node you provided</CodeStep>. Without a portal, the second `<p>` would be placed inside the parent `<div>`, but the portal "teleported" it into the [`document.body`:](https://developer.mozilla.org/en-US/docs/Web/API/Document/body) + +<Sandpack> + +```js +import { createPortal } from 'react-dom'; + +export default function MyComponent() { + return ( + <div style={{ border: '2px solid black' }}> + <p>This child is placed in the parent div.</p> + {createPortal( + <p>This child is placed in the document body.</p>, + document.body + )} + </div> + ); +} +``` + +</Sandpack> + +Notice how the second paragraph visually appears outside the parent `<div>` with the border. If you inspect the DOM structure with developer tools, you can confirm that the second `<p>` got placed direcly into the `<body>`: + +```html {4-6,9} +<body> + <div id="root"> + ... + <div style="border: 2px solid black"> + <p>This child is placed inside the parent div.</p> + </div> + ... + </div> + <p>This child is placed in the document body.</p> +</body> +``` + +A portal only changes the physical placement of the DOM node. In every other way, the JSX you render into a portal acts as a child node of the React component that renders it. For example, the child can access the context provided by the parent tree, and events still bubble up from children to parents according to the React tree. + +--- + +### Rendering a modal dialog with a portal {/*rendering-a-modal-dialog-with-a-portal*/} + +You can use a portal to create a modal dialog that floats above the rest of the page, even if the component that summons the dialog is inside a container with `overflow: hidden` or other styles that interfere with the dialog. + +In this example, the two containers have styles that disrupt the modal dialog, but the one rendered into a portal is unaffected because, in the DOM, the modal is not contained within the elements rendered by its parents. + +<Sandpack> + +```js App.js active +import NoPortalExample from './NoPortalExample'; +import PortalExample from './PortalExample'; + +export default function App() { + return ( + <> + <div className="clipping-container"> + <NoPortalExample /> + </div> + <div className="clipping-container"> + <PortalExample /> + </div> + </> + ); +} +``` + +```js NoPortalExample.js +import { useState } from 'react'; +import ModalContent from './ModalContent.js'; + +export default function NoPortalExample() { + const [showModal, setShowModal] = useState(false); + return ( + <> + <button onClick={() => setShowModal(true)}> + Show modal without a portal + </button> + {showModal && ( + <ModalContent onClose={() => setShowModal(false)} /> + )} + </> + ); +} +``` + +```js PortalExample.js active +import { useState } from 'react'; +import { createPortal } from 'react-dom'; +import ModalContent from './ModalContent.js'; + +export default function PortalExample() { + const [showModal, setShowModal] = useState(false); + return ( + <> + <button onClick={() => setShowModal(true)}> + Show modal using a portal + </button> + {showModal && createPortal( + <ModalContent onClose={() => setShowModal(false)} />, + document.body + )} + </> + ); +} +``` + +```js ModalContent.js +export default function ModalContent({ onClose }) { + return ( + <div className="modal"> + <div>I'm a modal dialog</div> + <button onClick={onClose}>Close</button> + </div> + ); +} +``` + + +```css styles.css +.clipping-container { + position: relative; + border: 1px solid #aaa; + margin-bottom: 12px; + padding: 12px; + width: 250px; + height: 80px; + overflow: hidden; +} + +.modal { + display: flex; + justify-content: space-evenly; + align-items: center; + box-shadow: rgba(100, 100, 111, 0.3) 0px 7px 29px 0px; + background-color: white; + border: 2px solid rgb(240, 240, 240); + border-radius: 12px; + position: absolute; + width: 250px; + top: 70px; + left: calc(50% - 125px); + bottom: 70px; +} +``` + +</Sandpack> + +<Pitfall> + +It's important to make sure that your app is accessible when using portals. For instance, you may need to manage keyboard focus so that the user can move the focus in and out of the portal in a natural way. + +Follow the [WAI-ARIA Modal Authoring Practices](https://www.w3.org/WAI/ARIA/apg/#dialog_modal) when creating modals. If you use a community package, ensure that it is accessible and follows these guidelines. + +</Pitfall> + +--- + +### Rendering React components into non-React server markup {/*rendering-react-components-into-non-react-server-markup*/} + +Portals can be useful if your React root is only part of a static or server-rendered page that isn't built with React. For example, if your page is built with a server framework like Rails or PHP, you can create areas of interactivity within static areas such as sidebars. Compared with having [multiple separate React roots,](/reference/react-dom/client/createRoot#rendering-a-page-partially-built-with-react) portals let you treat the app as a single React tree with shared state even though its parts render to different parts of the DOM. + +<Sandpack> + +```html index.html +<!DOCTYPE html> +<html> + <head><title>My app</title></head> + <body> + <h1>Welcome to my hybrid app</h1> + <div class="parent"> + <div class="sidebar"> + This is server non-React markup + <div id="sidebar-content"></div> + </div> + <div id="root"></div> + </div> + </body> +</html> +``` + +```js index.js +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.js'; +import './styles.css'; + +const root = createRoot(document.getElementById('root')); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +``` + +```js App.js active +import { createPortal } from 'react-dom'; + +const sidebarContentEl = document.getElementById('sidebar-content'); + +export default function App() { + return ( + <> + <MainContent /> + {createPortal( + <SidebarContent />, + sidebarContentEl + )} + </> + ); +} + +function MainContent() { + return <p>This part is rendered by React</p>; +} + +function SidebarContent() { + return <p>This part is also rendered by React!</p>; +} +``` + +```css +.parent { + display: flex; + flex-direction: row; +} + +#root { + margin-top: 12px; +} + +.sidebar { + padding: 12px; + background-color: #eee; + width: 200px; + height: 200px; + margin-right: 12px; +} + +#sidebar-content { + margin-top: 18px; + display: block; + background-color: white; +} + +p { + margin: 0; +} +``` + +</Sandpack> + +--- + +### Rendering React components into non-React DOM nodes {/*rendering-react-components-into-non-react-dom-nodes*/} + +You can also use a portal to manage the content of a DOM node that's managed outside of React. For example, suppose you're integrating with a non-React map widget and you want to render React content inside a popup. + +To do this, declare a `popupContainer` state variable to store the DOM node you're going to render into: + +```js +const [popupContainer, setPopupContainer] = useState(null); +``` + +When you initialize the third-party widget, store the DOM node returned by the widget so you can render into it: + +```js {5-6} +useEffect(() => { + if (mapRef.current === null) { + const map = createMapWidget(containerRef.current); + mapRef.current = map; + const popupDiv = addPopupToMapWidget(map); + setPopupContainer(popupDiv); + } +}, []); +``` + +This lets you use `createPortal` to render React content into `popupContainer` once it becomes available: + +```js {3-6} +return ( + <div style={{ width: 250, height: 250 }} ref={containerRef}> + {popupContainer !== null && createPortal( + <p>Hello from React!</p>, + popupContainer + )} + </div> +); +``` + +Here is a complete example you can play with: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "leaflet": "1.9.1", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "remarkable": "2.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { useRef, useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { createMapWidget, addPopupToMapWidget } from './map-widget.js'; + +export default function Map() { + const containerRef = useRef(null); + const mapRef = useRef(null); + const [popupContainer, setPopupContainer] = useState(null); + + useEffect(() => { + if (mapRef.current === null) { + const map = createMapWidget(containerRef.current); + mapRef.current = map; + const popupDiv = addPopupToMapWidget(map); + setPopupContainer(popupDiv); + } + }, []); + + return ( + <div style={{ width: 250, height: 250 }} ref={containerRef}> + {popupContainer !== null && createPortal( + <p>Hello from React!</p>, + popupContainer + )} + </div> + ); +} +``` + +```js map-widget.js +import 'leaflet/dist/leaflet.css'; +import * as L from 'leaflet'; + +export function createMapWidget(containerDomNode) { + const map = L.map(containerDomNode); + map.setView([0, 0], 0); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(map); + return map; +} + +export function addPopupToMapWidget(map) { + const popupDiv = document.createElement('div'); + L.popup() + .setLatLng([0, 0]) + .setContent(popupDiv) + .openOn(map); + return popupDiv; +} +``` + +```css +button { margin: 5px; } +``` + +</Sandpack> diff --git a/beta/src/content/reference/react-dom/findDOMNode.md b/beta/src/content/reference/react-dom/findDOMNode.md new file mode 100644 index 000000000..df3057f5b --- /dev/null +++ b/beta/src/content/reference/react-dom/findDOMNode.md @@ -0,0 +1,435 @@ +--- +title: findDOMNode +--- + +<Deprecated> + +This API will be removed in a future major version of React. [See the alternatives.](#alternatives) + +</Deprecated> + +<Intro> + +`findDOMNode` finds the browser DOM node for a React [class component](/reference/react/Component) instance. + +```js +const domNode = findDOMNode(componentInstance) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `findDOMNode(componentInstance)` {/*finddomnode*/} + +Call `findDOMNode` to find the browser DOM node for a given React [class component](/reference/react/Component) instance. + +```js +import { findDOMNode } from 'react-dom'; + +const domNode = findDOMNode(componentInstance); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `componentInstance`: An instance of the [`Component`](/reference/react/Component) subclass. For example, `this` inside a class component. + + +#### Returns {/*returns*/} + +`findDOMNode` returns the first closest browser DOM node within the given `componentInstance`. When a component renders to `null`, or renders `false`, `findDOMNode` returns `null`. When a component renders to a string, `findDOMNode` returns a text DOM node containing that value. + +#### Caveats {/*caveats*/} + +* A component may return an array or a [Fragment](/reference/react/Fragment) with multiple children. In that case `findDOMNode`, will return the DOM node corresponding to the first non-empty child. + +* `findDOMNode` only works on mounted components (that is, components that have been placed in the DOM). If you try to call this on a component that has not been mounted yet (like calling `findDOMNode()` in `render()` on a component that has yet to be created), an exception will be thrown. + +* `findDOMNode` only returns the result at the time of your call. If a child component renders a different node later, there is no way for you to be notified of this change. + +* `findDOMNode` accepts a class component instance, so it can't be used with function components. + +--- + +## Usage {/*usage*/} + +### Finding the root DOM node of a class component {/*finding-the-root-dom-node-of-a-class-component*/} + +Call `findDOMNode` with a [class component](/reference/react/Component) instance (usually, `this`) to find the DOM node it has rendered. + +```js {3} +class AutoselectingInput extends Component { + componentDidMount() { + const input = findDOMNode(this); + input.select() + } + + render() { + return <input defaultValue="Hello" /> + } +} +``` + +Here, the `input` variable will be set to the `<input>` DOM element. This lets you do something with it. For example, when clicking "Show example" below mounts the input, [`input.select()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select) selects all text in the input: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AutoselectingInput from './AutoselectingInput.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Show example + </button> + <hr /> + {show && <AutoselectingInput />} + </> + ); +} +``` + +```js AutoselectingInput.js active +import { Component } from 'react'; +import { findDOMNode } from 'react-dom'; + +class AutoselectingInput extends Component { + componentDidMount() { + const input = findDOMNode(this); + input.select() + } + + render() { + return <input defaultValue="Hello" /> + } +} + +export default AutoselectingInput; +``` + +</Sandpack> + +--- + +## Alternatives {/*alternatives*/} + +### Reading component's own DOM node from a ref {/*reading-components-own-dom-node-from-a-ref*/} + +Code using `findDOMNode` is fragile because the connection between the JSX node and the code manipulating the corresponding DOM node is not explicit. For example, try wrapping `<input />` from this example into a `<div>`: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AutoselectingInput from './AutoselectingInput.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Show example + </button> + <hr /> + {show && <AutoselectingInput />} + </> + ); +} +``` + +```js AutoselectingInput.js active +import { Component } from 'react'; +import { findDOMNode } from 'react-dom'; + +class AutoselectingInput extends Component { + componentDidMount() { + const input = findDOMNode(this); + input.select() + } + render() { + return <input defaultValue="Hello" /> + } +} + +export default AutoselectingInput; +``` + +</Sandpack> + +This will break the code because now, `findDOMNode(this)` finds the `<div>` DOM node, but the code expects an `<input>` DOM node. To avoid these kinds of problems, use [`createRef`](/reference/react/createRef) to manage a specific DOM node. + +In this example, `findDOMNode` is no longer used. Instead, `inputRef = createRef(null)` is defined as an instance field on the class. To read the DOM node from it, you can use `this.inputRef.current`. To attach it to the JSX, you render `<input ref={this.inputRef} />`. You have connected the code using the DOM node to its JSX: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AutoselectingInput from './AutoselectingInput.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Show example + </button> + <hr /> + {show && <AutoselectingInput />} + </> + ); +} +``` + +```js AutoselectingInput.js active +import { createRef, Component } from 'react'; + +class AutoselectingInput extends Component { + inputRef = createRef(null); + + componentDidMount() { + const input = this.inputRef.current; + input.select() + } + + render() { + return ( + <input ref={this.inputRef} defaultValue="Hello" /> + ); + } +} + +export default AutoselectingInput; +``` + +</Sandpack> + +In modern React without class components, the equivalent code would call [`useRef`](/reference/react/useRef) instead: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AutoselectingInput from './AutoselectingInput.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Show example + </button> + <hr /> + {show && <AutoselectingInput />} + </> + ); +} +``` + +```js AutoselectingInput.js active +import { useRef, useEffect } from 'react'; + +export default function AutoselectingInput() { + const inputRef = useRef(null); + + useEffect(() => { + const input = inputRef.current; + input.select(); + }, []); + + return <input ref={inputRef} defaultValue="Hello" /> +} +``` + +</Sandpack> + +[Read more about manipulating the DOM with refs.](/learn/manipulating-the-dom-with-refs) + +--- + +### Reading a child component's DOM node from a forwarded ref {/*reading-a-child-components-dom-node-from-a-forwarded-ref*/} + +In this example, `findDOMNode(this)` finds a DOM node that belongs to another component. The `AutoselectingInput` renders `MyInput`, which is your own component that renders a browser `<input>`. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AutoselectingInput from './AutoselectingInput.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Show example + </button> + <hr /> + {show && <AutoselectingInput />} + </> + ); +} +``` + +```js AutoselectingInput.js active +import { Component } from 'react'; +import { findDOMNode } from 'react-dom'; +import MyInput from './MyInput.js'; + +class AutoselectingInput extends Component { + componentDidMount() { + const input = findDOMNode(this); + input.select() + } + render() { + return <MyInput />; + } +} + +export default AutoselectingInput; +``` + +```js MyInput.js +export default function MyInput() { + return <input defaultValue="Hello" />; +} +``` + +</Sandpack> + +Notice that calling `findDOMNode(this)` inside `AutoselectingInput` still gives you the DOM `<input>`--even though the JSX for this `<input>` is hidden inside the `MyInput` component. This seems convenient for the above example, but it leads to fragile code. Imagine that you wanted to edit `MyInput` later and add a wrapper `<div>` around it. This would break the code of `AutoselectingInput` (which expects to find an `<input>` DOM node). + +To replace `findDOMNode` in this example, the two components need to coordinate: + +1. `AutoSelectingInput` should declare a ref, like [in the earlier example](#reading-components-own-dom-node-from-a-ref), and pass it to `<MyInput>`. +2. `MyInput` should be declared with [`forwardRef`](/reference/react/forwardRef) to read the passed ref, and pass it down to the `<input>` node. + +This version does that, so it no longer needs `findDOMNode`: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AutoselectingInput from './AutoselectingInput.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Show example + </button> + <hr /> + {show && <AutoselectingInput />} + </> + ); +} +``` + +```js AutoselectingInput.js active +import { createRef, Component } from 'react'; +import MyInput from './MyInput.js'; + +class AutoselectingInput extends Component { + inputRef = createRef(null); + + componentDidMount() { + const input = this.inputRef.current; + input.select() + } + + render() { + return ( + <MyInput ref={this.inputRef} /> + ); + } +} + +export default AutoselectingInput; +``` + +```js MyInput.js +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return <input ref={ref} defaultValue="Hello" />; +}); + +export default MyInput; +``` + +</Sandpack> + +Here is how this code would look like with function components instead of classes: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AutoselectingInput from './AutoselectingInput.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Show example + </button> + <hr /> + {show && <AutoselectingInput />} + </> + ); +} +``` + +```js AutoselectingInput.js active +import { useRef, useEffect } from 'react'; +import MyInput from './MyInput.js'; + +export default function AutoselectingInput() { + const inputRef = useRef(null); + + useEffect(() => { + const input = inputRef.current; + input.select(); + }, []); + + return <MyInput ref={inputRef} defaultValue="Hello" /> +} +``` + +```js MyInput.js +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return <input ref={ref} defaultValue="Hello" />; +}); + +export default MyInput; +``` + +</Sandpack> + +--- + +### Adding a wrapper `<div>` element {/*adding-a-wrapper-div-element*/} + +Sometimes a component needs to know the position and size of its children. This makes it tempting to find the children with `findDOMNode(this)`, and then use DOM methods like [`getBoundingClientRect`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) for measurements. + +There is currently no direct equivalent for this use case, which is why `findDOMNode` is deprecated but is not yet removed completely from React. In the meantime, you can try rendering a wrapper `<div>` node around the content as a workaround, and getting a ref to that node. However, extra wrappers can sometimes break styling. + +```js +<div ref={someRef}> + {children} +</div> +``` + +This also applies to focusing and scrolling to arbitrary children. diff --git a/beta/src/content/reference/react-dom/flushSync.md b/beta/src/content/reference/react-dom/flushSync.md new file mode 100644 index 000000000..4c26579c6 --- /dev/null +++ b/beta/src/content/reference/react-dom/flushSync.md @@ -0,0 +1,135 @@ +--- +title: flushSync +--- + +<Pitfall> + +Using `flushSync` is uncommon and can hurt the performance of your app. + +</Pitfall> + +<Intro> + +`flushSync` lets you force React to flush any updates inside the provided callback synchronously. This ensures that the DOM is updated immediately. + +```js +flushSync(callback) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `flushSync(callback)` {/*flushsync*/} + +Call `flushSync` to force React to flush any pending work and update the DOM synchronously. + +```js +import { flushSync } from 'react-dom'; + +flushSync(() => { + setSomething(123); +}); +``` + +Most of the time, `flushSync` can be avoided. Use `flushSync` as last resort. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + + +* `callback`: A function. React will immediately call this callback and flush any updates it contains synchronously. It may also flush any pending updates, or Effects, or updates inside of Effects. If an update suspends as a result of this `flushSync` call, the fallbacks may be re-shown. + +#### Returns {/*returns*/} + +`flushSync` returns `undefined`. + +#### Caveats {/*caveats*/} + +* `flushSync` can significantly hurt performance. Use sparingly. +* `flushSync` may force pending Suspense boundaries to show their `fallback` state. +* `flushSync` may run pending effects and synchronously apply any updates they contain before returning. +* `flushSync` may flush updates outside the callback when necessary to flush the updates inside the callback. For example, if there are pending updates from a click, React may flush those before flushing the updates inside the callback. + +--- + +## Usage {/*usage*/} + +### Flushing updates for third-party integrations {/*flushing-updates-for-third-party-integrations*/} + +When integrating with third-party code such as browser APIs or UI libraries, it may be necessary to force React to flush updates. Use `flushSync` to force React to flush any <CodeStep step={1}>state updates</CodeStep> inside the callback synchronously: + +```js [[1, 2, "setSomething(123)"]] +flushSync(() => { + setSomething(123); +}); +// By this line, the DOM is updated. +``` + +This ensures that, by the time the next line of code runs, React has already updated the DOM. + +**Using `flushSync` is uncommon, and using it often can significantly hurt the performance of your app.** If your app only uses React APIs, and does not integrate with third-party libraries, `flushSync` should be unnecessary. + +However, it can be helpful for integrating with third-party code like browser APIs. + +Some browser APIs expect results inside of callbacks to be written to the DOM synchronously, by the end of the callback, so the browser can do something with the rendered DOM. In most cases, React handles this for you automatically. But in some cases it may be necessary to break outside of React and force a synchronous update. + +For example, the browser `onbeforeprint` API allows you to change the page immediately before the print dialog opens. This is useful for applying custom print styles that allow the document to display better for printing. + +In the example below, you use `flushSync` inside of the `onbeforeprint` callback to immediately "flush" the React state to the DOM. By doing this, when the print dialog opens, the state has been updated in `isPrinting` is "yes": + +<Sandpack> + +```js App.js active +import { useState, useEffect } from 'react'; +import { flushSync } from 'react-dom'; + +export default function PrintApp() { + const [isPrinting, setIsPrinting] = useState(false); + + useEffect(() => { + function handleBeforePrint() { + flushSync(() => { + setIsPrinting(true); + }) + } + + function handleAfterPrint() { + setIsPrinting(false); + } + + window.addEventListener('beforeprint', handleBeforePrint); + window.addEventListener('afterprint', handleAfterPrint); + return () => { + window.removeEventListener('beforeprint', handleBeforePrint); + window.removeEventListener('afterprint', handleAfterPrint); + } + }, []); + + return ( + <> + <h1>isPrinting: {isPrinting ? 'yes' : 'no'}</h1> + <button onClick={() => window.print()}> + Print + </button> + </> + ); +} +``` + +</Sandpack> + +If you remove the call to `flushSync`, then when the print dialog will display `isPrinting` as "no". This is because React batches the updates asynchronously and the print dialog is displayed before the state is updated. + +<Pitfall> + +`flushSync` can significantly hurt performance, and may unexpectedly force pending Suspense boundaries to show their fallback state. + +Most of the time, `flushSync` can be avoided, so use `flushSync` as a last resort. + +</Pitfall> diff --git a/beta/src/content/reference/react-dom/hydrate.md b/beta/src/content/reference/react-dom/hydrate.md new file mode 100644 index 000000000..8e414a505 --- /dev/null +++ b/beta/src/content/reference/react-dom/hydrate.md @@ -0,0 +1,201 @@ +--- +title: hydrate +--- + +<Deprecated> + +This API will be removed in a future major version of React. + +In React 18, `hydrate` was replaced by [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Using `hydrate` in React 18 will warn that your app will behave as if it’s running React 17. Learn more [here.](/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis) + +</Deprecated> + +<Intro> + +`hydrate` lets you display React components inside a browser DOM node whose HTML content was previously generated by [`react-dom/server`](/reference/react-dom/server) in React 17 and below. + +```js +hydrate(reactNode, domNode, callback?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `hydrate(reactNode, domNode, callback?)` {/*hydrate*/} + +Call `hydrate` in React 17 and below to “attach” React to existing HTML that was already rendered by React in a server environment. + +```js +import { hydrate } from 'react-dom'; + +hydrate(reactNode, domNode); +``` + +React will attach to the HTML that exists inside the `domNode`, and take over managing the DOM inside it. An app fully built with React will usually only have one `hydrate` call with its root component. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: The "React node" used to render the existing HTML. This will usually be a piece of JSX like `<App />` which was rendered with a `ReactDOM Server` method such as `renderToString(<App />)` in React 17. + +* `domNode`: A [DOM element](https://developer.mozilla.org/en-US/docs/Web/API/Element) that was rendered as the root element on the server. + +* **optional**: `callback`: A function. If passed, React will call it after your component is hydrated. + +#### Returns {/*returns*/} + +`hydrate` returns null. + +#### Caveats {/*caveats*/} +* `hydrate` expects the rendered content to be identical with the server-rendered content. React can patch up differences in text content, but you should treat mismatches as bugs and fix them. +* In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive. +* You'll likely have only one `hydrate` call in your app. If you use a framework, it might do this call for you. +* If your app is client-rendered with no HTML rendered already, using `hydrate()` is not supported. Use [render()](/reference/react-dom/render) (for React 17 and below) or [createRoot()](/reference/react-dom/client/createRoot) (for React 18+) instead. + +--- + +## Usage {/*usage*/} + +Call `hydrate` to attach a <CodeStep step={1}>React component</CodeStep> into a server-rendered <CodeStep step={2}>browser DOM node</CodeStep>. + +```js [[1, 3, "<App />"], [2, 3, "document.getElementById('root')"]] +import {hydrate} from 'react-dom'; + +hydrate(<App />, document.getElementById('root')); +```` + +Using `hydrate()` to render a client-only app (an app without server-rendered HTML) is not supported. Use [`render()`](/reference/react-dom/render) (in React 17 and below) or [`createRoot()`](/reference/react-dom/client/createRoot) (in React 18+) instead. + +### Hydrating server-rendered HTML {/*hydrating-server-rendered-html*/} + +In React, "hydration" is how React "attaches" to existing HTML that was already rendered by React in a server environment. During hydration, React will attempt to attach event listeners to the existing markup and take over rendering the app on the client. + +In apps fully built with React, **you will usually only hydrate one "root", once at startup for your entire app**. + +<Sandpack> + +```html public/index.html +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><h1>Hello, world!</h1></div> +``` + +```js index.js active +import './styles.css'; +import {hydrate} from 'react-dom'; +import App from './App.js'; + +hydrate(<App />, document.getElementById('root')); +``` + +```js App.js +export default function App() { + return <h1>Hello, world!</h1>; +} +``` + +</Sandpack> + +Usually you shouldn't need to call `hydrate` again or to call it in more places. From this point on, React will be managing the DOM of your application. If you want to update the UI, your components can do this by [using state.](/reference/react/useState) + +For more information on hydration, see the docs for [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) + +--- + +### Suppressing unavoidable hydration mismatch errors {/*suppressing-unavoidable-hydration-mismatch-errors*/} + +If a single element’s attribute or text content is unavoidably different between the server and the client (for example, a timestamp), you may silence the hydration mismatch warning. + +To silence hydration warnings on an element, add `suppressHydrationWarning={true}`: + +<Sandpack> + +```html public/index.html +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><h1>Current Date: 01/01/2020</h1></div> +``` + +```js index.js +import './styles.css'; +import {hydrate} from 'react-dom'; +import App from './App.js'; + +hydrate(<App />, document.getElementById('root')); +``` + +```js App.js active +export default function App() { + return ( + <h1 suppressHydrationWarning={true}> + Current Date: {new Date().toLocaleDateString()} + </h1> + ); +} +``` + +</Sandpack> + +This only works one level deep, and is intended to be an escape hatch. Don’t overuse it. Unless it’s text content, React still won’t attempt to patch it up, so it may remain inconsistent until future updates. + +--- + +### Handling different client and server content {/*handling-different-client-and-server-content*/} + +If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a [state variable](/reference/react/useState) like `isClient`, which you can set to `true` in an [effect](/reference/react/useEffect): + +<Sandpack> + +```html public/index.html +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><h1>Is Server</h1></div> +``` + +```js index.js +import './styles.css'; +import {hydrate} from 'react-dom'; +import App from './App.js'; + +hydrate(<App />, document.getElementById('root')); +``` + +```js App.js active +import { useState, useEffect } from "react"; + +export default function App() { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + return ( + <h1> + {isClient ? 'Is Client' : 'Is Server'} + </h1> + ); +} +``` + +</Sandpack> + +This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. + +<Pitfall> + +This approach makes hydration slower because your components have to render twice. Be mindful of the user experience on slow connections. The JavaScript code may load significantly later than the initial HTML render, so rendering a different UI immediately after hydration may also feel jarring to the user. + +</Pitfall> diff --git a/beta/src/content/reference/react-dom/index.md b/beta/src/content/reference/react-dom/index.md new file mode 100644 index 000000000..5b2648c18 --- /dev/null +++ b/beta/src/content/reference/react-dom/index.md @@ -0,0 +1,43 @@ +--- +title: React DOM APIs +--- + +<Intro> + +The `react-dom` package contains methods that are only supported for the web applications (which run in the browser DOM environment). They are not supported for React Native. + +</Intro> + +--- + +## APIs {/*apis*/} + +These APIs can be imported from your components. They are rarely used: + +* [`createPortal`](/reference/react-dom/createPortal) lets you render child components in a different part of the DOM tree. +* [`flushSync`](/reference/react-dom/flushSync) lets you force React to flush a state update and update the DOM synchronously. + +--- + +## Entry points {/*entry-points*/} + +The `react-dom` package provides two additional entry points: + +* [`react-dom/client`](/reference/react-dom/client) contains APIs to render React components on the client (in the browser). +* [`react-dom/server`](/reference/react-dom/server) contains APIs to render React components on the server. + +--- + +## Deprecated APIs {/*deprecated-apis*/} + +<Deprecated> + +These APIs will be removed in a future major version of React. + +</Deprecated> + +* [`findDOMNode`](/reference/react-dom/findDOMNode) finds the closest DOM node corresponding to a class component instance. +* [`hydrate`](/reference/react-dom/hydrate) mounts a tree into the DOM created from server HTML. Deprecated in favor of [`hydrateRoot`](/reference/react-dom/client/hydrateRoot). +* [`render`](/reference/react-dom/render) mounts a tree into the DOM. Deprecated in favor of [`createRoot`](/reference/react-dom/client/createRoot). +* [`unmountComponentAtNode`](/reference/react-dom/unmountComponentAtNode) unmounts a tree from the DOM. Deprecated in favor of [`root.unmount()`.](/reference/react-dom/client/createRoot#root-unmount) + diff --git a/beta/src/content/reference/react-dom/render.md b/beta/src/content/reference/react-dom/render.md new file mode 100644 index 000000000..fe8eefab7 --- /dev/null +++ b/beta/src/content/reference/react-dom/render.md @@ -0,0 +1,218 @@ +--- +title: render +--- + +<Deprecated> + +This API will be removed in a future major version of React. + +In React 18, `render` was replaced by [`createRoot`.](/reference/react-dom/client/createRoot) Using `render` in React 18 will warn that your app will behave as if it’s running React 17. Learn more [here.](/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis) + +</Deprecated> + +<Intro> + +`render` renders a piece of [JSX](/learn/writing-markup-with-jsx) ("React node") into a browser DOM node. + +```js +render(reactNode, domNode, callback?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `render(reactNode, domNode, callback?)` {/*render*/} + +Call `render` to display a React component inside a browser DOM element. + +```js +import { render } from 'react-dom'; + +const domNode = document.getElementById('root'); +render(<App />, domNode); +``` + +React will display `<App />` in the `domNode`, and take over managing the DOM inside it. + +An app fully built with React will usually only have one `render` call with its root component. A page that uses "sprinkles" of React for parts of the page may have as many `render` calls as needed. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: A *React node* that you want to display. This will usually be a piece of JSX like `<App />`, but you can also pass a React element constructed with [`createElement()`](/reference/react/createElement), a string, a number, `null`, or `undefined`. + +* `domNode`: A [DOM element.](https://developer.mozilla.org/en-US/docs/Web/API/Element) React will display the `reactNode` you pass inside this DOM element. From this moment, React will manage the DOM inside the `domNode` and update it when your React tree changes. + +* **optional** `callback`: A function. If passed, React will call it after your component is placed into the DOM. + + +#### Returns {/*returns*/} + +`render` usually returns `null`. However, if the `reactNode` you pass is a *class component*, then it will return an instance of that component. + +#### Caveats {/*caveats*/} + +* In React 18, `render` was replaced by [`createRoot`.](/reference/react-dom/client/createRoot) Please use `createRoot` for React 18 and beyond. + +* The first time you call `render`, React will clear all the existing HTML content inside the `domNode` before rendering the React component into it. If your `domNode` contains HTML generated by React on the server or during the build, use [`hydrate()`](/reference/react-dom/hydrate) instead, which attaches the event handlers to the existing HTML. + +* If you call `render` on the same `domNode` more than once, React will update the DOM as necessary to reflect the latest JSX you passed. React will decide which parts of the DOM can be reused and which need to be recreated by ["matching it up"](/learn/preserving-and-resetting-state) with the previously rendered tree. Calling `render` on the same `domNode` again is similar to calling the [`set` function](/reference/react/useState#setstate) on the root component: React avoids unnecessary DOM updates. + +* If your app is fully built with React, you'll likely have only one `render` call in your app. (If you use a framework, it might do this call for you.) When you want to render a piece of JSX in a different part of the DOM tree that isn't a child of your component (for example, a modal or a tooltip), use [`createPortal`](/reference/react-dom/createPortal) instead of `render`. + +--- + +## Usage {/*usage*/} + +Call `render` to display a <CodeStep step={1}>React component</CodeStep> inside a <CodeStep step={2}>browser DOM node</CodeStep>. + +```js [[1, 4, "<App />"], [2, 4, "document.getElementById('root')"]] +import {render} from 'react-dom'; +import App from './App.js'; + +render(<App />, document.getElementById('root')); +```` + +### Rendering the root component {/*rendering-the-root-component*/} + +In apps fully built with React, **you will usually only do this once at startup**--to render the "root" component. + +<Sandpack> + +```js index.js active +import './styles.css'; +import {render} from 'react-dom'; +import App from './App.js'; + +render(<App />, document.getElementById('root')); +``` + +```js App.js +export default function App() { + return <h1>Hello, world!</h1>; +} +``` + +</Sandpack> + +Usually you shouldn't need to call `render` again or to call it in more places. From this point on, React will be managing the DOM of your application. If you want to update the UI, your components can do this by [using state.](/reference/react/useState) + +--- + +### Rendering multiple roots {/*rendering-multiple-roots*/} + +If your page [isn't fully built with React](/learn/add-react-to-a-website), call `render` for each top-level piece of UI managed by React. + +<Sandpack> + +```html public/index.html +<nav id="navigation"></nav> +<main> + <p>This paragraph is not rendered by React (open index.html to verify).</p> + <section id="comments"></section> +</main> +``` + +```js index.js active +import './styles.css'; +import { render } from 'react-dom'; +import { Comments, Navigation } from './Components.js'; + +render( + <Navigation />, + document.getElementById('navigation') +); + +render( + <Comments />, + document.getElementById('comments') +); +``` + +```js Components.js +export function Navigation() { + return ( + <ul> + <NavLink href="/">Home</NavLink> + <NavLink href="/about">About</NavLink> + </ul> + ); +} + +function NavLink({ href, children }) { + return ( + <li> + <a href={href}>{children}</a> + </li> + ); +} + +export function Comments() { + return ( + <> + <h2>Comments</h2> + <Comment text="Hello!" author="Sophie" /> + <Comment text="How are you?" author="Sunil" /> + </> + ); +} + +function Comment({ text, author }) { + return ( + <p>{text} — <i>{author}</i></p> + ); +} +``` + +```css +nav ul { padding: 0; margin: 0; } +nav ul li { display: inline-block; margin-right: 20px; } +``` + +</Sandpack> + +You can destroy the rendered trees with [`unmountComponentAtNode()`.](/reference/react-dom/unmountComponentAtNode) + +--- + +### Updating the rendered tree {/*updating-the-rendered-tree*/} + +You can call `render` more than once on the same DOM node. As long as the component tree structure matches up with what was previously rendered, React will [preserve the state.](/learn/preserving-and-resetting-state) Notice how you can type in the input, which means that the updates from repeated `render` calls every second in this example are not destructive: + +<Sandpack> + +```js index.js active +import {render} from 'react-dom'; +import './styles.css'; +import App from './App.js'; + +let i = 0; +setInterval(() => { + render( + <App counter={i} />, + document.getElementById('root') + ); + i++; +}, 1000); +``` + +```js App.js +export default function App({counter}) { + return ( + <> + <h1>Hello, world! {counter}</h1> + <input placeholder="Type something here" /> + </> + ); +} +``` + +</Sandpack> + +It is uncommon to call `render` multiple times. Usually, you'll [update state](/reference/react/useState) inside one of the components instead. diff --git a/beta/src/content/reference/react-dom/server/index.md b/beta/src/content/reference/react-dom/server/index.md new file mode 100644 index 000000000..9e8703f3a --- /dev/null +++ b/beta/src/content/reference/react-dom/server/index.md @@ -0,0 +1,49 @@ +--- +title: Server React DOM APIs +--- + +<Intro> + +The `react-dom/server` APIs let you render React components to HTML on the server. These APIs are only used on the server at the top level of your app to generate the initial HTML. A [framework](/learn/start-a-new-react-project#building-with-a-full-featured-framework) may call them for you. Most of your components don't need to import or use them. + +</Intro> + +--- + +## Server APIs for Node.js Streams {/*server-apis-for-nodejs-streams*/} + +These methods are only available in the environments with [Node.js Streams:](https://nodejs.org/api/stream.html) + +* [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) renders a React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html) +* [`renderToStaticNodeStream`](/reference/react-dom/server/renderToStaticNodeStream) renders a non-interactive React tree to a [Node.js Readable Stream.](https://nodejs.org/api/stream.html#readable-streams) + +--- + +## Server APIs for Web Streams {/*server-apis-for-web-streams*/} + +These methods are only available in the environments with [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API), which includes browsers, Deno, and some modern edge runtimes: + +* [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) renders a React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + +--- + +## Server APIs for non-streaming environments {/*server-apis-for-non-streaming-environments*/} + +These methods can be used in the environments that don't support streams: + +* [`renderToString`](/reference/react-dom/server/renderToString) renders a React tree to a string. +* [`renderToStaticMarkup`](/reference/react-dom/server/renderToStaticMarkup) renders a non-interactive React tree to a string. + +They have limited functionality compared to the streaming APIs. + +--- + +## Deprecated server APIs {/*deprecated-server-apis*/} + +<Deprecated> + +These APIs will be removed in a future major version of React. + +</Deprecated> + +* [`renderToNodeStream`](/reference/react-dom/server/renderToNodeStream) renders a React tree to a [Node.js Readable stream.](https://nodejs.org/api/stream.html#readable-streams) (Deprecated.) diff --git a/beta/src/content/reference/react-dom/server/renderToNodeStream.md b/beta/src/content/reference/react-dom/server/renderToNodeStream.md new file mode 100644 index 000000000..a4ab2e570 --- /dev/null +++ b/beta/src/content/reference/react-dom/server/renderToNodeStream.md @@ -0,0 +1,76 @@ +--- +title: renderToNodeStream +--- + +<Deprecated> + +This API will be removed in a future major version of React. Use [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) instead. + +</Deprecated> + +<Intro> + +`renderToNodeStream` renders a React tree to a [Node.js Readable Stream.](https://nodejs.org/api/stream.html#readable-streams) + +```js +const stream = renderToNodeStream(reactNode) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `renderToNodeStream(reactNode)` {/*rendertonodestream*/} + +On the server, call `renderToNodeStream` to get a [Node.js Readable Stream](https://nodejs.org/api/stream.html#readable-streams) which you can pipe into the response. + +```js +import { renderToNodeStream } from 'react-dom/server'; + +const stream = renderToNodeStream(<App />); +stream.pipe(response); +``` + +On the client, call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to make the server-generated HTML interactive. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: A React node you want to render to HTML. For example, a JSX element like `<App />`. + +#### Returns {/*returns*/} + +A [Node.js Readable Stream](https://nodejs.org/api/stream.html#readable-streams) that outputs an HTML string. + +#### Caveats {/*caveats*/} + +* This method will wait for all [Suspense boundaries](/reference/react/Suspense) to complete before returning any output. + +* As of React 18, this method buffers all of its output, so it doesn't actually provide any streaming benefits. This is why it's recommended that you migrate to [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) instead. + +* The returned stream is a byte stream encoded in utf-8. If you need a stream in another encoding, take a look at a project like [iconv-lite](https://www.npmjs.com/package/iconv-lite), which provides transform streams for transcoding text. + +--- + +## Usage {/*usage*/} + +### Rendering a React tree as HTML to a Node.js Readable Stream {/*rendering-a-react-tree-as-html-to-a-nodejs-readable-stream*/} + +Call `renderToNodeStream` to get a [Node.js Readable Stream](https://nodejs.org/api/stream.html#readable-streams) which you can pipe to your server response: + +```js {5-6} +import { renderToNodeStream } from 'react-dom/server'; + +// The route handler syntax depends on your backend framework +app.use('/', (request, response) => { + const stream = renderToNodeStream(<App />); + stream.pipe(response); +}); +``` + +The stream will produce the initial non-interactive HTML output of your React components. On the client, you will need to call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to *hydrate* that server-generated HTML and make it interactive. diff --git a/beta/src/content/reference/react-dom/server/renderToPipeableStream.md b/beta/src/content/reference/react-dom/server/renderToPipeableStream.md new file mode 100644 index 000000000..06426734c --- /dev/null +++ b/beta/src/content/reference/react-dom/server/renderToPipeableStream.md @@ -0,0 +1,605 @@ +--- +title: renderToPipeableStream +--- + +<Intro> + +`renderToPipeableStream` renders a React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html) + +```js +const { pipe, abort } = renderToPipeableStream(reactNode, options?) +``` + +</Intro> + +<InlineToc /> + +<Note> + +This API is specific to Node.js. Environments with [Web Streams,](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) like Deno and modern edge runtimes, should use [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) instead. + +</Note> + +--- + +## Reference {/*reference*/} + +### `renderToPipeableStream(reactNode, options)` {/*rendertopipeablestream*/} + +Call `renderToPipeableStream` to render your React tree as HTML into a [Node.js Stream.](https://nodejs.org/api/stream.html#writable-streams) + +```js +import { renderToPipeableStream } from 'react-dom/server'; + +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.setHeader('content-type', 'text/html'); + pipe(response); + } +}); +``` + +On the client, call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to make the server-generated HTML interactive. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: A React node you want to render to HTML. For example, a JSX element like `<App />`. It is expected to represent the entire document, so the `App` component should render the `<html>` tag. + +* **optional** `options`: An object with streaming options. + * **optional** `bootstrapScriptContent`: If specified, this string will be placed in an inline `<script>` tag. + * **optional** `bootstrapScripts`: An array of string URLs for the `<script>` tags to emit on the page. Use this to include the `<script>` that calls [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Omit it if you don't want to run React on the client at all. + * **optional** `bootstrapModules`: Like `bootstrapScripts`, but emits [`<script type="module">`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) instead. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed to [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot#parameters) + * **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML. + * **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). + * **optional** `onAllReady`: A callback that fires when all rendering is complete, including both the [shell](#specifying-what-goes-into-the-shell) and all additional [content.](#streaming-more-content-as-it-loads) You can use this instead of `onShellReady` [for crawlers and static generation.](#waiting-for-all-content-to-load-for-crawlers-and-static-generation) If you start streaming here, you won't get any progressive loading. The stream will contain the final HTML. + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](#recovering-from-errors-outside-the-shell) or [not.](#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](#setting-the-status-code) before the shell is emitted. + * **optional** `onShellReady`: A callback that fires right after the [initial shell](#specifying-what-goes-into-the-shell) has been rendered. You can [set the status code](#setting-the-status-code) and call `pipe` here to start streaming. React will [stream the additional content](#streaming-more-content-as-it-loads) after the shell along with the inline `<script>` tags that place that replace the HTML loading fallbacks with the content. + * **optional** `onShellError`: A callback that fires if there was an error rendering the initial shell. It receives the error as an argument. No bytes were emitted from the stream yet, and neither `onShellReady` nor `onAllReady` will get called, so you can [output a fallback HTML shell.](#recovering-from-errors-inside-the-shell) + * **optional** `progressiveChunkSize`: The number of bytes in a chunk. [Read more about the default heuristic.](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) + + +#### Returns {/*returns*/} + +`renderToPipeableStream` returns an object with two methods: + +* `pipe` outputs the HTML into the provided [Writable Node.js Stream.](https://nodejs.org/api/stream.html#writable-streams) Call `pipe` in `onShellReady` if you want to enable streaming, or in `onAllReady` for crawlers and static generation. +* `abort` lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client. + +--- + +## Usage {/*usage*/} + +### Rendering a React tree as HTML to a Node.js Stream {/*rendering-a-react-tree-as-html-to-a-nodejs-stream*/} + +Call `renderToPipeableStream` to render your React tree as HTML into a [Node.js Stream:](https://nodejs.org/api/stream.html#writable-streams) + +```js [[1, 5, "<App />"], [2, 6, "['/main.js']"]] +import { renderToPipeableStream } from 'react-dom/server'; + +// The route handler syntax depends on your backend framework +app.use('/', (request, response) => { + const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.setHeader('content-type', 'text/html'); + pipe(response); + } + }); +}); +``` + +Along with the <CodeStep step={1}>root component</CodeStep>, you need to provide a list of <CodeStep step={2}>boostrap `<script>` paths</CodeStep>. Your root component should return **the entire document including the root `<html>` tag.** For example, it might look like this: + +```js [[1, 1, "App"]] +export default function App() { + return ( + <html> + <head> + <meta charSet="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/styles.css"></link> + <title>My app</title> + </head> + <body> + <Router /> + </body> + </html> + ); +} +``` + +React will automatically inject the [doctype](https://developer.mozilla.org/en-US/docs/Glossary/Doctype) and your <CodeStep step={2}>bootstrap `<script>` tags</CodeStep> into the resulting HTML stream: + +```html [[2, 5, "/main.js"]] +<!DOCTYPE html> +<html> + <!-- ... HTML from your components ... --> +</html> +<script src="/main.js" async=""></script> +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, "<App />"]] +import {hydrateRoot} from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, <App />); +``` + +This will attach event listeners to the server-generated HTML and make it interactive. + +<DeepDive> + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + <html> + <head> + ... + <link rel="stylesheet" href={assetMap['styles.css']}></link> + ... + </head> + ... + </html> + ); +} +``` + +On the server, render `<App assetMap={assetMap} />` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', (request, response) => { + const { pipe } = renderToPipeableStream(<App assetMap={assetMap} />, { + bootstrapScripts: [assetMap['main.js']], + onShellReady() { + response.setHeader('content-type', 'text/html'); + pipe(response); + } + }); +}); +``` + +Since your server is now rendering `<App assetMap={assetMap} />`, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', (request, response) => { + const { pipe } = renderToPipeableStream(<App assetMap={assetMap} />, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['main.js']], + onShellReady() { + response.setHeader('content-type', 'text/html'); + pipe(response); + } + }); +}); +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline `<script>` tag that sets the global `window.assetMap` variable on the client. This lets the client code read the same `assetMap`: + +```js {4} +import {hydrateRoot} from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, <App assetMap={window.assetMap} />); +``` + +Both client and server render `App` with the same `assetMap` prop, so there are no hydration errors. + +</DeepDive> + +--- + +### Streaming more content as it loads {/*streaming-more-content-as-it-loads*/} + +Streaming allows the user to start seeing the content even before all the data has loaded on the server. For example, consider a profile page that shows a cover, a sidebar with friends and photos, and a list of posts: + +```js +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Posts /> + </ProfileLayout> + ); +} +``` + +Imagine that loading data for `<Posts />` takes some time. Ideally, you'd want to show the rest of the profile page content to the user without waiting for the posts. To do this, [wrap `Posts` in a `<Suspense>` boundary:](/reference/react/Suspense#displaying-a-fallback-while-content-is-loading) + +```js {9,11} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </ProfileLayout> + ); +} +``` + +This way, you tell React to start streaming the HTML before `Posts` loads its data. React will send the HTML for the loading fallback (`PostsGlimmer`) first, and then, when `Posts` finishes loading its data, React will send the remaining HTML along with an inline `<script>` tag that replaces the loading fallback with that HTML. From the user's perspective, the page will first appear with the `PostsGlimmer`, and then it will be replaced by the `Posts`. + +You can further [nest `<Suspense>` boundaries](/reference/react/Suspense#revealing-nested-content-as-it-loads) to create a more granular loading sequence: + +```js {5,13} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<BigSpinner />}> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </Suspense> + </ProfileLayout> + ); +} +``` + +In this example, React can start streaming the page even earlier. Only `ProfileLayout` and `ProfileCover` must finish rendering first because they are not wrapped in any `<Suspense>` boundary. However, if `Sidebar`, `Friends`, or `Photos` need to load some data, React will send the HTML for the `BigSpinner` fallback instead. Then, as enough data becomes available, more content will continue to be revealed until all of it becomes visible. + +Streaming does not need to wait for React itself to load in the browser, or for your app to become interactive. The HTML content from the server will get progressively revealed before any of the `<script>` tags have loaded. + +[Read more about how streaming HTML works.](https://github.com/reactwg/react-18/discussions/37) + +<Note> + +**Only Suspense-enabled data sources will activate the Suspense component.** They include: + +- Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/advanced-features/react-18) +- Lazy-loading component code with [`lazy`](/reference/react/lazy) + +Suspense **does not** detect when data is fetched inside an Effect or event handler. + +The exact way you would load data in the `Posts` component above depends on your framework. If you use a Suspense-enabled framework, you'll find the details in its data fetching documentation. + +Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. + +</Note> + +--- + +### Specifying what goes into the shell {/*specifying-what-goes-into-the-shell*/} + +The part of your app outside of any `<Suspense>` boundaries is called *the shell:* + +```js {3-5,13,14} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<BigSpinner />}> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </Suspense> + </ProfileLayout> + ); +} +``` + +It determines the earliest loading state that the user may see: + +```js {3-5,13 +<ProfileLayout> + <ProfileCover /> + <BigSpinner /> +</ProfileLayout> +``` + +If you wrap the whole app into a `<Suspense>` boundary at the root, the shell will only contain that spinner. However, that's not a pleasant user experience because seeing a big spinner on the screen can feel slower and more annoying than waiting a bit more and seeing the real layout. This is why usually you'll want to place the `<Suspense>` boundaries so that the shell feels *minimal but complete*--like a skeleton of the entire page layout. + +The `onShellReady` callback fires when the entire shell has been rendered. Usually, you'll start streaming then: + +```js {3-6} +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.setHeader('content-type', 'text/html'); + pipe(response); + } +}); +``` + +By the time `onShellReady` fires, components in nested `<Suspense>` boundaries might still be loading data. + +--- + +### Logging crashes on the server {/*logging-crashes-on-the-server*/} + +By default, all errors on the server are logged to console. You can override this behavior to log crash reports: + +```js {7-10} +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.setHeader('content-type', 'text/html'); + pipe(response); + }, + onError(error) { + console.error(error); + logServerCrashReport(error); + } +}); +``` + +If you provide a custom `onError` implementation, don't forget to also log errors to the console like above. + +--- + +### Recovering from errors inside the shell {/*recovering-from-errors-inside-the-shell*/} + +In this example, the shell contains `ProfileLayout`, `ProfileCover`, and `PostsGlimmer`: + +```js {3-5,7-8} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </ProfileLayout> + ); +} +``` + +If an error occurs while rendering those components, React won't have any meaningful HTML to send to the client. Override `onShellError` to send a fallback HTML that doesn't rely on server rendering as the last resort: + +```js {7-11} +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.setHeader('content-type', 'text/html'); + pipe(response); + }, + onShellError(error) { + response.statusCode = 500; + response.setHeader('content-type', 'text/html'); + response.send('<h1>Something went wrong</h1>'); + }, + onError(error) { + console.error(error); + logServerCrashReport(error); + } +}); +``` + +If there is an error while generating the shell, both `onError` and `onShellError` will fire. Use `onError` for error reporting and use `onShellError` to send the fallback HTML document. Your fallback HTML does not have to be an error page. For example, you can include an alternative shell that tries to render your app on the client only. + +--- + +### Recovering from errors outside the shell {/*recovering-from-errors-outside-the-shell*/} + +In this example, the `<Posts />` component is wrapped in `<Suspense>` so it is *not* a part of the shell: + +```js {6} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </ProfileLayout> + ); +} +``` + +If an error happens in the `Posts` component or somewhere inside it, React will [try to recover from it:](/reference/react/Suspense#providing-a-fallback-for-server-errors-and-server-only-content) + +1. It will emit the loading fallback for the closest `<Suspense>` boundary (`PostsGlimmer`) into the HTML. +2. It will "give up" on trying to render the `Posts` content on the server anymore. +3. When the JavaScript code loads on the client, React will *retry* rendering the `Posts` component on the client. + +If retrying rendering `Posts` on the client *also* fails, React will throw the error on the client. As with all the errors thrown during rendering, the [closest parent error boundary](/reference/react/Component#static-getderivedstatefromerror) determines how to present the error to the user. In practice, this means that the user will see a loading indicator until it is certain that the error is not recoverable. + +If retrying rendering `Posts` on the client succeeds, the loading fallback from the server will be replaced with the client rendering output. The user will not know that there was a server error. However, the server `onError` callback and the client [`onRecoverableError`](/reference/react-dom/client/hydrateRoot#hydrateroot) callbacks will fire so that you can get notified about the error. + +--- + +### Setting the status code {/*setting-the-status-code*/} + +Streaming introduces a tradeoff. You want to start streaming the page as early as possible so that the user can see the content sooner. However, once you start streaming, you can no longer set the response status code. + +By [dividing your app](#specifying-what-goes-into-the-shell) into the shell (above all `<Suspense>` boundaries) and the rest of the content, you've already solved a part of this problem. If the shell errors, you'll get the `onShellError` callback which lets you set the error status code. Otherwise, you know that the app may recover on the client, so the "OK" status code is reasonable. + +```js {4} +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.statusCode = 200; + response.setHeader('content-type', 'text/html'); + pipe(response); + }, + onShellError(error) { + response.statusCode = 500; + response.setHeader('content-type', 'text/html'); + response.send('<h1>Something went wrong</h1>'); + }, + onError(error) { + console.error(error); + logServerCrashReport(error); + } +}); +``` + +If a component *outside* the shell (i.e. inside a `<Suspense>` boundary) throws an error, React will not stop rendering. This means that the `onError` callback will fire, but you will still get `onShellReady` instead of `onShellError`. This is because React will try to recover from that error on the client, [as described above.](#recovering-from-errors-outside-the-shell) + +However, if you'd like, you can use the fact that something has errored to set the status code: + +```js {1,6,16} +let didError = false; + +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.statusCode = didError ? 500 : 200; + response.setHeader('content-type', 'text/html'); + pipe(response); + }, + onShellError(error) { + response.statusCode = 500; + response.setHeader('content-type', 'text/html'); + response.send('<h1>Something went wrong</h1>'); + }, + onError(error) { + didError = true; + console.error(error); + logServerCrashReport(error); + } +}); +``` + +This will only catch errors outside the shell that happened while generating the initial shell content, so it's not exhaustive. If knowing whether an error occurred for some content is critical, you can move it up into the shell. + +--- + +### Handling different errors in different ways {/*handling-different-errors-in-different-ways*/} + +You can [create your own `Error` subclasses](https://javascript.info/custom-errors) and use the [`instanceof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) operator to check which error is thrown. For example, you can define a custom `NotFoundError` and throw it from your component. Then your `onError`, `onShellReady`, and `onShellError` callbacks can do something different depending on the error type: + +```js {2,4-14,19,24,30} +let didError = false; +let caughtError = null; + +function getStatusCode() { + if (didError) { + if (caughtError instanceof NotFoundError) { + return 404; + } else { + return 500; + } + } else { + return 200; + } +} + +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + response.statusCode = getStatusCode(); + response.setHeader('content-type', 'text/html'); + pipe(response); + }, + onShellError(error) { + response.statusCode = getStatusCode(); + response.setHeader('content-type', 'text/html'); + response.send('<h1>Something went wrong</h1>'); + }, + onError(error) { + didError = true; + caughtError = error; + console.error(error); + logServerCrashReport(error); + } +}); +``` + +Keep in mind that once you emit the shell and start streaming, you can't change the status code. + +--- + +### Waiting for all content to load for crawlers and static generation {/*waiting-for-all-content-to-load-for-crawlers-and-static-generation*/} + +Streaming offers a better user experience because the user can see the content as it becomes available. + +However, when a crawler visits your page, or if you're generating the pages at the build time, you might want to let all of the content load first and then produce the final HTML output instead of revealing it progressively. + +You can wait for all the content to load using the `onAllReady` callback: + + +```js {2,7,11,18-24} +let didError = false; +let isCrawler = // ... depends on your bot detection strategy ... + +const { pipe } = renderToPipeableStream(<App />, { + bootstrapScripts: ['/main.js'], + onShellReady() { + if (!isCrawler) { + response.statusCode = didError ? 500 : 200; + response.setHeader('content-type', 'text/html'); + pipe(response); + } + }, + onShellError(error) { + response.statusCode = 500; + response.setHeader('content-type', 'text/html'); + response.send('<h1>Something went wrong</h1>'); + }, + onAllReady() { + if (isCrawler) { + response.statusCode = didError ? 500 : 200; + response.setHeader('content-type', 'text/html'); + pipe(response); + } + }, + onError(error) { + didError = true; + console.error(error); + logServerCrashReport(error); + } +}); +``` + +A regular visitor will get a stream of progressively loaded content. A crawler will receive the final HTML output after all the data loads. However, this also means that the crawler will have to wait for *all* data, some of which might be slow to load or error. Depending on your app, you could choose to send the shell to the crawlers too. + +--- + +### Aborting server rendering {/*aborting-server-rendering*/} + +You can force the server rendering to "give up" after a timeout: + +```js {1,5-7} +const { pipe, abort } = renderToPipeableStream(<App />, { + // ... +}); + +setTimeout(() => { + abort(); +}, 10000); +``` + +React will flush the remaining loading fallbacks as HTML, and will attempt to render the rest on the client. diff --git a/beta/src/content/reference/react-dom/server/renderToReadableStream.md b/beta/src/content/reference/react-dom/server/renderToReadableStream.md new file mode 100644 index 000000000..37025ba20 --- /dev/null +++ b/beta/src/content/reference/react-dom/server/renderToReadableStream.md @@ -0,0 +1,621 @@ +--- +title: renderToReadableStream +--- + +<Intro> + +`renderToReadableStream` renders a React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + +```js +const stream = await renderToReadableStream(reactNode, options?) +``` + +</Intro> + +<InlineToc /> + +<Note> + +This API depends on [Web Streams.](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) For Node.js, use [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) instead. + +</Note> + +--- + +## Reference {/*reference*/} + +### `renderToReadableStream(reactNode, options?)` {/*rendertoreadablestream*/} + +Call `renderToReadableStream` to render your React tree as HTML into a [Node.js Stream.](https://nodejs.org/api/stream.html#writable-streams) + +```js +import { renderToReadableStream } from 'react-dom/server'; + +async function handler(request) { + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'] + }); + return new Response(stream, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +On the client, call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to make the server-generated HTML interactive. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: A React node you want to render to HTML. For example, a JSX element like `<App />`. It is expected to represent the entire document, so the `App` component should render the `<html>` tag. + +* **optional** `options`: An object with streaming options. + * **optional** `bootstrapScriptContent`: If specified, this string will be placed in an inline `<script>` tag. + * **optional** `bootstrapScripts`: An array of string URLs for the `<script>` tags to emit on the page. Use this to include the `<script>` that calls [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Omit it if you don't want to run React on the client at all. + * **optional** `bootstrapModules`: Like `bootstrapScripts`, but emits [`<script type="module">`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) instead. + * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed to [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot#parameters) + * **optional** `namespaceURI`: A string with the root [namespace URI](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris) for the stream. Defaults to regular HTML. Pass `'http://www.w3.org/2000/svg'` for SVG or `'http://www.w3.org/1998/Math/MathML'` for MathML. + * **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](#recovering-from-errors-outside-the-shell) or [not.](#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](#logging-crashes-on-the-server) make sure that you still call `console.error`. You can also use it to [adjust the status code](#setting-the-status-code) before the shell is emitted. + * **optional** `progressiveChunkSize`: The number of bytes in a chunk. [Read more about the default heuristic.](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225) + * **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client. + + +#### Returns {/*returns*/} + +`renderToReadableStream` returns a Promise: + +- If rendering the [shell](#specifying-what-goes-into-the-shell) is successful, that Promise will resolve to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) +- If rendering the shell fails, the Promise will be rejected. [Use this to output a fallback shell.](#recovering-from-errors-inside-the-shell) + +The returned stream has an additional property: + +* `allReady`: A Promise that resolves when all rendering is complete, including both the [shell](#specifying-what-goes-into-the-shell) and all additional [content.](#streaming-more-content-as-it-loads) You can `await stream.allReady` before returning a response [for crawlers and static generation.](#waiting-for-all-content-to-load-for-crawlers-and-static-generation) If you do that, you won't get any progressive loading. The stream will contain the final HTML. + +--- + +## Usage {/*usage*/} + +### Rendering a React tree as HTML to a Readable Web Stream {/*rendering-a-react-tree-as-html-to-a-readable-web-stream*/} + +Call `renderToReadableStream` to render your React tree as HTML into a [Readable Web Stream:](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + +```js [[1, 4, "<App />"], [2, 5, "['/main.js']"]] +import { renderToReadableStream } from 'react-dom/server'; + +async function handler(request) { + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'] + }); + return new Response(stream, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +Along with the <CodeStep step={1}>root component</CodeStep>, you need to provide a list of <CodeStep step={2}>boostrap `<script>` paths</CodeStep>. Your root component should return **the entire document including the root `<html>` tag.** For example, it might look like this: + +```js [[1, 1, "App"]] +export default function App() { + return ( + <html> + <head> + <meta charSet="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="stylesheet" href="/styles.css"></link> + <title>My app</title> + </head> + <body> + <Router /> + </body> + </html> + ); +} +``` + +React will automatically inject the [doctype](https://developer.mozilla.org/en-US/docs/Glossary/Doctype) and your <CodeStep step={2}>bootstrap `<script>` tags</CodeStep> into the resulting HTML stream: + +```html [[2, 5, "/main.js"]] +<!DOCTYPE html> +<html> + <!-- ... HTML from your components ... --> +</html> +<script src="/main.js" async=""></script> +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, "<App />"]] +import {hydrateRoot} from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, <App />); +``` + +This will attach event listeners to the server-generated HTML and make it interactive. + +<DeepDive> + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + <html> + <head> + <title>My app</title> + <link rel="stylesheet" href={assetMap['styles.css']}></link> + </head> + ... + </html> + ); +} +``` + +On the server, render `<App assetMap={assetMap} />` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const stream = await renderToReadableStream(<App assetMap={assetMap} />, { + bootstrapScripts: [assets['/main.js']] + }); + return new Response(stream, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +Since your server is now rendering `<App assetMap={assetMap} />`, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const stream = await renderToReadableStream(<App assetMap={assetMap} />, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assets['/main.js']], + }); + return new Response(stream, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline `<script>` tag that sets the global `window.assetMap` variable on the client. This lets the client code read the same `assetMap`: + +```js {4} +import {hydrateRoot} from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, <App assetMap={window.assetMap} />); +``` + +Both client and server render `App` with the same `assetMap` prop, so there are no hydration errors. + +</DeepDive> + +--- + +### Streaming more content as it loads {/*streaming-more-content-as-it-loads*/} + +Streaming allows the user to start seeing the content even before all the data has loaded on the server. For example, consider a profile page that shows a cover, a sidebar with friends and photos, and a list of posts: + +```js +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Posts /> + </ProfileLayout> + ); +} +``` + +Imagine that loading data for `<Posts />` takes some time. Ideally, you'd want to show the rest of the profile page content to the user without waiting for the posts. To do this, [wrap `Posts` in a `<Suspense>` boundary:](/reference/react/Suspense#displaying-a-fallback-while-content-is-loading) + +```js {9,11} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </ProfileLayout> + ); +} +``` + +This way, you tell React to start streaming the HTML before `Posts` loads its data. React will send the HTML for the loading fallback (`PostsGlimmer`) first, and then, when `Posts` finishes loading its data, React will send the remaining HTML along with an inline `<script>` tag that replaces the loading fallback with that HTML. From the user's perspective, the page will first appear with the `PostsGlimmer`, and then it will be replaced by the `Posts`. + +You can further [nest `<Suspense>` boundaries](/reference/react/Suspense#revealing-nested-content-as-it-loads) to create a more granular loading sequence: + +```js {5,13} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<BigSpinner />}> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </Suspense> + </ProfileLayout> + ); +} +``` + +In this example, React can start streaming the page even earlier. Only `ProfileLayout` and `ProfileCover` must finish rendering first because they are not wrapped in any `<Suspense>` boundary. However, if `Sidebar`, `Friends`, or `Photos` need to load some data, React will send the HTML for the `BigSpinner` fallback instead. Then, as enough data becomes available, more content will continue to be revealed until all of it becomes visible. + +Streaming does not need to wait for React itself to load in the browser, or for your app to become interactive. The HTML content from the server will get progressively revealed before any of the `<script>` tags have loaded. + +[Read more about how streaming HTML works.](https://github.com/reactwg/react-18/discussions/37) + +<Note> + +**Only Suspense-enabled data sources will activate the Suspense component.** They include: + +- Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/advanced-features/react-18) +- Lazy-loading component code with [`lazy`](/reference/react/lazy) + +Suspense **does not** detect when data is fetched inside an Effect or event handler. + +The exact way you would load data in the `Posts` component above depends on your framework. If you use a Suspense-enabled framework, you'll find the details in its data fetching documentation. + +Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. + +</Note> + +--- + +### Specifying what goes into the shell {/*specifying-what-goes-into-the-shell*/} + +The part of your app outside of any `<Suspense>` boundaries is called *the shell:* + +```js {3-5,13,14} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<BigSpinner />}> + <Sidebar> + <Friends /> + <Photos /> + </Sidebar> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </Suspense> + </ProfileLayout> + ); +} +``` + +It determines the earliest loading state that the user may see: + +```js {3-5,13 +<ProfileLayout> + <ProfileCover /> + <BigSpinner /> +</ProfileLayout> +``` + +If you wrap the whole app into a `<Suspense>` boundary at the root, the shell will only contain that spinner. However, that's not a pleasant user experience because seeing a big spinner on the screen can feel slower and more annoying than waiting a bit more and seeing the real layout. This is why usually you'll want to place the `<Suspense>` boundaries so that the shell feels *minimal but complete*--like a skeleton of the entire page layout. + +The async call to `renderToReadableStream` will resolve to a `stream` as soon as the entire shell has been rendered. Usually, you'll start streaming then by creating and returning a response with that `stream`: + +```js {5} +async function handler(request) { + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'] + }); + return new Response(stream, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +By the time the `stream` is returned, components in nested `<Suspense>` boundaries might still be loading data. + +--- + +### Logging crashes on the server {/*logging-crashes-on-the-server*/} + +By default, all errors on the server are logged to console. You can override this behavior to log crash reports: + +```js {4-7} +async function handler(request) { + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'], + onError(error) { + console.error(error); + logServerCrashReport(error); + } + }); + return new Response(stream, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +If you provide a custom `onError` implementation, don't forget to also log errors to the console like above. + +--- + +### Recovering from errors inside the shell {/*recovering-from-errors-inside-the-shell*/} + +In this example, the shell contains `ProfileLayout`, `ProfileCover`, and `PostsGlimmer`: + +```js {3-5,7-8} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </ProfileLayout> + ); +} +``` + +If an error occurs while rendering those components, React won't have any meaningful HTML to send to the client. Wrap your `renderToReadableStream` call in a `try...catch` to send a fallback HTML that doesn't rely on server rendering as the last resort: + +```js {2,13-18} +async function handler(request) { + try { + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'], + onError(error) { + console.error(error); + logServerCrashReport(error); + } + }); + return new Response(stream, { + headers: { 'content-type': 'text/html' }, + }); + } catch (error) { + return new Response('<h1>Something went wrong</h1>', { + status: 500, + headers: { 'content-type': 'text/html' }, + }); + } +} +``` + +If there is an error while generating the shell, both `onError` and your `catch` block will run. Use `onError` for error reporting and use the `catch` block to send the fallback HTML document. Your fallback HTML does not have to be an error page. For example, you can include an alternative shell that tries to render your app on the client only. + +--- + +### Recovering from errors outside the shell {/*recovering-from-errors-outside-the-shell*/} + +In this example, the `<Posts />` component is wrapped in `<Suspense>` so it is *not* a part of the shell: + +```js {6} +function ProfilePage() { + return ( + <ProfileLayout> + <ProfileCover /> + <Suspense fallback={<PostsGlimmer />}> + <Posts /> + </Suspense> + </ProfileLayout> + ); +} +``` + +If an error happens in the `Posts` component or somewhere inside it, React will [try to recover from it:](/reference/react/Suspense#providing-a-fallback-for-server-errors-and-server-only-content) + +1. It will emit the loading fallback for the closest `<Suspense>` boundary (`PostsGlimmer`) into the HTML. +2. It will "give up" on trying to render the `Posts` content on the server anymore. +3. When the JavaScript code loads on the client, React will *retry* rendering the `Posts` component on the client. + +If retrying rendering `Posts` on the client *also* fails, React will throw the error on the client. As with all the errors thrown during rendering, the [closest parent error boundary](/reference/react/Component#static-getderivedstatefromerror) determines how to present the error to the user. In practice, this means that the user will see a loading indicator until it is certain that the error is not recoverable. + +If retrying rendering `Posts` on the client succeeds, the loading fallback from the server will be replaced with the client rendering output. The user will not know that there was a server error. However, the server `onError` callback and the client [`onRecoverableError`](/reference/react-dom/client/hydrateRoot#hydrateroot) callbacks will fire so that you can get notified about the error. + +--- + +### Setting the status code {/*setting-the-status-code*/} + +Streaming introduces a tradeoff. You want to start streaming the page as early as possible so that the user can see the content sooner. However, once you start streaming, you can no longer set the response status code. + +By [dividing your app](#specifying-what-goes-into-the-shell) into the shell (above all `<Suspense>` boundaries) and the rest of the content, you've already solved a part of this problem. If the shell errors, your `catch` block will run which lets you set the error status code. Otherwise, you know that the app may recover on the client, so the "OK" status code is reasonable. + +```js {11} +async function handler(request) { + try { + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'], + onError(error) { + console.error(error); + logServerCrashReport(error); + } + }); + return new Response(stream, { + status: 200, + headers: { 'content-type': 'text/html' }, + }); + } catch (error) { + return new Response('<h1>Something went wrong</h1>', { + status: 500, + headers: { 'content-type': 'text/html' }, + }); + } +} +``` + +If a component *outside* the shell (i.e. inside a `<Suspense>` boundary) throws an error, React will not stop rendering. This means that the `onError` callback will fire, but your code will continue running without getting into the `catch` block. This is because React will try to recover from that error on the client, [as described above.](#recovering-from-errors-outside-the-shell) + +However, if you'd like, you can use the fact that something has errored to set the status code: + +```js {3,7,13} +async function handler(request) { + try { + let didError = false; + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'], + onError(error) { + didError = true; + console.error(error); + logServerCrashReport(error); + } + }); + return new Response(stream, { + status: didError ? 500 : 200, + headers: { 'content-type': 'text/html' }, + }); + } catch (error) { + return new Response('<h1>Something went wrong</h1>', { + status: 500, + headers: { 'content-type': 'text/html' }, + }); + } +} +``` + +This will only catch errors outside the shell that happened while generating the initial shell content, so it's not exhaustive. If knowing whether an error occurred for some content is critical, you can move it up into the shell. + +--- + +### Handling different errors in different ways {/*handling-different-errors-in-different-ways*/} + +You can [create your own `Error` subclasses](https://javascript.info/custom-errors) and use the [`instanceof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) operator to check which error is thrown. For example, you can define a custom `NotFoundError` and throw it from your component. Then you can save the error in `onError` and do something different before returning the response depending on the error type: + +```js {2-3,5-15,22,28,33} +async function handler(request) { + let didError = false; + let caughtError = null; + + function getStatusCode() { + if (didError) { + if (caughtError instanceof NotFoundError) { + return 404; + } else { + return 500; + } + } else { + return 200; + } + } + + try { + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'], + onError(error) { + didError = true; + caughtError = error; + console.error(error); + logServerCrashReport(error); + } + }); + return new Response(stream, { + status: getStatusCode(), + headers: { 'content-type': 'text/html' }, + }); + } catch (error) { + return new Response('<h1>Something went wrong</h1>', { + status: getStatusCode(), + headers: { 'content-type': 'text/html' }, + }); + } +} +``` + +Keep in mind that once you emit the shell and start streaming, you can't change the status code. + +--- + +### Waiting for all content to load for crawlers and static generation {/*waiting-for-all-content-to-load-for-crawlers-and-static-generation*/} + +Streaming offers a better user experience because the user can see the content as it becomes available. + +However, when a crawler visits your page, or if you're generating the pages at the build time, you might want to let all of the content load first and then produce the final HTML output instead of revealing it progressively. + +You can wait for all the content to load by awaiting the `stream.allReady` Promise: + +```js {12-15} +async function handler(request) { + try { + let didError = false; + const stream = await renderToReadableStream(<App />, { + bootstrapScripts: ['/main.js'], + onError(error) { + didError = true; + console.error(error); + logServerCrashReport(error); + } + }); + let isCrawler = // ... depends on your bot detection strategy ... + if (isCrawler) { + await stream.allReady; + } + return new Response(stream, { + status: didError ? 500 : 200, + headers: { 'content-type': 'text/html' }, + }); + } catch (error) { + return new Response('<h1>Something went wrong</h1>', { + status: 500, + headers: { 'content-type': 'text/html' }, + }); + } +} +``` + +A regular visitor will get a stream of progressively loaded content. A crawler will receive the final HTML output after all the data loads. However, this also means that the crawler will have to wait for *all* data, some of which might be slow to load or error. Depending on your app, you could choose to send the shell to the crawlers too. + +--- + +### Aborting server rendering {/*aborting-server-rendering*/} + +You can force the server rendering to "give up" after a timeout: + +```js {3,4-6,9} +async function handler(request) { + try { + const controller = new AbortController(); + setTimeout(() => { + controller.abort(); + }, 10000); + + const stream = await renderToReadableStream(<App />, { + signal: controller.signal, + bootstrapScripts: ['/main.js'], + onError(error) { + didError = true; + console.error(error); + logServerCrashReport(error); + } + }); + // ... +``` + +React will flush the remaining loading fallbacks as HTML, and will attempt to render the rest on the client. diff --git a/beta/src/content/reference/react-dom/server/renderToStaticMarkup.md b/beta/src/content/reference/react-dom/server/renderToStaticMarkup.md new file mode 100644 index 000000000..01ff17ee6 --- /dev/null +++ b/beta/src/content/reference/react-dom/server/renderToStaticMarkup.md @@ -0,0 +1,77 @@ +--- +title: renderToStaticMarkup +--- + +<Intro> + +`renderToStaticMarkup` renders a non-interactive React tree to an HTML string. + +```js +const html = renderToStaticMarkup(reactNode) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `renderToStaticMarkup(reactNode)` {/*rendertostaticmarkup*/} + +On the server, call `renderToStaticMarkup` to render your app to HTML. + +```js +import { renderToStaticMarkup } from 'react-dom/server'; + +const html = renderToStaticMarkup(<Page />); +``` + +It will produce non-interactive HTML output of your React components. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: A React node you want to render to HTML. For example, a JSX node like `<Page />`. + +#### Returns {/*returns*/} + +An HTML string. + +#### Caveats {/*caveats*/} + +* `renderToStaticMarkup` output cannot be hydrated. + +* `renderToStaticMarkup` has limited Suspense support. If a component suspends, `renderToStaticMarkup` immediately sends its fallback as HTML. + +* `renderToStaticMarkup` works in the browser, but using it in the client code is not recommended. If you need to render a component to HTML in the browser, [get the HTML by rendering it into a DOM node.](/reference/react-dom/server/renderToString#removing-rendertostring-from-the-client-code) + +--- + +## Usage {/*usage*/} + +### Rendering a non-interactive React tree as HTML to a string {/*rendering-a-non-interactive-react-tree-as-html-to-a-string*/} + +Call `renderToStaticMarkup` to render your app to an HTML string which you can send with your server response: + +```js {5-6} +import { renderToStaticMarkup } from 'react-dom/server'; + +// The route handler syntax depends on your backend framework +app.use('/', (request, response) => { + const html = renderToStaticMarkup(<Page />); + response.send(html); +}); +``` + +This will produce the initial non-interactive HTML output of your React components. + +<Pitfall> + +This method renders **non-interactive HTML that cannot be hydrated.** This is useful if you want to use React as a simple static page generator, or if you're rendering completely static content like emails. + +Interactive apps should use [`renderToString`](/reference/react-dom/server/renderToString) on the server and [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) on the client. + +</Pitfall> diff --git a/beta/src/content/reference/react-dom/server/renderToStaticNodeStream.md b/beta/src/content/reference/react-dom/server/renderToStaticNodeStream.md new file mode 100644 index 000000000..ec3d58937 --- /dev/null +++ b/beta/src/content/reference/react-dom/server/renderToStaticNodeStream.md @@ -0,0 +1,80 @@ +--- +title: renderToStaticNodeStream +--- + +<Intro> + +`renderToStaticNodeStream` renders a non-interactive React tree to a [Node.js Readable Stream.](https://nodejs.org/api/stream.html#readable-streams) + +```js +const stream = renderToStaticNodeStream(reactNode) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `renderToStaticNodeStream(reactNode)` {/*rendertostaticnodestream*/} + +On the server, call `renderToStaticNodeStream` to get a [Node.js Readable Stream](https://nodejs.org/api/stream.html#readable-streams). + +```js +import { renderToStaticNodeStream } from 'react-dom/server'; + +const stream = renderToStaticNodeStream(<Page />); +stream.pipe(response); +``` + +[See more examples below.](#usage) + +The stream will produce non-interactive HTML output of your React components. + +#### Parameters {/*parameters*/} + +* `reactNode`: A React node you want to render to HTML. For example, a JSX element like `<Page />`. + +#### Returns {/*returns*/} + +A [Node.js Readable Stream](https://nodejs.org/api/stream.html#readable-streams) that outputs an HTML string. The resulting HTML can't be hydrated on the client. + +#### Caveats {/*caveats*/} + +* `renderToStaticNodeStream` output cannot be hydrated. + +* This method will wait for all [Suspense boundaries](/reference/react/Suspense) to complete before returning any output. + +* As of React 18, this method buffers all of its output, so it doesn't actually provide any streaming benefits. + +* The returned stream is a byte stream encoded in utf-8. If you need a stream in another encoding, take a look at a project like [iconv-lite](https://www.npmjs.com/package/iconv-lite), which provides transform streams for transcoding text. + +--- + +## Usage {/*usage*/} + +### Rendering a React tree as static HTML to a Node.js Readable Stream {/*rendering-a-react-tree-as-static-html-to-a-nodejs-readable-stream*/} + +Call `renderToStaticNodeStream` to get a [Node.js Readable Stream](https://nodejs.org/api/stream.html#readable-streams) which you can pipe to your server response: + +```js {5-6} +import { renderToStaticNodeStream } from 'react-dom/server'; + +// The route handler syntax depends on your backend framework +app.use('/', (request, response) => { + const stream = renderToStaticNodeStream(<Page />); + stream.pipe(response); +}); +``` + +The stream will produce the initial non-interactive HTML output of your React components. + +<Pitfall> + +This method renders **non-interactive HTML that cannot be hydrated.** This is useful if you want to use React as a simple static page generator, or if you're rendering completely static content like emails. + +Interactive apps should use [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) on the server and [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) on the client. + +</Pitfall> diff --git a/beta/src/content/reference/react-dom/server/renderToString.md b/beta/src/content/reference/react-dom/server/renderToString.md new file mode 100644 index 000000000..970793763 --- /dev/null +++ b/beta/src/content/reference/react-dom/server/renderToString.md @@ -0,0 +1,138 @@ +--- +title: renderToString +--- + +<Pitfall> + +`renderToString` does not support streaming or waiting for data. [See the alternatives.](#alternatives) + +</Pitfall> + +<Intro> + +`renderToString` renders a React tree to an HTML string. + +```js +const html = renderToString(reactNode) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `renderToString(reactNode)` {/*rendertostring*/} + +On the server, call `renderToString` to render your app to HTML. + +```js +import { renderToString } from 'react-dom/server'; + +const html = renderToString(<App />); +``` + +On the client, call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to make the server-generated HTML interactive. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reactNode`: A React node you want to render to HTML. For example, a JSX node like `<App />`. + +#### Returns {/*returns*/} + +An HTML string. + +#### Caveats {/*caveats*/} + +* `renderToString` has limited Suspense support. If a component suspends, `renderToString` immediately sends its fallback as HTML. + +* `renderToString` works in the browser, but using it in the client code is [not recommended.](#removing-rendertostring-from-the-client-code) + +--- + +## Usage {/*usage*/} + +### Rendering a React tree as HTML to a string {/*rendering-a-react-tree-as-html-to-a-string*/} + +Call `renderToString` to render your app to an HTML string which you can send with your server response: + +```js {5-6} +import { renderToString } from 'react-dom/server'; + +// The route handler syntax depends on your backend framework +app.use('/', (request, response) => { + const html = renderToString(<App />); + response.send(html); +}); +``` + +This will produce the initial non-interactive HTML output of your React components. On the client, you will need to call [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) to *hydrate* that server-generated HTML and make it interactive. + + +<Pitfall> + +`renderToString` does not support streaming or waiting for data. [See the alternatives.](#alternatives) + +</Pitfall> + +--- + +## Alternatives {/*alternatives*/} + +### Migrating from `renderToString` to a streaming method on the server {/*migrating-from-rendertostring-to-a-streaming-method-on-the-server*/} + +`renderToString` returns a string immediately, so it does not support streaming or waiting for data. + +When possible, we recommend to use these fully-featured alternatives: + +* If you use Node.js, use [`renderToPipeableStream`.](/reference/react-dom/server/renderToPipeableStream) +* If you use Deno or a modern edge runtime with [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API), use [`renderToReadableStream`.](/reference/react-dom/server/renderToReadableStream) + +You can continue using `renderToString` if your server environment does not support streams. + +--- + +### Removing `renderToString` from the client code {/*removing-rendertostring-from-the-client-code*/} + +Sometimes, `renderToString` is used on the client to convert some component to HTML. + +```js {1-2} +// 🚩 Unnecessary: using renderToString on the client +import { renderToString } from 'react-dom/server'; + +const html = renderToString(<MyIcon />); +console.log(html); // For example, "<svg>...</svg>" +``` + +Importing `react-dom/server` **on the client** unnecessarily increases your bundle size and should be avoided. If you need to render some component to HTML in the browser, use [`createRoot`](/reference/react-dom/client/createRoot) and read HTML from the DOM: + +```js +import { createRoot } from 'react-dom/client'; +import { flushSync } from 'react-dom'; + +const div = document.createElement('div'); +const root = createRoot(div); +flushSync(() => { + root.render(<MyIcon />); +}); +console.log(div.innerHTML); // For example, "<svg>...</svg>" +``` + +The [`flushSync`](/reference/react-dom/flushSync) call is necessary so that the DOM is updated before reading its [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### When a component suspends, the HTML always contains a fallback {/*when-a-component-suspends-the-html-always-contains-a-fallback*/} + +`renderToString` does not fully support Suspense. + +If some component suspends (for example, because it's defined with [`lazy`](/reference/react/lazy) or fetches data), `renderToString` will not wait for its content to resolve. Instead, `renderToString` will find the closest [`<Suspense>`](/reference/react/Suspense) boundary above it and render its `fallback` prop in the HTML. The content will not appear until the client code loads. + +To solve this, use one of the [recommended streaming solutions.](#migrating-from-rendertostring-to-a-streaming-method-on-the-server) They can stream content in chunks as it resolves on the server so that the user sees the page being progressively filled in even before the client code loads. + diff --git a/beta/src/content/reference/react-dom/unmountComponentAtNode.md b/beta/src/content/reference/react-dom/unmountComponentAtNode.md new file mode 100644 index 000000000..008943bd5 --- /dev/null +++ b/beta/src/content/reference/react-dom/unmountComponentAtNode.md @@ -0,0 +1,113 @@ +--- +title: unmountComponentAtNode +--- + +<Deprecated> + +This API will be removed in a future major version of React. + +In React 18, `unmountComponentAtNode` was replaced by [`root.unmount()`](/reference/react-dom/client/createRoot#root-unmount). + +</Deprecated> + +<Intro> + +`unmountComponentAtNode` removes a mounted React component from the DOM. + +```js +unmountComponentAtNode(domNode) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `unmountComponentAtNode(domNode)` {/*unmountcomponentatnode*/} + +Call `unmountComponentAtNode` to remove a mounted React component from the DOM and clean up its event handlers and state. + +```js +import { unmountComponentAtNode } from 'react-dom'; + +const domNode = document.getElementById('root'); +render(<App />, domNode); + +unmountComponentAtNode(domNode); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `domNode`: A [DOM element.](https://developer.mozilla.org/en-US/docs/Web/API/Element) React will remove a mounted React component from this element. + +#### Returns {/*returns*/} + +`unmountComponentAtNode` returns `true` if a component was unmounted and `false` otherwise. + +--- + +## Usage {/*usage*/} + +Call `unmountComponentAtNode` to remove a <CodeStep step={1}>mounted React component</CodeStep> from a <CodeStep step={2}>browser DOM node</CodeStep> and clean up its event handlers and state. + +```js [[1, 5, "<App />"], [2, 5, "rootNode"], [2, 8, "rootNode"]] +import {render, unmountComponentAtNode} from 'react-dom'; +import App from './App.js'; + +const rootNode = document.getElementById('root'); +render(<App />, rootNode); + +// ... +unmountComponentAtNode(rootNode); +```` + + +### Removing a React app from a DOM element {/*removing-a-react-app-from-a-dom-element*/} + +Occasionally, you may want to "sprinkle" React on an existing page, or a page that is not fully written in React. In those cases, you may need to "stop" the React app, by removing all of the UI, state, and listeners from the DOM node it was rendered to. + +In this example, clicking "Render React App" will render a React app. Click "Unmount React App" to destroy it: + +<Sandpack> + +```html index.html +<!DOCTYPE html> +<html> + <head><title>My app</title></head> + <body> + <button id='render'>Render React App</button> + <button id='unmount'>Unmount React App</button> + <!-- This is the React App node --> + <div id='root'></div> + </body> +</html> +``` + +```js index.js active +import './styles.css'; +import {render, unmountComponentAtNode} from 'react-dom'; +import App from './App.js'; + +const domNode = document.getElementById('root'); + +document.getElementById('render').addEventListener('click', () => { + render(<App />, domNode); +}); + +document.getElementById('unmount').addEventListener('click', () => { + unmountComponentAtNode(domNode); +}); +``` + +```js App.js +export default function App() { + return <h1>Hello, world!</h1>; +} +``` + +</Sandpack> diff --git a/beta/src/content/reference/react/Children.md b/beta/src/content/reference/react/Children.md new file mode 100644 index 000000000..3683cb7cc --- /dev/null +++ b/beta/src/content/reference/react/Children.md @@ -0,0 +1,949 @@ +--- +title: Children +--- + +<Pitfall> + +Using `Children` is uncommon and can lead to fragile code. [See common alternatives.](#alternatives) + +</Pitfall> + +<Intro> + +`Children` lets you manipulate and transform the JSX you received as the [`children` prop.](/learn/passing-props-to-a-component#passing-jsx-as-children) + +```js +const mappedChildren = Children.map(children, child => + <div className="Row"> + {child} + </div> +); + +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `Children.count(children)` {/*children-count*/} + +Call `Children.count(children)` to count the number of children in the `children` data structure. + +```js RowList.js active +import { Children } from 'react'; + +function RowList({ children }) { + return ( + <> + <h1>Total rows: {Children.count(children)}</h1> + ... + </> + ); +} +``` + +[See more examples below.](#counting-children) + +#### Parameters {/*children-count-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. + +#### Returns {/*children-count-returns*/} + +The number of nodes inside these `children`. + +#### Caveats {/*children-count-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans), strings, numbers, and [React elements](/reference/react/createElement) count as individual nodes. Arrays don't count as individual nodes, but their children do. **The traversal does not go deeper than React elements:** they don't get rendered, and their children aren't traversed. [Fragments](/reference/react/Fragment) don't get traversed. + +--- + +### `Children.forEach(children, fn, thisArg?)` {/*children-foreach*/} + +Call `Children.forEach(children, fn, thisArg?)` to run some code for each child in the `children` data structure. + +```js RowList.js active +import { Children } from 'react'; + +function SeparatorList({ children }) { + const result = []; + Children.forEach(children, (child, index) => { + result.push(child); + result.push(<hr key={index} />); + }); + // ... +``` + +[See more examples below.](#running-some-code-for-each-child) + +#### Parameters {/*children-foreach-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. +* `fn`: The function you want to run for each child, similar to the [array `forEach` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) callback. It will be called with the child as the first argument and its index as the second argument. The index starts at `0` and increments on each call. +* **optional** `thisArg`: The [`this` value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) with which the `fn` function should be called. If omitted, it's `undefined`. + +#### Returns {/*children-foreach-returns*/} + +`Children.forEach` returns `undefined`. + +#### Caveats {/*children-foreach-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans), strings, numbers, and [React elements](/reference/react/createElement) count as individual nodes. Arrays don't count as individual nodes, but their children do. **The traversal does not go deeper than React elements:** they don't get rendered, and their children aren't traversed. [Fragments](/reference/react/Fragment) don't get traversed. + +--- + +### `Children.map(children, fn, thisArg?)` {/*children-map*/} + +Call `Children.map(children, fn, thisArg?)` to map or transform each child in the `children` data structure. + +```js RowList.js active +import { Children } from 'react'; + +function RowList({ children }) { + return ( + <div className="RowList"> + {Children.map(children, child => + <div className="Row"> + {child} + </div> + )} + </div> + ); +} +``` + +[See more examples below.](#transforming-children) + +#### Parameters {/*children-map-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. +* `fn`: The mapping function, similar to the [array `map` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) callback. It will be called with the child as the first argument and its index as the second argument. The index starts at `0` and increments on each call. You need to return a React node from this function. This may be an empty node (`null`, `undefined`, or a Boolean), a string, a number, a React element, or an array of other React nodes. +* **optional** `thisArg`: The [`this` value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) with which the `fn` function should be called. If omitted, it's `undefined`. + +#### Returns {/*children-map-returns*/} + +If `children` is `null` or `undefined`, returns the same value. + +Otherwise, returns a flat array consisting of the nodes you've returned from the `fn` function. The returned array will contain all nodes you returned except for `null` and `undefined`. + +#### Caveats {/*children-map-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans), strings, numbers, and [React elements](/reference/react/createElement) count as individual nodes. Arrays don't count as individual nodes, but their children do. **The traversal does not go deeper than React elements:** they don't get rendered, and their children aren't traversed. [Fragments](/reference/react/Fragment) don't get traversed. + +- If you return an element or an array of elements with keys from `fn`, **the returned elements' keys will be automatically combined with the key of the corresponding original item from `children`.** When you return multiple elements from `fn` in an array, their keys only need to be unique locally amongst each other. + +--- + +### `Children.only(children)` {/*children-only*/} + + +Call `Children.only(children)` to assert that `children` represent a single React element. + +```js +function Box({ children }) { + const element = Children.only(children); + // ... +``` + +#### Parameters {/*children-only-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. + +#### Returns {/*children-only-returns*/} + +If `children` [is a valid element,](/reference/react/isValidElement) returns that element. + +Otherwise, throws an error. + +#### Caveats {/*children-only-caveats*/} + +- This method always **throws if you pass an array (such as the return value of `Children.map`) as `children`.** In other words, it enforces that `children` is a single React element, not that it's an array with a single element. + +--- + +### `Children.toArray(children)` {/*children-toarray*/} + +Call `Children.toArray(children)` to create an array out of the `children` data structure. + +```js ReversedList.js active +import { Children } from 'react'; + +export default function ReversedList({ children }) { + const result = Children.toArray(children); + result.reverse(); + // ... +``` + +#### Parameters {/*children-toarray-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. + +#### Returns {/*children-toarray-returns*/} + +Returns a flat array of elements in `children`. + +#### Caveats {/*children-toarray-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans) will be omitted in the returned array. **The returned elements' keys will be calculated from the original elements' keys and their level of nesting and position.** This ensures that flattening the array does not introduce changes in behavior. + +--- + +## Usage {/*usage*/} + +### Transforming children {/*transforming-children*/} + +To transform the children JSX that your component [receives as the `children` prop,](/learn/passing-props-to-a-component#passing-jsx-as-children) call `Children.map`: + +```js {6,10} +import { Children } from 'react'; + +function RowList({ children }) { + return ( + <div className="RowList"> + {Children.map(children, child => + <div className="Row"> + {child} + </div> + )} + </div> + ); +} +``` + +In the example above, the `RowList` wraps every child it receives into a `<div className="Row">` container. For example, let's say the parent component passes three `<p>` tags as the `children` prop to `RowList`: + +```js +<RowList> + <p>This is the first item.</p> + <p>This is the second item.</p> + <p>This is the third item.</p> +</RowList> +``` + +Then, with the `RowList` implementation above, the final rendered result will look like this: + +```js +<div className="RowList"> + <div className="Row"> + <p>This is the first item.</p> + </div> + <div className="Row"> + <p>This is the second item.</p> + </div> + <div className="Row"> + <p>This is the third item.</p> + </div> +</div> +``` + +`Children.map` is similar to [to transforming arrays with `map()`.](/learn/rendering-lists) The difference is that the `children` data structure is considered *opaque.* This means that even if it's sometimes an array, you should not assume it's an array or any other particular data type. This is why you should use `Children.map` if you need to transform it. + +<Sandpack> + +```js +import RowList from './RowList.js'; + +export default function App() { + return ( + <RowList> + <p>This is the first item.</p> + <p>This is the second item.</p> + <p>This is the third item.</p> + </RowList> + ); +} +``` + +```js RowList.js active +import { Children } from 'react'; + +export default function RowList({ children }) { + return ( + <div className="RowList"> + {Children.map(children, child => + <div className="Row"> + {child} + </div> + )} + </div> + ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +</Sandpack> + +<DeepDive> + +#### Why is the children prop not always an array? {/*why-is-the-children-prop-not-always-an-array*/} + +In React, the `children` prop is considered an *opaque* data structure. This means that you shouldn't rely on how it is structured. To transform, filter, or count children, you should use the `Children` methods. + +In practice, the `children` data structure is often represented as an array internally. However, if there is only a single child, then React won't create an extra array since this would lead to unnecessary memory overhead. As long as you use the `Children` methods instead of directly introspecting the `children` prop, your code will not break even if React changes how the data structure is actually implemented. + +Even when `children` is an array, `Children.map` has useful special behavior. For example, `Children.map` combines the [keys](/learn/rendering-lists#keeping-list-items-in-order-with-key) on the returned elements with the keys on the `children` you've passed to it. This ensures the original JSX children don't "lose" keys even if they get wrapped like in the example above. + +</DeepDive> + +<Pitfall> + +The `children` data structure **does not include rendered output** of the components you pass as JSX. In the example below, the `children` received by the `RowList` only contains two items rather than three: + +1. `<p>This is the first item.</p>` +2. `<MoreRows />` + +This is why only two row wrappers are generated in this example: + +<Sandpack> + +```js +import RowList from './RowList.js'; + +export default function App() { + return ( + <RowList> + <p>This is the first item.</p> + <MoreRows /> + </RowList> + ); +} + +function MoreRows() { + return ( + <> + <p>This is the second item.</p> + <p>This is the third item.</p> + </> + ); +} +``` + +```js RowList.js +import { Children } from 'react'; + +export default function RowList({ children }) { + return ( + <div className="RowList"> + {Children.map(children, child => + <div className="Row"> + {child} + </div> + )} + </div> + ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +</Sandpack> + +**There is no way to get the rendered output of an inner component** like `<MoreRows />` when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + +</Pitfall> + +--- + +### Running some code for each child {/*running-some-code-for-each-child*/} + +Call `Children.forEach` to iterate over each child in the `children` data structure. It does not return any value and is similar to the [array `forEach` method.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) You can use it to run custom logic like constructing your own array. + +<Sandpack> + +```js +import SeparatorList from './SeparatorList.js'; + +export default function App() { + return ( + <SeparatorList> + <p>This is the first item.</p> + <p>This is the second item.</p> + <p>This is the third item.</p> + </SeparatorList> + ); +} +``` + +```js SeparatorList.js active +import { Children } from 'react'; + +export default function SeparatorList({ children }) { + const result = []; + Children.forEach(children, (child, index) => { + result.push(child); + result.push(<hr key={index} />); + }); + result.pop(); // Remove the last separator + return result; +} +``` + +</Sandpack> + +<Pitfall> + +As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + +</Pitfall> + +--- + +### Counting children {/*counting-children*/} + +Call `Children.count(children)` to calculate the number of children. + +<Sandpack> + +```js +import RowList from './RowList.js'; + +export default function App() { + return ( + <RowList> + <p>This is the first item.</p> + <p>This is the second item.</p> + <p>This is the third item.</p> + </RowList> + ); +} +``` + +```js RowList.js active +import { Children } from 'react'; + +export default function RowList({ children }) { + return ( + <div className="RowList"> + <h1 className="RowListHeader"> + Total rows: {Children.count(children)} + </h1> + {Children.map(children, child => + <div className="Row"> + {child} + </div> + )} + </div> + ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.RowListHeader { + padding-top: 5px; + font-size: 25px; + font-weight: bold; + text-align: center; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +</Sandpack> + +<Pitfall> + +As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + +</Pitfall> + +--- + +### Converting children to an array {/*converting-children-to-an-array*/} + +Call `Children.toArray(children)` to turn the `children` data structure into a regular JavaScript array. This lets you manipulate the array with built-in array methods like [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), [`sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), or [`reverse`.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) + +<Sandpack> + +```js +import ReversedList from './ReversedList.js'; + +export default function App() { + return ( + <ReversedList> + <p>This is the first item.</p> + <p>This is the second item.</p> + <p>This is the third item.</p> + </ReversedList> + ); +} +``` + +```js ReversedList.js active +import { Children } from 'react'; + +export default function ReversedList({ children }) { + const result = Children.toArray(children); + result.reverse(); + return result; +} +``` + +</Sandpack> + +<Pitfall> + +As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + +</Pitfall> + +--- + +## Alternatives {/*alternatives*/} + +<Note> + +This section describes alternatives to the `Children` API (with capital `C`) that's imported like this: + +```js +import { Children } from 'react'; +``` + +Don't confuse it with [using the `children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) (lowercase `c`), which is good and encouraged. + +</Note> + +### Exposing multiple components {/*exposing-multiple-components*/} + +Manipulating children with the `Children` methods often leads to fragile code. When you pass children to a component in JSX, you don't usually expect the component to manipulate or transform the individual children. + +When you can, try to avoid using the `Children` methods. For example, if you want every child of `RowList` to be wrapped in `<div className="Row">`, export a `Row` component, and manually wrap every row into it like this: + +<Sandpack> + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + <RowList> + <Row> + <p>This is the first item.</p> + </Row> + <Row> + <p>This is the second item.</p> + </Row> + <Row> + <p>This is the third item.</p> + </Row> + </RowList> + ); +} +``` + +```js RowList.js +export function RowList({ children }) { + return ( + <div className="RowList"> + {children} + </div> + ); +} + +export function Row({ children }) { + return ( + <div className="Row"> + {children} + </div> + ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +</Sandpack> + +Unlike using `Children.map`, this approach does not wrap every child automatically. **However, this approach has a significant benefit compared to the [earlier example with `Children.map`](#transforming-children) because it works even if you keep extracting more components.** For example, it still works if you extract your own `MoreRows` component: + +<Sandpack> + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + <RowList> + <Row> + <p>This is the first item.</p> + </Row> + <MoreRows /> + </RowList> + ); +} + +function MoreRows() { + return ( + <> + <Row> + <p>This is the second item.</p> + </Row> + <Row> + <p>This is the third item.</p> + </Row> + </> + ); +} +``` + +```js RowList.js +export function RowList({ children }) { + return ( + <div className="RowList"> + {children} + </div> + ); +} + +export function Row({ children }) { + return ( + <div className="Row"> + {children} + </div> + ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +</Sandpack> + +This wouldn't work with `Children.map` because it would "see" `<MoreRows />` as a single child (and a single row). + +--- + +### Accepting an array of objects as a prop {/*accepting-an-array-of-objects-as-a-prop*/} + +You can also explicitly pass an array as a prop. For example, this `RowList` accepts a `rows` array as a prop: + +<Sandpack> + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + <RowList rows={[ + { id: 'first', content: <p>This is the first item.</p> }, + { id: 'second', content: <p>This is the second item.</p> }, + { id: 'third', content: <p>This is the third item.</p> } + ]} /> + ); +} +``` + +```js RowList.js +export function RowList({ rows }) { + return ( + <div className="RowList"> + {rows.map(row => ( + <div className="Row" key={row.id}> + {row.content} + </div> + ))} + </div> + ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +</Sandpack> + +Since `rows` is a regular JavaScript array, the `RowList` component can use built-in array methods like [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on it. + +This pattern is especially useful when you want to be able to pass more information as structured data together with children. In the below example, the `TabSwitcher` component receives an array of objects as the `tabs` prop: + +<Sandpack> + +```js +import TabSwitcher from './TabSwitcher.js'; + +export default function App() { + return ( + <TabSwitcher tabs={[ + { + id: 'first', + header: 'First', + content: <p>This is the first item.</p> + }, + { + id: 'second', + header: 'Second', + content: <p>This is the second item.</p> + }, + { + id: 'third', + header: 'Third', + content: <p>This is the third item.</p> + } + ]} /> + ); +} +``` + +```js TabSwitcher.js +import { useState } from 'react'; + +export default function TabSwitcher({ tabs }) { + const [selectedId, setSelectedId] = useState(tabs[0].id); + const selectedTab = tabs.find(tab => tab.id === selectedId); + return ( + <> + {tabs.map(tab => ( + <button + key={tab.id} + onClick={() => setSelectedId(tab.id)} + > + {tab.header} + </button> + ))} + <hr /> + <div key={selectedId}> + <h3>{selectedTab.header}</h3> + {selectedTab.content} + </div> + </> + ); +} +``` + +</Sandpack> + +Unlike passing the children as JSX, this approach lets you associate some extra data like `header` with each item. Because you are working with the `tabs` directly, and it is an array, you do not need the `Children` methods. + +--- + +### Calling a render prop to customize rendering {/*calling-a-render-prop-to-customize-rendering*/} + +Instead of producing JSX for every single item, you can also pass a function that returns JSX, and call that function when necessary. In this example, the `App` component passes a `renderContent` function to the `TabSwitcher` component. The `TabSwitcher` component calls `renderContent` only for the selected tab: + +<Sandpack> + +```js +import TabSwitcher from './TabSwitcher.js'; + +export default function App() { + return ( + <TabSwitcher + tabIds={['first', 'second', 'third']} + getHeader={tabId => { + return tabId[0].toUpperCase() + tabId.slice(1); + }} + renderContent={tabId => { + return <p>This is the {tabId} item.</p>; + }} + /> + ); +} +``` + +```js TabSwitcher.js +import { useState } from 'react'; + +export default function TabSwitcher({ tabIds, getHeader, renderContent }) { + const [selectedId, setSelectedId] = useState(tabIds[0]); + return ( + <> + {tabIds.map((tabId) => ( + <button + key={tabId} + onClick={() => setSelectedId(tabId)} + > + {getHeader(tabId)} + </button> + ))} + <hr /> + <div key={selectedId}> + <h3>{getHeader(selectedId)}</h3> + {renderContent(selectedId)} + </div> + </> + ); +} +``` + +</Sandpack> + +A prop like `renderContent` is called a *render prop* because it is a prop that specifies how to render a piece of the user interface. However, there is nothing special about it: it is a regular prop which happens to be a function. + +Render props are functions, so you can pass information to them. For example, this `RowList` component passes the `id` and the `index` of each row to the `renderRow` render prop, which uses `index` to highlight even rows: + +<Sandpack> + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + <RowList + rowIds={['first', 'second', 'third']} + renderRow={(id, index) => { + return ( + <Row isHighlighted={index % 2 === 0}> + <p>This is the {id} item.</p> + </Row> + ); + }} + /> + ); +} +``` + +```js RowList.js +import { Fragment } from 'react'; + +export function RowList({ rowIds, renderRow }) { + return ( + <div className="RowList"> + <h1 className="RowListHeader"> + Total rows: {rowIds.length} + </h1> + {rowIds.map((rowId, index) => + <Fragment key={rowId}> + {renderRow(rowId, index)} + </Fragment> + )} + </div> + ); +} + +export function Row({ children, isHighlighted }) { + return ( + <div className={[ + 'Row', + isHighlighted ? 'RowHighlighted' : '' + ].join(' ')}> + {children} + </div> + ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.RowListHeader { + padding-top: 5px; + font-size: 25px; + font-weight: bold; + text-align: center; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} + +.RowHighlighted { + background: #ffa; +} +``` + +</Sandpack> + +This is another example of how parent and child components can cooperate without manipulating the children. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I pass a custom component, but the `Children` methods don't show its render result {/*i-pass-a-custom-component-but-the-children-methods-dont-show-its-render-result*/} + +Suppose you pass two children to `RowList` like this: + +```js +<RowList> + <p>First item</p> + <MoreRows /> +</RowList> +``` + +If you do `Children.count(children)` inside `RowList`, you will get `2`. Even if `MoreRows` renders 10 different items, or if it returns `null`, `Children.count(children)` will still be `2`. From the `RowList`'s perspective, it only "sees" the JSX it has received. It does not "see" the internals of the `MoreRows` component. + +The limitation makes it hard to extract a component. This is why [alternatives](#alternatives) are preferred to using `Children`. diff --git a/beta/src/content/reference/react/Component.md b/beta/src/content/reference/react/Component.md new file mode 100644 index 000000000..8ff515c0a --- /dev/null +++ b/beta/src/content/reference/react/Component.md @@ -0,0 +1,1987 @@ +--- +title: Component +--- + +<Pitfall> + +We recommend to define components as functions instead of classes. [See how to migrate.](#alternatives) + +</Pitfall> + +<Intro> + +`Component` is the base class for the React components defined as [JavaScript classes.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) Class components are still supported by React, but we don't recommend using them in new code. + +```js +class Greeting extends Component { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `Component` {/*component*/} + +To define a React component as a class, extend the built-in `Component` class and define a [`render` method:](#render) + +```js +import { Component } from 'react'; + +class Greeting extends Component { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} +``` + +Only the `render` method is required, other methods are optional. + +[See more examples below.](#usage) + +--- + +### `context` {/*context*/} + +The [context](/learn/passing-data-deeply-with-context) of a class component is available as `this.context`. It is only available if you specify *which* context you want to receive using [`static contextType`](#static-contexttype) (modern) or [`static contextTypes`](#static-contexttypes) (deprecated). + +A class component can only read one context at a time. + +```js {2,5} +class Button extends Component { + static contextType = ThemeContext; + + render() { + const theme = this.context; + const className = 'button-' + theme; + return ( + <button className={className}> + {this.props.children} + </button> + ); + } +} + +``` + +<Note> + +Reading `this.context` in class components is equivalent to [`useContext`](/reference/react/useContext) in function components. + +[See how to migrate.](#migrating-a-component-with-context-from-a-class-to-a-function) + +</Note> + +--- + +### `props` {/*props*/} + +The props passed to a class component are available as `this.props`. + +```js {3} +class Greeting extends Component { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} + +<Greeting name="Taylor" /> +``` + +<Note> + +Reading `this.props` in class components is equivalent to [declaring props](/learn/passing-props-to-a-component#step-2-read-props-inside-the-child-component) in function components. + +[See how to migrate.](#migrating-a-simple-component-from-a-class-to-a-function) + +</Note> + +--- + +### `refs` {/*refs*/} + +<Deprecated> + +This API will be removed in a future major version of React. [Use `createRef` instead.](/reference/react/createRef) + +</Deprecated> + +Lets you access [legacy string refs](https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs) for this component. + +--- + +### `state` {/*state*/} + +The state of a class component is available as `this.state`. The `state` field must be an object. Do not mutate the state directly. If you wish to change the state, call `setState` with the new state. + +```js {2-4,7-9,18} +class Counter extends Component { + state = { + age: 42, + }; + + handleAgeChange = () => { + this.setState({ + age: this.state.age + 1 + }); + }; + + render() { + return ( + <> + <button onClick={this.handleAgeChange}> + Increment age + </button> + <p>You are {this.state.age}.</p> + </> + ); + } +} +``` + +<Note> + +Defining `state` in class components is equivalent to calling [`useState`](/reference/react/useState) in function components. + +[See how to migrate.](#migrating-a-component-with-state-from-a-class-to-a-function) + +</Note> + +--- + +### `constructor(props)` {/*constructor*/} + +The [constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor) runs before your class component *mounts* (gets added to the screen). Typically, a constructor is only used for two purposes in React. It lets you declare state and [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind) your class methods to the class instance: + +```js {2-6} +class Counter extends Component { + constructor(props) { + super(props); + this.state = { counter: 0 }; + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + // ... + } +``` + +If you use modern JavaScript syntax, constructors are rarely needed. Instead, you can rewrite this code above using the [public class field syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields) which is supported both by modern browsers and tools like [Babel:](https://babeljs.io/) + +```js {2,4} +class Counter extends Component { + state = { counter: 0 }; + + handleClick = () => { + // ... + } +``` + +A constructor should not contain any side effects or subscriptions. + +#### Parameters {/*constructor-parameters*/} + +* `props`: The component's initial props. + +#### Returns {/*constructor-returns*/} + +`constructor` should not return anything. + +#### Caveats {/*constructor-caveats*/} + +* Do not run any side effects or subscriptions in the constructor. Instead, use [`componentDidMount`](#componentdidmount) for that. + +* Inside a constructor, you need to call `super(props)` before any other statement. If you don't do that, `this.props` will be `undefined` while the constructor runs, which can be confusing and cause bugs. + +* Constructor is the only place where you can assign [`this.state`](#state) directly. In all other methods, you need to use [`this.setState()`](#setstate) instead. Do not call `setState` in the constructor. + +* When you use [server rendering,](/reference/react-dom/server) the constructor will run on the server too, followed by the [`render`](#render) method. However, lifecycle methods like `componentDidMount` or `componentWillUnmount` will not run on the server. + +* When [Strict Mode](/reference/react/StrictMode) is on, React will call `constructor` twice in development and then throw away one of the instances. This helps you notice the accidental side effects that need to be moved out of the `constructor`. + +<Note> + +There is no exact equivalent for `constructor` in function components. To declare state in a function component, call [`useState`.](/reference/react/useState) To avoid recalculating the initial state, [pass a function to `useState`.](/reference/react/useState#avoiding-recreating-the-initial-state) + +</Note> + +--- + +### `componentDidCatch(error, info)` {/*componentdidcatch*/} + +If you define `componentDidCatch`, React will call it when some child component (including distant children) throws an error during rendering. This lets you log that error to an error reporting service in production. + +Typically, it is used together with [`static getDerivedStateFromError`](#static-getderivedstatefromerror) which lets you update state in response to an error and display an error message to the user. A component with these methods is called an *error boundary.* + +[See an example.](#catching-rendering-errors-with-an-error-boundary) + +#### Parameters {/*componentdidcatch-parameters*/} + +* `error`: The error that was thrown. In practice, it will usually be an instance of [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) but this is not guaranteed because JavaScript allows to [`throw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw) any value, including strings or even `null`. + +* `info`: An object containing additional information about the error. Its `componentStack` field contains a stack trace with the component that threw, as well as the names and source locations of all its parent components. In production, the component names will be minified. If you set up production error reporting, you can decode the component stack using sourcemaps the same way as you would do for regular JavaScript error stacks. + +#### Returns {/*componentdidcatch-returns*/} + +`componentDidCatch` should not return anything. + +#### Caveats {/*componentdidcatch-caveats*/} + +* In the past, it was common to call `setState` inside `componentDidCatch` in order to update the UI and display the fallback error message. This is deprecated in favor of defining [`static getDerivedStateFromError`.](#static-getderivedstatefromerror) + +* Production and development builds of React slightly differ in the way `componentDidCatch` handles errors. In development, the errors will bubble up to `window`, which means that any `window.onerror` or `window.addEventListener('error', callback)` will intercept the errors that have been caught by `componentDidCatch`. In production, instead, the errors will not bubble up, which means any ancestor error handler will only receive errors not explicitly caught by `componentDidCatch`. + +<Note> + +There is no direct equivalent for `componentDidCatch` in function components yet. If you'd like to avoid creating class components, write a single `ErrorBoundary` component like above and use it throughout your app. Alternatively, you can use the [`react-error-boundary`](https://github.com/bvaughn/react-error-boundary) package which does that for you. + +</Note> + +--- + +### `componentDidMount()` {/*componentdidmount*/} + +If you define the `componentDidMount` method, React will call it when your component is first added *(mounted)* to the screen. This is a common place to start data fetching, set up subscriptions, or manipulate the DOM nodes. + +If you implement `componentDidMount`, you usually need to implement other lifecycle methods to avoid bugs. For example, if `componentDidMount` reads some state or props, you also have to implement [`componentDidUpdate`](#componentdidupdate) to handle their changes, and [`componentWillUnmount`](#componentwillunmount) to clean up whatever `componentDidMount` was doing. + +```js {6-8} +class ChatRoom extends Component { + state = { + serverUrl: 'https://localhost:1234' + }; + + componentDidMount() { + this.setupConnection(); + } + + componentDidUpdate(prevProps, prevState) { + if ( + this.props.roomId !== prevProps.roomId || + this.state.serverUrl !== prevState.serverUrl + ) { + this.destroyConnection(); + this.setupConnection(); + } + } + + componentWillUnmount() { + this.destroyConnection(); + } + + // ... +} +``` + +[See more examples.](#adding-lifecycle-methods-to-a-class-component) + +#### Parameters {/*componentdidmount-parameters*/} + +`componentDidMount` does not take any parameters. + +#### Returns {/*componentdidmount-returns*/} + +`componentDidMount` should not return anything. + +#### Caveats {/*componentdidmount-caveats*/} + +- When [Strict Mode](/reference/react/StrictMode) is on, in development React will call `componentDidMount`, then immediately call [`componentWillUnmount`,](#componentwillunmount) and then call `componentDidMount` again. This helps you notice if you forgot to implement `componentWillUnmount` or if its logic doesn't fully "mirror" what `componentDidMount` does. + +- Although you may call [`setState`](#setstate) immediately in `componentDidMount`, it's best to avoid that when you can. It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the [`render`](#render) will be called twice in this case, the user won't see the intermediate state. Use this pattern with caution because it often causes performance issues. In most cases, you should be able to assign the initial state in the [`constructor`](#constructor) instead. It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position. + +<Note> + +For many use cases, defining `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` together in class components is equivalent to calling [`useEffect`](/reference/react/useEffect) in function components. In the rare cases where it's important for the code to run before browser paint, [`useLayoutEffect`](/reference/react/useLayoutEffect) is a closer match. + +[See how to migrate.](#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function) + +</Note> + +--- + +### `componentDidUpdate(prevProps, prevState, snapshot?)` {/*componentdidupdate*/} + +If you define the `componentDidUpdate` method, React will call it immediately after your component has been re-rendered with updated props or state. This method is not called for the initial render. + +You can use it to manipulate the DOM after an update. This is also a common place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed). Typically, you'd use it together with [`componentDidMount`](#componentdidmount) and [`componentWillUnmount`:](#componentwillunmount) + +```js {10-18} +class ChatRoom extends Component { + state = { + serverUrl: 'https://localhost:1234' + }; + + componentDidMount() { + this.setupConnection(); + } + + componentDidUpdate(prevProps, prevState) { + if ( + this.props.roomId !== prevProps.roomId || + this.state.serverUrl !== prevState.serverUrl + ) { + this.destroyConnection(); + this.setupConnection(); + } + } + + componentWillUnmount() { + this.destroyConnection(); + } + + // ... +} +``` + +[See more examples.](#adding-lifecycle-methods-to-a-class-component) + + +#### Parameters {/*componentdidupdate-parameters*/} + +* `prevProps`: Props before the update. Compare `prevProps` to [`this.props`](#props) to determine what changed. + +* `prevState`: State before the update. Compare `prevState` to [`this.state`](#state) to determine what changed. + +* `snapshot`: If you implemented [`getSnapshotBeforeUpdate`](#getsnapshotbeforeupdate), `snapshot` will contain the value you returned from that method. Otherwise, it will be `undefined`. + +#### Returns {/*componentdidupdate-returns*/} + +`componentDidUpdate` should not return anything. + +#### Caveats {/*componentdidupdate-caveats*/} + +- `componentDidUpdate` will not get called if [`shouldComponentUpdate`](#shouldcomponentupdate) is defined and returns `false`. + +- The logic inside `componentDidUpdate` should usually be wrapped in conditions comparing `this.props` with `prevProps`, and `this.state` with `prevState`. Otherwise, there's a risk of creating infinite loops. + +- Although you may call [`setState`](#setstate) immediately in `componentDidUpdate`, it's best to avoid that when you can. It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the [`render`](#render) will be called twice in this case, the user won't see the intermediate state. This pattern often causes performance issues, but it may be necessary for rare cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position. + +<Note> + +For many use cases, defining `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` together in class components is equivalent to calling [`useEffect`](/reference/react/useEffect) in function components. In the rare cases where it's important for the code to run before browser paint, [`useLayoutEffect`](/reference/react/useLayoutEffect) is a closer match. + +[See how to migrate.](#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function) + +</Note> +--- + +### `componentWillMount()` {/*componentwillmount*/} + +<Deprecated> + +This API has been renamed from `componentWillMount` to [`UNSAFE_componentWillMount`.](#unsafe_componentwillmount) The old name has been deprecated. In a future major version of React, only the new name will work. + +Run the [`rename-unsafe-lifecycles` codemod](https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles) to automatically update your components. + +</Deprecated> + +--- + +### `componentWillReceiveProps(nextProps)` {/*componentwillreceiveprops*/} + +<Deprecated> + +This API has been renamed from `componentWillReceiveProps` to [`UNSAFE_componentWillReceiveProps`.](#unsafe_componentwillreceiveprops) The old name has been deprecated. In a future major version of React, only the new name will work. + +Run the [`rename-unsafe-lifecycles` codemod](https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles) to automatically update your components. + +</Deprecated> + +--- + +### `componentWillUpdate(nextProps, nextState)` {/*componentwillupdate*/} + +<Deprecated> + +This API has been renamed from `componentWillUpdate` to [`UNSAFE_componentWillUpdate`.](#unsafe_componentwillupdate) The old name has been deprecated. In a future major version of React, only the new name will work. + +Run the [`rename-unsafe-lifecycles` codemod](https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles) to automatically update your components. + +</Deprecated> + +--- + +### `componentWillUnmount()` {/*componentwillunmount*/} + +If you define the `componentWillUnmount` method, React will call it before your component is removed *(unmounted)* from the screen. This is a common place to cancel data fetching or remove subscriptions. + +The logic inside `componentWillUnmount` should "mirror" the logic inside [`componentDidMount`.](#componentdidmount) For example, if `componentDidMount` sets up a subscription, `componentWillUnmount` should clean up that subscription. If the cleanup logic your `componentWillUnmount` reads some props or state, you will usually also need to implement [`componentDidUpdate`](#componentdidupdate) to clean up resources (such as subscriptions) corresponding to the old props and state. + +```js {20-22} +class ChatRoom extends Component { + state = { + serverUrl: 'https://localhost:1234' + }; + + componentDidMount() { + this.setupConnection(); + } + + componentDidUpdate(prevProps, prevState) { + if ( + this.props.roomId !== prevProps.roomId || + this.state.serverUrl !== prevState.serverUrl + ) { + this.destroyConnection(); + this.setupConnection(); + } + } + + componentWillUnmount() { + this.destroyConnection(); + } + + // ... +} +``` + +[See more examples.](#adding-lifecycle-methods-to-a-class-component) + +#### Parameters {/*componentwillunmount-parameters*/} + +`componentWillUnmount` does not take any parameters. + +#### Returns {/*componentwillunmount-returns*/} + +`componentWillUnmount` should not return anything. + +#### Caveats {/*componentwillunmount-caveats*/} + +- When [Strict Mode](/reference/react/StrictMode) is on, in development React will call [`componentDidMount`,](#componentdidmount) then immediately call `componentWillUnmount`, and then call `componentDidMount` again. This helps you notice if you forgot to implement `componentWillUnmount` or if its logic doesn't fully "mirror" what `componentDidMount` does. + +<Note> + +For many use cases, defining `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` together in class components is equivalent to calling [`useEffect`](/reference/react/useEffect) in function components. In the rare cases where it's important for the code to run before browser paint, [`useLayoutEffect`](/reference/react/useLayoutEffect) is a closer match. + +[See how to migrate.](#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function) + +</Note> + +--- + +### `forceUpdate(callback?)` {/*forceupdate*/} + +Forces a component to re-render. + +Usually, this is not necessary. If your component's [`render`](#render) method only reads from [`this.props`](#props), [`this.state`](#state), or [`this.context`,](#context) it will re-render automatically when you call [`setState`](#setstate) inside your component or one of its parents. However, if your component's `render` method reads directly from an external data source, you have to tell React to update the user interface when that data source changes. That's what `forceUpdate` lets you do. + +Try to avoid all uses of `forceUpdate` and only read from `this.props` and `this.state` in `render`. + +#### Parameters {/*forceupdate-parameters*/} + +* **optional** `callback` If specified, React will call the `callback` you've provided after the update is committed. + +#### Returns {/*forceupdate-returns*/} + +`forceUpdate` does not return anything. + +#### Caveats {/*forceupdate-caveats*/} + +- If you call `forceUpdate`, React will re-render without calling [`shouldComponentUpdate`.](#shouldcomponentupdate) + +<Note> + +Reading an external data source and forcing class components to re-render in response to its changes with `forceUpdate` has been superseded by [`useSyncExternalStore`](/reference/react/useSyncExternalStore) in function components. + +</Note> + +--- + +### `getChildContext()` {/*getchildcontext*/} + +<Deprecated> + +This API will be removed in a future major version of React. [Use `Context.Provider` instead.](/reference/react/createContext#provider) + +</Deprecated> + +Lets you specify the values for the [legacy context](https://reactjs.org/docs/legacy-context.html) is provided by this component. + +--- + +### `getSnapshotBeforeUpdate(prevProps, prevState)` {/*getsnapshotbeforeupdate*/} + +If you implement `getSnapshotBeforeUpdate`, React will call it immediately before React updates the DOM. It enables your component to capture some information from the DOM (e.g. scroll position) before it is potentially changed. Any value returned by this lifecycle method will be passed as a parameter to [`componentDidUpdate`.](#componentdidupdate) + +For example, you can use it in a UI like a chat thread that needs to preserve its scroll position during updates: + +```js {7-15,17} +class ScrollingList extends React.Component { + constructor(props) { + super(props); + this.listRef = React.createRef(); + } + + getSnapshotBeforeUpdate(prevProps, prevState) { + // Are we adding new items to the list? + // Capture the scroll position so we can adjust scroll later. + if (prevProps.list.length < this.props.list.length) { + const list = this.listRef.current; + return list.scrollHeight - list.scrollTop; + } + return null; + } + + componentDidUpdate(prevProps, prevState, snapshot) { + // If we have a snapshot value, we've just added new items. + // Adjust scroll so these new items don't push the old ones out of view. + // (snapshot here is the value returned from getSnapshotBeforeUpdate) + if (snapshot !== null) { + const list = this.listRef.current; + list.scrollTop = list.scrollHeight - snapshot; + } + } + + render() { + return ( + <div ref={this.listRef}>{/* ...contents... */}</div> + ); + } +} +``` + +In the above example, it is important to read the `scrollHeight` property directly in `getSnapshotBeforeUpdate`. It is not safe to read it in [`render`](#render), [`UNSAFE_componentWillReceiveProps`](#unsafe_componentwillreceiveprops), or [`UNSAFE_componentWillUpdate`](#unsafe_componentwillupdate) because there is a potential time gap between these methods getting called and React updating the DOM. + +#### Parameters {/*getsnapshotbeforeupdate-parameters*/} + +* `prevProps`: Props before the update. Compare `prevProps` to [`this.props`](#props) to determine what changed. + +* `prevState`: State before the update. Compare `prevState` to [`this.state`](#state) to determine what changed. + +#### Returns {/*getsnapshotbeforeupdate-returns*/} + +You should return a snapshot value of any type that you'd like, or `null`. The value you returned will be passed as the third argument to [`componentDidUpdate`.](#componentdidupdate) + +#### Caveats {/*getsnapshotbeforeupdate-caveats*/} + +- `getSnapshotBeforeUpdate` will not get called if [`shouldComponentUpdate`](#shouldcomponentupdate) is defined and returns `false`. + +<Note> + +At the moment, there is no equivalent to `getSnapshotBeforeUpdate` for function components. This use case is very uncommon, but if you have the need for it, for now you'll have to write a class component. + +</Note> + +--- + +### `render()` {/*render*/} + +The `render` method is the only required method in a class component. + +The `render` method should specify what you want to appear on the screen, for example: + +```js {4-6} +import { Component } from 'react'; + +class Greeting extends Component { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} +``` + +React may call `render` at any moment, so you shouldn't assume that it runs at a particular time. Usually, the `render` method should return a piece of [JSX](/learn/writing-markup-with-jsx), but a few [other return types](#render-returns) (like strings) are supported. To calculate the returned JSX, the `render` method can read [`this.props`](#props), [`this.state`](#state), and [`this.context`](#context). + +You should write the `render` method as a pure function, meaning that it should return the same result if props, state, and context are the same. It also shouldn't contain side effects (like setting up subscriptions) or interact with the browser APIs. Side effects should happen either in event handlers or methods like [`componentDidMount`.](#componentdidmount) + +#### Parameters {/*render-parameters*/} + +* `prevProps`: Props before the update. Compare `prevProps` to [`this.props`](#props) to determine what changed. + +* `prevState`: State before the update. Compare `prevState` to [`this.state`](#state) to determine what changed. + +#### Returns {/*render-returns*/} + +`render` can return any valid React node. This includes React elements such as `<div />`, strings, numbers, [portals](/reference/react-dom/createPortal), empty nodes (`null`, `undefined`, `true`, and `false`), and arrays of React nodes. + +#### Caveats {/*render-caveats*/} + +- `render` should be written as a pure function of props, state, and context. It should not have side effects. + +- `render` will not get called if [`shouldComponentUpdate`](#shouldcomponentupdate) is defined and returns `false`. + +- When [Strict Mode](/reference/react/StrictMode) is on, React will call `render` twice in development and then throw away one of the results. This helps you notice the accidental side effects that need to be moved out of the `render` method. + +- There is no one-to-one correspondence between the `render` call and the subsequent `componentDidMount` or `componentDidUpdate` call. Some of the `render` call results may be discarded by React when it's beneficial. + +--- + +### `setState(nextState, callback?)` {/*setstate*/} + +Call `setState` to update the state of your React component. + +```js {8-10} +class Form extends Component { + state = { + name: 'Taylor', + }; + + handleNameChange = (e) => { + const newName = e.target.value; + this.setState({ + name: newName + }); + } + + render() { + return ( + <> + <input value={this.state.name} onChange={this.handleNameChange} /> + <p>Hello, {this.state.name}. + </> + ); + } +} +``` + +`setState` enqueues changes to the component state. It tells React that this component and its children need to re-render with the new state. This is the main way you'll update the user interface in response to interactions. + +<Pitfall> + +Calling `setState` **does not** change the current state in the already executing code: + +```js {6} +function handleClick() { + console.log(this.state.name); // "Taylor" + this.setState({ + name: 'Robin' + }); + console.log(this.state.name); // Still "Taylor"! +} +``` + +It only affects what `this.state` will return starting from the *next* render. + +</Pitfall> + +You can also pass a function to `setState`. It lets you update state based on the previous state: + +```js {2-6} + handleIncreaseAge = () => { + this.setState(prevState => { + return { + age: prevState.age + 1 + }; + }); + } +``` + +You don't have to do this, but it's handy if you want to update state multiple times during the same event. + +#### Parameters {/*setstate-parameters*/} + +* `nextState`: Either an object or a function. + * If you pass an object as `nextState`, it will be shallowly merged into `this.state`. + * If you pass a function as `nextState`, it will be treated as an _updater function_. It must be pure, should take the pending state and props as arguments, and should return the object to be shallowly merged into `this.state`. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying all of the queued updaters to the previous state. + +* **optional** `callback`: If specified, React will call the `callback` you've provided after the update is committed. + +#### Returns {/*setstate-returns*/} + +`setState` does not return anything. + +#### Caveats {/*setstate-caveats*/} + +- Think of `setState` as a *request* rather than an immediate command to update the component. When multiple components update their state in response to an event, React will batch their updates and re-render them together in a single pass at the end of the event. In the rare case that you need to force a particular state update to be applied synchronously, you may wrap it in [`flushSync`,](/reference/react-dom/flushSync) but this may hurt performance. + +- `setState` does not update `this.state` immediately. This makes reading `this.state` right after calling `setState` a potential pitfall. Instead, use [`componentDidUpdate`](#componentdidupdate) or the setState `callback` argument, either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, you can pass a function to `nextState` as described above. + +<Note> + +Calling `setState` in class components is similar to calling a [`set` function](/reference/react/useState#setstate) in function components. + +[See how to migrate.](#migrating-a-component-with-state-from-a-class-to-a-function) + +</Note> + +--- + +### `shouldComponentUpdate(nextProps, nextState, nextContext)` {/*shouldcomponentupdate*/} + +If you define `shouldComponentUpdate`, React will call it to determine whether a re-render can be skipped. + +If you are confident you want to write it by hand, you may compare `this.props` with `nextProps` and `this.state` with `nextState` and return `false` to tell React the update can be skipped. + +```js {6-18} +class Rectangle extends Component { + state = { + isHovered: false + }; + + shouldComponentUpdate(nextProps, nextState) { + if ( + nextProps.position.x === this.props.position.x && + nextProps.position.y === this.props.position.y && + nextProps.size.width === this.props.size.width && + nextProps.size.height === this.props.size.height && + nextState.isHovered === this.state.isHovered + ) { + // Nothing has changed, so a re-render is unnecessary + return false; + } + return true; + } + + // ... +} + +``` + +React calls `shouldComponentUpdate` before rendering when new props or state are being received. Defaults to `true`. This method is not called for the initial render or when [`forceUpdate`](#forceupdate) is used. + +#### Parameters {/*shouldcomponentupdate-parameters*/} + +- `nextProps`: The next props that the component is about to render with. Compare `nextProps` to [`this.props`](#props) to determine what changed. +- `nextState`: The next props that the component is about to render with. Compare `nextState` to [`this.state`](#props) to determine what changed. +- `nextContext`: The next props that the component is about to render with. Compare `nextContext` to [`this.context`](#context) to determine what changed. Only available if you specify [`static contextType`](#static-contexttype) (modern) or [`static contextTypes`](#static-contexttypes) (legacy). + +#### Returns {/*shouldcomponentupdate-returns*/} + +Return `true` if you want the component to re-render. That's the default behavior. + +Return `false` to tell React that re-rendering can be skipped. + +#### Caveats {/*shouldcomponentupdate-caveats*/} + +- This method *only* exists as a performance optimization. If your component breaks without it, fix that first. + +- Consider using [`PureComponent`](/reference/react/PureComponent) instead of writing `shouldComponentUpdate` by hand. `PureComponent` shallowly compares props and state, and reduces the chance that you'll skip a necessary update. + +- We do not recommend doing deep equality checks or using `JSON.stringify` in `shouldComponentUpdate`. It makes performance unpredictable and dependent on the data structure of every prop and state. In the best case, you risk introducing multi-second stalls to your application, and in the worst case you risk crashing it. + +- Returning `false` does not prevent child components from re-rendering when *their* state changes. + +- Returning `false` does not *guarantee* that the component will not re-render. React will use the return value as a hint but it may still choose to re-render your component if it makes sense to do for other reasons. + +<Note> + +Optimizing class components with `shouldComponentUpdate` is similar to optimizing function components with [`memo`.](/reference/react/memo) Function components also offer more granular optimization with [`useMemo`.](/reference/react/useMemo) + +</Note> + +--- + +### `UNSAFE_componentWillMount()` {/*unsafe_componentwillmount*/} + +If you define `UNSAFE_componentWillMount`, React will call it immediately after the [`constructor`.](#constructor) It only exists for historical reasons and should not be used in any new code. Instead, use one of the alternatives: + +- To initialize state, declare [`state`](#state) as a class field or set `this.state` inside the [`constructor`.](#constructor) +- If you need to run a side effect or set up a subscription, move that logic to [`componentDidMount`](#componentdidmount) instead. + +[See examples of migrating away from unsafe lifecycles.](/blog/2018/03/27/update-on-async-rendering#examples) + +#### Parameters {/*unsafe_componentwillmount-parameters*/} + +`UNSAFE_componentWillMount` does not take any parameters. + +#### Returns {/*unsafe_componentwillmount-returns*/} + +`UNSAFE_componentWillMount` should not return anything. + +#### Caveats {/*unsafe_componentwillmount-caveats*/} + +- `UNSAFE_componentWillMount` will not get called if the component implements [`static getDerivedStateFromProps`](getDerivedStateFromProps) or [`getSnapshotBeforeUpdate`.](#getsnapshotbeforeupdate) + +- Despite its naming, `UNSAFE_componentWillMount` does not guarantee that the component *will* get mounted if your app uses modern React features like [`Suspense`.](/reference/react/Suspense) If a render attempt is suspended (for example, because the code for some child component has not loaded yet), React will throw the in-progress tree away and attempt to construct the component from scratch during the next attempt. This is why this method is "unsafe". Code that relies on mounting (like adding a subscription) should go into [`componentDidMount`.](#componentdidmount) + +- `UNSAFE_componentWillMount` is the only lifecycle method that runs during [server rendering.](/reference/react-dom/server) For all practical purposes, it is identical to [`constructor`,](#constructor) so you should use the `constructor` for this type of logic instead. + +<Note> + +Calling [`setState`](#setstate) inside `UNSAFE_componentWillMount` in a class component to initialize state is equivalent to passing that state as the initial state to [`useState`](/reference/react/useState) in a function component. + +</Note> + +--- + +### `UNSAFE_componentWillReceiveProps(nextProps, nextContext)` {/*unsafe_componentwillreceiveprops*/} + +If you define `UNSAFE_componentWillReceiveProps`, React will call it when the component receives new props. It only exists for historical reasons and should not be used in any new code. Instead, use one of the alternatives: + +- If you need to **run a side effect** (for example, fetch data, run an animation, or reinitialize a subscription) in response to prop changes, move that logic to [`componentDidUpdate`](#componentdidupdate) instead. +- If you need to **avoid re-computing some data only when a prop changes,** use a [memoization helper](/blog/2018/06/07/you-probably-dont-need-derived-state#what-about-memoization) instead. +- If you need to **"reset" some state when a prop changes,** consider either making a component [fully controlled](/blog/2018/06/07/you-probably-dont-need-derived-state#recommendation-fully-controlled-component) or [fully uncontrolled with a key](/blog/2018/06/07/you-probably-dont-need-derived-state#recommendation-fully-uncontrolled-component-with-a-key) instead. +- If you need to **"adjust" some state when a prop changes,** check whether you can compute all the necessary information from props alone during rendering. If you can't, use [`static getDerivedStateFromProps`](/reference/react/Component#static-getderivedstatefromprops) instead. + +[See examples of migrating away from unsafe lifecycles.](/blog/2018/03/27/update-on-async-rendering#updating-state-based-on-props) + +#### Parameters {/*unsafe_componentwillreceiveprops-parameters*/} + +- `nextProps`: The next props that the component is about to receive from its parent component. Compare `nextProps` to [`this.props`](#props) to determine what changed. +- `nextContext`: The next props that the component is about to receive from the closest provider. Compare `nextContext` to [`this.context`](#context) to determine what changed. Only available if you specify [`static contextType`](#static-contexttype) (modern) or [`static contextTypes`](#static-contexttypes) (legacy). + +#### Returns {/*unsafe_componentwillreceiveprops-returns*/} + +`UNSAFE_componentWillReceiveProps` should not return anything. + +#### Caveats {/*unsafe_componentwillreceiveprops-caveats*/} + +- `UNSAFE_componentWillReceiveProps` will not get called if the component implements [`static getDerivedStateFromProps`](getDerivedStateFromProps) or [`getSnapshotBeforeUpdate`.](#getsnapshotbeforeupdate) + +- Despite its naming, `UNSAFE_componentWillReceiveProps` does not guarantee that the component *will* receive those props if your app uses modern React features like [`Suspense`.](/reference/react/Suspense) If a render attempt is suspended (for example, because the code for some child component has not loaded yet), React will throw the in-progress tree away and attempt to construct the component from scratch during the next attempt. By the time of the next render attempt, the props might be different. This is why this method is "unsafe". Code that should run only for committed updates (like resetting a subscription) should go into [`componentDidUpdate`.](#componentdidupdate) + +- `UNSAFE_componentWillReceiveProps` does not mean that the component has received *different* props than the last time. You need to compare `nextProps` and `this.props` yourself to check if something changed. + +- React doesn't call `UNSAFE_componentWillReceiveProps` with initial props during mounting. It only calls this method if some of component's props are going to be updated. For example, calling [`setState`](#setstate) doesn't generally trigger `UNSAFE_componentWillReceiveProps` inside the same component. + +<Note> + +Calling [`setState`](#setstate) inside `UNSAFE_componentWillReceiveProps` in a class component to "adjust" state is equivalent to [calling the `set` function from `useState` during rendering](/reference/react/useState#storing-information-from-previous-renders) in a function component. + +</Note> + +--- + +### `UNSAFE_componentWillUpdate(nextProps, nextState)` {/*unsafe_componentwillupdate*/} + + +If you define `UNSAFE_componentWillUpdate`, React will call it before rendering with the new props or state. It only exists for historical reasons and should not be used in any new code. Instead, use one of the alternatives: + +- If you need to run a side effect (for example, fetch data, run an animation, or reinitialize a subscription) in response to prop or state changes, move that logic to [`componentDidUpdate`](#componentdidupdate) instead. +- If you need to read some information from the DOM (for example, to save the current scroll position) so that you can use it in [`componentDidUpdate`](#componentdidupdate) later, read it inside [`getSnapshotBeforeUpdate`](#getsnapshotbeforeupdate) instead. + +[See examples of migrating away from unsafe lifecycles.](/blog/2018/03/27/update-on-async-rendering#examples) + +#### Parameters {/*unsafe_componentwillupdate-parameters*/} + +- `nextProps`: The next props that the component is about to render with. Compare `nextProps` to [`this.props`](#props) to determine what changed. +- `nextState`: The next state that the component is about to render with. Compare `nextState` to [`this.state`](#state) to determine what changed. + +#### Returns {/*unsafe_componentwillupdate-returns*/} + +`UNSAFE_componentWillUpdate` should not return anything. + +#### Caveats {/*unsafe_componentwillupdate-caveats*/} + +- `UNSAFE_componentWillUpdate` will not get called if [`shouldComponentUpdate`](#shouldcomponentupdate) is defined and returns `false`. + +- `UNSAFE_componentWillUpdate` will not get called if the component implements [`static getDerivedStateFromProps`](getDerivedStateFromProps) or [`getSnapshotBeforeUpdate`.](#getsnapshotbeforeupdate) + +- It's not supported to call [`setState`](#setstate) (or any method that leads to `setState` being called, like dispatching a Redux action) during `componentWillUpdate`. + +- Despite its naming, `UNSAFE_componentWillUpdate` does not guarantee that the component *will* update if your app uses modern React features like [`Suspense`.](/reference/react/Suspense) If a render attempt is suspended (for example, because the code for some child component has not loaded yet), React will throw the in-progress tree away and attempt to construct the component from scratch during the next attempt. By the time of the next render attempt, the props and state might be different. This is why this method is "unsafe". Code that should run only for committed updates (like resetting a subscription) should go into [`componentDidUpdate`.](#componentdidupdate) + +- `UNSAFE_componentWillUpdate` does not mean that the component has received *different* props or state than the last time. You need to compare `nextProps` with `this.props` and `nextState` with `this.state` yourself to check if something changed. + +- React doesn't call `UNSAFE_componentWillUpdate` with initial props and state during mounting. + +<Note> + +There is no direct equivalent to `UNSAFE_componentWillUpdate` in function components. + +</Note> + +--- + +### `static childContextTypes` {/*static-childcontexttypes*/} + +<Deprecated> + +This API will be removed in a future major version of React. [Use `static contextType` instead.](#static-contexttype) + +</Deprecated> + +Lets you specify which [legacy context](https://reactjs.org/docs/legacy-context.html) is provided by this component. + +--- + +### `static contextTypes` {/*static-contexttypes*/} + +<Deprecated> + +This API will be removed in a future major version of React. [Use `static contextType` instead.](#static-contexttype) + +</Deprecated> + +Lets you specify which [legacy context](https://reactjs.org/docs/legacy-context.html) is consumed by this component. + +--- + +### `static contextType` {/*static-contexttype*/} + +If you want to read [`this.context`](#context-instance-field) from your class component, you must specify which context it needs to read. The context you specify as the `static contextType` must be a value previously created by [`createContext`.](/reference/react/createContext) + +```js {2} +class Button extends Component { + static contextType = ThemeContext; + + render() { + const theme = this.context; + const className = 'button-' + theme; + return ( + <button className={className}> + {this.props.children} + </button> + ); + } +} +``` + +<Note> + +Reading `this.context` in class components is equivalent to [`useContext`](/reference/react/useContext) in function components. + +[See how to migrate.](#migrating-a-component-with-context-from-a-class-to-a-function) + +</Note> + +--- + +### `static defaultProps` {/*static-defaultprops*/} + +You can define `static defaultProps` to set the default props for the class. They will be used for `undefined` and missing props, but not for `null` props. + +For example, here is how you define that the `color` prop should default to `'blue'`: + +```js {2-4} +class Button extends Component { + static defaultProps = { + color: 'blue' + }; + + render() { + return <button className={this.props.color}>click me</button>; + } +} +``` + +If the `color` prop is not provided or is `undefined`, it will be set by default to `'blue'`: + +```js +<> + {/* this.props.color is "blue" */} + <Button /> + + {/* this.props.color is "blue" */} + <Button color={undefined} /> + + {/* this.props.color is null */} + <Button color={null} /> + + {/* this.props.color is "red" */} + <Button color="red" /> +</> +``` + +<Note> + +Defining `defaultProps` in class components is similar to using [default values](/learn/passing-props-to-a-component#specifying-a-default-value-for-a-prop) in function components. + +</Note> + +--- + +### `static getDerivedStateFromError(error)` {/*static-getderivedstatefromerror*/} + +If you define `static getDerivedStateFromError`, React will call it when a child component (including distant children) throws an error during rendering. This lets you display an error message instead of clearing the UI. + +Typically, it is used together with [`componentDidCatch`](#componentDidCatch) which lets you send the error report to some analytics service. A component with these methods is called an *error boundary.* + +[See an example.](#catching-rendering-errors-with-an-error-boundary) + +#### Parameters {/*static-getderivedstatefromerror-parameters*/} + +* `error`: The error that was thrown. In practice, it will usually be an instance of [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) but this is not guaranteed because JavaScript allows to [`throw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw) any value, including strings or even `null`. + +#### Returns {/*static-getderivedstatefromerror-returns*/} + +`static getDerivedStateFromError` should return the state telling the component to display the error message. + +#### Caveats {/*static-getderivedstatefromerror-caveats*/} + +* `static getDerivedStateFromError` should be a pure function. If you want to perform a side effect (for example, to call an analytics service), you need to also implement [`componentDidCatch`.](#componentdidcatch) + +<Note> + +There is no direct equivalent for `static getDerivedStateFromError` in function components yet. If you'd like to avoid creating class components, write a single `ErrorBoundary` component like above and use it throughout your app. Alternatively, use the [`react-error-boundary`](https://github.com/bvaughn/react-error-boundary) package which does that. + +</Note> + +--- + +### `static getDerivedStateFromProps(props, state)` {/*static-getderivedstatefromprops*/} + +If you define `static getDerivedStateFromProps`, React will call it right before calling [`render`,](#render) both on the initial mount and on subsequent updates. It should return an object to update the state, or `null` to update nothing. + +This method exists for [rare use cases](/blog/2018/06/07/you-probably-dont-need-derived-state#when-to-use-derived-state) where the state depends on changes in props over time. For example, this `Form` component resets the `email` state when the `userID` prop changes: + +```js {7-18} +class Form extends Component { + state = { + email: this.props.defaultEmail, + prevUserID: this.props.userID + }; + + static getDerivedStateFromProps(props, state) { + // Any time the current user changes, + // Reset any parts of state that are tied to that user. + // In this simple example, that's just the email. + if (props.userID !== state.prevUserID) { + return { + prevUserID: props.userID, + email: props.defaultEmail + }; + } + return null; + } + + // ... +} +``` + +Note that this pattern requires you to keep a previous value of the prop (like `userID`) in state (like `prevUserID`). + +<Pitfall> + +Deriving state leads to verbose code and makes your components difficult to think about. [Make sure you're familiar with simpler alternatives:](/blog/2018/06/07/you-probably-dont-need-derived-state.html) + +- If you need to **perform a side effect** (for example, data fetching or an animation) in response to a change in props, use [`componentDidUpdate`](#componentdidupdate) method instead. +- If you want to **re-compute some data only when a prop changes,** [use a memoization helper instead.](/blog/2018/06/07/you-probably-dont-need-derived-state#what-about-memoization) +- If you want to **"reset" some state when a prop changes,** consider either making a component [fully controlled](/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-controlled-component) or [fully uncontrolled with a key](/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key) instead. + +</Pitfall> + +#### Parameters {/*static-getderivedstatefromprops-parameters*/} + +- `props`: The next props that the component is about to render with. +- `state`: The next state that the component is about to render with. + +#### Returns {/*static-getderivedstatefromprops-returns*/} + +`static getDerivedStateFromProps` return an object to update the state, or `null` to update nothing. + +#### Caveats {/*static-getderivedstatefromprops-caveats*/} + +- This method is fired on *every* render, regardless of the cause. This is different from [`UNSAFE_componentWillReceiveProps`](#unsafe_cmoponentwillreceiveprops), which only fires when the parent causes a re-render and not as a result of a local `setState`. + +- This method doesn't have access to the component instance. If you'd like, you can reuse some code between `static getDerivedStateFromProps` and the other class methods by extracting pure functions of the component props and state outside the class definition. + +<Note> + +Implementing `static getDerivedStateFromProps` in a class component is equivalent to [calling the `set` function from `useState` during rendering](/reference/react/useState#storing-information-from-previous-renders) in a function component. + +</Note> + +--- + +## Usage {/*usage*/} + +### Defining a class component {/*defining-a-class-component*/} + +To define a React component as a class, extend the built-in `Component` class and define a [`render` method:](#render) + +```js +import { Component } from 'react'; + +class Greeting extends Component { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} +``` + +React will call your [`render`](#render) method whenever it needs to figure out what to display on the screen. Usually, you will return some [JSX](/learn/writing-markup-with-jsx) from it. Your `render` method should be a [pure function:](https://en.wikipedia.org/wiki/Pure_function) it should only calculate the JSX. + +Similarly to [function components,](/learn/your-first-component#defining-a-component) a class component can [receive information by props](/learn/your-first-component#defining-a-component) from its parent component. However, the syntax for reading props is different. For example, if the parent component renders `<Greeting name="Taylor" />`, then you can read the `name` prop from [`this.props`](#props), like `this.props.name`: + +<Sandpack> + +```js +import { Component } from 'react'; + +class Greeting extends Component { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} + +export default function App() { + return ( + <> + <Greeting name="Sara" /> + <Greeting name="Cahal" /> + <Greeting name="Edite" /> + </> + ); +} +``` + +</Sandpack> + +Note that Hooks (functions starting with `use`, like [`useState`](/reference/react/useState)) are not supported inside class components. + +<Pitfall> + +We recommend to define components as functions instead of classes. [See how to migrate.](#migrating-a-simple-component-from-a-class-to-a-function) + +</Pitfall> + +--- + +### Adding state to a class component {/*adding-state-to-a-class-component*/} + +To add [state](/learn/state-a-components-memory) to a class, assign an object to a property called [`state`](#state). To update state, call [`this.setState`](#setstate). + +<Sandpack> + +```js +import { Component } from 'react'; + +export default class Counter extends Component { + state = { + name: 'Taylor', + age: 42, + }; + + handleNameChange = (e) => { + this.setState({ + name: e.target.value + }); + } + + handleAgeChange = () => { + this.setState({ + age: this.state.age + 1 + }); + }; + + render() { + return ( + <> + <input + value={this.state.name} + onChange={this.handleNameChange} + /> + <button onClick={this.handleAgeChange}> + Increment age + </button> + <p>Hello, {this.state.name}. You are {this.state.age}.</p> + </> + ); + } +} +``` + +```css +button { display: block; margin-top: 10px; } +``` + +</Sandpack> + +<Pitfall> + +We recommend to define components as functions instead of classes. [See how to migrate.](#migrating-a-component-with-state-from-a-class-to-a-function) + +</Pitfall> + +--- + +### Adding lifecycle methods to a class component {/*adding-lifecycle-methods-to-a-class-component*/} + +There are a few special methods you can define on your class. + +If you define the [`componentDidMount`](#componentdidmount) method, React will call it when your component is first added *(mounted)* to the screen. React will call [`componentDidUpdate`](#componentdidupdate) after your component re-renders due to changed props or state. React will call [`componentWillUnmount`](#componentwillunmount) after your component has been removed *(unmounted)* from the screen. + +If you implement `componentDidMount`, you usually need to implement all three lifecycles to avoid bugs. For example, if `componentDidMount` reads some state or props, you also have to implement `componentDidUpdate` to handle their changes, and `componentWillUnmount` to clean up whatever `componentDidMount` was doing. + +For example, this `ChatRoom` component keeps a chat connection synchronized with props and state: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js ChatRoom.js active +import { Component } from 'react'; +import { createConnection } from './chat.js'; + +export default class ChatRoom extends Component { + state = { + serverUrl: 'https://localhost:1234' + }; + + componentDidMount() { + this.setupConnection(); + } + + componentDidUpdate(prevProps, prevState) { + if ( + this.props.roomId !== prevProps.roomId || + this.state.serverUrl !== prevState.serverUrl + ) { + this.destroyConnection(); + this.setupConnection(); + } + } + + componentWillUnmount() { + this.destroyConnection(); + } + + setupConnection() { + this.connection = createConnection( + this.state.serverUrl, + this.props.roomId + ); + this.connection.connect(); + } + + destroyConnection() { + this.connection.disconnect(); + this.connection = null; + } + + render() { + return ( + <> + <label> + Server URL:{' '} + <input + value={this.state.serverUrl} + onChange={e => { + this.setState({ + serverUrl: e.target.value + }); + }} + /> + </label> + <h1>Welcome to the {this.props.roomId} room!</h1> + </> + ); + } +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Note that in development when [Strict Mode](/reference/react/StrictMode) is on, React will call `componentDidMount`, immediately call `componentWillUnmount`, and then call `componentDidMount` again. This helps you notice if you forgot to implement `componentWillUnmount` or if its logic doesn't fully "mirror" what `componentDidMount` does. + +<Pitfall> + +We recommend to define components as functions instead of classes. [See how to migrate.](#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function) + +</Pitfall> + +--- + +### Catching rendering errors with an error boundary {/*catching-rendering-errors-with-an-error-boundary*/} + +By default, if your application throws an error during rendering, React will remove its UI from the screen. To prevent this, you can wrap a part of your UI into an *error boundary*. An error boundary is a special component that lets you display some fallback UI instead of the part that crashed--for example, an error message. + +To implement an error boundary component, you need to provide [`static getDerivedStateFromError`](#static-getderivedstatefromerror) which lets you update state in response to an error and display an error message to the user. You can also optionally implement [`componentDidcatch`](#componentdidcatch) to add some extra logic, for example, to log the error to an analytics service. + +```js {7-10,12-19} +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch(error, info) { + // Example "componentStack": + // in ComponentThatThrows (created by App) + // in ErrorBoundary (created by App) + // in div (created by App) + // in App + logErrorToMyService(error, info.componentStack); + } + + render() { + if (this.state.hasError) { + // You can render any custom fallback UI + return this.props.fallback; + } + + return this.props.children; + } +} +``` + +Then you can wrap a part of your component tree with it: + +```js {1,3} +<ErrorBoundary fallback={<p>Something went wrong</p>}> + <Profile /> +</ErrorBoundary> +``` + +If `Profile` or its child component throws an error, `ErrorBoundary` will "catch" that error, display a fallback UI with the error message you've provided, and send a production error report to your error reporting service. + +You don't need to wrap every component into a separate error boundary. When you think about the [granularity of error boundaries,](https://aweary.dev/fault-tolerance-react/) consider where it makes sense to display an error message. For example, in a messaging app, it makes sense to place an error boundary around the list of conversations. It also makes sense to place one around every individual message. However, it wouldn't make sense to place a boundary around every avatar. + +<Note> + +There is currently no way to write an error boundary as a function component. However, you don't have to write the error boundary class yourself. For example, you can use [`react-error-boundary`](https://github.com/bvaughn/react-error-boundary) instead. + +</Note> + +--- + +## Alternatives {/*alternatives*/} + +### Migrating a simple component from a class to a function {/*migrating-a-simple-component-from-a-class-to-a-function*/} + +Typically, you will [define components as functions](/learn/your-first-component#defining-a-component) instead. + +For example, suppose you're converting this `Greeting` class component to a function: + +<Sandpack> + +```js +import { Component } from 'react'; + +class Greeting extends Component { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} + +export default function App() { + return ( + <> + <Greeting name="Sara" /> + <Greeting name="Cahal" /> + <Greeting name="Edite" /> + </> + ); +} +``` + +</Sandpack> + +Define a function called `Greeting`. This is where you will move the body of your `render` function. + +```js +function Greeting() { + // ... move the code from the render method here ... +} +``` + +Instead of `this.props.name`, define the `name` prop [using the destructuring syntax](/learn/passing-props-to-a-component) and read it directly: + +```js +function Greeting({ name }) { + return <h1>Hello, {name}!</h1>; +} +``` + +Here is a complete example: + +<Sandpack> + +```js +function Greeting({ name }) { + return <h1>Hello, {name}!</h1>; +} + +export default function App() { + return ( + <> + <Greeting name="Sara" /> + <Greeting name="Cahal" /> + <Greeting name="Edite" /> + </> + ); +} +``` + +</Sandpack> + +--- + +### Migrating a component with state from a class to a function {/*migrating-a-component-with-state-from-a-class-to-a-function*/} + +Suppose you're converting this `Counter` class component to a function: + +<Sandpack> + +```js +import { Component } from 'react'; + +export default class Counter extends Component { + state = { + name: 'Taylor', + age: 42, + }; + + handleNameChange = (e) => { + this.setState({ + name: e.target.value + }); + } + + handleAgeChange = (e) => { + this.setState({ + age: this.state.age + 1 + }); + }; + + render() { + return ( + <> + <input + value={this.state.name} + onChange={this.handleNameChange} + /> + <button onClick={this.handleAgeChange}> + Increment age + </button> + <p>Hello, {this.state.name}. You are {this.state.age}.</p> + </> + ); + } +} +``` + +```css +button { display: block; margin-top: 10px; } +``` + +</Sandpack> + +Start by declaring a function with the necessary [state variables:](/reference/react/useState#adding-state-to-a-component) + +```js {4-5} +import { useState } from 'react'; + +function Counter() { + const [name, setName] = useState('Taylor'); + const [age, setAge] = useState(42); + // ... +``` + +Next, convert the event handlers: + +```js {5-7,9-11} +function Counter() { + const [name, setName] = useState('Taylor'); + const [age, setAge] = useState(42); + + function handleNameChange(e) { + setName(e.target.value); + } + + function handleAgeChange() { + setAge(age + 1); + } + // ... +``` + +Finally, replace all references starting with `this` with the variables and functions you defined in your component. For example, replace `this.state.age` with `age`, and replace `this.handleNameChange` with `handleNameChange`. + +Here is a fully converted component: + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [name, setName] = useState('Taylor'); + const [age, setAge] = useState(42); + + function handleNameChange(e) { + setName(e.target.value); + } + + function handleAgeChange() { + setAge(age + 1); + } + + return ( + <> + <input + value={name} + onChange={handleNameChange} + /> + <button onClick={handleAgeChange}> + Increment age + </button> + <p>Hello, {name}. You are {age}.</p> + </> + ) +} +``` + +```css +button { display: block; margin-top: 10px; } +``` + +</Sandpack> + +--- + +### Migrating a component with lifecycle methods from a class to a function {/*migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function*/} + +Suppose you're converting this `ChatRoom` class component with lifecycle methods to a function: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js ChatRoom.js active +import { Component } from 'react'; +import { createConnection } from './chat.js'; + +export default class ChatRoom extends Component { + state = { + serverUrl: 'https://localhost:1234' + }; + + componentDidMount() { + this.setupConnection(); + } + + componentDidUpdate(prevProps, prevState) { + if ( + this.props.roomId !== prevProps.roomId || + this.state.serverUrl !== prevState.serverUrl + ) { + this.destroyConnection(); + this.setupConnection(); + } + } + + componentWillUnmount() { + this.destroyConnection(); + } + + setupConnection() { + this.connection = createConnection( + this.state.serverUrl, + this.props.roomId + ); + this.connection.connect(); + } + + destroyConnection() { + this.connection.disconnect(); + this.connection = null; + } + + render() { + return ( + <> + <label> + Server URL:{' '} + <input + value={this.state.serverUrl} + onChange={e => { + this.setState({ + serverUrl: e.target.value + }); + }} + /> + </label> + <h1>Welcome to the {this.props.roomId} room!</h1> + </> + ); + } +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +First, verify that your [`componentWillUnmount`](#componentwillunmount) does the opposite of [`componentDidMount`.](#componentdidmount) In the above example, that's true: it disconnects the connection that `componentDidMount` sets up. If such logic is missing, add it first. + +Next, verify that your [`componentDidUpdate`](#componentdidupdate) method handles changes to any props and state you're using in `componentDidMount`. In the above example, `componentDidMount` calls `setupConnection` which reads `this.state.serverUrl` and `this.props.roomId`. This is why `componentDidUpdate` checks whether `this.state.serverUrl` and `this.props.roomId` have changed, and resets the connection if they did. If your `componentDidUpdate` logic is missing or doesn't handle changes to all relevant props and state, fix that first. + +In the above example, the logic inside the lifecycle methods connects the component to a system outside of React (a chat server). To connect a component to an external system, [describe this logic as a single Effect:](/reference/react/useEffect#connecting-to-an-external-system) + +```js {6-12} +import { useState, useEffect } from 'react'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [serverUrl, roomId]); + + // ... +} +``` + +This [`useEffect`](/api/useEffect) call is equivalent to the logic in the lifecycle methods above. If your lifecycle methods do multiple unrelated things, [split them into multiple independent Effects.](/learn/removing-effect-dependencies#is-your-effect-doing-several-unrelated-things) Here is a complete example you can play with: + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ChatRoom from './ChatRoom.js'; + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js ChatRoom.js active +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export default function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId, serverUrl]); + + return ( + <> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +<Note> + +If your component does not synchronize with any external systems, [you might not need an Effect.](/learn/you-might-not-need-an-effect) + +</Note> + +--- + +### Migrating a component with context from a class to a function {/*migrating-a-component-with-context-from-a-class-to-a-function*/} + +In this example, the `Panel` and `Button` class components read [context](/learn/passing-data-deeply-with-context) from [`this.context`:](#context) + +<Sandpack> + +```js +import { createContext, Component } from 'react'; + +const ThemeContext = createContext(null); + +class Panel extends Component { + static contextType = ThemeContext; + + render() { + const theme = this.context; + const className = 'panel-' + theme; + return ( + <section className={className}> + <h1>{this.props.title}</h1> + {this.props.children} + </section> + ); + } +} + +class Button extends Component { + static contextType = ThemeContext; + + render() { + const theme = this.context; + const className = 'button-' + theme; + return ( + <button className={className}> + {this.props.children} + </button> + ); + } +} + +function Form() { + return ( + <Panel title="Welcome"> + <Button>Sign up</Button> + <Button>Log in</Button> + </Panel> + ); +} + +export default function MyApp() { + return ( + <ThemeContext.Provider value="dark"> + <Form /> + </ThemeContext.Provider> + ) +} +``` + +```css +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> + +When you convert them to function components, replace `this.context` with [`useContext`](/reference/react/useContext) calls: + +<Sandpack> + +```js +import { createContext, useContext } from 'react'; + +const ThemeContext = createContext(null); + +function Panel({ title, children }) { + const theme = useContext(ThemeContext); + const className = 'panel-' + theme; + return ( + <section className={className}> + <h1>{title}</h1> + {children} + </section> + ) +} + +function Button({ children }) { + const theme = useContext(ThemeContext); + const className = 'button-' + theme; + return ( + <button className={className}> + {children} + </button> + ); +} + +function Form() { + return ( + <Panel title="Welcome"> + <Button>Sign up</Button> + <Button>Log in</Button> + </Panel> + ); +} + +export default function MyApp() { + return ( + <ThemeContext.Provider value="dark"> + <Form /> + </ThemeContext.Provider> + ) +} +``` + +```css +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> diff --git a/beta/src/content/reference/react/Fragment.md b/beta/src/content/reference/react/Fragment.md new file mode 100644 index 000000000..3538d4f08 --- /dev/null +++ b/beta/src/content/reference/react/Fragment.md @@ -0,0 +1,210 @@ +--- +title: <Fragment> (<>...</>) +--- + +<Intro> + +`<Fragment>`, often used via `<>...</>` syntax, lets you group elements without a wrapper node. + +```js +<> + <OneChild /> + <AnotherChild /> +</> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<Fragment>` {/*fragment*/} + +Wrap elements in `<Fragment>` to group them together in situations where you need a single element. Grouping elements in `Fragment` has no effect on the resulting DOM; it is the same as if the elements were not grouped. The empty JSX tag `<></>` is shorthand for `<Fragment></Fragment>` in most cases. + +#### Props {/*props*/} + +- **optional** `key`: Fragments declared with the explicit `<Fragment>` syntax may have [keys.](https://beta.reactjs.org/learn/rendering-lists#keeping-list-items-in-order-with-key) + +#### Caveats {/*caveats*/} + +- If you want to pass `key` to a Fragment, you can't use the `<>...</>` syntax. You have to explicitly import `Fragment` from `'react'` and render `<Fragment key={yourKey}>...</Fragment>`. + +- React does not [reset state](/learn/preserving-and-resetting-state) when you go from rendering `<><Child /></>` to `[<Child />]` or back, or when you go from rendering `<><Child /></>` to `<Child />` and back. This only works a single level deep: for example, going from `<><><Child /></></>` to `<Child />` resets the state. See the precise semantics [here.](https://gist.github.com/clemmy/b3ef00f9507909429d8aa0d3ee4f986b) + +--- + +## Usage {/*usage*/} + +### Returning multiple elements {/*returning-multiple-elements*/} + +Use `Fragment`, or the equivalent `<>...</>` syntax, to group multiple elements together. You can use it to put multiple elements in any place where a single element can go. For example, a component can only return one element, but by using a Fragment you can group multiple elements together and then return them as a group: + +```js {3,6} +function Post() { + return ( + <> + <PostTitle /> + <PostBody /> + </> + ); +} +``` + +Fragments are useful because grouping elements with a Fragment has no effect on layout or styles, unlike if you wrapped the elements in some other container such as a DOM element. If you inspect this example with the browser tools, you'll see that all `<h1>` and `<p>` DOM nodes appear as siblings without wrappers around them: + +<Sandpack> + +```js +export default function Blog() { + return ( + <> + <Post title="An update" body="It's been a while since I posted..." /> + <Post title="My new blog" body="I am starting a new blog!" /> + </> + ) +} + +function Post({ title, body }) { + return ( + <> + <PostTitle title={title} /> + <PostBody body={body} /> + </> + ); +} + +function PostTitle({ title }) { + return <h1>{title}</h1> +} + +function PostBody({ body }) { + return ( + <article> + <p>{body}</p> + </article> + ); +} +``` + +</Sandpack> + +<DeepDive> + +#### How to write a Fragment without the special syntax? {/*how-to-write-a-fragment-without-the-special-syntax*/} + +The example above is equivalent to importing `Fragment` from React: + +```js {1,5,8} +import { Fragment } from 'react'; + +function Post() { + return ( + <Fragment> + <PostTitle /> + <PostBody /> + </Fragment> + ); +} +``` + +Usually you won't need this unless you need to [pass a `key` to your `Fragment`.](#rendering-a-list-of-fragments) + +</DeepDive> + +--- + +### Assigning multiple elements to a variable {/*assigning-multiple-elements-to-a-variable*/} + +Like any other element, you can assign Fragment elements to variables, pass them as props, and so on: + +```js +function CloseDialog() { + const buttons = ( + <> + <OKButton /> + <CancelButton /> + </> + ); + return ( + <AlertDialog buttons={buttons}> + Are you sure you want to leave this page? + </AlertDialog> + ); +} +``` + +--- + +### Grouping elements with text {/*grouping-elements-with-text*/} + +You can use `Fragment` to group text together with components: + +```js +function DateRangePicker({ start, end }) { + return ( + <> + From + <DatePicker date={start} /> + to + <DatePicker date={end} /> + </> + ); +} +``` + +--- + +### Rendering a list of Fragments {/*rendering-a-list-of-fragments*/} + +Here's a situation where you need to write `Fragment` explicitly instead of using the `<></>` syntax. When you [render multiple elements in a loop](/learn/rendering-lists), you need to assign a `key` to each element. If the elements within the loop are Fragments, you need to use the normal JSX element syntax in order to provide the `key` attribute: + +```js {3,6} +function Blog() { + return posts.map(post => + <Fragment key={post.id}> + <PostTitle title={post.title} /> + <PostBody body={post.body} /> + </Fragment> + ); +} +``` + +You can inspect the DOM to verify that there are no wrapper elements around the Fragment children: + +<Sandpack> + +```js +import { Fragment } from 'react'; + +const posts = [ + { id: 1, title: 'An update', body: "It's been a while since I posted..." }, + { id: 2, title: 'My new blog', body: 'I am starting a new blog!' } +]; + +export default function Blog() { + return posts.map(post => + <Fragment key={post.id}> + <PostTitle title={post.title} /> + <PostBody body={post.body} /> + </Fragment> + ); +} + +function PostTitle({ title }) { + return <h1>{title}</h1> +} + +function PostBody({ body }) { + return ( + <article> + <p>{body}</p> + </article> + ); +} +``` + +</Sandpack> diff --git a/beta/src/content/reference/react/Profiler.md b/beta/src/content/reference/react/Profiler.md new file mode 100644 index 000000000..5bfa3b317 --- /dev/null +++ b/beta/src/content/reference/react/Profiler.md @@ -0,0 +1,132 @@ +--- +title: <Profiler> +--- + +<Intro> + +`<Profiler>` lets you measure rendering performance of a React tree programmatically. + +```js +<Profiler id="App" onRender={onRender}> + <App /> +</Profiler> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<Profiler>` {/*profiler*/} + +Wrap a component tree in a `<Profiler>` to measure its rendering performance. + +```js +<Profiler id="App" onRender={onRender}> + <App /> +</Profiler> +``` + +#### Props {/*props*/} + +* `id`: A string identifying the part of the UI you are measuring. +* `onRender`: An [`onRender` callback](#onrender-callback) that React calls it every time components within the profiled tree update. It receives information about what was rendered and how much time it took. + +#### Caveats {/*caveats*/} + +* Profiling adds some additional overhead, so **it is disabled in the production build by default.** To opt into production profiling, you need to enable a [special production build with profiling enabled.](https://fb.me/react-profiling) + +--- + +### `onRender` callback {/*onrender-callback*/} + +React will call your `onRender` callback with information about what was rendered. + +```js +function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime) { + // Aggregate or log render timings... +} +``` + +#### Parameters {/*onrender-parameters*/} + +* `id`: The string `id` prop of the `<Profiler>` tree that has just committed. This lets you identify which part of the tree was committed if you are using multiple profilers. +* `phase`: `"mount"`, `"update"` or `"nested-update"`. This lets you know whether the tree has just been mounted for the first time or re-rendered due to a change in props, state, or hooks. +* `actualDuration`: The number of milliseconds spent rendering the `<Profiler>` and its descendants for the current update. This indicates how well the subtree makes use of memoization (e.g. [`memo`](/reference/react/memo) and [`useMemo`](/reference/react/useMemo)). Ideally this value should decrease significantly after the initial mount as many of the descendants will only need to re-render if their specific props change. +* `baseDuration`: The number of milliseconds estimating how much time it would take to re-render the entire `<Profiler>` subtree without any optimizations. It is calculated by summing up the most recent render durations of each component in the tree. This value estimates a worst-case cost of rendering (e.g. the initial mount or a tree with no memoization). Compare `actualDuration` against it to see if memoization is working. +* `startTime`: A numeric timestamp for when React began rendering the current update. +* `endTime`: A numeric timestamp for when React committed the current update. This value is shared between all profilers in a commit, enabling them to be grouped if desirable. + +--- + +## Usage {/*usage*/} + +### Measuring rendering performance programmatically {/*measuring-rendering-performance-programmatically*/} + +Wrap the `<Profiler>` component around a React tree to measure its rendering performance. + +```js {2,4} +<App> + <Profiler id="Sidebar" onRender={onRender}> + <Sidebar /> + </Profiler> + <PageContent /> +</App> +``` + +It requires two props: an `id` (string) and an `onRender` callback (function) which React calls any time a component within the tree "commits" an update. + +<Pitfall> + +Profiling adds some additional overhead, so **it is disabled in the production build by default.** To opt into production profiling, you need to enable a [special production build with profiling enabled.](https://fb.me/react-profiling) + +</Pitfall> + +<Note> + +`<Profiler>` lets you gather measurements programmatically. If you're looking for an interactive profiler, try the Profiler tab in [React Developer Tools](/learn/react-developer-tools). It exposes similar functionality as a browser extension. + +</Note> + +--- + +### Measuring different parts of the application {/*measuring-different-parts-of-the-application*/} + +You can use multiple `<Profiler>` components to measure different parts of your application: + +```js {5,7} +<App> + <Profiler id="Sidebar" onRender={onRender}> + <Sidebar /> + </Profiler> + <Profiler id="Content" onRender={onRender}> + <Content /> + </Profiler> +</App> +``` + +You can also nest `<Profiler>` components: + +```js {5,7,9,12} +<App> + <Profiler id="Sidebar" onRender={onRender}> + <Sidebar /> + </Profiler> + <Profiler id="Content" onRender={onRender}> + <Content> + <Profiler id="Editor" onRender={onRender}> + <Editor /> + </Profiler> + <Preview /> + </Content> + </Profiler> +</App> +``` + +Although `<Profiler>` is a lightweight component, it should be used only when necessary. Each use adds some CPU and memory overhead to an application. + +--- + diff --git a/beta/src/content/reference/react/PureComponent.md b/beta/src/content/reference/react/PureComponent.md new file mode 100644 index 000000000..a505131b7 --- /dev/null +++ b/beta/src/content/reference/react/PureComponent.md @@ -0,0 +1,208 @@ +--- +title: PureComponent +--- + +<Pitfall> + +We recommend to define components as functions instead of classes. [See how to migrate.](#alternatives) + +</Pitfall> + +<Intro> + +`PureComponent` is similar to [`Component`](/reference/react/Component) but it skips re-renders for same props and state. Class components are still supported by React, but we don't recommend using them in new code. + +```js +class Greeting extends PureComponent { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `PureComponent` {/*purecomponent*/} + +To skip re-rendering a class component for same props and state, extend `PureComponent` instead of [`Component`:](/reference/react/Component) + +```js +import { PureComponent } from 'react'; + +class Greeting extends PureComponent { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} +``` + +`PureComponent` is a subclass of `Component` and supports [all the `Component` APIs.](/reference/react/Component#reference) Extending `PureComponent` is equivalent to defining a custom [`shouldComponentUpdate`](/reference/react/Component#shouldcomponentupdate) method that shallowly compares props and state. + + +[See more examples below.](#usage) + +--- + +## Usage {/*usage*/} + +### Skipping unnecessary re-renders for class components {/*skipping-unnecessary-re-renders-for-class-components*/} + +React normally re-renders a component whenever its parent re-renders. As an optimization, you can create a component that React will not re-render when its parent re-renders so long as its new props and state are the same as the old props and state. [Class components](/reference/react/Component) can opt into this behavior by extending `PureComponent`: + +```js {1} +class Greeting extends PureComponent { + render() { + return <h1>Hello, {this.props.name}!</h1>; + } +} +``` + +A React component should always have [pure rendering logic.](/learn/keeping-components-pure) This means that it must return the same output if its props, state, and context haven't changed. By using `PureComponent`, you are telling React that your component complies with this requirement, so React doesn't need to re-render as long as its props and state haven't changed. However, your component will still re-render if a context that it's using changes. + +In this example, notice that the `Greeting` component re-renders whenever `name` is changed (because that's one of its props), but not when `address` is changed (because it's not passed to `Greeting` as a prop): + +<Sandpack> + +```js +import { PureComponent, useState } from 'react'; + +class Greeting extends PureComponent { + render() { + console.log("Greeting was rendered at", new Date().toLocaleTimeString()); + return <h3>Hello{this.props.name && ', '}{this.props.name}!</h3>; + } +} + +export default function MyApp() { + const [name, setName] = useState(''); + const [address, setAddress] = useState(''); + return ( + <> + <label> + Name{': '} + <input value={name} onChange={e => setName(e.target.value)} /> + </label> + <label> + Address{': '} + <input value={address} onChange={e => setAddress(e.target.value)} /> + </label> + <Greeting name={name} /> + </> + ); +} +``` + +```css +label { + display: block; + margin-bottom: 16px; +} +``` + +</Sandpack> + +<Pitfall> + +We recommend to define components as functions instead of classes. [See how to migrate.](#alternatives) + +</Pitfall> + +--- + +## Alternatives {/*alternatives*/} + +### Migrating from a `PureComponent` class component to a function {/*migrating-from-a-purecomponent-class-component-to-a-function*/} + +We recommend to use function components instead of [class components](/reference/react/Component) in the new code. If you have some existing class components using `PureComponent`, here is how you can convert them. This is the original code: + +<Sandpack> + +```js +import { PureComponent, useState } from 'react'; + +class Greeting extends PureComponent { + render() { + console.log("Greeting was rendered at", new Date().toLocaleTimeString()); + return <h3>Hello{this.props.name && ', '}{this.props.name}!</h3>; + } +} + +export default function MyApp() { + const [name, setName] = useState(''); + const [address, setAddress] = useState(''); + return ( + <> + <label> + Name{': '} + <input value={name} onChange={e => setName(e.target.value)} /> + </label> + <label> + Address{': '} + <input value={address} onChange={e => setAddress(e.target.value)} /> + </label> + <Greeting name={name} /> + </> + ); +} +``` + +```css +label { + display: block; + margin-bottom: 16px; +} +``` + +</Sandpack> + +When you [convert this component from a class to a function,](/reference/react/Component#alternatives) wrap it in [`memo`:](/reference/react/memo) + +<Sandpack> + +```js +import { memo, useState } from 'react'; + +const Greeting = memo(function Greeting({ name }) { + console.log("Greeting was rendered at", new Date().toLocaleTimeString()); + return <h3>Hello{name && ', '}{name}!</h3>; +}); + +export default function MyApp() { + const [name, setName] = useState(''); + const [address, setAddress] = useState(''); + return ( + <> + <label> + Name{': '} + <input value={name} onChange={e => setName(e.target.value)} /> + </label> + <label> + Address{': '} + <input value={address} onChange={e => setAddress(e.target.value)} /> + </label> + <Greeting name={name} /> + </> + ); +} +``` + +```css +label { + display: block; + margin-bottom: 16px; +} +``` + +</Sandpack> + +<Note> + +Unlike `PureComponent`, [`memo`](/reference/react/memo) does not compare the new and the old state. In function components, calling the [`set` function](/reference/react/useState#setstate) with the same state [already prevents re-renders by default,](/reference/react/memo#updating-a-memoized-component-using-state) even without `memo`. + +</Note> diff --git a/beta/src/content/reference/react/StrictMode.md b/beta/src/content/reference/react/StrictMode.md new file mode 100644 index 000000000..dab5e0614 --- /dev/null +++ b/beta/src/content/reference/react/StrictMode.md @@ -0,0 +1,830 @@ +--- +title: <StrictMode> +--- + + +<Intro> + +`<StrictMode>` lets you find common bugs in your components early during development. + + +```js +<StrictMode> + <App /> +</StrictMode> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<StrictMode>` {/*strictmode*/} + +Use `StrictMode` to enable additional development behaviors and warnings for the entire component tree inside: + +```js +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +const root = createRoot(document.getElementById('root')); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +``` + +[See more examples below.](#usage) + +Strict Mode enables the following development-only behaviors: + +- Your components will [re-render an extra time](#fixing-bugs-found-by-double-rendering-in-development) to find bugs caused by impure rendering. +- Your components will [re-run Effects an extra time](#fixing-bugs-found-by-re-running-effects-in-development) to find bugs caused by missing Effect cleanup. +- Your components will [be checked for usage of deprecated APIs.](#fixing-deprecation-warnings-enabled-by-strict-mode) + +#### Props {/*props*/} + +`StrictMode` accepts no props. + +#### Caveats {/*caveats*/} + +* There is no way to opt out of Strict Mode inside a tree wrapped in `<StrictMode>`. This gives you confidence that all components inside `<StrictMode>` are checked. If two teams working on a product disagree whether they find the checks valuable, they need to either reach consensus or move `<StrictMode>` down in the tree. + +--- + +## Usage {/*usage*/} + +### Enabling Strict Mode for entire app {/*enabling-strict-mode-for-entire-app*/} + +Strict Mode enables extra development-only checks for the entire component tree inside the `<StrictMode>` component. These checks help you find common bugs in your components early in the development process. + + +To enable Strict Mode for your entire app, wrap your root component with `<StrictMode>` when you render it: + +```js {6,8} +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +const root = createRoot(document.getElementById('root')); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +``` + +We recommend to wrap your entire app in Strict Mode, especially for newly created apps. If you use a framework that calls [`createRoot`](/reference/react/createRoot) for you, check its documentation for how to enable Strict Mode. + +Although the Strict Mode checks **only run in development,** they help you find bugs that already exist in your code but can be tricky to reliably reproduce in production. Strict Mode lets you fix bugs before your users report them. + +<Note> + +Strict Mode enables the following checks in development: + +- Your components will [re-render an extra time](#fixing-bugs-found-by-double-rendering-in-development) to find bugs caused by impure rendering. +- Your components will [re-run Effects an extra time](#fixing-bugs-found-by-re-running-effects-in-development) to find bugs caused by missing Effect cleanup. +- Your components will [be checked for usage of deprecated APIs.](#fixing-deprecation-warnings-enabled-by-strict-mode) + +**All of these checks are development-only and do not impact the production build.** + +</Note> + +--- + +### Enabling strict mode for a part of the app {/*enabling-strict-mode-for-a-part-of-the-app*/} + +You can also enable Strict Mode for any part of your application: + +```js {7,12} +import { StrictMode } from 'react'; + +function App() { + return ( + <> + <Header /> + <StrictMode> + <main> + <Sidebar /> + <Content /> + </main> + </StrictMode> + <Footer /> + </> + ); +} +``` + +In this example, Strict Mode checks will not run against the `Header` and `Footer` components. However, they will run on `Sidebar` and `Content`, as well as all of the components inside them, no matter how deep. + +--- + +### Fixing bugs found by double rendering in development {/*fixing-bugs-found-by-double-rendering-in-development*/} + +[React assumes that every component you write is a pure function.](/learn/keeping-components-pure) This means that React components you write must always return the same JSX given the same inputs (props, state, and context). + +Components breaking this rule behave unpredictably and cause bugs. To help you find accidentally impure code, Strict Mode calls some of your functions (only the ones that should be pure) **twice in development.** This includes: + +- Your component function body (only top-level logic, so this doesn't include code inside event handlers) +- Functions that you pass to [`useState`](/reference/react/useState), [`set` functions](/reference/react/useState#setstate), [`useMemo`](/reference/react/useMemo), or [`useReducer`](/reference/react/useReducer) +- Some class component methods like [`constructor`](/reference/react/Component#constructor), [`render`](/reference/react/Component#render), [`shouldComponentUpdate`](/reference/react/Component#shouldcomponentupdate) ([see the whole list](https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects)) + +If a function is pure, running it twice does not change its behavior because a pure function produces the same result every time. However, if a function is impure (for example, it mutates the data it receives), running that impure code twice tends to be noticeable (that's what makes it impure!) This helps you spot and fix the bug early. + +**Here is an example to illustrate how double rendering in Strict Mode helps you find bugs early.** + +This `StoryTray` component takes an array of `stories` and adds one last "Create Story" item at the end: + +<Sandpack> + +```js index.js +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById("root")); +root.render(<App />); +``` + +```js App.js +import { useState } from 'react'; +import StoryTray from './StoryTray.js'; + +let initialStories = [ + {id: 0, label: "Ankit's Story" }, + {id: 1, label: "Taylor's Story" }, +]; + +export default function App() { + let [stories, setStories] = useState(initialStories) + return ( + <div + style={{ + width: '100%', + height: '100%', + textAlign: 'center', + }} + > + <StoryTray stories={stories} /> + </div> + ); +} +``` + +```js StoryTray.js active +export default function StoryTray({ stories }) { + const items = stories; + items.push({ id: 'create', label: 'Create Story' }); + return ( + <ul> + {items.map(story => ( + <li key={story.id}> + {story.label} + </li> + ))} + </ul> + ); +} +``` + +```css +ul { + margin: 0; + list-style-type: none; + height: 100%; +} + +li { + border: 1px solid #aaa; + border-radius: 6px; + float: left; + margin: 5px; + margin-bottom: 20px; + padding: 5px; + width: 70px; + height: 100px; +} +``` + +</Sandpack> + +There is a mistake in the code above. However, it is easy to miss because the initial output appears correct. + +This mistake will become more noticeable if the `StoryTray` component re-renders multiple times. For example, let's make the `StoryTray` re-render with a different background color whenever you hover the pointer over it: + +<Sandpack> + +```js index.js +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById('root')); +root.render(<App />); +``` + +```js App.js +import { useState } from 'react'; +import StoryTray from './StoryTray.js'; + +let initialStories = [ + {id: 0, label: "Ankit's Story" }, + {id: 1, label: "Taylor's Story" }, +]; + +export default function App() { + let [stories, setStories] = useState(initialStories) + return ( + <div + style={{ + width: '100%', + height: '100%', + textAlign: 'center', + }} + > + <StoryTray stories={stories} /> + </div> + ); +} +``` + +```js StoryTray.js active +import { useState } from 'react'; + +export default function StoryTray({ stories }) { + const [isHover, setIsHover] = useState(false); + const items = stories; + items.push({ id: 'create', label: 'Create Story' }); + return ( + <ul + onPointerEnter={() => setIsHover(true)} + onPointerLeave={() => setIsHover(false)} + style={{ + backgroundColor: isHover ? '#ddd' : '#fff' + }} + > + {items.map(story => ( + <li key={story.id}> + {story.label} + </li> + ))} + </ul> + ); +} +``` + +```css +ul { + margin: 0; + list-style-type: none; + height: 100%; +} + +li { + border: 1px solid #aaa; + border-radius: 6px; + float: left; + margin: 5px; + margin-bottom: 20px; + padding: 5px; + width: 70px; + height: 100px; +} +``` + +</Sandpack> + +Notice how every time you hover over the `StoryTray` component, "Create Story" gets added to the list again. The intention of the code was to add it once at the end. But `StoryTray` directly modifies the `stories` array from the props. Every time `StoryTray` renders, it adds "Create Story" again at the end of the same array. In other words, `StoryTray` is not a pure function--running it multiple times produces different results. + +To fix this problem, you can make a copy of the array, and modify that copy instead of the original one: + +```js {2} +export default function StoryTray({ stories }) { + const items = stories.slice(); // Clone the array + // ✅ Good: Pushing into a new array + items.push({ id: 'create', label: 'Create Story' }); +``` + +This would [make the `StoryTray` function pure.](/learn/keeping-components-pure) Each time it is called, it would only modify a new copy of the array, and would not affect any external objects or variables. This solves the bug, but notice that you had to make the component re-render more often before it became obvious that something is wrong with its behavior. + +**In the original example, the bug wasn't obvious. Now let's wrap the original (buggy) code in `<StrictMode>`:** + +<Sandpack> + +```js index.js +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById("root")); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +``` + +```js App.js +import { useState } from 'react'; +import StoryTray from './StoryTray.js'; + +let initialStories = [ + {id: 0, label: "Ankit's Story" }, + {id: 1, label: "Taylor's Story" }, +]; + +export default function App() { + let [stories, setStories] = useState(initialStories) + return ( + <div + style={{ + width: '100%', + height: '100%', + textAlign: 'center', + }} + > + <StoryTray stories={stories} /> + </div> + ); +} +``` + +```js StoryTray.js active +export default function StoryTray({ stories }) { + const items = stories; + items.push({ id: 'create', label: 'Create Story' }); + return ( + <ul> + {items.map(story => ( + <li key={story.id}> + {story.label} + </li> + ))} + </ul> + ); +} +``` + +```css +ul { + margin: 0; + list-style-type: none; + height: 100%; +} + +li { + border: 1px solid #aaa; + border-radius: 6px; + float: left; + margin: 5px; + margin-bottom: 20px; + padding: 5px; + width: 70px; + height: 100px; +} +``` + +</Sandpack> + +**Strict Mode *always* calls your rendering function twice, so you can see the mistake right away** ("Create Story" appears twice). Strict Mode lets you notice such mistakes early in the process. When you fix your component to render in Strict Mode, you *also* fix many possible future production bugs like the hover functionality from before: + +<Sandpack> + +```js index.js +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById('root')); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +``` + +```js App.js +import { useState } from 'react'; +import StoryTray from './StoryTray.js'; + +let initialStories = [ + {id: 0, label: "Ankit's Story" }, + {id: 1, label: "Taylor's Story" }, +]; + +export default function App() { + let [stories, setStories] = useState(initialStories) + return ( + <div + style={{ + width: '100%', + height: '100%', + textAlign: 'center', + }} + > + <StoryTray stories={stories} /> + </div> + ); +} +``` + +```js StoryTray.js active +import { useState } from 'react'; + +export default function StoryTray({ stories }) { + const [isHover, setIsHover] = useState(false); + const items = stories.slice(); // Clone the array + items.push({ id: 'create', label: 'Create Story' }); + return ( + <ul + onPointerEnter={() => setIsHover(true)} + onPointerLeave={() => setIsHover(false)} + style={{ + backgroundColor: isHover ? '#ddd' : '#fff' + }} + > + {items.map(story => ( + <li key={story.id}> + {story.label} + </li> + ))} + </ul> + ); +} +``` + +```css +ul { + margin: 0; + list-style-type: none; + height: 100%; +} + +li { + border: 1px solid #aaa; + border-radius: 6px; + float: left; + margin: 5px; + margin-bottom: 20px; + padding: 5px; + width: 70px; + height: 100px; +} +``` + +</Sandpack> + +Without Strict Mode, it was easy to miss the bug until you added more re-renders. Strict Mode made the same bug appear right away. Strict Mode helps you find bugs before you push them to your team and to your users. + +[Read more about keeping components pure.](/learn/keeping-components-pure) + +<Note> + +If you have [React DevTools](/learn/react-developer-tools) installed, any `console.log` calls during the second render call will appear slightly dimmed. React DevTools also offers a setting (off by default) to suppress them completely. + +</Note> + +--- + +### Fixing bugs found by re-running Effects in development {/*fixing-bugs-found-by-re-running-effects-in-development*/} + +Strict Mode can also help find bugs in [Effects.](/learn/synchronizing-with-effects) + +Every Effect has some setup code and may have some cleanup code. Normally, React calls setup when the component *mounts* (is added to the screen) and calls cleanup when the component *unmounts* (is removed from the screen). Additionally, React calls cleanup and setup again if its dependencies changed since the last render. + +When Strict Mode is on, React will also run **one extra setup+cleanup cycle in development for every Effect.** This may feel surprising, but it helps reveal subtle bugs that are hard to catch manually. + +**Here is an example to illustrate how re-running Effects in Strict Mode helps you find bugs early.** + +Consider this example that connects a component to a chat: + +<Sandpack> + +```js index.js +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById("root")); +root.render(<App />); +``` + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; +const roomId = 'general'; + +export default function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + }, []); + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +let connections = 0; + +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + connections++; + console.log('Active connections: ' + connections); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + connections--; + console.log('Active connections: ' + connections); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +There is an issue with this code, but it might not be immediately clear. + +To make the issue more obvious, let's implement a feature. In the example below, `roomId` is not hardcoded. Instead, the user can select the `roomId` that they want to connect to from a dropdown. Click "Open chat" and then select different chat rooms one by one. Keep track of the number of active connections in the console: + +<Sandpack> + +```js index.js +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById("root")); +root.render(<App />); +``` + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1>; +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js chat.js +let connections = 0; + +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + connections++; + console.log('Active connections: ' + connections); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + connections--; + console.log('Active connections: ' + connections); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +You'll notice that the number of open connections always keeps growing. In a real app, this would cause performance and network problems. The issue is that [your Effect is missing a cleanup function:](/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed) + +```js {4} + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); +``` + +Now that your Effect "cleans up" after itself and destroys the outdated connections, the leak is solved. However, notice that the problem did not become immediately visible until you've added more features (the select box). + +**In the original example, the bug wasn't obvious. Now let's wrap the original (buggy) code in `<StrictMode>`:** + +<Sandpack> + +```js index.js +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById("root")); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +``` + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; +const roomId = 'general'; + +export default function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + }, []); + return <h1>Welcome to the {roomId} room!</h1>; +} +``` + +```js chat.js +let connections = 0; + +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + connections++; + console.log('Active connections: ' + connections); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + connections--; + console.log('Active connections: ' + connections); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +**With Strict Mode, you immediately see that there is a problem** (the number of active connections jumps to 2). This is because Strict Mode runs an extra setup+cleanup cycle for every Effect. This Effect has no cleanup logic, so it creates an extra connection but doesn't destroy it. This is a hint that you're missing a cleanup function. + +Strict Mode lets you notice such mistakes early in the process. When you fix your Effect by adding a cleanup function in Strict Mode, you *also* fix many possible future production bugs like the select box from before: + +<Sandpack> + +```js index.js +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './App'; + +const root = createRoot(document.getElementById("root")); +root.render( + <StrictMode> + <App /> + </StrictMode> +); +``` + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return <h1>Welcome to the {roomId} room!</h1>; +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js chat.js +let connections = 0; + +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + connections++; + console.log('Active connections: ' + connections); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + connections--; + console.log('Active connections: ' + connections); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Notice how the active connection count in the console doesn't keep growing anymore. + +Without Strict Mode, it was easy to miss that your Effect needed cleanup. By running *setup → cleanup → setup* instead of *setup* for your Effect in development, Strict Mode made the missing cleanup logic more noticeable. + +[Read more about implementing Effect cleanup.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) + +--- + +### Fixing deprecation warnings enabled by Strict Mode {/*fixing-deprecation-warnings-enabled-by-strict-mode*/} + +React warns if some component anywhere inside a `<StrictMode>` tree uses one of these deprecated APIs: + +* [`findDOMNode`](/reference/react-dom/findDOMNode). [See alternatives.](https://reactjs.org/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage) +* `UNSAFE_` class lifecycle methods like [`UNSAFE_componentWillMount`](/reference/react/Component#unsafe_componentwillmount). [See alternatives.](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#migrating-from-legacy-lifecycles) +* Legacy context ([`childContextTypes`](/reference/react/Component#static-childcontexttypes), [`contextTypes`](/reference/react/Component#static-contexttypes), and [`getChildContext`](/reference/react/Component#getchildcontext)). [See alternatives.](/reference/react/createContext) +* Legacy string refs ([`this.refs`](/reference/react/Component#refs)). [See alternatives.](https://reactjs.org/docs/strict-mode.html#warning-about-legacy-string-ref-api-usage) + +These APIs are primarily used in older [class components](/reference/react/Component) so they rarely appear in modern apps. diff --git a/beta/src/content/reference/react/Suspense.md b/beta/src/content/reference/react/Suspense.md new file mode 100644 index 000000000..6e4f44ff7 --- /dev/null +++ b/beta/src/content/reference/react/Suspense.md @@ -0,0 +1,2562 @@ +--- +title: <Suspense> +--- + +<Intro> + +`<Suspense>` lets you display a fallback until its children have finished loading. + + +```js +<Suspense fallback={<Loading />}> + <SomeComponent /> +</Suspense> +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `<Suspense>` {/*suspense*/} + +#### Props {/*props*/} +* `children`: The actual UI you intend to render. If `children` suspends while rendering, the Suspense boundary will switch to rendering `fallback`. +* `fallback`: An alternate UI to render in place of the actual UI if it has not finished loading. Any valid React node is accepted, though in practice, a fallback is a lightweight placeholder view, such as a loading spinner or skeleton. Suspense will automatically switch to `fallback` when `children` suspends, and back to `children` when the data is ready. If `fallback` suspends while rendering, it will activate the closest parent Suspense boundary. + +#### Caveats {/*caveats*/} + +- React does not preserve any state for renders that got suspended before they were able to mount for the first time. When the component has loaded, React will retry rendering the suspended tree from scratch. +- If Suspense was displaying content for the tree, but then it suspended again, the `fallback` will be shown again unless the update causing it was caused by [`startTransition`](/reference/react/startTransition) or [`useDeferredValue`](/reference/react/useDeferredValue). +- If React needs to hide the already visible content because it suspended again, it will clean up [layout Effects](/reference/react/useLayoutEffect) in the content tree. When the content is ready to be shown again, React will fire the layout Effects again. This lets you make sure that Effects measuring the DOM layout don't try to do this while the content is hidden. +- React includes under-the-hood optimizations like *Streaming Server Rendering* and *Selective Hydration* that are integrated with Suspense. Read [an architectural overview](https://github.com/reactwg/react-18/discussions/37) and watch [a technical talk](https://www.youtube.com/watch?v=pj5N-Khihgc) to learn more. + +--- + +## Usage {/*usage*/} + +### Displaying a fallback while content is loading {/*displaying-a-fallback-while-content-is-loading*/} + +You can wrap any part of your application with a Suspense boundary: + +```js [[1, 1, "<Loading />"], [2, 2, "<Albums />"]] +<Suspense fallback={<Loading />}> + <Albums /> +</Suspense> +``` + +React will display your <CodeStep step={1}>loading fallback</CodeStep> until all the code and data needed by <CodeStep step={2}>the children</CodeStep> has been loaded. + +In the example below, the `Albums` component *suspends* while fetching the list of albums. Until it's ready to render, React switches the closest Suspense boundary above to show the fallback--your `Loading` component. Then, when the data loads, React hides the `Loading` fallback and renders the `Albums` component with data. + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js hidden +import { useState } from 'react'; +import ArtistPage from './ArtistPage.js'; + +export default function App() { + const [show, setShow] = useState(false); + if (show) { + return ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } else { + return ( + <button onClick={() => setShow(true)}> + Open The Beatles artist page + </button> + ); + } +} +``` + +```js ArtistPage.js active +import { Suspense } from 'react'; +import Albums from './Albums.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Suspense fallback={<Loading />}> + <Albums artistId={artist.id} /> + </Suspense> + </> + ); +} + +function Loading() { + return <h2>🌀 Loading...</h2>; +} +``` + +```js Albums.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Albums({ artistId }) { + const albums = use(fetchData(`/${artistId}/albums`)); + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else { + throw Error('Not implemented'); + } +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +</Sandpack> + +<Note> + +**Only Suspense-enabled data sources will activate the Suspense component.** They include: + +- Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/advanced-features/react-18) +- Lazy-loading component code with [`lazy`](/reference/react/lazy) + +Suspense **does not** detect when data is fetched inside an Effect or event handler. + +The exact way you would load data in the `Albums` component above depends on your framework. If you use a Suspense-enabled framework, you'll find the details in its data fetching documentation. + +Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. + +</Note> + +--- + +### Revealing content together at once {/*revealing-content-together-at-once*/} + +By default, the whole tree inside Suspense is treated as a single unit. For example, even if *only one* of these components suspends waiting for some data, *all* of them together will be replaced by the loading indicator: + +```js {2-5} +<Suspense fallback={<Loading />}> + <Biography /> + <Panel> + <Albums /> + </Panel> +</Suspense> +``` + +Then, after all of them are ready to be displayed, they will all appear together at once. + +In the example below, both `Biography` and `Albums` fetch some data. However, because they are grouped under a single Suspense boundary, these components always "pop in" together at the same time. + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js hidden +import { useState } from 'react'; +import ArtistPage from './ArtistPage.js'; + +export default function App() { + const [show, setShow] = useState(false); + if (show) { + return ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } else { + return ( + <button onClick={() => setShow(true)}> + Open The Beatles artist page + </button> + ); + } +} +``` + +```js ArtistPage.js active +import { Suspense } from 'react'; +import Albums from './Albums.js'; +import Biography from './Biography.js'; +import Panel from './Panel.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Suspense fallback={<Loading />}> + <Biography artistId={artist.id} /> + <Panel> + <Albums artistId={artist.id} /> + </Panel> + </Suspense> + </> + ); +} + +function Loading() { + return <h2>🌀 Loading...</h2>; +} +``` + +```js Panel.js +export default function Panel({ children }) { + return ( + <section className="panel"> + {children} + </section> + ); +} +``` + +```js Biography.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Biography({ artistId }) { + const bio = use(fetchData(`/${artistId}/bio`)); + return ( + <section> + <p className="bio">{bio}</p> + </section> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Albums.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Albums({ artistId }) { + const albums = use(fetchData(`/${artistId}/albums`)); + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else if (url === '/the-beatles/bio') { + return await getBio(); + } else { + throw Error('Not implemented'); + } +} + +async function getBio() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 1500); + }); + + return `The Beatles were an English rock band, + formed in Liverpool in 1960, that comprised + John Lennon, Paul McCartney, George Harrison + and Ringo Starr.`; +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +```css +.bio { font-style: italic; } + +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} +``` + +</Sandpack> + +Components that load data don't have to be direct children of the Suspense boundary. For example, you can move `Biography` and `Albums` into a new `Details` component. This doesn't change the behavior. Because `Biography` and `Albums` share the same closest parent Suspense boundary, their reveal is coordinated together. + +```js {2,8-11} +<Suspense fallback={<Loading />}> + <Details artistId={artist.id} /> +</Suspense> + +function Details({ artistId }) { + return ( + <> + <Biography artistId={artistId} /> + <Panel> + <Albums artistId={artistId} /> + </Panel> + </> + ); +} +``` + +--- + +### Revealing nested content as it loads {/*revealing-nested-content-as-it-loads*/} + +When a component suspends, the closest parent Suspense component shows the fallback. This lets you nest multiple Suspense components to create a loading sequence. Each Suspense boundary's fallback will be filled in as the next level of content becomes available. For example, you can give the album list its own loading fallback: + +```js {3,7} +<Suspense fallback={<BigSpinner />}> + <Biography /> + <Suspense fallback={<AlbumsGlimmer />}> + <Panel> + <Albums /> + </Panel> + </Suspense> +</Suspense> +``` + +With this change, displaying the `Biography` doesn't need to "wait" for the `Albums` to load. + +The sequence will be: + +1. If `Biography` hasn't loaded yet, `BigSpinner` is shown in place of the entire content area. +1. Once `Biography` finishes loading, `BigSpinner` is replaced by the content. +1. If `Albums` hasn't loaded yet, `AlbumsGlimmer` is shown in place of `Albums` and its parent `Panel`. +1. Finally, once `Albums` finishes loading, it replaces `AlbumsGlimmer`. + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js hidden +import { useState } from 'react'; +import ArtistPage from './ArtistPage.js'; + +export default function App() { + const [show, setShow] = useState(false); + if (show) { + return ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } else { + return ( + <button onClick={() => setShow(true)}> + Open The Beatles artist page + </button> + ); + } +} +``` + +```js ArtistPage.js active +import { Suspense } from 'react'; +import Albums from './Albums.js'; +import Biography from './Biography.js'; +import Panel from './Panel.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Suspense fallback={<BigSpinner />}> + <Biography artistId={artist.id} /> + <Suspense fallback={<AlbumsGlimmer />}> + <Panel> + <Albums artistId={artist.id} /> + </Panel> + </Suspense> + </Suspense> + </> + ); +} + +function BigSpinner() { + return <h2>🌀 Loading...</h2>; +} + +function AlbumsGlimmer() { + return ( + <div className="glimmer-panel"> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + </div> + ); +} +``` + +```js Panel.js +export default function Panel({ children }) { + return ( + <section className="panel"> + {children} + </section> + ); +} +``` + +```js Biography.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Biography({ artistId }) { + const bio = use(fetchData(`/${artistId}/bio`)); + return ( + <section> + <p className="bio">{bio}</p> + </section> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Albums.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Albums({ artistId }) { + const albums = use(fetchData(`/${artistId}/albums`)); + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else if (url === '/the-beatles/bio') { + return await getBio(); + } else { + throw Error('Not implemented'); + } +} + +async function getBio() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + return `The Beatles were an English rock band, + formed in Liverpool in 1960, that comprised + John Lennon, Paul McCartney, George Harrison + and Ringo Starr.`; +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +```css +.bio { font-style: italic; } + +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-panel { + border: 1px dashed #aaa; + background: linear-gradient(90deg, rgba(221,221,221,1) 0%, rgba(255,255,255,1) 100%); + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-line { + display: block; + width: 60%; + height: 20px; + margin: 10px; + border-radius: 4px; + background: #f0f0f0; +} +``` + +</Sandpack> + +Suspense boundaries let you coordinate which parts of your UI should always "pop in" together at the same time, and which parts should progressively reveal more content in a sequence of loading states. You can add, move, or delete Suspense boundaries in any place in the tree without affecting the rest of your app's behavior. + +Don't put a Suspense boundary around every component. Suspense boundaries should not be more granular than the loading sequence that you want the user to experience. If you work with a designer, ask them where the loading states should be placed--it's likely that they've already included them in their design wireframes. + +--- + +### Showing stale content while fresh content is loading {/*showing-stale-content-while-fresh-content-is-loading*/} + +In this example, the `SearchResults` component suspends while fetching the search results. Try typing `"a"`, waiting for the results, and then editing it to `"ab"`. The results for `"a"` will get replaced by the loading fallback. + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState } from 'react'; +import SearchResults from './SearchResults.js'; + +export default function App() { + const [query, setQuery] = useState(''); + return ( + <> + <label> + Search albums: + <input value={query} onChange={e => setQuery(e.target.value)} /> + </label> + <Suspense fallback={<h2>Loading...</h2>}> + <SearchResults query={query} /> + </Suspense> + </> + ); +} +``` + +```js SearchResults.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function SearchResults({ query }) { + if (query === '') { + return null; + } + const albums = use(fetchData(`/search?q=${query}`)); + if (albums.length === 0) { + return <p>No matches for <i>"{query}"</i></p>; + } + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url.startsWith('/search?q=')) { + return await getSearchResults(url.slice('/search?q='.length)); + } else { + throw Error('Not implemented'); + } +} + +async function getSearchResults(query) { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + const allAlbums = [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; + + const lowerQuery = query.trim().toLowerCase(); + return allAlbums.filter(album => { + const lowerTitle = album.title.toLowerCase(); + return ( + lowerTitle.startsWith(lowerQuery) || + lowerTitle.indexOf(' ' + lowerQuery) !== -1 + ) + }); +} +``` + +```css +input { margin: 10px; } +``` + +</Sandpack> + +A common alternative UI pattern is to *defer* updating the list of results and to keep showing the previous results until the new results are ready. The [`useDeferredValue`](/reference/react/useDeferredValue) Hook lets you pass a deferred version of the query down: + +```js {3,11} +export default function App() { + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query); + return ( + <> + <label> + Search albums: + <input value={query} onChange={e => setQuery(e.target.value)} /> + </label> + <Suspense fallback={<h2>Loading...</h2>}> + <SearchResults query={deferredQuery} /> + </Suspense> + </> + ); +} +``` + +The `query` will update immediately, so the input will display the new value. However, the `deferredQuery` will keep its previous value until the data has loaded, so `SearchResults` will show the stale results for a bit. + +To make it more obvious to the user, you can add a visual indication when the stale result list is displayed: + +```js {2} +<div style={{ + opacity: query !== deferredQuery ? 0.5 : 1 +}}> + <SearchResults query={deferredQuery} /> +</div> +``` + +Enter `"a"` in the example below, wait for the results to load, and then edit the input to `"ab"`. Notice how instead of the Suspense fallback, you now see the slightly dimmed stale result list until the new results have loaded: + + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState, useDeferredValue } from 'react'; +import SearchResults from './SearchResults.js'; + +export default function App() { + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query); + const isStale = query !== deferredQuery; + return ( + <> + <label> + Search albums: + <input value={query} onChange={e => setQuery(e.target.value)} /> + </label> + <Suspense fallback={<h2>Loading...</h2>}> + <div style={{ opacity: isStale ? 0.5 : 1 }}> + <SearchResults query={deferredQuery} /> + </div> + </Suspense> + </> + ); +} +``` + +```js SearchResults.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function SearchResults({ query }) { + if (query === '') { + return null; + } + const albums = use(fetchData(`/search?q=${query}`)); + if (albums.length === 0) { + return <p>No matches for <i>"{query}"</i></p>; + } + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url.startsWith('/search?q=')) { + return await getSearchResults(url.slice('/search?q='.length)); + } else { + throw Error('Not implemented'); + } +} + +async function getSearchResults(query) { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + const allAlbums = [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; + + const lowerQuery = query.trim().toLowerCase(); + return allAlbums.filter(album => { + const lowerTitle = album.title.toLowerCase(); + return ( + lowerTitle.startsWith(lowerQuery) || + lowerTitle.indexOf(' ' + lowerQuery) !== -1 + ) + }); +} +``` + +```css +input { margin: 10px; } +``` + +</Sandpack> + +<Note> + +Both deferred values and [transitions](#preventing-already-revealed-content-from-hiding) let you avoid showing Suspense fallback in favor of inline indicators. Transitions mark the whole update as non-urgent so they are typically used by frameworks and router libraries for navigation. Deferred values, on the other hand, are mostly useful in application code where you want to mark a part of UI as non-urgent, meaning that it's allowed to "lag behind" the rest of the UI. + +</Note> + +--- + +### Preventing already revealed content from hiding {/*preventing-already-revealed-content-from-hiding*/} + +When a component suspends, the closest parent Suspense boundary switches to showing the fallback. This can lead to a jarring user experience if it was already displaying some content. Press the button in the example below: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState } from 'react'; +import IndexPage from './IndexPage.js'; +import ArtistPage from './ArtistPage.js'; +import Layout from './Layout.js'; + +export default function App() { + return ( + <Suspense fallback={<BigSpinner />}> + <Router /> + </Suspense> + ); +} + +function Router() { + const [page, setPage] = useState('/'); + + function navigate(url) { + setPage(url); + } + + let content; + if (page === '/') { + content = ( + <IndexPage navigate={navigate} /> + ); + } else if (page === '/the-beatles') { + content = ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } + return ( + <Layout> + {content} + </Layout> + ); +} + +function BigSpinner() { + return <h2>🌀 Loading...</h2>; +} +``` + +```js Layout.js +export default function Layout({ children }) { + return ( + <div className="layout"> + <section className="header"> + Music Browser + </section> + <main> + {children} + </main> + </div> + ); +} +``` + +```js IndexPage.js +export default function IndexPage({ navigate }) { + return ( + <button onClick={() => navigate('/the-beatles')}> + Open The Beatles artist page + </button> + ); +} +``` + +```js ArtistPage.js +import { Suspense } from 'react'; +import Albums from './Albums.js'; +import Biography from './Biography.js'; +import Panel from './Panel.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Biography artistId={artist.id} /> + <Suspense fallback={<AlbumsGlimmer />}> + <Panel> + <Albums artistId={artist.id} /> + </Panel> + </Suspense> + </> + ); +} + +function AlbumsGlimmer() { + return ( + <div className="glimmer-panel"> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + </div> + ); +} +``` + +```js Albums.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Albums({ artistId }) { + const albums = use(fetchData(`/${artistId}/albums`)); + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Biography.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Biography({ artistId }) { + const bio = use(fetchData(`/${artistId}/bio`)); + return ( + <section> + <p className="bio">{bio}</p> + </section> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Panel.js hidden +export default function Panel({ children }) { + return ( + <section className="panel"> + {children} + </section> + ); +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else if (url === '/the-beatles/bio') { + return await getBio(); + } else { + throw Error('Not implemented'); + } +} + +async function getBio() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + return `The Beatles were an English rock band, + formed in Liverpool in 1960, that comprised + John Lennon, Paul McCartney, George Harrison + and Ringo Starr.`; +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +```css +main { + min-height: 200px; + padding: 10px; +} + +.layout { + border: 1px solid black; +} + +.header { + background: #222; + padding: 10px; + text-align: center; + color: white; +} + +.bio { font-style: italic; } + +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-panel { + border: 1px dashed #aaa; + background: linear-gradient(90deg, rgba(221,221,221,1) 0%, rgba(255,255,255,1) 100%); + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-line { + display: block; + width: 60%; + height: 20px; + margin: 10px; + border-radius: 4px; + background: #f0f0f0; +} +``` + +</Sandpack> + +When you pressed the button, the `Router` component rendered `ArtistPage` instead of `IndexPage`. A component inside the `ArtistPage` suspended, so the closest Suspense boundary started showing the fallback. The closest Suspense boundary was near the root, so the whole site layout got replaced by `BigSpinner`. + +To prevent this from happening, you can mark the navigation state update as a *transition* with [`startTransition`:](/reference/react/startTransition) + +```js {5,7} +function Router() { + const [page, setPage] = useState('/'); + + function navigate(url) { + startTransition(() => { + setPage(url); + }); + } + // ... +``` + +This tells React that the state transition is not urgent, and it's better to keep showing the previous page instead of hiding any already revealed content. Notice how clicking the button now "waits" for the `Biography` to load: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, startTransition, useState } from 'react'; +import IndexPage from './IndexPage.js'; +import ArtistPage from './ArtistPage.js'; +import Layout from './Layout.js'; + +export default function App() { + return ( + <Suspense fallback={<BigSpinner />}> + <Router /> + </Suspense> + ); +} + +function Router() { + const [page, setPage] = useState('/'); + + function navigate(url) { + startTransition(() => { + setPage(url); + }); + } + + let content; + if (page === '/') { + content = ( + <IndexPage navigate={navigate} /> + ); + } else if (page === '/the-beatles') { + content = ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } + return ( + <Layout> + {content} + </Layout> + ); +} + +function BigSpinner() { + return <h2>🌀 Loading...</h2>; +} +``` + +```js Layout.js +export default function Layout({ children }) { + return ( + <div className="layout"> + <section className="header"> + Music Browser + </section> + <main> + {children} + </main> + </div> + ); +} +``` + +```js IndexPage.js +export default function IndexPage({ navigate }) { + return ( + <button onClick={() => navigate('/the-beatles')}> + Open The Beatles artist page + </button> + ); +} +``` + +```js ArtistPage.js +import { Suspense } from 'react'; +import Albums from './Albums.js'; +import Biography from './Biography.js'; +import Panel from './Panel.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Biography artistId={artist.id} /> + <Suspense fallback={<AlbumsGlimmer />}> + <Panel> + <Albums artistId={artist.id} /> + </Panel> + </Suspense> + </> + ); +} + +function AlbumsGlimmer() { + return ( + <div className="glimmer-panel"> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + </div> + ); +} +``` + +```js Albums.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Albums({ artistId }) { + const albums = use(fetchData(`/${artistId}/albums`)); + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Biography.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Biography({ artistId }) { + const bio = use(fetchData(`/${artistId}/bio`)); + return ( + <section> + <p className="bio">{bio}</p> + </section> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Panel.js hidden +export default function Panel({ children }) { + return ( + <section className="panel"> + {children} + </section> + ); +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else if (url === '/the-beatles/bio') { + return await getBio(); + } else { + throw Error('Not implemented'); + } +} + +async function getBio() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + return `The Beatles were an English rock band, + formed in Liverpool in 1960, that comprised + John Lennon, Paul McCartney, George Harrison + and Ringo Starr.`; +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +```css +main { + min-height: 200px; + padding: 10px; +} + +.layout { + border: 1px solid black; +} + +.header { + background: #222; + padding: 10px; + text-align: center; + color: white; +} + +.bio { font-style: italic; } + +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-panel { + border: 1px dashed #aaa; + background: linear-gradient(90deg, rgba(221,221,221,1) 0%, rgba(255,255,255,1) 100%); + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-line { + display: block; + width: 60%; + height: 20px; + margin: 10px; + border-radius: 4px; + background: #f0f0f0; +} +``` + +</Sandpack> + +A transition doesn't wait for *all* content to load. It only waits long enough to avoid hiding already revealed content. For example, the website `Layout` was already revealed, so it would be bad to hide it behind a loading spinner. However, the nested `Suspense` boundary around `Albums` is new, so the transition doesn't wait for it. + +<Note> + +Suspense-enabled routers are expected to wrap the navigation updates into transitions by default. + +</Note> + +--- + +### Indicating that a transition is happening {/*indicating-that-a-transition-is-happening*/} + +In the above example, once you click the button, there is no visual indication that a navigation is in progress. To add an indicator, you can replace [`startTransition`](/reference/react/startTransition) with [`useTransition`](/reference/react/useTransition) which gives you a boolean `isPending` value. In the example below, it's used to change the website header styling while a transition is happening: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState, useTransition } from 'react'; +import IndexPage from './IndexPage.js'; +import ArtistPage from './ArtistPage.js'; +import Layout from './Layout.js'; + +export default function App() { + return ( + <Suspense fallback={<BigSpinner />}> + <Router /> + </Suspense> + ); +} + +function Router() { + const [page, setPage] = useState('/'); + const [isPending, startTransition] = useTransition(); + + function navigate(url) { + startTransition(() => { + setPage(url); + }); + } + + let content; + if (page === '/') { + content = ( + <IndexPage navigate={navigate} /> + ); + } else if (page === '/the-beatles') { + content = ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } + return ( + <Layout isPending={isPending}> + {content} + </Layout> + ); +} + +function BigSpinner() { + return <h2>🌀 Loading...</h2>; +} +``` + +```js Layout.js +export default function Layout({ children, isPending }) { + return ( + <div className="layout"> + <section className="header" style={{ + opacity: isPending ? 0.7 : 1 + }}> + Music Browser + </section> + <main> + {children} + </main> + </div> + ); +} +``` + +```js IndexPage.js +export default function IndexPage({ navigate }) { + return ( + <button onClick={() => navigate('/the-beatles')}> + Open The Beatles artist page + </button> + ); +} +``` + +```js ArtistPage.js +import { Suspense } from 'react'; +import Albums from './Albums.js'; +import Biography from './Biography.js'; +import Panel from './Panel.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Biography artistId={artist.id} /> + <Suspense fallback={<AlbumsGlimmer />}> + <Panel> + <Albums artistId={artist.id} /> + </Panel> + </Suspense> + </> + ); +} + +function AlbumsGlimmer() { + return ( + <div className="glimmer-panel"> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + </div> + ); +} +``` + +```js Albums.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Albums({ artistId }) { + const albums = use(fetchData(`/${artistId}/albums`)); + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Biography.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Biography({ artistId }) { + const bio = use(fetchData(`/${artistId}/bio`)); + return ( + <section> + <p className="bio">{bio}</p> + </section> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Panel.js hidden +export default function Panel({ children }) { + return ( + <section className="panel"> + {children} + </section> + ); +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else if (url === '/the-beatles/bio') { + return await getBio(); + } else { + throw Error('Not implemented'); + } +} + +async function getBio() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + return `The Beatles were an English rock band, + formed in Liverpool in 1960, that comprised + John Lennon, Paul McCartney, George Harrison + and Ringo Starr.`; +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +```css +main { + min-height: 200px; + padding: 10px; +} + +.layout { + border: 1px solid black; +} + +.header { + background: #222; + padding: 10px; + text-align: center; + color: white; +} + +.bio { font-style: italic; } + +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-panel { + border: 1px dashed #aaa; + background: linear-gradient(90deg, rgba(221,221,221,1) 0%, rgba(255,255,255,1) 100%); + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-line { + display: block; + width: 60%; + height: 20px; + margin: 10px; + border-radius: 4px; + background: #f0f0f0; +} +``` + +</Sandpack> + +--- + +### Resetting Suspense boundaries on navigation {/*resetting-suspense-boundaries-on-navigation*/} + +During a transition, React will avoid hiding already revealed content. However, if you navigate to a route with different parameters, you might want to tell React it is *different* content. You can express this with a `key`: + +```js +<ProfilePage key={queryParams.id} /> +``` + +Imagine you're navigating within a user's profile page, and something suspends. If that update is wrapped in a transition, it will not trigger the fallback for already visible content. That's the expected behavior. + +However, now imagine you're navigating between two different user profiles. In that case, it makes sense to show the fallback. For example, one user's timeline is *different content* from another user's timeline. By specifying a `key`, you ensure that React treats different users' profiles as different components, and resets the Suspense boundaries during navigation. A Suspense-integrated routing framework should do this automatically. + +--- + +### Providing a fallback for server errors and server-only content {/*providing-a-fallback-for-server-errors-and-server-only-content*/} + +If you use one of the [streaming server rendering APIs](/reference/react-dom/server) (or a framework that relies on them), React will also use your `<Suspense>` boundaries to handle errors on the server. If a component throws an error on the server, React will not abort the server render. Instead, it will find the closest `<Suspense>` component above it and include its fallback (such as a spinner) into the generated server HTML. The user will see a spinner instead of an error. + +On the client, React will attempt to render the same component again. If it errors on the client too, React will throw the error and display the closest [error boundary.](/reference/react/Component#static-getderivedstatefromerror) However, if it does not error on the client, React will not display the error to the user since the content was eventually displayed successfully. + +You can use this to opt out some components from rendering on the server. To do this, throw an error from them in the server environment and then wrap them in a `<Suspense>` boundary to replace their HTML with fallbacks: + +```js +<Suspense fallback={<Loading />}> + <Chat /> +</Suspense> + +function Chat() { + if (typeof window === 'undefined') { + throw Error('Chat should only render on the client.'); + } + // ... +} +``` + +The server HTML will include the loading indicator. It will be replaced by the `Chat` component on the client. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### How do I prevent the UI from being replaced by a fallback during an update? {/*preventing-unwanted-fallbacks*/} + +Replacing visible UI with a fallback creates a jarring user experience. This can happen when an update causes a component to suspend, and the nearest Suspense boundary is already showing content to the user. + +To prevent this from happening, [mark the update as non-urgent using `startTransition`](#preventing-already-revealed-content-from-hiding). During a transition, React will wait until enough data has loaded to prevent an unwanted fallback from appearing: + +```js {2-3,5} +function handleNextPageClick() { + // If this update suspends, don't hide the already displayed content + startTransition(() => { + setCurrentPage(currentPage + 1); + }); +} +``` + +This will avoid hiding existing content. However, any newly rendered `Suspense` boundaries will still immediately display fallbacks to avoid blocking the UI and let the user see the content as it becomes available. + +**React will only prevent unwanted fallbacks during non-urgent updates**. It will not delay a render if it's the result of an urgent update. You must opt in with an API like [`startTransition`](/reference/react/startTransition) or [`useDeferredValue`](/reference/react/useDeferredValue). + +If your router is integrated with Suspense, it should wrap its updates into [`startTransition`](/reference/react/startTransition) automatically. diff --git a/beta/src/content/reference/react/apis.md b/beta/src/content/reference/react/apis.md new file mode 100644 index 000000000..9c1437870 --- /dev/null +++ b/beta/src/content/reference/react/apis.md @@ -0,0 +1,17 @@ +--- +title: "Built-in React APIs" +--- + +<Intro> + +In addition to [Hooks](/reference/react) and [Components](/reference/react/components), the `react` package exports a few other APIs that are useful for defining components. This page lists all the remaining modern React APIs. + +</Intro> + +--- + +* [`createContext`](/reference/react/createContext) lets you define and provide context to the child components. Used with [`useContext`.](/reference/react/useContext) +* [`forwardRef`](/reference/react/forwardRef) lets your component expose a DOM node as a ref to the parent. Used with [`useRef`.](/reference/react/useRef) +* [`lazy`](/reference/react/lazy) lets you defer loading a component's code until it's rendered for the first time. +* [`memo`](/reference/react/memo) lets your component skip re-renders with same props. Used with [`useMemo`](/reference/react/useMemo) and [`useCallback`.](/reference/react/useCallback) +* [`startTransition`](/reference/react/startTransition) lets you mark a state update as non-urgent. Similar to [`useTransition`.](/reference/react/useTransition) diff --git a/beta/src/content/reference/react/cloneElement.md b/beta/src/content/reference/react/cloneElement.md new file mode 100644 index 000000000..bdd5d9aaa --- /dev/null +++ b/beta/src/content/reference/react/cloneElement.md @@ -0,0 +1,694 @@ +--- +title: cloneElement +--- + +<Pitfall> + +Using `cloneElement` is uncommon and can lead to fragile code. [See common alternatives.](#alternatives) + +</Pitfall> + +<Intro> + +`cloneElement` lets you create a new React element using another element as a starting point. + +```js +const clonedElement = cloneElement(element, props, ...children) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `cloneElement(element, props, ...children)` {/*cloneelement*/} + +Call `cloneElement` to create a React element based on the `element`, but with different `props` and `children`: + +```js +import { cloneElement } from 'react'; + +// ... +const clonedElement = cloneElement( + <Row title="Cabbage"> + Hello + </Row>, + { isHighlighted: true }, + 'Goodbye' +); + +console.log(clonedElement); // <Row title="Cabbage">Goodbye</Row> +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `element`: The `element` argument must be a valid React element. For example, it could be a JSX node like `<Something />`, the result of calling [`createElement`](/reference/react/createElement), or the result of another `cloneElement` call. + +* `props`: The `props` argument must either be an object or `null`. If you pass `null`, the cloned element will retain all of the original `element.props`. Otherwise, for every prop in the `props` object, the returned element will "prefer" the value from `props` over the value from `element.props`. The rest of the props will be filled from the original `element.props`. If you pass `props.key` or `props.ref`, they will replace the original ones. + +* **optional** `...children`: Zero or more child nodes. They can be any React nodes, including React elements, strings, numbers, [portals](/reference/react-dom/createPortal), empty nodes (`null`, `undefined`, `true`, and `false`), and arrays of React nodes. If you don't pass any `...children` arguments, the original `element.props.children` will be preserved. + +#### Returns {/*returns*/} + +`cloneElement` returns a React element object with a few properties: + +* `type`: Same as `element.type`. +* `props`: The result of shallowly merging `element.props` with the overriding `props` you have passed. +* `ref`: The original `element.ref`, unless it was overridden by `props.ref`. +* `key`: The original `element.key`, unless it was overridden by `props.key`. + +Usually, you'll return the element from your component or make it a child of another element. Although you may read the element's properties, it's best to treat every element as opaque after it's created, and only render it. + +#### Caveats {/*caveats*/} + +* Cloning an element **does not modify the original element.** + +* You should only **pass children as multiple arguments to `cloneElement` if they are all statically known,** like `cloneElement(element, null, child1, child2, child3)`. If your children are dynamic, pass the entire array as the third argument: `cloneElement(element, null, listItems)`. This ensures that React will [warn you about missing `key`s](/learn/rendering-lists#keeping-list-items-in-order-with-key) for any dynamic lists. For static lists this is not necessary because they never reorder. + +* `cloneElement` makes it harder to trace the data flow, so **try the [alternatives](/#alternatives) instead.** + +--- + +## Usage {/*usage*/} + +### Overriding props of an element {/*overriding-props-of-an-element*/} + +To override the props of some <CodeStep step={1}>React element</CodeStep>, pass it to `cloneElement` with the <CodeStep step={2}>props you want to override</CodeStep>: + +```js [[1, 5, "<Row title=\\"Cabbage\\" />"], [2, 6, "{ isHighlighted: true }"], [3, 4, "clonedElement"]] +import { cloneElement } from 'react'; + +// ... +const clonedElement = cloneElement( + <Row title="Cabbage" />, + { isHighlighted: true } +); +``` + +Here, the resulting <CodeStep step={3}>cloned element</CodeStep> will be `<Row title="Cabbage" isHighlighted={true} />`. + +**Let's walk through an example to see when it's useful.** + +Imagine a `List` component that renders its [`children`](/learn/passing-props-to-a-component#passing-jsx-as-children) as a list of selectable rows with a "Next" button that changes which row is selected. The `List` component needs to render the selected `Row` differently, so it clones every `<Row>` child that it has received, and adds an extra `isHighlighted: true` or `isHighlighted: false` prop: + +```js {6-8} +export default function List({ children }) { + const [selectedIndex, setSelectedIndex] = useState(0); + return ( + <div className="List"> + {Children.map(children, (child, index) => + cloneElement(child, { + isHighlighted: index === selectedIndex + }) + )} +``` + +Let's say the original JSX received by `List` looks like this: + +```js {2-4} +<List> + <Row title="Cabbage" /> + <Row title="Garlic" /> + <Row title="Apple" /> +</List> +``` + +By cloning its children, the `List` can pass extra information to every `Row` inside. The result looks like this: + +```js {4,8,12} +<List> + <Row + title="Cabbage" + isHighlighted={true} + /> + <Row + title="Garlic" + isHighlighted={false} + /> + <Row + title="Apple" + isHighlighted={false} + /> +</List> +``` + +Notice how pressing "Next" updates the state of the `List`, and highlights a different row: + +<Sandpack> + +```js +import List from './List.js'; +import Row from './Row.js'; +import { products } from './data.js'; + +export default function App() { + return ( + <List> + {products.map(product => + <Row + key={product.id} + title={product.title} + /> + )} + </List> + ); +} +``` + +```js List.js active +import { Children, cloneElement, useState } from 'react'; + +export default function List({ children }) { + const [selectedIndex, setSelectedIndex] = useState(0); + return ( + <div className="List"> + {Children.map(children, (child, index) => + cloneElement(child, { + isHighlighted: index === selectedIndex + }) + )} + <hr /> + <button onClick={() => { + setSelectedIndex(i => + (i + 1) % Children.count(children) + ); + }}> + Next + </button> + </div> + ); +} +``` + +```js Row.js +export default function Row({ title, isHighlighted }) { + return ( + <div className={[ + 'Row', + isHighlighted ? 'RowHighlighted' : '' + ].join(' ')}> + {title} + </div> + ); +} +``` + +```js data.js +export const products = [ + { title: 'Cabbage', id: 1 }, + { title: 'Garlic', id: 2 }, + { title: 'Apple', id: 3 }, +]; +``` + +```css +.List { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} + +.RowHighlighted { + background: #ffa; +} + +button { + height: 40px; + font-size: 20px; +} +``` + +</Sandpack> + +To summarize, the `List` cloned the `<Row />` elements it received and added an extra prop to them. + +<Pitfall> + +Cloning children makes it hard to tell how the data flows through your app. Try one of the [alternatives.](#alternatives) + +</Pitfall> + +--- + +## Alternatives {/*alternatives*/} + +### Passing data with a render prop {/*passing-data-with-a-render-prop*/} + +Instead of using `cloneElement`, consider accepting a *render prop* like `renderItem`. Here, `List` receives `renderItem` as a prop. `List` calls `renderItem` for every item and passes `isHighlighted` as an argument: + +```js {1,7} +export default function List({ items, renderItem }) { + const [selectedIndex, setSelectedIndex] = useState(0); + return ( + <div className="List"> + {items.map((item, index) => { + const isHighlighted = index === selectedIndex; + return renderItem(item, isHighlighted); + })} +``` + +The `renderItem` prop is called a "render prop" because it's a prop that specifies how to render something. For example, you can pass a `renderItem` implementation that renders a `<Row>` with the given `isHighlighted` value: + +```js {3,7} +<List + items={products} + renderItem={(product, isHighlighted) => + <Row + key={product.id} + title={product.title} + isHighlighted={isHighlighted} + /> + } +/> +``` + +The end result is the same as with `cloneElement`: + +```js {4,8,12} +<List> + <Row + title="Cabbage" + isHighlighted={true} + /> + <Row + title="Garlic" + isHighlighted={false} + /> + <Row + title="Apple" + isHighlighted={false} + /> +</List> +``` + +However, you can clearly trace where the `isHighlighted` value is coming from. + +<Sandpack> + +```js +import List from './List.js'; +import Row from './Row.js'; +import { products } from './data.js'; + +export default function App() { + return ( + <List + items={products} + renderItem={(product, isHighlighted) => + <Row + key={product.id} + title={product.title} + isHighlighted={isHighlighted} + /> + } + /> + ); +} +``` + +```js List.js active +import { useState } from 'react'; + +export default function List({ items, renderItem }) { + const [selectedIndex, setSelectedIndex] = useState(0); + return ( + <div className="List"> + {items.map((item, index) => { + const isHighlighted = index === selectedIndex; + return renderItem(item, isHighlighted); + })} + <hr /> + <button onClick={() => { + setSelectedIndex(i => + (i + 1) % items.length + ); + }}> + Next + </button> + </div> + ); +} +``` + +```js Row.js +export default function Row({ title, isHighlighted }) { + return ( + <div className={[ + 'Row', + isHighlighted ? 'RowHighlighted' : '' + ].join(' ')}> + {title} + </div> + ); +} +``` + +```js data.js +export const products = [ + { title: 'Cabbage', id: 1 }, + { title: 'Garlic', id: 2 }, + { title: 'Apple', id: 3 }, +]; +``` + +```css +.List { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} + +.RowHighlighted { + background: #ffa; +} + +button { + height: 40px; + font-size: 20px; +} +``` + +</Sandpack> + +This pattern is preferred to `cloneElement` because it is more explicit. + +--- + +### Passing data through context {/*passing-data-through-context*/} + +Another alternative to `cloneElement` is to [pass data through context.](/learn/passing-data-deeply-with-context) + + +For example, you can call [`createContext`](/reference/react/createContext) to define a `HighlightContext`: + +```js +export const HighlightContext = createContext(false); +``` + +Your `List` component can wrap every item it renders into a `HighlightContext` provider: + +```js {8,10} +export default function List({ items, renderItem }) { + const [selectedIndex, setSelectedIndex] = useState(0); + return ( + <div className="List"> + {items.map((item, index) => { + const isHighlighted = index === selectedIndex; + return ( + <HighlightContext.Provider key={item.id} value={isHighlighted}> + {renderItem(item)} + </HighlightContext.Provider> + ); + })} +``` + +With this approach, `Row` does not need to receive an `isHighlighted` prop at all. Instead, it reads the context: + +```js Row.js {2} +export default function Row({ title }) { + const isHighlighted = useContext(HighlightContext); + // ... +```` + +This allows the calling component to not know or worry about passing `isHighlighted` to `<Row>`: + +```js {4} +<List + items={products} + renderItem={product => + <Row title={product.title} /> + } +/> +``` + +Instead, `List` and `Row` coordinate the highlighting logic through context. + +<Sandpack> + +```js +import List from './List.js'; +import Row from './Row.js'; +import { products } from './data.js'; + +export default function App() { + return ( + <List + items={products} + renderItem={(product) => + <Row title={product.title} /> + } + /> + ); +} +``` + +```js List.js active +import { useState } from 'react'; +import { HighlightContext } from './HighlightContext.js'; + +export default function List({ items, renderItem }) { + const [selectedIndex, setSelectedIndex] = useState(0); + return ( + <div className="List"> + {items.map((item, index) => { + const isHighlighted = index === selectedIndex; + return ( + <HighlightContext.Provider + key={item.id} + value={isHighlighted} + > + {renderItem(item)} + </HighlightContext.Provider> + ); + })} + <hr /> + <button onClick={() => { + setSelectedIndex(i => + (i + 1) % items.length + ); + }}> + Next + </button> + </div> + ); +} +``` + +```js Row.js +import { useContext } from 'react'; +import { HighlightContext } from './HighlightContext.js'; + +export default function Row({ title }) { + const isHighlighted = useContext(HighlightContext); + return ( + <div className={[ + 'Row', + isHighlighted ? 'RowHighlighted' : '' + ].join(' ')}> + {title} + </div> + ); +} +``` + +```js HighlightContext.js +import { createContext } from 'react'; + +export const HighlightContext = createContext(false); +``` + +```js data.js +export const products = [ + { title: 'Cabbage', id: 1 }, + { title: 'Garlic', id: 2 }, + { title: 'Apple', id: 3 }, +]; +``` + +```css +.List { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} + +.RowHighlighted { + background: #ffa; +} + +button { + height: 40px; + font-size: 20px; +} +``` + +</Sandpack> + +[Learn more about passing data through context.](/reference/react/useContext#passing-data-deeply-into-the-tree) + +--- + +### Extracting logic into a custom Hook {/*extracting-logic-into-a-custom-hook*/} + +Another approach you can try is to extract the "non-visual" logic into your own Hook, and use the information returned by your Hook to decide what to render. For example, you could write a `useList` custom Hook like this: + +```js +import { useState } from 'react'; + +export default function useList(items) { + const [selectedIndex, setSelectedIndex] = useState(0); + + function onNext() { + setSelectedIndex(i => + (i + 1) % items.length + ); + } + + const selected = items[selectedIndex]; + return [selected, onNext]; +} +``` + +Then you could use it like this: + +```js {2,9,13} +export default function App() { + const [selected, onNext] = useList(products); + return ( + <div className="List"> + {products.map(product => + <Row + key={product.id} + title={product.title} + isHighlighted={selected === product} + /> + )} + <hr /> + <button onClick={onNext}> + Next + </button> + </div> + ); +} +``` + +The data flow is explicit, but the state is inside the `useList` custom Hook that you can use from any component: + +<Sandpack> + +```js +import Row from './Row.js'; +import useList from './useList.js'; +import { products } from './data.js'; + +export default function App() { + const [selected, onNext] = useList(products); + return ( + <div className="List"> + {products.map(product => + <Row + key={product.id} + title={product.title} + isHighlighted={selected === product} + /> + )} + <hr /> + <button onClick={onNext}> + Next + </button> + </div> + ); +} +``` + +```js useList.js +import { useState } from 'react'; + +export default function useList(items) { + const [selectedIndex, setSelectedIndex] = useState(0); + + function onNext() { + setSelectedIndex(i => + (i + 1) % items.length + ); + } + + const selected = items[selectedIndex]; + return [selected, onNext]; +} +``` + +```js Row.js +export default function Row({ title, isHighlighted }) { + return ( + <div className={[ + 'Row', + isHighlighted ? 'RowHighlighted' : '' + ].join(' ')}> + {title} + </div> + ); +} +``` + +```js data.js +export const products = [ + { title: 'Cabbage', id: 1 }, + { title: 'Garlic', id: 2 }, + { title: 'Apple', id: 3 }, +]; +``` + +```css +.List { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} + +.RowHighlighted { + background: #ffa; +} + +button { + height: 40px; + font-size: 20px; +} +``` + +</Sandpack> + +This approach is particularly useful if you want to reuse this logic between different components. diff --git a/beta/src/content/reference/react/components.md b/beta/src/content/reference/react/components.md new file mode 100644 index 000000000..7ce3fab63 --- /dev/null +++ b/beta/src/content/reference/react/components.md @@ -0,0 +1,24 @@ +--- +title: "Built-in React Components" +--- + +<Intro> + +React exposes a few built-in components that you can use in your JSX. + +</Intro> + +--- + +## Built-in components {/*built-in-components*/} + +* [`<Fragment>`](/reference/react/Fragment), alternatively written as `<>...</>`, lets you group multiple JSX nodes together. +* [`<Profiler>`](/reference/react/Profiler) lets you measure rendering performance of a React tree programmatically. +* [`<Suspense>`](/reference/react/Suspense) lets you display a fallback while the child components are loading. +* [`<StrictMode>`](/reference/react/StrictMode) enables extra development-only checks that help you find bugs early. + +--- + +## Your own components {/*your-own-components*/} + +You can also [define your own components](/learn/your-first-component) as JavaScript functions. diff --git a/beta/src/content/reference/react/createContext.md b/beta/src/content/reference/react/createContext.md new file mode 100644 index 000000000..4d719ac59 --- /dev/null +++ b/beta/src/content/reference/react/createContext.md @@ -0,0 +1,217 @@ +--- +title: createContext +--- + +<Intro> + +`createContext` lets you create a [context](/learn/passing-data-deeply-with-context) that components can provide or read. + +```js +const SomeContext = createContext(defaultValue) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `createContext(defaultValue)` {/*createcontext*/} + +Call `createContext` outside of any components to create a context. + +```js +import { createContext } from 'react'; + +const ThemeContext = createContext('light'); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `defaultValue`: The value that you want the context to have when there is no matching context provider in the tree above the component that reads context. If you don't have any meaningful default value, specify `null`. The default value is meant as a "last resort" fallback. It is static and never changes over time. + +#### Returns {/*returns*/} + +`createContext` returns a context object. + +**The context object itself does not hold any information.** It represents _which_ context other components can read or provide. Typically, you will use [`SomeContext.Provider`](#provider) in components above to specify the context value, and call [`useContext(SomeContext)`](/reference/react/useContext) in components below to read it. The context object has a few properties: + +* `SomeContext.Provider` lets you provide the context value to components. +* `SomeContext.Consumer` is an alternative and rarely used way to read the context value. + +--- + +### `SomeContext.Provider` {/*provider*/} + +Wrap your components into a context provider to specify the value of this context for all components inside: + +```js +function App() { + const [theme, setTheme] = useState('light'); + // ... + return ( + <ThemeContext.Provider value={theme}> + <Page /> + </ThemeContext.Provider> + ); +} +``` + +#### Props {/*provider-props*/} + +* `value`: The value that you want to pass to all the components reading this context inside this provider, no matter how deep. The context value can be of any type. A component calling [`useContext(SomeContext)`](/reference/react/useContext) inside of the provider receives the `value` of the innermost corresponding context provider above it. + +--- + +### `SomeContext.Consumer` {/*consumer*/} + +Before `useContext` existed, there was an older way to read context: + +```js +function Button() { + // 🟡 Legacy way (not recommended) + return ( + <ThemeContext.Consumer> + {theme => ( + <button className={theme} /> + )} + </ThemeContext.Consumer> + ); +} +``` + +Although this older way still works, but **newly written code should read context with [`useContext()`](/reference/react/useContext) instead:** + +```js +function Button() { + // ✅ Recommended way + const theme = useContext(ThemeContext); + return <button className={theme} />; +} +``` + +#### Props {/*consumer-props*/} + +* `children`: A function. React will call the function you pass with the current context value determined by the same algorithm as [`useContext()`](/reference/react/useContext) does, and render the result you return from this function. React will also re-run this function and update the UI whenever the context passed from the parent components have changed. + +--- + +## Usage {/*usage*/} + +### Creating context {/*creating-context*/} + +Context lets components [pass information deep down](/learn/passing-data-deeply-with-context) without explicitly passing props. + +Call `createContext` outside any components to create one or more contexts. + +```js [[1, 3, "ThemeContext"], [1, 4, "AuthContext"], [3, 3, "'light'"], [3, 4, "null"]] +import { createContext } from 'react'; + +const ThemeContext = createContext('light'); +const AuthContext = createContext(null); +``` + +`createContext` returns a <CodeStep step={1}>context object</CodeStep>. Components can read context by passing it to [`useContext()`](/reference/react/useContext): + +```js [[1, 2, "ThemeContext"], [1, 7, "AuthContext"]] +function Button() { + const theme = useContext(ThemeContext); + // ... +} + +function Profile() { + const currentUser = useContext(AuthContext); + // ... +} +``` + +By default, the values they receive will be the <CodeStep step={3}>default values</CodeStep> you have specified when creating the contexts. However, by itself this isn't useful because the default values never change. + +Context is useful because you can **provide other, dynamic values from your components:** + +```js {8-9,11-12} +function App() { + const [theme, setTheme] = useState('dark'); + const [currentUser, setCurrentUser] = useState({ name: 'Taylor' }); + + // ... + + return ( + <ThemeContext.Provider value={theme}> + <AuthContext.Provider value={currentUser}> + <Page /> + </AuthContext.Provider> + </ThemeContext.Provider> + ); +} +``` + +Now the `Page` component and any components inside it, no matter how deep, will "see" the passed context values. If the passed context values change, React will re-render the components reading the context as well. + +[Read more about reading and providing context and see examples.](/reference/react/useContext) + +--- + +### Importing and exporting context from a file {/*importing-and-exporting-context-from-a-file*/} + +Often, components in different files will need access to the same context. This is why it's common to declare contexts in a separate file. Then you can use the [`export` statement](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export) to make context available for other files: + +```js {4-5} +// Contexts.js +import { createContext } from 'react'; + +export const ThemeContext = createContext('light'); +export const AuthContext = createContext(null); +```` + +Components declared in other files can then use the [`import`](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/import) statement to read or provide this context: + +```js {2} +// Button.js +import { ThemeContext } from './Contexts.js'; + +function Button() { + const theme = useContext(ThemeContext); + // ... +} +``` + +```js {2} +// App.js +import { ThemeContext, AuthContext } from './Contexts.js'; + +function App() { + // ... + return ( + <ThemeContext.Provider value={theme}> + <AuthContext.Provider value={currentUser}> + <Page /> + </AuthContext.Provider> + </ThemeContext.Provider> + ); +} +``` + +This works similar to [importing and exporting components.](/learn/importing-and-exporting-components) + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I can't find a way to change the context value {/*i-cant-find-a-way-to-change-the-context-value*/} + + +Code like this specifies the *default* context value: + +```js +const ThemeContext = createContext('light'); +``` + +This value never changes. React only uses this value as a fallback if it can't find a matching provider above. + +To make context change over time, [add state and wrap components in a context provider.](/reference/react/useContext#updating-data-passed-via-context) + diff --git a/beta/src/content/reference/react/createElement.md b/beta/src/content/reference/react/createElement.md new file mode 100644 index 000000000..a5f684c66 --- /dev/null +++ b/beta/src/content/reference/react/createElement.md @@ -0,0 +1,205 @@ +--- +title: createElement +--- + +<Intro> + +`createElement` lets you create a React element. It serves as an alternative to writing [JSX.](/learn/writing-markup-with-jsx) + +```js +const element = createElement(type, props, ...children) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `createElement(type, props, ...children)` {/*createelement*/} + +Call `createElement` to create a React element with the given `type`, `props`, and `children`. + +```js +import { createElement } from 'react'; + +function Greeting({ name }) { + return createElement( + 'h1', + { className: 'greeting' }, + 'Hello' + ); +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `type`: The `type` argument must be a valid React component type. For example, it could be a tag name string (such as `'div'` or `'span'`), or a React component (a function, a class, or a special component like [`Fragment`](/reference/react/Fragment)). + +* `props`: The `props` argument must either be an object or `null`. If you pass `null`, it will be treated the same as an empty object. React will create an element with props matching the `props` you have passed. Note that `ref` and `key` from your `props` object are special and will *not* be available as `element.props.ref` and `element.props.key` on the returned `element`. They will be available as `element.ref` and `element.key`. + +* **optional** `...children`: Zero or more child nodes. They can be any React nodes, including React elements, strings, numbers, [portals](/reference/react-dom/createPortal), empty nodes (`null`, `undefined`, `true`, and `false`), and arrays of React nodes. + +#### Returns {/*returns*/} + +`createElement` returns a React element object with a few properties: + +* `type`: The `type` you have passed. +* `props`: The `props` you have passed except for `ref` and `key`. If the `type` is a component with legacy `type.defaultProps`, then any missing or undefined `props` will get the values from `type.defaultProps`. +* `ref`: The `ref` you have passed. If missing, `null`. +* `key`: The `key` you have passed, coerced to a string. If missing, `null`. + +Usually, you'll return the element from your component or make it a child of another element. Although you may read the element's properties, it's best to treat every element as opaque after it's created, and only render it. + +#### Caveats {/*caveats*/} + +* You must **treat React elements and their props as [immutable](https://en.wikipedia.org/wiki/Immutable_object)** and never change their contents after creation. In development, React will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) the returned element and its `props` property shallowly to enforce this. + +* When you use JSX, **you must start a tag with a capital letter to render your own custom component.** In other words, `<Something />` is equivalent to `createElement(Something)`, but `<something />` (lowercase) is equivalent to `createElement('something')` (note it's a string, so it will be treated as a built-in HTML tag). + +* You should only **pass children as multiple arguments to `createElement` if they are all statically known,** like `createElement('h1', {}, child1, child2, child3)`. If your children are dynamic, pass the entire array as the third argument: `createElement('ul', {}, listItems)`. This ensures that React will [warn you about missing `key`s](/learn/rendering-lists#keeping-list-items-in-order-with-key) for any dynamic lists. For static lists this is not necessary because they never reorder. + +--- + +## Usage {/*usage*/} + +### Creating an element without JSX {/*creating-an-element-without-jsx*/} + +If you don't like [JSX](/learn/writing-markup-with-jsx) or can't use it in your project, you can use `createElement` as an alternative. + +To create an element without JSX, call `createElement` with some <CodeStep step={1}>type</CodeStep>, <CodeStep step={2}>props</CodeStep>, and <CodeStep step={3}>children</CodeStep>: + +```js [[1, 5, "'h1'"], [2, 6, "{ className: 'greeting' }"], [3, 7, "'Hello ',"], [3, 8, "createElement('i', null, name),"], [3, 9, "'. Welcome!'"]] +import { createElement } from 'react'; + +function Greeting({ name }) { + return createElement( + 'h1', + { className: 'greeting' }, + 'Hello ', + createElement('i', null, name), + '. Welcome!' + ); +} +``` + +The <CodeStep step={3}>children</CodeStep> are optional, and you can pass as many as you need (the example above has three children). This code will display a `<h1>` header with a greeting. For comparison, here is the same example rewritten with JSX: + +```js [[1, 3, "h1"], [2, 3, "className=\\"greeting\\""], [3, 4, "Hello <i>{name}</i>. Welcome!"], [1, 5, "h1"]] +function Greeting({ name }) { + return ( + <h1 className="greeting"> + Hello <i>{name}</i>. Welcome! + </h1> + ); +} +``` + +To render your own React component, pass a function like `Greeting` as the <CodeStep step={1}>type</CodeStep> instead of a string like `'h1'`: + +```js [[1, 2, "Greeting"], [2, 2, "{ name: 'Taylor' }"]] +export default function App() { + return createElement(Greeting, { name: 'Taylor' }); +} +``` + +With JSX, it would look like this: + +```js [[1, 2, "Greeting"], [2, 2, "name=\\"Taylor\\""]] +export default function App() { + return <Greeting name="Taylor" />; +} +``` + +Here is a complete example written with `createElement`: + +<Sandpack> + +```js +import { createElement } from 'react'; + +function Greeting({ name }) { + return createElement( + 'h1', + { className: 'greeting' }, + 'Hello ', + createElement('i', null, name), + '. Welcome!' + ); +} + +export default function App() { + return createElement( + Greeting, + { name: 'Taylor' } + ); +} +``` + +```css +.greeting { + color: darkgreen; + font-family: Georgia; +} +``` + +</Sandpack> + +And here is the same example written using JSX: + +<Sandpack> + +```js +function Greeting({ name }) { + return ( + <h1 className="greeting"> + Hello <i>{name}</i>. Welcome! + </h1> + ); +} + +export default function App() { + return <Greeting name="Taylor" />; +} +``` + +```css +.greeting { + color: darkgreen; + font-family: Georgia; +} +``` + +</Sandpack> + +Both coding styles are fine, so you can use whichever one you prefer for your project. The main benefit of using JSX compared to `createElement` is that it's easy to see which closing tag corresponds to which opening tag. + +<DeepDive> + +#### What is a React element, exactly? {/*what-is-a-react-element-exactly*/} + +An element is a lightweight description of a piece of the user interface. For example, both `<Greeting name="Taylor" />` and `createElement(Greeting, { name: 'Taylor' })` produce an object like this: + +```js +// Slightly simplified +{ + type: Greeting, + props: { + name: 'Taylor' + }, + key: null, + ref: null, +} +``` + +**Note that creating this object does not render the `Greeting` component or create any DOM elements.** + +A React element is more like a description--an instruction for React to later render the `Greeting` component. By returning this object from your `App` component, you tell React what to do next. + +Creating elements is extremely cheap so you don't need to try to optimize or avoid it. + +</DeepDive> diff --git a/beta/src/content/reference/react/createFactory.md b/beta/src/content/reference/react/createFactory.md new file mode 100644 index 000000000..eb851ad97 --- /dev/null +++ b/beta/src/content/reference/react/createFactory.md @@ -0,0 +1,231 @@ +--- +title: createFactory +--- + +<Deprecated> + +This API will be removed in a future major version of React. [See the alternatives.](#alternatives) + +</Deprecated> + +<Intro> + +`createFactory` lets you create a function that produces React elements of a given type. + +```js +const factory = createFactory(type) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `createFactory(type)` {/*createfactory*/} + +Call `createFactory(type)` to create a factory function which produces React elements of a given `type`. + +```js +import { createFactory } from 'react'; + +const button = createFactory('button'); +``` + +Then you can use it to create React elements without JSX: + +```js +export default function App() { + return button({ + onClick: () => { + alert('Clicked!') + } + }, 'Click me'); +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `type`: The `type` argument must be a valid React component type. For example, it could be a tag name string (such as `'div'` or `'span'`), or a React component (a function, a class, or a special component like [`Fragment`](/reference/react/Fragment)). + +#### Returns {/*returns*/} + +Returns a factory function. That factory function receives a `props` object as the first argument, followed by a list of `...children` arguments, and returns a React element with the given `type`, `props` and `children`. + +--- + +## Usage {/*usage*/} + +### Creating React elements with a factory {/*creating-react-elements-with-a-factory*/} + +Although most React projects use [JSX](/learn/writing-markup-with-jsx) to describe the user interface, JSX is not required. In the past, `createFactory` used to be one of the ways you could describe the user interface without JSX. + +Call `createFactory` to create a *factory function* for a specific element type like `'button'`: + +```js +import { createFactory } from 'react'; + +const button = createFactory('button'); +``` + +Calling that factory function will produce React elements with the props and children you have provided: + +<Sandpack> + +```js App.js +import { createFactory } from 'react'; + +const button = createFactory('button'); + +export default function App() { + return button({ + onClick: () => { + alert('Clicked!') + } + }, 'Click me'); +} +``` + +</Sandpack> + +This is how `createFactory` was used as an alternative to JSX. However, `createFactory` is deprecated, and you should not call `createFactory` in any new code. See how to migrate away from `createFactory` below. + +--- + +## Alternatives {/*alternatives*/} + +### Copying `createFactory` into your project {/*copying-createfactory-into-your-project*/} + +If your project has many `createFactory` calls, copy this `createFactory.js` implementation into your project: + +<Sandpack> + +```js App.js +import { createFactory } from './createFactory.js'; + +const button = createFactory('button'); + +export default function App() { + return button({ + onClick: () => { + alert('Clicked!') + } + }, 'Click me'); +} +``` + +```js createFactory.js +import { createElement } from 'react'; + +export function createFactory(type) { + return createElement.bind(null, type); +} +``` + +</Sandpack> + +This lets you keep all of your code unchanged except the imports. + +--- + +### Replacing `createFactory` with `createElement` {/*replacing-createfactory-with-createelement*/} + +If you have a few `createFactory` calls that you don't mind porting manually, and you don't want to use JSX, you can replace every call a factory function with a [`createElement`](/reference/react/createElement) call. For example, you can replace this code: + +```js {1,3,6} +import { createFactory } from 'react'; + +const button = createFactory('button'); + +export default function App() { + return button({ + onClick: () => { + alert('Clicked!') + } + }, 'Click me'); +} +``` + +with this code: + + +```js {1,4} +import { createElement } from 'react'; + +export default function App() { + return createElement('button', { + onClick: () => { + alert('Clicked!') + } + }, 'Click me'); +} +``` + +Here is a complete example of using React without JSX: + +<Sandpack> + +```js App.js +import { createElement } from 'react'; + +export default function App() { + return createElement('button', { + onClick: () => { + alert('Clicked!') + } + }, 'Click me'); +} +``` + +</Sandpack> + +--- + +### Replacing `createFactory` with JSX {/*replacing-createfactory-with-jsx*/} + +Finally, you can use JSX instead of `createFactory`. This is the most common way to use React: + +<Sandpack> + +```js App.js +export default function App() { + return ( + <button onClick={() => { + alert('Clicked!'); + }}> + Click me + </button> + ); +}; +``` + +</Sandpack> + +<Pitfall> + +Sometimes, your existing code might pass some variable as a `type` instead of a constant like `'button'`: + +```js {3} +function Heading({ isSubheading, ...props }) { + const type = isSubheading ? 'h2' : 'h1'; + const factory = createFactory(type); + return factory(props); +} +``` + +To do the same in JSX, you need to rename your variable to start with an uppercase letter like `Type`: + +```js {2,3} +function Heading({ isSubheading, ...props }) { + const Type = isSubheading ? 'h2' : 'h1'; + return <Type {...props} />; +} +``` + +Otherwise React will interpret `<type>` as a built-in HTML tag because it is lowercase. + +</Pitfall> diff --git a/beta/src/content/reference/react/createRef.md b/beta/src/content/reference/react/createRef.md new file mode 100644 index 000000000..7974659dc --- /dev/null +++ b/beta/src/content/reference/react/createRef.md @@ -0,0 +1,174 @@ +--- +title: createRef +--- + +<Pitfall> + +`createRef` is mostly used for [class components.](/reference/react/Component) Function components typically rely on [`useRef`](/reference/react/useRef) instead. + +</Pitfall> + +<Intro> + +`createRef` creates a [ref](/learn/referencing-values-with-refs) object which can contain arbitrary value. + +```js +class MyInput extends Component { + inputRef = createRef(); + // ... +} +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `createRef()` {/*createref*/} + +Call `createRef` to declare a [ref](/learn/referencing-values-with-refs) inside a [class component.](/reference/react/Component) + +```js +import { createRef, Component } from 'react'; + +class MyComponent extends Component { + intervalRef = createRef(); + inputRef = createRef(); + // ... +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +`createRef` takes no parameters. + +#### Returns {/*returns*/} + +`createRef` returns an object with a single property: + +* `current`: Initially, it's set to the `null`. You can later set it to something else. If you pass the ref object to React as a `ref` attribute to a JSX node, React will set its `current` property. + +#### Caveats {/*caveats*/} + +* `createRef` always returns a *different* object. It's equivalent to writing `{ current: null }` yourself. +* In a function component, you probably want [`useRef`](/reference/react/useRef) instead which always returns the same object. +* `const ref = useRef()` is equivalent to `const [ref, _] = useState(() => createRef(null))`. + +--- + +## Usage {/*usage*/} + +### Declaring a ref in a class component {/*declaring-a-ref-in-a-class-component*/} + +To declare a ref inside a [class component,](/reference/react/Component) call `createRef` and assign its result to a class field: + +```js {4} +import { Component, createRef } from 'react'; + +class Form extends Component { + inputRef = createRef(); + + // ... +} +``` + +If you now pass `ref={this.inputRef}` to an `<input>` in your JSX, React will populate `this.inputRef.current` with the input DOM node. For example, here is how you make a button that focuses the input: + +<Sandpack> + +```js +import { Component, createRef } from 'react'; + +export default class Form extends Component { + inputRef = createRef(); + + handleClick = () => { + this.inputRef.current.focus(); + } + + render() { + return ( + <> + <input ref={this.inputRef} /> + <button onClick={this.handleClick}> + Focus the input + </button> + </> + ); + } +} +``` + +</Sandpack> + +<Pitfall> + +`createRef` is mostly used for [class components.](/reference/react/Component) Function components typically rely on [`useRef`](/reference/react/useRef) instead. + +</Pitfall> + +--- + +## Alternatives {/*alternatives*/} + +### Migrating from a class with `createRef` to a function with `useRef` {/*migrating-from-a-class-with-createref-to-a-function-with-useref*/} + +We recommend to use function components instead of [class components](/reference/react/Component) in the new code. If you have some existing class components using `createRef`, here is how you can convert them. This is the original code: + +<Sandpack> + +```js +import { Component, createRef } from 'react'; + +export default class Form extends Component { + inputRef = createRef(); + + handleClick = () => { + this.inputRef.current.focus(); + } + + render() { + return ( + <> + <input ref={this.inputRef} /> + <button onClick={this.handleClick}> + Focus the input + </button> + </> + ); + } +} +``` + +</Sandpack> + +When you [convert this component from a class to a function,](/reference/react/Component#alternatives) replace calls to `createRef` with calls to [`useRef`:](/reference/react/useRef) + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <input ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> diff --git a/beta/src/content/reference/react/forwardRef.md b/beta/src/content/reference/react/forwardRef.md new file mode 100644 index 000000000..860051165 --- /dev/null +++ b/beta/src/content/reference/react/forwardRef.md @@ -0,0 +1,536 @@ +--- +title: forwardRef +--- + +<Intro> + +`forwardRef` lets your component expose a DOM node to parent component with a [ref.](/learn/manipulating-the-dom-with-refs) + +```js +const SomeComponent = forwardRef(render) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `forwardRef(render)` {/*forwardref*/} + +Call `forwardRef()` to let your component receive a ref and forward it to a child component: + +```js +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + // ... +}); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `render`: The render function for your component. React calls this function with the props and `ref` that your component received from its parent. The JSX you return will be the output of your component. + +#### Returns {/*returns*/} + +`forwardRef` returns a React component that you can render in JSX. Unlike React components defined as plain functions, a component returned by `forwardRef` is also able to receive a `ref` prop. + +#### Caveats {/*caveats*/} + +* In Strict Mode, React will **call your render function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your render function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored. + + +--- + +### `render` function {/*render-function*/} + +`forwardRef` accepts a render function as an argument. React calls this function with `props` and `ref`: + +```js +const MyInput = forwardRef(function MyInput(props, ref) { + return ( + <label> + {props.label} + <input ref={ref} /> + </label> + ); +}); +``` + +#### Parameters {/*render-parameters*/} + +* `props`: The props passed by the parent component. + +* `ref`: The `ref` attribute passed by the parent component. The `ref` can be an object or a function. If the parent component has not passed a ref, it will be `null`. You should either pass the `ref` you receive to another component, or pass it to [`useImperativeHandle`.](/reference/react/useImperativeHandle) + +#### Returns {/*render-returns*/} + +`forwardRef` returns a React component that you can render in JSX. Unlike React components defined as plain functions, the component returned by `forwardRef` is able to take a `ref` prop. + +--- + +## Usage {/*usage*/} + +### Exposing a DOM node to the parent component {/*exposing-a-dom-node-to-the-parent-component*/} + +By default, each component's DOM nodes are private. However, sometimes it's useful to expose a DOM node to the parent--for example, to allow focusing it. To opt in, wrap your component definition into `forwardRef()`: + +```js {3,11} +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + const { label, ...otherProps } = props; + return ( + <label> + {label} + <input {...otherProps} /> + </label> + ); +}); +``` + +You will receive a <CodeStep step={1}>ref</CodeStep> as the second argument after props. Pass it to the DOM node that you want to expose: + +```js {8} [[1, 3, "ref"], [1, 8, "ref", 30]] +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + const { label, ...otherProps } = props; + return ( + <label> + {label} + <input {...otherProps} ref={ref} /> + </label> + ); +}); +``` + +This lets the parent `Form` component access the <CodeStep step={2}>`<input>` DOM node</CodeStep> exposed by `MyInput`: + +```js [[1, 2, "ref"], [1, 10, "ref", 41], [2, 5, "ref.current"]] +function Form() { + const ref = useRef(null); + + function handleClick() { + ref.current.focus(); + } + + return ( + <form> + <MyInput label="Enter your name:" ref={ref} /> + <button type="button" onClick={handleClick}> + Edit + </button> + </form> + ); +} +``` + +This `Form` component [passes a ref](/reference/useref#manipulating-the-dom-with-a-ref) to `MyInput`. The `MyInput` component *forwards* that ref to the `<input>` browser tag. As a result, the `Form` component can access that `<input>` DOM node and call [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on it. + +Keep in mind that by exposing a ref to the DOM node inside your component, you're making it harder to change your component's internals later. You will typically expose DOM nodes from reusable low-level components like buttons or text inputs, but you won't do it for application-level components like an avatar or a comment. + +<Recipes title="Examples of forwarding a ref"> + +#### Focusing a text input {/*focusing-a-text-input*/} + +Clicking the button will focus the input. The `Form` component defines a ref and passes it to the `MyInput` component. The `MyInput` component forwards that ref to the browser `<input>`. This lets the `Form` component focus the `<input>`. + +<Sandpack> + +```js +import { useRef } from 'react'; +import MyInput from './MyInput.js'; + +export default function Form() { + const ref = useRef(null); + + function handleClick() { + ref.current.focus(); + } + + return ( + <form> + <MyInput label="Enter your name:" ref={ref} /> + <button type="button" onClick={handleClick}> + Edit + </button> + </form> + ); +} +``` + +```js MyInput.js +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + const { label, ...otherProps } = props; + return ( + <label> + {label} + <input {...otherProps} ref={ref} /> + </label> + ); +}); + +export default MyInput; +``` + +```css +input { + margin: 5px; +} +``` + +</Sandpack> + +<Solution /> + +#### Playing and pausing a video {/*playing-and-pausing-a-video*/} + +Clicking the button will call [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) and [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) on a `<video>` DOM node. The `App` component defines a ref and passes it to the `MyVideoPlayer` component. The `MyVideoPlayer` component forwards that ref to the browser `<video>` node. This lets the `App` component play and pause the `<video>`. + +<Sandpack> + +```js +import { useRef } from 'react'; +import MyVideoPlayer from './MyVideoPlayer.js'; + +export default function App() { + const ref = useRef(null); + return ( + <> + <button onClick={() => ref.current.play()}> + Play + </button> + <button onClick={() => ref.current.pause()}> + Pause + </button> + <br /> + <MyVideoPlayer + ref={ref} + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + type="video/mp4" + width="250" + /> + </> + ); +} +``` + +```js MyVideoPlayer.js +import { forwardRef } from 'react'; + +const VideoPlayer = forwardRef(function VideoPlayer({ src, type, width }, ref) { + return ( + <video width={width} ref={ref}> + <source + src={src} + type={type} + /> + </video> + ); +}); + +export default VideoPlayer; +``` + +```css +button { margin-bottom: 10px; margin-right: 10px; } +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Forwarding a ref through multiple components {/*forwarding-a-ref-through-multiple-components*/} + +Instead of forwarding a `ref` to a DOM node, you can forward it to your own component like `MyInput`: + +```js {1,5} +const FormField = forwardRef(function FormField(props, ref) { + // ... + return ( + <> + <MyInput ref={ref} /> + ... + </> + ); +}); +``` + +If that `MyInput` component forwards a ref to its `<input>`, a ref to `FormField` will give you that `<input>`: + +```js {2,5,10} +function Form() { + const ref = useRef(null); + + function handleClick() { + ref.current.focus(); + } + + return ( + <form> + <FormField label="Enter your name:" ref={ref} isRequired={true} /> + <button type="button" onClick={handleClick}> + Edit + </button> + </form> + ); +} +``` + +The `Form` component defines a ref and passes it to `FormField`. The `FormField` component forwards that ref to `MyInput`, which forwards this ref to a browser `<input>` DOM node. This is how `Form` accesses that DOM node. + + +<Sandpack> + +```js +import { useRef } from 'react'; +import FormField from './FormField.js'; + +export default function Form() { + const ref = useRef(null); + + function handleClick() { + ref.current.focus(); + } + + return ( + <form> + <FormField label="Enter your name:" ref={ref} isRequired={true} /> + <button type="button" onClick={handleClick}> + Edit + </button> + </form> + ); +} +``` + +```js FormField.js +import { forwardRef, useState } from 'react'; +import MyInput from './MyInput.js'; + +const FormField = forwardRef(function FormField({ label, isRequired }, ref) { + const [value, setValue] = useState(''); + return ( + <> + <MyInput + ref={ref} + label={label} + value={value} + onChange={e => setValue(e.target.value)} + /> + {(isRequired && value === '') && + <i>Required</i> + } + </> + ); +}); + +export default FormField; +``` + + +```js MyInput.js +import { forwardRef } from 'react'; + +const MyInput = forwardRef((props, ref) => { + const { label, ...otherProps } = props; + return ( + <label> + {label} + <input {...otherProps} ref={ref} /> + </label> + ); +}); + +export default MyInput; +``` + +```css +input, button { + margin: 5px; +} +``` + +</Sandpack> + +--- + +### Exposing an imperative handle instead of a DOM node {/*exposing-an-imperative-handle-instead-of-a-dom-node*/} + +Instead of exposing an entire DOM node, you can expose a custom object, called an *imperative handle,* with a more constrained set of methods. To do this, you'd need to define a separate ref to hold the DOM node: + +```js {2,6} +const MyInput = forwardRef(function MyInput(props, ref) { + const inputRef = useRef(null); + + // ... + + return <input {...props} ref={inputRef} />; +}); +``` + +Then pass the `ref` you received to [`useImperativeHandle`](/reference/react/useImperativeHandle) and specify the value you want to expose to the `ref`: + +```js {6-15} +import { forwardRef, useRef, useImperativeHandle } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + const inputRef = useRef(null); + + useImperativeHandle(ref, () => { + return { + focus() { + inputRef.current.focus(); + }, + scrollIntoView() { + inputRef.current.scrollIntoView(); + }, + }; + }, []); + + return <input {...props} ref={inputRef} />; +}); +``` + +If some component gets a ref to `MyInput` now, it will only receive your `{ focus, scrollIntoView }` object instead of the DOM node. This lets you limit the information you expose about your DOM node to the minimum. + +<Sandpack> + +```js +import { useRef } from 'react'; +import MyInput from './MyInput.js'; + +export default function Form() { + const ref = useRef(null); + + function handleClick() { + ref.current.focus(); + // This won't work because the DOM node isn't exposed: + // ref.current.style.opacity = 0.5; + } + + return ( + <form> + <MyInput label="Enter your name:" ref={ref} /> + <button type="button" onClick={handleClick}> + Edit + </button> + </form> + ); +} +``` + +```js MyInput.js +import { forwardRef, useRef, useImperativeHandle } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + const inputRef = useRef(null); + + useImperativeHandle(ref, () => { + return { + focus() { + inputRef.current.focus(); + }, + scrollIntoView() { + inputRef.current.scrollIntoView(); + }, + }; + }, []); + + return <input {...props} ref={inputRef} />; +}); + +export default MyInput; +``` + +```css +input { + margin: 5px; +} +``` + +</Sandpack> + +[Read more about using imperative handles.](/reference/react/useImperativeHandle) + +<Pitfall> + +**Do not overuse refs.** You should only use refs for *imperative* behaviors that you can't express as props: for example, scrolling to a node, focusing a node, triggering an animation, selecting text, and so on. + +**If you can express something as a prop, you should not use a ref.** For example, instead of exposing an imperative handle like `{ open, close }` from a `Modal` component, it is better to take `isOpen` as a prop like `<Modal isOpen={isOpen} />`. [Effects](/learn/synchronizing-with-effects) can help you expose imperative behaviors via props. + +</Pitfall> + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My component is wrapped in `forwardRef`, but the `ref` to it is always `null` {/*my-component-is-wrapped-in-forwardref-but-the-ref-to-it-is-always-null*/} + +This usually means that you forgot to actually use the `ref` that you received. + +For example, this component doesn't do anything with its `ref`: + +```js {1} +const MyInput = forwardRef(function MyInput({ label }, ref) { + return ( + <label> + {label} + <input /> + </label> + ); +}); +``` + +To fix it, pass the `ref` down to a DOM node or another component that can accept a ref: + +```js {1,5} +const MyInput = forwardRef(function MyInput({ label }, ref) { + return ( + <label> + {label} + <input ref={ref} /> + </label> + ); +}); +``` + +The `ref` to `MyInput` could also be `null` if some of the logic is conditional: + +```js {1,5} +const MyInput = forwardRef(function MyInput({ label, showInput }, ref) { + return ( + <label> + {label} + {showInput && <input ref={ref} />} + </label> + ); +}); +``` + +If `showInput` is `false`, then the ref won't be forwarded to any node, and a ref to `MyInput` will remain empty. This is particularly easy to miss if the condition is hidden inside another component, like `Panel` in this example: + +```js {5,7} +const MyInput = forwardRef(function MyInput({ label, showInput }, ref) { + return ( + <label> + {label} + <Panel isExpanded={showInput}> + <input ref={ref} /> + </Panel> + </label> + ); +}); +``` diff --git a/beta/src/content/reference/react/index.md b/beta/src/content/reference/react/index.md new file mode 100644 index 000000000..8360b3790 --- /dev/null +++ b/beta/src/content/reference/react/index.md @@ -0,0 +1,121 @@ +--- +title: "Built-in React Hooks" +--- + +<Intro> + +*Hooks* let you use different React features from your components. You can either use the built-in Hooks or combine them to build your own. This page lists all the built-in Hooks in React. + +</Intro> + +--- + +## State Hooks {/*state-hooks*/} + +*State* lets a component ["remember" information like user input.](/learn/state-a-components-memory) For example, a form component can use state to store the input value, while an image gallery component can use state to store the selected image index. + +To add state to a component, use one of these Hooks: + +* [`useState`](/reference/react/useState) declares a state variable that you can update directly. +* [`useReducer`](/reference/react/useReducer) declares a state variable with the update logic inside a [reducer function.](/learn/extracting-state-logic-into-a-reducer) + +```js +function ImageGallery() { + const [index, setIndex] = useState(0); + // ... +``` + +--- + +## Context Hooks {/*context-hooks*/} + +*Context* lets a component [receive information from distant parents without passing it as props.](/learn/passing-props-to-a-component) For example, your app's top-level component can pass the current UI theme to all components below, no matter how deep. + +* [`useContext`](/reference/react/useContext) reads and subscribes to a context. + +```js +function Button() { + const theme = useContext(ThemeContext); + // ... +``` + +--- + +## Ref Hooks {/*ref-hooks*/} + +*Refs* let a component [hold some information that isn't used for rendering,](/learn/referencing-values-with-refs) like a DOM node or a timeout ID. Unlike with state, updating a ref does not re-render your component. Refs are an "escape hatch" from the React paradigm. They are useful when you need to work with non-React systems, such as the built-in browser APIs. + +* [`useRef`](/reference/react/useRef) declares a ref. You can hold any value in it, but most often it's used to hold a DOM node. +* [`useImperativeHandle`](/reference/react/useImperativeHandle) lets you customize the ref exposed by your component. This is rarely used. + +```js +function Form() { + const inputRef = useRef(null); + // ... +``` + +--- + +## Effect Hooks {/*effect-hooks*/} + +*Effects* let a component [connect to and synchronize with external systems.](/learn/synchronizing-with-effects) This includes dealing with network, browser DOM, animations, widgets written using a different UI library, and in general any non-React code. + +* [`useEffect`](/reference/react/useEffect) connects a component to an external system. + +```js +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + // ... +``` + +Effects are an "escape hatch" from the React paradigm. Don't use Effects to orchestrate the data flow of your application. If you're not interacting with an external system, [you might not need an Effect.](/learn/you-might-not-need-an-effect) + +There are two rarely used variations of `useEffect` with differences in timing: + +* [`useLayoutEffect`](/reference/react/useLayoutEffect) fires before the browser repaints the screen. You can measure layout here. +* [`useInsertionEffect`](/reference/react/useInsertionEffect) fires before React makes changes to the DOM. Libraries can insert dynamic CSS here. + +--- + +## Performance Hooks {/*performance-hooks*/} + +A common way to optimize re-rendering performance is to skip unnecessary work. For example, you can tell React to reuse a cached calculation or to skip a re-render if the data has not changed since the previous render. + +To skip calculations and unnecessary re-rendering, use one of these Hooks: + +- [`useMemo`](/reference/react/useMemo) lets you cache the result of an expensive calculation. +- [`useCallback`](/reference/react/useCallback) lets you cache a function definition before passing it down to an optimized component. + +```js +function TodoList({ todos, tab, theme }) { + const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); + // ... +} +``` + +Sometimes, you can't skip re-rendering because the screen actually needs to update. In that case, you can improve performance by separating blocking updates that must be synchronous (like typing into an input) from non-blocking updates which don't need to block the user interface (like updating a chart). + +To prioritize rendering, use one of these Hooks: + +- [`useTransition`](/reference/react/useTransition) lets you mark a state transition as non-blocking and allow other updates to interrupt it. +- [`useDeferredValue`](/reference/react/useDeferredValue) lets you defer updating a non-critical part of the UI and let other parts update first. + +--- + +## Other Hooks {/*other-hooks*/} + +These Hooks are mostly useful to library authors and aren't commonly used in the application code. + +- [`useDebugValue`](/reference/react/useDebugValue) lets you customize the label React DevTools displays for your custom Hook. +- [`useId`](/reference/react/useId) lets a component associate a unique ID with itself. Typically used with accessibility APIs. +- [`useSyncExternalStore`](/reference/react/useSyncExternalStore) lets a component subscribe to an external store. + +--- + +## Your own Hooks {/*your-own-hooks*/} + +You can also [define your own custom Hooks](/learn/reusing-logic-with-custom-hooks#extracting-your-own-custom-hook-from-a-component) as JavaScript functions. diff --git a/beta/src/content/reference/react/isValidElement.md b/beta/src/content/reference/react/isValidElement.md new file mode 100644 index 000000000..bc4fbaef4 --- /dev/null +++ b/beta/src/content/reference/react/isValidElement.md @@ -0,0 +1,128 @@ +--- +title: isValidElement +--- + +<Intro> + +`isValidElement` checks whether a value is a React element. + +```js +const isElement = isValidElement(value) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `isValidElement(value)` {/*isvalidelement*/} + +Call `isValidElement(value)` to check whether `value` is a React element. + +```js +import { isValidElement, createElement } from 'react'; + +// ✅ React elements +console.log(isValidElement(<p />)); // true +console.log(isValidElement(createElement('p'))); // true + +// ❌ Not React elements +console.log(isValidElement(25)); // false +console.log(isValidElement('Hello')); // false +console.log(isValidElement({ age: 42 })); // false +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `value`: The `value` you want to check. It can be any a value of any type. + +#### Returns {/*returns*/} + +`isValidElement` returns `true` if the `value` is a React element. Otherwise, it returns `false`. + +#### Caveats {/*caveats*/} + +* **Only [JSX tags](/learn/writing-markup-with-jsx) and objects returned by [`createElement`](/reference/react/createElement) are considered to be React elements.** For example, even though a number like `42` is a valid React *node* (and can be returned from a component), it is not a valid React element. Arrays and portals created with [`createPortal`](/reference/react-dom/createPortal) are also *not* considered to be React elements. + +--- + +## Usage {/*usage*/} + +### Checking if something is a React element {/*checking-if-something-is-a-react-element*/} + +Call `isValidElement` to check if some value is a *React element.* + +React elements are: + +- Values produced by writing a [JSX tag](/learn/writing-markup-with-jsx) +- Values produced by calling [`createElement`](/reference/react/createElement) + +For React elements, `isValidElement` returns `true`: + +```js +import { isValidElement, createElement } from 'react'; + +// ✅ JSX tags are React elements +console.log(isValidElement(<p />)); // true +console.log(isValidElement(<MyComponent />)); // true + +// ✅ Values returned by createElement are React elements +console.log(isValidElement(createElement('p'))); // true +console.log(isValidElement(createElement(MyComponent))); // true +``` + +Any other values, such as strings, numbers, or arbitrary objects and arrays, are not React elements. + +For them, `isValidElement` returns `false`: + +```js +// ❌ These are *not* React elements +console.log(isValidElement(null)); // false +console.log(isValidElement(25)); // false +console.log(isValidElement('Hello')); // false +console.log(isValidElement({ age: 42 })); // false +console.log(isValidElement([<div />, <div />])); // false +console.log(isValidElement(MyComponent)); // false +``` + +It is very uncommon to need `isValidElement`. It's mostly useful if you're calling another API that *only* accepts elements (like [`cloneElement`](/reference/react/cloneElement) does) and you want to avoid an error when your argument is not a React element. + +Unless you have some very specific reason to add an `isValidElement` check, you probably don't need it. + +<DeepDive> + +#### React elements vs React nodes {/*react-elements-vs-react-nodes*/} + +When you write a component, you can return any kind of *React node* from it: + +```js +function MyComponent() { + // ... you can return any React node ... +} +``` + +A React node can be: + +- A React element created like `<div />` or `createElement('div')` +- A portal created with [`createPortal`](/reference/react-dom/createPortal) +- A string +- A number +- `true`, `false`, `null`, or `undefined` (which are not displayed) +- An array of other React nodes + +**Note `isValidElement` checks whether the argument is a *React element,* not whether it's a React node.** For example, `42` is not a valid React element. However, it is a perfectly valid React node: + +```js +function MyComponent() { + return 42; // It's ok to return a number from component +} +``` + +This is why you shouldn't use `isValidElement` as a way to check whether something can be rendered. + +</DeepDive> diff --git a/beta/src/content/reference/react/lazy.md b/beta/src/content/reference/react/lazy.md new file mode 100644 index 000000000..cd2850472 --- /dev/null +++ b/beta/src/content/reference/react/lazy.md @@ -0,0 +1,211 @@ +--- +title: lazy +--- + +<Intro> + +`lazy` lets you defer loading component's code until it is rendered for the first time. + +```js +const SomeComponent = lazy(load) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `lazy(load)` {/*lazy*/} + +Call `lazy` outside your components to declare a lazy-loaded React component: + +```js +import { lazy } from 'react'; + +const MarkdownPreview = lazy(() => import('./MarkdownPreview.js')); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `load`: A function that returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or some other *thenable* (a Promise-like object with a `then` method). React will not call `load` until the first time you attempt to render the returned component. After React first calls `load`, it will wait for it to resolve, and then render the resolved value as a React component. Both the returned Promise and the Promise's resolved value will be cached, so React will not call `load` more than once. If the Promise rejects, React will `throw` the rejection reason to let the closest Error Boundary above handle it. + +#### Returns {/*returns*/} + +`lazy` returns a React component that you can render in your tree. While the code for the lazy component is still loading, attempting to render it will *suspend.* Use [`<Suspense>`](/reference/react/Suspense) to display a loading indicator while it's loading. + +--- + +### `load` function {/*load*/} + +#### Parameters {/*load-parameters*/} + +`load` receives no parameters. + +#### Returns {/*load-returns*/} + +You need to return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or some other *thenable* (a Promise-like object with a `then` method). It needs to eventually resolve to a valid React component type, such as a function, [`memo`](/reference/react/memo), or a [`forwardRef`](/reference/react/forwardRef) component. + +--- + +## Usage {/*usage*/} + +### Lazy-loading components with Suspense {/*suspense-for-code-splitting*/} + +Usually, you import components with the static [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) declaration: + +```js +import MarkdownPreview from './MarkdownPreview.js'; +``` + +To defer loading this component's code until it's rendered for the first time, replace this import with: + +```js +import { lazy } from 'react'; + +const MarkdownPreview = lazy(() => import('./MarkdownPreview.js')); +``` + +This code relies on [dynamic `import()`,](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) which might require support from your bundler or framework. + +Now that your component's code loads on demand, you also need to specify what should be displayed while it is loading. You can do this by wrapping the lazy component or any of its parents into a [`<Suspense>`](/reference/react/Suspense) boundary: + +```js {1,4} +<Suspense fallback={<Loading />}> + <h2>Preview</h2> + <MarkdownPreview /> + </Suspense> +``` + +In this example, the code for `MarkdownPreview` won't be loaded until you attempt to render it. If `MarkdownPreview` hasn't loaded yet, `Loading` will be shown in its place. Try ticking the checkbox: + +<Sandpack> + +```js App.js +import { useState, Suspense, lazy } from 'react'; +import Loading from './Loading.js'; + +const MarkdownPreview = lazy(() => delayForDemo(import('./MarkdownPreview.js'))); + +export default function MarkdownEditor() { + const [showPreview, setShowPreview] = useState(false); + const [markdown, setMarkdown] = useState('Hello, **world**!'); + return ( + <> + <textarea value={markdown} onChange={e => setMarkdown(e.target.value)} /> + <label> + <input type="checkbox" checked={showPreview} onChange={e => setShowPreview(e.target.checked)} /> + Show preview + </label> + <hr /> + {showPreview && ( + <Suspense fallback={<Loading />}> + <h2>Preview</h2> + <MarkdownPreview markdown={markdown} /> + </Suspense> + )} + </> + ); +} + +// Add a fixed delay so you can see the loading state +function delayForDemo(promise) { + return new Promise(resolve => { + setTimeout(resolve, 2000); + }).then(() => promise); +} +``` + +```js Loading.js +export default function Loading() { + return <p><i>Loading...</i></p>; +} +``` + +```js MarkdownPreview.js +import { Remarkable } from 'remarkable'; + +const md = new Remarkable(); + +export default function MarkdownPreview({ markdown }) { + return ( + <div + className="content" + dangerouslySetInnerHTML={{__html: md.render(markdown)}} + /> + ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "remarkable": "2.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```css +label { + display: block; +} + +input, textarea { + margin-bottom: 10px; +} + +body { + min-height: 200px; +} +``` + +</Sandpack> + +This demo loads with an artificial delay. The next time you untick and tick the checkbox, `Preview` will be cached, so there will be no loading state displayed. To see the loading state again, click "Reset" on the sandbox. + +[Learn more about managing loading states with Suspense.](/reference/react/Suspense) + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My `lazy` component's state gets reset unexpectedly {/*my-lazy-components-state-gets-reset-unexpectedly*/} + +Do not declare `lazy` components *inside* other components: + +```js {4-5} +import { lazy } from 'react'; + +function Editor() { + // 🔴 Bad: This will cause all state to be reset on re-renders + const MarkdownPreview = lazy(() => import('./MarkdownPreview.js')); + // ... +} +``` + +Instead, always declare them at the top level of your module: + +```js {3-4} +import { lazy } from 'react'; + +// ✅ Good: Declare lazy components outside of your components +const MarkdownPreview = lazy(() => import('./MarkdownPreview.js')); + +function Editor() { + // ... +} +``` diff --git a/beta/src/content/reference/react/legacy.md b/beta/src/content/reference/react/legacy.md new file mode 100644 index 000000000..3e13c983e --- /dev/null +++ b/beta/src/content/reference/react/legacy.md @@ -0,0 +1,34 @@ +--- +title: "Legacy React APIs" +--- + +<Intro> + +These APIs are exported from the `react` package, but they are not recommended for use in the newly written code. See the linked individual API pages for the suggested alternatives. + +</Intro> + +--- + +## Legacy APIs {/*legacy-apis*/} + +* [`Children`](/reference/react/Children) lets you manipulate and transform the JSX received as the `children` prop. [See alternatives.](/reference/react/Children#alternatives) +* [`cloneElement`](/reference/react/cloneElement) lets you create a React element using another element as a starting point. [See alternatives.](/reference/react/cloneElement#alternatives) +* [`Component`](/reference/react/Component) lets you define a React component as a JavaScript class. [See alternatives.](/reference/react/Component#alternatives) +* [`createElement`](/reference/react/createElement) lets you create a React element. Typically, you'll use JSX instead. +* [`createRef`](/reference/react/createRef) creates a ref object which can contain arbitrary value. [See alternatives.](/reference/react/createRef#alternatives) +* [`isValidElement`](/reference/react/isValidElement) checks whether a value is a React element. Typically used with [`cloneElement`.](/reference/react/cloneElement) +* [`PureComponent`](/reference/react/PureComponent) is similar to [`Component`,](/reference/react/Component) but it skip re-renders with same props. [See alternatives.](/reference/react/PureComponent#alternatives) + + +--- + +## Deprecated APIs {/*deprecated-apis*/} + +<Deprecated> + +These APIs will be removed in a future major version of React. + +</Deprecated> + +* [`createFactory`](/reference/react/createFactory) lets you create a function that produces React elements of a certain type. \ No newline at end of file diff --git a/beta/src/content/reference/react/memo.md b/beta/src/content/reference/react/memo.md new file mode 100644 index 000000000..25f656f6b --- /dev/null +++ b/beta/src/content/reference/react/memo.md @@ -0,0 +1,363 @@ +--- +title: memo +--- + +<Intro> + +`memo` lets you skip re-rendering a component when its props are unchanged. + +``` +const MemoizedComponent = memo(SomeComponent, arePropsEqual?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `memo(Component, arePropsEqual?)` {/*memo*/} + +Wrap a component in `memo` to get a *memoized* version of that component. This memoized version of your component will usually not be re-rendered when its parent component is re-rendered as long as its props have not changed. But React may still re-render it: memoization is only a performance optimization, not a guarantee. + +```js +import { memo } from 'react'; + +const SomeComponent = memo(function SomeComponent(props) { + // ... +}); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `Component`: The component that you want to memoize. The `memo` does not modify this component, but returns a new, memoized component instead. Any valid React component, including functions and [`forwardRef`](/reference/react/forwardRef) components, is accepted. + +* **optional** `arePropsEqual`: A function that accepts two arguments: the component's previous props, and its new props. It should return `true` if the old and new props are equal: that is, if the component will render the same output and behave in the same way with the new props as with the old. Otherwise it should return `false`. + +#### Returns {/*returns*/} + +`memo` returns a new React component. It behaves the same as the component provided to `memo` except that React will not always re-render it when its parent is being re-rendered unless its props have changed. + +--- + +## Usage {/*usage*/} + +### Skipping re-rendering when props are unchanged {/*skipping-re-rendering-when-props-are-unchanged*/} + +React normally re-renders a component whenever its parent re-renders. With `memo`, you can create a component that React will not re-render when its parent re-renders so long as its new props are the same as the old props. Such a component is said to be *memoized*. + +To memoize a component, wrap it in a call to `memo` and use the value that it returns in place of your original component: + +```js +const Greeting = memo(function Greeting({ name }) { + return <h1>Hello, {name}!</h1>; +}); + +export default Greeting; +``` + +A React component should always have [pure rendering logic.](/learn/keeping-components-pure) This means that it must return the same output if its props, state, and context haven't changed. By using `memo`, you are telling React that your component complies with this requirement, so React doesn't need to re-render as long as its props haven't changed. When you use `memo`, your component will still re-render if its own state changes or if a context that it's using changes. + +In this example, notice that the `Greeting` component re-renders whenever `name` is changed (because that's one of its props), but not when `address` is changed (because it's not passed to `Greeting` as a prop): + +<Sandpack> + +```js +import { memo, useState } from 'react'; + +export default function MyApp() { + const [name, setName] = useState(''); + const [address, setAddress] = useState(''); + return ( + <> + <label> + Name{': '} + <input value={name} onChange={e => setName(e.target.value)} /> + </label> + <label> + Address{': '} + <input value={address} onChange={e => setAddress(e.target.value)} /> + </label> + <Greeting name={name} /> + </> + ); +} + +const Greeting = memo(function Greeting({ name }) { + console.log("Greeting was rendered at", new Date().toLocaleTimeString()); + return <h3>Hello{name && ', '}{name}!</h3>; +}); +``` + +```css +label { + display: block; + margin-bottom: 16px; +} +``` + +</Sandpack> + +<Note> + +**You should only rely on `memo` as a performance optimization.** If your code doesn't work without it, find the underlying problem and fix it first. Then you may add `memo` to improve performance. + +</Note> + +<DeepDive> + +#### Should you add memo everywhere? {/*should-you-add-memo-everywhere*/} + +If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful. + +Optimizing with `memo` is only valuable when your component re-renders often with the same exact props, and its re-rendering logic is expensive. If there is no perceptible lag when your component re-renders, `memo` is unnecessary. Keep in mind that `memo` is completely useless if the props passed to your component are *always different,* such as if you pass an object or a plain function defined during rendering. This is why you will often need [`useMemo`](/reference/react/useMemo#skipping-re-rendering-of-components) and [`useCallback`](/reference/react/useCallback#skipping-re-rendering-of-components) together with `memo`. + +There is no benefit to wrapping a component in `memo` in other cases. There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible. The downside of this approach is that code becomes less readable. Also, not all memoization is effective: a single value that's "always new" is enough to break memoization for an entire component. + +**In practice, you can make a lot of memoization unnecessary by following a few principles:** + +1. When a component visually wraps other components, let it [accept JSX as children.](/learn/passing-props-to-a-component#passing-jsx-as-children) This way, when the wrapper component updates its own state, React knows that its children don't need to re-render. +1. Prefer local state and don't [lift state up](/learn/sharing-state-between-components) any further than necessary. For example, don't keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library. +1. Keep your [rendering logic pure.](/learn/keeping-components-pure) If re-rendering a component causes a problem or produces some noticeable visual artifact, it's a bug in your component! Fix the bug instead of adding memoization. +1. Avoid [unnecessary Effects that update state.](/learn/you-might-not-need-an-effect) Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over. +1. Try to [remove unnecessary dependencies from your Effects.](/learn/removing-effect-dependencies) For example, instead of memoization, it's often simpler to move some object or a function inside an Effect or outside the component. + +If a specific interaction still feels laggy, [use the React Developer Tools profiler](/blog/2018/09/10/introducing-the-react-profiler.html) to see which components would benefit the most from memoization, and add memoization where needed. These principles make your components easier to debug and understand, so it's good to follow them in any case. In the long term, we're researching [doing granular memoization automatically](https://www.youtube.com/watch?v=lGEMwh32soc) to solve this once and for all. + +</DeepDive> + +--- + +### Updating a memoized component using state {/*updating-a-memoized-component-using-state*/} + +Even when a component is memoized, it will still re-render when its own state changes. Memoization only has to do with props that are passed to the component from its parent. + +<Sandpack> + +```js +import { memo, useState } from 'react'; + +export default function MyApp() { + const [name, setName] = useState(''); + const [address, setAddress] = useState(''); + return ( + <> + <label> + Name{': '} + <input value={name} onChange={e => setName(e.target.value)} /> + </label> + <label> + Address{': '} + <input value={address} onChange={e => setAddress(e.target.value)} /> + </label> + <Greeting name={name} /> + </> + ); +} + +const Greeting = memo(function Greeting({ name }) { + console.log('Greeting was rendered at', new Date().toLocaleTimeString()); + const [greeting, setGreeting] = useState('Hello'); + return ( + <> + <h3>{greeting}{name && ', '}{name}!</h3> + <GreetingSelector value={greeting} onChange={setGreeting} /> + </> + ); +}); + +function GreetingSelector({ value, onChange }) { + return ( + <> + <label> + <input + type="radio" + checked={value === 'Hello'} + onChange={e => onChange('Hello')} + /> + Regular greeting + </label> + <label> + <input + type="radio" + checked={value === 'Hello and welcome'} + onChange={e => onChange('Hello and welcome')} + /> + Enthusiastic greeting + </label> + </> + ); +} +``` + +```css +label { + display: block; + margin-bottom: 16px; +} +``` + +</Sandpack> + +If you set a state variable to its current value, React will skip re-rendering your component even without `memo`. You may still see your component function being called an extra time, but the result will be discarded. + +--- + +### Updating a memoized component using a context {/*updating-a-memoized-component-using-a-context*/} + +Even when a component is memoized, it will still re-render when a context that it's using changes. Memoization only has to do with props that are passed to the component from its parent. + +<Sandpack> + +```js +import { createContext, memo, useContext, useState } from 'react'; + +const ThemeContext = createContext(null); + +export default function MyApp() { + const [theme, setTheme] = useState('dark'); + + function handleClick() { + setTheme(theme === 'dark' ? 'light' : 'dark'); + } + + return ( + <ThemeContext.Provider value={theme}> + <button onClick={handleClick}> + Switch theme + </button> + <Greeting name="Taylor" /> + </ThemeContext.Provider> + ); +} + +const Greeting = memo(function Greeting({ name }) { + console.log("Greeting was rendered at", new Date().toLocaleTimeString()); + const theme = useContext(ThemeContext); + return ( + <h3 className={theme}>Hello, {name}!</h3> + ); +}); +``` + +```css +label { + display: block; + margin-bottom: 16px; +} + +.light { + color: black; + background-color: white; +} + +.dark { + color: white; + background-color: black; +} +``` + +</Sandpack> + +To make your component re-render only when a _part_ of some context changes, split your component in two. Read what you need from the context in the outer component, and pass it down to a memoized child as a prop. + +--- + +### Minimizing props changes {/*minimizing-props-changes*/} + +When you use `memo`, your component re-renders whenever any prop is not *shallowly equal* to what it was previously. This means that React compares every prop in your component with the previous value of that prop using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. Note that `Object.is(3, 3)` is `true`, but `Object.is({}, {})` is `false`. + + +To get the most out of `memo`, minimize the times that the props change. For example, if the prop is an object, prevent the parent component from re-creating that object every time by using [`useMemo`:](/reference/react/useMemo) + +```js {5-8} +function Page() { + const [name, setName] = useState('Taylor'); + const [age, setAge] = useState(42); + + const person = useMemo( + () => ({ name, age }), + [name, age] + ); + + return <Profile person={person} />; +} + +const Profile = memo(function Profile({ person }) { + // ... +}); +``` + +A better way to minimize props changes is to make sure the component accepts the minimum necessary information in its props. For example, it could accept individual values instead of a whole object: + +```js {4,7} +function Page() { + const [name, setName] = useState('Taylor'); + const [age, setAge] = useState(42); + return <Profile name={name} age={age} />; +} + +const Profile = memo(function Profile({ name, age }) { + // ... +}); +``` + +Even individual values can sometimes be projected to ones that change less frequently. For example, here a component accepts a boolean indicating the presence of a value rather than the value itself: + +```js {3} +function GroupsLanding({ person }) { + const hasGroups = person.groups !== null; + return <CallToAction hasGroups={hasGroups} />; +} + +const CallToAction = memo(function CallToAction({ hasGroups }) { + // ... +}); +``` + +When you need to pass a function to memoized component, either declare it outside your component so that it never changes, or [`useCallback`](/reference/react/useCallback#skipping-re-rendering-of-components) to cache its definition between re-renders. + +--- + +### Specifying a custom comparison function {/*specifying-a-custom-comparison-function*/} + +In rare cases it may be infeasible to minimize the props changes of a memoized component. In that case, you can provide a custom comparison function, which React will use to compare the old and new props instead of using shallow equality. This function is passed as a second argument to `memo`. It should return `true` only if the new props would result in the same output as the old props; otherwise it should return `false`. + +```js {3} +const Chart = memo(function Chart({ dataPoints }) { + // ... +}, arePropsEqual); + +function arePropsEqual(oldProps, newProps) { + return ( + oldProps.dataPoints.length === newProps.dataPoints.length && + oldProps.dataPoints.every((oldPoint, index) => { + const newPoint = newProps.dataPoints[index]; + return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y; + }) + ); +} +``` + +If you do this, use the Performance panel in your browser developer tools to make sure that your comparison function is actually faster than re-rendering the component. You might be surprised. + +When you do performance measurements, make sure that React is running in the production mode. + +<Pitfall> + +If you provide a custom `arePropsEqual` implementation, **you must compare every prop, including functions.** Functions often [close over](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) the props and state of parent components. If you return `true` when `oldProps.onClick !== newProps.onClick`, your component will keep "seeing" the props and state from a previous render inside its `onClick` handler, leading to very confusing bugs. + +Avoid doing deep equality checks inside `arePropsEqual` unless you are 100% sure that the data structure you're working with has a known limited depth. **Deep equality checks can become incredibly slow** and can freeze your app for many seconds if someone changes the data structure later. + +</Pitfall> + +--- + +## Troubleshooting {/*troubleshooting*/} +### My component re-renders when a prop is an object, array, or function {/*my-component-rerenders-when-a-prop-is-an-object-or-array*/} + +React compares old and new props by shallow equality: that is, it considers whether each new prop is reference-equal to the old prop. If you create a new object or array each time the parent is re-rendered, even if the individual elements are each the same, React will still consider it to be changed. Similarly, if you create a new function when rendering the parent component, React will consider it to have changed even if the function has the same definition. Avoid this by [simplifying props or memoizing props in the parent component](#minimizing-props-changes). diff --git a/beta/src/content/reference/react/startTransition.md b/beta/src/content/reference/react/startTransition.md new file mode 100644 index 000000000..32fef7a97 --- /dev/null +++ b/beta/src/content/reference/react/startTransition.md @@ -0,0 +1,97 @@ +--- +title: startTransition +--- + +<Intro> + +`startTransition` lets you update the state without blocking the UI. + +```js +startTransition(scope) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `startTransition(scope)` {/*starttransitionscope*/} + +The `startTransition` function lets you mark a state update as a transition. + +```js {7,9} +import { startTransition } from 'react'; + +function TabContainer() { + const [tab, setTab] = useState('about'); + + function selectTab(nextTab) { + startTransition(() => { + setTab(nextTab); + }); + } + // ... +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `scope`: A function that updates some state by calling one or more [`set` functions.](/reference/react/useState#setstate) React immediately calls `scope` with no parameters and marks all state updates scheduled synchronously during the `scope` function call as transitions. They will be [non-blocking](/reference/react/useTransition#marking-a-state-update-as-a-non-blocking-transition) and [will not display unwanted loading indicators.](/reference/react/useTransition#preventing-unwanted-loading-indicators) + +#### Returns {/*returns*/} + +`startTransition` does not return anything. + +#### Caveats {/*caveats*/} + +* `startTransition` does not provide a way to track whether a transition is pending. To show a pending indicator while the transition is ongoing, you need [`useTransition`](/reference/react/useTransition) instead. + +* You can wrap an update into a transition only if you have access to the `set` function of that state. If you want to start a transition in response to some prop or a custom Hook return value, try [`useDeferredValue`](/reference/react/usedeferredvalue) instead. + +* The function you pass to `startTransition` must be synchronous. React immediately executes this function, marking all state updates that happen while it executes as transitions. If you try to perform more state updates later (for example, in a timeout), they won't be marked as transitions. + +* A state update marked as a transition will be interrupted by other state updates. For example, if you update a chart component inside a transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input state update. + +* Transition updates can't be used to control text inputs. + +* If there are multiple ongoing transitions, React currently batches them together. This is a limitation that will likely be removed in a future release. + +--- + +## Usage {/*usage*/} + +### Marking a state update as a non-blocking transition {/*marking-a-state-update-as-a-non-blocking-transition*/} + +You can mark a state update as a *transition* by wrapping it in a `startTransition` call: + +```js {7,9} +import { startTransition } from 'react'; + +function TabContainer() { + const [tab, setTab] = useState('about'); + + function selectTab(nextTab) { + startTransition(() => { + setTab(nextTab); + }); + } + // ... +} +``` + +Transitions let you keep the user interface updates responsive even on slow devices. + +With a transition, your UI stays responsive in the middle of a re-render. For example, if the user clicks a tab but then change their mind and click another tab, they can do that without waiting for the first re-render to finish. + +<Note> + +`startTransition` is very similar to [`useTransition`](/reference/react/useTransition), except that it does not provide the `isPending` flag to track whether a transition is ongoing. You can call `startTransition` when `useTransition` is not available. For example, `startTransition` works outside components, such as from a data library. + +[Learn about transitions and see examples on the `useTransition` page.](/reference/react/useTransition) + +</Note> diff --git a/beta/src/content/reference/react/useCallback.md b/beta/src/content/reference/react/useCallback.md new file mode 100644 index 000000000..de659d9ce --- /dev/null +++ b/beta/src/content/reference/react/useCallback.md @@ -0,0 +1,931 @@ +--- +title: useCallback +--- + +<Intro> + +`useCallback` is a React Hook that lets you cache a function definition between re-renders. + +```js +const cachedFn = useCallback(fn, dependencies) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useCallback(fn, dependencies)` {/*usecallback*/} + +Call `useCallback` at the top level of your component to cache a function definition between re-renders: + +```js {4,9} +import { useCallback } from 'react'; + +export default function ProductPage({ productId, referrer, theme }) { + const handleSubmit = useCallback((orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }, [productId, referrer]); +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `fn`: The function value that you want to cache. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On subsequent renders, React will give you the same function again if the `dependencies` have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later. React will not call your function. The function is returned to you so you can decide when and whether to call it. + +* `dependencies`: The list of all reactive values referenced inside of the `fn` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. + +#### Returns {/*returns*/} + +On the initial render, `useCallback` returns the `fn` function you have passed. + +During subsequent renders, it will either return an already stored `fn` function from the last render (if the dependencies haven't changed), or return the `fn` function you have passed during this render. + +#### Caveats {/*caveats*/} + +* `useCallback` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. +* React **will not throw away the cached function unless there is a specific reason to do that.** For example, in development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of throwing away the cache--for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should match your expectations if you rely on `useCallback` as a performance optimization. Otherwise, a [state variable](/reference/react/useState#im-trying-to-set-state-to-a-function-but-it-gets-called-instead) or a [ref](/reference/react/useRef#avoiding-recreating-the-ref-contents) may be more appropriate. + +--- + +## Usage {/*usage*/} + +### Skipping re-rendering of components {/*skipping-re-rendering-of-components*/} + +When you optimize rendering performance, you will sometimes need to cache the functions that you pass to child components. Let's first look at the syntax for how to do this, and then see in which cases it's useful. + +To cache a function between re-renders of your component, wrap its definition into the `useCallback` Hook: + +```js [[3, 4, "handleSubmit"], [2, 9, "[productId, referrer]"]] +import { useCallback } from 'react'; + +function ProductPage({ productId, referrer, theme }) { + const handleSubmit = useCallback((orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }, [productId, referrer]); + // ... +``` + +You need to pass two things to `useCallback`: + +1. A function definition that you want to cache between re-renders. +2. A <CodeStep step={2}>list of dependencies</CodeStep> including every value within your component that's used inside your function. + +On the initial render, the <CodeStep step={3}>returned function</CodeStep> you'll get from `useCallback` will be the function you passed. + +On the following renders, React will compare the <CodeStep step={2}>dependencies</CodeStep> with the dependencies you passed during the previous render. If none of the dependencies have changed (compared with [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), `useCallback` will return the same function as before. Otherwise, `useCallback` will return the function you passed on *this* render. + +In other words, `useCallback` caches a function between re-renders until its dependencies change. + +**Let's walk through an example to see when this is useful.** + +Say you're passing a `handleSubmit` function down from the `ProductPage` to the `ShippingForm` component: + +```js {5} +function ProductPage({ productId, referrer, theme }) { + // ... + return ( + <div className={theme}> + <ShippingForm onSubmit={handleSubmit} /> + </div> + ); +``` + +You've noticed that toggling the `theme` prop freezes the app for a moment, but if you remove `<ShippingForm />` from your JSX, it feels fast. This tells you that it's worth trying to optimize the `ShippingForm` component. + +**By default, when a component re-renders, React re-renders all of its children recursively.** This is why, when `ProductPage` re-renders with a different `theme`, the `ShippingForm` component *also* re-renders. This is fine for components that don't require much calculation to re-render. But if you've verified that a re-render is slow, you can tell `ShippingForm` to skip re-rendering when its props are the same as on last render by wrapping it in [`memo`:](/reference/react/memo) + +```js {3,5} +import { memo } from 'react'; + +const ShippingForm = memo(function ShippingForm({ onSubmit }) { + // ... +}); +``` + +**With this change, `ShippingForm` will skip re-rendering if all of its props are the *same* as on the last render.** This is where caching a function becomes important! Imagine that you defined `handleSubmit` without `useCallback`: + +```js {2,3,8,12-13} +function ProductPage({ productId, referrer, theme }) { + // Every time the theme changes, this will be a different function... + function handleSubmit(orderDetails) { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + } + + return ( + <div className={theme}> + {/* ... so ShippingForm's props will never be the same, and it will re-render every time */} + <ShippingForm onSubmit={handleSubmit} /> + </div> + ); +} +``` + +**In JavaScript, a `function () {}` or `() => {}` always creates a _different_ function,** similar to how the `{}` object literal always creates a new object. Normally, this wouldn't be a problem, but it means that `ShippingForm` props will never be the same, and your [`memo`](/reference/react/memo) optimization won't work. This is where `useCallback` comes in handy: + +```js {2,3,8,12-13} +function ProductPage({ productId, referrer, theme }) { + // Tell React to cache your function between re-renders... + const handleSubmit = useCallback((orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }, [productId, referrer]); // ...so as long as these dependencies don't change... + + return ( + <div className={theme}> + {/* ...ShippingForm will receive the same props and can skip re-rendering */} + <ShippingForm onSubmit={handleSubmit} /> + </div> + ); +} +``` + +**By wrapping `handleSubmit` in `useCallback`, you ensure that it's the *same* function between the re-renders** (until dependencies change). You don't *have to* wrap a function in `useCallback` unless you do it for some specific reason. In this example, the reason is that you pass it to a component wrapped in [`memo`,](/reference/react/memo) and this lets it skip re-rendering. There are a few other reasons you might need `useCallback` which are described further on this page. + +<Note> + +**You should only rely on `useCallback` as a performance optimization.** If your code doesn't work without it, find the underlying problem and fix it first. Then you may add `useCallback` to improve performance. + +</Note> + +<DeepDive> + +#### How is useCallback related to useMemo? {/*how-is-usecallback-related-to-usememo*/} + +You will often see [`useMemo`](/reference/react/useMemo) alongside `useCallback`. They are both useful when you're trying to optimize a child component. They let you [memoize](https://en.wikipedia.org/wiki/Memoization) (or, in other words, cache) something you're passing down: + +```js {6-8,10-15,19} +import { useMemo, useCallback } from 'react'; + +function ProductPage({ productId, referrer }) { + const product = useData('/product/' + productId); + + const requirements = useMemo(() => { // Calls your function and caches its result + return computeRequirements(product); + }, [product]); + + const handleSubmit = useCallback((orderDetails) => { // Caches your function itself + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }, [productId, referrer]); + + return ( + <div className={theme}> + <ShippingForm requirements={requirements} onSubmit={handleSubmit} /> + </div> + ); +} +``` + +The difference is in *what* they're letting you cache: + +* **[`useMemo`](/reference/react/useMemo) caches the *result* of calling your function.** In this example, it caches the result of calling `computeRequirements(product)` so that it doesn't change unless `product` has changed. This lets you pass the `requirements` object down without unnecessarily re-rendering `ShippingForm`. When necessary, React will call the function you've passed during rendering to calculate the result. +* **`useCallback` caches *the function itself.*** Unlike `useMemo`, it does not call the function you provide. Instead, it caches the function you provided so that `handleSubmit` *itself* doesn't change unless `productId` or `referrer` has changed. This lets you pass the `handleSubmit` function down without unnecessarily re-rendering `ShippingForm`. Your code won't be called until the user submits the form. + +If you're already familiar with [`useMemo`,](/reference/react/useMemo) you might find it helpful to think of `useCallback` as this: + +```js +// Simplified implementation (inside React) +function useCallback(fn, dependencies) { + return useMemo(() => fn, dependencies); +} +``` + +[Read more about the difference between `useMemo` and `useCallback`.](/reference/react/useMemo#memoizing-a-function) + +</DeepDive> + +<DeepDive> + +#### Should you add useCallback everywhere? {/*should-you-add-usecallback-everywhere*/} + +If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful. + +Caching a function with `useCallback` is only valuable in a few cases: + +- You pass it as a prop to a component wrapped in [`memo`.](/reference/react/memo) You want to skip re-rendering if the value hasn't changed. Memoization lets your component re-render only when dependencies aren't the same. +- The function you're passing is later used as a dependency of some Hook. For example, another function wrapped in `useCallback` depends on it, or you depend on this function from [`useEffect.`](/reference/react/useEffect) + +There is no benefit to wrapping a function in `useCallback` in other cases. There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible. The downside of this approach is that code becomes less readable. Also, not all memoization is effective: a single value that's "always new" is enough to break memoization for an entire component. + +Note that `useCallback` does not prevent *creating* the function. You're always creating a function (and that's fine!), but React ignores it and gives you back a cached function if dependencies haven't changed. + +**In practice, you can make a lot of memoization unnecessary by following a few principles:** + +1. When a component visually wraps other components, let it [accept JSX as children.](/learn/passing-props-to-a-component#passing-jsx-as-children) This way, when the wrapper component updates its own state, React knows that its children don't need to re-render. +1. Prefer local state and don't [lift state up](/learn/sharing-state-between-components) any further than necessary. For example, don't keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library. +1. Keep your [rendering logic pure.](/learn/keeping-components-pure) If re-rendering a component causes a problem or produces some noticeable visual artifact, it's a bug in your component! Fix the bug instead of adding memoization. +1. Avoid [unnecessary Effects that update state.](/learn/you-might-not-need-an-effect) Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over. +1. Try to [remove unnecessary dependencies from your Effects.](/learn/removing-effect-dependencies) For example, instead of memoization, it's often simpler to move some object or a function inside an Effect or outside the component. + +If a specific interaction still feels laggy, [use the React Developer Tools profiler](/blog/2018/09/10/introducing-the-react-profiler.html) to see which components would benefit the most from memoization, and add memoization where needed. These principles make your components easier to debug and understand, so it's good to follow them in any case. In the long term, we're researching [doing granular memoization automatically](https://www.youtube.com/watch?v=lGEMwh32soc) to solve this once and for all. + +</DeepDive> + +<Recipes titleText="The difference between useCallback and declaring a function directly" titleId="examples-rerendering"> + +#### Skipping re-rendering with `useCallback` and `memo` {/*skipping-re-rendering-with-usecallback-and-memo*/} + +In this example, the `ShippingForm` component is **artificially slowed down** so that you can see what happens when a React component you're rendering is genuinely slow. Try incrementing the counter and toggling the theme. + +Incrementing the counter feels slow because it forces the slowed down `ShippingForm` to re-render. That's expected because the counter has changed, and so you need to reflect the user's new choice on the screen. + +Next, try toggling the theme. **Thanks to `useCallback` together with [`memo`](/reference/react/memo), it’s fast despite the artificial slowdown!** `ShippingForm` skipped re-rendering because the `handleSubmit` function has not changed. The `handleSubmit` function has not changed because both `productId` and `referral` (your `useCallback` dependencies) haven't changed since last render. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ProductPage from './ProductPage.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <ProductPage + referrerId="wizard_of_oz" + productId={123} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js ProductPage.js active +import { useCallback } from 'react'; +import ShippingForm from './ShippingForm.js'; + +export default function ProductPage({ productId, referrer, theme }) { + const handleSubmit = useCallback((orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }, [productId, referrer]); + + return ( + <div className={theme}> + <ShippingForm onSubmit={handleSubmit} /> + </div> + ); +} + +function post(url, data) { + // Imagine this sends a request... + console.log('POST /' + url); + console.log(data); +} +``` + +```js ShippingForm.js +import { memo, useState } from 'react'; + +const ShippingForm = memo(function ShippingForm({ onSubmit }) { + const [count, setCount] = useState(1); + + console.log('[ARTIFICIALLY SLOW] Rendering <ShippingForm />'); + let startTime = performance.now(); + while (performance.now() - startTime < 500) { + // Do nothing for 500 ms to emulate extremely slow code + } + + function handleSubmit(e) { + e.preventDefault(); + const formData = new FormData(e.target); + const orderDetails = { + ...Object.fromEntries(formData), + count + }; + onSubmit(orderDetails); + } + + return ( + <form onSubmit={handleSubmit}> + <p><b>Note: <code>ShippingForm</code> is artificially slowed down!</b></p> + <label> + Number of items: + <button type="button" onClick={() => setCount(count - 1)}>–</button> + {count} + <button type="button" onClick={() => setCount(count + 1)}>+</button> + </label> + <label> + Street: + <input name="street" /> + </label> + <label> + City: + <input name="city" /> + </label> + <label> + Postal code: + <input name="zipCode" /> + </label> + <button type="submit">Submit</button> + </form> + ); +}); + +export default ShippingForm; +``` + +```css +label { + display: block; margin-top: 10px; +} + +input { + margin-left: 5px; +} + +button[type="button"] { + margin: 5px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + +<Solution /> + +#### Always re-rendering a component {/*always-re-rendering-a-component*/} + +In this example, the `ShoppingForm` implementation is also **artificially slowed down** so that you can see what happens when some React component you're rendering is genuinely slow. Try incrementing the counter and toggling the theme. + +Unlike in the previous example, toggling the theme is also slow now! This is because **there is no `useCallback` call in this version,** so `handleSubmit` is always a new function, and the slowed down `ShoppingForm` component can't skip re-rendering. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ProductPage from './ProductPage.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <ProductPage + referrerId="wizard_of_oz" + productId={123} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js ProductPage.js active +import ShippingForm from './ShippingForm.js'; + +export default function ProductPage({ productId, referrer, theme }) { + function handleSubmit(orderDetails) { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + } + + return ( + <div className={theme}> + <ShippingForm onSubmit={handleSubmit} /> + </div> + ); +} + +function post(url, data) { + // Imagine this sends a request... + console.log('POST /' + url); + console.log(data); +} +``` + +```js ShippingForm.js +import { memo, useState } from 'react'; + +const ShippingForm = memo(function ShippingForm({ onSubmit }) { + const [count, setCount] = useState(1); + + console.log('[ARTIFICIALLY SLOW] Rendering <ShippingForm />'); + let startTime = performance.now(); + while (performance.now() - startTime < 500) { + // Do nothing for 500 ms to emulate extremely slow code + } + + function handleSubmit(e) { + e.preventDefault(); + const formData = new FormData(e.target); + const orderDetails = { + ...Object.fromEntries(formData), + count + }; + onSubmit(orderDetails); + } + + return ( + <form onSubmit={handleSubmit}> + <p><b>Note: <code>ShippingForm</code> is artificially slowed down!</b></p> + <label> + Number of items: + <button type="button" onClick={() => setCount(count - 1)}>–</button> + {count} + <button type="button" onClick={() => setCount(count + 1)}>+</button> + </label> + <label> + Street: + <input name="street" /> + </label> + <label> + City: + <input name="city" /> + </label> + <label> + Postal code: + <input name="zipCode" /> + </label> + <button type="submit">Submit</button> + </form> + ); +}); + +export default ShippingForm; +``` + +```css +label { + display: block; margin-top: 10px; +} + +input { + margin-left: 5px; +} + +button[type="button"] { + margin: 5px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + + +However, here is the same code **with the artificial slowdown removed.** Does the lack of `useCallback` feel noticeable or not? + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import ProductPage from './ProductPage.js'; + +export default function App() { + const [isDark, setIsDark] = useState(false); + return ( + <> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <ProductPage + referrerId="wizard_of_oz" + productId={123} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js ProductPage.js active +import ShippingForm from './ShippingForm.js'; + +export default function ProductPage({ productId, referrer, theme }) { + function handleSubmit(orderDetails) { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + } + + return ( + <div className={theme}> + <ShippingForm onSubmit={handleSubmit} /> + </div> + ); +} + +function post(url, data) { + // Imagine this sends a request... + console.log('POST /' + url); + console.log(data); +} +``` + +```js ShippingForm.js +import { memo, useState } from 'react'; + +const ShippingForm = memo(function ShippingForm({ onSubmit }) { + const [count, setCount] = useState(1); + + console.log('Rendering <ShippingForm />'); + + function handleSubmit(e) { + e.preventDefault(); + const formData = new FormData(e.target); + const orderDetails = { + ...Object.fromEntries(formData), + count + }; + onSubmit(orderDetails); + } + + return ( + <form onSubmit={handleSubmit}> + <label> + Number of items: + <button type="button" onClick={() => setCount(count - 1)}>–</button> + {count} + <button type="button" onClick={() => setCount(count + 1)}>+</button> + </label> + <label> + Street: + <input name="street" /> + </label> + <label> + City: + <input name="city" /> + </label> + <label> + Postal code: + <input name="zipCode" /> + </label> + <button type="submit">Submit</button> + </form> + ); +}); + +export default ShippingForm; +``` + +```css +label { + display: block; margin-top: 10px; +} + +input { + margin-left: 5px; +} + +button[type="button"] { + margin: 5px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + + +Quite often, code without memoization works fine. If your interactions are fast enough, you don't need memoization. + +Keep in mind that you need to run React in production mode, disable [React Developer Tools](/learn/react-developer-tools), and use devices similar to the ones your app's users have in order to get a realistic sense of what's actually slowing down your app. + +<Solution /> + +</Recipes> + +--- + +### Updating state from a memoized callback {/*updating-state-from-a-memoized-callback*/} + +Sometimes, you might need to update state based on previous state from a memoized callback. + +This `handleAddTodo` function specifies `todos` as a dependency because it computes the next todos from it: + +```js {6,7} +function TodoList() { + const [todos, setTodos] = useState([]); + + const handleAddTodo = useCallback((text) => { + const newTodo = { id: nextId++, text }; + setTodos([...todos, newTodo]); + }, [todos]); + // ... +``` + +You'll usually want your memoized functions to have as few dependencies as possible. When you read some state only to calculate the next state, you can remove that dependency by passing an [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) instead: + +```js {6,7} +function TodoList() { + const [todos, setTodos] = useState([]); + + const handleAddTodo = useCallback((text) => { + const newTodo = { id: nextId++, text }; + setTodos(todos => [...todos, newTodo]); + }, []); // ✅ No need for the todos dependency + // ... +``` + +Here, instead of making `todos` a dependency of your function and reading it there, you pass an instruction about *how* to update the state (`todos => [...todos, newTodo]`) to React. [Read more about updater functions.](/reference/react/useState#updating-state-based-on-the-previous-state) + +--- + +### Preventing an Effect from firing too often {/*preventing-an-effect-from-firing-too-often*/} + +Sometimes, you might want to call a function from inside an [Effect:](/learn/synchronizing-with-effects) + +```js {4-9,12} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + function createOptions() { + return { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + } + + useEffect(() => { + const options = createOptions(); + const connection = createConnection(); + connection.connect(); + // ... +``` + +This creates a problem. [Every reactive value must be declared as a dependency of your Effect.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) However, if you declare `createOptions` as a dependency, it will cause your Effect to constantly reconnect to the chat room: + + +```js {6} + useEffect(() => { + const options = createOptions(); + const connection = createConnection(); + connection.connect(); + return () => connection.disconnect(); + }, [createOptions]); // 🔴 Problem: This dependency changes on every render + // ... +``` + +To solve this, you can wrap the function you need to call from an Effect into `useCallback`: + +```js {4-9,16} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const createOptions = useCallback(() => { + return { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + }, [roomId]); // ✅ Only changes when roomId changes + + useEffect(() => { + const options = createOptions(); + const connection = createConnection(); + connection.connect(); + return () => connection.disconnect(); + }, [createOptions]); // ✅ Only changes when createOptions changes + // ... +``` + +This ensures that the `createOptions` function is the same between re-renders if the `roomId` is the same. **However, it's even better to remove the need for a function dependency.** Move your function *inside* the Effect: + +```js {5-10,16} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + function createOptions() { // ✅ No need for useCallback or function dependencies! + return { + serverUrl: 'https://localhost:1234', + roomId: roomId + }; + } + + const options = createOptions(); + const connection = createConnection(); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ Only changes when roomId changes + // ... +``` + +Now your code is simpler and doesn't need `useCallback`. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) + +--- + +### Optimizing a custom Hook {/*optimizing-a-custom-hook*/} + +If you're writing a [custom Hook,](/learn/reusing-logic-with-custom-hooks) it's recommended to wrap any functions that it returns into `useCallback`: + +```js {4-6,8-10} +function useRouter() { + const { dispatch } = useContext(RouterStateContext); + + const navigate = useCallback((url) => { + dispatch({ type: 'navigate', url }); + }, [dispatch]); + + const goBack = useCallback(() => { + dispatch({ type: 'back' }); + }, [dispatch]); + + return { + navigate, + goBack, + }; +} +``` + +This ensures that the consumers of your Hook can optimize their own code when needed. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### Every time my component renders, `useCallback` returns a different function {/*every-time-my-component-renders-usecallback-returns-a-different-function*/} + +Make sure you've specified the dependency array as a second argument! + +If you forget the dependency array, `useCallback` will return a new function every time: + +```js {7} +function ProductPage({ productId, referrer }) { + const handleSubmit = useCallback((orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }); // 🔴 Returns a new function every time: no dependency array + // ... +``` + +This is the corrected version passing the dependency array as a second argument: + +```js {7} +function ProductPage({ productId, referrer }) { + const handleSubmit = useCallback((orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }, [productId, referrer]); // ✅ Does not return a new function unnecessarily + // ... +``` + +If this doesn't help, then the problem is that at least one of your dependencies is different from the previous render. You can debug this problem by manually logging your dependencies to the console: + +```js {5} + const handleSubmit = useCallback((orderDetails) => { + // .. + }, [productId, referrer]); + + console.log([productId, referrer]); +``` + +You can then right-click on the arrays from different re-renders in the console and select "Store as a global variable" for both of them. Assuming the first one got saved as `temp1` and the second one got saved as `temp2`, you can then use the browser console to check whether each dependency in both arrays is the same: + +```js +Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays? +Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays? +Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ... +``` + +When you find which dependency is breaking memoization, either find a way to remove it, or [memoize it as well.](/reference/react/useMemo#memoizing-a-dependency-of-another-hook) + +--- + +### I need to call `useCallback` for each list item in a loop, but it's not allowed {/*i-need-to-call-usememo-for-each-list-item-in-a-loop-but-its-not-allowed*/} + +Suppose the `Chart` component is wrapped in [`memo`](/reference/react/memo). You want to skip re-rendering every `Chart` in the list when the `ReportList` component re-renders. However, you can't call `useCallback` in a loop: + +```js {5-14} +function ReportList({ items }) { + return ( + <article> + {items.map(item => { + // 🔴 You can't call useCallback in a loop like this: + const handleClick = useCallback(() => { + sendReport(item) + }, [item]); + + return ( + <figure key={item.id}> + <Chart onClick={handleClick} /> + </figure> + ); + })} + </article> + ); +} +``` + +Instead, extract a component for an individual item, and put `useCallback` there: + +```js {5,12-21} +function ReportList({ items }) { + return ( + <article> + {items.map(item => + <Report key={item.id} item={item} /> + )} + </article> + ); +} + +function Report({ item }) { + // ✅ Call useCallback at the top level: + const handleClick = useCallback(() => { + sendReport(item) + }, [item]); + + return ( + <figure> + <Chart onClick={handleClick} /> + </figure> + ); +} +``` + +Alternatively, you could remove `useCallback` in the last snippet and instead wrap `Report` itself in [`memo`.](/reference/react/memo) If the `item` prop does not change, `Report` will skip re-rendering, so `Chart` will skip re-rendering too: + +```js {5,6-8,15} +function ReportList({ items }) { + // ... +} + +const Report = memo(function Report({ item }) { + function handleClick() { + sendReport(item); + } + + return ( + <figure> + <Chart data={data} /> + </figure> + ); +}); +``` diff --git a/beta/src/content/reference/react/useContext.md b/beta/src/content/reference/react/useContext.md new file mode 100644 index 000000000..0cf433512 --- /dev/null +++ b/beta/src/content/reference/react/useContext.md @@ -0,0 +1,1385 @@ +--- +title: useContext +--- + +<Intro> + +`useContext` is a React Hook that lets you read and subscribe to [context](/learn/passing-data-deeply-with-context) from your component. + +```js +const value = useContext(SomeContext) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useContext(SomeContext)` {/*usecontext*/} + +Call `useContext` at the top level of your component to read and subscribe to [context.](/learn/passing-data-deeply-with-context) + +```js +import { useContext } from 'react'; + +function MyComponent() { + const theme = useContext(ThemeContext); + // ... +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `SomeContext`: The context that you've previously created with [`createContext`](/reference/react/createContext). The context itself does not hold the information, it only represents the kind of information you can provide or read from components. + +#### Returns {/*returns*/} + +`useContext` returns the context value for the calling component. It is determined as the `value` passed to the closest `SomeContext.Provider` above the calling component in the tree. If there is no such provider, then the returned value will be the `defaultValue` you have passed to [`createContext`](/reference/react/createContext) for that context. The returned value is always up-to-date. React automatically re-renders components that read some context if it changes. + +#### Caveats {/*caveats*/} + +* `useContext()` call in a component is not affected by providers returned from the *same* component. The corresponding `<Context.Provider>` **needs to be *above*** the component doing the `useContext()` call. +* React **automatically re-renders** all the children that use a particular context starting from the provider that receives a different `value`. The previous and the next values are compared with the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. Skipping re-renders with [`memo`](/reference/react/memo) does not prevent the children receiving fresh context values from above. +* If your build system produces duplicates modules in the output (which can happen if you use symlinks), this can break context. Passing something via context only works if `SomeContext` that you use to provide context and `SomeContext` that you use to read it are ***exactly* the same object**, as determined by a `===` comparison. + +--- + +## Usage {/*usage*/} + + +### Passing data deeply into the tree {/*passing-data-deeply-into-the-tree*/} + +Call `useContext` at the top level of your component to read and subscribe to [context.](/learn/passing-data-deeply-with-context) + +```js [[2, 4, "theme"], [1, 4, "ThemeContext"]] +import { useContext } from 'react'; + +function Button() { + const theme = useContext(ThemeContext); + // ... +``` + +`useContext` returns the <CodeStep step={2}>context value</CodeStep> for the <CodeStep step={1}>context</CodeStep> you passed. To determine the context value, React searches the component tree and finds **the closest context provider above** for that particular context. + +To pass context to a `Button`, wrap it or one of its parent components into the corresponding context provider: + +```js [[1, 3, "ThemeContext"], [2, 3, "\\"dark\\""], [1, 5, "ThemeContext"]] +function MyPage() { + return ( + <ThemeContext.Provider value="dark"> + <Form /> + </ThemeContext.Provider> + ); +} + +function Form() { + // ... renders buttons inside ... +} +``` + +It doesn't matter how many layers of components there are between the provider and the `Button`. When a `Button` *anywhere* inside of `Form` calls `useContext(ThemeContext)`, it will receive `"dark"` as the value. + +<Pitfall> + +`useContext()` always looks for the closest provider *above* the component that calls it. It searches upwards and **does not** consider providers in the component from which you're calling `useContext()`. + +</Pitfall> + +<Sandpack> + +```js +import { createContext, useContext } from 'react'; + +const ThemeContext = createContext(null); + +export default function MyApp() { + return ( + <ThemeContext.Provider value="dark"> + <Form /> + </ThemeContext.Provider> + ) +} + +function Form() { + return ( + <Panel title="Welcome"> + <Button>Sign up</Button> + <Button>Log in</Button> + </Panel> + ); +} + +function Panel({ title, children }) { + const theme = useContext(ThemeContext); + const className = 'panel-' + theme; + return ( + <section className={className}> + <h1>{title}</h1> + {children} + </section> + ) +} + +function Button({ children }) { + const theme = useContext(ThemeContext); + const className = 'button-' + theme; + return ( + <button className={className}> + {children} + </button> + ); +} +``` + +```css +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> + +--- + +### Updating data passed via context {/*updating-data-passed-via-context*/} + +Often, you'll want the context to change over time. To update context, you need to combine it with [state.](/reference/react/useState) Declare a state variable in the parent component, and pass the current state down as the <CodeStep step={2}>context value</CodeStep> to the provider. + +```js {2} [[1, 4, "ThemeContext"], [2, 4, "theme"], [1, 11, "ThemeContext"]] +function MyPage() { + const [theme, setTheme] = useState('dark'); + return ( + <ThemeContext.Provider value={theme}> + <Form /> + <Button onClick={() => { + setTheme('light'); + }}> + Switch to light theme + </Button> + </ThemeContext.Provider> + ); +} +``` + +Now any `Button` inside of the provider will receive the current `theme` value. If you call `setTheme` to update the `theme` value that you pass to the provider, all `Button` components will re-render with the new `'light'` value. + +<Recipes titleText="Examples of updating context" titleId="examples-basic"> + +#### Updating a value via context {/*updating-a-value-via-context*/} + +In this example, the `MyApp` component holds a state variable which is then passed to the `ThemeContext` provider. Checking the "Dark mode" checkbox updates the state. Changing the provided value re-renders all the components using that context. + +<Sandpack> + +```js +import { createContext, useContext, useState } from 'react'; + +const ThemeContext = createContext(null); + +export default function MyApp() { + const [theme, setTheme] = useState('light'); + return ( + <ThemeContext.Provider value={theme}> + <Form /> + <label> + <input + type="checkbox" + checked={theme === 'dark'} + onChange={(e) => { + setTheme(e.target.checked ? 'dark' : 'light') + }} + /> + Use dark mode + </label> + </ThemeContext.Provider> + ) +} + +function Form({ children }) { + return ( + <Panel title="Welcome"> + <Button>Sign up</Button> + <Button>Log in</Button> + </Panel> + ); +} + +function Panel({ title, children }) { + const theme = useContext(ThemeContext); + const className = 'panel-' + theme; + return ( + <section className={className}> + <h1>{title}</h1> + {children} + </section> + ) +} + +function Button({ children }) { + const theme = useContext(ThemeContext); + const className = 'button-' + theme; + return ( + <button className={className}> + {children} + </button> + ); +} +``` + +```css +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; + margin-bottom: 10px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> + +Note that `value="dark"` passes the `"dark"` string, but `value={theme}` passes the value of the JavaScript `theme` variable with [JSX curly braces.](/learn/javascript-in-jsx-with-curly-braces) Curly braces also let you pass context values that aren't strings. + +<Solution /> + +#### Updating an object via context {/*updating-an-object-via-context*/} + +In this example, there is a `currentUser` state variable which holds an object. You combine `{ currentUser, setCurrentUser }` into a single object and pass it down through the context inside the `value={}`. This lets any component below, such as `LoginButton`, read both `currentUser` and `setCurrentUser`, and then call `setCurrentUser` when needed. + +<Sandpack> + +```js +import { createContext, useContext, useState } from 'react'; + +const CurrentUserContext = createContext(null); + +export default function MyApp() { + const [currentUser, setCurrentUser] = useState(null); + return ( + <CurrentUserContext.Provider + value={{ + currentUser, + setCurrentUser + }} + > + <Form /> + </CurrentUserContext.Provider> + ); +} + +function Form({ children }) { + return ( + <Panel title="Welcome"> + <LoginButton /> + </Panel> + ); +} + +function LoginButton() { + const { + currentUser, + setCurrentUser + } = useContext(CurrentUserContext); + + if (currentUser !== null) { + return <p>You logged in as {currentUser.name}.</p>; + } + + return ( + <Button onClick={() => { + setCurrentUser({ name: 'Advika' }) + }}>Log in as Advika</Button> + ); +} + +function Panel({ title, children }) { + return ( + <section className="panel"> + <h1>{title}</h1> + {children} + </section> + ) +} + +function Button({ children, onClick }) { + return ( + <button className="button" onClick={onClick}> + {children} + </button> + ); +} +``` + +```css +label { + display: block; +} + +.panel { + border: 1px solid black; + border-radius: 4px; + padding: 20px; + margin-bottom: 10px; +} + +.button { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} +``` + +</Sandpack> + +<Solution /> + +#### Multiple contexts {/*multiple-contexts*/} + +In this example, there are two independent contexts. `ThemeContext` provides the current theme, which is a string, while `CurrentUserContext` holds the object representing the current user. + +<Sandpack> + +```js +import { createContext, useContext, useState } from 'react'; + +const ThemeContext = createContext(null); +const CurrentUserContext = createContext(null); + +export default function MyApp() { + const [theme, setTheme] = useState('light'); + const [currentUser, setCurrentUser] = useState(null); + return ( + <ThemeContext.Provider value={theme}> + <CurrentUserContext.Provider + value={{ + currentUser, + setCurrentUser + }} + > + <WelcomePanel /> + <label> + <input + type="checkbox" + checked={theme === 'dark'} + onChange={(e) => { + setTheme(e.target.checked ? 'dark' : 'light') + }} + /> + Use dark mode + </label> + </CurrentUserContext.Provider> + </ThemeContext.Provider> + ) +} + +function WelcomePanel({ children }) { + const {currentUser} = useContext(CurrentUserContext); + return ( + <Panel title="Welcome"> + {currentUser !== null ? + <Greeting /> : + <LoginForm /> + } + </Panel> + ); +} + +function Greeting() { + const {currentUser} = useContext(CurrentUserContext); + return ( + <p>You logged in as {currentUser.name}.</p> + ) +} + +function LoginForm() { + const {setCurrentUser} = useContext(CurrentUserContext); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const canLogin = firstName !== '' && lastName !== ''; + return ( + <> + <label> + First name{': '} + <input + required + value={firstName} + onChange={e => setFirstName(e.target.value)} + /> + </label> + <label> + Last name{': '} + <input + required + value={lastName} + onChange={e => setLastName(e.target.value)} + /> + </label> + <Button + disabled={!canLogin} + onClick={() => { + setCurrentUser({ + name: firstName + ' ' + lastName + }); + }} + > + Log in + </Button> + {!canLogin && <i>Fill in both fields.</i>} + </> + ); +} + +function Panel({ title, children }) { + const theme = useContext(ThemeContext); + const className = 'panel-' + theme; + return ( + <section className={className}> + <h1>{title}</h1> + {children} + </section> + ) +} + +function Button({ children, disabled, onClick }) { + const theme = useContext(ThemeContext); + const className = 'button-' + theme; + return ( + <button + className={className} + disabled={disabled} + onClick={onClick} + > + {children} + </button> + ); +} +``` + +```css +label { + display: block; +} + +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; + margin-bottom: 10px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> + +<Solution /> + +#### Extracting providers to a component {/*extracting-providers-to-a-component*/} + +As your app grows, it is expected that you'll have a "pyramid" of contexts closer to the root of your app. There is nothing wrong with that. However, if you dislike the nesting aesthetically, you can extract the providers into a single component. In this example, `MyProviders` hides the "plumbing" and renders the children passed to it inside the necessary providers. Note that the `theme` and `setTheme` state is needed in `MyApp` itself, so `MyApp` still owns that piece of the state. + +<Sandpack> + +```js +import { createContext, useContext, useState } from 'react'; + +const ThemeContext = createContext(null); +const CurrentUserContext = createContext(null); + +export default function MyApp() { + const [theme, setTheme] = useState('light'); + return ( + <MyProviders theme={theme} setTheme={setTheme}> + <WelcomePanel /> + <label> + <input + type="checkbox" + checked={theme === 'dark'} + onChange={(e) => { + setTheme(e.target.checked ? 'dark' : 'light') + }} + /> + Use dark mode + </label> + </MyProviders> + ); +} + +function MyProviders({ children, theme, setTheme }) { + const [currentUser, setCurrentUser] = useState(null); + return ( + <ThemeContext.Provider value={theme}> + <CurrentUserContext.Provider + value={{ + currentUser, + setCurrentUser + }} + > + {children} + </CurrentUserContext.Provider> + </ThemeContext.Provider> + ); +} + +function WelcomePanel({ children }) { + const {currentUser} = useContext(CurrentUserContext); + return ( + <Panel title="Welcome"> + {currentUser !== null ? + <Greeting /> : + <LoginForm /> + } + </Panel> + ); +} + +function Greeting() { + const {currentUser} = useContext(CurrentUserContext); + return ( + <p>You logged in as {currentUser.name}.</p> + ) +} + +function LoginForm() { + const {setCurrentUser} = useContext(CurrentUserContext); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const canLogin = firstName !== '' && lastName !== ''; + return ( + <> + <label> + First name{': '} + <input + required + value={firstName} + onChange={e => setFirstName(e.target.value)} + /> + </label> + <label> + Last name{': '} + <input + required + value={lastName} + onChange={e => setLastName(e.target.value)} + /> + </label> + <Button + disabled={!canLogin} + onClick={() => { + setCurrentUser({ + name: firstName + ' ' + lastName + }); + }} + > + Log in + </Button> + {!canLogin && <i>Fill in both fields.</i>} + </> + ); +} + +function Panel({ title, children }) { + const theme = useContext(ThemeContext); + const className = 'panel-' + theme; + return ( + <section className={className}> + <h1>{title}</h1> + {children} + </section> + ) +} + +function Button({ children, disabled, onClick }) { + const theme = useContext(ThemeContext); + const className = 'button-' + theme; + return ( + <button + className={className} + disabled={disabled} + onClick={onClick} + > + {children} + </button> + ); +} +``` + +```css +label { + display: block; +} + +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; + margin-bottom: 10px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> + +<Solution /> + +#### Scaling up with context and a reducer {/*scaling-up-with-context-and-a-reducer*/} + +In larger apps, it is common to combine context with a [reducer](/reference/react/useReducer) to extract the logic related to some state out of components. In this example, all the "wiring" is hidden in the `TasksContext.js`, which contains a reducer and two separate contexts. + +Read a [full walkthrough](/learn/scaling-up-with-reducer-and-context) of this example. + +<Sandpack> + +```js App.js +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; +import { TasksProvider } from './TasksContext.js'; + +export default function TaskApp() { + return ( + <TasksProvider> + <h1>Day off in Kyoto</h1> + <AddTask /> + <TaskList /> + </TasksProvider> + ); +} +``` + +```js TasksContext.js +import { createContext, useContext, useReducer } from 'react'; + +const TasksContext = createContext(null); + +const TasksDispatchContext = createContext(null); + +export function TasksProvider({ children }) { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + return ( + <TasksContext.Provider value={tasks}> + <TasksDispatchContext.Provider value={dispatch}> + {children} + </TasksDispatchContext.Provider> + </TasksContext.Provider> + ); +} + +export function useTasks() { + return useContext(TasksContext); +} + +export function useTasksDispatch() { + return useContext(TasksDispatchContext); +} + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +const initialTasks = [ + { id: 0, text: 'Philosopher’s Path', done: true }, + { id: 1, text: 'Visit the temple', done: false }, + { id: 2, text: 'Drink matcha', done: false } +]; +``` + +```js AddTask.js +import { useState, useContext } from 'react'; +import { useTasksDispatch } from './TasksContext.js'; + +export default function AddTask() { + const [text, setText] = useState(''); + const dispatch = useTasksDispatch(); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + }}>Add</button> + </> + ); +} + +let nextId = 3; +``` + +```js TaskList.js +import { useState, useContext } from 'react'; +import { useTasks, useTasksDispatch } from './TasksContext.js'; + +export default function TaskList() { + const tasks = useTasks(); + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task task={task} /> + </li> + ))} + </ul> + ); +} + +function Task({ task }) { + const [isEditing, setIsEditing] = useState(false); + const dispatch = useTasksDispatch(); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + text: e.target.value + } + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + dispatch({ + type: 'changed', + task: { + ...task, + done: e.target.checked + } + }); + }} + /> + {taskContent} + <button onClick={() => { + dispatch({ + type: 'deleted', + id: task.id + }); + }}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Specifying a fallback default value {/*specifying-a-fallback-default-value*/} + +If React can't find any providers of that particular <CodeStep step={1}>context</CodeStep> in the parent tree, the context value returned by `useContext()` will be equal to the <CodeStep step={3}>default value</CodeStep> that you specified when you [created that context](/reference/react/createContext): + +```js [[1, 1, "ThemeContext"], [3, 1, "null"]] +const ThemeContext = createContext(null); +``` + +The default value **never changes**. If you want to update context, use it with state as [described above.](#updating-data-passed-via-context) + +Often, instead of `null`, there is some more meaningful value you can use as a default, for example: + +```js [[1, 1, "ThemeContext"], [3, 1, "light"]] +const ThemeContext = createContext('light'); +``` + +This way, if you accidentally render some component without a corresponding provider, it won't break. This also helps your components work well in a test environment without setting up a lot of providers in the tests. + +In the example below, the "Toggle theme" button is always light because it's **outside any theme context provider** and the default context theme value is `'light'`. Try editing the default theme to be `'dark'`. + +<Sandpack> + +```js +import { createContext, useContext, useState } from 'react'; + +const ThemeContext = createContext('light'); + +export default function MyApp() { + const [theme, setTheme] = useState('light'); + return ( + <> + <ThemeContext.Provider value={theme}> + <Form /> + </ThemeContext.Provider> + <Button onClick={() => { + setTheme(theme === 'dark' ? 'light' : 'dark'); + }}> + Toggle theme + </Button> + </> + ) +} + +function Form({ children }) { + return ( + <Panel title="Welcome"> + <Button>Sign up</Button> + <Button>Log in</Button> + </Panel> + ); +} + +function Panel({ title, children }) { + const theme = useContext(ThemeContext); + const className = 'panel-' + theme; + return ( + <section className={className}> + <h1>{title}</h1> + {children} + </section> + ) +} + +function Button({ children, onClick }) { + const theme = useContext(ThemeContext); + const className = 'button-' + theme; + return ( + <button className={className} onClick={onClick}> + {children} + </button> + ); +} +``` + +```css +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; + margin-bottom: 10px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> + +--- + +### Overriding context for a part of the tree {/*overriding-context-for-a-part-of-the-tree*/} + +You can override the context for a part of the tree by wrapping that part in a provider with a different value. + +```js {3,5} +<ThemeContext.Provider value="dark"> + ... + <ThemeContext.Provider value="light"> + <Footer /> + </ThemeContext.Provider> + ... +</ThemeContext.Provider> +``` + +You can nest and override providers as many times as you need. + +<Recipes title="Examples of overriding context"> + +#### Overriding a theme {/*overriding-a-theme*/} + +Here, the button *inside* the `Footer` receives a different context value (`"light"`) than the buttons outside (`"dark"`). + +<Sandpack> + +```js +import { createContext, useContext } from 'react'; + +const ThemeContext = createContext(null); + +export default function MyApp() { + return ( + <ThemeContext.Provider value="dark"> + <Form /> + </ThemeContext.Provider> + ) +} + +function Form() { + return ( + <Panel title="Welcome"> + <Button>Sign up</Button> + <Button>Log in</Button> + <ThemeContext.Provider value="light"> + <Footer /> + </ThemeContext.Provider> + </Panel> + ); +} + +function Footer() { + return ( + <footer> + <Button>Settings</Button> + </footer> + ); +} + +function Panel({ title, children }) { + const theme = useContext(ThemeContext); + const className = 'panel-' + theme; + return ( + <section className={className}> + {title && <h1>{title}</h1>} + {children} + </section> + ) +} + +function Button({ children }) { + const theme = useContext(ThemeContext); + const className = 'button-' + theme; + return ( + <button className={className}> + {children} + </button> + ); +} +``` + +```css +footer { + margin-top: 20px; + border-top: 1px solid #aaa; +} + +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +</Sandpack> + +<Solution /> + +#### Automatically nested headings {/*automatically-nested-headings*/} + +You can "accumulate" information when you nest context providers. In this example, the `Section` component keeps track of the `LevelContext` which specifies the depth of the section nesting. It reads the `LevelContext` from the parent section, and provides the `LevelContext` number increased by one to its children. As a result, the `Heading` component can automatically decide which of the `<h1>`, `<h2>`, `<h3>`, ..., tags to use based on how many `Section` components it is nested inside of. + +Read a [detailed walkthrough](/learn/passing-data-deeply-with-context) of this example. + +<Sandpack> + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( + <Section> + <Heading>Title</Heading> + <Section> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Heading>Heading</Heading> + <Section> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Heading>Sub-heading</Heading> + <Section> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + <Heading>Sub-sub-heading</Heading> + </Section> + </Section> + </Section> + </Section> + ); +} +``` + +```js Section.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children }) { + const level = useContext(LevelContext); + return ( + <section className="section"> + <LevelContext.Provider value={level + 1}> + {children} + </LevelContext.Provider> + </section> + ); +} +``` + +```js Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 0: + throw Error('Heading must be inside a Section!'); + case 1: + return <h1>{children}</h1>; + case 2: + return <h2>{children}</h2>; + case 3: + return <h3>{children}</h3>; + case 4: + return <h4>{children}</h4>; + case 5: + return <h5>{children}</h5>; + case 6: + return <h6>{children}</h6>; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(0); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Optimizing re-renders when passing objects and functions {/*optimizing-re-renders-when-passing-objects-and-functions*/} + +You can pass any values via context, including objects and functions. + +```js [[2, 10, "{ currentUser, login }"]] +function MyApp() { + const [currentUser, setCurrentUser] = useState(null); + + function login(response) { + storeCredentials(response.credentials); + setCurrentUser(response.user); + } + + return ( + <AuthContext.Provider value={{ currentUser, login }}> + <Page /> + </AuthContext.Provider> + ); +} +``` + +Here, the <CodeStep step={2}>context value</CodeStep> is a JavaScript object with two properties, one of which is a function. Whenever `MyApp` re-renders (for example, on a route update), this will be a *different* object pointing at a *different* function, so React will also have to re-render all components deep in the tree that call `useContext(AuthContext)`. + +In smaller apps, this is not a problem. However, there is no need to re-render them if the underlying data, like `currentUser`, has not changed. To help React take advantage of that fact, you may wrap the `login` function with [`useCallback`](/reference/react/useCallback) and wrap the object creation into [`useMemo`](/reference/react/useMemo). This is a performance optimization: + +```js {6,9,11,14,17} +import { useCallback, useMemo } from 'react'; + +function MyApp() { + const [currentUser, setCurrentUser] = useState(null); + + const login = useCallback((response) => { + storeCredentials(response.credentials); + setCurrentUser(response.user); + }, []); + + const contextValue = useMemo(() => ({ + currentUser, + login + }), [currentUser, login]); + + return ( + <AuthContext.Provider value={contextValue}> + <Page /> + </AuthContext.Provider> + ); +} +``` + +As a result of this change, even if `MyApp` needs to re-render, the components calling `useContext(AuthProvider)` won't need to re-render unless `currentUser` has changed. Read more about [`useMemo`](/reference/react/useMemo#skipping-re-rendering-of-components) and [`useCallback`.](/reference/react/useCallback#skipping-re-rendering-of-components) + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My component doesn't see the value from my provider {/*my-component-doesnt-see-the-value-from-my-provider*/} + +There are a few common ways that this can happen: + +1. You're rendering `<SomeContext.Provider>` in the same component (or below) as where you're calling `useContext()`. Move `<SomeContext.Provider>` *above and outside* the component calling `useContext()`. +2. You may have forgotten to wrap your component with `<SomeContext.Provider>`, or you might have put it in a different part of the tree than you thought. Check whether the hierarchy is right using [React DevTools.](/learn/react-developer-tools) +3. You might be running into some build issue with your tooling that causes `SomeContext` as seen from the providing component and `SomeContext` as seen by the reading component to be two different objects. This can happen if you use symlinks, for example. You can verify this by assigning them to globals like `window.SomeContext1` and `window.SomeContext2` and then checking whether `window.SomeContext1 === window.SomeContext2` in the console. If they're not the same, you need to fix that issue on the build tool level. + +### I am always getting `undefined` from my context although the default value is different {/*i-am-always-getting-undefined-from-my-context-although-the-default-value-is-different*/} + +You might have a provider without a `value` in the tree: + +```js {1,2} +// 🚩 Doesn't work: no value prop +<ThemeContext.Provider> + <Button /> +</ThemeContext.Provider> +``` + +If you forget to specify `value`, it's like passing `value={undefined}`. + +You may have also mistakingly used a different prop name by mistake: + +```js {1,2} +// 🚩 Doesn't work: prop should be called "value" +<ThemeContext.Provider theme={theme}> + <Button /> +</ThemeContext.Provider> +``` + +In both of these cases you should see a warning from React in the console. To fix them, call the prop `value`: + +```js {1,2} +// ✅ Passing the value prop +<ThemeContext.Provider value={theme}> + <Button /> +</ThemeContext.Provider> +``` + +Note that the [default value from your `createContext(defaultValue)` call](#specifying-a-fallback-default-value) is only used **if there is no matching provider above at all.** If there is a `<SomeContext.Provider value={undefined}>` component somewhere in the parent tree, the component calling `useContext(SomeContext)` *will* receive `undefined` as the context value. diff --git a/beta/src/content/reference/react/useDebugValue.md b/beta/src/content/reference/react/useDebugValue.md new file mode 100644 index 000000000..e30704a66 --- /dev/null +++ b/beta/src/content/reference/react/useDebugValue.md @@ -0,0 +1,122 @@ +--- +title: useDebugValue +--- + +<Intro> + +`useDebugValue` is a React Hook that lets you add a label to a custom Hook in [React DevTools.](/learn/react-developer-tools) + +```js +useDebugValue(value, format?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useDebugValue(value, format?)` {/*usedebugvalue*/} + +Call `useDebugValue` at the top level of your [custom Hook](/learn/reusing-logic-with-custom-hooks) to display a readable debug value: + +```js +import { useDebugValue } from 'react'; + +function useOnlineStatus() { + // ... + useDebugValue(isOnline ? 'Online' : 'Offline'); + // ... +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `value`: The value you want to display in React DevTools. It can have any type. +* **optional** `format`: A formatting function. When the component is inspected, React DevTools will call the formatting function with the `value` as the argument, and then display the returned formatted value (which may have any type). If you don't specify the formatting function, the original `value` itself will be displayed. + +#### Returns {/*returns*/} + +`useDebugValue` does not return anything. + +## Usage {/*usage*/} + +### Adding a label to a custom Hook {/*adding-a-label-to-a-custom-hook*/} + +Call `useDebugValue` at the top level of your [custom Hook](/learn/reusing-logic-with-custom-hooks) to display a readable <CodeStep step={1}>debug value</CodeStep> for [React DevTools.](/learn/react-developer-tools) + +```js [[1, 5, "isOnline ? 'Online' : 'Offline'"]] +import { useDebugValue } from 'react'; + +function useOnlineStatus() { + // ... + useDebugValue(isOnline ? 'Online' : 'Offline'); + // ... +} +``` + +This gives components calling `useOnlineStatus` a label like `OnlineStatus: "Online"` when you inspect them: + +![A screenshot of React DevTools showing the debug value](/images/docs/react-devtools-usedebugvalue.png) + +Without the `useDebugValue` call, only the underlying data (in this example, `true`) would be displayed. + +<Sandpack> + +```js +import { useOnlineStatus } from './useOnlineStatus.js'; + +function StatusBar() { + const isOnline = useOnlineStatus(); + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} + +export default function App() { + return <StatusBar />; +} +``` + +```js useOnlineStatus.js active +import { useSyncExternalStore, useDebugValue } from 'react'; + +export function useOnlineStatus() { + const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine, () => true); + useDebugValue(isOnline ? 'Online' : 'Offline'); + return isOnline; +} + +function subscribe(callback) { + window.addEventListener('online', callback); + window.addEventListener('offline', callback); + return () => { + window.removeEventListener('online', callback); + window.removeEventListener('offline', callback); + }; +} +``` + +</Sandpack> + +<Note> + +We don't recommend adding debug values to every custom Hook. It's most valuable for custom Hooks that are part of shared libraries and that have a complex internal data structure that's difficult to inspect. + +</Note> + +--- + +### Deferring formatting of a debug value {/*deferring-formatting-of-a-debug-value*/} + +You can also pass a formatting function as the second argument to `useDebugValue`: + +```js [[1, 1, "date", 18], [2, 1, "date.toDateString()"]] +useDebugValue(date, date => date.toDateString()); +``` + +Your formatting function will receive the <CodeStep step={1}>debug value</CodeStep> as a parameter and should return a <CodeStep step={2}>formatted display value</CodeStep>. When your component is inspected, React DevTools will call the formatting function and display its result. + +This lets you avoid running potentially expensive formatting logic unless the component is actually inspected. For example, if `date` is a Date value, this avoids calling `toDateString()` on it for every render of your component. diff --git a/beta/src/content/reference/react/useDeferredValue.md b/beta/src/content/reference/react/useDeferredValue.md new file mode 100644 index 000000000..32fa09068 --- /dev/null +++ b/beta/src/content/reference/react/useDeferredValue.md @@ -0,0 +1,958 @@ +--- +title: useDeferredValue +--- + +<Intro> + +`useDeferredValue` is a React Hook that lets you defer updating a part of the UI. + +```js +const deferredValue = useDeferredValue(value) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useDeferredValue(value)` {/*usedeferredvalue*/} + +Call `useDeferredValue` at the top level of your component to get a deferred version of that value. + +```js +import { useState, useDeferredValue } from 'react'; + +function SearchPage() { + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query); + // ... +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `value`: The value you want to defer. It can have any type. + +#### Returns {/*returns*/} + +During the initial render, the returned deferred value will be the same as the value you provided. During updates, React will first attempt a re-render with the old value (so the returned value will match the old value), and then try another re-render in background with the new value (so the returned value will match the updated value). + +#### Caveats {/*caveats*/} + +- The values you pass to `useDeferredValue` should either be primitive values (like strings and numbers) or objects created outside of rendering. If you create a new object during rendering and immediately pass it to `useDeferredValue`, it will be different on every render, causing unnecessary background re-renders. + +- When `useDeferredValue` receives a different value (compared with [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), in addition to the current render (when it still uses the previous value), it schedules a re-render in the background with the new value. The background re-render is interruptible: if there's another update to the `value`, React will restart the background re-render from scratch. For example, if the user is typing into an input faster than a chart receiving its deferred value can re-render, the chart will only re-render after the user stops typing. + +- `useDeferredValue` is integrated with [`<Suspense>`.](/reference/react/Suspense) If the background update caused by a new value suspends the UI, the user will not see the fallback. They will keep seeing the old deferred value until the data loads. + +- `useDeferredValue` does not by itself prevent extra network requests. + +- There is no fixed delay caused by `useDeferredValue` itself. As soon as React finishes the original re-render, React will immediately start working on the background re-render with the new deferred value. However, any updates caused by events (like typing) will interrupt the background re-render and get prioritized over it. + +- The background re-render caused by `useDeferredValue` does not fire Effects until it's committed to the screen. If the background re-render suspends, its Effects will run after the data loads and the UI updates. + +--- + +## Usage {/*usage*/} + +### Showing stale content while fresh content is loading {/*showing-stale-content-while-fresh-content-is-loading*/} + +Call `useDeferredValue` at the top level of your component to defer updating some part of your UI. + +```js [[1, 5, "query"], [2, 5, "deferredQuery"]] +import { useState, useDeferredValue } from 'react'; + +function SearchPage() { + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query); + // ... +} +``` + +During the initial render, the <CodeStep step={2}>deferred value</CodeStep> will be the same as the <CodeStep step={1}>value</CodeStep> you provided. + +During updates, the <CodeStep step={2}>deferred value</CodeStep> will "lag behind" the latest <CodeStep step={1}>value</CodeStep>. In particular, React will first re-render *without* updating the deferred value, and then try to re-render with the newly received value in background. + +**Let's walk through an example to see when this is useful.** + +<Note> + +This example assumes you use one of Suspense-enabled data sources: + +- Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/advanced-features/react-18) +- Lazy-loading component code with [`lazy`](/reference/react/lazy) + +[Learn more about Suspense and its limitations.](/reference/react/Suspense) + +</Note> + + +In this example, the `SearchResults` component [suspends](/reference/react/Suspense#displaying-a-fallback-while-content-is-loading) while fetching the search results. Try typing `"a"`, waiting for the results, and then editing it to `"ab"`. The results for `"a"` will get replaced by the loading fallback. + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState } from 'react'; +import SearchResults from './SearchResults.js'; + +export default function App() { + const [query, setQuery] = useState(''); + return ( + <> + <label> + Search albums: + <input value={query} onChange={e => setQuery(e.target.value)} /> + </label> + <Suspense fallback={<h2>Loading...</h2>}> + <SearchResults query={query} /> + </Suspense> + </> + ); +} +``` + +```js SearchResults.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function SearchResults({ query }) { + if (query === '') { + return null; + } + const albums = use(fetchData(`/search?q=${query}`)); + if (albums.length === 0) { + return <p>No matches for <i>"{query}"</i></p>; + } + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url.startsWith('/search?q=')) { + return await getSearchResults(url.slice('/search?q='.length)); + } else { + throw Error('Not implemented'); + } +} + +async function getSearchResults(query) { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + const allAlbums = [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; + + const lowerQuery = query.trim().toLowerCase(); + return allAlbums.filter(album => { + const lowerTitle = album.title.toLowerCase(); + return ( + lowerTitle.startsWith(lowerQuery) || + lowerTitle.indexOf(' ' + lowerQuery) !== -1 + ) + }); +} +``` + +```css +input { margin: 10px; } +``` + +</Sandpack> + +A common alternative UI pattern is to *defer* updating the list of results and to keep showing the previous results until the new results are ready. The `useDeferredValue` Hook lets you pass a deferred version of the query down: + +```js {3,11} +export default function App() { + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query); + return ( + <> + <label> + Search albums: + <input value={query} onChange={e => setQuery(e.target.value)} /> + </label> + <Suspense fallback={<h2>Loading...</h2>}> + <SearchResults query={deferredQuery} /> + </Suspense> + </> + ); +} +``` + +The `query` will update immediately, so the input will display the new value. However, the `deferredQuery` will keep its previous value until the data has loaded, so `SearchResults` will show the stale results for a bit. + +Enter `"a"` in the example below, wait for the results to load, and then edit the input to `"ab"`. Notice how instead of the Suspense fallback, you now see the stale result list until the new results have loaded: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState, useDeferredValue } from 'react'; +import SearchResults from './SearchResults.js'; + +export default function App() { + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query); + return ( + <> + <label> + Search albums: + <input value={query} onChange={e => setQuery(e.target.value)} /> + </label> + <Suspense fallback={<h2>Loading...</h2>}> + <SearchResults query={deferredQuery} /> + </Suspense> + </> + ); +} +``` + +```js SearchResults.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function SearchResults({ query }) { + if (query === '') { + return null; + } + const albums = use(fetchData(`/search?q=${query}`)); + if (albums.length === 0) { + return <p>No matches for <i>"{query}"</i></p>; + } + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url.startsWith('/search?q=')) { + return await getSearchResults(url.slice('/search?q='.length)); + } else { + throw Error('Not implemented'); + } +} + +async function getSearchResults(query) { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + const allAlbums = [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; + + const lowerQuery = query.trim().toLowerCase(); + return allAlbums.filter(album => { + const lowerTitle = album.title.toLowerCase(); + return ( + lowerTitle.startsWith(lowerQuery) || + lowerTitle.indexOf(' ' + lowerQuery) !== -1 + ) + }); +} +``` + +```css +input { margin: 10px; } +``` + +</Sandpack> + +<DeepDive> + +#### How does deferring a value work under the hood? {/*how-does-deferring-a-value-work-under-the-hood*/} + +You can think of it as happening in two steps: + +1. **First, React re-renders with the new `query` (`"ab"`) but with the old `deferredQuery` (still `"a")`.** The `deferredQuery` value, which you pass to the result list, is *deferred:* it "lags behind" the `query` value. + +2. **In background, React tries to re-render with *both* `query` and `deferredQuery` updated to `"ab"`.** If this re-render completes, React will show it on the screen. However, if it suspends (the results for `"ab"` have not loaded yet), React will abandon this rendering attempt, and retry this re-render again after the data has loaded. The user will keep seeing the stale deferred value until the data is ready. + +The deferred "background" rendering is interruptible. For example, if you type into the input again, React will abandon it and restart with the new value. React will always use the latest provided value. + +Note that there is still a network request per each keystroke. What's being deferred here is displaying results (until they're ready), not the network requests themselves. Even if the user continues typing, responses for each keystroke get cached, so pressing Backspace is instant and doesn't fetch again. + +</DeepDive> + +--- + +### Indicating that the content is stale {/*indicating-that-the-content-is-stale*/} + +In the example above, there is no indication that the result list for the latest query is still loading. This can be confusing to the user if the new results take a while to load. To make it more obvious to the user that the result list does not match the latest query, you can add a visual indication when the stale result list is displayed: + +```js {2} +<div style={{ + opacity: query !== deferredQuery ? 0.5 : 1, +}}> + <SearchResults query={deferredQuery} /> +</div> +``` + +With this change, as soon as you start typing, the stale result list gets slightly dimmed until the new result list loads. You can also add a CSS transition to delay dimming so that it feels gradual, like in the example below: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState, useDeferredValue } from 'react'; +import SearchResults from './SearchResults.js'; + +export default function App() { + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query); + const isStale = query !== deferredQuery; + return ( + <> + <label> + Search albums: + <input value={query} onChange={e => setQuery(e.target.value)} /> + </label> + <Suspense fallback={<h2>Loading...</h2>}> + <div style={{ + opacity: isStale ? 0.5 : 1, + transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear' + }}> + <SearchResults query={deferredQuery} /> + </div> + </Suspense> + </> + ); +} +``` + +```js SearchResults.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function SearchResults({ query }) { + if (query === '') { + return null; + } + const albums = use(fetchData(`/search?q=${query}`)); + if (albums.length === 0) { + return <p>No matches for <i>"{query}"</i></p>; + } + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url.startsWith('/search?q=')) { + return await getSearchResults(url.slice('/search?q='.length)); + } else { + throw Error('Not implemented'); + } +} + +async function getSearchResults(query) { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + const allAlbums = [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; + + const lowerQuery = query.trim().toLowerCase(); + return allAlbums.filter(album => { + const lowerTitle = album.title.toLowerCase(); + return ( + lowerTitle.startsWith(lowerQuery) || + lowerTitle.indexOf(' ' + lowerQuery) !== -1 + ) + }); +} +``` + +```css +input { margin: 10px; } +``` + +</Sandpack> + +--- + +### Deferring re-rendering for a part of the UI {/*deferring-re-rendering-for-a-part-of-the-ui*/} + +You can also apply `useDeferredValue` as a performance optimization. It is useful when a part of your UI is slow to re-render, there's no easy way to optimize it, and you want to prevent it from blocking the rest of the UI. + +Imagine you have a text field and a component (like a chart or a long list) that re-renders on every keystroke: + +```js +function App() { + const [text, setText] = useState(''); + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <SlowList text={text} /> + </> + ); +} +``` + +First, optimize `SlowList` to skip re-rendering when its props are the same. To do this, [wrap it in `memo`:](/reference/react/memo#skipping-re-rendering-when-props-are-unchanged) + +```js {1,3} +const SlowList = memo(function SlowList({ text }) { + // ... +}); +``` + +However, this only helps if the `SlowList` props are *the same* as during the previous render. The problem you're facing now is that it's slow when they're *different,* and when you actually need to show different visual output. + +Concretely, the main performance problem is that whenever you type into the input, the `SlowList` receives new props, and re-rendering its entire tree makes the typing feel janky. In this case, `useDeferredValue` lets you prioritize updating the input (which must be fast) over updating the result list (which is allowed to be slower): + +```js {3,7} +function App() { + const [text, setText] = useState(''); + const deferredText = useDeferredValue(text); + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <SlowList text={deferredText} /> + </> + ); +} +``` + +This does not make re-rendering of the `SlowList` faster. However, it tells React that re-rendering the list can be deprioritized so that it doesn't block the keystrokes. The list will "lag behind" the input and then "catch up". Like before, React will attempt to update the list as soon as possible, but it will not block the user from typing again. + +<Recipes titleText="The difference between useDeferredValue and unoptimized re-rendering" titleId="examples"> + +#### Deferred re-rendering of the list {/*deferred-re-rendering-of-the-list*/} + +In this example, each item in the `SlowList` component is **artificially slowed down** so that you can see how `useDeferredValue` lets you keep the input responsive. Type into the input and notice that typing feels snappy while the list "lags behind" it. + +<Sandpack> + +```js +import { useState, useDeferredValue } from 'react'; +import SlowList from './SlowList.js'; + +export default function App() { + const [text, setText] = useState(''); + const deferredText = useDeferredValue(text); + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <SlowList text={deferredText} /> + </> + ); +} +``` + +```js SlowList.js +import { memo } from 'react'; + +const SlowList = memo(function SlowList({ text }) { + // Log once. The actual slowdown is inside SlowItem. + console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />'); + + let items = []; + for (let i = 0; i < 250; i++) { + items.push(<SlowItem key={i} text={text} />); + } + return ( + <ul className="items"> + {items} + </ul> + ); +}); + +function SlowItem({ text }) { + let startTime = performance.now(); + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return ( + <li className="item"> + Text: {text} + </li> + ) +} + +export default SlowList; +``` + +```css +.items { + padding: 0; +} + +.item { + list-style: none; + display: block; + height: 40px; + padding: 5px; + margin-top: 10px; + border-radius: 4px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +<Solution /> + +#### Unoptimized re-rendering of the list {/*unoptimized-re-rendering-of-the-list*/} + +In this example, each item in the `SlowList` component is **artificially slowed down**, but there is no `useDeferredValue`. + +Notice how typing into the input feels very janky. This is because without `useDeferredValue`, each keystroke forces the entire list to re-render immediately in a non-interruptible way. + +<Sandpack> + +```js +import { useState } from 'react'; +import SlowList from './SlowList.js'; + +export default function App() { + const [text, setText] = useState(''); + return ( + <> + <input value={text} onChange={e => setText(e.target.value)} /> + <SlowList text={text} /> + </> + ); +} +``` + +```js SlowList.js +import { memo } from 'react'; + +const SlowList = memo(function SlowList({ text }) { + // Log once. The actual slowdown is inside SlowItem. + console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />'); + + let items = []; + for (let i = 0; i < 250; i++) { + items.push(<SlowItem key={i} text={text} />); + } + return ( + <ul className="items"> + {items} + </ul> + ); +}); + +function SlowItem({ text }) { + let startTime = performance.now(); + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return ( + <li className="item"> + Text: {text} + </li> + ) +} + +export default SlowList; +``` + +```css +.items { + padding: 0; +} + +.item { + list-style: none; + display: block; + height: 40px; + padding: 5px; + margin-top: 10px; + border-radius: 4px; + border: 1px solid #aaa; +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +<Pitfall> + +This optimization requires `SlowList` to be wrapped in [`memo`.](/reference/react/memo) This is because whenever the `text` changes, React needs to be able to re-render the parent component quickly. During that re-render, `deferredText` still has its previous value, so `SlowList` is able to skip re-rendering (its props have not changed). Without [`memo`,](/reference/react/memo) it would have to re-render anyway, defeating the point of the optimization. + +</Pitfall> + +<DeepDive> + +#### How is deferring a value different from debouncing and throttling? {/*how-is-deferring-a-value-different-from-debouncing-and-throttling*/} + +There are two common optimization techniques you might have used before in this scenario: + +- *Debouncing* means you'd wait for the user to stop typing (e.g. for a second) before updating the list. +- *Throttling* means you'd update the list every once in a while (e.g. at most once a second). + +While these techniques are helpful in some cases, `useDeferredValue` is better suited to optimizing rendering because it is deeply integrated with React itself and adapts to the user's device. + +Unlike debouncing or throttling, it doesn't require choosing any fixed delay. If the user's device is fast (e.g. powerful laptop), the deferred re-render would happen almost immediately and wouldn't be noticeable. If the user's device is slow, the list would "lag behind" the input proportionally to how slow the device is. + +Also, unlike with debouncing or throttling, deferred re-renders done by `useDeferredValue` are interruptible by default. This means that if React is in the middle of re-rendering a large list, but the user makes another keystroke, React will abandon that re-render, handle the keystroke, and then start rendering in background again. By contrast, debouncing and throttling still produce a janky experience because they're *blocking:* they merely postpone the moment when rendering blocks the keystroke. + +If the work you're optimizing doesn't happen during rendering, debouncing and throttling are still useful. For example, they can let you fire fewer network requests. You can also use these techniques together. + +</DeepDive> diff --git a/beta/src/content/reference/react/useEffect.md b/beta/src/content/reference/react/useEffect.md new file mode 100644 index 000000000..ed2be0702 --- /dev/null +++ b/beta/src/content/reference/react/useEffect.md @@ -0,0 +1,1863 @@ +--- +title: useEffect +--- + +<Intro> + +`useEffect` is a React Hook that lets you [synchronize a component with an external system.](/learn/synchronizing-with-effects) + +```js +useEffect(setup, dependencies?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useEffect(setup, dependencies?)` {/*useeffect*/} + +Call `useEffect` at the top level of your component to declare an Effect: + +```js +import { useEffect } from 'react'; +import { createConnection } from './chat.js'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [serverUrl, roomId]); + // ... +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `setup`: The function with your Effect's logic. Your setup function may also optionally return a *cleanup* function. When your component is first added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. After your component is removed from the DOM, React will run your cleanup function one last time. + +* **optional** `dependencies`: The list of all reactive values referenced inside of the `setup` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. If you don't specify the dependencies at all, your Effect will re-run after every re-render of the component. [See the difference between passing an array of dependencies, an empty array, and no dependencies at all.](#examples-dependencies) + +#### Returns {/*returns*/} + +`useEffect` returns `undefined`. + +#### Caveats {/*caveats*/} + +* `useEffect` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. + +* If you're **not trying to synchronize with some external system,** [you probably don't need an Effect.](/learn/you-might-not-need-an-effect) + +* When Strict Mode is on, React will **run one extra development-only setup+cleanup cycle** before the first real setup. This is a stress-test that ensures that your cleanup logic "mirrors" your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, [you need to implement the cleanup function.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) + +* If some of your dependencies are objects or functions defined inside the component, there is a risk that they will **cause the Effect to re-run more often than needed.** To fix this, remove unnecessary [object](#removing-unnecessary-object-dependencies) and [function](#removing-unnecessary-function-dependencies) dependencies. You can also [extract state updates](#updating-state-based-on-previous-state-from-an-effect) and [non-reactive logic](#reading-the-latest-props-and-state-from-an-effect) outside of your Effect. + +* If your Effect wasn't caused by an interaction (like a click), React will let the browser **paint the updated screen first before running your Effect.** If your Effect is doing something visual (for example, positioning a tooltip), and the delay is noticeable (for example, it flickers), you need to replace `useEffect` with [`useLayoutEffect`.](/reference/react/useLayoutEffect) + +* Even if your Effect was caused by an interaction (like a click), **the browser may repaint the screen before processing the state updates inside your Effect.** Usually, that's what you want. However, if you must block the browser from repainting the screen, you need to replace `useEffect` with [`useLayoutEffect`.](/reference/react/useLayoutEffect) + +* Effects **only run on the client.** They don't run during server rendering. + +--- + +## Usage {/*usage*/} + +### Connecting to an external system {/*connecting-to-an-external-system*/} + +Sometimes, your component might need to stay connected to the network, some browser API, or a third-party library, while it is displayed on the page. Such systems aren't controlled by React, so they are called *external.* + +To [connect your component to some external system,](/learn/synchronizing-with-effects) call `useEffect` at the top level of your component: + +```js [[1, 8, "const connection = createConnection(serverUrl, roomId);"], [1, 9, "connection.connect();"], [2, 11, "connection.disconnect();"], [3, 13, "[serverUrl, roomId]"]] +import { useEffect } from 'react'; +import { createConnection } from './chat.js'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [serverUrl, roomId]); + // ... +} +``` + +You need to pass two arguments to `useEffect`: + +1. A *setup function* with <CodeStep step={1}>setup code</CodeStep> that connects to that system. + - It should return a *cleanup function* with <CodeStep step={2}>cleanup code</CodeStep> that disconnects from that system. +2. A <CodeStep step={3}>list of dependencies</CodeStep> including every value from your component used inside of those functions. + +**React calls your setup and cleanup functions whenever it's necessary, which may happen multiple times:** + +1. Your <CodeStep step={1}>setup code</CodeStep> runs when your component is added to the page *(mounts)*. +2. After every re-render of your component where the <CodeStep step={3}>dependencies</CodeStep> have changed: + - First, your <CodeStep step={2}>cleanup code</CodeStep> runs with the old props and state. + - Then, your <CodeStep step={1}>setup code</CodeStep> runs with the new props and state. +3. Your <CodeStep step={2}>cleanup code</CodeStep> runs one final time after your component is removed from the page *(unmounts).* + +**Let's illustrate this sequence for the example above.** + +When the `ChatRoom` component above gets added to the page, it will connect to the chat room with the initial `serverUrl` and `roomId`. If either `serverUrl` or `roomId` change as a result of a re-render (say, if the user picks a different chat room in a dropdown), your Effect will *disconnect from the previous room, and connect to the next one.* When the `ChatRoom` component is finally removed from the page, your Effect will disconnect one last time. + +**To [help you find bugs,](/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed) in development React runs <CodeStep step={1}>setup</CodeStep> and <CodeStep step={2}>cleanup</CodeStep> one extra time before the actual <CodeStep step={1}>setup</CodeStep>.** This is a stress-test that verifies your Effect's logic is implemented correctly. If this causes visible issues, your cleanup function is missing some logic. The cleanup function should stop or undo whatever the setup function was doing. The rule of thumb is that the user shouldn't be able to distinguish between the setup being called once (as in production) and a *setup* → *cleanup* → *setup* sequence (as in development). [See common solutions.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) + +**Try to [write every Effect as an independent process](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) and [only think about a single setup/cleanup cycle at a time.](/learn/lifecycle-of-reactive-effects#thinking-from-the-effects-perspective)** It shouldn't matter whether your component is mounting, updating, or unmounting. When your cleanup logic correctly "mirrors" the setup logic, your Effect will be resilient to running setup and cleanup as often as needed. + +<Note> + +An Effect lets you [keep your component synchronized](/learn/synchronizing-with-effects) with some external system (like a chat service). Here, *external system* means any piece of code that's not controlled by React, such as: + +* A timer managed with <CodeStep step={1}>[`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)</CodeStep> and <CodeStep step={2}>[`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)</CodeStep>. +* An event subscription using <CodeStep step={1}>[`window.addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)</CodeStep> and <CodeStep step={2}>[`window.removeEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener)</CodeStep>. +* A third-party animation library with an API like <CodeStep step={1}>`animation.start()`</CodeStep> and <CodeStep step={2}>`animation.reset()`</CodeStep>. + +**If you're not connecting to any external system, [you probably don't need an Effect.](/learn/you-might-not-need-an-effect)** + +</Note> + +<Recipes titleText="Examples of connecting to an external system" titleId="examples-connecting"> + +#### Connecting to a chat server {/*connecting-to-a-chat-server*/} + +In this example, the `ChatRoom` component uses an Effect to stay connected to an external system defined in `chat.js`. Press "Open chat" to make the `ChatRoom` component appear. This sandbox runs in development mode, so there is an extra connect-and-disconnect cycle, as [explained here.](/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed) Try changing the `roomId` and `serverUrl` using the dropdown and the input, and see how the Effect re-connects to the chat. Press "Close chat" to see the Effect disconnect one last time. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId, serverUrl]); + + return ( + <> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +<Solution /> + +#### Listening to a global browser event {/*listening-to-a-global-browser-event*/} + +In this example, the external system is the browser DOM itself. Normally, you'd specify event listeners with JSX, but you can't listen to the global [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) object this way. An Effect lets you connect to the `window` object and listen to its events. Listening to the `pointermove` event lets you track the cursor (or finger) position and update the red dot to move with it. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + + useEffect(() => { + function handleMove(e) { + setPosition({ x: e.clientX, y: e.clientY }); + } + window.addEventListener('pointermove', handleMove); + return () => { + window.removeEventListener('pointermove', handleMove); + }; + }, []); + + return ( + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + ); +} +``` + +```css +body { + min-height: 300px; +} +``` + +</Sandpack> + +<Solution /> + +#### Triggering an animation {/*triggering-an-animation*/} + +In this example, the external system is the animation library in `animation.js`. It provides a JavaScript class called `FadeInAnimation` that takes a DOM node as an argument and exposes `start()` and `stop()` methods to control the animation. This component [uses a ref](/learn/manipulating-the-dom-with-refs) to access the underlying DOM node. The Effect reads the DOM node from the ref and automatically starts the animation for that node when the component appears. + +<Sandpack> + +```js +import { useState, useEffect, useRef } from 'react'; +import { FadeInAnimation } from './animation.js'; + +function Welcome() { + const ref = useRef(null); + + useEffect(() => { + const animation = new FadeInAnimation(ref.current); + animation.start(1000); + return () => { + animation.stop(); + }; + }, []); + + return ( + <h1 + ref={ref} + style={{ + opacity: 0, + color: 'white', + padding: 50, + textAlign: 'center', + fontSize: 50, + backgroundImage: 'radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%)' + }} + > + Welcome + </h1> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Remove' : 'Show'} + </button> + <hr /> + {show && <Welcome />} + </> + ); +} +``` + +```js animation.js +export class FadeInAnimation { + constructor(node) { + this.node = node; + } + start(duration) { + this.duration = duration; + if (this.duration === 0) { + // Jump to end immediately + this.onProgress(1); + } else { + this.onProgress(0); + // Start animating + this.startTime = performance.now(); + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + } + onFrame() { + const timePassed = performance.now() - this.startTime; + const progress = Math.min(timePassed / this.duration, 1); + this.onProgress(progress); + if (progress < 1) { + // We still have more frames to paint + this.frameId = requestAnimationFrame(() => this.onFrame()); + } + } + onProgress(progress) { + this.node.style.opacity = progress; + } + stop() { + cancelAnimationFrame(this.frameId); + this.startTime = null; + this.frameId = null; + this.duration = 0; + } +} +``` + +```css +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } +``` + +</Sandpack> + +<Solution /> + +#### Controlling a modal dialog {/*controlling-a-modal-dialog*/} + +In this example, the external system is the browser DOM. The `ModalDialog` component renders a [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) element. It uses an Effect to synchronize the `isOpen` prop to the [`showModal()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) and [`close()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close) method calls. + +<Sandpack> + +```js +import { useState } from 'react'; +import ModalDialog from './ModalDialog.js'; + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(true)}> + Open dialog + </button> + <ModalDialog isOpen={show}> + Hello there! + <br /> + <button onClick={() => { + setShow(false); + }}>Close</button> + </ModalDialog> + </> + ); +} +``` + +```js ModalDialog.js active +import { useEffect, useRef } from 'react'; + +export default function ModalDialog({ isOpen, children }) { + const ref = useRef(); + + useEffect(() => { + if (!isOpen) { + return; + } + const dialog = ref.current; + dialog.showModal(); + return () => { + dialog.close(); + }; + }, [isOpen]); + + return <dialog ref={ref}>{children}</dialog>; +} +``` + +```css +body { + min-height: 300px; +} +``` + +</Sandpack> + +<Solution /> + +#### Tracking element visibility {/*tracking-element-visibility*/} + +In this example, the external system is again the browser DOM. The `App` component displays a long list, then a `Box` component, and then another long list. Scroll the list down. Notice that when the `Box` component appears in the viewport, the background color changes to black. To implement this, the `Box` component uses an Effect to manage an [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). This browser API notifies you when the DOM element is visible in the viewport. + +<Sandpack> + +```js +import Box from './Box.js'; + +export default function App() { + return ( + <> + <LongSection /> + <Box /> + <LongSection /> + <Box /> + <LongSection /> + </> + ); +} + +function LongSection() { + const items = []; + for (let i = 0; i < 50; i++) { + items.push(<li key={i}>Item #{i} (keep scrolling)</li>); + } + return <ul>{items}</ul> +} +``` + +```js Box.js active +import { useRef, useEffect } from 'react'; + +export default function Box() { + const ref = useRef(null); + + useEffect(() => { + const div = ref.current; + const observer = new IntersectionObserver(entries => { + const entry = entries[0]; + if (entry.isIntersecting) { + document.body.style.backgroundColor = 'black'; + document.body.style.color = 'white'; + } else { + document.body.style.backgroundColor = 'white'; + document.body.style.color = 'black'; + } + }); + observer.observe(div, { + threshold: 1.0 + }); + return () => { + observer.disconnect(); + } + }, []); + + return ( + <div ref={ref} style={{ + margin: 20, + height: 100, + width: 100, + border: '2px solid black', + backgroundColor: 'blue' + }} /> + ); +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Wrapping Effects in custom Hooks {/*wrapping-effects-in-custom-hooks*/} + +Effects are an ["escape hatch":](/learn/escape-hatches) you use them when you need to "step outside React" and when there is no better built-in solution for your use case. If you find yourself often needing to manually write Effects, it's usually a sign that you need to extract some [custom Hooks](/learn/reusing-logic-with-custom-hooks) for common behaviors that your components rely on. + +For example, this `useChatRoom` custom Hook "hides" the logic of your Effect behind a more declarative API: + +```js {1,11} +function useChatRoom({ serverUrl, roomId }) { + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId, serverUrl]); +} +``` + +Then you can use it from any component like this: + +```js {4-7} +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl + }); + // ... +```` + +There are also many excellent custom Hooks for every purpose available in the React ecosystem. + +[Learn more about wrapping Effects in custom Hooks.](/learn/reusing-logic-with-custom-hooks) + +<Recipes titleText="Examples of wrapping Effects in custom Hooks" titleId="examples-custom-hooks"> + +#### Custom `useChatRoom` Hook {/*custom-usechatroom-hook*/} + +This example is identical to one of the [earlier examples,](#examples-connecting) but the logic is extracted to a custom Hook. + +<Sandpack> + +```js +import { useState } from 'react'; +import { useChatRoom } from './useChatRoom.js'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useChatRoom({ + roomId: roomId, + serverUrl: serverUrl + }); + + return ( + <> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <h1>Welcome to the {roomId} room!</h1> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + const [show, setShow] = useState(false); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom roomId={roomId} />} + </> + ); +} +``` + +```js useChatRoom.js +import { useEffect } from 'react'; +import { createConnection } from './chat.js'; + +export function useChatRoom({ serverUrl, roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [roomId, serverUrl]); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +<Solution /> + +#### Custom `useWindowListener` Hook {/*custom-usewindowlistener-hook*/} + +This example is identical to one of the [earlier examples,](#examples-connecting) but the logic is extracted to a custom Hook. + +<Sandpack> + +```js +import { useState } from 'react'; +import { useWindowListener } from './useWindowListener.js'; + +export default function App() { + const [position, setPosition] = useState({ x: 0, y: 0 }); + + useWindowListener('pointermove', (e) => { + setPosition({ x: e.clientX, y: e.clientY }); + }); + + return ( + <div style={{ + position: 'absolute', + backgroundColor: 'pink', + borderRadius: '50%', + opacity: 0.6, + transform: `translate(${position.x}px, ${position.y}px)`, + pointerEvents: 'none', + left: -20, + top: -20, + width: 40, + height: 40, + }} /> + ); +} +``` + +```js useWindowListener.js +import { useState, useEffect } from 'react'; + +export function useWindowListener(eventType, listener) { + useEffect(() => { + window.addEventListener(eventType, listener); + return () => { + window.removeEventListener(eventType, listener); + }; + }, [eventType, listener]); +} +``` + +```css +body { + min-height: 300px; +} +``` + +</Sandpack> + +<Solution /> + +#### Custom `useIntersectionObserver` Hook {/*custom-useintersectionobserver-hook*/} + +This example is identical to one of the [earlier examples,](#examples-connecting) but the logic is partially extracted to a custom Hook. + +<Sandpack> + +```js +import Box from './Box.js'; + +export default function App() { + return ( + <> + <LongSection /> + <Box /> + <LongSection /> + <Box /> + <LongSection /> + </> + ); +} + +function LongSection() { + const items = []; + for (let i = 0; i < 50; i++) { + items.push(<li key={i}>Item #{i} (keep scrolling)</li>); + } + return <ul>{items}</ul> +} +``` + +```js Box.js active +import { useRef, useEffect } from 'react'; +import { useIntersectionObserver } from './useIntersectionObserver.js'; + +export default function Box() { + const ref = useRef(null); + const isIntersecting = useIntersectionObserver(ref); + + useEffect(() => { + if (isIntersecting) { + document.body.style.backgroundColor = 'black'; + document.body.style.color = 'white'; + } else { + document.body.style.backgroundColor = 'white'; + document.body.style.color = 'black'; + } + }, [isIntersecting]); + + return ( + <div ref={ref} style={{ + margin: 20, + height: 100, + width: 100, + border: '2px solid black', + backgroundColor: 'blue' + }} /> + ); +} +``` + +```js useIntersectionObserver.js +import { useState, useEffect } from 'react'; + +export function useIntersectionObserver(ref) { + const [isIntersecting, setIsIntersecting] = useState(false); + + useEffect(() => { + const div = ref.current; + const observer = new IntersectionObserver(entries => { + const entry = entries[0]; + setIsIntersecting(entry.isIntersecting); + }); + observer.observe(div, { + threshold: 1.0 + }); + return () => { + observer.disconnect(); + } + }, [ref]); + + return isIntersecting; +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Controlling a non-React widget {/*controlling-a-non-react-widget*/} + +Sometimes, you want to keep an external system synchronized to some prop or state of your component. + +For example, if you have a third-party map widget or a video player component written without React, you can use an Effect to call methods on it that make its state match the current state of your React component. This Effect creates an instance of a `MapWidget` class defined in `map-widget.js`. When you change the `zoomLevel` prop of the `Map` component, the Effect calls the `setZoom()` on the class instance to keep it synchronized: + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "leaflet": "1.9.1", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "remarkable": "2.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { useState } from 'react'; +import Map from './Map.js'; + +export default function App() { + const [zoomLevel, setZoomLevel] = useState(0); + return ( + <> + Zoom level: {zoomLevel}x + <button onClick={() => setZoomLevel(zoomLevel + 1)}>+</button> + <button onClick={() => setZoomLevel(zoomLevel - 1)}>-</button> + <hr /> + <Map zoomLevel={zoomLevel} /> + </> + ); +} +``` + +```js Map.js active +import { useRef, useEffect } from 'react'; +import { MapWidget } from './map-widget.js'; + +export default function Map({ zoomLevel }) { + const containerRef = useRef(null); + const mapRef = useRef(null); + + useEffect(() => { + if (mapRef.current === null) { + mapRef.current = new MapWidget(containerRef.current); + } + + const map = mapRef.current; + map.setZoom(zoomLevel); + }, [zoomLevel]); + + return ( + <div + style={{ width: 200, height: 200 }} + ref={containerRef} + /> + ); +} +``` + +```js map-widget.js +import 'leaflet/dist/leaflet.css'; +import * as L from 'leaflet'; + +export class MapWidget { + constructor(domNode) { + this.map = L.map(domNode, { + zoomControl: false, + doubleClickZoom: false, + boxZoom: false, + keyboard: false, + scrollWheelZoom: false, + zoomAnimation: false, + touchZoom: false, + zoomSnap: 0.1 + }); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(this.map); + this.map.setView([0, 0], 0); + } + setZoom(level) { + this.map.setZoom(level); + } +} +``` + +```css +button { margin: 5px; } +``` + +</Sandpack> + +In this example, a cleanup function is not needed because the `MapWidget` class manages only the DOM node that was passed to it. After the `Map` React component is removed from the tree, both the DOM node and the `MapWidget` class instance will be automatically garbage-collected by the browser JavaScript engine. + +--- + +### Fetching data with Effects {/*fetching-data-with-effects*/} + +You can use an Effect to fetch data for your component. Note that [if you use a framework,](/learn/start-a-new-react-project#building-with-a-full-featured-framework) using your framework's data fetching mechanism will be a lot more efficient than writing Effects manually. + +If you want to fetch data from an Effect manually, your code might look like this: + +```js +import { useState, useEffect } from 'react'; +import { fetchBio } from './api.js'; + +export default function Page() { + const [person, setPerson] = useState('Alice'); + const [bio, setBio] = useState(null); + + useEffect(() => { + let ignore = false; + setBio(null); + fetchBio(person).then(result => { + if (!ignore) { + setBio(result); + } + }); + return () => { + ignore = true; + }; + }, [person]); + + // ... +``` + +Note the `ignore` variable which is initialized to `false`, and is set to `true` during cleanup. This ensures [your code doesn't suffer from "race conditions":](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect) network responses may arrive in a different order than you sent them. + +<Sandpack> + +```js App.js +import { useState, useEffect } from 'react'; +import { fetchBio } from './api.js'; + +export default function Page() { + const [person, setPerson] = useState('Alice'); + const [bio, setBio] = useState(null); + useEffect(() => { + let ignore = false; + setBio(null); + fetchBio(person).then(result => { + if (!ignore) { + setBio(result); + } + }); + return () => { + ignore = true; + } + }, [person]); + + return ( + <> + <select value={person} onChange={e => { + setPerson(e.target.value); + }}> + <option value="Alice">Alice</option> + <option value="Bob">Bob</option> + <option value="Taylor">Taylor</option> + </select> + <hr /> + <p><i>{bio ?? 'Loading...'}</i></p> + </> + ); +} +``` + +```js api.js hidden +export async function fetchBio(person) { + const delay = person === 'Bob' ? 2000 : 200; + return new Promise(resolve => { + setTimeout(() => { + resolve('This is ' + person + '’s bio.'); + }, delay); + }) +} +``` + +</Sandpack> + +You can also rewrite using the [`async` / `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) syntax, but you still need to provide a cleanup function: + +<Sandpack> + +```js App.js +import { useState, useEffect } from 'react'; +import { fetchBio } from './api.js'; + +export default function Page() { + const [person, setPerson] = useState('Alice'); + const [bio, setBio] = useState(null); + useEffect(() => { + async function startFetching() { + setBio(null); + const result = await fetchBio(person); + if (!ignore) { + setBio(result); + } + } + + let ignore = false; + startFetching(); + return () => { + ignore = true; + } + }, [person]); + + return ( + <> + <select value={person} onChange={e => { + setPerson(e.target.value); + }}> + <option value="Alice">Alice</option> + <option value="Bob">Bob</option> + <option value="Taylor">Taylor</option> + </select> + <hr /> + <p><i>{bio ?? 'Loading...'}</i></p> + </> + ); +} +``` + +```js api.js hidden +export async function fetchBio(person) { + const delay = person === 'Bob' ? 2000 : 200; + return new Promise(resolve => { + setTimeout(() => { + resolve('This is ' + person + '’s bio.'); + }, delay); + }) +} +``` + +</Sandpack> + +Writing data fetching directly in Effects gets repetitive and makes it difficult to add optimizations like caching and server rendering later. [It's easier to use a custom Hook--either your own or maintained by the community.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) + +<DeepDive> + +#### What are good alternatives to data fetching in Effects? {/*what-are-good-alternatives-to-data-fetching-in-effects*/} + +Writing `fetch` calls inside Effects is a [popular way to fetch data](https://www.robinwieruch.de/react-hooks-fetch-data/), especially in fully client-side apps. This is, however, a very manual approach and it has significant downsides: + +- **Effects don't run on the server.** This means that the initial server-rendered HTML will only include a loading state with no data. The client computer will have to download all JavaScript and render your app only to discover that now it needs to load the data. This is not very efficient. +- **Fetching directly in Effects makes it easy to create "network waterfalls".** You render the parent component, it fetches some data, renders the child components, and then they start fetching their data. If the network is not very fast, this is significantly slower than fetching all data in parallel. +- **Fetching directly in Effects usually means you don't preload or cache data.** For example, if the component unmounts and then mounts again, it would have to fetch the data again. +- **It's not very ergonomic.** There's quite a bit of boilerplate code involved when writing `fetch` calls in a way that doesn't suffer from bugs like [race conditions.](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect) + +This list of downsides is not specific to React. It applies to fetching data on mount with any library. Like with routing, data fetching is not trivial to do well, so we recommend the following approaches: + +- **If you use a [framework](/learn/start-a-new-react-project#building-with-a-full-featured-framework), use its built-in data fetching mechanism.** Modern React frameworks have integrated data fetching mechanisms that are efficient and don't suffer from the above pitfalls. +- **Otherwise, consider using or building a client-side cache.** Popular open source solutions include [React Query](https://react-query.tanstack.com/), [useSWR](https://swr.vercel.app/), and [React Router 6.4+.](https://beta.reactrouter.com/en/main/start/overview) You can build your own solution too, in which case you would use Effects under the hood but also add logic for deduplicating requests, caching responses, and avoiding network waterfalls (by preloading data or hoisting data requirements to routes). + +You can continue fetching data directly in Effects if neither of these approaches suit you. + +</DeepDive> + +--- + +### Specifying reactive dependencies {/*specifying-reactive-dependencies*/} + +**Notice that you can't "choose" the dependencies of your Effect.** Every <CodeStep step={2}>reactive value</CodeStep> used by your Effect's code must be declared as a dependency. Your Effect's dependency list is determined by the surrounding code: + +```js [[2, 1, "roomId"], [2, 2, "serverUrl"], [2, 5, "serverUrl"], [2, 5, "roomId"], [2, 8, "serverUrl"], [2, 8, "roomId"]] +function ChatRoom({ roomId }) { // This is a reactive value + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // This is a reactive value too + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); // This Effect reads these reactive values + connection.connect(); + return () => connection.disconnect(); + }, [serverUrl, roomId]); // ✅ So you must specify them as dependencies of your Effect + // ... +} +``` + +If either `serverUrl` or `roomId` change, your Effect will reconnect to the chat using the new values. + +**[Reactive values](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) include props and all variables and functions declared directly inside of your component.** Since `roomId` and `serverUrl` are reactive values, you can't remove them from the dependency list. If you try to omit them and [your linter is correctly configured for React,](/learn/editor-setup#linting) the linter will flag this as a mistake that you need to fix: + +```js {8} +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); // 🔴 React Hook useEffect has missing dependencies: 'roomId' and 'serverUrl' + // ... +} +``` + +**To remove a dependency, you need to ["prove" to the linter that it *doesn't need* to be a dependency.](/learn/removing-effect-dependencies#removing-unnecessary-dependencies)** For example, you can move `serverUrl` out of your component to prove that it's not reactive and won't change on re-renders: + +```js {1,8} +const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore + +function ChatRoom({ roomId }) { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); // ✅ All dependencies declared + // ... +} +``` + +Now that `serverUrl` is not a reactive value (and can't change on a re-render), it doesn't need to be a dependency. **If your Effect's code doesn't use any reactive values, its dependency list should be empty (`[]`):** + +```js {1,2,9} +const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore +const roomId = 'music'; // Not a reactive value anymore + +function ChatRoom() { + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); // ✅ All dependencies declared + // ... +} +``` + +[An Effect with empty dependencies](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) doesn't re-run when any of your component's props or state change. + +<Pitfall> + +If you have an existing codebase, you might have some Effects that suppress the linter like this: + +```js {3-4} +useEffect(() => { + // ... + // 🔴 Avoid suppressing the linter like this: + // eslint-ignore-next-line react-hooks/exhaustive-deps +}, []); +``` + +**When dependencies don't match the code, there is a high risk of introducing bugs.** By suppressing the linter, you "lie" to React about the values your Effect depends on. [Instead, prove they're unnecessary.](/learn/removing-effect-dependencies#removing-unnecessary-dependencies) + +</Pitfall> + +<Recipes titleText="Examples of passing reactive dependencies" titleId="examples-dependencies"> + +#### Passing a dependency array {/*passing-a-dependency-array*/} + +If you specify the dependencies, your Effect runs **after the initial render _and_ after re-renders with changed dependencies.** + +```js {3} +useEffect(() => { + // ... +}, [a, b]); // Runs again if a or b are different +``` + +In the below example, `serverUrl` and `roomId` are [reactive values,](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) so they both must be specified as dependencies. As a result, selecting a different room in the dropdown or editing the server URL input causes the chat to re-connect. However, since `message` isn't used in the Effect (and so it isn't a dependency), editing the message doesn't re-connect to the chat. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [serverUrl, roomId]); + + return ( + <> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <h1>Welcome to the {roomId} room!</h1> + <label> + Your message:{' '} + <input value={message} onChange={e => setMessage(e.target.value)} /> + </label> + </> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + </label> + {show && <hr />} + {show && <ChatRoom />} + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { margin-bottom: 10px; } +button { margin-left: 5px; } +``` + +</Sandpack> + +<Solution /> + +#### Passing an empty dependency array {/*passing-an-empty-dependency-array*/} + +If your Effect truly doesn't use any reactive values, it will only run **after the initial render.** + +```js {3} +useEffect(() => { + // ... +}, []); // Does not run again (except once in development) +``` + +**Even with empty dependencies, setup and cleanup will [run one extra time in development](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) to help you find bugs.** + + +In this example, both `serverUrl` and `roomId` are hardcoded. Since they're declared outside the component, they are not reactive values, and so they aren't dependencies. The dependency list is empty, so the Effect doesn't re-run on re-renders. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; +const roomId = 'music'; + +function ChatRoom() { + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => connection.disconnect(); + }, []); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <label> + Your message:{' '} + <input value={message} onChange={e => setMessage(e.target.value)} /> + </label> + </> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + return ( + <> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + {show && <hr />} + {show && <ChatRoom />} + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +</Sandpack> + +<Solution /> + + +#### Passing no dependency array at all {/*passing-no-dependency-array-at-all*/} + +If you pass no dependency array at all, your Effect runs **after every single render (and re-render)** of your component. + +```js {3} +useEffect(() => { + // ... +}); // Always runs again +``` + +In this example, the Effect re-runs when you change `serverUrl` and `roomId`, which is sensible. However, it *also* re-runs when you change the `message`, which is probably undesirable. This is why usually you'll specify the dependency array. + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +function ChatRoom({ roomId }) { + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); + const [message, setMessage] = useState(''); + + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }); // No dependency array at all + + return ( + <> + <label> + Server URL:{' '} + <input + value={serverUrl} + onChange={e => setServerUrl(e.target.value)} + /> + </label> + <h1>Welcome to the {roomId} room!</h1> + <label> + Your message:{' '} + <input value={message} onChange={e => setMessage(e.target.value)} /> + </label> + </> + ); +} + +export default function App() { + const [show, setShow] = useState(false); + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + <button onClick={() => setShow(!show)}> + {show ? 'Close chat' : 'Open chat'} + </button> + </label> + {show && <hr />} + {show && <ChatRoom />} + </> + ); +} +``` + +```js chat.js +export function createConnection(serverUrl, roomId) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { margin-bottom: 10px; } +button { margin-left: 5px; } +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Updating state based on previous state from an Effect {/*updating-state-based-on-previous-state-from-an-effect*/} + +When you want to update state based on previous state from an Effect, you might run into a problem: + +```js {6,9} +function Counter() { + const [count, setCount] = useState(0); + + useEffect(() => { + const intervalId = setInterval(() => { + setCount(count + 1); // You want to increment the counter every second... + }, 1000) + return () => clearInterval(intervalId); + }, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval. + // ... +} +``` + +Since `count` is a reactive value, it must be specified in the list of dependencies. However, that causes the Effect to cleanup and setup again every time the `count` changes. This is not ideal. + +To fix this, [pass the `c => c + 1` state updater](/reference/react/useState#updating-state-based-on-the-previous-state) to `setCount`: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; + +export default function Counter() { + const [count, setCount] = useState(0); + + useEffect(() => { + const intervalId = setInterval(() => { + setCount(c => c + 1); // ✅ Pass a state updater + }, 1000); + return () => clearInterval(intervalId); + }, []); // ✅ Now count is not a dependency + + return <h1>{count}</h1>; +} +``` + +```css +label { + display: block; + margin-top: 20px; + margin-bottom: 20px; +} + +body { + min-height: 150px; +} +``` + +</Sandpack> + +Now that you're passing `c => c + 1` instead of `count + 1`, [your Effect no longer needs to depend on `count`.](/learn/removing-effect-dependencies#are-you-reading-some-state-to-calculate-the-next-state) As a result of this fix, it won't need to cleanup and setup the interval again every time the `count` changes. + +--- + + +### Removing unnecessary object dependencies {/*removing-unnecessary-object-dependencies*/} + +If your Effect depends on an object or a function created during rendering, it might run more often than needed. For example, this Effect re-connects after every render because the `options` object is [different for every render:](/learn/removing-effect-dependencies#does-some-reactive-value-change-unintentionally) + +```js {6-9,12,15} +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + const options = { // 🚩 This object is created from scratch on every re-render + serverUrl: serverUrl, + roomId: roomId + }; + + useEffect(() => { + const connection = createConnection(options); // It's used inside the Effect + connection.connect(); + return () => connection.disconnect(); + }, [options]); // 🚩 As a result, these dependencies are always different on a re-render + // ... +``` + +Avoid using an object created during rendering as a dependency. Instead, create the object inside the Effect: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + const options = { + serverUrl: serverUrl, + roomId: roomId + }; + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input value={message} onChange={e => setMessage(e.target.value)} /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Now that you create the `options` object inside the Effect, the Effect itself only depends on the `roomId` string. + +With this fix, typing into the input doesn't reconnect the chat. Unlike an object which gets re-created, a string like `roomId` doesn't change unless you set it to another value. [Read more about removing dependencies.](/learn/removing-effect-dependencies) + +--- + +### Removing unnecessary function dependencies {/*removing-unnecessary-function-dependencies*/} + +If your Effect depends on an object or a function created during rendering, it might run more often than needed. For example, this Effect re-connects after every render because the `createOptions` function is [different for every render:](/learn/removing-effect-dependencies#does-some-reactive-value-change-unintentionally) + +```js {4-9,12,16} +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + function createOptions() { // 🚩 This function is created from scratch on every re-render + return { + serverUrl: serverUrl, + roomId: roomId + }; + } + + useEffect(() => { + const options = createOptions(); // It's used inside the Effect + const connection = createConnection(); + connection.connect(); + return () => connection.disconnect(); + }, [createOptions]); // 🚩 As a result, these dependencies are always different on a re-render + // ... +``` + +By itself, creating a function from scratch on every re-render is not a problem. You don't need to optimize that. However, if you use it as a dependency of your Effect, it will cause your Effect to re-run after every re-render. + +Avoid using a function created during rendering as a dependency. Instead, declare it inside the Effect: + +<Sandpack> + +```js +import { useState, useEffect } from 'react'; +import { createConnection } from './chat.js'; + +const serverUrl = 'https://localhost:1234'; + +function ChatRoom({ roomId }) { + const [message, setMessage] = useState(''); + + useEffect(() => { + function createOptions() { + return { + serverUrl: serverUrl, + roomId: roomId + }; + } + + const options = createOptions(); + const connection = createConnection(options); + connection.connect(); + return () => connection.disconnect(); + }, [roomId]); + + return ( + <> + <h1>Welcome to the {roomId} room!</h1> + <input value={message} onChange={e => setMessage(e.target.value)} /> + </> + ); +} + +export default function App() { + const [roomId, setRoomId] = useState('general'); + return ( + <> + <label> + Choose the chat room:{' '} + <select + value={roomId} + onChange={e => setRoomId(e.target.value)} + > + <option value="general">general</option> + <option value="travel">travel</option> + <option value="music">music</option> + </select> + </label> + <hr /> + <ChatRoom roomId={roomId} /> + </> + ); +} +``` + +```js chat.js +export function createConnection({ serverUrl, roomId }) { + // A real implementation would actually connect to the server + return { + connect() { + console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); + }, + disconnect() { + console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl); + } + }; +} +``` + +```css +input { display: block; margin-bottom: 20px; } +button { margin-left: 10px; } +``` + +</Sandpack> + +Now that you define the `createOptions` function inside the Effect, the Effect itself only depends on the `roomId` string. With this fix, typing into the input doesn't reconnect the chat. Unlike a function which gets re-created, a string like `roomId` doesn't change unless you set it to another value. [Read more about removing dependencies.](/learn/removing-effect-dependencies) + +--- + +### Reading the latest props and state from an Effect {/*reading-the-latest-props-and-state-from-an-effect*/} + +<Wip> + +This section describes an **experimental API that has not yet been added to React,** so you can't use it yet. + +</Wip> + +By default, when you read a reactive value from an Effect, you have to add it as a dependency. This ensures that your Effect "reacts" to every change of that value. For most dependencies, that's the behavior you want. + +**However, sometimes you'll want to read the *latest* props and state from an Effect without "reacting" to them.** For example, imagine you want to log the number of the items in the shopping cart for every page visit: + +```js {3} +function Page({ url, shoppingCart }) { + useEffect(() => { + logVisit(url, shoppingCart.length); + }, [url, shoppingCart]); // ✅ All dependencies declared + // ... +} +``` + +**What if you want to log a new page visit after every `url` change, but *not* if only the `shoppingCart` changes?** You can't exclude `shoppingCart` from dependencies without breaking the [reactivity rules.](#specifying-reactive-dependencies) However, you can express that you *don't want* a piece of code to "react" to changes even though it is called from inside an Effect. [Declare an *Effect Event*](/learn/separating-events-from-effects#declaring-an-effect-event) with the [`useEffectEvent`](/reference/react/useEffectEvent) Hook, and move the code that reads `shoppingCart` inside of it: + +```js {2-4,7,8} +function Page({ url, shoppingCart }) { + const onVisit = useEffectEvent(visitedUrl => { + logVisit(visitedUrl, shoppingCart.length) + }); + + useEffect(() => { + onVisit(url); + }, [url]); // ✅ All dependencies declared + // ... +} +``` + +**Effect Events are not reactive and must always be omitted from dependencies of your Effect.** This is what lets you put non-reactive code (where you can read the latest value of some props and state) inside of them. For example, by reading `shoppingCart` inside of `onVisit`, you ensure that `shoppingCart` won't re-run your Effect. In the future, the linter will support `useEffectEvent` and check that you omit Effect Events from dependencies. + +[Read more about how Effect Events let you separate reactive and non-reactive code.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) + + +--- + +### Displaying different content on the server and the client {/*displaying-different-content-on-the-server-and-the-client*/} + +If your app uses server rendering (either [directly](/reference/react-dom/server) or via a [framework](/learn/start-a-new-react-project#building-with-a-full-featured-framework)), your component will render in two different environments. On the server, it will render to produce the initial HTML. On the client, React will run the rendering code again so that it can attach your event handlers to that HTML. This is why, for [hydration](/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html) to work, your initial render output must be identical on the client and the server. + +In rare cases, you might need to display different content on the client. For example, if your app reads some data from [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), it can't possibly do that on the server. Here is how you would typically implement this: + +```js +function MyComponent() { + const [didMount, setDidMount] = useState(false); + + useEffect(() => { + setDidMount(true); + }, []); + + if (didMount) { + // ... return client-only JSX ... + } else { + // ... return initial JSX ... + } +} +``` + +While the app is loading, the user will see the initial render output. Then, when it's loaded and hydrated, your Effect will run and set `didMount` to `true`, triggering a re-render. This will switch to the client-only render output. Note that Effects don't run on the server, so this is why `didMount` was `false` during the initial server render. + +Use this pattern sparingly. Keep in mind that users with a slow connection will see the initial content for quite a bit of time--potentially, many seconds--so you don't want to make jarring changes to your component's appearance. In many cases, you can avoid the need for this by conditionally showing different things with CSS. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My Effect runs twice when the component mounts {/*my-effect-runs-twice-when-the-component-mounts*/} + +When Strict Mode is on, in development, React runs setup and cleanup one extra time before the actual setup. + +This is a stress-test that verifies your Effect’s logic is implemented correctly. If this causes visible issues, your cleanup function is missing some logic. The cleanup function should stop or undo whatever the setup function was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the setup being called once (as in production) and a setup → cleanup → setup sequence (as in development). + +Read more about [how this helps find bugs](/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed) and [how to fix your logic.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) + +--- + +### My Effect runs after every re-render {/*my-effect-runs-after-every-re-render*/} + +First, check that you haven't forgotten to specify the dependency array: + +```js {3} +useEffect(() => { + // ... +}); // 🚩 No dependency array: re-runs after every render! +``` + +If you've specified the dependency array but your Effect still re-runs in a loop, it's because one of your dependencies is different on every re-render. + +You can debug this problem by manually logging your dependencies to the console: + +```js {5} + useEffect(() => { + // .. + }, [serverUrl, roomId]); + + console.log([serverUrl, roomId]); +``` + +You can then right-click on the arrays from different re-renders in the console and select "Store as a global variable" for both of them. Assuming the first one got saved as `temp1` and the second one got saved as `temp2`, you can then use the browser console to check whether each dependency in both arrays is the same: + +```js +Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays? +Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays? +Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ... +``` + +When you find the dependency that is different on every re-render, you can usually fix it in one of these ways: + +- [Updating state based on previous state from an Effect](#updating-state-based-on-previous-state-from-an-effect) +- [Removing unnecessary object dependencies](#removing-unnecessary-object-dependencies) +- [Removing unnecessary function dependencies](#removing-unnecessary-function-dependencies) +- [Reading the latest props and state from an Effect](#reading-the-latest-props-and-state-from-an-effect) + +As a last resort (if these methods didn't help), wrap its creation with [`useMemo`](/reference/react/useMemo#memoizing-a-dependency-of-another-hook) or [`useCallback`](/reference/react/useCallback#preventing-an-effect-from-firing-too-often) (for functions). + +--- + +### My Effect keeps re-running in an infinite cycle {/*my-effect-keeps-re-running-in-an-infinite-cycle*/} + +If your Effect runs in an infinite cycle, these two things must be true: + +- Your Effect is updating some state. +- That state leads to a re-render, which causes the Effect's dependencies to change. + +Before you start fixing the problem, ask yourself whether your Effect is connecting to some external system (like DOM, network, a third-party widget, and so on). Why does your Effect need to set state? Does it synchronize some state with that external system? Or are you trying to manage your application's data flow with it? + +If there is no external system, consider whether [removing the Effect altogether](/learn/you-might-not-need-an-effect) would simplify your logic. + +If you're genuinely synchronizing with some external system, think about why and under what conditions your Effect should update the state. Has something changed that affects your component's visual output? If you need to keep track of some data that isn't used by rendering, a [ref](/reference/react/useRef#referencing-a-value-with-a-ref) (which doesn't trigger re-renders) might be more appropriate. Verify your Effect doesn't update the state (and trigger re-renders) more than needed. + +Finally, if your Effect is updating the state at the right time, but there is still a loop, it's because that state update leads to one of your Effect's dependencies changing. [Read how to debug and resolve dependency changes.](/reference/react/useEffect#my-effect-runs-after-every-re-render) + +--- + +### My cleanup logic runs even though my component didn't unmount {/*my-cleanup-logic-runs-even-though-my-component-didnt-unmount*/} + +The cleanup function runs not only during unmount, but before every re-render with changed dependencies. Additionally, in development, React [runs setup+cleanup one extra time immediately after component mounts.](#my-effect-runs-twice-when-the-component-mounts) + +If you have cleanup code without corresponding setup code, it's usually a code smell: + +```js {2-5} +useEffect(() => { + // 🔴 Avoid: Cleanup logic without corresponding setup logic + return () => { + doSomething(); + }; +}, []); +``` + +Your cleanup logic should be "symmetrical" to the setup logic, and should stop or undo whatever setup did: + +```js {2-3,5} + useEffect(() => { + const connection = createConnection(serverUrl, roomId); + connection.connect(); + return () => { + connection.disconnect(); + }; + }, [serverUrl, roomId]); +``` + +[Learn how the Effect lifecycle is different from the component's lifecycle.](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) + +--- + +### My Effect does something visual, and I see a flicker before it runs {/*my-effect-does-something-visual-and-i-see-a-flicker-before-it-runs*/} + +If your Effect must block the browser from [painting the screen,](/learn/render-and-commit#epilogue-browser-paint) replace `useEffect` with [`useLayoutEffect`](/reference/react/useLayoutEffect). Note that **this shouldn't be needed for the vast majority of Effects.** You'll only need this if it's crucial to run your Effect before the browser paint: for example, to measure and position a tooltip before the user sees it for the first time. diff --git a/beta/src/content/reference/react/useEffectEvent.md b/beta/src/content/reference/react/useEffectEvent.md new file mode 100644 index 000000000..8541efc66 --- /dev/null +++ b/beta/src/content/reference/react/useEffectEvent.md @@ -0,0 +1,30 @@ +--- +title: useEffectEvent +--- + +<Wip> + +**This API is experimental and is not available in a stable version of React yet.** + +You can try it by upgrading React packages to the most recent experimental version: + +- `react@experimental` +- `react-dom@experimental` +- `eslint-plugin-react-hooks@experimental` + +Experimental versions of React may contain bugs. Don't use them in production. + +</Wip> + + +<Intro> + +`useEffectEvent` is a React Hook that lets you extract non-reactive logic into an [Effect Event.](/learn/separating-events-from-effects#declaring-an-effect-event) + +```js +const onSomething = useEffectEvent(callback) +``` + +</Intro> + +<InlineToc /> diff --git a/beta/src/content/reference/react/useId.md b/beta/src/content/reference/react/useId.md new file mode 100644 index 000000000..59fae34d7 --- /dev/null +++ b/beta/src/content/reference/react/useId.md @@ -0,0 +1,305 @@ +--- +title: useId +--- + +<Intro> + +`useId` is a React Hook for generating unique IDs that can be passed to accessibility attributes. + +```js +const id = useId() +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useId()` {/*useid*/} + +Call `useId` at the top level of your component to generate a unique ID: + +```js +import { useId } from 'react'; + +function PasswordField() { + const passwordHintId = useId(); + // ... +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +`useId` does not take any parameters. + +#### Returns {/*returns*/} + +`useId` returns a unique ID string associated with this particular `useId` call in this particular component. + +#### Caveats {/*caveats*/} + +* `useId` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. + +* `useId` **should not be used to generate keys** in a list. [Keys should be generated from your data.](/learn/rendering-lists#where-to-get-your-key) + +--- + +## Usage {/*usage*/} + +<Pitfall> + +**Do not call `useId` to generate keys in a list.** [Keys should be generated from your data.](/learn/rendering-lists#where-to-get-your-key) + +</Pitfall> + +### Generating unique IDs for accessibility attributes {/*generating-unique-ids-for-accessibility-attributes*/} + +Call `useId` at the top level of your component to generate a unique ID: + +```js [[1, 4, "passwordHintId"]] +import { useId } from 'react'; + +function PasswordField() { + const passwordHintId = useId(); + // ... +``` + +You can then pass the <CodeStep step={1}>generated ID</CodeStep> to different attributes: + +```js [[1, 2, "passwordHintId"], [1, 3, "passwordHintId"]] +<> + <input type="password" aria-describedby={passwordHintId} /> + <p id={passwordHintId}> +</> +``` + +**Let's walk through an example to see when this is useful.** + +[HTML accessibility attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) like [`aria-describedby`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby) let you specify that two tags are related to each other. For example, you can specify that a certain element (like an input) is described by another element (like a paragraph). + +In regular HTML, you would write it like this: + +```html {5,8} +<label> + Password: + <input + type="password" + aria-describedby="password-hint" + /> +</label> +<p id="password-hint"> + The password should contain at least 18 characters +</p> +``` + +However, hardcoding IDs like this is not a good practice in React. A component may be rendered more than once on the page--but IDs have to be unique! Instead of hardcoding an ID, you can generate a unique ID with `useId`: + +```js {4,11,14} +import { useId } from 'react'; + +function PasswordField() { + const passwordHintId = useId(); + return ( + <> + <label> + Password: + <input + type="password" + aria-describedby={passwordHintId} + /> + </label> + <p id={passwordHintId}> + The password should contain at least 18 characters + </p> + </> + ); +} +``` + +Now, even if `PasswordField` appears multiple times on the screen, the generated IDs won't clash. + +<Sandpack> + +```js +import { useId } from 'react'; + +function PasswordField() { + const passwordHintId = useId(); + return ( + <> + <label> + Password: + <input + type="password" + aria-describedby={passwordHintId} + /> + </label> + <p id={passwordHintId}> + The password should contain at least 18 characters + </p> + </> + ); +} + +export default function App() { + return ( + <> + <h2>Choose password</h2> + <PasswordField /> + <h2>Confirm password</h2> + <PasswordField /> + </> + ); +} +``` + +```css +input { margin: 5px; } +``` + +</Sandpack> + +[Watch this video](https://www.youtube.com/watch?v=0dNzNcuEuOo) to see the difference in the user experience with assistive technologies. + +<Pitfall> + +**`useId` requires an identical component tree on the server and the client** when you use [server rendering](/reference/react-dom/server). If the trees you render on the server and the client don't match exactly, the generated IDs won't match. + +</Pitfall> + +<DeepDive> + +#### Why is useId better than an incrementing counter? {/*why-is-useid-better-than-an-incrementing-counter*/} + +You might be wondering why `useId` is better than incrementing a global variable like `nextId++`. + +The primary benefit of `useId` is that React ensures that it works with [server rendering.](/reference/react-dom/server) During server rendering, your components generate HTML output. Later, on the client, [hydration](/reference/react-dom/client/hydrateRoot) attaches your event handlers to the generated HTML. For hydration to work, the client output must match the server HTML. + +This is very difficult to guarantee with an incrementing counter because the order in which the client components are hydrated may not match the order in which the server HTML was emitted. By calling `useId`, you ensure that hydration will work, and the output will match between the server and the client. + +Inside React, `useId` is generated from the "parent path" of the calling component. This is why, if the client and the server tree are the same, the "parent path" will match up regardless of rendering order. + +</DeepDive> + +--- + +### Generating IDs for several related elements {/*generating-ids-for-several-related-elements*/} + +If you need to give IDs to multiple related elements, you can call `useId` to generate a shared prefix for them: + +<Sandpack> + +```js +import { useId } from 'react'; + +export default function Form() { + const id = useId(); + return ( + <form> + <label htmlFor={id + '-firstName'}>First Name:</label> + <input id={id + '-firstName'} type="text" /> + <hr /> + <label htmlFor={id + '-lastName'}>Last Name:</label> + <input id={id + '-lastName'} type="text" /> + </form> + ); +} +``` + +```css +input { margin: 5px; } +``` + +</Sandpack> + +This lets you avoid calling `useId` for every single element that needs a unique ID. + +--- + +### Specifying a shared prefix for all generated IDs {/*specifying-a-shared-prefix-for-all-generated-ids*/} + +If you render multiple independent React applications on a single page, you may pass `identifierPrefix` as an option to your [`createRoot`](/reference/react-dom/client/createRoot#parameters) or [`hydrateRoot`](/reference/react-dom/client/hydrateRoot) calls. This ensures that the IDs generated by the two different apps never clash because every identifier generated with `useId` will start with the distinct prefix you've specified. + +<Sandpack> + +```html index.html +<!DOCTYPE html> +<html> + <head><title>My app</title></head> + <body> + <div id="root1"></div> + <div id="root2"></div> + </body> +</html> +``` + +```js +import { useId } from 'react'; + +function PasswordField() { + const passwordHintId = useId(); + console.log('Generated identifier:', passwordHintId) + return ( + <> + <label> + Password: + <input + type="password" + aria-describedby={passwordHintId} + /> + </label> + <p id={passwordHintId}> + The password should contain at least 18 characters + </p> + </> + ); +} + +export default function App() { + return ( + <> + <h2>Choose password</h2> + <PasswordField /> + </> + ); +} +``` + +```js index.js active +import { createRoot } from 'react-dom/client'; +import App from './App.js'; +import './styles.css'; + +const root1 = createRoot(document.getElementById('root1'), { + identifierPrefix: 'my-first-app-' +}); +root1.render(<App />); + +const root2 = createRoot(document.getElementById('root2'), { + identifierPrefix: 'my-second-app-' +}); +root2.render(<App />); +``` + +```css +#root1 { + border: 5px solid blue; + padding: 10px; + margin: 5px; +} + +#root2 { + border: 5px solid green; + padding: 10px; + margin: 5px; +} + +input { margin: 5px; } +``` + +</Sandpack> + diff --git a/beta/src/content/reference/react/useImperativeHandle.md b/beta/src/content/reference/react/useImperativeHandle.md new file mode 100644 index 000000000..bf09c4ef8 --- /dev/null +++ b/beta/src/content/reference/react/useImperativeHandle.md @@ -0,0 +1,288 @@ +--- +title: useImperativeHandle +--- + +<Intro> + +`useImperativeHandle` is a React Hook that lets you customize the handle exposed as a [ref.](/learn/manipulating-the-dom-with-refs) + +```js +useImperativeHandle(ref, createHandle, dependencies?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useImperativeHandle(ref, createHandle, dependencies?)` {/*useimperativehandle*/} + +Call `useImperativeHandle` at the top level of your component to customize the ref handle it exposes: + +```js +import { forwardRef, useImperativeHandle } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + useImperativeHandle(ref, () => { + return { + // ... your methods ... + }; + }, []); + // ... +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `ref`: The `ref` you received as the second argument from the [`forwardRef` render function.](/reference/react/forwardRef#render-function) + +* `createHandle`: A function that takes no arguments and returns the ref handle you want to expose. The ref handle you return can have any type. Usually, you will return an object with the methods you want to expose. + +* **optional** `dependencies`: The list of all reactive values referenced inside of the `createHandle` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. If a re-render resulted in a change to some dependency, or if you did not specify the dependencies at all, your `createHandle` function will re-execute, and the newly created handle will be assigned to the ref. + +#### Returns {/*returns*/} + +`useImperativeHandle` returns `undefined`. + +--- + +## Usage {/*usage*/} + +### Exposing a custom ref handle to the parent component {/*exposing-a-custom-ref-handle-to-the-parent-component*/} + +By default, components don't expose their DOM nodes to parent components. For example, if you want the parent component of `MyInput` to [have access](/learn/manipulating-the-dom-with-refs) to the `<input>` DOM node, you have to opt in with [`forwardRef`:](/reference/react/forwardRef) + +```js {4} +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return <input {...props} ref={ref} />; +}); +``` + +With the code above, [a ref to `MyInput` will receive the `<input>` DOM node.](/reference/react/forwardRef#exposing-a-dom-node-to-the-parent-component) However, you can expose a custom value instead. To customize the exposed handle, call `useImperativeHandle` at the top level of your component: + +```js {4-8} +import { forwardRef, useImperativeHandle } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + useImperativeHandle(ref, () => { + return { + // ... your methods ... + }; + }, []); + + return <input {...props} />; +}); +``` + +Note that in the code above, the `ref` is no longer forwarded to the `<input>`. + +For example, suppose you don't want to expose the entire `<input>` DOM node, but you want to expose two of its methods: `focus` and `scrollIntoView`. To do this, keep the real browser DOM in a separate ref. Then use `useImperativeHandle` to expose a handle with only the methods that you want the parent component to call: + +```js {7-14} +import { forwardRef, useRef, useImperativeHandle } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + const inputRef = useRef(null); + + useImperativeHandle(ref, () => { + return { + focus() { + inputRef.current.focus(); + }, + scrollIntoView() { + inputRef.current.scrollIntoView(); + }, + }; + }, []); + + return <input {...props} ref={inputRef} />; +}); +``` + +Now, if the parent component gets a ref to `MyInput`, it will be able to call the `focus` and `scrollIntoView` methods on it. However, it will not have full access to the underlying `<input>` DOM node. + +<Sandpack> + +```js +import { useRef } from 'react'; +import MyInput from './MyInput.js'; + +export default function Form() { + const ref = useRef(null); + + function handleClick() { + ref.current.focus(); + // This won't work because the DOM node isn't exposed: + // ref.current.style.opacity = 0.5; + } + + return ( + <form> + <MyInput label="Enter your name:" ref={ref} /> + <button type="button" onClick={handleClick}> + Edit + </button> + </form> + ); +} +``` + +```js MyInput.js +import { forwardRef, useRef, useImperativeHandle } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + const inputRef = useRef(null); + + useImperativeHandle(ref, () => { + return { + focus() { + inputRef.current.focus(); + }, + scrollIntoView() { + inputRef.current.scrollIntoView(); + }, + }; + }, []); + + return <input {...props} ref={inputRef} />; +}); + +export default MyInput; +``` + +```css +input { + margin: 5px; +} +``` + +</Sandpack> + +--- + +### Exposing your own imperative methods {/*exposing-your-own-imperative-methods*/} + +The methods you expose via an imperative handle don't have to match the DOM methods exactly. For example, the `Post` component in the example below exposes a `scrollAndFocusAddComment` method via an imperative handle. This lets the parent `Page` scroll the list of comments *and* focus the input field when you click the button: + +<Sandpack> + +```js +import { useRef } from 'react'; +import Post from './Post.js'; + +export default function Page() { + const postRef = useRef(null); + + function handleClick() { + postRef.current.scrollAndFocusAddComment(); + } + + return ( + <> + <button onClick={handleClick}> + Write a comment + </button> + <Post ref={postRef} /> + </> + ); +} +``` + +```js Post.js +import { forwardRef, useRef, useImperativeHandle } from 'react'; +import CommentList from './CommentList.js'; +import AddComment from './AddComment.js'; + +const Post = forwardRef((props, ref) => { + const commentsRef = useRef(null); + const addCommentRef = useRef(null); + + useImperativeHandle(ref, () => { + return { + scrollAndFocusAddComment() { + commentsRef.current.scrollToBottom(); + addCommentRef.current.focus(); + } + }; + }, []); + + return ( + <> + <article> + <p>Welcome to my blog!</p> + </article> + <CommentList ref={commentsRef} /> + <AddComment ref={addCommentRef} /> + </> + ); +}); + +export default Post; +``` + + +```js CommentList.js +import { forwardRef, useRef, useImperativeHandle } from 'react'; + +const CommentList = forwardRef(function CommentList(props, ref) { + const divRef = useRef(null); + + useImperativeHandle(ref, () => { + return { + scrollToBottom() { + const node = divRef.current; + node.scrollTop = node.scrollHeight; + } + }; + }, []); + + let comments = []; + for (let i = 0; i < 50; i++) { + comments.push(<p key={i}>Comment #{i}</p>); + } + + return ( + <div className="CommentList" ref={divRef}> + {comments} + </div> + ); +}); + +export default CommentList; +``` + +```js AddComment.js +import { forwardRef, useRef, useImperativeHandle } from 'react'; + +const AddComment = forwardRef(function AddComment(props, ref) { + return <input placeholder="Add comment..." ref={ref} />; +}); + +export default AddComment; +``` + +```css +.CommentList { + height: 100px; + overflow: scroll; + border: 1px solid black; + margin-top: 20px; + margin-bottom: 20px; +} +``` + +</Sandpack> + +<Pitfall> + +**Do not overuse refs.** You should only use refs for *imperative* behaviors that you can't express as props: for example, scrolling to a node, focusing a node, triggering an animation, selecting text, and so on. + +**If you can express something as a prop, you should not use a ref.** For example, instead of exposing an imperative handle like `{ open, close }` from a `Modal` component, it is better to take `isOpen` as a prop like `<Modal isOpen={isOpen} />`. [Effects](/learn/synchronizing-with-effects) can help you expose imperative behaviors via props. + +</Pitfall> diff --git a/beta/src/content/reference/react/useInsertionEffect.md b/beta/src/content/reference/react/useInsertionEffect.md new file mode 100644 index 000000000..43b31a122 --- /dev/null +++ b/beta/src/content/reference/react/useInsertionEffect.md @@ -0,0 +1,139 @@ +--- +title: useInsertionEffect +--- + +<Pitfall> + +`useInsertionEffect` is aimed at CSS-in-JS library authors. Unless you are working on a CSS-in-JS library and need a place to inject the styles, you probably want [`useEffect`](/reference/react/useEffect) or [`useLayoutEffect`](/reference/react/useLayoutEffect) instead. + +</Pitfall> + +<Intro> + +`useInsertionEffect` is a version of [`useEffect`](/reference/react/useEffect) that fires before any DOM mutations. + +```js +useInsertionEffect(setup, dependencies?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useInsertionEffect(setup, dependencies?)` {/*useinsertioneffect*/} + +Call `useInsertionEffect` to insert the styles before any DOM mutations: + +```js +import { useInsertionEffect } from 'react'; + +// Inside your CSS-in-JS library +function useCSS(rule) { + useInsertionEffect(() => { + // ... inject <style> tags here ... + }); + return rule; +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `setup`: The function with your Effect's logic. Your setup function may also optionally return a *cleanup* function. Before your component is first added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. Before your component is removed from the DOM, React will run your cleanup function one last time. + +* **optional** `dependencies`: The list of all reactive values referenced inside of the `setup` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. If you don't specify the dependencies at all, your Effect will re-run after every re-render of the component. + +#### Returns {/*returns*/} + +`useInsertionEffect` returns `undefined`. + +#### Caveats {/*caveats*/} + +* Effects only run on the client. They don't run during server rendering. +* You can't update state from inside `useInsertionEffect`. +* By the time `useInsertionEffect` runs, refs are not attached yet, and DOM is not yet updated. + +--- + +## Usage {/*usage*/} + +### Injecting dynamic styles from CSS-in-JS libraries {/*injecting-dynamic-styles-from-css-in-js-libraries*/} + +Traditionally, you would style React components using plain CSS. + +```js +// In your JS file: +<button className="success" /> + +// In your CSS file: +.success { color: green; } +``` + +Some teams prefer to author styles directly in JavaScript code instead of writing CSS files. This usually requires using a CSS-in-JS library or a tool. There are three common approaches to CSS-in-JS you might encounter: + +1. Static extraction to CSS files with a compiler +2. Inline styles, e.g. `<div style={{ opacity: 1 }}>` +3. Runtime injection of `<style>` tags + +If you use CSS-in-JS, we recommend a combination of the first two approaches (CSS files for static styles, inline styles for dynamic styles). **We don't recommend runtime `<style>` tag injection for two reasons:** + +1. Runtime injection forces the browser to recalculate the styles a lot more often. +2. Runtime injection can be very slow if it happens at the wrong time in the React lifecycle. + +The first problem is not solvable, but `useInsertionEffect` helps you solve the second problem. + +Call `useInsertionEffect` to insert the styles before any DOM mutations: + +```js {4-11} +// Inside your CSS-in-JS library +let isInserted = new Set(); +function useCSS(rule) { + useInsertionEffect(() => { + // As explained earlier, we don't recommend runtime injection of <style> tags. + // But if you have to do it, then it's important to do in useInsertionEffect. + if (!isInserted.has(rule)) { + isInserted.add(rule); + document.head.appendChild(getStyleForRule(rule)); + } + }); + return rule; +} + +function Button() { + const className = useCSS('...'); + return <div className={className} />; +} +``` + +Similarly to `useEffect`, `useInsertionEffect` does not run on the server. If you need to collect which CSS rules have been used on the server, you can do it during rendering: + +```js {1,4-6} +let collectedRulesSet = new Set(); + +function useCSS(rule) { + if (typeof window === 'undefined') { + collectedRulesSet.add(rule); + } + useInsertionEffect(() => { + // ... + }); + return rule; +} +``` + +[Read more about upgrading CSS-in-JS libraries with runtime injection to `useInsertionEffect`.](https://github.com/reactwg/react-18/discussions/110) + +<DeepDive> + +#### How is this better than injecting styles during rendering or useLayoutEffect? {/*how-is-this-better-than-injecting-styles-during-rendering-or-uselayouteffect*/} + +If you insert styles during rendering and React is processing a [non-blocking update,](/reference/react/useTransition#marking-a-state-update-as-a-non-blocking-transition) the browser will recalculate the styles every single frame while rendering a component tree, which can be **extremely slow.** + +`useInsertionEffect` is better than inserting styles during [`useLayoutEffect`](/reference/react/useLayoutEffect) or [`useEffect`](/reference/react/useEffect) because it ensures that by the time other Effects run in your components, the `<style>` tags have already been inserted. Otherwise, layout calculations in regular Effects would be wrong due to outdated styles. + +</DeepDive> diff --git a/beta/src/content/reference/react/useLayoutEffect.md b/beta/src/content/reference/react/useLayoutEffect.md new file mode 100644 index 000000000..cfedd6719 --- /dev/null +++ b/beta/src/content/reference/react/useLayoutEffect.md @@ -0,0 +1,743 @@ +--- +title: useLayoutEffect +--- + +<Pitfall> + +`useLayoutEffect` can hurt performance. Prefer [`useEffect`](/reference/react/useEffect) when possible. + +</Pitfall> + +<Intro> + +`useLayoutEffect` is a version of [`useEffect`](/reference/react/useEffect) that fires before the browser repaints the screen. + +```js +useLayoutEffect(setup, dependencies?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useLayoutEffect(setup, dependencies?)` {/*useinsertioneffect*/} + +Call `useLayoutEffect` perform the layout measurements before the browser repaints the screen: + +```js +import { useState, useRef, useLayoutEffect } from 'react'; + +function Tooltip() { + const ref = useRef(null); + const [tooltipHeight, setTooltipHeight] = useState(0); + + useLayoutEffect(() => { + const { height } = ref.current.getBoundingClientRect(); + setTooltipHeight(height); + }, []); + // ... +``` + + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `setup`: The function with your Effect's logic. Your setup function may also optionally return a *cleanup* function. Before your component is first added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. Before your component is removed from the DOM, React will run your cleanup function one last time. + +* **optional** `dependencies`: The list of all reactive values referenced inside of the `setup` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. If you don't specify the dependencies at all, your Effect will re-run after every re-render of the component. + +#### Returns {/*returns*/} + +`useLayoutEffect` returns `undefined`. + +#### Caveats {/*caveats*/} + +* `useLayoutEffect` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. + +* When Strict Mode is on, React will **run one extra development-only setup+cleanup cycle** before the first real setup. This is a stress-test that ensures that your cleanup logic "mirrors" your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, [you need to implement the cleanup function.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) + +* If some of your dependencies are objects or functions defined inside the component, there is a risk that they will **cause the Effect to re-run more often than needed.** To fix this, remove unnecessary [object](/reference/react/useEffect#removing-unnecessary-object-dependencies) and [function](/reference/react/useEffect#removing-unnecessary-function-dependencies) dependencies. You can also [extract state updates](/reference/react/useEffect#updating-state-based-on-previous-state-from-an-effect) and [non-reactive logic](/reference/react/useEffect#reading-the-latest-props-and-state-from-an-effect) outside of your Effect. + +* Effects **only run on the client.** They don't run during server rendering. + +* The code inside `useLayoutEffect` and all state updates scheduled from it **block the browser from repainting the screen.** When used excessively, this can make your app very slow. When possible, prefer [`useEffect`.](/reference/reac/useEffect) + +--- + +## Usage {/*usage*/} + +### Measuring layout before the browser repaints the screen {/*measuring-layout-before-the-browser-repaints-the-screen*/} + +Most components don't need to know their position and size on the screen to decide what to render. They only return some JSX with CSS. Then the browser calculates their *layout* (position and size) and repaints the screen. + +Sometimes, that's not enough. Imagine a tooltip that appears next to some element on hover. If there's enough space, the tooltip should appear above the element, but if it doesn't fit, it should appear below. This means that in order to render the tooltip at the right final position, you need to know its height (i.e. whether it fits at the top). + +To do this, you need to render in two passes: + +1. Render the tooltip anywhere (even with a wrong position). +2. Measure its height and decide where to place the tooltip. +3. Render the tooltip *again* in the correct place. + +**All of this needs to happen before the browser repaints the screen.** You don't want the user to see the tooltip moving. Call `useLayoutEffect` to perform the layout measurements before the browser repaints the screen: + +```js {5-8} +function Tooltip() { + const ref = useRef(null); + const [tooltipHeight, setTooltipHeight] = useState(0); // You don't know real height yet + + useLayoutEffect(() => { + const { height } = ref.current.getBoundingClientRect(); + setTooltipHeight(height); // Re-render now that you know the real height + }, []); + + // ...use tooltipHeight in the rendering logic below... +} +``` + +Here's how this works step by step: + +1. `Tooltip` renders with the initial `tooltipHeight = 0` (so the tooltip may be wrongly positioned). +2. React places it in the DOM and runs the code in `useLayoutEffect`. +3. Your `useLayoutEffect` [measures the height](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of the tooltip content and triggers an immediate re-render. +4. `Tooltip` renders again with the real `tooltipHeight` (so the tooltip is correctly positioned). +5. React updates it in the DOM, and the browser finally displays the tooltip. + +Hover over the buttons below and see how the tooltip adjusts its position depending on whether it fits: + +<Sandpack> + +```js +import ButtonWithTooltip from './ButtonWithTooltip.js'; + +export default function App() { + return ( + <div> + <ButtonWithTooltip + tooltipContent={ + <div> + This tooltip does not fit above the button. + <br /> + This is why it's displayed below instead! + </div> + } + > + Hover over me (tooltip above) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + </div> + ); +} +``` + +```js ButtonWithTooltip.js +import { useState, useRef } from 'react'; +import Tooltip from './Tooltip.js'; + +export default function ButtonWithTooltip({ tooltipContent, ...rest }) { + const [targetRect, setTargetRect] = useState(null); + const buttonRef = useRef(null); + return ( + <> + <button + {...rest} + ref={buttonRef} + onPointerEnter={() => { + const rect = buttonRef.current.getBoundingClientRect(); + setTargetRect({ + left: rect.left, + top: rect.top, + right: rect.right, + bottom: rect.bottom, + }); + }} + onPointerLeave={() => { + setTargetRect(null); + }} + /> + {targetRect !== null && ( + <Tooltip targetRect={targetRect}> + {tooltipContent} + </Tooltip> + ) + } + </> + ); +} +``` + +```js Tooltip.js active +import { useRef, useLayoutEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import TooltipContainer from './TooltipContainer.js'; + +export default function Tooltip({ children, targetRect }) { + const ref = useRef(null); + const [tooltipHeight, setTooltipHeight] = useState(0); + + useLayoutEffect(() => { + const { height } = ref.current.getBoundingClientRect(); + setTooltipHeight(height); + console.log('Measured tooltip height: ' + height); + }, []); + + let tooltipX = 0; + let tooltipY = 0; + if (targetRect !== null) { + tooltipX = targetRect.left; + tooltipY = targetRect.top - tooltipHeight; + if (tooltipY < 0) { + // It doesn't fit above, so place below. + tooltipY = targetRect.bottom; + } + } + + return createPortal( + <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> + {children} + </TooltipContainer>, + document.body + ); +} +``` + +```js TooltipContainer.js +export default function TooltipContainer({ children, x, y, contentRef }) { + return ( + <div + style={{ + position: 'absolute', + pointerEvents: 'none', + left: 0, + top: 0, + transform: `translate3d(${x}px, ${y}px, 0)` + }} + > + <div ref={contentRef} className="tooltip"> + {children} + </div> + </div> + ); +} +``` + +```css +.tooltip { + color: white; + background: #222; + border-radius: 4px; + padding: 4px; +} +``` + +</Sandpack> + +Notice that even though the `Tooltip` component has to render in two passes (first, with `tooltipHeight` initialized to `0` and then with the real measured height), you only see the final result. This is why you need `useLayoutEffect` instead of [`useEffect`](/reference/react/useEffect) for this example. Let's look at the difference in detail below. + +<Recipes titleText="useLayoutEffect vs useEffect" titleId="examples"> + +#### `useLayoutEffect` blocks the browser from repainting {/*uselayouteffect-blocks-the-browser-from-repainting*/} + +React guarantees that the code inside `useLayoutEffect` and any state updates scheduled inside it will be processed **before the browser repaints the screen.** This lets you render the tooltip, measure it, and re-render the tooltip again without the user noticing the first extra render. In other words, `useLayoutEffect` blocks the browser from painting. + +<Sandpack> + +```js +import ButtonWithTooltip from './ButtonWithTooltip.js'; + +export default function App() { + return ( + <div> + <ButtonWithTooltip + tooltipContent={ + <div> + This tooltip does not fit above the button. + <br /> + This is why it's displayed below instead! + </div> + } + > + Hover over me (tooltip above) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + </div> + ); +} +``` + +```js ButtonWithTooltip.js +import { useState, useRef } from 'react'; +import Tooltip from './Tooltip.js'; + +export default function ButtonWithTooltip({ tooltipContent, ...rest }) { + const [targetRect, setTargetRect] = useState(null); + const buttonRef = useRef(null); + return ( + <> + <button + {...rest} + ref={buttonRef} + onPointerEnter={() => { + const rect = buttonRef.current.getBoundingClientRect(); + setTargetRect({ + left: rect.left, + top: rect.top, + right: rect.right, + bottom: rect.bottom, + }); + }} + onPointerLeave={() => { + setTargetRect(null); + }} + /> + {targetRect !== null && ( + <Tooltip targetRect={targetRect}> + {tooltipContent} + </Tooltip> + ) + } + </> + ); +} +``` + +```js Tooltip.js active +import { useRef, useLayoutEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import TooltipContainer from './TooltipContainer.js'; + +export default function Tooltip({ children, targetRect }) { + const ref = useRef(null); + const [tooltipHeight, setTooltipHeight] = useState(0); + + useLayoutEffect(() => { + const { height } = ref.current.getBoundingClientRect(); + setTooltipHeight(height); + }, []); + + let tooltipX = 0; + let tooltipY = 0; + if (targetRect !== null) { + tooltipX = targetRect.left; + tooltipY = targetRect.top - tooltipHeight; + if (tooltipY < 0) { + // It doesn't fit above, so place below. + tooltipY = targetRect.bottom; + } + } + + return createPortal( + <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> + {children} + </TooltipContainer>, + document.body + ); +} +``` + +```js TooltipContainer.js +export default function TooltipContainer({ children, x, y, contentRef }) { + return ( + <div + style={{ + position: 'absolute', + pointerEvents: 'none', + left: 0, + top: 0, + transform: `translate3d(${x}px, ${y}px, 0)` + }} + > + <div ref={contentRef} className="tooltip"> + {children} + </div> + </div> + ); +} +``` + +```css +.tooltip { + color: white; + background: #222; + border-radius: 4px; + padding: 4px; +} +``` + +</Sandpack> + +<Solution /> + +#### `useEffect` does not block the browser {/*useeffect-does-not-block-the-browser*/} + +Here is the same example, but with [`useEffect`](/reference/react/useEffect) instead of `useLayoutEffect`. If you're on a slower device, you might notice that sometimes the tooltip "flickers" and you briefly see its initial position before the corrected position. + +<Sandpack> + +```js +import ButtonWithTooltip from './ButtonWithTooltip.js'; + +export default function App() { + return ( + <div> + <ButtonWithTooltip + tooltipContent={ + <div> + This tooltip does not fit above the button. + <br /> + This is why it's displayed below instead! + </div> + } + > + Hover over me (tooltip above) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + </div> + ); +} +``` + +```js ButtonWithTooltip.js +import { useState, useRef } from 'react'; +import Tooltip from './Tooltip.js'; + +export default function ButtonWithTooltip({ tooltipContent, ...rest }) { + const [targetRect, setTargetRect] = useState(null); + const buttonRef = useRef(null); + return ( + <> + <button + {...rest} + ref={buttonRef} + onPointerEnter={() => { + const rect = buttonRef.current.getBoundingClientRect(); + setTargetRect({ + left: rect.left, + top: rect.top, + right: rect.right, + bottom: rect.bottom, + }); + }} + onPointerLeave={() => { + setTargetRect(null); + }} + /> + {targetRect !== null && ( + <Tooltip targetRect={targetRect}> + {tooltipContent} + </Tooltip> + ) + } + </> + ); +} +``` + +```js Tooltip.js active +import { useRef, useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import TooltipContainer from './TooltipContainer.js'; + +export default function Tooltip({ children, targetRect }) { + const ref = useRef(null); + const [tooltipHeight, setTooltipHeight] = useState(0); + + useEffect(() => { + const { height } = ref.current.getBoundingClientRect(); + setTooltipHeight(height); + }, []); + + let tooltipX = 0; + let tooltipY = 0; + if (targetRect !== null) { + tooltipX = targetRect.left; + tooltipY = targetRect.top - tooltipHeight; + if (tooltipY < 0) { + // It doesn't fit above, so place below. + tooltipY = targetRect.bottom; + } + } + + return createPortal( + <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> + {children} + </TooltipContainer>, + document.body + ); +} +``` + +```js TooltipContainer.js +export default function TooltipContainer({ children, x, y, contentRef }) { + return ( + <div + style={{ + position: 'absolute', + pointerEvents: 'none', + left: 0, + top: 0, + transform: `translate3d(${x}px, ${y}px, 0)` + }} + > + <div ref={contentRef} className="tooltip"> + {children} + </div> + </div> + ); +} +``` + +```css +.tooltip { + color: white; + background: #222; + border-radius: 4px; + padding: 4px; +} +``` + +</Sandpack> + +To make the bug easier to reproduce, this version adds an artificial delay during rendering. React will let the browser paint the screen before it processes the state update inside `useEffect`. As a result, the tooltip flickers: + +<Sandpack> + +```js +import ButtonWithTooltip from './ButtonWithTooltip.js'; + +export default function App() { + return ( + <div> + <ButtonWithTooltip + tooltipContent={ + <div> + This tooltip does not fit above the button. + <br /> + This is why it's displayed below instead! + </div> + } + > + Hover over me (tooltip above) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + <div style={{ height: 50 }} /> + <ButtonWithTooltip + tooltipContent={ + <div>This tooltip fits above the button</div> + } + > + Hover over me (tooltip below) + </ButtonWithTooltip> + </div> + ); +} +``` + +```js ButtonWithTooltip.js +import { useState, useRef } from 'react'; +import Tooltip from './Tooltip.js'; + +export default function ButtonWithTooltip({ tooltipContent, ...rest }) { + const [targetRect, setTargetRect] = useState(null); + const buttonRef = useRef(null); + return ( + <> + <button + {...rest} + ref={buttonRef} + onPointerEnter={() => { + const rect = buttonRef.current.getBoundingClientRect(); + setTargetRect({ + left: rect.left, + top: rect.top, + right: rect.right, + bottom: rect.bottom, + }); + }} + onPointerLeave={() => { + setTargetRect(null); + }} + /> + {targetRect !== null && ( + <Tooltip targetRect={targetRect}> + {tooltipContent} + </Tooltip> + ) + } + </> + ); +} +``` + +```js Tooltip.js active +import { useRef, useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import TooltipContainer from './TooltipContainer.js'; + +export default function Tooltip({ children, targetRect }) { + const ref = useRef(null); + const [tooltipHeight, setTooltipHeight] = useState(0); + + // This artificially slows down rendering + let now = performance.now(); + while (performance.now() - now < 100) { + // Do nothing for a bit... + } + + useEffect(() => { + const { height } = ref.current.getBoundingClientRect(); + setTooltipHeight(height); + }, []); + + let tooltipX = 0; + let tooltipY = 0; + if (targetRect !== null) { + tooltipX = targetRect.left; + tooltipY = targetRect.top - tooltipHeight; + if (tooltipY < 0) { + // It doesn't fit above, so place below. + tooltipY = targetRect.bottom; + } + } + + return createPortal( + <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> + {children} + </TooltipContainer>, + document.body + ); +} +``` + +```js TooltipContainer.js +export default function TooltipContainer({ children, x, y, contentRef }) { + return ( + <div + style={{ + position: 'absolute', + pointerEvents: 'none', + left: 0, + top: 0, + transform: `translate3d(${x}px, ${y}px, 0)` + }} + > + <div ref={contentRef} className="tooltip"> + {children} + </div> + </div> + ); +} +``` + +```css +.tooltip { + color: white; + background: #222; + border-radius: 4px; + padding: 4px; +} +``` + +</Sandpack> + +Edit this example to `useLayoutEffect` and observe that it blocks the paint even if rendering is slowed down. + +<Solution /> + +</Recipes> + +<Note> + +Rendering in two passes and blocking the browser hurts performance. Try to avoid this when you can. + +</Note> + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I'm getting an error: "`useLayoutEffect` does nothing on the server" {/*im-getting-an-error-uselayouteffect-does-nothing-on-the-server*/} + +The purpose of `useLayoutEffect` is to let your component [use layout information for rendering:](#measuring-layout-before-the-browser-repaints-the-screen) + +1. Render the initial content. +2. Measure the layout *before the browser repaints the screen.* +3. Render the final content using the layout information you've read. + +When you or your framework uses [server rendering](/reference/react-dom/server), your React app renders to HTML on the server for the initial render. This lets you show the initial HTML before the JavaScript code loads. + +The problem is that on the server, there is no layout information. + +In the [earlier example](#measuring-layout-before-the-browser-repaints-the-screen), the `useLayoutEffect` call in the `Tooltip` component lets it position itself correctly (either above or below content) depending on the content height. If you tried to render `Tooltip` as a part of the initial server HTML, this would be impossible to determine. On the server, there is no browser and no layout! So, even if you rendered it on the server, its position would "jump" on the client after the JavaScript loads and runs. + +Usually, components that rely on layout information don't need to render on the server anyway. For example, it probably doesn't make sense to show a `Tooltip` during the initial render. It is triggered by a client interaction. + +However, if you're running into this problem, you have a few options: + +1. You can replace `useLayoutEffect` with [`useEffect`.](/reference/react/useEffect) This tells React that it's okay to display the initial render result without blocking the paint (because the original HTML will become visible before your Effect runs). + +2. You can [mark your component as client-only.](/reference/react/Suspense#providing-a-fallback-for-server-errors-and-server-only-content) This tells React to replace its content up to the closest [`<Suspense>`](/reference/react/Suspense) boundary with a loading fallback (for example, a spinner or a glimmer) during server rendering. + +3. You can display different components on the server and on the client. One way to do this is to keep a boolean `isMounted` state that's initialized to `false`, and set it to `true` inside a `useEffect` call. Your rendering logic can then be like `return isMounted ? <RealContent /> : <FallbackContent />`. On the server and during the hydration, the user will see `FallbackContent` which should not call `useLayoutEffect`. Then React will replace it with `RealContent` which runs on the client only and can include `useLayoutEffect` calls. + +4. If you synchronize your component with an external data store and rely on `useLayoutEffect` for different reasons than measuring layout, consider [`useSyncExternalStore`](/reference/react/useSyncExternalStore) instead which [supports server rendering.](/reference/react/useSyncExternalStore#adding-support-for-server-rendering) + + + + diff --git a/beta/src/content/reference/react/useMemo.md b/beta/src/content/reference/react/useMemo.md new file mode 100644 index 000000000..b2b3dafed --- /dev/null +++ b/beta/src/content/reference/react/useMemo.md @@ -0,0 +1,1348 @@ +--- +title: useMemo +--- + +<Intro> + +`useMemo` is a React Hook that lets you cache the result of a calculation between re-renders. + +```js +const cachedValue = useMemo(calculateValue, dependencies) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useMemo(calculateValue, dependencies)` {/*usememo*/} + +Call `useMemo` at the top level of your component to cache a calculation between re-renders: + +```js +import { useMemo } from 'react'; + +function TodoList({ todos, tab }) { + const visibleTodos = useMemo( + () => filterTodos(todos, tab), + [todos, tab] + ); + // ... +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `calculateValue`: The function calculating the value that you want to cache. It should be pure, should take no arguments, and should return a value of any type. React will call your function during the initial render. On subsequent renders, React will return the same value again if the `dependencies` have not changed since the last render. Otherwise, it will call `calculateValue`, return its result, and store it in case it can be reused later. + +* `dependencies`: The list of all reactive values referenced inside of the `calculateValue` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. + +#### Returns {/*returns*/} + +On the initial render, `useMemo` returns the result of calling `calculateValue` with no arguments. + +During subsequent renders, it will either return an already stored value from the last render (if the dependencies haven't changed), or call `calculateValue` again, and return the result that `calculateValue` has returned. + +#### Caveats {/*caveats*/} + +* `useMemo` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. +* In Strict Mode, React will **call your calculation function twice** in order to [help you find accidental impurities.](#my-calculation-runs-twice-on-every-re-render) This is development-only behavior and does not affect production. If your calculation function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored. +* React **will not throw away the cached value unless there is a specific reason to do that.** For example, in development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of throwing away the cache--for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should match your expectations if you rely on `useMemo` solely as a performance optimization. Otherwise, a [state variable](/reference/react/useState#avoiding-recreating-the-initial-state) or a [ref](/reference/react/useRef#avoiding-recreating-the-ref-contents) may be more appropriate. + +<Note> + +Caching return values like this is also known as [*memoization*,](https://en.wikipedia.org/wiki/Memoization) which is why this Hook is called `useMemo`. + +</Note> + +--- + +## Usage {/*usage*/} + +### Skipping expensive recalculations {/*skipping-expensive-recalculations*/} + +To cache a calculation between re-renders, wrap it in a `useMemo` call at the top level of your component: + +```js [[3, 4, "visibleTodos"], [1, 4, "() => filterTodos(todos, tab)"], [2, 4, "[todos, tab]"]] +import { useMemo } from 'react'; + +function TodoList({ todos, tab, theme }) { + const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); + // ... +} +``` + +You need to pass two things to `useMemo`: + +1. A <CodeStep step={1}>calculation function</CodeStep> that takes no arguments, like `() =>`, and returns what you wanted to calculate. +2. A <CodeStep step={2}>list of dependencies</CodeStep> including every value within your component that's used inside your calculation. + +On the initial render, the <CodeStep step={3}>value</CodeStep> you'll get from `useMemo` will be the result of calling your <CodeStep step={1}>calculation</CodeStep>. + +On every subsequent render, React will compare the <CodeStep step={2}>dependencies</CodeStep> with the dependencies you passed during the last render. If none of the dependencies have changed (compared with [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), `useMemo` will return the value you already calculated before. Otherwise, React will re-run your calculation and return the new value. + +In other words, `useMemo` caches a calculation result between re-renders until its dependencies change. + +**Let's walk through an example to see when this is useful.** + +By default, React will re-run the entire body of your component every time that it re-renders. For example, if this `TodoList` updates its state or receives new props from its parent, the `filterTodos` function will re-run: + +```js {2} +function TodoList({ todos, tab, theme }) { + const visibleTodos = filterTodos(todos, tab); + // ... +} +``` + +Usually, this isn't a problem because most calculations are very fast. However, if you're filtering or transforming a large array, or doing some expensive computation, you might want to skip doing it again if data hasn't changed. If both `todos` and `tab` are the same as they were during the last render, wrapping the calculation in `useMemo` like earlier lets you reuse `visibleTodos` you've already calculated before. This type of caching is called *[memoization.](https://en.wikipedia.org/wiki/Memoization)* + +<Note> + +**You should only rely on `useMemo` as a performance optimization.** If your code doesn't work without it, find the underlying problem and fix it first. Then you may add `useMemo` to improve performance. + +</Note> + +<DeepDive> + +#### How to tell if a calculation is expensive? {/*how-to-tell-if-a-calculation-is-expensive*/} + +In general, unless you're creating or looping over thousands of objects, it's probably not expensive. If you want to get more confidence, you can add a console log to measure the time spent in a piece of code: + +```js {1,3} +console.time('filter array'); +const visibleTodos = filterTodos(todos, tab); +console.timeEnd('filter array'); +``` + +Perform the interaction you're measuring (for example, typing into the input). You will then see logs like `filter array: 0.15ms` in your console. If the overall logged time adds up to a significant amount (say, `1ms` or more), it might make sense to memoize that calculation. As an experiment, you can then wrap the calculation in `useMemo` to verify whether the total logged time has decreased for that interaction or not: + +```js +console.time('filter array'); +const visibleTodos = useMemo(() => { + return filterTodos(todos, tab); // Skipped if todos and tab haven't changed +}, [todos, tab]); +console.timeEnd('filter array'); +``` + +`useMemo` won't make the *first* render faster. It only helps you skip unnecessary work on updates. + +Keep in mind that your machine is probably faster than your users' so it's a good idea to test the performance with an artificial slowdown. For example, Chrome offers a [CPU Throttling](https://developer.chrome.com/blog/new-in-devtools-61/#throttling) option for this. + +Also note that measuring performance in development will not give you the most accurate results. (For example, when [Strict Mode](/reference/react/StrictMode) is on, you will see each component render twice rather than once.) To get the most accurate timings, build your app for production and test it on a device like your users have. + +</DeepDive> + +<DeepDive> + +#### Should you add useMemo everywhere? {/*should-you-add-usememo-everywhere*/} + +If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful. + +Optimizing with `useMemo` is only valuable in a few cases: + +- The calculation you're putting in `useMemo` is noticeably slow, and its dependencies rarely change. +- You pass it as a prop to a component wrapped in [`memo`.](/reference/react/memo) You want to skip re-rendering if the value hasn't changed. Memoization lets your component re-render only when dependencies aren't the same. +- The value you're passing is later used as a dependency of some Hook. For example, maybe another `useMemo` calculation value depends on it. Or maybe you are depending on this value from [`useEffect.`](/reference/react/useEffect) + +There is no benefit to wrapping a calculation in `useMemo` in other cases. There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible. The downside of this approach is that code becomes less readable. Also, not all memoization is effective: a single value that's "always new" is enough to break memoization for an entire component. + +**In practice, you can make a lot of memoization unnecessary by following a few principles:** + +1. When a component visually wraps other components, let it [accept JSX as children.](/learn/passing-props-to-a-component#passing-jsx-as-children) This way, when the wrapper component updates its own state, React knows that its children don't need to re-render. +1. Prefer local state and don't [lift state up](/learn/sharing-state-between-components) any further than necessary. For example, don't keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library. +1. Keep your [rendering logic pure.](/learn/keeping-components-pure) If re-rendering a component causes a problem or produces some noticeable visual artifact, it's a bug in your component! Fix the bug instead of adding memoization. +1. Avoid [unnecessary Effects that update state.](/learn/you-might-not-need-an-effect) Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over. +1. Try to [remove unnecessary dependencies from your Effects.](/learn/removing-effect-dependencies) For example, instead of memoization, it's often simpler to move some object or a function inside an Effect or outside the component. + +If a specific interaction still feels laggy, [use the React Developer Tools profiler](/blog/2018/09/10/introducing-the-react-profiler.html) to see which components would benefit the most from memoization, and add memoization where needed. These principles make your components easier to debug and understand, so it's good to follow them in any case. In the long term, we're researching [doing granular memoization automatically](https://www.youtube.com/watch?v=lGEMwh32soc) to solve this once and for all. + +</DeepDive> + +<Recipes titleText="The difference between useMemo and calculating a value directly" titleId="examples-recalculation"> + +#### Skipping recalculation with `useMemo` {/*skipping-recalculation-with-usememo*/} + +In this example, the `filterTodos` implementation is **artificially slowed down** so that you can see what happens when some JavaScript function you're calling during rendering is genuinely slow. Try switching the tabs and toggling the theme. + +Switching the tabs feels slow because it forces the slowed down `filterTodos` to re-execute. That's expected because the `tab` has changed, and so the entire calculation *needs* to re-run. (If you're curious why it runs twice, it's explained [here.](#my-calculation-runs-twice-on-every-re-render)) + +Next, try toggling the theme. **Thanks to `useMemo`, it's fast despite the artificial slowdown!** The slow `filterTodos` call was skipped because both `todos` and `tab` (which you pass as dependencies to `useMemo`) haven't changed since the last render. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { createTodos } from './utils.js'; +import TodoList from './TodoList.js'; + +const todos = createTodos(); + +export default function App() { + const [tab, setTab] = useState('all'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <button onClick={() => setTab('all')}> + All + </button> + <button onClick={() => setTab('active')}> + Active + </button> + <button onClick={() => setTab('completed')}> + Completed + </button> + <br /> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <TodoList + todos={todos} + tab={tab} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} + +``` + +```js TodoList.js active +import { useMemo } from 'react'; +import { filterTodos } from './utils.js' + +export default function TodoList({ todos, theme, tab }) { + const visibleTodos = useMemo( + () => filterTodos(todos, tab), + [todos, tab] + ); + return ( + <div className={theme}> + <p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p> + <ul> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? + <s>{todo.text}</s> : + todo.text + } + </li> + ))} + </ul> + </div> + ); +} +``` + +```js utils.js +export function createTodos() { + const todos = []; + for (let i = 0; i < 50; i++) { + todos.push({ + id: i, + text: "Todo " + (i + 1), + completed: Math.random() > 0.5 + }); + } + return todos; +} + +export function filterTodos(todos, tab) { + console.log('[ARTIFICIALLY SLOW] Filtering ' + todos.length + ' todos for "' + tab + '" tab.'); + let startTime = performance.now(); + while (performance.now() - startTime < 500) { + // Do nothing for 500 ms to emulate extremely slow code + } + + return todos.filter(todo => { + if (tab === 'all') { + return true; + } else if (tab === 'active') { + return !todo.completed; + } else if (tab === 'completed') { + return todo.completed; + } + }); +} +``` + +```css +label { + display: block; + margin-top: 10px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + +<Solution /> + +#### Always recalculating a value {/*always-recalculating-a-value*/} + +In this example, the `filterTodos` implementation is also **artificially slowed down** so that you can see what happens when some JavaScript function you're calling during rendering is genuinely slow. Try switching the tabs and toggling the theme. + +Unlike in the previous example, toggling the theme is also slow now! This is because **there is no `useMemo` call in this version,** so the artificially slowed down `filterTodos` gets called on every re-render. It is called even if only `theme` has changed. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { createTodos } from './utils.js'; +import TodoList from './TodoList.js'; + +const todos = createTodos(); + +export default function App() { + const [tab, setTab] = useState('all'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <button onClick={() => setTab('all')}> + All + </button> + <button onClick={() => setTab('active')}> + Active + </button> + <button onClick={() => setTab('completed')}> + Completed + </button> + <br /> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <TodoList + todos={todos} + tab={tab} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} + +``` + +```js TodoList.js active +import { filterTodos } from './utils.js' + +export default function TodoList({ todos, theme, tab }) { + const visibleTodos = filterTodos(todos, tab); + return ( + <div className={theme}> + <ul> + <p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? + <s>{todo.text}</s> : + todo.text + } + </li> + ))} + </ul> + </div> + ); +} +``` + +```js utils.js +export function createTodos() { + const todos = []; + for (let i = 0; i < 50; i++) { + todos.push({ + id: i, + text: "Todo " + (i + 1), + completed: Math.random() > 0.5 + }); + } + return todos; +} + +export function filterTodos(todos, tab) { + console.log('[ARTIFICIALLY SLOW] Filtering ' + todos.length + ' todos for "' + tab + '" tab.'); + let startTime = performance.now(); + while (performance.now() - startTime < 500) { + // Do nothing for 500 ms to emulate extremely slow code + } + + return todos.filter(todo => { + if (tab === 'all') { + return true; + } else if (tab === 'active') { + return !todo.completed; + } else if (tab === 'completed') { + return todo.completed; + } + }); +} +``` + +```css +label { + display: block; + margin-top: 10px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + +However, here is the same code **with the artificial slowdown removed.** Does the lack of `useMemo` feel noticeable or not? + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { createTodos } from './utils.js'; +import TodoList from './TodoList.js'; + +const todos = createTodos(); + +export default function App() { + const [tab, setTab] = useState('all'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <button onClick={() => setTab('all')}> + All + </button> + <button onClick={() => setTab('active')}> + Active + </button> + <button onClick={() => setTab('completed')}> + Completed + </button> + <br /> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <TodoList + todos={todos} + tab={tab} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} + +``` + +```js TodoList.js active +import { filterTodos } from './utils.js' + +export default function TodoList({ todos, theme, tab }) { + const visibleTodos = filterTodos(todos, tab); + return ( + <div className={theme}> + <ul> + {visibleTodos.map(todo => ( + <li key={todo.id}> + {todo.completed ? + <s>{todo.text}</s> : + todo.text + } + </li> + ))} + </ul> + </div> + ); +} +``` + +```js utils.js +export function createTodos() { + const todos = []; + for (let i = 0; i < 50; i++) { + todos.push({ + id: i, + text: "Todo " + (i + 1), + completed: Math.random() > 0.5 + }); + } + return todos; +} + +export function filterTodos(todos, tab) { + console.log('Filtering ' + todos.length + ' todos for "' + tab + '" tab.'); + + return todos.filter(todo => { + if (tab === 'all') { + return true; + } else if (tab === 'active') { + return !todo.completed; + } else if (tab === 'completed') { + return todo.completed; + } + }); +} +``` + +```css +label { + display: block; + margin-top: 10px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + +Quite often, code without memoization works fine. If your interactions are fast enough, you might not need memoization. + +You can try increasing the number of todo items in `utils.js` and see how the behavior changes. This particular calculation wasn't very expensive to begin with, but if the number of todos grows significantly, most of the overhead will be in re-rendering rather than in the filtering. Keep reading below to see how you can optimize re-rendering with `useMemo`. + +<Solution /> + +</Recipes> + +--- + +### Skipping re-rendering of components {/*skipping-re-rendering-of-components*/} + +In some cases, `useMemo` can also help you optimize performance of re-rendering child components. To illustrate this, let's say this `TodoList` component passes the `visibleTodos` as a prop to the child `List` component: + +```js {5} +export default function TodoList({ todos, tab, theme }) { + // ... + return ( + <div className={theme}> + <List items={visibleTodos} /> + </div> + ); +} +``` + +You've noticed that toggling the `theme` prop freezes the app for a moment, but if you remove `<List />` from your JSX, it feels fast. This tells you that it's worth trying to optimize the `List` component. + +**By default, when a component re-renders, React re-renders all of its children recursively.** This is why, when `TodoList` re-renders with a different `theme`, the `List` component *also* re-renders. This is fine for components that don't require much calculation to re-render. But if you've verified that a re-render is slow, you can tell `List` to skip re-rendering when its props are the same as on last render by wrapping it in [`memo`:](/reference/react/memo) + +```js {3,5} +import { memo } from 'react'; + +const List = memo(function List({ items }) { + // ... +}); +``` + +**With this change, `List` will skip re-rendering if all of its props are the *same* as on the last render.** This is where caching the calculation becomes important! Imagine that you calculated `visibleTodos` without `useMemo`: + +```js {2-3,6-7} +export default function TodoList({ todos, tab, theme }) { + // Every time the theme changes, this will be a different array... + const visibleTodos = filterTodos(todos, tab); + return ( + <div className={theme}> + {/* ... so List's props will never be the same, and it will re-render every time */} + <List items={visibleTodos} /> + </div> + ); +} +``` + +**In the above example, the `filterTodos` function always creates a *different* array,** similar to how the `{}` object literal always creates a new object. Normally, this wouldn't be a problem, but it means that `List` props will never be the same, and your [`memo`](/reference/react/memo) optimization won't work. This is where `useMemo` comes in handy: + +```js {2-3,5,9-10} +export default function TodoList({ todos, tab, theme }) { + // Tell React to cache your calculation between re-renders... + const visibleTodos = useMemo( + () => filterTodos(todos, tab), + [todos, tab] // ...so as long as these dependencies don't change... + ); + return ( + <div className={theme}> + {/* ...List will receive the same props and can skip re-rendering */} + <List items={visibleTodos} /> + </div> + ); +} +``` + + +**By wrapping the `visibleTodos` calculation in `useMemo`, you ensure that it has the *same* value between the re-renders** (until dependencies change). You don't *have to* wrap a calculation in `useMemo` unless you do it for some specific reason. In this example, the reason is that you pass it to a component wrapped in [`memo`,](/reference/react/memo) and this lets it skip re-rendering. There are a few other reasons to add `useMemo` which are described further on this page. + +<DeepDive> + +#### Memoizing individual JSX nodes {/*memoizing-individual-jsx-nodes*/} + +Instead of wrapping `List` in [`memo`](/reference/react/memo), you could wrap the `<List />` JSX node itself in `useMemo`: + +```js {3,6} +export default function TodoList({ todos, tab, theme }) { + const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); + const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]); + return ( + <div className={theme}> + {children} + </div> + ); +} +``` + +The behavior would be the same. If the `visibleTodos` haven't changed, `List` won't be re-rendered. + +A JSX node like `<List items={visibleTodos} />` is an object like `{ type: List, props: { items: visibleTodos } }`. Creating this object is very cheap, but React doesn't know whether its contents is the same as last time or not. This is why by default, React will re-render the `List` component. + +However, if React sees the same exact JSX as during the previous render, it won't try to re-render your component. This is because JSX nodes are [immutable.](https://en.wikipedia.org/wiki/Immutable_object) A JSX node object could not have changed over time, so React knows it's safe to skip a re-render. However, for this to work, the node has to *actually be the same object*, not merely look the same in code. This is what `useMemo` does in this example. + +Manually wrapping JSX nodes into `useMemo` is not convenient. For example, you can't do this conditionally. This is usually why you would wrap components with [`memo`](/reference/react/memo) instead of wrapping JSX nodes. + +</DeepDive> + +<Recipes titleText="The difference between skipping re-renders and always re-rendering" titleId="examples-rerendering"> + +#### Skipping re-rendering with `useMemo` and `memo` {/*skipping-re-rendering-with-usememo-and-memo*/} + +In this example, the `List` component is **artificially slowed down** so that you can see what happens when a React component you're rendering is genuinely slow. Try switching the tabs and toggling the theme. + +Switching the tabs feels slow because it forces the slowed down `List` to re-render. That's expected because the `tab` has changed, and so you need to reflect the user's new choice on the screen. + +Next, try toggling the theme. **Thanks to `useMemo` together with [`memo`](/reference/react/memo), it’s fast despite the artificial slowdown!** The `List` skipped re-rendering because the `visibleItems` array has not changed since the last render. The `visibleItems` array has not changed because both `todos` and `tab` (which you pass as dependencies to `useMemo`) haven't changed since the last render. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { createTodos } from './utils.js'; +import TodoList from './TodoList.js'; + +const todos = createTodos(); + +export default function App() { + const [tab, setTab] = useState('all'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <button onClick={() => setTab('all')}> + All + </button> + <button onClick={() => setTab('active')}> + Active + </button> + <button onClick={() => setTab('completed')}> + Completed + </button> + <br /> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <TodoList + todos={todos} + tab={tab} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js TodoList.js active +import { useMemo } from 'react'; +import List from './List.js'; +import { filterTodos } from './utils.js' + +export default function TodoList({ todos, theme, tab }) { + const visibleTodos = useMemo( + () => filterTodos(todos, tab), + [todos, tab] + ); + return ( + <div className={theme}> + <p><b>Note: <code>List</code> is artificially slowed down!</b></p> + <List items={visibleTodos} /> + </div> + ); +} +``` + +```js List.js +import { memo } from 'react'; + +const List = memo(function List({ items }) { + console.log('[ARTIFICIALLY SLOW] Rendering <List /> with ' + items.length + ' items'); + let startTime = performance.now(); + while (performance.now() - startTime < 500) { + // Do nothing for 500 ms to emulate extremely slow code + } + + return ( + <ul> + {items.map(item => ( + <li key={item.id}> + {item.completed ? + <s>{item.text}</s> : + item.text + } + </li> + ))} + </ul> + ); +}); + +export default List; +``` + +```js utils.js +export function createTodos() { + const todos = []; + for (let i = 0; i < 50; i++) { + todos.push({ + id: i, + text: "Todo " + (i + 1), + completed: Math.random() > 0.5 + }); + } + return todos; +} + +export function filterTodos(todos, tab) { + return todos.filter(todo => { + if (tab === 'all') { + return true; + } else if (tab === 'active') { + return !todo.completed; + } else if (tab === 'completed') { + return todo.completed; + } + }); +} +``` + +```css +label { + display: block; + margin-top: 10px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + +<Solution /> + +#### Always re-rendering a component {/*always-re-rendering-a-component*/} + +In this example, the `List` implementation is also **artificially slowed down** so that you can see what happens when some React component you're rendering is genuinely slow. Try switching the tabs and toggling the theme. + +Unlike in the previous example, toggling the theme is also slow now! This is because **there is no `useMemo` call in this version,** so the `visibleTodos` is always a different array, and the slowed down `List` component can't skip re-rendering. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { createTodos } from './utils.js'; +import TodoList from './TodoList.js'; + +const todos = createTodos(); + +export default function App() { + const [tab, setTab] = useState('all'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <button onClick={() => setTab('all')}> + All + </button> + <button onClick={() => setTab('active')}> + Active + </button> + <button onClick={() => setTab('completed')}> + Completed + </button> + <br /> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <TodoList + todos={todos} + tab={tab} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js TodoList.js active +import List from './List.js'; +import { filterTodos } from './utils.js' + +export default function TodoList({ todos, theme, tab }) { + const visibleTodos = filterTodos(todos, tab); + return ( + <div className={theme}> + <p><b>Note: <code>List</code> is artificially slowed down!</b></p> + <List items={visibleTodos} /> + </div> + ); +} +``` + +```js List.js +import { memo } from 'react'; + +const List = memo(function List({ items }) { + console.log('[ARTIFICIALLY SLOW] Rendering <List /> with ' + items.length + ' items'); + let startTime = performance.now(); + while (performance.now() - startTime < 500) { + // Do nothing for 500 ms to emulate extremely slow code + } + + return ( + <ul> + {items.map(item => ( + <li key={item.id}> + {item.completed ? + <s>{item.text}</s> : + item.text + } + </li> + ))} + </ul> + ); +}); + +export default List; +``` + +```js utils.js +export function createTodos() { + const todos = []; + for (let i = 0; i < 50; i++) { + todos.push({ + id: i, + text: "Todo " + (i + 1), + completed: Math.random() > 0.5 + }); + } + return todos; +} + +export function filterTodos(todos, tab) { + return todos.filter(todo => { + if (tab === 'all') { + return true; + } else if (tab === 'active') { + return !todo.completed; + } else if (tab === 'completed') { + return todo.completed; + } + }); +} +``` + +```css +label { + display: block; + margin-top: 10px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + +However, here is the same code **with the artificial slowdown removed.** Does the lack of `useMemo` feel noticeable or not? + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import { createTodos } from './utils.js'; +import TodoList from './TodoList.js'; + +const todos = createTodos(); + +export default function App() { + const [tab, setTab] = useState('all'); + const [isDark, setIsDark] = useState(false); + return ( + <> + <button onClick={() => setTab('all')}> + All + </button> + <button onClick={() => setTab('active')}> + Active + </button> + <button onClick={() => setTab('completed')}> + Completed + </button> + <br /> + <label> + <input + type="checkbox" + checked={isDark} + onChange={e => setIsDark(e.target.checked)} + /> + Dark mode + </label> + <hr /> + <TodoList + todos={todos} + tab={tab} + theme={isDark ? 'dark' : 'light'} + /> + </> + ); +} +``` + +```js TodoList.js active +import List from './List.js'; +import { filterTodos } from './utils.js' + +export default function TodoList({ todos, theme, tab }) { + const visibleTodos = filterTodos(todos, tab); + return ( + <div className={theme}> + <List items={visibleTodos} /> + </div> + ); +} +``` + +```js List.js +import { memo } from 'react'; + +function List({ items }) { + return ( + <ul> + {items.map(item => ( + <li key={item.id}> + {item.completed ? + <s>{item.text}</s> : + item.text + } + </li> + ))} + </ul> + ); +} + +export default memo(List); +``` + +```js utils.js +export function createTodos() { + const todos = []; + for (let i = 0; i < 50; i++) { + todos.push({ + id: i, + text: "Todo " + (i + 1), + completed: Math.random() > 0.5 + }); + } + return todos; +} + +export function filterTodos(todos, tab) { + return todos.filter(todo => { + if (tab === 'all') { + return true; + } else if (tab === 'active') { + return !todo.completed; + } else if (tab === 'completed') { + return todo.completed; + } + }); +} +``` + +```css +label { + display: block; + margin-top: 10px; +} + +.dark { + background-color: black; + color: white; +} + +.light { + background-color: white; + color: black; +} +``` + +</Sandpack> + +Quite often, code without memoization works fine. If your interactions are fast enough, you don't need memoization. + +Keep in mind that you need to run React in production mode, disable [React Developer Tools](/learn/react-developer-tools), and use devices similar to the ones your app's users have in order to get a realistic sense of what's actually slowing down your app. + +<Solution /> + +</Recipes> + +--- + +### Memoizing a dependency of another Hook {/*memoizing-a-dependency-of-another-hook*/} + +Suppose you have a calculation that depends on an object created directly in the component body: + +```js {2} +function Dropdown({ allItems, text }) { + const searchOptions = { matchMode: 'whole-word', text }; + + const visibleItems = useMemo(() => { + return searchItems(allItems, searchOptions); + }, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body + // ... +``` + +Depending on an object like this defeats the point of memoization. When a component re-renders, all of the code directly inside the component body runs again. **The lines of code creating the `searchOptions` object will also run on every re-render.** Since `searchOptions` is a dependency of your `useMemo` call, and it's different every time, React will know the dependencies are different from the last time, and recalculate `searchItems` every time. + +To fix this, you could memoize the `searchOptions` object *itself* before passing it as a dependency: + +```js {2-4} +function Dropdown({ allItems, text }) { + const searchOptions = useMemo(() => { + return { matchMode: 'whole-word', text }; + }, [text]); // ✅ Only changes when text changes + + const visibleItems = useMemo(() => { + return searchItems(allItems, searchOptions); + }, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes + // ... +``` + +In the example above, if the `text` did not change, the `searchOptions` object also won't change. However, an even better fix is to move the `searchOptions` object declaration *inside* of the `useMemo` calculation function: + +```js {3} +function Dropdown({ allItems, text }) { + const visibleItems = useMemo(() => { + const searchOptions = { matchMode: 'whole-word', text }; + return searchItems(allItems, searchOptions); + }, [allItems, text]); // ✅ Only changes when allItems or text changes + // ... +``` + +**Now your calculation depends on `text` directly (which is a string and can't "accidentally" be new like an object).** + You can use a similar approach to prevent [`useEffect`](/reference/react/useEffect) from firing again unnecessarily. Before you try to optimize dependencies with `useMemo`, see if you can make them unnecessary. [Read about removing Effect dependencies.](/learn/removing-effect-dependencies) + +--- + +### Memoizing a function {/*memoizing-a-function*/} + +Suppose the `Form` component is wrapped in [`memo`.](/reference/react/memo) You want to pass a function to it as a prop: + +```js {2-7} +export default function ProductPage({ productId, referrer }) { + function handleSubmit(orderDetails) { + post('/product/' + productId + '/buy', { + referrer, + orderDetails + }); + } + + return <Form onSubmit={handleSubmit} />; +} +``` + +Similar to how `{}` always creates a different object, function declarations like `function() {}` and expressions like `() => {}` produce a *different* function on every re-render. By itself, creating a new function is not a problem. This is not something to avoid! However, if the `Form` component is memoized, presumably you want to skip re-rendering it when no props have changed. A prop that is *always* different would defeat the point of memoization. + +To memoize a function with `useMemo`, your calculation function would have to return another function: + +```js {2-3,8-9} +export default function Page({ productId, referrer }) { + const handleSubmit = useMemo(() => { + return (orderDetails) => { + post('/product/' + product.id + '/buy', { + referrer, + orderDetails + }); + }; + }, [productId, referrer]); + + return <Form onSubmit={handleSubmit} />; +} +``` + +This looks clunky! **Memoizing functions is common enough that React has a built-in Hook specifically for that. Wrap your functions into [`useCallback`](/reference/react/useCallback) instead of `useMemo`** to avoid having to write an extra nested function: + +```js {2,7} +export default function Page({ productId, referrer }) { + const handleSubmit = useCallback((orderDetails) => { + post('/product/' + product.id + '/buy', { + referrer, + orderDetails + }); + }, [productId, referrer]); + + return <Form onSubmit={handleSubmit} />; +} +``` + +The two examples above are completely equivalent. The only benefit to `useCallback` is that it lets you avoid writing an extra nested function inside. It doesn't do anything else. [Read more about `useCallback`.](/reference/react/useCallback) + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My calculation runs twice on every re-render {/*my-calculation-runs-twice-on-every-re-render*/} + +In [Strict Mode](/reference/react/StrictMode), React will call some of your functions twice instead of once: + +```js {2,5,6} +function TodoList({ todos, tab }) { + // This component function will run twice for every render. + + const visibleTodos = useMemo(() => { + // This calculation will run twice if any of the dependencies change. + return filterTodos(todos, tab); + }, [todos, tab]); + + // ... +``` + +This is expected and shouldn't break your code. + +This **development-only** behavior helps you [keep components pure.](/learn/keeping-components-pure) React uses the result of one of the calls, and ignores the result of the other call. As long as your component and calculation functions are pure, this shouldn't affect your logic. However, if they are accidentally impure, this helps you notice the mistakes and fix it. + +For example, this impure calculation function mutates an array you received as a prop: + +```js {2-3} + const visibleTodos = useMemo(() => { + // 🚩 Mistake: mutating a prop + todos.push({ id: 'last', text: 'Go for a walk!' }); + const filtered = filterTodos(todos, tab); + return filtered; + }, [todos, tab]); +``` + +Because React calls your calculation twice, you'll see the todo was added twice, so you'll know that there is a mistake. Your calculation can't change the objects that it received, but it can change any *new* objects you created during the calculation. For example, if `filterTodos` always returns a *different* array, you can mutate *that* array: + +```js {3,4} + const visibleTodos = useMemo(() => { + const filtered = filterTodos(todos, tab); + // ✅ Correct: mutating an object you created during the calculation + filtered.push({ id: 'last', text: 'Go for a walk!' }); + return filtered; + }, [todos, tab]); +``` + +Read [keeping components pure](/learn/keeping-components-pure) to learn more about purity. + +Also, check out the guides on [updating objects](/learn/updating-objects-in-state) and [updating arrays](/learn/updating-arrays-in-state) without mutation. + +--- + +### My `useMemo` call is supposed to return an object, but returns undefined {/*my-usememo-call-is-supposed-to-return-an-object-but-returns-undefined*/} + +This code doesn't work: + +```js {1-2,5} + // 🔴 You can't return an object from an arrow function with () => { + const searchOptions = useMemo(() => { + matchMode: 'whole-word', + text: text + }, [text]); +``` + +In JavaScript, `() => {` starts the arrow function body, so the `{` brace is not a part of your object. This is why it doesn't return an object, and leads to confusing mistakes. You could fix it by adding parentheses like `({` and `})`: + +```js {1-2,5} + // This works, but is easy for someone to break again + const searchOptions = useMemo(() => ({ + matchMode: 'whole-word', + text: text + }), [text]); +``` + +However, this is still confusing and too easy for someone to break by removing the parentheses. + +To avoid this mistake, write a `return` statement explicitly: + +```js {1-3,6-7} + // ✅ This works and is explicit + const searchOptions = useMemo(() => { + return { + matchMode: 'whole-word', + text: text + }; + }, [text]); +``` + +--- + +### Every time my component renders, the calculation in `useMemo` re-runs {/*every-time-my-component-renders-the-calculation-in-usememo-re-runs*/} + +Make sure you've specified the dependency array as a second argument! + +If you forget the dependency array, `useMemo` will re-run the calculation every time: + +```js {2-3} +function TodoList({ todos, tab }) { + // 🔴 Recalculates every time: no dependency array + const visibleTodos = useMemo(() => filterTodos(todos, tab)); + // ... +``` + +This is the corrected version passing the dependency array as a second argument: + +```js {2-3} +function TodoList({ todos, tab }) { + // ✅ Does not recalculate unnecessarily + const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); + // ... +``` + +If this doesn't help, then the problem is that at least one of your dependencies is different from the previous render. You can debug this problem by manually logging your dependencies to the console: + +```js + const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); + console.log([todos, tab]); +``` + +You can then right-click on the arrays from different re-renders in the console and select "Store as a global variable" for both of them. Assuming the first one got saved as `temp1` and the second one got saved as `temp2`, you can then use the browser console to check whether each dependency in both arrays is the same: + +```js +Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays? +Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays? +Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ... +``` + +When you find which dependency is breaking memoization, either find a way to remove it, or [memoize it as well.](#memoizing-a-dependency-of-another-hook) + +--- + +### I need to call `useMemo` for each list item in a loop, but it's not allowed {/*i-need-to-call-usememo-for-each-list-item-in-a-loop-but-its-not-allowed*/} + +Suppose the `Chart` component is wrapped in [`memo`](/reference/react/memo). You want to skip re-rendering every `Chart` in the list when the `ReportList` component re-renders. However, you can't call `useMemo` in a loop: + +```js {5-11} +function ReportList({ items }) { + return ( + <article> + {items.map(item => { + // 🔴 You can't call useMemo in a loop like this: + const data = useMemo(() => calculateReport(item), [item]); + return ( + <figure key={item.id}> + <Chart data={data} /> + </figure> + ); + })} + </article> + ); +} +``` + +Instead, extract a component for each item and memoize data for individual items: + +```js {5,12-18} +function ReportList({ items }) { + return ( + <article> + {items.map(item => + <Report key={item.id} item={item} /> + )} + </article> + ); +} + +function Report({ item }) { + // ✅ Call useMemo at the top level: + const data = useMemo(() => calculateReport(item), [item]); + return ( + <figure> + <Chart data={data} /> + </figure> + ); +} +``` + +Alternatively, you could remove `useMemo` and instead wrap `Report` itself in [`memo`.](/reference/react/memo) If the `item` prop does not change, `Report` will skip re-rendering, so `Chart` will skip re-rendering too: + +```js {5,6,12} +function ReportList({ items }) { + // ... +} + +const Report = memo(function Report({ item }) { + const data = calculateReport(item); + return ( + <figure> + <Chart data={data} /> + </figure> + ); +}); +``` diff --git a/beta/src/content/reference/react/useReducer.md b/beta/src/content/reference/react/useReducer.md new file mode 100644 index 000000000..70c0b5a69 --- /dev/null +++ b/beta/src/content/reference/react/useReducer.md @@ -0,0 +1,1125 @@ +--- +title: useReducer +--- + +<Intro> + +`useReducer` is a React Hook that lets you add a [reducer](/learn/extracting-state-logic-into-a-reducer) to your component. + +```js +const [state, dispatch] = useReducer(reducer, initialArg, init?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useReducer(reducer, initialArg, init?)` {/*usereducer*/} + +Call `useReducer` at the top level of your component to manage its state with a [reducer.](/learn/extracting-state-logic-into-a-reducer) + +```js +import { useReducer } from 'react'; + +function reducer(state, action) { + // ... +} + +function MyComponent() { + const [state, dispatch] = useReducer(reducer, { age: 42 }); + // ... +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `reducer`: The reducer function that specifies how the state gets updated. It must be pure, should take the state and action as arguments, and should return the next state. State and action can be of any types. +* `initialArg`: The value from which the initial state is calculated. It can be a value of any type. How the initial state is calculated from it depends on the next `init` argument. +* **optional** `init`: The initializer function that specifies how the initial state is calculated. If it's not specified, the initial state is set to `initialArg`. Otherwise, the initial state is set to the result of calling `init(initialArg)`. + +#### Returns {/*returns*/} + +`useReducer` returns an array with exactly two values: + +1. The current state. During the first render, it's set to `init(initialArg)` or `initialArg` (if there's no `init`). +2. The [`dispatch` function](#dispatch) that lets you update the state to a different value and trigger a re-render. + +#### Caveats {/*caveats*/} + +* `useReducer` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. +* In Strict Mode, React will **call your reducer and initializer twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your reducer and initializer are pure (as they should be), this should not affect the logic of your component. The result from one of the calls is ignored. + +--- + +### `dispatch` function {/*dispatch*/} + +The `dispatch` function returned by `useReducer` lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the `dispatch` function: + +```js +const [state, dispatch] = useReducer(reducer, { age: 42 }); + +function handleClick() { + dispatch({ type: 'incremented_age' }); + // ... +``` + +React will set the next state to the result of calling the `reducer` function you've provided with the current `state` and the action you've passed to `dispatch`. + +#### Parameters {/*dispatch-parameters*/} + +* `action`: The action performed by the user. It can be a value of any type. By convention, an action is usually an object with a `type` property identifying it and, optionally, other properties with additional information. + +#### Returns {/*dispatch-returns*/} + +`dispatch` functions do not have a return value. + +#### Caveats {/*setstate-caveats*/} + +* The `dispatch` function **only updates the state variable for the *next* render**. If you read the state variable after calling the `dispatch` function, [you will still get the old value](#ive-dispatched-an-action-but-logging-gives-me-the-old-state-value) that was on the screen before your call. + +* If the new value you provide is identical to the current `state`, as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison, React will **skip re-rendering the component and its children.** This is an optimization. React may still need to call your component before ignoring the result, but it shouldn't affect your code. + +* React [batches state updates.](/learn/queueing-a-series-of-state-updates) It updates the screen **after all the event handlers have run** and have called their `set` functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use [`flushSync`.](/reference/react-dom/flushsync) + +--- + +## Usage {/*usage*/} + +### Adding a reducer to a component {/*adding-a-reducer-to-a-component*/} + +Call `useReducer` at the top level of your component to manage state with a [reducer.](/learn/extracting-state-logic-into-a-reducer) + +```js [[1, 8, "state"], [2, 8, "dispatch"], [4, 8, "reducer"], [3, 8, "{ age: 42 }"]] +import { useReducer } from 'react'; + +function reducer(state, action) { + // ... +} + +function MyComponent() { + const [state, dispatch] = useReducer(reducer, { age: 42 }); + // ... +``` + +`useReducer` returns an array with exactly two items: + +1. The <CodeStep step={1}>current state</CodeStep> of this state variable, initially set to the <CodeStep step={3}>initial state</CodeStep> you provided. +2. The <CodeStep step={2}>`dispatch` function</CodeStep> that lets you change it in response to interaction. + +To update what's on the screen, call <CodeStep step={2}>`dispatch`</CodeStep> with an object representing what the user did, called an *action*: + +```js [[2, 2, "dispatch"]] +function handleClick() { + dispatch({ type: 'incremented_age' }); +} +``` + +React will pass the current state and the action to your <CodeStep step={4}>reducer function</CodeStep>. Your reducer will calculate and return the next state. React will store that next state, render your component with it, and update the UI. + +<Sandpack> + +```js +import { useReducer } from 'react'; + +function reducer(state, action) { + if (action.type === 'incremented_age') { + return { + age: state.age + 1 + }; + } + throw Error('Unknown action.'); +} + +export default function Counter() { + const [state, dispatch] = useReducer(reducer, { age: 42 }); + + return ( + <> + <button onClick={() => { + dispatch({ type: 'incremented_age' }) + }}> + Increment age + </button> + <p>Hello! You are {state.age}.</p> + </> + ); +} +``` + +```css +button { display: block; margin-top: 10px; } +``` + +</Sandpack> + +`useReducer` is very similar to [`useState`](/reference/react/useState), but it lets you move the state update logic from event handlers into a single function outside of your component. Read more about [choosing between `useState` and `useReducer`.](/learn/extracting-state-logic-into-a-reducer#comparing-usestate-and-usereducer) + +--- + +### Writing the reducer function {/*writing-the-reducer-function*/} + +A reducer function is declared like this: + +```js +function reducer(state, action) { + // ... +} +``` + +Then you need to fill in the code that will calculate and return the next state. By convention, it is common to write it as a [`switch` statement.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch) For each `case` in the `switch`, you need to calculate and return some next state. + +```js {4-7,10-13} +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + return { + name: state.name, + age: state.age + 1 + }; + } + case 'changed_name': { + return { + name: action.nextName, + age: state.age + }; + } + } + throw Error('Unknown action: ' + action.type); +} +``` + +Actions can have any shape. By convention, it's common to pass objects with a `type` property identifying the action. It should include the minimal necessary information that the reducer needs to compute the next state. + +```js {5,9-12} +function Form() { + const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 }); + + function handleButtonClick() { + dispatch({ type: 'incremented_age' }); + } + + function handleInputChange(e) { + dispatch({ + type: 'changed_name', + nextName: e.target.value + }); + } + // ... +``` + +The action type names are local to your component. [Each action describes a single interaction, even if that leads to multiple changes in data.](/learn/extracting-state-logic-into-a-reducer#writing-reducers-well) The shape of the state is arbitrary, but usually it'll be an object or an array. + +Read [extracting state logic into a reducer](/learn/extracting-state-logic-into-a-reducer) to learn more. + +<Pitfall> + +State is read-only. Don't modify any objects or arrays in state: + +```js {4,5} +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + // 🚩 Don't mutate an object in state like this: + state.age = state.age + 1; + return state; + } +``` + +Instead, always return new objects from your reducer: + +```js {4-8} +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + // ✅ Instead, return a new object + return { + ...state, + age: state.age + 1 + }; + } +``` + +Read [updating objects in state](/learn/updating-objects-in-state) and [updating arrays in state](/learn/updating-arrays-in-state) to learn more. + +</Pitfall> + +<Recipes titleText="Basic useReducer examples" titleId="examples-basic"> + +#### Form (object) {/*form-object*/} + +In this example, the reducer manages a state object with two fields: `name` and `age`. + +<Sandpack> + +```js +import { useReducer } from 'react'; + +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + return { + name: state.name, + age: state.age + 1 + }; + } + case 'changed_name': { + return { + name: action.nextName, + age: state.age + }; + } + } + throw Error('Unknown action: ' + action.type); +} + +const initialState = { name: 'Taylor', age: 42 }; + +export default function Form() { + const [state, dispatch] = useReducer(reducer, initialState); + + function handleButtonClick() { + dispatch({ type: 'incremented_age' }); + } + + function handleInputChange(e) { + dispatch({ + type: 'changed_name', + nextName: e.target.value + }); + } + + return ( + <> + <input + value={state.name} + onChange={handleInputChange} + /> + <button onClick={handleButtonClick}> + Increment age + </button> + <p>Hello, {state.name}. You are {state.age}.</p> + </> + ); +} +``` + +```css +button { display: block; margin-top: 10px; } +``` + +</Sandpack> + +<Solution /> + +#### Todo list (array) {/*todo-list-array*/} + +In this example, the reducer manages an array of tasks. The array needs to be updated [without mutation.](/learn/updating-arrays-in-state) + +<Sandpack> + +```js App.js +import { useReducer } from 'react'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +function tasksReducer(tasks, action) { + switch (action.type) { + case 'added': { + return [...tasks, { + id: action.id, + text: action.text, + done: false + }]; + } + case 'changed': { + return tasks.map(t => { + if (t.id === action.task.id) { + return action.task; + } else { + return t; + } + }); + } + case 'deleted': { + return tasks.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +export default function TaskApp() { + const [tasks, dispatch] = useReducer( + tasksReducer, + initialTasks + ); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId + }); + } + + return ( + <> + <h1>Prague itinerary</h1> + <AddTask + onAddTask={handleAddTask} + /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +let nextId = 3; +const initialTasks = [ + { id: 0, text: 'Visit Kafka Museum', done: true }, + { id: 1, text: 'Watch a puppet show', done: false }, + { id: 2, text: 'Lennon Wall pic', done: false } +]; +``` + +```js AddTask.js hidden +import { useState } from 'react'; + +export default function AddTask({ onAddTask }) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + onAddTask(text); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js hidden +import { useState } from 'react'; + +export default function TaskList({ + tasks, + onChangeTask, + onDeleteTask +}) { + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task + task={task} + onChange={onChangeTask} + onDelete={onDeleteTask} + /> + </li> + ))} + </ul> + ); +} + +function Task({ task, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + onChange({ + ...task, + text: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + onChange({ + ...task, + done: e.target.checked + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +<Solution /> + +#### Writing concise update logic with Immer {/*writing-concise-update-logic-with-immer*/} + +If updating arrays and objects without mutation feels tedious, you can use a library like [Immer](https://github.com/immerjs/use-immer#useimmerreducer) to reduce repetitive code. Immer lets you write concise code as if you were mutating objects, but under the hood it performs immutable updates: + +<Sandpack> + +```js App.js +import { useImmerReducer } from 'use-immer'; +import AddTask from './AddTask.js'; +import TaskList from './TaskList.js'; + +function tasksReducer(draft, action) { + switch (action.type) { + case 'added': { + draft.push({ + id: action.id, + text: action.text, + done: false + }); + break; + } + case 'changed': { + const index = draft.findIndex(t => + t.id === action.task.id + ); + draft[index] = action.task; + break; + } + case 'deleted': { + return draft.filter(t => t.id !== action.id); + } + default: { + throw Error('Unknown action: ' + action.type); + } + } +} + +export default function TaskApp() { + const [tasks, dispatch] = useImmerReducer( + tasksReducer, + initialTasks + ); + + function handleAddTask(text) { + dispatch({ + type: 'added', + id: nextId++, + text: text, + }); + } + + function handleChangeTask(task) { + dispatch({ + type: 'changed', + task: task + }); + } + + function handleDeleteTask(taskId) { + dispatch({ + type: 'deleted', + id: taskId + }); + } + + return ( + <> + <h1>Prague itinerary</h1> + <AddTask + onAddTask={handleAddTask} + /> + <TaskList + tasks={tasks} + onChangeTask={handleChangeTask} + onDeleteTask={handleDeleteTask} + /> + </> + ); +} + +let nextId = 3; +const initialTasks = [ + { id: 0, text: 'Visit Kafka Museum', done: true }, + { id: 1, text: 'Watch a puppet show', done: false }, + { id: 2, text: 'Lennon Wall pic', done: false }, +]; +``` + +```js AddTask.js hidden +import { useState } from 'react'; + +export default function AddTask({ onAddTask }) { + const [text, setText] = useState(''); + return ( + <> + <input + placeholder="Add task" + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + onAddTask(text); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js hidden +import { useState } from 'react'; + +export default function TaskList({ + tasks, + onChangeTask, + onDeleteTask +}) { + return ( + <ul> + {tasks.map(task => ( + <li key={task.id}> + <Task + task={task} + onChange={onChangeTask} + onDelete={onDeleteTask} + /> + </li> + ))} + </ul> + ); +} + +function Task({ task, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let taskContent; + if (isEditing) { + taskContent = ( + <> + <input + value={task.text} + onChange={e => { + onChange({ + ...task, + text: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + taskContent = ( + <> + {task.text} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={task.done} + onChange={e => { + onChange({ + ...task, + done: e.target.checked + }); + }} + /> + {taskContent} + <button onClick={() => onDelete(task.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Avoiding recreating the initial state {/*avoiding-recreating-the-initial-state*/} + +React saves the initial state once and ignores it on the next renders. + +```js +function createInitialState(username) { + // ... +} + +function TodoList({ username }) { + const [state, dispatch] = useReducer(reducer, createInitialState(username)); + // ... +``` + +Although the result of `createInitialState(username)` is only used for the initial render, you're still calling this function on every render. This can be wasteful if it's creating large arrays or performing expensive calculations. + +To solve this, you may **pass it as an _initializer_ function** to `useReducer` as the third argument instead: + +```js {6} +function createInitialState(username) { + // ... +} + +function TodoList({ username }) { + const [state, dispatch] = useReducer(reducer, username, createInitialState); + // ... +``` + +Notice that you’re passing `createInitialState`, which is the *function itself*, and not `createInitialState()`, which is the result of calling it. This way, the initial state does not get re-created after initialization. + +In the above example, `createInitialState` takes a `username` argument. If your initializer doesn't need any information to compute the initial state, you may pass `null` as the second argument to `useReducer`. + +<Recipes titleText="The difference between passing an initializer and passing the initial state directly" titleId="examples-initializer"> + +#### Passing the initializer function {/*passing-the-initializer-function*/} + +This example passes the initializer function, so the `createInitialState` function only runs during initialization. It does not run when component re-renders, such as when you type into the input. + +<Sandpack> + +```js App.js hidden +import TodoList from './TodoList.js'; + +export default function App() { + return <TodoList username="Taylor" />; +} +``` + +```js TodoList.js active +import { useReducer } from 'react'; + +function createInitialState(username) { + const initialTodos = []; + for (let i = 0; i < 50; i++) { + initialTodos.push({ + id: i, + text: username + "'s task #" + (i + 1) + }); + } + return { + draft: '', + todos: initialTodos, + }; +} + +function reducer(state, action) { + switch (action.type) { + case 'changed_draft': { + return { + draft: action.nextDraft, + todos: state.todos, + }; + }; + case 'added_todo': { + return { + draft: '', + todos: [{ + id: state.todos.length, + text: state.draft + }, ...state.todos] + } + } + } + throw Error('Unknown action: ' + action.type); +} + +export default function TodoList({ username }) { + const [state, dispatch] = useReducer( + reducer, + username, + createInitialState + ); + return ( + <> + <input + value={state.draft} + onChange={e => { + dispatch({ + type: 'changed_draft', + nextDraft: e.target.value + }) + }} + /> + <button onClick={() => { + dispatch({ type: 'added_todo' }); + }}>Add</button> + <ul> + {state.todos.map(item => ( + <li key={item.id}> + {item.text} + </li> + ))} + </ul> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +#### Passing the initial state directly {/*passing-the-initial-state-directly*/} + +This example **does not** pass the initializer function, so the `createInitialState` function runs on every render, such as when you type into the input. There is no observable difference in behavior, but this code is less efficient. + +<Sandpack> + +```js App.js hidden +import TodoList from './TodoList.js'; + +export default function App() { + return <TodoList username="Taylor" />; +} +``` + +```js TodoList.js active +import { useReducer } from 'react'; + +function createInitialState(username) { + const initialTodos = []; + for (let i = 0; i < 50; i++) { + initialTodos.push({ + id: i, + text: username + "'s task #" + (i + 1) + }); + } + return { + draft: '', + todos: initialTodos, + }; +} + +function reducer(state, action) { + switch (action.type) { + case 'changed_draft': { + return { + draft: action.nextDraft, + todos: state.todos, + }; + }; + case 'added_todo': { + return { + draft: '', + todos: [{ + id: state.todos.length, + text: state.draft + }, ...state.todos] + } + } + } + throw Error('Unknown action: ' + action.type); +} + +export default function TodoList({ username }) { + const [state, dispatch] = useReducer( + reducer, + createInitialState(username) + ); + return ( + <> + <input + value={state.draft} + onChange={e => { + dispatch({ + type: 'changed_draft', + nextDraft: e.target.value + }) + }} + /> + <button onClick={() => { + dispatch({ type: 'added_todo' }); + }}>Add</button> + <ul> + {state.todos.map(item => ( + <li key={item.id}> + {item.text} + </li> + ))} + </ul> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I've dispatched an action, but logging gives me the old state value {/*ive-dispatched-an-action-but-logging-gives-me-the-old-state-value*/} + +Calling the `dispatch` function **does not change state in the running code**: + +```js {4,5,8} +function handleClick() { + console.log(state.age); // 42 + + dispatch({ type: 'incremented_age' }); // Request a re-render with 43 + console.log(state.age); // Still 42! + + setTimeout(() => { + console.log(state.age); // Also 42! + }, 5000); +} +``` + +This is because [states behaves like a snapshot.](/learn/state-as-a-snapshot) Updating state requests another render with the new state value, but does not affect the `state` JavaScript variable in your already-running event handler. + +If you need to guess the next state value, you can calculate it manually by calling the reducer yourself: + +```js +const action = { type: 'incremented_age' }; +dispatch(action); + +const nextState = reducer(state, action); +console.log(state); // { age: 42 } +console.log(nextState); // { age: 43 } +``` + +--- + +### I've dispatched an action, but the screen doesn't update {/*ive-dispatched-an-action-but-the-screen-doesnt-update*/} + +React will **ignore your update if the next state is equal to the previous state,** as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. This usually happens when you change an object or an array in state directly: + +```js {4-5,9-10} +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + // 🚩 Wrong: mutating existing object + state.age++; + return state; + } + case 'changed_name': { + // 🚩 Wrong: mutating existing object + state.name = action.nextName; + return state; + } + // ... + } +} +``` + +You mutated an existing `state` object and returned it, so React ignored the update. To fix this, you need to ensure that you're always [_replacing_ objects and arrays in state instead of _mutating_ them](#updating-objects-and-arrays-in-state): + +```js {4-8,11-15} +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + // ✅ Correct: creating a new object + return { + ...state, + age: state.age + 1 + }; + } + case 'changed_name': { + // ✅ Correct: creating a new object + return { + ...state, + name: action.nextName + }; + } + // ... + } +} +``` + +--- + +### A part of my reducer state becomes undefined after dispatching {/*a-part-of-my-reducer-state-becomes-undefined-after-dispatching*/} + +Make sure that every `case` branch **copies all of the existing fields** when returning the new state: + +```js {5} +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + return { + ...state, // Don't forget this! + age: state.age + 1 + }; + } + // ... +``` + +Without `...state` above, the returned next state would only contain the `age` field and nothing else. + +--- + +### My entire reducer state becomes undefined after dispatching {/*my-entire-reducer-state-becomes-undefined-after-dispatching*/} + +If your state unexpectedly becomes `undefined`, you're likely forgetting to `return` state in one of the cases, or your action type doesn't match any of the `case` statements. To find why, throw an error outside the `switch`: + +```js {10} +function reducer(state, action) { + switch (action.type) { + case 'incremented_age': { + // ... + } + case 'edited_name': { + // ... + } + } + throw Error('Unknown action: ' + action.type); +} +``` + +You can also use a static type checker like TypeScript to catch such mistakes. + +--- + +### I'm getting an error: "Too many re-renders" {/*im-getting-an-error-too-many-re-renders*/} + +You might get an error that says: `Too many re-renders. React limits the number of renders to prevent an infinite loop.` Typically, this means that you're unconditionally dispatching an action *during render*, so your component enters a loop: render, dispatch (which causes a render), render, dispatch (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler: + +```js {1-2} +// 🚩 Wrong: calls the handler during render +return <button onClick={handleClick()}>Click me</button> + +// ✅ Correct: passes down the event handler +return <button onClick={handleClick}>Click me</button> + +// ✅ Correct: passes down an inline function +return <button onClick={(e) => handleClick(e)}>Click me</button> +``` + +If you can't find the cause of this error, click on the arrow next to the error in the console and look through the JavaScript stack to find the specific `dispatch` function call responsible for the error. + +--- + +### My reducer or initializer function runs twice {/*my-reducer-or-initializer-function-runs-twice*/} + +In [Strict Mode](/reference/react/StrictMode), React will call your reducer and initializer functions twice. This shouldn't break your code. + +This **development-only** behavior helps you [keep components pure.](/learn/keeping-components-pure) React uses the result of one of the calls, and ignores the result of the other call. As long as your component, initializer, and reducer functions are pure, this shouldn't affect your logic. However, if they are accidentally impure, this helps you notice the mistakes and fix it. + +For example, this impure reducer function mutates an array in state: + +```js {4-6} +function reducer(state, action) { + switch (action.type) { + case 'added_todo': { + // 🚩 Mistake: mutating state + state.todos.push({ id: nextId++, text: action.text }); + return state; + } + // ... + } +} +``` + +Because React calls your reducer function twice, you'll see the todo was added twice, so you'll know that there is a mistake. In this example, you can fix the mistake by [replacing the array instead of mutating it](#updating-objects-and-arrays-in-state): + +```js {4-11} +function reducer(state, action) { + switch (action.type) { + case 'added_todo': { + // ✅ Correct: replacing with new state + return { + ...state, + todos: [ + ...state.todos, + { id: nextId++, text: action.text } + ] + }; + } + // ... + } +} +``` + +Now that this reducer function is pure, calling it an extra time doesn't make a difference in behavior. This is why React calling it twice helps you find mistakes. **Only component, initializer, and reducer functions need to be pure.** Event handlers don't need to be pure, so React will never call your event handlers twice. + +Read [keeping components pure](/learn/keeping-components-pure) to learn more. diff --git a/beta/src/content/reference/react/useRef.md b/beta/src/content/reference/react/useRef.md new file mode 100644 index 000000000..af4371b38 --- /dev/null +++ b/beta/src/content/reference/react/useRef.md @@ -0,0 +1,596 @@ +--- +title: useRef +--- + +<Intro> + +`useRef` is a React Hook that lets you reference a value that's not needed for rendering. + +```js +const ref = useRef(initialValue) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useRef(initialValue)` {/*useref*/} + +Call `useRef` at the top level of your component to declare a [ref.](/learn/referencing-values-with-refs) + +```js +import { useRef } from 'react'; + +function MyComponent() { + const intervalRef = useRef(0); + const inputRef = useRef(null); + // ... +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `initialValue`: The value you want the ref object's `current` property to be initially. It can be a value of any type. This argument is ignored after the initial render. + +#### Returns {/*returns*/} + +`useRef` returns an object with a single property: + +* `current`: Initially, it's set to the `initialValue` you have passed. You can later set it to something else. If you pass the ref object to React as a `ref` attribute to a JSX node, React will set its `current` property. + +On the next renders, `useRef` will return the same object. + +#### Caveats {/*caveats*/} + +* You can mutate the `ref.current` property. Unlike state, it is mutable. However, if it holds an object that is used for rendering (for example, a piece of your state), then you shouldn't mutate that object. +* When you change the `ref.current` property, React does not re-render your component. React is not aware of when you change it because a ref is a plain JavaScript object. +* Do not write _or read_ `ref.current` during rendering, except for [initialization.](#avoiding-recreating-the-ref-contents) This makes your component's behavior unpredictable. +* In Strict Mode, React will **call your component function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. This means that each ref object will be created twice, and one of the versions will be discarded. If your component function is pure (as it should be), this should not affect the logic of your component. + +--- + +## Usage {/*usage*/} + +### Referencing a value with a ref {/*referencing-a-value-with-a-ref*/} + +Call `useRef` at the top level of your component to declare one or more [refs.](/learn/referencing-values-with-refs) + +```js [[1, 4, "intervalRef"], [3, 4, "0"]] +import { useRef } from 'react'; + +function Stopwatch() { + const intervalRef = useRef(0); + // ... +``` + +`useRef` returns a <CodeStep step={1}>ref object</CodeStep> with a single <CodeStep step={2}>`current` property</CodeStep> initially set to the <CodeStep step={3}>initial value</CodeStep> you provided. + +On the next renders, `useRef` will return the same object. You can change its `current` property to store information and read it later. This might remind you of [state](/reference/react/useState), but there is an important difference. + +**Changing a ref does not trigger a re-render.** This means refs are perfect for storing information that doesn't affect the visual output of your component. For example, if you need to store an [interval ID](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) and retrieve it later, you can put it in a ref. To update the value inside the ref, you need to manually change its <CodeStep step={2}>`current` property</CodeStep>: + +```js [[2, 5, "intervalRef.current"]] +function handleStartClick() { + const intervalId = setInterval(() => { + // ... + }, 1000); + intervalRef.current = intervalId; +} +``` + +Later, you can read that interval ID from the ref so that you can call [clear that interval](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval): + +```js [[2, 2, "intervalRef.current"]] +function handleStopClick() { + const intervalId = intervalRef.current; + clearInterval(intervalId); +} +``` + +By using a ref, you ensure that: + +- You can **store information** between re-renders (unlike regular variables, which reset on every render). +- Changing it **does not trigger a re-render** (unlike state variables, which trigger a re-render). +- The **information is local** to each copy of your component (unlike the variables outside, which are shared). + +Changing a ref does not trigger a re-render, so refs are not appropriate for storing information that you want to display on the screen. Use state for that instead. Read more about [choosing between `useRef` and `useState`.](/learn/referencing-values-with-refs#differences-between-refs-and-state) + +<Recipes titleText="Examples of referencing a value with useRef" titleId="examples-value"> + +#### Click counter {/*click-counter*/} + +This component uses a ref to keep track of how many times the button was clicked. Note that it's okay to use a ref instead of state here because the click count is only read and written in an event handler. + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Counter() { + let ref = useRef(0); + + function handleClick() { + ref.current = ref.current + 1; + alert('You clicked ' + ref.current + ' times!'); + } + + return ( + <button onClick={handleClick}> + Click me! + </button> + ); +} +``` + +</Sandpack> + +If you show `{ref.current}` in the JSX, the number won't update on click. This is because setting `ref.current` does not trigger a re-render. Information that's used for rendering should be state instead. + +<Solution /> + +#### A stopwatch {/*a-stopwatch*/} + +This example uses a combination of state and refs. Both `startTime` and `now` are state variables because they are used for rendering. But we also need to hold an [interval ID](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) so that we can stop the interval on button press. Since the interval ID is not used for rendering, it's appropriate to keep it in a ref, and manually update it. + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function Stopwatch() { + const [startTime, setStartTime] = useState(null); + const [now, setNow] = useState(null); + const intervalRef = useRef(null); + + function handleStart() { + setStartTime(Date.now()); + setNow(Date.now()); + + clearInterval(intervalRef.current); + intervalRef.current = setInterval(() => { + setNow(Date.now()); + }, 10); + } + + function handleStop() { + clearInterval(intervalRef.current); + } + + let secondsPassed = 0; + if (startTime != null && now != null) { + secondsPassed = (now - startTime) / 1000; + } + + return ( + <> + <h1>Time passed: {secondsPassed.toFixed(3)}</h1> + <button onClick={handleStart}> + Start + </button> + <button onClick={handleStop}> + Stop + </button> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +<Pitfall> + +**Do not write _or read_ `ref.current` during rendering.** + +React expects that the body of your component [behaves like a pure function](/learn/keeping-components-pure): + +- If the inputs ([props](/learn/passing-props-to-a-component), [state](/learn/state-a-components-memory), and [context](/learn/passing-data-deeply-with-context)) are the same, it should return exactly the same JSX. +- Calling it in a different order or with different arguments should not affect the results of other calls. + +Reading or writing a ref **during rendering** breaks these expectations. + +```js {3-4,6-7} +function MyComponent() { + // ... + // 🚩 Don't write a ref during rendering + myRef.current = 123; + // ... + // 🚩 Don't read a ref during rendering + return <h1>{myOtherRef.current}</h1>; +} +``` + +You can read or write refs **from event handlers or effects instead**. + +```js {4-5,9-10} +function MyComponent() { + // ... + useEffect(() => { + // ✅ You can read or write refs in effects + myRef.current = 123; + }); + // ... + function handleClick() { + // ✅ You can read or write refs in event handlers + doSomething(myOtherRef.current); + } + // ... +} +``` + +If you *have to* read [or write](/reference/react/useState#storing-information-from-previous-renders) something during rendering, [use state](/reference/react/useState) instead. + +When you break these rules, your component might still work, but most of the newer features we're adding to React will rely on these expectations. Read more about [keeping your components pure.](/learn/keeping-components-pure#where-you-can-cause-side-effects) + +</Pitfall> + +--- + +### Manipulating the DOM with a ref {/*manipulating-the-dom-with-a-ref*/} + +It's particularly common to use a ref to manipulate the [DOM.](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API) React has built-in support for this. + +First, declare a <CodeStep step={1}>ref object</CodeStep> with an <CodeStep step={3}>initial value</CodeStep> of `null`: + +```js [[1, 4, "inputRef"], [3, 4, "null"]] +import { useRef } from 'react'; + +function MyComponent() { + const inputRef = useRef(null); + // ... +``` + +Then pass your ref object as the `ref` attribute to the JSX of the DOM node you want to manipulate: + +```js [[1, 2, "inputRef"]] + // ... + return <input ref={inputRef} />; +``` + +After React creates the DOM node and puts it on the screen, React will set the <CodeStep step={2}>`current` property</CodeStep> of your ref object to that DOM node. Now you can access the `<input>`'s DOM node and call methods like [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus): + +```js [[2, 2, "inputRef.current"]] + function handleClick() { + inputRef.current.focus(); + } +``` + +React will set the `current` property back to `null` when the node is removed from the screen. + +Read more about [manipulating the DOM with refs.](/learn/manipulating-the-dom-with-refs) + +<Recipes titleText="Examples of manipulating the DOM with useRef" titleId="examples-dom"> + +#### Focusing a text input {/*focusing-a-text-input*/} + +In this example, clicking the button will focus the input: + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <input ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +#### Scrolling an image into view {/*scrolling-an-image-into-view*/} + +In this example, clicking the button will scroll an image into view. It uses a ref to the list DOM node, and then calls DOM [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) API to find the image we want to scroll to. + +<Sandpack> + +```js +import { useRef } from 'react'; + +export default function CatFriends() { + const listRef = useRef(null); + + function scrollToIndex(index) { + const listNode = listRef.current; + // This line assumes a particular DOM structure: + const imgNode = listNode.querySelectorAll('li > img')[index]; + imgNode.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }); + } + + return ( + <> + <nav> + <button onClick={() => scrollToIndex(0)}> + Tom + </button> + <button onClick={() => scrollToIndex(1)}> + Maru + </button> + <button onClick={() => scrollToIndex(2)}> + Jellylorum + </button> + </nav> + <div> + <ul ref={listRef}> + <li> + <img + src="https://placekitten.com/g/200/200" + alt="Tom" + /> + </li> + <li> + <img + src="https://placekitten.com/g/300/200" + alt="Maru" + /> + </li> + <li> + <img + src="https://placekitten.com/g/250/200" + alt="Jellylorum" + /> + </li> + </ul> + </div> + </> + ); +} +``` + +```css +div { + width: 100%; + overflow: hidden; +} + +nav { + text-align: center; +} + +button { + margin: .25rem; +} + +ul, +li { + list-style: none; + white-space: nowrap; +} + +li { + display: inline; + padding: 0.5rem; +} +``` + +</Sandpack> + +<Solution /> + +#### Playing and pausing a video {/*playing-and-pausing-a-video*/} + +This example uses a ref to call [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) and [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) on a `<video>` DOM node. + +<Sandpack> + +```js +import { useState, useRef } from 'react'; + +export default function VideoPlayer() { + const [isPlaying, setIsPlaying] = useState(false); + const ref = useRef(null); + + function handleClick() { + const nextIsPlaying = !isPlaying; + setIsPlaying(nextIsPlaying); + + if (nextIsPlaying) { + ref.current.play(); + } else { + ref.current.pause(); + } + } + + return ( + <> + <button onClick={handleClick}> + {isPlaying ? 'Pause' : 'Play'} + </button> + <video + width="250" + ref={ref} + onPlay={() => setIsPlaying(true)} + onPause={() => setIsPlaying(false)} + > + <source + src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" + type="video/mp4" + /> + </video> + </> + ); +} +``` + +```css +button { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +<Solution /> + +#### Exposing a ref to your own component {/*exposing-a-ref-to-your-own-component*/} + +Sometimes, you may want to let the parent component manipulate the DOM inside of your component. For example, maybe you're writing a `MyInput` component, but you want the parent to be able to focus the input (which the parent has no access to). You can use a combination of `useRef` to hold the input and [`forwardRef`](/reference/react/forwardRef) to expose it to the parent component. Read a [detailed walkthrough](/learn/manipulating-the-dom-with-refs#accessing-another-components-dom-nodes) here. + +<Sandpack> + +```js +import { forwardRef, useRef } from 'react'; + +const MyInput = forwardRef((props, ref) => { + return <input {...props} ref={ref} />; +}); + +export default function Form() { + const inputRef = useRef(null); + + function handleClick() { + inputRef.current.focus(); + } + + return ( + <> + <MyInput ref={inputRef} /> + <button onClick={handleClick}> + Focus the input + </button> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Avoiding recreating the ref contents {/*avoiding-recreating-the-ref-contents*/} + +React saves the initial ref value once and ignores it on the next renders. + +```js +function Video() { + const playerRef = useRef(new VideoPlayer()); + // ... +``` + +Although the result of `new VideoPlayer()` is only used for the initial render, you're still calling this function on every render. This can be wasteful if it's creating expensive objects. + +To solve it, you may initialize the ref like this instead: + +```js +function Video() { + const playerRef = useRef(null); + if (playerRef.current === null) { + playerRef.current = new VideoPlayer(); + } + // ... +``` + +Normally, writing or reading `ref.current` during render is not allowed. However, it's fine in this case because the result is always the same, and the condition only executes during initialization so it's fully predictable. + +<DeepDive> + +#### How to avoid null checks when initializing useRef later {/*how-to-avoid-null-checks-when-initializing-use-ref-later*/} + +If you use a type checker and don't want to always check for `null`, you can try a pattern like this instead: + +```js +function Video() { + const playerRef = useRef(null); + + function getPlayer() { + if (playerRef.current !== null) { + return playerRef.current; + } + const player = new VideoPlayer(); + playerRef.current = player; + return player; + } + + // ... +``` + +Here, the `playerRef` itself is nullable. However, you should be able to convince your type checker that there is no case in which `getPlayer()` returns `null`. Then use `getPlayer()` in your event handlers. + +</DeepDive> + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I can't get a ref to a custom component {/*i-cant-get-a-ref-to-a-custom-component*/} + +If you try to pass a `ref` to your own component like this: + +```js +const inputRef = useRef(null); + +return <MyInput ref={inputRef} />; +``` + +You might get an error in the console: + +<ConsoleBlock level="error"> + +Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? + +</ConsoleBlock> + +By default, your own components don't expose refs to the DOM nodes inside them. + +To fix this, find the component that you want to get a ref to: + +```js +export default function MyInput({ value, onChange }) { + return ( + <input + value={value} + onChange={onChange} + /> + ); +} +``` + +And then wrap it in [`forwardRef`](/reference/react/forwardRef) like this: + +```js {3,8} +import { forwardRef } from 'react'; + +const MyInput = forwardRef(({ value, onChange }, ref) => { + return ( + <input + value={value} + onChange={onChange} + ref={ref} + /> + ); +}); + +export default MyInput; +``` + +Then the parent component can get a ref to it. + +Read more about [accessing another component's DOM nodes.](/learn/manipulating-the-dom-with-refs#accessing-another-components-dom-nodes) diff --git a/beta/src/content/reference/react/useState.md b/beta/src/content/reference/react/useState.md new file mode 100644 index 000000000..86dd260b1 --- /dev/null +++ b/beta/src/content/reference/react/useState.md @@ -0,0 +1,1290 @@ +--- +title: useState +--- + +<Intro> + +`useState` is a React Hook that lets you add a [state variable](/learn/state-a-components-memory) to your component. + +```js +const [state, setState] = useState(initialState) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useState(initialState)` {/*usestate*/} + +Call `useState` at the top level of your component to declare a [state variable.](/learn/state-a-components-memory) + +```js +import { useState } from 'react'; + +function MyComponent() { + const [age, setAge] = useState(28); + const [name, setName] = useState('Taylor'); + const [todos, setTodos] = useState(() => createTodos()); + // ... +``` + +The convention is to name state variables like `[something, setSomething]` using [array destructuring.](https://javascript.info/destructuring-assignment) + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `initialState`: The value you want the state to be initially. It can be a value of any type, but there is a special behavior for functions. This argument is ignored after the initial render. + * If you pass a function as `initialState`, it will be treated as an _initializer function_. It should be pure, should take no arguments, and should return a value of any type. React will call your initializer function when initializing the component, and store its return value as the initial state. [See an example below.](#avoiding-recreating-the-initial-state) + +#### Returns {/*returns*/} + +`useState` returns an array with exactly two values: + +1. The current state. During the first render, it will match the `initialState` you have passed. +2. The [`set` function](#setstate) that lets you update the state to a different value and trigger a re-render. + +#### Caveats {/*caveats*/} + +* `useState` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. +* In Strict Mode, React will **call your initializer function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your initializer function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored. + +--- + +### `set` functions, like `setSomething(nextState)` {/*setstate*/} + +The `set` function returned by `useState` lets you update the state to a different value and trigger a re-render. You can pass the next state directly, or a function that calculates it from the previous state: + +```js +const [name, setName] = useState('Edward'); + +function handleClick() { + setName('Taylor'); + setAge(a => a + 1); + // ... +``` + +#### Parameters {/*setstate-parameters*/} + +* `nextState`: The value that you want the state to be. It can be a value of any type, but there is a special behavior for functions. + * If you pass a function as `nextState`, it will be treated as an _updater function_. It must be pure, should take the pending state as its only argument, and should return the next state. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying all of the queued updaters to the previous state. [See an example below.](#updating-state-based-on-the-previous-state) + +#### Returns {/*setstate-returns*/} + +`set` functions do not have a return value. + +#### Caveats {/*setstate-caveats*/} + +* The `set` function **only updates the state variable for the *next* render**. If you read the state variable after calling the `set` function, [you will still get the old value](#ive-updated-the-state-but-logging-gives-me-the-old-value) that was on the screen before your call. + +* If the new value you provide is identical to the current `state`, as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison, React will **skip re-rendering the component and its children.** This is an optimization. Although in some cases React may still need to call your component before skipping the children, it shouldn't affect your code. + +* React [batches state updates.](/learn/queueing-a-series-of-state-updates) It updates the screen **after all the event handlers have run** and have called their `set` functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use [`flushSync`.](/reference/react-dom/flushSync) + +* Calling the `set` function *during rendering* is only allowed from within the currently rendering component. React will discard its output and immediately attempt to render it again with the new state. This pattern is rarely needed, but you can use it to **store information from the previous renders**. [See an example below.](#storing-information-from-previous-renders) + +* In Strict Mode, React will **call your updater function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your updater function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored. + +--- + +## Usage {/*usage*/} + +### Adding state to a component {/*adding-state-to-a-component*/} + +Call `useState` at the top level of your component to declare one or more [state variables.](/learn/state-a-components-memory) + +```js [[1, 4, "age"], [2, 4, "setAge"], [3, 4, "42"], [1, 5, "name"], [2, 5, "setName"], [3, 5, "'Taylor'"]] +import { useState } from 'react'; + +function MyComponent() { + const [age, setAge] = useState(42); + const [name, setName] = useState('Taylor'); + // ... +``` + +The convention is to name state variables like `[something, setSomething]` using [array destructuring.](https://javascript.info/destructuring-assignment) + +`useState` returns an array with exactly two items: + +1. The <CodeStep step={1}>current state</CodeStep> of this state variable, initially set to the <CodeStep step={3}>initial state</CodeStep> you provided. +2. The <CodeStep step={2}>`set` function</CodeStep> that lets you change it to any other value in response to interaction. + +To update what’s on the screen, call the `set` function with some next state: + +```js [[2, 2, "setName"]] +function handleClick() { + setName('Robin'); +} +``` + +React will store the next state, render your component again with the new values, and update the UI. + +<Pitfall> + +Calling the `set` function [**does not** change the current state in the already executing code](#ive-updated-the-state-but-logging-gives-me-the-old-value): + +```js {3} +function handleClick() { + setName('Robin'); + console.log(name); // Still "Taylor"! +} +``` + +It only affects what `useState` will return starting from the *next* render. + +</Pitfall> + +<Recipes titleText="Basic useState examples" titleId="examples-basic"> + +#### Counter (number) {/*counter-number*/} + +In this example, the `count` state variable holds a number. Clicking the button increments it. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ( + <button onClick={handleClick}> + You pressed me {count} times + </button> + ); +} +``` + +</Sandpack> + +<Solution /> + +#### Text field (string) {/*text-field-string*/} + +In this example, the `text` state variable holds a string. When you type, `handleChange` reads the latest input value from the browser input DOM element, and calls `setText` to update the state. This allows you to display the current `text` below. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function MyInput() { + const [text, setText] = useState('hello'); + + function handleChange(e) { + setText(e.target.value); + } + + return ( + <> + <input value={text} onChange={handleChange} /> + <p>You typed: {text}</p> + <button onClick={() => setText('hello')}> + Reset + </button> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +#### Checkbox (boolean) {/*checkbox-boolean*/} + +In this example, the `liked` state variable holds a boolean. When you click the input, `setLiked` updates the `liked` state variable with whether the browser checkbox input is checked. The `liked` variable is used to render the text below the checkbox. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function MyCheckbox() { + const [liked, setLiked] = useState(true); + + function handleChange(e) { + setLiked(e.target.checked); + } + + return ( + <> + <label> + <input + type="checkbox" + checked={liked} + onChange={handleChange} + /> + I liked this + </label> + <p>You {liked ? 'liked' : 'did not like'} this.</p> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +#### Form (two variables) {/*form-two-variables*/} + +You can declare more than one state variable in the same component. Each state variable is completely independent. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [name, setName] = useState('Taylor'); + const [age, setAge] = useState(42); + + return ( + <> + <input + value={name} + onChange={e => setName(e.target.value)} + /> + <button onClick={() => setAge(age + 1)}> + Increment age + </button> + <p>Hello, {name}. You are {age}.</p> + </> + ); +} +``` + +```css +button { display: block; margin-top: 10px; } +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Updating state based on the previous state {/*updating-state-based-on-the-previous-state*/} + +Suppose the `age` is `42`. This handler calls `setAge(age + 1)` three times: + +```js +function handleClick() { + setAge(age + 1); // setAge(42 + 1) + setAge(age + 1); // setAge(42 + 1) + setAge(age + 1); // setAge(42 + 1) +} +``` + +However, after one click, `age` will only be `43` rather than `45`! This is because calling the `set` function [does not update](/learn/state-as-a-snapshot) the `age` state variable in the already running code. So each `setAge(age + 1)` call becomes `setAge(43)`. + +To solve this problem, **you may pass an *updater function*** to `setAge` instead of the next state: + +```js [[1, 2, "a", 0], [2, 2, "a + 1"], [1, 3, "a", 0], [2, 3, "a + 1"], [1, 4, "a", 0], [2, 4, "a + 1"]] +function handleClick() { + setAge(a => a + 1); // setAge(42 => 43) + setAge(a => a + 1); // setAge(43 => 44) + setAge(a => a + 1); // setAge(44 => 45) +} +``` + +Here, `a => a + 1` is your updater function. It takes the <CodeStep step={1}>pending state</CodeStep> and calculates the <CodeStep step={2}>next state</CodeStep> from it. + +React puts your updater functions in a [queue.](/learn/queueing-a-series-of-state-updates) Then, during the next render, it will call them in the same order: + +1. `a => a + 1` will receive `42` as the pending state and return `43` as the next state. +1. `a => a + 1` will receive `43` as the pending state and return `44` as the next state. +1. `a => a + 1` will receive `44` as the pending state and return `45` as the next state. + +There are no other queued updates, so React will store `45` as the current state in the end. + +By convention, it's common to name the pending state argument for the first letter of the state variable name, like `a` for `age`. However, you may also call it like `prevAge` or something else that you find clearer. + +React may [call your updaters twice](#my-initializer-or-updater-function-runs-twice) in development to verify that they are [pure.](/learn/keeping-components-pure) + +<DeepDive> + +#### Is using an updater always preferred? {/*is-using-an-updater-always-preferred*/} + +You might hear a recommendation to always write code like `setAge(a => a + 1)` if the state you're setting is calculated from the previous state. There is no harm in it, but it is also not always necessary. + +In most cases, there is no difference between these two approaches. React always makes sure that for intentional user actions, like clicks, the `age` state variable would be updated before the next click. This means there is no risk of a click handler seeing a "stale" `age` at the beginning of the event handler. + +However, if you do multiple updates within the same event, updaters can be helpful. They're also helpful if accessing the state variable itself is inconvenient (you might run into this when optimizing re-renders). + +If you prefer consistency over slightly more verbose syntax, it's reasonable to always write an updater if the state you're setting is calculated from the previous state. If it's calculated from the previous state of some *other* state variable, you might want to combine them into one object and [use a reducer.](/learn/extracting-state-logic-into-a-reducer) + +</DeepDive> + +<Recipes titleText="The difference between passing an updater and passing the next state directly" titleId="examples-updater"> + +#### Passing the updater function {/*passing-the-updater-function*/} + +This example passes the updater function, so the "+3" button works. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [age, setAge] = useState(42); + + function increment() { + setAge(a => a + 1); + } + + return ( + <> + <h1>Your age: {age}</h1> + <button onClick={() => { + increment(); + increment(); + increment(); + }}>+3</button> + <button onClick={() => { + increment(); + }}>+1</button> + </> + ); +} +``` + +```css +button { display: block; margin: 10px; font-size: 20px; } +h1 { display: block; margin: 10px; } +``` + +</Sandpack> + +<Solution /> + +#### Passing the next state directly {/*passing-the-next-state-directly*/} + +This example **does not** pass the updater function, so the "+3" button **doesn't work as intended**. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Counter() { + const [age, setAge] = useState(42); + + function increment() { + setAge(age + 1); + } + + return ( + <> + <h1>Your age: {age}</h1> + <button onClick={() => { + increment(); + increment(); + increment(); + }}>+3</button> + <button onClick={() => { + increment(); + }}>+1</button> + </> + ); +} +``` + +```css +button { display: block; margin: 10px; font-size: 20px; } +h1 { display: block; margin: 10px; } +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Updating objects and arrays in state {/*updating-objects-and-arrays-in-state*/} + +You can put objects and arrays into state. In React, state is considered read-only, so **you should *replace* it rather than *mutate* your existing objects**. For example, if you have a `form` object in state, don't update it like this: + +```js +// 🚩 Don't mutate an object in state like this: +form.firstName = 'Taylor'; +``` + +Instead, replace the whole object by creating a new one: + +```js +// ✅ Replace state with a new object +setForm({ + ...form, + firstName: 'Taylor' +}); +``` + +Read [updating objects in state](/learn/updating-objects-in-state) and [updating arrays in state](/learn/updating-arrays-in-state) to learn more. + +<Recipes titleText="Examples of objects and arrays in state" titleId="examples-objects"> + +#### Form (object) {/*form-object*/} + +In this example, the `form` state variable holds an object. Each input has a change handler that calls `setForm` with the next state of the entire form. The `{ ...form }` spread syntax ensures that the state object is replaced rather than mutated. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [form, setForm] = useState({ + firstName: 'Barbara', + lastName: 'Hepworth', + email: 'bhepworth@sculpture.com', + }); + + return ( + <> + <label> + First name: + <input + value={form.firstName} + onChange={e => { + setForm({ + ...form, + firstName: e.target.value + }); + }} + /> + </label> + <label> + Last name: + <input + value={form.lastName} + onChange={e => { + setForm({ + ...form, + lastName: e.target.value + }); + }} + /> + </label> + <label> + Email: + <input + value={form.email} + onChange={e => { + setForm({ + ...form, + email: e.target.value + }); + }} + /> + </label> + <p> + {form.firstName}{' '} + {form.lastName}{' '} + ({form.email}) + </p> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; } +``` + +</Sandpack> + +<Solution /> + +#### Form (nested object) {/*form-nested-object*/} + +In this example, the state is more nested. When you update nested state, you need to create a copy of the object you're updating, as well as any objects "containing" it on the way upwards. Read [updating a nested object](/learn/updating-objects-in-state#updating-a-nested-object) to learn more. + +<Sandpack> + +```js +import { useState } from 'react'; + +export default function Form() { + const [person, setPerson] = useState({ + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } + }); + + function handleNameChange(e) { + setPerson({ + ...person, + name: e.target.value + }); + } + + function handleTitleChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + title: e.target.value + } + }); + } + + function handleCityChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + city: e.target.value + } + }); + } + + function handleImageChange(e) { + setPerson({ + ...person, + artwork: { + ...person.artwork, + image: e.target.value + } + }); + } + + return ( + <> + <label> + Name: + <input + value={person.name} + onChange={handleNameChange} + /> + </label> + <label> + Title: + <input + value={person.artwork.title} + onChange={handleTitleChange} + /> + </label> + <label> + City: + <input + value={person.artwork.city} + onChange={handleCityChange} + /> + </label> + <label> + Image: + <input + value={person.artwork.image} + onChange={handleImageChange} + /> + </label> + <p> + <i>{person.artwork.title}</i> + {' by '} + {person.name} + <br /> + (located in {person.artwork.city}) + </p> + <img + src={person.artwork.image} + alt={person.artwork.title} + /> + </> + ); +} +``` + +```css +label { display: block; } +input { margin-left: 5px; margin-bottom: 5px; } +img { width: 200px; height: 200px; } +``` + +</Sandpack> + +<Solution /> + +#### List (array) {/*list-array*/} + +In this example, the `todos` state variable holds an array. Each button handler calls `setTodos` with the next version of that array. The `[...todos]` spread syntax, `todos.map()` and `todos.filter()` ensure the state array is replaced rather than mutated. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import AddTodo from './AddTodo.js'; +import TaskList from './TaskList.js'; + +let nextId = 3; +const initialTodos = [ + { id: 0, title: 'Buy milk', done: true }, + { id: 1, title: 'Eat tacos', done: false }, + { id: 2, title: 'Brew tea', done: false }, +]; + +export default function TaskApp() { + const [todos, setTodos] = useState(initialTodos); + + function handleAddTodo(title) { + setTodos([ + ...todos, + { + id: nextId++, + title: title, + done: false + } + ]); + } + + function handleChangeTodo(nextTodo) { + setTodos(todos.map(t => { + if (t.id === nextTodo.id) { + return nextTodo; + } else { + return t; + } + })); + } + + function handleDeleteTodo(todoId) { + setTodos( + todos.filter(t => t.id !== todoId) + ); + } + + return ( + <> + <AddTodo + onAddTodo={handleAddTodo} + /> + <TaskList + todos={todos} + onChangeTodo={handleChangeTodo} + onDeleteTodo={handleDeleteTodo} + /> + </> + ); +} +``` + +```js AddTodo.js +import { useState } from 'react'; + +export default function AddTodo({ onAddTodo }) { + const [title, setTitle] = useState(''); + return ( + <> + <input + placeholder="Add todo" + value={title} + onChange={e => setTitle(e.target.value)} + /> + <button onClick={() => { + setTitle(''); + onAddTodo(title); + }}>Add</button> + </> + ) +} +``` + +```js TaskList.js +import { useState } from 'react'; + +export default function TaskList({ + todos, + onChangeTodo, + onDeleteTodo +}) { + return ( + <ul> + {todos.map(todo => ( + <li key={todo.id}> + <Task + todo={todo} + onChange={onChangeTodo} + onDelete={onDeleteTodo} + /> + </li> + ))} + </ul> + ); +} + +function Task({ todo, onChange, onDelete }) { + const [isEditing, setIsEditing] = useState(false); + let todoContent; + if (isEditing) { + todoContent = ( + <> + <input + value={todo.title} + onChange={e => { + onChange({ + ...todo, + title: e.target.value + }); + }} /> + <button onClick={() => setIsEditing(false)}> + Save + </button> + </> + ); + } else { + todoContent = ( + <> + {todo.title} + <button onClick={() => setIsEditing(true)}> + Edit + </button> + </> + ); + } + return ( + <label> + <input + type="checkbox" + checked={todo.done} + onChange={e => { + onChange({ + ...todo, + done: e.target.checked + }); + }} + /> + {todoContent} + <button onClick={() => onDelete(todo.id)}> + Delete + </button> + </label> + ); +} +``` + +```css +button { margin: 5px; } +li { list-style-type: none; } +ul, li { margin: 0; padding: 0; } +``` + +</Sandpack> + +<Solution /> + +#### Writing concise update logic with Immer {/*writing-concise-update-logic-with-immer*/} + +If updating arrays and objects without mutation feels tedious, you can use a library like [Immer](https://github.com/immerjs/use-immer) to reduce repetitive code. Immer lets you write concise code as if you were mutating objects, but under the hood it performs immutable updates: + +<Sandpack> + +```js +import { useState } from 'react'; +import { useImmer } from 'use-immer'; + +let nextId = 3; +const initialList = [ + { id: 0, title: 'Big Bellies', seen: false }, + { id: 1, title: 'Lunar Landscape', seen: false }, + { id: 2, title: 'Terracotta Army', seen: true }, +]; + +export default function BucketList() { + const [list, updateList] = useImmer(initialList); + + function handleToggle(artworkId, nextSeen) { + updateList(draft => { + const artwork = draft.find(a => + a.id === artworkId + ); + artwork.seen = nextSeen; + }); + } + + return ( + <> + <h1>Art Bucket List</h1> + <h2>My list of art to see:</h2> + <ItemList + artworks={list} + onToggle={handleToggle} /> + </> + ); +} + +function ItemList({ artworks, onToggle }) { + return ( + <ul> + {artworks.map(artwork => ( + <li key={artwork.id}> + <label> + <input + type="checkbox" + checked={artwork.seen} + onChange={e => { + onToggle( + artwork.id, + e.target.checked + ); + }} + /> + {artwork.title} + </label> + </li> + ))} + </ul> + ); +} +``` + +```json package.json +{ + "dependencies": { + "immer": "1.7.3", + "react": "latest", + "react-dom": "latest", + "react-scripts": "latest", + "use-immer": "0.5.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Avoiding recreating the initial state {/*avoiding-recreating-the-initial-state*/} + +React saves the initial state once and ignores it on the next renders. + +```js +function TodoList() { + const [todos, setTodos] = useState(createInitialTodos()); + // ... +``` + +Although the result of `createInitialTodos()` is only used for the initial render, you're still calling this function on every render. This can be wasteful if it's creating large arrays or performing expensive calculations. + +To solve this, you may **pass it as an _initializer_ function** to `useState` instead: + +```js +function TodoList() { + const [todos, setTodos] = useState(createInitialTodos); + // ... +``` + +Notice that you’re passing `createInitialTodos`, which is the *function itself*, and not `createInitialTodos()`, which is the result of calling it. If you pass a function to `useState`, React will only call it during initialization. + +React may [call your initializers twice](#my-initializer-or-updater-function-runs-twice) in development to verify that they are [pure.](/learn/keeping-components-pure) + +<Recipes titleText="The difference between passing an initializer and passing the initial state directly" titleId="examples-initializer"> + +#### Passing the initializer function {/*passing-the-initializer-function*/} + +This example passes the initializer function, so the `createInitialTodos` function only runs during initialization. It does not run when component re-renders, such as when you type into the input. + +<Sandpack> + +```js +import { useState } from 'react'; + +function createInitialTodos() { + const initialTodos = []; + for (let i = 0; i < 50; i++) { + initialTodos.push({ + id: i, + text: 'Item ' + (i + 1) + }); + } + return initialTodos; +} + +export default function TodoList() { + const [todos, setTodos] = useState(createInitialTodos); + const [text, setText] = useState(''); + + return ( + <> + <input + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + setTodos([{ + id: todos.length, + text: text + }, ...todos]); + }}>Add</button> + <ul> + {todos.map(item => ( + <li key={item.id}> + {item.text} + </li> + ))} + </ul> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +#### Passing the initial state directly {/*passing-the-initial-state-directly*/} + +This example **does not** pass the initializer function, so the `createInitialTodos` function runs on every render, such as when you type into the input. There is no observable difference in behavior, but this code is less efficient. + +<Sandpack> + +```js +import { useState } from 'react'; + +function createInitialTodos() { + const initialTodos = []; + for (let i = 0; i < 50; i++) { + initialTodos.push({ + id: i, + text: 'Item ' + (i + 1) + }); + } + return initialTodos; +} + +export default function TodoList() { + const [todos, setTodos] = useState(createInitialTodos()); + const [text, setText] = useState(''); + + return ( + <> + <input + value={text} + onChange={e => setText(e.target.value)} + /> + <button onClick={() => { + setText(''); + setTodos([{ + id: todos.length, + text: text + }, ...todos]); + }}>Add</button> + <ul> + {todos.map(item => ( + <li key={item.id}> + {item.text} + </li> + ))} + </ul> + </> + ); +} +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Resetting state with a key {/*resetting-state-with-a-key*/} + +Typically, you might encounter the `key` attribute when [rendering lists.](/learn/rendering-lists) However, it also serves another purpose. + +You can **reset a component's state by passing a different `key` to a component.** In this example, the Reset button changes the `version` state variable, which we pass as a `key` to the `Form`. When the `key` changes, React re-creates the `Form` component (and all of its children) from scratch, so its state gets reset. + +Read [preserving and resetting state](/learn/preserving-and-resetting-state) to learn more. + +<Sandpack> + +```js App.js +import { useState } from 'react'; + +export default function App() { + const [version, setVersion] = useState(0); + + function handleReset() { + setVersion(version + 1); + } + + return ( + <> + <button onClick={handleReset}>Reset</button> + <Form key={version} /> + </> + ); +} + +function Form() { + const [name, setName] = useState('Taylor'); + + return ( + <> + <input + value={name} + onChange={e => setName(e.target.value)} + /> + <p>Hello, {name}.</p> + </> + ); +} +``` + +```css +button { display: block; margin-bottom: 20px; } +``` + +</Sandpack> + +--- + +### Storing information from previous renders {/*storing-information-from-previous-renders*/} + +Usually, you will update state in event handlers. However, in rare cases you might want to adjust state in response to rendering -- for example, you might want to change a state variable when a prop changes. + +In most cases, you don't need this: + +* **If the value you need can be computed entirely from the current props or other state, [remove that redundant state altogether.](/learn/choosing-the-state-structure#avoid-redundant-state)** If you're worried about recomputing too often, the [`useMemo` Hook](/reference/react/useMemo) can help. +* If you want to reset the entire component tree's state, [pass a different `key` to your component.](#resetting-state-with-a-key) +* If you can, update all the relevant state in the event handlers. + +In the rare case that none of these apply, there is a pattern you can use to update state based on the values that have been rendered so far, by calling a `set` function while your component is rendering. + +Here's an example. This `CountLabel` component displays the `count` prop passed to it: + +```js CountLabel.js +export default function CountLabel({ count }) { + return <h1>{count}</h1> +} +``` + +Say you want to show whether the counter has *increased or decreased* since the last change. The `count` prop doesn't tell you this -- you need to keep track of its previous value. Add the `prevCount` state variable to track it. Add another state variable called `trend` to hold whether the count has increased or decreased. Compare `prevCount` with `count`, and if they're not equal, update both `prevCount` and `trend`. Now you can show both the current count prop and *how it has changed since the last render*. + +<Sandpack> + +```js App.js +import { useState } from 'react'; +import CountLabel from './CountLabel.js'; + +export default function App() { + const [count, setCount] = useState(0); + return ( + <> + <button onClick={() => setCount(count + 1)}> + Increment + </button> + <button onClick={() => setCount(count - 1)}> + Decrement + </button> + <CountLabel count={count} /> + </> + ); +} +``` + +```js CountLabel.js active +import { useState } from 'react'; + +export default function CountLabel({ count }) { + const [prevCount, setPrevCount] = useState(count); + const [trend, setTrend] = useState(null); + if (prevCount !== count) { + setPrevCount(count); + setTrend(count > prevCount ? 'increasing' : 'decreasing'); + } + return ( + <> + <h1>{count}</h1> + {trend && <p>The count is {trend}</p>} + </> + ); +} +``` + +```css +button { margin-bottom: 10px; } +``` + +</Sandpack> + +Note that if you call a `set` function while rendering, it must be inside a condition like `prevCount !== count`, and there must be a call like `setPrevCount(count)` inside of the condition. Otherwise, your component would re-render in a loop until it crashes. Also, you can only update the state of the *currently rendering* component like this. Calling the `set` function of *another* component during rendering is an error. Finally, your `set` call should still [update state without mutation](#updating-objects-and-arrays-in-state) -- this special case doesn't mean you can break other rules of [pure functions.](/learn/keeping-components-pure) + +This pattern can be hard to understand and is usually best avoided. However, it's better than updating state in an effect. When you call the `set` function during render, React will re-render that component immediately after your component exits with a `return` statement, and before rendering the children. This way, children don't need to render twice. The rest of your component function will still execute (and the result will be thrown away), but if your condition is below all the calls to Hooks, you may add an early `return;` inside it to restart rendering earlier. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I've updated the state, but logging gives me the old value {/*ive-updated-the-state-but-logging-gives-me-the-old-value*/} + +Calling the `set` function **does not change state in the running code**: + +```js {4,5,8} +function handleClick() { + console.log(count); // 0 + + setCount(count + 1); // Request a re-render with 1 + console.log(count); // Still 0! + + setTimeout(() => { + console.log(count); // Also 0! + }, 5000); +} +``` + +This is because [states behaves like a snapshot.](/learn/state-as-a-snapshot) Updating state requests another render with the new state value, but does not affect the `count` JavaScript variable in your already-running event handler. + +If you need to use the next state, you can save it in a variable before passing it to the `set` function: + +```js +const nextCount = count + 1; +setCount(nextCount); + +console.log(count); // 0 +console.log(nextCount); // 1 +``` + +--- + +### I've updated the state, but the screen doesn't update {/*ive-updated-the-state-but-the-screen-doesnt-update*/} + +React will **ignore your update if the next state is equal to the previous state,** as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. This usually happens when you change an object or an array in state directly: + +```js +obj.x = 10; // 🚩 Wrong: mutating existing object +setObj(obj); // 🚩 Doesn't do anything +``` + +You mutated an existing `obj` object and passed it back to `setObj`, so React ignored the update. To fix this, you need to ensure that you're always [_replacing_ objects and arrays in state instead of _mutating_ them](#updating-objects-and-arrays-in-state): + +```js +// ✅ Correct: creating a new object +setObj({ + ...obj, + x: 10 +}); +``` + +--- + +### I'm getting an error: "Too many re-renders" {/*im-getting-an-error-too-many-re-renders*/} + +You might get an error that says: `Too many re-renders. React limits the number of renders to prevent an infinite loop.` Typically, this means that you're unconditionally setting state *during render*, so your component enters a loop: render, set state (which causes a render), render, set state (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler: + +```js {1-2} +// 🚩 Wrong: calls the handler during render +return <button onClick={handleClick()}>Click me</button> + +// ✅ Correct: passes down the event handler +return <button onClick={handleClick}>Click me</button> + +// ✅ Correct: passes down an inline function +return <button onClick={(e) => handleClick(e)}>Click me</button> +``` + +If you can't find the cause of this error, click on the arrow next to the error in the console and look through the JavaScript stack to find the specific `set` function call responsible for the error. + +--- + +### My initializer or updater function runs twice {/*my-initializer-or-updater-function-runs-twice*/} + +In [Strict Mode](/reference/react/StrictMode), React will call some of your functions twice instead of once: + +```js {2,5-6,11-12} +function TodoList() { + // This component function will run twice for every render. + + const [todos, setTodos] = useState(() => { + // This initializer function will run twice during initialization. + return createTodos(); + }); + + function handleClick() { + setTodos(prevTodos => { + // This updater function will run twice for every click. + return [...prevTodos, createTodo()]; + }); + } + // ... +``` + +This is expected and shouldn't break your code. + +This **development-only** behavior helps you [keep components pure.](/learn/keeping-components-pure) React uses the result of one of the calls, and ignores the result of the other call. As long as your component, initializer, and updater functions are pure, this shouldn't affect your logic. However, if they are accidentally impure, this helps you notice the mistakes and fix it. + +For example, this impure updater function mutates an array in state: + +```js {2,3} +setTodos(prevTodos => { + // 🚩 Mistake: mutating state + prevTodos.push(createTodo()); +}); +``` + +Because React calls your updater function twice, you'll see the todo was added twice, so you'll know that there is a mistake. In this example, you can fix the mistake by [replacing the array instead of mutating it](#updating-objects-and-arrays-in-state): + +```js {2,3} +setTodos(prevTodos => { + // ✅ Correct: replacing with new state + return [...prevTodos, createTodo()]; +}); +``` + +Now that this updater function is pure, calling it an extra time doesn't make a difference in behavior. This is why React calling it twice helps you find mistakes. **Only component, initializer, and updater functions need to be pure.** Event handlers don't need to be pure, so React will never call your event handlers twice. + +Read [keeping components pure](/learn/keeping-components-pure) to learn more. + +--- + +### I'm trying to set state to a function, but it gets called instead {/*im-trying-to-set-state-to-a-function-but-it-gets-called-instead*/} + +You can't put a function into state like this: + +```js +const [fn, setFn] = useState(someFunction); + +function handleClick() { + setFn(someOtherFunction); +} +``` + +Because you're passing a function, React assumes that `someFunction` is an [initializer function](#avoiding-recreating-the-initial-state), and that `someOtherFunction` is an [updater function](#updating-state-based-on-the-previous-state), so it tries to call them and store the result. To actually *store* a function, you have to put `() =>` before them in both cases. Then React will store the functions you pass. + +```js {1,4} +const [fn, setFn] = useState(() => someFunction); + +function handleClick() { + setFn(() => someOtherFunction); +} +``` diff --git a/beta/src/content/reference/react/useSyncExternalStore.md b/beta/src/content/reference/react/useSyncExternalStore.md new file mode 100644 index 000000000..38ab316cd --- /dev/null +++ b/beta/src/content/reference/react/useSyncExternalStore.md @@ -0,0 +1,426 @@ +--- +title: useSyncExternalStore +--- + +<Intro> + +`useSyncExternalStore` is a React Hook that lets you subscribe to an external store. + +```js +const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?) +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)` {/*usesyncexternalstore*/} + +Call `useSyncExternalStore` at the top level of your component to read a value from an external data store. + +```js +import { useSyncExternalStore } from 'react'; +import { todosStore } from './todoStore.js'; + +function TodosApp() { + const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot); + // ... +} +``` + +It returns the snapshot of the data in the store. You need to pass two functions as arguments: + +1. The `subscribe` function should subscribe to the store and return a function that unsubscribes. +2. The `getSnapshot` function should read a snapshot of the data from the store. + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +* `subscribe`: A function that takes a single `callback` argument and subscribes it to the store. When the store changes, it should invoke the provided `callback`. This will cause the component to re-render. The `subscribe` function should return a function that cleans up the subscription. + +* `getSnapshot`: A function that returns a snapshot of the data in the store that's needed by the component. While the store has not changed, repeated calls to `getSnapshot` must return the same value. If the store changes and the returned value is different (as compared by [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), React will re-render the component. + +* **optional** `getServerSnapshot`: A function that returns the initial snapshot of the data in the store. It will be used only during server rendering and during hydration of server-rendered content on the client. The server snapshot must be the same between the client and the server, and is usually serialized and passed from the server to the client. If this function is not provided, rendering the component on the server will throw an error. + +#### Returns {/*returns*/} + +The current snapshot of the store which you can use in your rendering logic. + +#### Caveats {/*caveats*/} + +* The store snapshot returned by `getSnapshot` must be immutable. If the underlying store has mutable data, return a new immutable snapshot if the data has changed. Otherwise, return a cached last snapshot. + +* If a different `subscribe` function is passed during a re-render, React will re-subscribe to the store using the newly passed `subscribe` function. You can prevent this by declaring `subscribe` outside the component. + +--- + +## Usage {/*usage*/} + +### Subscribing to an external store {/*subscribing-to-an-external-store*/} + +Most of your React components will only read data from their [props,](/learn/passing-props-to-a-component) [state,](/reference/react/useState) and [context.](/reference/react/useContext) However, sometimes a component needs to read some data from some store outside of React that changes over time. This includes: + +* Third-party state management libraries that hold state outside of React. +* Browser APIs that expose a mutable value and events to subscribe to its changes. + +Call `useSyncExternalStore` at the top level of your component to read a value from an external data store. + +```js [[1, 5, "todosStore.subscribe"], [2, 5, "todosStore.getSnapshot"], [3, 5, "todos", 0]] +import { useSyncExternalStore } from 'react'; +import { todosStore } from './todoStore.js'; + +function TodosApp() { + const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot); + // ... +} +``` + +It returns the <CodeStep step={3}>snapshot</CodeStep> of the data in the store. You need to pass two functions as arguments: + +1. The <CodeStep step={1}>`subscribe` function</CodeStep> should subscribe to the store and return a function that unsubscribes. +2. The <CodeStep step={2}>`getSnapshot` function</CodeStep> should read a snapshot of the data from the store. + +React will use these functions to keep your component subscribed to the store and re-render it on changes. + +For example, in the sandbox below, `todosStore` is implemented as an external store that stores data outside of React. The `TodosApp` component connects to that external store with the `useSyncExternalStore` Hook. + +<Sandpack> + +```js +import { useSyncExternalStore } from 'react'; +import { todosStore } from './todoStore.js'; + +export default function TodosApp() { + const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot); + return ( + <> + <button onClick={() => todosStore.addTodo()}>Add todo</button> + <hr /> + <ul> + {todos.map(todo => ( + <li key={todo.id}>{todo.text}</li> + ))} + </ul> + </> + ); +} +``` + +```js todoStore.js +// This is an example of a third-party store +// that you might need to integrate with React. + +// If your app is fully built with React, +// we recommend using React state instead. + +let nextId = 0; +let todos = [{ id: nextId++, text: 'Todo #1' }]; +let listeners = []; + +export const todosStore = { + addTodo() { + todos = [...todos, { id: nextId++, text: 'Todo #' + nextId }] + emitChange(); + }, + subscribe(listener) { + listeners = [...listeners, listener]; + return () => { + listeners = listeners.filter(l => l !== listener); + }; + }, + getSnapshot() { + return todos; + } +}; + +function emitChange() { + for (let listener of listeners) { + listener(); + } +} +``` + +</Sandpack> + +<Note> + +When possible, we recommend to use the built-in React state with [`useState`](/reference/react/useState) and [`useReducer`](/reference/react/useReducer) instead. The `useSyncExternalStore` API is mostly useful if you need to integrate with existing non-React code. + +</Note> + +--- + +### Subscribing to a browser API {/*subscribing-to-a-browser-api*/} + +Another reason to add `useSyncExternalStore` is when you want to subscribe to some value exposed by the browser that changes over time. For example, suppose that you want your component to display whether the network connection is active. The browser exposes this information via a property called [`navigator.onLine`.](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine) This value can change over time without React's knowledge, so you need to read it with `useSyncExternalStore`. + +```js +import { useSyncExternalStore } from 'react'; + +function ChatIndicator() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot); + // ... +} +``` + +To implement the `getSnapshot` function, read the current value from the browser API: + +```js +function getSnapshot() { + return navigator.onLine; +} +``` + +Next, you need to implement the `subscribe` function. For example, when `navigator.onLine` changes, the browser fires the [`online`](https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event) and [`offline`](https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event) events on the `window` object. You need to subscribe the `callback` argument to the corresponding events, and then return a function that cleans up the subscriptions: + +```js +function subscribe(callback) { + window.addEventListener('online', callback); + window.addEventListener('offline', callback); + return () => { + window.removeEventListener('online', callback); + window.removeEventListener('offline', callback); + }; +} +``` + +Now React knows how to read the value from the external `navigator.onLine` API and how to subscribe to its changes. Try to disconnect your device from the network and notice that the component re-renders in response: + +<Sandpack> + +```js +import { useSyncExternalStore } from 'react'; + +export default function ChatIndicator() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot); + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} + +function getSnapshot() { + return navigator.onLine; +} + +function subscribe(callback) { + window.addEventListener('online', callback); + window.addEventListener('offline', callback); + return () => { + window.removeEventListener('online', callback); + window.removeEventListener('offline', callback); + }; +} +``` + +</Sandpack> + +--- + +### Extracting the logic to a custom Hook {/*extracting-the-logic-to-a-custom-hook*/} + +Usually you won't write `useSyncExternalStore` directly in your components. Instead, you'll typically call it from your own custom Hook. This lets you use the same external store from different components. + +For example, this custom `useOnlineStatus` Hook tracks whether the network is online: + +```js {3,6} +import { useSyncExternalStore } from 'react'; + +export function useOnlineStatus() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot); + return isOnline; +} + +function getSnapshot() { + // ... +} + +function subscribe(callback) { + // ... +} +``` + +Now different components can call `useOnlineStatus` without repeating the underlying implementation: + +<Sandpack> + +```js +import { useOnlineStatus } from './useOnlineStatus.js'; + +function StatusBar() { + const isOnline = useOnlineStatus(); + return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + + function handleSaveClick() { + console.log('✅ Progress saved'); + } + + return ( + <button disabled={!isOnline} onClick={handleSaveClick}> + {isOnline ? 'Save progress' : 'Reconnecting...'} + </button> + ); +} + +export default function App() { + return ( + <> + <SaveButton /> + <StatusBar /> + </> + ); +} +``` + +```js useOnlineStatus.js +import { useSyncExternalStore } from 'react'; + +export function useOnlineStatus() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot); + return isOnline; +} + +function getSnapshot() { + return navigator.onLine; +} + +function subscribe(callback) { + window.addEventListener('online', callback); + window.addEventListener('offline', callback); + return () => { + window.removeEventListener('online', callback); + window.removeEventListener('offline', callback); + }; +} +``` + +</Sandpack> + +--- + +### Adding support for server rendering {/*adding-support-for-server-rendering*/} + +If your React app uses [server rendering,](/reference/react-dom/server) your React components will also run outside the browser environment to generate the initial HTML. This creates a few challenges when connecting to an external store: + +- If you're connecting to a browser-only API, it won't work because it does not exist on the server. +- If you're connecting to a third-party data store, you'll need its data to match between the server and client. + +To solve these issues, pass a `getServerSnapshot` function as the third argument to `useSyncExternalStore`: + +```js {4,12-14} +import { useSyncExternalStore } from 'react'; + +export function useOnlineStatus() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); + return isOnline; +} + +function getSnapshot() { + return navigator.onLine; +} + +function getServerSnapshot() { + return true; // Always show "Online" for server-generated HTML +} + +function subscribe(callback) { + // ... +} +``` + +The `getServerSnapshot` function is similar to `getSnapshot`, but it runs only in two situations: + +- It runs on the server when generating the HTML. +- It runs on the client during [hydration](/reference/react-dom/client/hydrateRoot), i.e. when React takes the server HTML and makes it interactive. + +This lets you provide the initial snapshot value which will be used before the app becomes interactive. If there is no meaningful initial value for the server rendering, you can [force the component to render only on the client.](/reference/react/Suspense#providing-a-fallback-for-server-errors-and-server-only-content) + +<Note> + +Make sure that `getServerSnapshot` returns the same exact data on the initial client render as it returned on the server. For example, if `getServerSnapshot` returned some prepopulated store content on the server, you need to transfer this content to the client. One common way to do this is to emit a `<script>` tag that sets a global like `window.MY_STORE_DATA` during server rendering, and then read from that global on the client in `getServerSnapshot`. Your external store should provide instructions on how to do that. + +</Note> + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I'm getting an error: "The result of `getSnapshot` should be cached" {/*im-getting-an-error-the-result-of-getsnapshot-should-be-cached*/} + +If you get this error, it means your `getSnapshot` function returns a new object every time it's called, for example: + +```js {2-5} +function getSnapshot() { + // 🔴 Do not return always different objects from getSnapshot + return { + todos: myStore.todos + }; +} +``` + +React will re-render the component if `getSnapshot` return value is different from the last time. This is why, if you always return a different value, you will enter an infinite loop and get this error. + +Your `getSnapshot` object should only return a different object if something has actually changed. If your store contains immutable data, you can return that data directly: + +```js {2-3} +function getSnapshot() { + // ✅ You can return immutable data + return myStore.todos; +} +``` + +If your store data is mutable, your `getSnapshot` function should return an immutable snapshot of it. This means it *does* need to create new objects, but it shouldn't do this for every single call. Instead, it should store the last calculated snapshot, and return the same snapshot as the last time if the data in the store has not changed. How you determine whether mutable data has changed depends on how your mutable store is implemented. + +--- + +### My `subscribe` function gets called after every re-render {/*my-subscribe-function-gets-called-after-every-re-render*/} + +This `subscribe` function is defined *inside* a component so it is different on every re-render: + +```js {4-7} +function ChatIndicator() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot); + + // 🚩 Always a different function, so React will resubscribe on every re-render + function subscribe() { + // ... + } + + // ... +} +``` + +React will resubscribe to your store if you pass a different `subscribe` function between re-renders. If this causes performance issues and you'd like to avoid resubscribing to the store, move the `subscribe` function outside: + +```js {6-9} +function ChatIndicator() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot); + // ... +} + +// ✅ Always the same function, so React won't need to resubscribe +function subscribe() { + // ... +} +``` + +Alternatively, wrap `subscribe` into [`useCallback`](/reference/react/useCallback) to only resubscribe when some argument changes: + +```js {4-8} +function ChatIndicator({ userId }) { + const isOnline = useSyncExternalStore(subscribe, getSnapshot); + + // ✅ Same function as long as userId doesn't change + const subscribe = useCallback(() => { + // ... + }, [userId]); + + // ... +} +``` \ No newline at end of file diff --git a/beta/src/content/reference/react/useTransition.md b/beta/src/content/reference/react/useTransition.md new file mode 100644 index 000000000..a94e1f7c3 --- /dev/null +++ b/beta/src/content/reference/react/useTransition.md @@ -0,0 +1,1626 @@ +--- +title: useTransition +--- + +<Intro> + +`useTransition` is a React Hook that lets you update the state without blocking the UI. + +```js +const [isPending, startTransition] = useTransition() +``` + +</Intro> + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `useTransition()` {/*usetransition*/} + +Call `useTransition` at the top level of your component to mark some state updates as transitions. + +```js +import { useTransition } from 'react'; + +function TabContainer() { + const [isPending, startTransition] = useTransition(); + // ... +} +``` + +[See more examples below.](#usage) + +#### Parameters {/*parameters*/} + +`useTransition` does not take any parameters. + +#### Returns {/*returns*/} + +`useTransition` returns an array with exactly two items: + +1. The `isPending` flag that tells you whether there is a pending transition. +2. The [`startTransition` function](#starttransition) that lets you mark a state update as a transition. + +--- + +### `startTransition` function {/*starttransition*/} + +The `startTransition` function returned by `useTransition` lets you mark a state update as a transition. + +```js {6,8} +function TabContainer() { + const [isPending, startTransition] = useTransition(); + const [tab, setTab] = useState('about'); + + function selectTab(nextTab) { + startTransition(() => { + setTab(nextTab); + }); + } + // ... +} +``` + +#### Parameters {/*starttransition-parameters*/} + +* `scope`: A function that updates some state by calling one or more [`set` functions.](/reference/react/useState#setstate) React immediately calls `scope` with no parameters and marks all state updates scheduled synchronously during the `scope` function call as transitions. They will be [non-blocking](#marking-a-state-update-as-a-non-blocking-transition) and [will not display unwanted loading indicators.](#preventing-unwanted-loading-indicators) + +#### Returns {/*starttransition-returns*/} + +`startTransition` does not return anything. + +#### Caveats {/*starttransition-caveats*/} + +* `useTransition` is a Hook, so it can only be called inside components or custom Hooks. If you need to start a transition somewhere else (for example, from a data library), call the standalone [`startTransition`](/reference/react/startTransition) instead. + +* You can wrap an update into a transition only if you have access to the `set` function of that state. If you want to start a transition in response to some prop or a custom Hook return value, try [`useDeferredValue`](/reference/react/useDeferredValue) instead. + +* The function you pass to `startTransition` must be synchronous. React immediately executes this function, marking all state updates that happen while it executes as transitions. If you try to perform more state updates later (for example, in a timeout), they won't be marked as transitions. + +* A state update marked as a transition will be interrupted by other state updates. For example, if you update a chart component inside a transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input state update. + +* Transition updates can't be used to control text inputs. + +* If there are multiple ongoing transitions, React currently batches them together. This is a limitation that will likely be removed in a future release. + +--- + +## Usage {/*usage*/} + +### Marking a state update as a non-blocking transition {/*marking-a-state-update-as-a-non-blocking-transition*/} + +Call `useTransition` at the top level of your component to mark some state updates as non-blocking *transitions*. + +```js [[1, 4, "isPending"], [2, 4, "startTransition"]] +import { useState, useTransition } from 'react'; + +function TabContainer() { + const [isPending, startTransition] = useTransition(); + // ... +} +``` + +`useTransition` returns an array with exactly two items: + +1. The <CodeStep step={1}>`isPending` flag</CodeStep> that tells you whether there is a pending transition. +2. The <CodeStep step={2}>`startTransition` function</CodeStep> that lets you mark a state update as a transition. + +You can then mark a state update as a transition like this: + +```js {6,8} +function TabContainer() { + const [isPending, startTransition] = useTransition(); + const [tab, setTab] = useState('about'); + + function selectTab(nextTab) { + startTransition(() => { + setTab(nextTab); + }); + } + // ... +} +``` + +Transitions let you keep the user interface updates responsive even on slow devices. + +With a transition, your UI stays responsive in the middle of a re-render. For example, if the user clicks a tab but then change their mind and click another tab, they can do that without waiting for the first re-render to finish. + +<Recipes titleText="The difference between useTransition and regular state updates" titleId="examples"> + +#### Updating the current tab in a transition {/*updating-the-current-tab-in-a-transition*/} + +In this example, the "Posts" tab is **artificially slowed down** so that it takes at least a second to render. + +Click "Posts" and then immediately click "Contact". Notice that this interrupts the slow render of "Posts". The "Contact" tab shows immediately. Because this state update is marked as a transition, a slow re-render did not freeze the user interface. + +<Sandpack> + +```js +import { useState, useTransition } from 'react'; +import TabButton from './TabButton.js'; +import AboutTab from './AboutTab.js'; +import PostsTab from './PostsTab.js'; +import ContactTab from './ContactTab.js'; + +export default function TabContainer() { + const [isPending, startTransition] = useTransition(); + const [tab, setTab] = useState('about'); + + function selectTab(nextTab) { + startTransition(() => { + setTab(nextTab); + }); + } + + return ( + <> + <TabButton + isActive={tab === 'about'} + onClick={() => selectTab('about')} + > + About + </TabButton> + <TabButton + isActive={tab === 'posts'} + onClick={() => selectTab('posts')} + > + Posts (slow) + </TabButton> + <TabButton + isActive={tab === 'contact'} + onClick={() => selectTab('contact')} + > + Contact + </TabButton> + <hr /> + {tab === 'about' && <AboutTab />} + {tab === 'posts' && <PostsTab />} + {tab === 'contact' && <ContactTab />} + </> + ); +} +``` + +```js TabButton.js +import { useTransition } from 'react'; + +export default function TabButton({ children, isActive, onClick }) { + if (isActive) { + return <b>{children}</b> + } + return ( + <button onClick={() => { + onClick(); + }}> + {children} + </button> + ) +} + +``` + +```js AboutTab.js +export default function AboutTab() { + return ( + <p>Welcome to my profile!</p> + ); +} +``` + +```js PostsTab.js +import { memo } from 'react'; + +const PostsTab = memo(function PostsTab() { + // Log once. The actual slowdown is inside SlowPost. + console.log('[ARTIFICIALLY SLOW] Rendering 500 <SlowPost />'); + + let items = []; + for (let i = 0; i < 500; i++) { + items.push(<SlowPost key={i} index={i} />); + } + return ( + <ul className="items"> + {items} + </ul> + ); +}); + +function SlowPost({ index }) { + let startTime = performance.now(); + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return ( + <li className="item"> + Post #{index + 1} + </li> + ); +} + +export default PostsTab; +``` + +```js ContactTab.js +export default function ContactTab() { + return ( + <> + <p> + You can find me online here: + </p> + <ul> + <li>admin@mysite.com</li> + <li>+123456789</li> + </ul> + </> + ); +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +``` + +</Sandpack> + +<Solution /> + +#### Updating the current tab without a transition {/*updating-the-current-tab-without-a-transition*/} + +In this example, the "Posts" tab is also **artificially slowed down** so that it takes at least a second to render. Unlike in the previous example, this state update is **not a transition.** + +Click "Posts" and then immediately click "Contact". Notice that the app freezes while rendering the slowed down tab, and the UI becomes unresponsive. This state update is not a transition, so a slow re-render freezed the user interface. + +<Sandpack> + +```js +import { useState } from 'react'; +import TabButton from './TabButton.js'; +import AboutTab from './AboutTab.js'; +import PostsTab from './PostsTab.js'; +import ContactTab from './ContactTab.js'; + +export default function TabContainer() { + const [tab, setTab] = useState('about'); + + function selectTab(nextTab) { + setTab(nextTab); + } + + return ( + <> + <TabButton + isActive={tab === 'about'} + onClick={() => selectTab('about')} + > + About + </TabButton> + <TabButton + isActive={tab === 'posts'} + onClick={() => selectTab('posts')} + > + Posts (slow) + </TabButton> + <TabButton + isActive={tab === 'contact'} + onClick={() => selectTab('contact')} + > + Contact + </TabButton> + <hr /> + {tab === 'about' && <AboutTab />} + {tab === 'posts' && <PostsTab />} + {tab === 'contact' && <ContactTab />} + </> + ); +} +``` + +```js TabButton.js +import { useTransition } from 'react'; + +export default function TabButton({ children, isActive, onClick }) { + if (isActive) { + return <b>{children}</b> + } + return ( + <button onClick={() => { + onClick(); + }}> + {children} + </button> + ) +} + +``` + +```js AboutTab.js +export default function AboutTab() { + return ( + <p>Welcome to my profile!</p> + ); +} +``` + +```js PostsTab.js +import { memo } from 'react'; + +const PostsTab = memo(function PostsTab() { + // Log once. The actual slowdown is inside SlowPost. + console.log('[ARTIFICIALLY SLOW] Rendering 500 <SlowPost />'); + + let items = []; + for (let i = 0; i < 500; i++) { + items.push(<SlowPost key={i} index={i} />); + } + return ( + <ul className="items"> + {items} + </ul> + ); +}); + +function SlowPost({ index }) { + let startTime = performance.now(); + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return ( + <li className="item"> + Post #{index + 1} + </li> + ); +} + +export default PostsTab; +``` + +```js ContactTab.js +export default function ContactTab() { + return ( + <> + <p> + You can find me online here: + </p> + <ul> + <li>admin@mysite.com</li> + <li>+123456789</li> + </ul> + </> + ); +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +``` + +</Sandpack> + +<Solution /> + +</Recipes> + +--- + +### Updating the parent component in a transition {/*updating-the-parent-component-in-a-transition*/} + +The `useTransition` call does not have to be in the same component whose state you're updating. You can also move it into a child component. For example, this `TabButton` component wraps its `onClick` logic in a transition: + +```js {8-10} +export default function TabButton({ children, isActive, onClick }) { + const [isPending, startTransition] = useTransition(); + if (isActive) { + return <b>{children}</b> + } + return ( + <button onClick={() => { + startTransition(() => { + onClick(); + }); + }}> + {children} + </button> + ); +} +``` + +Because the parent component updates its state inside the `onClick` event handler, that state update gets marked as a transition. This is why, like in the earlier example, you can click on "Posts" and then immediately click "Contact". Updating the selected tab is marked as a transition, so it does not block further user interactions. + +<Sandpack> + +```js +import { useState } from 'react'; +import TabButton from './TabButton.js'; +import AboutTab from './AboutTab.js'; +import PostsTab from './PostsTab.js'; +import ContactTab from './ContactTab.js'; + +export default function TabContainer() { + const [tab, setTab] = useState('about'); + return ( + <> + <TabButton + isActive={tab === 'about'} + onClick={() => setTab('about')} + > + About + </TabButton> + <TabButton + isActive={tab === 'posts'} + onClick={() => setTab('posts')} + > + Posts (slow) + </TabButton> + <TabButton + isActive={tab === 'contact'} + onClick={() => setTab('contact')} + > + Contact + </TabButton> + <hr /> + {tab === 'about' && <AboutTab />} + {tab === 'posts' && <PostsTab />} + {tab === 'contact' && <ContactTab />} + </> + ); +} +``` + +```js TabButton.js active +import { useTransition } from 'react'; + +export default function TabButton({ children, isActive, onClick }) { + const [isPending, startTransition] = useTransition(); + if (isActive) { + return <b>{children}</b> + } + return ( + <button onClick={() => { + startTransition(() => { + onClick(); + }); + }}> + {children} + </button> + ); +} +``` + +```js AboutTab.js +export default function AboutTab() { + return ( + <p>Welcome to my profile!</p> + ); +} +``` + +```js PostsTab.js +import { memo } from 'react'; + +const PostsTab = memo(function PostsTab() { + // Log once. The actual slowdown is inside SlowPost. + console.log('[ARTIFICIALLY SLOW] Rendering 500 <SlowPost />'); + + let items = []; + for (let i = 0; i < 500; i++) { + items.push(<SlowPost key={i} index={i} />); + } + return ( + <ul className="items"> + {items} + </ul> + ); +}); + +function SlowPost({ index }) { + let startTime = performance.now(); + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return ( + <li className="item"> + Post #{index + 1} + </li> + ); +} + +export default PostsTab; +``` + +```js ContactTab.js +export default function ContactTab() { + return ( + <> + <p> + You can find me online here: + </p> + <ul> + <li>admin@mysite.com</li> + <li>+123456789</li> + </ul> + </> + ); +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +``` + +</Sandpack> + +--- + +### Displaying a pending visual state during the transition {/*displaying-a-pending-visual-state-during-the-transition*/} + +You can use the `isPending` boolean value returned by `useTransition` to indicate to the user that a transition is in progress. For example, the tab button can have a special "pending" visual state: + +```js {4-6} +function TabButton({ children, isActive, onClick }) { + const [isPending, startTransition] = useTransition(); + // ... + if (isPending) { + return <b className="pending">{children}</b>; + } + // ... +``` + +Notice how clicking "Posts" now feels more responsive because the tab button itself updates right away: + +<Sandpack> + +```js +import { useState } from 'react'; +import TabButton from './TabButton.js'; +import AboutTab from './AboutTab.js'; +import PostsTab from './PostsTab.js'; +import ContactTab from './ContactTab.js'; + +export default function TabContainer() { + const [tab, setTab] = useState('about'); + return ( + <> + <TabButton + isActive={tab === 'about'} + onClick={() => setTab('about')} + > + About + </TabButton> + <TabButton + isActive={tab === 'posts'} + onClick={() => setTab('posts')} + > + Posts (slow) + </TabButton> + <TabButton + isActive={tab === 'contact'} + onClick={() => setTab('contact')} + > + Contact + </TabButton> + <hr /> + {tab === 'about' && <AboutTab />} + {tab === 'posts' && <PostsTab />} + {tab === 'contact' && <ContactTab />} + </> + ); +} +``` + +```js TabButton.js active +import { useTransition } from 'react'; + +export default function TabButton({ children, isActive, onClick }) { + const [isPending, startTransition] = useTransition(); + if (isActive) { + return <b>{children}</b> + } + if (isPending) { + return <b className="pending">{children}</b>; + } + return ( + <button onClick={() => { + startTransition(() => { + onClick(); + }); + }}> + {children} + </button> + ); +} +``` + +```js AboutTab.js +export default function AboutTab() { + return ( + <p>Welcome to my profile!</p> + ); +} +``` + +```js PostsTab.js +import { memo } from 'react'; + +const PostsTab = memo(function PostsTab() { + // Log once. The actual slowdown is inside SlowPost. + console.log('[ARTIFICIALLY SLOW] Rendering 500 <SlowPost />'); + + let items = []; + for (let i = 0; i < 500; i++) { + items.push(<SlowPost key={i} index={i} />); + } + return ( + <ul className="items"> + {items} + </ul> + ); +}); + +function SlowPost({ index }) { + let startTime = performance.now(); + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return ( + <li className="item"> + Post #{index + 1} + </li> + ); +} + +export default PostsTab; +``` + +```js ContactTab.js +export default function ContactTab() { + return ( + <> + <p> + You can find me online here: + </p> + <ul> + <li>admin@mysite.com</li> + <li>+123456789</li> + </ul> + </> + ); +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +.pending { color: #777; } +``` + +</Sandpack> + +--- + +### Preventing unwanted loading indicators {/*preventing-unwanted-loading-indicators*/} + +In this example, the `PostsTab` component fetches some data using a [Suspense-enabled](/reference/react/Suspense) data source. When you click the "Posts" tab, the `PostsTab` component *suspends*, causing the closest loading fallback to be displayed: + +<Sandpack> + +```js +import { Suspense, useState } from 'react'; +import TabButton from './TabButton.js'; +import AboutTab from './AboutTab.js'; +import PostsTab from './PostsTab.js'; +import ContactTab from './ContactTab.js'; + +export default function TabContainer() { + const [tab, setTab] = useState('about'); + return ( + <Suspense fallback={<h1>🌀 Loading...</h1>}> + <TabButton + isActive={tab === 'about'} + onClick={() => setTab('about')} + > + About + </TabButton> + <TabButton + isActive={tab === 'posts'} + onClick={() => setTab('posts')} + > + Posts + </TabButton> + <TabButton + isActive={tab === 'contact'} + onClick={() => setTab('contact')} + > + Contact + </TabButton> + <hr /> + {tab === 'about' && <AboutTab />} + {tab === 'posts' && <PostsTab />} + {tab === 'contact' && <ContactTab />} + </Suspense> + ); +} +``` + +```js TabButton.js +export default function TabButton({ children, isActive, onClick }) { + if (isActive) { + return <b>{children}</b> + } + return ( + <button onClick={() => { + onClick(); + }}> + {children} + </button> + ); +} +``` + +```js AboutTab.js hidden +export default function AboutTab() { + return ( + <p>Welcome to my profile!</p> + ); +} +``` + +```js PostsTab.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +function PostsTab() { + const posts = use(fetchData('/posts')); + return ( + <ul className="items"> + {posts.map(post => + <Post key={post.id} title={post.title} /> + )} + </ul> + ); +} + +function Post({ title }) { + return ( + <li className="item"> + {title} + </li> + ); +} + +export default PostsTab; + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js ContactTab.js hidden +export default function ContactTab() { + return ( + <> + <p> + You can find me online here: + </p> + <ul> + <li>admin@mysite.com</li> + <li>+123456789</li> + </ul> + </> + ); +} +``` + + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url.startsWith('/posts')) { + return await getPosts(); + } else { + throw Error('Not implemented'); + } +} + +async function getPosts() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 1000); + }); + let posts = []; + for (let i = 0; i < 500; i++) { + posts.push({ + id: i, + title: 'Post #' + (i + 1) + }); + } + return posts; +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +.pending { color: #777; } +``` + +</Sandpack> + +Hiding the entire tab container to show a loading indicator leads to a jarring user experience. If you add `useTransition` to `TabButton`, you can instead indicate display the pending state in the tab button instead. + +Notice that clicking "Posts" no longer replaces the entire tab container with a spinner: + +<Sandpack> + +```js +import { Suspense, useState } from 'react'; +import TabButton from './TabButton.js'; +import AboutTab from './AboutTab.js'; +import PostsTab from './PostsTab.js'; +import ContactTab from './ContactTab.js'; + +export default function TabContainer() { + const [tab, setTab] = useState('about'); + return ( + <Suspense fallback={<h1>🌀 Loading...</h1>}> + <TabButton + isActive={tab === 'about'} + onClick={() => setTab('about')} + > + About + </TabButton> + <TabButton + isActive={tab === 'posts'} + onClick={() => setTab('posts')} + > + Posts + </TabButton> + <TabButton + isActive={tab === 'contact'} + onClick={() => setTab('contact')} + > + Contact + </TabButton> + <hr /> + {tab === 'about' && <AboutTab />} + {tab === 'posts' && <PostsTab />} + {tab === 'contact' && <ContactTab />} + </Suspense> + ); +} +``` + +```js TabButton.js active +import { useTransition } from 'react'; + +export default function TabButton({ children, isActive, onClick }) { + const [isPending, startTransition] = useTransition(); + if (isActive) { + return <b>{children}</b> + } + if (isPending) { + return <b className="pending">{children}</b>; + } + return ( + <button onClick={() => { + startTransition(() => { + onClick(); + }); + }}> + {children} + </button> + ); +} +``` + +```js AboutTab.js hidden +export default function AboutTab() { + return ( + <p>Welcome to my profile!</p> + ); +} +``` + +```js PostsTab.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +function PostsTab() { + const posts = use(fetchData('/posts')); + return ( + <ul className="items"> + {posts.map(post => + <Post key={post.id} title={post.title} /> + )} + </ul> + ); +} + +function Post({ title }) { + return ( + <li className="item"> + {title} + </li> + ); +} + +export default PostsTab; + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js ContactTab.js hidden +export default function ContactTab() { + return ( + <> + <p> + You can find me online here: + </p> + <ul> + <li>admin@mysite.com</li> + <li>+123456789</li> + </ul> + </> + ); +} +``` + + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url.startsWith('/posts')) { + return await getPosts(); + } else { + throw Error('Not implemented'); + } +} + +async function getPosts() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 1000); + }); + let posts = []; + for (let i = 0; i < 500; i++) { + posts.push({ + id: i, + title: 'Post #' + (i + 1) + }); + } + return posts; +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +.pending { color: #777; } +``` + +</Sandpack> + +[Read more about using transitions with Suspense.](/reference/react/Suspense#preventing-already-revealed-content-from-hiding) + +<Note> + +Transitions will only "wait" long enough to avoid hiding *already revealed* content (like the tab container). For example, if the Posts tab had a [nested `<Suspense>` boundary,](/reference/react/Suspense#revealing-nested-content-as-it-loads) the transition would not "wait" for it. + +</Note> + +--- + +### Building a Suspense-enabled router {/*building-a-suspense-enabled-router*/} + +If you're building your own React framework or a router, we recommend to mark page navigations as transitions. + +```js {3,6,8} +function Router() { + const [page, setPage] = useState('/'); + const [isPending, startTransition] = useTransition(); + + function navigate(url) { + startTransition(() => { + setPage(url); + }); + } + // ... +``` + +This is recommended for two reasons: + +- [Transitions are interruptible,](#marking-a-state-update-as-a-non-blocking-transition) which lets the user click away without waiting for the re-render to complete. +- [Transitions prevent unwanted loading indicators,](#preventing-unwanted-loading-indicators) which lets the user avoid jarring jumps on navigation. + +Here is a tiny simplified router example using transitions for navigations. + +<Sandpack> + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js App.js +import { Suspense, useState, useTransition } from 'react'; +import IndexPage from './IndexPage.js'; +import ArtistPage from './ArtistPage.js'; +import Layout from './Layout.js'; + +export default function App() { + return ( + <Suspense fallback={<BigSpinner />}> + <Router /> + </Suspense> + ); +} + +function Router() { + const [page, setPage] = useState('/'); + const [isPending, startTransition] = useTransition(); + + function navigate(url) { + startTransition(() => { + setPage(url); + }); + } + + let content; + if (page === '/') { + content = ( + <IndexPage navigate={navigate} /> + ); + } else if (page === '/the-beatles') { + content = ( + <ArtistPage + artist={{ + id: 'the-beatles', + name: 'The Beatles', + }} + /> + ); + } + return ( + <Layout isPending={isPending}> + {content} + </Layout> + ); +} + +function BigSpinner() { + return <h2>🌀 Loading...</h2>; +} +``` + +```js Layout.js +export default function Layout({ children, isPending }) { + return ( + <div className="layout"> + <section className="header" style={{ + opacity: isPending ? 0.7 : 1 + }}> + Music Browser + </section> + <main> + {children} + </main> + </div> + ); +} +``` + +```js IndexPage.js +export default function IndexPage({ navigate }) { + return ( + <button onClick={() => navigate('/the-beatles')}> + Open The Beatles artist page + </button> + ); +} +``` + +```js ArtistPage.js +import { Suspense } from 'react'; +import Albums from './Albums.js'; +import Biography from './Biography.js'; +import Panel from './Panel.js'; + +export default function ArtistPage({ artist }) { + return ( + <> + <h1>{artist.name}</h1> + <Biography artistId={artist.id} /> + <Suspense fallback={<AlbumsGlimmer />}> + <Panel> + <Albums artistId={artist.id} /> + </Panel> + </Suspense> + </> + ); +} + +function AlbumsGlimmer() { + return ( + <div className="glimmer-panel"> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + <div className="glimmer-line" /> + </div> + ); +} +``` + +```js Albums.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Albums({ artistId }) { + const albums = use(fetchData(`/${artistId}/albums`)); + return ( + <ul> + {albums.map(album => ( + <li key={album.id}> + {album.title} ({album.year}) + </li> + ))} + </ul> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Biography.js hidden +import { fetchData } from './data.js'; + +// Note: this component is written using an experimental API +// that's not yet available in stable versions of React. + +// For a realistic example you can follow today, try a framework +// that's integrated with Suspense, like Relay or Next.js. + +export default function Biography({ artistId }) { + const bio = use(fetchData(`/${artistId}/bio`)); + return ( + <section> + <p className="bio">{bio}</p> + </section> + ); +} + +// This is a workaround for a bug to get the demo running. +// TODO: replace with real implementation when the bug is fixed. +function use(promise) { + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + promise.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} +``` + +```js Panel.js hidden +export default function Panel({ children }) { + return ( + <section className="panel"> + {children} + </section> + ); +} +``` + +```js data.js hidden +// Note: the way you would do data fetching depends on +// the framework that you use together with Suspense. +// Normally, the caching logic would be inside a framework. + +let cache = new Map(); + +export function fetchData(url) { + if (!cache.has(url)) { + cache.set(url, getData(url)); + } + return cache.get(url); +} + +async function getData(url) { + if (url === '/the-beatles/albums') { + return await getAlbums(); + } else if (url === '/the-beatles/bio') { + return await getBio(); + } else { + throw Error('Not implemented'); + } +} + +async function getBio() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 500); + }); + + return `The Beatles were an English rock band, + formed in Liverpool in 1960, that comprised + John Lennon, Paul McCartney, George Harrison + and Ringo Starr.`; +} + +async function getAlbums() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 3000); + }); + + return [{ + id: 13, + title: 'Let It Be', + year: 1970 + }, { + id: 12, + title: 'Abbey Road', + year: 1969 + }, { + id: 11, + title: 'Yellow Submarine', + year: 1969 + }, { + id: 10, + title: 'The Beatles', + year: 1968 + }, { + id: 9, + title: 'Magical Mystery Tour', + year: 1967 + }, { + id: 8, + title: 'Sgt. Pepper\'s Lonely Hearts Club Band', + year: 1967 + }, { + id: 7, + title: 'Revolver', + year: 1966 + }, { + id: 6, + title: 'Rubber Soul', + year: 1965 + }, { + id: 5, + title: 'Help!', + year: 1965 + }, { + id: 4, + title: 'Beatles For Sale', + year: 1964 + }, { + id: 3, + title: 'A Hard Day\'s Night', + year: 1964 + }, { + id: 2, + title: 'With The Beatles', + year: 1963 + }, { + id: 1, + title: 'Please Please Me', + year: 1963 + }]; +} +``` + +```css +main { + min-height: 200px; + padding: 10px; +} + +.layout { + border: 1px solid black; +} + +.header { + background: #222; + padding: 10px; + text-align: center; + color: white; +} + +.bio { font-style: italic; } + +.panel { + border: 1px solid #aaa; + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-panel { + border: 1px dashed #aaa; + background: linear-gradient(90deg, rgba(221,221,221,1) 0%, rgba(255,255,255,1) 100%); + border-radius: 6px; + margin-top: 20px; + padding: 10px; +} + +.glimmer-line { + display: block; + width: 60%; + height: 20px; + margin: 10px; + border-radius: 4px; + background: #f0f0f0; +} +``` + +</Sandpack> + +<Note> + +[Suspense-enabled](/reference/react/Suspense) routers are expected to wrap the navigation updates into transitions by default. + +</Note> + +--- + +## Troubleshooting {/*troubleshooting*/} + +### Updating an input in a transition doesn't work {/*updating-an-input-in-a-transition-doesnt-work*/} + +You can't use a transition for a state variable that controls an input: + +```js {4,10} +const [text, setText] = useState(''); +// ... +function handleChange(e) { + // ❌ Can't use transitions for controlled input state + startTransition(() => { + setText(e.target.value); + }); +} +// ... +return <input value={text} onChange={handleChange} />; +``` + +This is because transitions are non-blocking, but updating an input in response to the change event should happen synchronously. If you want to run a transition in response to typing, you have two options: + +1. You can declare two separate state variables: one for the input state (which always updates synchronously), and one that you will update in a transition. This lets you control the input using the synchronous state, and pass the transition state variable (which will "lag behind" the input) to the rest of your rendering logic. +2. Alternatively, you can have one state variable, and add [`useDeferredValue`](/reference/react/useDeferredValue) which will "lag behind" the real value. It will trigger non-blocking re-renders to "catch up" with the new value automatically. + +--- + +### React doesn't treat my state update as a transition {/*react-doesnt-treat-my-state-update-as-a-transition*/} + +When you wrap a state update in a transition, make sure that it happens *during* the `startTransition` call: + +```js +startTransition(() => { + // ✅ Setting state *during* startTransition call + setPage('/about'); +}); +``` + +The function you pass to `startTransition` must be synchronous. + +You can't mark an update as a transition like this: + +```js +startTransition(() => { + // ❌ Setting state *after* startTransition call + setTimeout(() => { + setPage('/about'); + }, 1000); +}); +``` + +Instead, you could do this: + +```js +setTimeout(() => { + startTransition(() => { + // ✅ Setting state *during* startTransition call + setPage('/about'); + }); +}, 1000); +``` + +Similarly, you can't mark an update as a transition like this: + +```js +startTransition(async () => { + await someAsyncFunction(); + // ❌ Setting state *after* startTransition call + setPage('/about'); +}); +``` + +However, this works instead: + +```js +await someAsyncFunction(); +startTransition(() => { + // ✅ Setting state *during* startTransition call + setPage('/about'); +}); +``` + +--- + +### I want to call `useTransition` from outside a component {/*i-want-to-call-usetransition-from-outside-a-component*/} + +You can't call `useTransition` outside a component because it's a Hook. In this case, use the standalone [`startTransition`](/reference/react/startTransition) method instead. It works the same way, but it doesn't provide the `isPending` indicator. + +--- + +### The function I pass to `startTransition` executes immediately {/*the-function-i-pass-to-starttransition-executes-immediately*/} + +If you run this code, it will print 1, 2, 3: + +```js {1,3,6} +console.log(1); +startTransition(() => { + console.log(2); + setPage('/about'); +}); +console.log(3); +``` + +**It is expected to print 1, 2, 3.** The function you pass to `startTransition` does not get delayed. Unlike with the browser `setTimeout`, it does not run the callback later. React executes your function immediately, but any state updates scheduled *while it is running* will get marked as transitions. You can imagine that it works like this: + +```js +// A simplified version of how React works + +let isInsideTransition = false; + +function startTransition(scope) { + isInsideTransition = true; + scope(); + isInsideTransition = false; +} + +function setState() { + if (isInsideTransition) { + // ... schedule a transition state update ... + } else { + // ... schedule an urgent state update ... + } +} +``` diff --git a/beta/src/content/warnings/invalid-aria-prop.md b/beta/src/content/warnings/invalid-aria-prop.md new file mode 100644 index 000000000..2d3b4253e --- /dev/null +++ b/beta/src/content/warnings/invalid-aria-prop.md @@ -0,0 +1,11 @@ +--- +title: Invalid ARIA Prop Warning +--- + +This warning will fire if you attempt to render a DOM element with an `aria-*` prop that does not exist in the Web Accessibility Initiative (WAI) Accessible Rich Internet Application (ARIA) [specification](https://www.w3.org/TR/wai-aria-1.1/#states_and_properties). + +1. If you feel that you are using a valid prop, check the spelling carefully. `aria-labelledby` and `aria-activedescendant` are often misspelled. + +2. If you wrote `aria-role`, you may have meant `role`. + +3. Otherwise, if you're on the latest version of React DOM and verified that you're using a valid property name listed in the ARIA specification, please [report a bug](https://github.com/facebook/react/issues/new/choose). diff --git a/beta/src/content/warnings/invalid-hook-call-warning.md b/beta/src/content/warnings/invalid-hook-call-warning.md new file mode 100644 index 000000000..5bbc2bbaa --- /dev/null +++ b/beta/src/content/warnings/invalid-hook-call-warning.md @@ -0,0 +1,158 @@ +--- +title: Rules of Hooks +--- + +You are probably here because you got the following error message: + +<ConsoleBlock level="error"> + +Hooks can only be called inside the body of a function component. + +</ConsoleBlock> + +There are three common reasons you might be seeing it: + +1. You might be **breaking the Rules of Hooks**. +2. You might have **mismatching versions** of React and React DOM. +3. You might have **more than one copy of React** in the same app. + +Let's look at each of these cases. + +## Breaking Rules of Hooks {/*breaking-rules-of-hooks*/} + +Functions whose names start with `use` are called [*Hooks*](/reference/react) in React. + +**Don’t call Hooks inside loops, conditions, or nested functions.** Instead, always use Hooks at the top level of your React function, before any early returns. You can only call Hooks while React is rendering a function component: + +* ✅ Call them at the top level in the body of a [function component](/learn/your-first-component). +* ✅ Call them at the top level in the body of a [custom Hook](/learn/reusing-logic-with-custom-hooks). + +```js{2-3,8-9} +function Counter() { + // ✅ Good: top-level in a function component + const [count, setCount] = useState(0); + // ... +} + +function useWindowWidth() { + // ✅ Good: top-level in a custom Hook + const [width, setWidth] = useState(window.innerWidth); + // ... +} +``` + +It’s **not** supported to call Hooks (functions starting with `use`) in any other cases, for example: + +* 🔴 Do not call Hooks inside conditions or loops. +* 🔴 Do not call Hooks after a conditional `return` statement. +* 🔴 Do not call Hooks in event handlers. +* 🔴 Do not call Hooks in class components. +* 🔴 Do not call Hooks inside functions passed to `useMemo`, `useReducer`, or `useEffect`. + +If you break these rules, you might see this error. + +```js{3-4,11-12,20-21} +function Bad({ cond }) { + if (cond) { + // 🔴 Bad: inside a condition (to fix, move it outside!) + const theme = useContext(ThemeContext); + } + // ... +} + +function Bad() { + for (let i = 0; i < 10; i++) { + // 🔴 Bad: inside a loop (to fix, move it outside!) + const theme = useContext(ThemeContext); + } + // ... +} + +function Bad({ cond }) { + if (cond) { + return; + } + // 🔴 Bad: after a conditional return (to fix, move it before the return!) + const theme = useContext(ThemeContext); + // ... +} + +function Bad() { + function handleClick() { + // 🔴 Bad: inside an event handler (to fix, move it outside!) + const theme = useContext(ThemeContext); + } + // ... +} + +function Bad() { + const style = useMemo(() => { + // 🔴 Bad: inside useMemo (to fix, move it outside!) + const theme = useContext(ThemeContext); + return createStyle(theme); + }); + // ... +} + +class Bad extends React.Component { + render() { + // 🔴 Bad: inside a class component (to fix, write a function component instead of a class!) + useEffect(() => {}) + // ... + } +} +``` + +You can use the [`eslint-plugin-react-hooks` plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks) to catch these mistakes. + +<Note> + +[Custom Hooks](/learn/reusing-logic-with-custom-hooks) *may* call other Hooks (that's their whole purpose). This works because custom Hooks are also supposed to only be called while a function component is rendering. + +</Note> + +## Mismatching Versions of React and React DOM {/*mismatching-versions-of-react-and-react-dom*/} + +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). + +## Duplicate React {/*duplicate-react*/} + +In order for Hooks to work, the `react` import from your application code needs to resolve to the same module as the `react` import from inside the `react-dom` package. + +If these `react` imports resolve to two different exports objects, you will see this warning. This may happen if you **accidentally end up with two copies** of the `react` package. + +If you use Node for package management, you can run this check in your project folder: + +<TerminalBlock> + +npm ls react + +</TerminalBlock> + +If you see more than one React, you'll need to figure out why this happens and fix your dependency tree. For example, maybe a library you're using incorrectly specifies `react` as a dependency (rather than a peer dependency). Until that library is fixed, [Yarn resolutions](https://yarnpkg.com/lang/en/docs/selective-version-resolutions/) is one possible workaround. + +You can also try to debug this problem by adding some logs and restarting your development server: + +```js +// Add this in node_modules/react-dom/index.js +window.React1 = require('react'); + +// Add this in your component file +require('react-dom'); +window.React2 = require('react'); +console.log(window.React1 === window.React2); +``` + +If it prints `false` then you might have two Reacts and need to figure out why that happened. [This issue](https://github.com/facebook/react/issues/13991) includes some common reasons encountered by the community. + +This problem can also come up when you use `npm link` or an equivalent. In that case, your bundler might "see" two Reacts — one in application folder and one in your library folder. Assuming `myapp` and `mylib` are sibling folders, one possible fix is to run `npm link ../myapp/node_modules/react` from `mylib`. This should make the library use the application's React copy. + +<Note> + +In general, React supports using multiple independent copies on one page (for example, if an app and a third-party widget both use it). It only breaks if `require('react')` resolves differently between the component and the `react-dom` copy it was rendered with. + +</Note> + +## Other Causes {/*other-causes*/} + +If none of this worked, please comment in [this issue](https://github.com/facebook/react/issues/13991) and we'll try to help. Try to create a small reproducing example — you might discover the problem as you're doing it. diff --git a/beta/src/content/warnings/special-props.md b/beta/src/content/warnings/special-props.md new file mode 100644 index 000000000..1646b531a --- /dev/null +++ b/beta/src/content/warnings/special-props.md @@ -0,0 +1,7 @@ +--- +title: Special Props Warning +--- + +Most props on a JSX element are passed on to the component, however, there are two special props (`ref` and `key`) which are used by React, and are thus not forwarded to the component. + +For instance, you can't read `props.key` from a component. If you need to access the same value within the child component, you should pass it as a different prop (ex: `<ListItemWrapper key={result.id} id={result.id} />` and read `props.id`). While this may seem redundant, it's important to separate app logic from hints to React. diff --git a/beta/src/content/warnings/unknown-prop.md b/beta/src/content/warnings/unknown-prop.md new file mode 100644 index 000000000..80bcdb142 --- /dev/null +++ b/beta/src/content/warnings/unknown-prop.md @@ -0,0 +1,61 @@ +--- +title: Unknown Prop Warning +--- + +The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around. + +There are a couple of likely reasons this warning could be appearing: + +1. Are you using `{...props}` or `cloneElement(element, props)`? When copying props to a child component, you should ensure that you are not accidentally forwarding props that were intended only for the parent component. See common fixes for this problem below. + +2. You are using a non-standard DOM attribute on a native DOM node, perhaps to represent custom data. If you are trying to attach custom data to a standard DOM element, consider using a custom data attribute as described [on MDN](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_data_attributes). + +3. React does not yet recognize the attribute you specified. This will likely be fixed in a future version of React. React will allow you to pass it without a warning if you write the attribute name lowercase. + +4. You are using a React component without an upper case, for example `<myButton />`. React interprets it as a DOM tag because React JSX transform uses the upper vs. lower case convention to distinguish between user-defined components and DOM tags. For your own React components, use PascalCase. For example, write `<MyButton />` instead of `<myButton />`. + +--- + +If you get this warning because you pass props like `{...props}`, your parent component needs to "consume" any prop that is intended for the parent component and not intended for the child component. Example: + +**Bad:** Unexpected `layout` prop is forwarded to the `div` tag. + +```js +function MyDiv(props) { + if (props.layout === 'horizontal') { + // BAD! Because you know for sure "layout" is not a prop that <div> understands. + return <div {...props} style={getHorizontalStyle()} /> + } else { + // BAD! Because you know for sure "layout" is not a prop that <div> understands. + return <div {...props} style={getVerticalStyle()} /> + } +} +``` + +**Good:** The spread syntax can be used to pull variables off props, and put the remaining props into a variable. + +```js +function MyDiv(props) { + const { layout, ...rest } = props + if (layout === 'horizontal') { + return <div {...rest} style={getHorizontalStyle()} /> + } else { + return <div {...rest} style={getVerticalStyle()} /> + } +} +``` + +**Good:** You can also assign the props to a new object and delete the keys that you're using from the new object. Be sure not to delete the props from the original `this.props` object, since that object should be considered immutable. + +```js +function MyDiv(props) { + const divProps = Object.assign({}, props); + delete divProps.layout; + + if (props.layout === 'horizontal') { + return <div {...divProps} style={getHorizontalStyle()} /> + } else { + return <div {...divProps} style={getVerticalStyle()} /> + } +} +``` diff --git a/beta/src/hooks/usePendingRoute.ts b/beta/src/hooks/usePendingRoute.ts new file mode 100644 index 000000000..73ff0b8af --- /dev/null +++ b/beta/src/hooks/usePendingRoute.ts @@ -0,0 +1,41 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useRouter} from 'next/router'; +import {useState, useRef, useEffect} from 'react'; + +const usePendingRoute = () => { + const {events} = useRouter(); + const [pendingRoute, setPendingRoute] = useState<string | null>(null); + const currentRoute = useRef<string | null>(null); + useEffect(() => { + let routeTransitionTimer: any = null; + + const handleRouteChangeStart = (url: string) => { + clearTimeout(routeTransitionTimer); + routeTransitionTimer = setTimeout(() => { + if (currentRoute.current !== url) { + currentRoute.current = url; + setPendingRoute(url); + } + }, 100); + }; + const handleRouteChangeComplete = () => { + setPendingRoute(null); + clearTimeout(routeTransitionTimer); + }; + events.on('routeChangeStart', handleRouteChangeStart); + events.on('routeChangeComplete', handleRouteChangeComplete); + + return () => { + events.off('routeChangeStart', handleRouteChangeStart); + events.off('routeChangeComplete', handleRouteChangeComplete); + clearTimeout(routeTransitionTimer); + }; + }, []); + + return pendingRoute; +}; + +export default usePendingRoute; diff --git a/beta/src/pages/404.js b/beta/src/pages/404.js new file mode 100644 index 000000000..1dc2fc1e0 --- /dev/null +++ b/beta/src/pages/404.js @@ -0,0 +1,26 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Page} from 'components/Layout/Page'; +import {MDXComponents} from 'components/MDX/MDXComponents'; +import sidebarLearn from '../sidebarLearn.json'; + +const {Intro, MaxWidth, p: P, a: A} = MDXComponents; + +export default function NotFound() { + return ( + <Page toc={[]} meta={{title: 'Not Found'}} routeTree={sidebarLearn}> + <MaxWidth> + <Intro> + <P>This page doesn’t exist.</P> + <P> + Quite possibly, it hasn’t been written yet. This beta is a{' '} + <A href="/#how-much-content-is-ready">work in progress!</A> + </P> + <P>Please check back later.</P> + </Intro> + </MaxWidth> + </Page> + ); +} diff --git a/beta/src/pages/500.js b/beta/src/pages/500.js new file mode 100644 index 000000000..136cb3911 --- /dev/null +++ b/beta/src/pages/500.js @@ -0,0 +1,31 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Page} from 'components/Layout/Page'; +import {MDXComponents} from 'components/MDX/MDXComponents'; +import sidebarLearn from '../sidebarLearn.json'; + +const {Intro, MaxWidth, p: P, a: A} = MDXComponents; + +export default function NotFound() { + return ( + <Page + toc={[]} + routeTree={sidebarLearn} + meta={{title: 'Something Went Wrong'}}> + <MaxWidth> + <Intro> + <P>Something went very wrong.</P> + <P>Sorry about that.</P> + <P> + If you’d like, please{' '} + <A href="https://github.com/reactjs/reactjs.org/issues/new"> + report a bug. + </A> + </P> + </Intro> + </MaxWidth> + </Page> + ); +} diff --git a/beta/src/pages/[[...markdownPath]].js b/beta/src/pages/[[...markdownPath]].js new file mode 100644 index 000000000..a4e123db4 --- /dev/null +++ b/beta/src/pages/[[...markdownPath]].js @@ -0,0 +1,275 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Fragment, useMemo} from 'react'; +import {useRouter} from 'next/router'; +import {MDXComponents} from 'components/MDX/MDXComponents'; +import {Page} from 'components/Layout/Page'; +import sidebarLearn from '../sidebarLearn.json'; +import sidebarReference from '../sidebarReference.json'; + +export default function Layout({content, toc, meta}) { + const parsedContent = useMemo( + () => JSON.parse(content, reviveNodeOnClient), + [content] + ); + const parsedToc = useMemo(() => JSON.parse(toc, reviveNodeOnClient), [toc]); + const section = useActiveSection(); + let routeTree = sidebarLearn; + switch (section) { + case 'reference': + routeTree = sidebarReference; + break; + } + return ( + <Page toc={parsedToc} routeTree={routeTree} meta={meta} section={section}> + {parsedContent} + </Page> + ); +} + +function useActiveSection() { + const {asPath} = useRouter(); + if (asPath.startsWith('/reference')) { + return 'reference'; + } else if (asPath.startsWith('/learn')) { + return 'learn'; + } else if (asPath.startsWith('/blog')) { + return 'learn'; + } else { + return 'home'; + } +} + +// Deserialize a client React tree from JSON. +function reviveNodeOnClient(key, val) { + if (Array.isArray(val) && val[0] == '$r') { + // Assume it's a React element. + let type = val[1]; + let key = val[2]; + let props = val[3]; + if (type === 'wrapper') { + type = Fragment; + props = {children: props.children}; + } + if (MDXComponents[type]) { + type = MDXComponents[type]; + } + if (!type) { + console.error('Unknown type: ' + type); + type = Fragment; + } + return { + $$typeof: Symbol.for('react.element'), + type: type, + key: key, + ref: null, + props: props, + _owner: null, + }; + } else { + return val; + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~ +const DISK_CACHE_BREAKER = 7; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// Put MDX output into JSON for client. +export async function getStaticProps(context) { + const fs = require('fs'); + const { + prepareMDX, + PREPARE_MDX_CACHE_BREAKER, + } = require('../utils/prepareMDX'); + const rootDir = process.cwd() + '/src/content/'; + const mdxComponentNames = Object.keys(MDXComponents); + + // Read MDX from the file. + let path = (context.params.markdownPath || []).join('/') || 'index'; + let mdx; + try { + mdx = fs.readFileSync(rootDir + path + '.md', 'utf8'); + } catch { + mdx = fs.readFileSync(rootDir + path + '/index.md', 'utf8'); + } + + // See if we have a cached output first. + const {FileStore, stableHash} = require('metro-cache'); + const store = new FileStore({ + root: process.cwd() + '/node_modules/.cache/react-docs-mdx/', + }); + const hash = Buffer.from( + stableHash({ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~ IMPORTANT: Everything that the code below may rely on. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + mdx, + mdxComponentNames, + DISK_CACHE_BREAKER, + PREPARE_MDX_CACHE_BREAKER, + lockfile: fs.readFileSync(process.cwd() + '/yarn.lock', 'utf8'), + }) + ); + const cached = await store.get(hash); + if (cached) { + console.log( + 'Reading compiled MDX for /' + path + ' from ./node_modules/.cache/' + ); + return cached; + } + if (process.env.NODE_ENV === 'production') { + console.log( + 'Cache miss for MDX for /' + path + ' from ./node_modules/.cache/' + ); + } + + // If we don't add these fake imports, the MDX compiler + // will insert a bunch of opaque components we can't introspect. + // This will break the prepareMDX() call below. + let mdxWithFakeImports = + mdx + + '\n\n' + + mdxComponentNames + .map((key) => 'import ' + key + ' from "' + key + '";\n') + .join('\n'); + + // Turn the MDX we just read into some JS we can execute. + const {remarkPlugins} = require('../../plugins/markdownToHtml'); + const {compile: compileMdx} = await import('@mdx-js/mdx'); + const visit = (await import('unist-util-visit')).default; + const jsxCode = await compileMdx(mdxWithFakeImports, { + remarkPlugins: [ + ...remarkPlugins, + (await import('remark-gfm')).default, + (await import('remark-frontmatter')).default, + ], + rehypePlugins: [ + // Support stuff like ```js App.js {1-5} active by passing it through. + function rehypeMetaAsAttributes() { + return (tree) => { + visit(tree, 'element', (node) => { + if (node.tagName === 'code' && node.data && node.data.meta) { + node.properties.meta = node.data.meta; + } + }); + }; + }, + ], + }); + const {transform} = require('@babel/core'); + const jsCode = await transform(jsxCode, { + plugins: ['@babel/plugin-transform-modules-commonjs'], + presets: ['@babel/preset-react'], + }).code; + + // Prepare environment for MDX. + let fakeExports = {}; + const fakeRequire = (name) => { + if (name === 'react/jsx-runtime') { + return require('react/jsx-runtime'); + } else { + // For each fake MDX import, give back the string component name. + // It will get serialized later. + return name; + } + }; + const evalJSCode = new Function('require', 'exports', jsCode); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // THIS IS A BUILD-TIME EVAL. NEVER DO THIS WITH UNTRUSTED MDX (LIKE FROM CMS)!!! + // In this case it's okay because anyone who can edit our MDX can also edit this file. + evalJSCode(fakeRequire, fakeExports); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + const reactTree = fakeExports.default({}); + + // Pre-process MDX output and serialize it. + let {toc, children} = prepareMDX(reactTree.props.children); + if (path === 'index') { + toc = []; + } + + // Parse Frontmatter headers from MDX. + const fm = require('gray-matter'); + const meta = fm(mdx).data; + + const output = { + props: { + content: JSON.stringify(children, stringifyNodeOnServer), + toc: JSON.stringify(toc, stringifyNodeOnServer), + meta, + }, + }; + + // Serialize a server React tree node to JSON. + function stringifyNodeOnServer(key, val) { + if (val != null && val.$$typeof === Symbol.for('react.element')) { + // Remove fake MDX props. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const {mdxType, originalType, parentName, ...cleanProps} = val.props; + return [ + '$r', + typeof val.type === 'string' ? val.type : mdxType, + val.key, + cleanProps, + ]; + } else { + return val; + } + } + + // Cache it on the disk. + await store.set(hash, output); + return output; +} + +// Collect all MDX files for static generation. +export async function getStaticPaths() { + const {promisify} = require('util'); + const {resolve} = require('path'); + const fs = require('fs'); + const readdir = promisify(fs.readdir); + const stat = promisify(fs.stat); + const rootDir = process.cwd() + '/src/content'; + + // Find all MD files recursively. + async function getFiles(dir) { + const subdirs = await readdir(dir); + const files = await Promise.all( + subdirs.map(async (subdir) => { + const res = resolve(dir, subdir); + return (await stat(res)).isDirectory() + ? getFiles(res) + : res.slice(rootDir.length + 1); + }) + ); + return files.flat().filter((file) => file.endsWith('.md')); + } + + // 'foo/bar/baz.md' -> ['foo', 'bar', 'baz'] + // 'foo/bar/qux/index.md' -> ['foo', 'bar', 'qux'] + function getSegments(file) { + let segments = file.slice(0, -3).replace(/\\/g, '/').split('/'); + if (segments[segments.length - 1] === 'index') { + segments.pop(); + } + return segments; + } + + const files = await getFiles(rootDir); + const paths = files.map((file) => ({ + params: { + markdownPath: getSegments(file), + // ^^^ CAREFUL HERE. + // If you rename markdownPath, update patches/next-remote-watch.patch too. + // Otherwise you'll break Fast Refresh for all MD files. + }, + })); + + return { + paths: paths, + fallback: false, + }; +} diff --git a/beta/src/pages/_app.tsx b/beta/src/pages/_app.tsx new file mode 100644 index 000000000..edd3f5edb --- /dev/null +++ b/beta/src/pages/_app.tsx @@ -0,0 +1,56 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {useEffect} from 'react'; +import {AppProps} from 'next/app'; +import {useRouter} from 'next/router'; +import {ga} from '../utils/analytics'; + +import '@docsearch/css'; +import '../styles/algolia.css'; +import '../styles/index.css'; +import '../styles/sandpack.css'; + +if (typeof window !== 'undefined') { + if (process.env.NODE_ENV === 'production') { + ga('create', process.env.NEXT_PUBLIC_GA_TRACKING_ID, 'auto'); + } + const terminationEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; + window.addEventListener(terminationEvent, function () { + ga('send', 'timing', 'JS Dependencies', 'unload'); + }); +} + +export default function MyApp({Component, pageProps}: AppProps) { + const router = useRouter(); + + useEffect(() => { + // Taken from StackOverflow. Trying to detect both Safari desktop and mobile. + const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + if (isSafari) { + // This is kind of a lie. + // We still rely on the manual Next.js scrollRestoration logic. + // However, we *also* don't want Safari grey screen during the back swipe gesture. + // Seems like it doesn't hurt to enable auto restore *and* Next.js logic at the same time. + history.scrollRestoration = 'auto'; + } else { + // For other browsers, let Next.js set scrollRestoration to 'manual'. + // It seems to work better for Chrome and Firefox which don't animate the back swipe. + } + }, []); + + useEffect(() => { + const handleRouteChange = (url: string) => { + const cleanedUrl = url.split(/[\?\#]/)[0]; + ga('set', 'page', cleanedUrl); + ga('send', 'pageview'); + }; + router.events.on('routeChangeComplete', handleRouteChange); + return () => { + router.events.off('routeChangeComplete', handleRouteChange); + }; + }, [router.events]); + + return <Component {...pageProps} />; +} diff --git a/beta/src/pages/_document.tsx b/beta/src/pages/_document.tsx new file mode 100644 index 000000000..9e37d6c61 --- /dev/null +++ b/beta/src/pages/_document.tsx @@ -0,0 +1,63 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Html, Head, Main, NextScript} from 'next/document'; + +const MyDocument = () => { + // @todo specify language in HTML? + return ( + <Html lang="en"> + <Head /> + <body className="font-sans antialiased text-lg bg-wash dark:bg-wash-dark text-secondary dark:text-secondary-dark leading-base"> + <script + dangerouslySetInnerHTML={{ + __html: ` + (function () { + function setTheme(newTheme) { + window.__theme = newTheme; + if (newTheme === 'dark') { + document.documentElement.classList.add('dark'); + } else if (newTheme === 'light') { + document.documentElement.classList.remove('dark'); + } + } + + var preferredTheme; + try { + preferredTheme = localStorage.getItem('theme'); + } catch (err) { } + + window.__setPreferredTheme = function(newTheme) { + preferredTheme = newTheme; + setTheme(newTheme); + try { + localStorage.setItem('theme', newTheme); + } catch (err) { } + }; + + var initialTheme = preferredTheme; + var darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + if (!initialTheme) { + initialTheme = darkQuery.matches ? 'dark' : 'light'; + } + setTheme(initialTheme); + + darkQuery.addEventListener('change', function (e) { + if (!preferredTheme) { + setTheme(e.matches ? 'dark' : 'light'); + } + }); + })(); + `, + }} + /> + <Main /> + <NextScript /> + </body> + </Html> + ); +}; + +export default MyDocument; diff --git a/beta/src/sidebarLearn.json b/beta/src/sidebarLearn.json new file mode 100644 index 000000000..b4734b67f --- /dev/null +++ b/beta/src/sidebarLearn.json @@ -0,0 +1,277 @@ +{ + "title": "Learn React", + "heading": true, + "path": "/learn", + "routes": [ + { + "heading": true, + "path": "", + "routes": [ + { + "hasSectionHeader": true, + "sectionHeader": "Get Started" + }, + { + "title": "Installation", + "path": "/learn/installation", + "routes": [ + { + "title": "Start a New React Project", + "path": "/learn/start-a-new-react-project" + }, + { + "title": "Add React to a Website", + "path": "/learn/add-react-to-a-website" + }, + { + "title": "Editor Setup", + "path": "/learn/editor-setup" + }, + { + "title": "React Developer Tools", + "path": "/learn/react-developer-tools" + } + ] + }, + { + "title": "Quick Start", + "path": "/learn", + "routes": [ + { + "title": "Tutorial: Tic-Tac-Toe", + "path": "/learn/tutorial-tic-tac-toe" + }, + { + "title": "Thinking in React", + "path": "/learn/thinking-in-react" + } + ] + }, + { + "hasSectionHeader": true, + "sectionHeader": "Learn React" + }, + { + "title": "Describing the UI", + "tags": [], + "path": "/learn/describing-the-ui", + "routes": [ + { + "title": "Your First Component", + "path": "/learn/your-first-component" + }, + { + "title": "Importing and Exporting Components", + "path": "/learn/importing-and-exporting-components" + }, + { + "title": "Writing Markup with JSX", + "path": "/learn/writing-markup-with-jsx" + }, + { + "title": "JavaScript in JSX with Curly Braces", + "path": "/learn/javascript-in-jsx-with-curly-braces" + }, + { + "title": "Passing Props to a Component", + "path": "/learn/passing-props-to-a-component" + }, + { + "title": "Conditional Rendering", + "path": "/learn/conditional-rendering" + }, + { + "title": "Rendering Lists", + "path": "/learn/rendering-lists" + }, + { + "title": "Keeping Components Pure", + "path": "/learn/keeping-components-pure" + } + ] + }, + { + "title": "Adding Interactivity", + "path": "/learn/adding-interactivity", + "tags": [], + "routes": [ + { + "title": "Responding to Events", + "path": "/learn/responding-to-events" + }, + { + "title": "State: A Component's Memory", + "path": "/learn/state-a-components-memory" + }, + { + "title": "Render and Commit", + "path": "/learn/render-and-commit" + }, + { + "title": "State as a Snapshot", + "path": "/learn/state-as-a-snapshot" + }, + { + "title": "Queueing a Series of State Updates", + "path": "/learn/queueing-a-series-of-state-updates" + }, + { + "title": "Updating Objects in State", + "path": "/learn/updating-objects-in-state" + }, + { + "title": "Updating Arrays in State", + "path": "/learn/updating-arrays-in-state" + } + ] + }, + { + "title": "Managing State", + "path": "/learn/managing-state", + "tags": ["intermediate"], + "routes": [ + { + "title": "Reacting to Input with State", + "path": "/learn/reacting-to-input-with-state" + }, + { + "title": "Choosing the State Structure", + "path": "/learn/choosing-the-state-structure" + }, + { + "title": "Sharing State Between Components", + "path": "/learn/sharing-state-between-components" + }, + { + "title": "Preserving and Resetting State", + "path": "/learn/preserving-and-resetting-state" + }, + { + "title": "Extracting State Logic into a Reducer", + "path": "/learn/extracting-state-logic-into-a-reducer" + }, + { + "title": "Passing Data Deeply with Context", + "path": "/learn/passing-data-deeply-with-context" + }, + { + "title": "Scaling Up with Reducer and Context", + "path": "/learn/scaling-up-with-reducer-and-context" + } + ] + }, + { + "title": "Escape Hatches", + "path": "/learn/escape-hatches", + "tags": ["advanced"], + "routes": [ + { + "title": "Referencing Values with Refs", + "path": "/learn/referencing-values-with-refs" + }, + { + "title": "Manipulating the DOM with Refs", + "path": "/learn/manipulating-the-dom-with-refs" + }, + { + "title": "Synchronizing with Effects", + "path": "/learn/synchronizing-with-effects" + }, + { + "title": "You Might Not Need an Effect", + "path": "/learn/you-might-not-need-an-effect" + }, + { + "title": "Lifecycle of Reactive Effects", + "path": "/learn/lifecycle-of-reactive-effects" + }, + { + "title": "Separating Events from Effects", + "path": "/learn/separating-events-from-effects" + }, + { + "title": "Removing Effect Dependencies", + "path": "/learn/removing-effect-dependencies" + }, + { + "title": "Reusing Logic with Custom Hooks", + "path": "/learn/reusing-logic-with-custom-hooks" + } + ] + }, + { + "hasSectionHeader": true, + "sectionHeader": "About React" + }, + { + "title": "Community", + "path": "/community", + "routes": [ + { + "title": "React Conferences", + "path": "/community/conferences" + }, + { + "title": "React Meetups", + "path": "/community/meetups" + }, + { + "title": "React Videos", + "path": "/community/videos" + }, + { + "title": "Meet the Team", + "path": "/community/team" + }, + { + "title": "Docs Contributors", + "path": "/community/docs-contributors" + }, + { + "title": "Acknowledgements", + "path": "/community/acknowledgements" + }, + { + "title": "Versioning Policy", + "path": "/community/versioning-policy" + } + ] + }, + { + "title": "Blog", + "path": "/blog", + "routes": [ + { + "title": "React Labs: June 2022", + "path": "/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022" + }, + { + "title": "React v18.0", + "path": "/blog/2022/03/29/react-v18" + }, + { + "title": "How to Upgrade to React 18", + "path": "/blog/2022/03/08/react-18-upgrade-guide" + }, + { + "title": "React Conf 2021 Recap", + "path": "/blog/2021/12/17/react-conf-2021-recap" + }, + { + "title": "The Plan for React 18", + "path": "/blog/2021/06/08/the-plan-for-react-18" + }, + { + "title": "Introducing Server Components", + "path": "/blog/2020/12/21/data-fetching-with-react-server-components" + }, + { + "title": "Older posts", + "path": "https://reactjs.org/blog/all.html" + } + ] + } + ] + } + ] +} diff --git a/beta/src/sidebarReference.json b/beta/src/sidebarReference.json new file mode 100644 index 000000000..4510b35ef --- /dev/null +++ b/beta/src/sidebarReference.json @@ -0,0 +1,281 @@ +{ + "title": "API Reference", + "heading": true, + "path": "/reference/react", + "routes": [ + { + "heading": true, + "path": "", + "routes": [ + { + "hasSectionHeader": true, + "sectionHeader": "'react' package" + }, + { + "title": "Components", + "path": "/reference/react/components", + "routes": [ + { + "title": "<Fragment> (<>)", + "path": "/reference/react/Fragment" + }, + { + "title": "<Profiler>", + "path": "/reference/react/Profiler" + }, + { + "title": "<StrictMode>", + "path": "/reference/react/StrictMode" + }, + { + "title": "<Suspense>", + "path": "/reference/react/Suspense" + } + ] + }, + { + "title": "Hooks", + "path": "/reference/react", + "routes": [ + { + "title": "useCallback", + "path": "/reference/react/useCallback" + }, + { + "title": "useContext", + "path": "/reference/react/useContext" + }, + { + "title": "useDebugValue", + "path": "/reference/react/useDebugValue" + }, + { + "title": "useDeferredValue", + "path": "/reference/react/useDeferredValue" + }, + { + "title": "useEffect", + "path": "/reference/react/useEffect" + }, + { + "title": "useId", + "path": "/reference/react/useId" + }, + { + "title": "useImperativeHandle", + "path": "/reference/react/useImperativeHandle" + }, + { + "title": "useInsertionEffect", + "path": "/reference/react/useInsertionEffect" + }, + { + "title": "useLayoutEffect", + "path": "/reference/react/useLayoutEffect" + }, + { + "title": "useMemo", + "path": "/reference/react/useMemo" + }, + { + "title": "useReducer", + "path": "/reference/react/useReducer" + }, + { + "title": "useRef", + "path": "/reference/react/useRef" + }, + { + "title": "useState", + "path": "/reference/react/useState" + }, + { + "title": "useSyncExternalStore", + "path": "/reference/react/useSyncExternalStore" + }, + { + "title": "useTransition", + "path": "/reference/react/useTransition" + } + ] + }, + { + "title": "APIs", + "path": "/reference/react/apis", + "routes": [ + { + "title": "createContext", + "path": "/reference/react/createContext" + }, + { + "title": "forwardRef", + "path": "/reference/react/forwardRef" + }, + { + "title": "lazy", + "path": "/reference/react/lazy" + }, + { + "title": "memo", + "path": "/reference/react/memo" + }, + { + "title": "startTransition", + "path": "/reference/react/startTransition" + } + ] + }, + { + "hasSectionHeader": true, + "sectionHeader": "'react-dom' package" + }, + { + "title": "Components", + "path": "/reference/react-dom/components", + "routes": [ + { + "title": "Common (e.g. <div>)", + "path": "/reference/react-dom/components/common" + }, + { + "title": "<input>", + "path": "/reference/react-dom/components/input" + }, + { + "title": "<option>", + "path": "/reference/react-dom/components/option" + }, + { + "title": "<progress>", + "path": "/reference/react-dom/components/progress" + }, + { + "title": "<select>", + "path": "/reference/react-dom/components/select" + }, + { + "title": "<textarea>", + "path": "/reference/react-dom/components/textarea" + } + ] + }, + { + "title": "APIs", + "path": "/reference/react-dom", + "routes": [ + { + "title": "createPortal", + "path": "/reference/react-dom/createPortal" + }, + { + "title": "flushSync", + "path": "/reference/react-dom/flushSync" + }, + { + "title": "findDOMNode", + "path": "/reference/react-dom/findDOMNode" + }, + { + "title": "hydrate", + "path": "/reference/react-dom/hydrate" + }, + { + "title": "render", + "path": "/reference/react-dom/render" + }, + { + "title": "unmountComponentAtNode", + "path": "/reference/react-dom/unmountComponentAtNode" + } + ] + }, + { + "title": "Client APIs", + "path": "/reference/react-dom/client", + "routes": [ + { + "title": "createRoot", + "path": "/reference/react-dom/client/createRoot" + }, + { + "title": "hydrateRoot", + "path": "/reference/react-dom/client/hydrateRoot" + } + ] + }, + { + "title": "Server APIs", + "path": "/reference/react-dom/server", + "routes": [ + { + "title": "renderToNodeStream", + "path": "/reference/react-dom/server/renderToNodeStream" + }, + { + "title": "renderToPipeableStream", + "path": "/reference/react-dom/server/renderToPipeableStream" + }, + { + "title": "renderToReadableStream", + "path": "/reference/react-dom/server/renderToReadableStream" + }, + { + "title": "renderToStaticMarkup", + "path": "/reference/react-dom/server/renderToStaticMarkup" + }, + { + "title": "renderToStaticNodeStream", + "path": "/reference/react-dom/server/renderToStaticNodeStream" + }, + { + "title": "renderToString", + "path": "/reference/react-dom/server/renderToString" + } + ] + }, + { + "hasSectionHeader": true, + "sectionHeader": "Legacy APIs" + }, + { + "title": "Legacy React APIs", + "path": "/reference/react/legacy", + "routes": [ + { + "title": "Children", + "path": "/reference/react/Children" + }, + { + "title": "cloneElement", + "path": "/reference/react/cloneElement" + }, + { + "title": "Component", + "path": "/reference/react/Component" + }, + { + "title": "createElement", + "path": "/reference/react/createElement" + }, + { + "title": "createFactory", + "path": "/reference/react/createFactory" + }, + { + "title": "createRef", + "path": "/reference/react/createRef" + }, + { + "title": "isValidElement", + "path": "/reference/react/isValidElement" + }, + { + "title": "PureComponent", + "path": "/reference/react/PureComponent" + } + ] + } + ] + } + ] +} diff --git a/beta/src/siteConfig.ts b/beta/src/siteConfig.ts new file mode 100644 index 000000000..f82b2ddc6 --- /dev/null +++ b/beta/src/siteConfig.ts @@ -0,0 +1,15 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +export const siteConfig = { + editUrl: 'https://github.com/reactjs/reactjs.org/edit/beta/src/pages', + copyright: `Copyright © ${new Date().getFullYear()} Facebook Inc. All Rights Reserved.`, + repoUrl: 'https://github.com/facebook/react', + twitterUrl: 'https://twitter.com/reactjs', + algolia: { + appId: '1FCF9AYYAT', + apiKey: '861ccfb8707150c0e776b88357286123', + indexName: 'beta-react', + }, +}; diff --git a/beta/src/styles/algolia.css b/beta/src/styles/algolia.css new file mode 100644 index 000000000..c5c7fb9d3 --- /dev/null +++ b/beta/src/styles/algolia.css @@ -0,0 +1,174 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/* Algolia v3 overrides */ +:root { + --docsearch-modal-background: #fff; + --docsearch-highlight-color: #087ea4; + --docsearch-primary-color: #0074a6; + --docsearch-container-background: rgba(52, 58, 70, 0.8); + --docsearch-modal-shadow: none; + --docsearch-searchbox-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + --ifm-z-index-fixed: 1000; + --docsearch-hit-height: 48px; + --docsearch-searchbox-height: 72px; + --docsearch-footer-height: 108px; + --docsearch-icon-stroke-width: 1.4; + --hover-overlay: rgba(0, 0, 0, 0.05); + --fds-animation-fade-in: cubic-bezier(0, 0, 1, 1); + --fds-animation-fade-out: cubic-bezier(0, 0, 1, 1); +} +html.dark { + --docsearch-modal-background: #23272f; + --docsearch-hit-background: #23272f; + --docsearch-highlight-color: #149eca; +} +.DocSearch--active #__next { + -webkit-filter: blur(0px); + filter: blur(0px); +} +.DocSearch-SearchBar { + @apply py-4; + @apply px-5; +} +.DocSearch-Form { + @apply rounded-lg; + @apply shadow-inner; + @apply text-sm; + @apply bg-gray-10; + @apply outline-none; + @apply h-auto; + @apply focus-within:ring; +} +html.dark .DocSearch-Form { + @apply bg-gray-80; +} +.DocSearch-Dropdown { + @apply px-0; + @apply h-full; + @apply max-h-full; +} +.DocSearch-Commands { + @apply w-full; + @apply justify-between; + @apply border-t; + @apply border-border; + @apply pt-4; +} +html.dark .DocSearch-Commands { + @apply border-border-dark; +} +.DocSearch-Commands-Key { + @apply shadow-none; + @apply bg-gray-10; + @apply text-primary; +} +.DocSearch-Logo { + @apply pt-4; + @apply pb-2; +} +.DocSearch-Label { + @apply text-xs; +} +.DocSearch-Footer { + @apply flex-col-reverse; + @apply items-start; + @apply h-auto; + @apply pb-2; + @apply px-5; + @apply shadow-none; +} +html.dark .DocSearch-Footer { + @apply bg-wash-dark; +} +.DocSearch-Input { + @apply py-3; + @apply text-sm; + @apply leading-tight; + @apply text-primary; + @apply appearance-none !important; + @apply focus:outline-none; +} +html.dark .DocSearch-Input { + @apply text-primary-dark; +} +.DocSearch-Hit a { + @apply rounded-r-lg; + @apply rounded-l-none; + @apply shadow-none; + @apply pl-5; +} +.DocSearch-Hit-source { + @apply uppercase; + @apply tracking-wide; + @apply text-sm; + @apply text-secondary; + @apply font-bold; + @apply pt-0; + @apply pl-5; + @apply m-0; +} +html.dark .DocSearch-Hit-source { + @apply text-secondary-dark; +} +.DocSearch-Dropdown ul { + @apply mr-5; +} +.DocSearch-Hit-title { + @apply text-base; + @apply text-primary; + @apply font-normal; + @apply text-ellipsis; + @apply whitespace-nowrap; + @apply overflow-hidden; +} +html.dark .DocSearch-Hit-title { + @apply text-primary-dark; +} +.DocSearch-Hit-path { + @apply font-normal; +} +.DocSearch-LoadingIndicator svg, +.DocSearch-MagnifierLabel svg { + width: 13px; + height: 13px; + @apply text-gray-30; + @apply mx-1; +} +.DocSearch-Modal { + margin: 0; + @apply flex; + @apply justify-between; + @apply h-full; + @apply max-w-xs; + @apply rounded-r-lg; + @apply rounded-l-none; +} +.DocSearch-Cancel { + @apply pl-5; + @apply ml-0; + @apply text-base; + @apply text-link; + @apply font-normal; +} +@media (max-width: 1024px) { + .DocSearch-Modal { + @apply max-w-full; + } + .DocSearch-Cancel { + @apply inline-block; + } + .DocSearch-Commands { + @apply hidden; + } + .DocSearch-Modal { + @apply rounded-none; + } +} +.DocSearch-Search-Icon { + height: 20px; + width: 20px; + stroke-width: 1.6; + @apply text-gray-60; +} diff --git a/beta/src/styles/index.css b/beta/src/styles/index.css new file mode 100644 index 000000000..462e33fb9 --- /dev/null +++ b/beta/src/styles/index.css @@ -0,0 +1,324 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + @font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('/fonts/Source-Code-Pro-Regular.woff2') format('woff2'); + } + + @font-face { + font-family: 'Optimistic Display'; + src: url('https://beta.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2') + format('woff2'); + font-weight: 300; + font-style: normal; + font-display: swap; + } + + @font-face { + font-family: 'Optimistic Display'; + src: url('https://beta.reactjs.org/fonts/Optimistic_Display_W_Md.woff2') + format('woff2'); + font-weight: 500; + font-style: normal; + font-display: swap; + } + + @font-face { + font-family: 'Optimistic Display'; + src: url('https://beta.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2') + format('woff2'); + font-weight: 700; + font-style: normal; + font-display: swap; + } + + /* Write your own custom base styles here */ + html { + color-scheme: light; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-tap-highlight-color: transparent; + } + + html.dark { + color-scheme: dark; + } + + html .dark-image { + display: none; + } + + html .light-image { + display: block; + } + + html.dark .dark-image { + display: block; + } + + html.dark .light-image { + display: none; + } + + html, + body { + padding: 0; + margin: 0; + } + + @media screen and (max-width: 1023px) { + body { + overflow-x: hidden; + } + } + + /* Start purging... */ + /* Force GPU Accelerated scrolling, credit: Twitter Lite */ + .scrolling-gpu { + transform: translateZ(0); + } + + @layer utilities { + .text-7xl { + font-size: 5rem; + } + + .text-8xl { + font-size: 6rem; + } + } + + a > code { + color: #087ea4 !important; /* blue-50 */ + text-decoration: none !important; + } + + html.dark a > code { + color: #149eca !important; /* blue-40 */ + } + + .text-code { + font-size: calc(1em - 10%) !important; + } + + .text-gradient { + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + box-decoration-break: clone; + background-repeat: no-repeat; + color: transparent; + } + + .text-gradient-electric-blue { + background-image: linear-gradient(45deg, #61dafb, #0072ff); + } + /* Stop purging. */ + /* Your own custom utilities */ + + details { + margin-bottom: 1rem; + } + + table { + width: 100%; + margin-bottom: 1rem; + display: block; + overflow-x: auto; + } + + table td, + table th { + padding: 0.75rem; + vertical-align: top; + border: 1px solid #dee2e6; + overflow: auto; + } + + img { + max-width: calc(min(700px, 100%)); + } + + summary::-webkit-details-marker { + display: none; + } + + /* + * Hopefully when scrollbar-color lands everywhere, + * (and not just in FF), we'll be able to keep just this. + */ + html .no-bg-scrollbar { + scrollbar-color: rgba(0, 0, 0, 0.2) transparent; + } + html.dark .no-bg-scrollbar { + scrollbar-color: rgba(255, 255, 255, 0.2) transparent; + } + /* + * Until then, we have ... this. + * If you're changing this, make sure you've tested: + * - Different browsers (Chrome, Safari, FF) + * - Dark and light modes + * - System scrollbar settings ("always on" vs "when scrolling") + * - Switching between modes should never jump width + * - When you interact with a sidebar, it should always be visible + * - For each combination, test overflowing and non-overflowing sidebar + * I've spent hours picking these so I expect no less diligence from you. + */ + html .no-bg-scrollbar::-webkit-scrollbar, + html .no-bg-scrollbar::-webkit-scrollbar-track { + background-color: transparent; + } + html .no-bg-scrollbar:hover::-webkit-scrollbar-thumb, + html .no-bg-scrollbar:focus::-webkit-scrollbar-thumb, + html .no-bg-scrollbar:focus-within::-webkit-scrollbar-thumb, + html .no-bg-scrollbar:active::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border: 4px solid transparent; + background-clip: content-box; + border-radius: 10px; + } + html .no-bg-scrollbar::-webkit-scrollbar-thumb:hover, + html .no-bg-scrollbar::-webkit-scrollbar-thumb:active { + background-color: rgba(0, 0, 0, 0.35) !important; + } + html.dark .no-bg-scrollbar:hover::-webkit-scrollbar-thumb, + html.dark .no-bg-scrollbar:focus::-webkit-scrollbar-thumb, + html.dark .no-bg-scrollbar:focus-within::-webkit-scrollbar-thumb, + html.dark .no-bg-scrollbar:active::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.2); + } + html.dark .no-bg-scrollbar::-webkit-scrollbar-thumb:hover, + html.dark .no-bg-scrollbar::-webkit-scrollbar-thumb:active { + background-color: rgba(255, 255, 255, 0.35) !important; + } +} + +.code-step * { + color: inherit !important; +} + +.code-step code { + background: none !important; + padding: 2px !important; +} +html.dark .code-step * { + color: inherit !important; +} + +.mdx-heading { + scroll-margin-top: 3.5em; + /* Space for the anchor */ + padding-right: 1em; +} + +@media (min-width: 1024px) { + .mdx-heading { + scroll-margin-top: 1em; + } +} + +.mdx-heading:before { + height: 6rem; + margin-top: -6rem; + visibility: hidden; + content: ''; +} +.mdx-heading .mdx-header-anchor { + /* Prevent the anchor from + overflowing to its own line */ + height: 0px; + width: 0px; +} +.mdx-heading .mdx-header-anchor svg { + display: inline; +} +.mdx-heading .mdx-header-anchor svg { + visibility: hidden; +} +.mdx-heading:hover .mdx-header-anchor svg { + visibility: visible; +} +.mdx-heading .mdx-header-anchor:focus svg { + visibility: visible; +} + +.mdx-blockquote > span > p:first-of-type { + margin-bottom: 0; +} +.mdx-blockquote > span > p:last-of-type { + margin-bottom: 1rem; +} +.mdx-illustration-block { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: center; + align-content: stretch; + align-items: stretch; + gap: 42px; +} +ol.mdx-illustration-block { + gap: 60px; +} +.mdx-illustration-block li { + display: flex; + align-items: flex-start; + align-content: stretch; + justify-content: space-around; + position: relative; + padding: 1rem; +} +.mdx-illustration-block figure { + display: flex; + flex-direction: column; + align-content: center; + align-items: center; + + justify-content: space-between; + position: relative; + height: 100%; +} +.mdx-illustration-block li:after { + content: ' '; + display: block; + position: absolute; + top: 50%; + right: 100%; + transform: translateY(-50%); + width: 60px; + height: 49px; + background: center / contain no-repeat url('/images/g_arrow.png'); +} +.mdx-illustration-block li:first-child:after { + content: ' '; + display: none; +} +.mdx-illustration-block img { + max-height: 250px; + width: 100%; +} +@media (max-width: 680px) { + .mdx-illustration-block { + flex-direction: column; + } + .mdx-illustration-block img { + max-height: 200px; + width: auto; + } + .mdx-illustration-block li:after { + top: 0; + left: 50%; + right: auto; + transform: translateX(-50%) translateY(-100%) rotate(90deg); + } +} diff --git a/beta/src/styles/sandpack.css b/beta/src/styles/sandpack.css new file mode 100644 index 000000000..d211c8ab0 --- /dev/null +++ b/beta/src/styles/sandpack.css @@ -0,0 +1,628 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +.sandpack { + color-scheme: inherit; + -webkit-font-smoothing: antialiased; + + --sp-space-1: 4px; + --sp-space-2: 8px; + --sp-space-3: 12px; + --sp-space-4: 16px; + --sp-space-5: 20px; + --sp-space-6: 24px; + --sp-space-7: 28px; + --sp-space-8: 32px; + --sp-space-9: 36px; + --sp-space-10: 40px; + --sp-space-11: 44px; + --sp-border-radius: 4px; + --sp-layout-height: 300px; + --sp-layout-headerHeight: 40px; + --sp-transitions-default: 150ms ease; + --sp-zIndices-base: 1; + --sp-zIndices-overlay: 2; + --sp-zIndices-top: 3; + + --sp-font-body: Optimistic Display, -apple-system, ui-sans-serif, system-ui, + -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, + Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, + Noto Color Emoji; + --sp-font-mono: Source Code Pro, ui-monospace, SFMono-Regular, Menlo, Monaco, + Consolas, Liberation Mono, Courier New, monospace; + --sp-font-size: calc(1em - 20%); + --sp-font-lineHeight: 24px; +} + +/* Default theme */ +html .sandpack { + --sp-colors-accent: #087ea4; + --sp-colors-clickable: #959da5; + --sp-colors-disabled: #24292e; + --sp-colors-error: #811e18; + --sp-colors-error-surface: #ffcdca; + --sp-colors-surface1: #fff; + --sp-colors-surface2: #e4e7eb; + + --sp-syntax-color-plain: #24292e; + --sp-syntax-color-comment: #6a737d; + --sp-syntax-color-keyword: #d73a49; + --sp-syntax-color-tag: #22863a; + --sp-syntax-color-punctuation: #24292e; + --sp-syntax-color-definition: #6f42c1; + --sp-syntax-color-property: #005cc5; + --sp-syntax-color-static: #032f62; + --sp-syntax-color-string: #032f62; +} + +/* Dark theme */ +html.dark .sp-wrapper { + --sp-colors-accent: #149eca; + --sp-colors-clickable: #999; + --sp-colors-disabled: #fff; + --sp-colors-error: #811e18; + --sp-colors-error-surface: #ffcdca; + --sp-colors-surface1: #16181d; + --sp-colors-surface2: #343a46; + + --sp-syntax-color-plain: #ffffff; + --sp-syntax-color-comment: #757575; + --sp-syntax-color-keyword: #77b7d7; + --sp-syntax-color-tag: #dfab5c; + --sp-syntax-color-punctuation: #ffffff; + --sp-syntax-color-definition: #86d9ca; + --sp-syntax-color-property: #77b7d7; + --sp-syntax-color-static: #c64640; + --sp-syntax-color-string: #977cdc; +} + +/** + * Reset + */ +.sandpack .sp-wrapper { + width: 100%; + + font-size: var(--sp-font-size); + font-family: var(--sp-font-body); + line-height: var(--sp-font-lineHeight); +} + +/** + * Layout + */ +.sandpack .sp-layout { + display: flex; + flex-wrap: wrap; + align-items: stretch; + background-color: var(--sp-colors-surface2); + + -webkit-mask-image: -webkit-radial-gradient( + var(--sp-colors-surface1), + var(--sp-colors-surface1) + ); /* safest way to make all corner rounded */ + + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + overflow: initial; + + gap: 1px; +} + +.sandpack .sp-stack { + display: flex; + flex-direction: column; + width: 100%; + position: relative; +} + +@media screen and (max-width: 768px) { + .sandpack .sp-layout > .sp-stack { + height: auto; + min-width: 100% !important; + } +} + +.sandpack .sp-layout > .sp-stack { + flex: 1 1 0px; + height: var(--sp-layout-height); +} + +/** + * Focus ring + */ +.sandpack--playground .sp-tab-button { + transition: none; +} + +.sandpack--playground .sp-tab-button:focus { + outline: revert; +} + +.sandpack--playground .sp-tab-button:focus-visible { + box-shadow: none; +} + +.sandpack .sp-cm:focus-visible { + box-shadow: inset 0 0 0 4px rgba(20, 158, 202, 0.4); + outline: none; + height: 100%; +} + +/** + * Navigation + */ +.sandpack .sp-tabs-scrollable-container { + overflow: auto; + display: flex; + flex-wrap: nowrap; + align-items: stretch; + min-height: 40px; + margin-bottom: -1px; +} + +.sp-tabs .sp-tab-button { + padding: 0 6px; + border-bottom: 2px solid transparent; +} + +@media (min-width: 768px) { + .sp-tabs .sp-tab-button { + margin: 0 12px 0 0; + } +} + +.sp-tabs .sp-tab-button, +.sp-tabs .sp-tab-button:hover:not(:disabled, [data-active='true']), +.sp-tabs .sp-tab-button[data-active='true'] { + color: var(--sp-colors-accent); +} + +.sp-tabs .sp-tab-button[data-active='true'] { + border-bottom: 2px solid var(--sp-colors-accent); +} + +/** + * Code block + */ +.cm-line { + padding-left: var(--sp-space-5); +} + +/** + * Editor + */ +.sandpack .sp-code-editor { + flex: 1 1; + position: relative; + overflow: auto; + background: var(--sp-colors-surface1); +} + +.sandpack .sp-code-editor .cm-editor, +.sandpack .sp-code-editor .cm-editor .cm-gutters { + background-color: transparent; +} + +.sandpack .sp-code-editor .cm-content, +.sandpack .sp-code-editor .cm-gutters, +.sandpack .sp-code-editor .cm-gutterElement { + padding: 0; + -webkit-font-smoothing: auto; /* Improve the legibility */ +} + +.sandpack .sp-code-editor .cm-content { + overflow-x: auto; + padding-bottom: 18px; +} + +.sandpack--playground .sp-code-editor .cm-line { + padding: 0 var(--sp-space-3); + width: max-content; +} + +.sandpack--playground .sp-code-editor .cm-lineNumbers { + padding-left: var(--sp-space-3); + padding-right: var(--sp-space-1); + font-size: 13.6px; +} + +.sandpack--playground .sp-code-editor .cm-line.cm-errorLine { + @apply bg-red-400; + --tw-bg-opacity: 0.1; /* Background tweak: base color + opacity */ + position: relative; + padding-right: 2em; + display: inline-block; + min-width: 100%; +} + +.sp-code-editor .cm-errorLine:after { + @apply text-red-500; + position: absolute; + right: 8px; + top: 0; + content: '\26A0'; + font-size: 22px; + line-height: 20px; +} + +.sp-code-editor .cm-tooltip { + border: 0; + max-width: 200px; +} + +.sp-code-editor .cm-diagnostic-error { + @apply border-red-40; +} + +.sandpack .sp-cm { + margin: 0px; + outline: none; + height: 100%; +} + +.sp-code-editor .sp-cm .cm-scroller { + overflow-x: hidden; + overflow-y: auto; + padding-top: 18px; +} + +/** + * Syntax highlight (code editor + code block) + */ +.sandpack .sp-syntax-string { + color: var(--sp-syntax-color-string); +} + +.sandpack .sp-syntax-plain { + color: var(--sp-syntax-color-plain); +} + +.sandpack .sp-syntax-comment { + color: var(--sp-syntax-color-comment); +} + +.sandpack .sp-syntax-keyword { + color: var(--sp-syntax-color-keyword); +} + +.sandpack .sp-syntax-definition { + color: var(--sp-syntax-color-definition); +} + +.sandpack .sp-syntax-punctuation { + color: var(--sp-syntax-color-punctuation); +} + +.sandpack .sp-syntax-property { + color: var(--sp-syntax-color-property); +} + +.sandpack .sp-syntax-tag { + color: var(--sp-syntax-color-tag); +} + +.sandpack .sp-syntax-static { + color: var(--sp-syntax-color-static); +} + +/** + * Loading & error overlay component + */ +.sandpack .sp-cube-wrapper { + background-color: var(--sp-colors-surface1); + position: absolute; + right: var(--sp-space-2); + bottom: var(--sp-space-2); + z-index: var(--sp-zIndices-top); + width: 32px; + height: 32px; + border-radius: var(--sp-border-radius); +} + +.sandpack .sp-button { + display: flex; + align-items: center; + margin: auto; + width: 100%; + height: 100%; +} + +.sandpack .sp-button svg { + min-width: var(--sp-space-5); + width: var(--sp-space-5); + height: var(--sp-space-5); + margin: auto; +} + +.sandpack .sp-cube-wrapper .sp-cube { + display: flex; +} + +.sandpack .sp-cube-wrapper .sp-button { + display: none; +} + +.sandpack .sp-cube-wrapper:hover .sp-button { + display: block; +} + +.sandpack .sp-cube-wrapper:hover .sp-cube { + display: none; +} + +.sandpack .sp-cube { + transform: translate(-4px, 9px) scale(0.13, 0.13); +} + +.sandpack .sp-cube * { + position: absolute; + width: 96px; + height: 96px; +} + +@keyframes cubeRotate { + 0% { + transform: rotateX(-25.5deg) rotateY(45deg); + } + + 100% { + transform: rotateX(-25.5deg) rotateY(405deg); + } +} + +.sandpack .sp-sides { + animation: cubeRotate 1s linear infinite; + animation-fill-mode: forwards; + transform-style: preserve-3d; + transform: rotateX(-25.5deg) rotateY(45deg); +} + +.sandpack .sp-sides * { + border: 10px solid var(--sp-colors-clickable); + border-radius: 8px; + background: var(--sp-colors-surface1); +} + +.sandpack .sp-sides .top { + transform: rotateX(90deg) translateZ(44px); + transform-origin: 50% 50%; +} +.sandpack .sp-sides .bottom { + transform: rotateX(-90deg) translateZ(44px); + transform-origin: 50% 50%; +} +.sandpack .sp-sides .front { + transform: rotateY(0deg) translateZ(44px); + transform-origin: 50% 50%; +} +.sandpack .sp-sides .back { + transform: rotateY(-180deg) translateZ(44px); + transform-origin: 50% 50%; +} +.sandpack .sp-sides .left { + transform: rotateY(-90deg) translateZ(44px); + transform-origin: 50% 50%; +} +.sandpack .sp-sides .right { + transform: rotateY(90deg) translateZ(44px); + transform-origin: 50% 50%; +} + +.sandpack .sp-overlay { + @apply bg-card; + position: absolute; + inset: 0; + z-index: var(--sp-zIndices-top); +} + +.sandpack .sp-error { + padding: var(--sp-space-4); + white-space: pre-wrap; + font-family: var(--sp-font-mono); + background-color: var(--sp-colors-error-surface); +} + +@keyframes fadeIn { + 0% { + opacity: 0; + transform: translateY(4px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.sandpack .sp-error-message { + animation: fadeIn 150ms ease; + color: var(--sp-colors-error); +} + +html.dark .sandpack--playground .sp-overlay { + @apply bg-wash-dark; +} + +/** + * Placeholder + */ +.sandpack .sp-code-editor .sp-pre-placeholder { + @apply font-mono; + font-size: 13.6px; + line-height: 24px; + padding: 18px 0; + -webkit-font-smoothing: auto; +} + +.sandpack--playground .sp-code-editor .sp-pre-placeholder { + padding-left: 48px !important; + margin-left: 0px !important; +} + +.text-xl .sp-pre-placeholder { + font-size: 16px !important; + line-height: 24px !important; +} + +/** + * Expand button + */ +.sandpack .sp-layout { + min-height: 216px; +} + +.sandpack .sp-layout > .sp-stack:nth-child(1) { + /* Force vertical if there isn't enough space. */ + min-width: 431px; + /* No min height on mobile because we know code in advance. */ + /* Max height is needed to avoid too long files. */ + max-height: 40vh; +} + +.sandpack .sp-layout > .sp-stack:nth-child(2) { + /* Force vertical if there isn't enough space. */ + min-width: 431px; + /* Keep preview a fixed size on mobile to avoid jumps. */ + /* This is because we don't know its content in advance. */ + min-height: 40vh; + max-height: 40vh; +} +.sandpack .sp-layout.sp-layout-expanded > .sp-stack:nth-child(1) { + /* Clicking "show more" lets mobile editor go full height. */ + max-height: unset; + height: auto; +} + +.sandpack .sp-layout.sp-layout-expanded > .sp-stack:nth-child(2) { + /* Clicking "show more" lets mobile preview go full height. */ + max-height: unset; + height: auto; +} + +@media (min-width: 1280px) { + .sandpack .sp-layout > .sp-stack:nth-child(1) { + /* On desktop, clamp height by pixels instead. */ + height: auto; + min-height: unset; + max-height: 406px; + } + .sandpack .sp-layout > .sp-stack:nth-child(2) { + /* On desktop, clamp height by pixels instead. */ + height: auto; + min-height: unset; + max-height: 406px; + } + .sandpack .sp-layout.sp-layout-expanded > .sp-stack:nth-child(1) { + max-height: unset; + } + .sandpack .sp-layout.sp-layout-expanded > .sp-stack:nth-child(2) { + max-height: unset; + } +} + +.sandpack .sp-layout .sandpack-expand { + border-left: none; + margin-left: 0; +} + +.expandable-callout .sp-stack:nth-child(2) { + min-width: 431px; + min-height: 40vh; + max-height: 40vh; +} + +/** + * Integrations: console + */ +.sandpack .console .sp-cm, +.sandpack .console .sp-cm .cm-scroller, +.sandpack .console .sp-cm .cm-line { + padding: 0px !important; +} + +/** + * Integrations: eslint + */ +.sandpack .sp-code-editor .cm-diagnostic { + @apply text-secondary; +} + +/** + * Integrations: React devtools inline + */ +.sandpack .sp-devtools { + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + overflow: hidden; +} + +.sandpack .sp-wrapper .sp-layout-devtools { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +/** + * Overwrite inline sty + */ +.sandpack .sp-devtools > div { + --color-background: var(--sp-colors-surface1) !important; + --color-background-inactive: var(--sp-colors-surface2) !important; + --color-background-selected: var(--sp-colors-accent) !important; + --color-background-hover: transparent !important; + --color-modal-background: #ffffffd2 !important; + + --color-tab-selected-border: #087ea4 !important; + + --color-component-name: var(--sp-syntax-color-definition) !important; + --color-attribute-name: var(--sp-syntax-color-property) !important; + --color-attribute-value: var(--sp-syntax-color-string) !important; + --color-attribute-editable-value: var(--sp-syntax-color-property) !important; + --color-attribute-name-not-editable: var(--sp-colors-clickable) !important; + --color-button-background-focus: var(--sp-colors-surface2) !important; + + --color-button-active: var(--sp-colors-accent) !important; + --color-button-background: transparent !important; + --color-button: var(--sp-colors-clickable) !important; + --color-button-hover: var(--sp-colors-disabled) !important; + + --color-border: var(--sp-colors-surface2) !important; + --color-text: rgb(35, 39, 47) !important; +} + +html.dark .sp-devtools > div { + --color-text: var(--sp-colors-clickable) !important; + --color-modal-background: #16181de0 !important; +} + +.sandpack .sp-devtools table td { + border: 1px solid var(--sp-colors-surface2); +} + +/** + * Hard fixes + */ + +/** + * The text-size-adjust CSS property controls the text inflation + * algorithm used on some smartphones and tablets + */ +.sandpack .sp-cm { + -webkit-text-size-adjust: none; +} + +/** + * For iOS: prevent browser zoom when clicking on sandbox. + * Does NOT apply to code blocks. + */ +@media screen and (max-width: 768px) { + @supports (-webkit-overflow-scrolling: touch) { + .sandpack--playground .cm-content, + .sandpack--playground .sp-code-editor .sp-pre-placeholder { + font-size: initial; + } + .DocSearch-Input { + font-size: initial; + } + } +} diff --git a/beta/src/utils/analytics.ts b/beta/src/utils/analytics.ts new file mode 100644 index 000000000..c2cc623b9 --- /dev/null +++ b/beta/src/utils/analytics.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +let buffer: Array<any> = []; +let galite: null | Function = null; +let galitePromise: null | Promise<any> = null; + +export function ga(...args: any[]): void { + if (typeof galite === 'function') { + galite.apply(null, args); + return; + } + buffer.push(args); + if (!galitePromise) { + // @ts-ignore + galitePromise = import('ga-lite').then((mod) => { + galite = mod.default; + galitePromise = null; + buffer.forEach((args) => { + mod.default.apply(null, args); + }); + buffer = []; + }); + } +} diff --git a/beta/src/utils/emptyShim.js b/beta/src/utils/emptyShim.js new file mode 100644 index 000000000..294b03de4 --- /dev/null +++ b/beta/src/utils/emptyShim.js @@ -0,0 +1,6 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// We use this file to shim out dependencies that we don't need. +// See next.config.js. diff --git a/beta/src/utils/forwardRefWithAs.tsx b/beta/src/utils/forwardRefWithAs.tsx new file mode 100644 index 000000000..a0e73370d --- /dev/null +++ b/beta/src/utils/forwardRefWithAs.tsx @@ -0,0 +1,145 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +/** + * Copied from Reach UI utils... + * + * It fixes TypeScript type inferencing to work with <Comp as={AnotherComp} /> + */ + +import * as React from 'react'; + +/** + * React.Ref uses the readonly type `React.RefObject` instead of + * `React.MutableRefObject`, We pretty much always assume ref objects are + * mutable (at least when we create them), so this type is a workaround so some + * of the weird mechanics of using refs with TS. + */ +export type AssignableRef<ValueType> = + | { + bivarianceHack(instance: ValueType | null): void; + }['bivarianceHack'] + | React.MutableRefObject<ValueType | null> + | null; + +//////////////////////////////////////////////////////////////////////////////// +// The following types help us deal with the `as` prop. +// I kind of hacked around until I got this to work using some other projects, +// as a rough guide, but it does seem to work so, err, that's cool? Yay TS! 🙃 +// P = additional props +// T = type of component to render + +export type As<BaseProps = any> = React.ElementType<BaseProps>; + +export type PropsWithAs< + ComponentType extends As, + ComponentProps +> = ComponentProps & + Omit< + React.ComponentPropsWithRef<ComponentType>, + 'as' | keyof ComponentProps + > & { + as?: ComponentType; + }; + +export type PropsFromAs< + ComponentType extends As, + ComponentProps +> = (PropsWithAs<ComponentType, ComponentProps> & {as: ComponentType}) & + PropsWithAs<ComponentType, ComponentProps>; + +export type ComponentWithForwardedRef< + ElementType extends React.ElementType, + ComponentProps +> = React.ForwardRefExoticComponent< + ComponentProps & + React.HTMLProps<React.ElementType<ElementType>> & + React.ComponentPropsWithRef<ElementType> +>; + +export interface ComponentWithAs<ComponentType extends As, ComponentProps> { + // These types are a bit of a hack, but cover us in cases where the `as` prop + // is not a JSX string type. Makes the compiler happy so 🤷♂️ + <TT extends As>( + props: PropsWithAs<TT, ComponentProps> + ): React.ReactElement | null; + ( + props: PropsWithAs<ComponentType, ComponentProps> + ): React.ReactElement | null; + + displayName?: string; + propTypes?: React.WeakValidationMap< + PropsWithAs<ComponentType, ComponentProps> + >; + contextTypes?: React.ValidationMap<any>; + defaultProps?: Partial<PropsWithAs<ComponentType, ComponentProps>>; +} + +/** + * This is a hack for sure. The thing is, getting a component to intelligently + * infer props based on a component or JSX string passed into an `as` prop is + * kind of a huge pain. Getting it to work and satisfy the constraints of + * `forwardRef` seems dang near impossible. To avoid needing to do this awkward + * type song-and-dance every time we want to forward a ref into a component + * that accepts an `as` prop, we abstract all of that mess to this function for + * the time time being. + * + * TODO: Eventually we should probably just try to get the type defs above + * working across the board, but ain't nobody got time for that mess! + * + * @param Comp + */ +export function forwardRefWithAs<Props, ComponentType extends As>( + comp: ( + props: PropsFromAs<ComponentType, Props>, + ref: React.RefObject<any> + ) => React.ReactElement | null +) { + return React.forwardRef(comp as any) as unknown as ComponentWithAs< + ComponentType, + Props + >; +} + +/* +Test components to make sure our dynamic As prop components work as intended +type PopupProps = { + lol: string; + children?: React.ReactNode | ((value?: number) => JSX.Element); +}; +export const Popup = forwardRefWithAs<PopupProps, 'input'>( + ({ as: Comp = 'input', lol, className, children, ...props }, ref) => { + return ( + <Comp ref={ref} {...props}> + {typeof children === 'function' ? children(56) : children} + </Comp> + ); + } +); +export const TryMe1: React.FC = () => { + return <Popup as="input" lol="lol" name="me" />; +}; +export const TryMe2: React.FC = () => { + let ref = React.useRef(null); + return <Popup ref={ref} as="div" lol="lol" />; +}; + +export const TryMe4: React.FC = () => { + return <Popup as={Whoa} lol="lol" test="123" name="boop" />; +}; +export const Whoa: React.FC<{ + help?: boolean; + lol: string; + name: string; + test: string; +}> = props => { + return <input {...props} />; +}; +*/ +// export const TryMe3: React.FC = () => { +// return <Popup as={Cool} lol="lol" name="me" test="123" />; +// }; +// let Cool = styled(Whoa)` +// padding: 10px; +// ` diff --git a/beta/src/utils/prepareMDX.js b/beta/src/utils/prepareMDX.js new file mode 100644 index 000000000..639d7db90 --- /dev/null +++ b/beta/src/utils/prepareMDX.js @@ -0,0 +1,117 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {Children} from 'react'; + +// TODO: This logic could be in MDX plugins instead. + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +export const PREPARE_MDX_CACHE_BREAKER = 2; +// !!! IMPORTANT !!! Bump this if you change any logic. +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +export function prepareMDX(rawChildren) { + const toc = getTableOfContents(rawChildren, /* depth */ 10); + const children = wrapChildrenInMaxWidthContainers(rawChildren); + return {toc, children}; +} + +function wrapChildrenInMaxWidthContainers(children) { + // Auto-wrap everything except a few types into + // <MaxWidth> wrappers. Keep reusing the same + // wrapper as long as we can until we meet + // a full-width section which interrupts it. + let fullWidthTypes = [ + 'Sandpack', + 'FullWidth', + 'Illustration', + 'IllustrationBlock', + 'Challenges', + 'Recipes', + ]; + let wrapQueue = []; + let finalChildren = []; + function flushWrapper(key) { + if (wrapQueue.length > 0) { + const Wrapper = 'MaxWidth'; + finalChildren.push(<Wrapper key={key}>{wrapQueue}</Wrapper>); + wrapQueue = []; + } + } + function handleChild(child, key) { + if (child == null) { + return; + } + if (typeof child !== 'object') { + wrapQueue.push(child); + return; + } + if (fullWidthTypes.includes(child.type)) { + flushWrapper(key); + finalChildren.push(child); + } else { + wrapQueue.push(child); + } + } + Children.forEach(children, handleChild); + flushWrapper('last'); + return finalChildren; +} + +function getTableOfContents(children, depth) { + const anchors = []; + extractHeaders(children, depth, anchors); + if (anchors.length > 0) { + anchors.unshift({ + url: '#', + text: 'Overview', + depth: 2, + }); + } + return anchors; +} + +const headerTypes = new Set([ + 'h1', + 'h2', + 'h3', + 'Challenges', + 'Recap', + 'TeamMember', +]); +function extractHeaders(children, depth, out) { + for (const child of Children.toArray(children)) { + if (child.type && headerTypes.has(child.type)) { + let header; + if (child.type === 'Challenges') { + header = { + url: '#challenges', + depth: 2, + text: 'Challenges', + }; + } else if (child.type === 'Recap') { + header = { + url: '#recap', + depth: 2, + text: 'Recap', + }; + } else if (child.type === 'TeamMember') { + header = { + url: '#' + child.props.permalink, + depth: 3, + text: child.props.name, + }; + } else { + header = { + url: '#' + child.props.id, + depth: (child.type && parseInt(child.type.replace('h', ''), 0)) ?? 0, + text: child.props.children, + }; + } + out.push(header); + } else if (child.children && depth > 0) { + extractHeaders(child.children, depth - 1, out); + } + } +} diff --git a/beta/src/utils/processShim.js b/beta/src/utils/processShim.js new file mode 100644 index 000000000..7d8454988 --- /dev/null +++ b/beta/src/utils/processShim.js @@ -0,0 +1,9 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// Used in next.config.js to remove the process transitive dependency. +module.exports = { + env: {}, + cwd() {}, +}; diff --git a/beta/src/utils/rafShim.js b/beta/src/utils/rafShim.js new file mode 100644 index 000000000..9c3097d8f --- /dev/null +++ b/beta/src/utils/rafShim.js @@ -0,0 +1,6 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +// Used in next.config.js to remove the raf transitive dependency. +export default window.requestAnimationFrame; diff --git a/beta/src/utils/toCommaSeparatedList.tsx b/beta/src/utils/toCommaSeparatedList.tsx new file mode 100644 index 000000000..e95ee1799 --- /dev/null +++ b/beta/src/utils/toCommaSeparatedList.tsx @@ -0,0 +1,35 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import * as React from 'react'; + +const addString = (list: React.ReactNode[], string: string) => + list.push(<span key={`${list.length}-${string}`}>{string}</span>); + +function toCommaSeparatedList<Item>( + array: Item[], + renderCallback: (item: Item, index: number) => React.ReactNode +): React.ReactNode[] { + if (array.length <= 1) { + return array.map(renderCallback); + } + + const list: React.ReactNode[] = []; + + array.forEach((item, index) => { + if (index === array.length - 1) { + addString(list, array.length === 2 ? ' and ' : ', and '); + list.push(renderCallback(item, index)); + } else if (index > 0) { + addString(list, ', '); + list.push(renderCallback(item, index)); + } else { + list.push(renderCallback(item, index)); + } + }); + + return list; +} + +export default toCommaSeparatedList; diff --git a/beta/tailwind.config.js b/beta/tailwind.config.js new file mode 100644 index 000000000..bcedbe064 --- /dev/null +++ b/beta/tailwind.config.js @@ -0,0 +1,79 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +const defaultTheme = require('tailwindcss/defaultTheme'); +const colors = require('./colors'); + +module.exports = { + content: [ + './src/components/**/*.{js,ts,jsx,tsx}', + './src/pages/**/*.{js,ts,jsx,tsx}', + './src/styles/**/*.{js,ts,jsx,tsx}', + ], + darkMode: 'class', + theme: { + // Override base screen sizes + screens: { + ...defaultTheme.screens, + betterhover: {raw: '(hover: hover)'}, + }, + boxShadow: { + sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + DEFAULT: + '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + lg: '0px 0.8px 2px rgba(0, 0, 0, 0.032), 0px 2.7px 6.7px rgba(0, 0, 0, 0.048), 0px 12px 30px rgba(0, 0, 0, 0.08)', + 'lg-dark': + '0 0 0 1px rgba(255,255,255,.15), 0px 0.8px 2px rgba(0, 0, 0, 0.032), 0px 2.7px 6.7px rgba(0, 0, 0, 0.048), 0px 12px 30px rgba(0, 0, 0, 0.08)', + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '3xl': '0 35px 60px -15px rgba(0, 0, 0, 0.3)', + inner: 'inset 0 1px 4px 0 rgba(0, 0, 0, 0.05)', + none: 'none', + }, + extend: { + maxWidth: { + xs: '21rem', + }, + outline: { + blue: ['1px auto ' + colors.link, '3px'], + }, + opacity: { + 8: '0.08', + }, + fontFamily: { + sans: [ + 'Optimistic Display', + '-apple-system', + ...defaultTheme.fontFamily.sans, + ], + mono: ['"Source Code Pro"', ...defaultTheme.fontFamily.mono], + }, + lineHeight: { + base: '30px', + large: '38px', + }, + fontSize: { + '6xl': '68px', + '5xl': '40px', + '4xl': '32px', + '3xl': '28px', + '2xl': '24px', + xl: '20px', + lg: '17px', + base: '15px', + sm: '13px', + xs: '11px', + code: 'calc(1em - 20%)', + }, + colors, + gridTemplateColumns: { + 'only-content': 'auto', + 'sidebar-content': '20rem auto', + 'sidebar-content-toc': '20rem auto 20rem', + }, + }, + }, + plugins: [], +}; diff --git a/beta/tsconfig.json b/beta/tsconfig.json new file mode 100644 index 000000000..440b38510 --- /dev/null +++ b/beta/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "baseUrl": "src", + "incremental": true + }, + "include": [ + "next-env.d.ts", + "src/**/*.ts", + "src/**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/beta/vercel.json b/beta/vercel.json new file mode 100644 index 000000000..3098945b6 --- /dev/null +++ b/beta/vercel.json @@ -0,0 +1,299 @@ +{ + "github": { + "silent": true + }, + "trailingSlash": false, + "redirects": [ + { + "source": "/docs/add-react-to-a-website.html", + "destination": "/learn/add-react-to-a-website", + "permanent": false + }, + { + "source": "/docs/create-a-new-react-app.html", + "destination": "/learn/start-a-new-react-project", + "permanent": false + }, + { + "source": "/docs/release-channels.html", + "destination": "/community/versioning-policy", + "permanent": false + }, + { + "source": "/docs/thinking-in-react.html", + "destination": "/learn/thinking-in-react", + "permanent": false + }, + { + "source": "/tutorial/tutorial.html", + "destination": "/learn/tutorial-tic-tac-toe", + "permanent": false + }, + { + "source": "/community/support.html", + "destination": "/community", + "permanent": false + }, + { + "source": "/community/conferences.html", + "destination": "/community/conferences", + "permanent": false + }, + { + "source": "/community/meetups.html", + "destination": "/community/meetups", + "permanent": false + }, + { + "source": "/community/team.html", + "destination": "/community/team", + "permanent": false + }, + { + "source": "/community/videos.html", + "destination": "/community/videos", + "permanent": false + }, + { + "source": "/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.html", + "destination": "/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022", + "permanent": false + }, + { + "source": "/blog/2022/03/29/react-v18.html", + "destination": "/blog/2022/03/29/react-v18", + "permanent": false + }, + { + "source": "/blog/2022/03/08/react-18-upgrade-guide.html", + "destination": "/blog/2022/03/08/react-18-upgrade-guide", + "permanent": false + }, + { + "source": "/blog/2021/12/17/react-conf-2021-recap.html", + "destination": "/blog/2021/12/17/react-conf-2021-recap", + "permanent": false + }, + { + "source": "/blog/2021/06/08/the-plan-for-react-18.html", + "destination": "/blog/2021/06/08/the-plan-for-react-18", + "permanent": false + }, + { + "source": "/blog/2020/12/21/data-fetching-with-react-server-components.html", + "destination": "/blog/2020/12/21/data-fetching-with-react-server-components", + "permanent": false + }, + { + "source": "/:path*(\\.html)", + "destination": "https://legacy.reactjs.org/:path*.html", + "permanent": false + }, + { + "source": "/tips/controlled-input-null-value.html", + "destination": "/reference/react-dom/components/input#im-getting-an-error-a-component-is-changing-an-uncontrolled-input-to-be-controlled", + "permanent": false + }, + { + "source": "/link/switch-to-createroot", + "destination": "https://github.com/reactwg/react-18/discussions/5", + "permanent": false + }, + { + "source": "/server-components", + "destination": "/blog/2020/12/21/data-fetching-with-react-server-components", + "permanent": false + }, + { + "source": "/concurrent", + "destination": "/blog/2022/03/29/react-v18", + "permanent": false + }, + { + "source": "/hooks", + "destination": "/reference/react", + "permanent": false + }, + { + "source": "/tutorial", + "destination": "/learn/tutorial-tic-tac-toe", + "permanent": false + }, + { + "source": "/link/attribute-behavior", + "destination": "https://legacy.reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html#changes-in-detail", + "permanent": false + }, + { + "source": "/link/controlled-components", + "destination": "/reference/react-dom/components/input#controlling-an-input-with-a-state-variable", + "permanent": false + }, + { + "source": "/link/crossorigin-error", + "destination": "https://legacy.reactjs.org/docs/cross-origin-errors.html", + "permanent": false + }, + { + "source": "/link/dangerously-set-inner-html", + "destination": "/reference/react-dom/components/common#dangerously-setting-the-inner-html", + "permanent": false + }, + { + "source": "/link/derived-state", + "destination": "https://legacy.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html", + "permanent": false + }, + { + "source": "/link/error-boundaries", + "destination": "https://legacy.reactjs.org/docs/error-boundaries.html", + "permanent": false + }, + { + "source": "/link/event-pooling", + "destination": "https://legacy.reactjs.org/docs/legacy-event-pooling.html", + "permanent": false + }, + { + "source": "/link/hooks-data-fetching", + "destination": "/reference/react/useEffect#fetching-data-with-effects", + "permanent": false + }, + { + "source": "/link/invalid-aria-props", + "destination": "/warnings/invalid-aria-prop", + "permanent": false + }, + { + "source": "/link/invalid-hook-call", + "destination": "/warnings/invalid-hook-call-warning", + "permanent": false + }, + { + "source": "/link/legacy-context", + "destination": "https://legacy.reactjs.org/docs/legacy-context.html", + "permanent": false + }, + { + "source": "/link/legacy-factories", + "destination": "https://legacy.reactjs.org/warnings/legacy-factories.html", + "permanent": false + }, + { + "source": "/link/perf-use-production-build", + "destination": "https://legacy.reactjs.org/docs/optimizing-performance.html#use-the-production-build", + "permanent": false + }, + { + "source": "/link/react-devtools", + "destination": "/learn/react-developer-tools", + "permanent": false + }, + { + "source": "/link/react-polyfills", + "destination": "https://legacy.reactjs.org/docs/javascript-environment-requirements.html", + "permanent": false + }, + { + "source": "/link/refs-must-have-owner", + "destination": "https://legacy.reactjs.org/warnings/refs-must-have-owner.html", + "permanent": false + }, + { + "source": "/link/rules-of-hooks", + "destination": "/warnings/invalid-hook-call-warning", + "permanent": false + }, + { + "source": "/link/special-props", + "destination": "/warnings/special-props", + "permanent": false + }, + { + "source": "/link/strict-mode-find-node", + "destination": "/reference/react-dom/findDOMNode#alternatives", + "permanent": false + }, + { + "source": "/link/strict-mode-string-ref", + "destination": "https://legacy.reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs", + "permanent": false + }, + { + "source": "/link/unsafe-component-lifecycles", + "destination": "https://legacy.reactjs.org/blog/2018/03/27/update-on-async-rendering.html", + "permanent": false + }, + { + "source": "/link/warning-keys", + "destination": "/learn/rendering-lists#keeping-list-items-in-order-with-key", + "permanent": false + }, + { + "source": "/link/wrap-tests-with-act", + "destination": "https://legacy.reactjs.org/docs/test-utils.html#act", + "permanent": false + }, + { + "source": "/link/interaction-tracing", + "destination": "https://gist.github.com/bvaughn/8de925562903afd2e7a12554adcdda16", + "permanent": false + }, + { + "source": "/link/profiling", + "destination": "https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977", + "permanent": false + }, + { + "source": "/link/test-utils-mock-component", + "destination": "https://gist.github.com/bvaughn/fbf41b3f895bf2d297935faa5525eee9", + "permanent": false + }, + { + "source": "/link/uselayouteffect-ssr", + "destination": "https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85", + "permanent": false + }, + { + "source": "/link/react-devtools-faq", + "destination": "https://github.com/facebook/react/tree/main/packages/react-devtools#faq", + "permanent": false + }, + { + "source": "/link/setstate-in-render", + "destination": "https://github.com/facebook/react/issues/18178#issuecomment-595846312", + "permanent": false + }, + { + "source": "/version/15.6", + "destination": "https://react-legacy.netlify.app", + "permanent": false + }, + { + "source": "/apis/:path*", + "destination": "/reference/:path*", + "permanent": true + }, + { + "source": "/reference", + "destination": "/reference/react", + "permanent": true + }, + { + "source": "/learn/meet-the-team", + "destination": "/community/team", + "permanent": true + } + ], + "headers": [ + { + "source": "/fonts/(.*).woff2", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=31536000, immutable" + } + ] + } + ] +} diff --git a/beta/yarn.lock b/beta/yarn.lock new file mode 100644 index 000000000..5aa76ffbe --- /dev/null +++ b/beta/yarn.lock @@ -0,0 +1,6690 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@algolia/autocomplete-core@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.2.2.tgz#c121e70c78fd0175c989a219918124ad7758e48b" + integrity sha512-JOQaURze45qVa8OOFDh+ozj2a/ObSRsVyz6Zd0aiBeej+RSTqrr1hDVpGNbbXYLW26G5ujuc9QIdH+rBHn95nw== + dependencies: + "@algolia/autocomplete-shared" "1.2.2" + +"@algolia/autocomplete-preset-algolia@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.2.2.tgz#da734ef9e42a5f64cdad2dfc81c4e9fbf805d976" + integrity sha512-AZkh+bAMaJDzMZTelFOXJTJqkp5VPGH8W3n0B+Ggce7DdozlMRsDLguKTCQAkZ0dJ1EbBPyFL5ztL/JImB137Q== + dependencies: + "@algolia/autocomplete-shared" "1.2.2" + +"@algolia/autocomplete-shared@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.2.2.tgz#ff25dc308f2a296b2b9b325f1e3b57498eea3e0c" + integrity sha512-mLTl7d2C1xVVazHt/bqh9EE/u2lbp5YOxLDdcjILXmUqOs5HH1D4SuySblXaQG1uf28FhTqMGp35qE5wJQnqAw== + +"@algolia/cache-browser-local-storage@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.12.0.tgz#1f873e4f28a39d25b0a589ebe8f826509458e1fb" + integrity sha512-l+G560B6N1k0rIcOjTO1yCzFUbg2Zy2HCii9s03e13jGgqduVQmk79UUCYszjsJ5GPJpUEKcVEtAIpP7tjsXVA== + dependencies: + "@algolia/cache-common" "4.12.0" + +"@algolia/cache-common@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.12.0.tgz#c1111a4d3e9ba2d52cadb4523152580db0887293" + integrity sha512-2Z8BV+NX7oN7RmmQbLqmW8lfN9aAjOexX1FJjzB0YfKC9ifpi9Jl4nSxlnbU+iLR6QhHo0IfuyQ7wcnucCGCGQ== + +"@algolia/cache-in-memory@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.12.0.tgz#f4bdcbf8a6419f0166cfc7ef5594af871741e29e" + integrity sha512-b6ANkZF6vGAo+sYv6g25W5a0u3o6F549gEAgtTDTVA1aHcdWwe/HG/dTJ7NsnHbuR+A831tIwnNYQjRp3/V/Jw== + dependencies: + "@algolia/cache-common" "4.12.0" + +"@algolia/client-account@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.12.0.tgz#b28445b47e2abf81dc76982d16ba8458f5c99521" + integrity sha512-gzXN75ZydNheNXUN3epS+aLsKnB/PHFVlGUUjXL8WHs4lJP3B5FtHvaA/NCN5DsM3aamhuY5p0ff1XIA+Lbcrw== + dependencies: + "@algolia/client-common" "4.12.0" + "@algolia/client-search" "4.12.0" + "@algolia/transporter" "4.12.0" + +"@algolia/client-analytics@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.12.0.tgz#470f115517256c92a5605ae95762531c7906ec74" + integrity sha512-rO2cZCt00Opk66QBZb7IBGfCq4ZE3EiuGkXssf2Monb5urujy0r8CknK2i7bzaKtPbd2vlvhmLP4CEHQqF6SLQ== + dependencies: + "@algolia/client-common" "4.12.0" + "@algolia/client-search" "4.12.0" + "@algolia/requester-common" "4.12.0" + "@algolia/transporter" "4.12.0" + +"@algolia/client-common@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.12.0.tgz#402395e2cffad89188d76b83615acffb3e45e658" + integrity sha512-fcrFN7FBmxiSyjeu3sF4OnPkC1l7/8oyQ8RMM8CHpVY8cad6/ay35MrfRfgfqdzdFA8LzcBYO7fykuJv0eOqxw== + dependencies: + "@algolia/requester-common" "4.12.0" + "@algolia/transporter" "4.12.0" + +"@algolia/client-personalization@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.12.0.tgz#09c89c1558a91db3bfa60d17f7258ae63861352b" + integrity sha512-wCJfSQEmX6ZOuJBJGjy+sbXiW0iy7tMNAhsVMV9RRaJE4727e5WAqwFWZssD877WQ74+/nF/VyTaB1+wejo33Q== + dependencies: + "@algolia/client-common" "4.12.0" + "@algolia/requester-common" "4.12.0" + "@algolia/transporter" "4.12.0" + +"@algolia/client-search@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.12.0.tgz#ac099ee9f8de85ec204d840bcac734224c7d150c" + integrity sha512-ik6dswcTQtOdZN+8aKntI9X2E6Qpqjtyda/+VANiHThY9GD2PBXuNuuC2HvlF26AbBYp5xaSE/EKxn1DIiIJ4Q== + dependencies: + "@algolia/client-common" "4.12.0" + "@algolia/requester-common" "4.12.0" + "@algolia/transporter" "4.12.0" + +"@algolia/logger-common@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.12.0.tgz#0f9dbe7ace88194b395a2cb958490eb47ac91f8e" + integrity sha512-V//9rzLdJujA3iZ/tPhmKR/m2kjSZrymxOfUiF3024u2/7UyOpH92OOCrHUf023uMGYHRzyhBz5ESfL1oCdh7g== + +"@algolia/logger-console@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.12.0.tgz#a40edeb989bf0d7ff79d989171dad64cd0f01225" + integrity sha512-pHvoGv53KXRIJHLk9uxBwKirwEo12G9+uo0sJLWESThAN3v5M+ycliU1AkUXQN8+9rds2KxfULAb+vfyfBKf8A== + dependencies: + "@algolia/logger-common" "4.12.0" + +"@algolia/requester-browser-xhr@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.12.0.tgz#64e8e4d4f0724e477421454215195400351cfe61" + integrity sha512-rGlHNMM3jIZBwSpz33CVkeXHilzuzHuFXEEW1icP/k3KW7kwBrKFJwBy42RzAJa5BYlLsTCFTS3xkPhYwTQKLg== + dependencies: + "@algolia/requester-common" "4.12.0" + +"@algolia/requester-common@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.12.0.tgz#b4d96f3cbd73206b6042e523d414a34cc005c2e2" + integrity sha512-qgfdc73nXqpVyOMr6CMTx3nXvud9dP6GcMGDqPct+fnxogGcJsp24cY2nMqUrAfgmTJe9Nmy7Lddv0FyHjONMg== + +"@algolia/requester-node-http@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.12.0.tgz#8d8e1b67edbaec8e8e8b8c7c606945b969667267" + integrity sha512-mOTRGf/v/dXshBoZKNhMG00ZGxoUH9QdSpuMKYnuWwIgstN24uj3DQx+Ho3c+uq0TYfq7n2v71uoJWuiW32NMQ== + dependencies: + "@algolia/requester-common" "4.12.0" + +"@algolia/transporter@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.12.0.tgz#e375e10731df95f1be3593b32e86b5c6452cc213" + integrity sha512-MOQVHZ4BcBpf3LtOY/3fqXHAcvI8MahrXDHk9QrBE/iGensQhDiZby5Dn3o2JN/zd9FMnVbdPQ8gnkiMwZiakQ== + dependencies: + "@algolia/cache-common" "4.12.0" + "@algolia/logger-common" "4.12.0" + "@algolia/requester-common" "4.12.0" + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" + integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== + +"@babel/core@^7.12.9": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" + integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.8.tgz#359d44d966b8cd059d543250ce79596f792f2ebe" + integrity sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw== + dependencies: + "@babel/types" "^7.16.8" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== + dependencies: + "@babel/types" "^7.19.0" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-compilation-targets@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" + integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== + dependencies: + "@babel/compat-data" "^7.19.0" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== + dependencies: + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" + integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.16.10", "@babel/parser@^7.16.7", "@babel/parser@^7.7.0": + version "7.16.12" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.12.tgz#9474794f9a650cf5e2f892444227f98e28cdf8b6" + integrity sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A== + +"@babel/parser@^7.18.10", "@babel/parser@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== + +"@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-modules-commonjs@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" + integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-react-display-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.18.6" + +"@babel/plugin-transform-react-jsx@^7.18.6": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.19.0" + +"@babel/plugin-transform-react-pure-annotations@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" + integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/preset-react@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" + integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-react-display-name" "^7.18.6" + "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-pure-annotations" "^7.18.6" + +"@babel/runtime-corejs3@^7.10.2": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.16.8.tgz#ea533d96eda6fdc76b1812248e9fbd0c11d4a1a7" + integrity sha512-3fKhuICS1lMz0plI5ktOE/yEtBRMVxplzRkdn6mJQ197XiY0JnrzYV0+Mxozq3JZ8SBV9Ecurmw1XsGbwOf+Sg== + dependencies: + core-js-pure "^3.20.2" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" + integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/template@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" + integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.0" + "@babel/types" "^7.19.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.7.0": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.10.tgz#448f940defbe95b5a8029975b051f75993e8239f" + integrity sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.16.8" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.16.10" + "@babel/types" "^7.16.8" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.7.0": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.8.tgz#0ba5da91dd71e0a4e7781a30f22770831062e3c1" + integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@code-hike/classer@^0.0.0-aa6efee": + version "0.0.0-e48fa74" + resolved "https://registry.yarnpkg.com/@code-hike/classer/-/classer-0.0.0-e48fa74.tgz#17243ca84d5af303c51e62b378e8db65e01cd3f4" + integrity sha512-CyPYvfl4K5Hp9uyhLhUemul56eiGOF0FNXh5ALzzK9VNhRmRmj1O0mKtLDpoccI8W90r9kQES/nW2FC8jVVieg== + +"@codemirror/autocomplete@^0.19.0": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-0.19.12.tgz#4c9e4487b45e6877807e4f16c1fffd5e7639ae52" + integrity sha512-zUQYo5gMdv7vhxlKoAY/vnNCGzlE9AU7+P649v3ovpQpoFdo3U1Nt01EJqFb4Sfaw6l1U/Elc9Iksd1lDy+MVw== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/state" "^0.19.4" + "@codemirror/text" "^0.19.2" + "@codemirror/tooltip" "^0.19.12" + "@codemirror/view" "^0.19.0" + "@lezer/common" "^0.15.0" + +"@codemirror/closebrackets@^0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@codemirror/closebrackets/-/closebrackets-0.19.0.tgz#69fdcee85779d638a00a42becd9f53a33a26d77f" + integrity sha512-dFWX5OEVYWRNtGaifSbwIAlymnRRjxWMiMbffbAjF7p0zfGHDbdGkiT56q3Xud63h5/tQdSo5dK1iyNTzHz5vg== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/rangeset" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/text" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/commands@^0.19.6": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-0.19.7.tgz#b55ccc7f3c1ad4cb0ec422d50c93568dbb05cc55" + integrity sha512-Mwh064xnuDbFw+9KJAi2Hmg8Va+YqQzgn6e/94/bSJavY3uuIBPr4vJp6pFEa1qPp40qs5/XJ01ty/0G3uLewA== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/matchbrackets" "^0.19.0" + "@codemirror/state" "^0.19.2" + "@codemirror/text" "^0.19.0" + "@codemirror/view" "^0.19.22" + "@lezer/common" "^0.15.0" + +"@codemirror/comment@^0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@codemirror/comment/-/comment-0.19.0.tgz#4f23497924e9346898c2e0123011acc535a0bea6" + integrity sha512-3hqAd0548fxqOBm4khFMcXVIivX8p0bSlbAuZJ6PNoUn/0wXhxkxowPp0FmFzU2+y37Z+ZQF5cRB5EREWPRIiQ== + dependencies: + "@codemirror/state" "^0.19.0" + "@codemirror/text" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/gutter@^0.19.4", "@codemirror/gutter@^0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@codemirror/gutter/-/gutter-0.19.9.tgz#bbb69f4d49570d9c1b3f3df5d134980c516cd42b" + integrity sha512-PFrtmilahin1g6uL27aG5tM/rqR9DZzZYZsIrCXA5Uc2OFTFqx4owuhoU9hqfYxHp5ovfvBwQ+txFzqS4vog6Q== + dependencies: + "@codemirror/rangeset" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.23" + +"@codemirror/highlight@^0.19.6", "@codemirror/highlight@^0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@codemirror/highlight/-/highlight-0.19.7.tgz#91a0c9994c759f5f153861e3aae74ff9e7c7c35b" + integrity sha512-3W32hBCY0pbbv/xidismw+RDMKuIag+fo4kZIbD7WoRj+Ttcaxjf+vP6RttRHXLaaqbWh031lTeON8kMlDhMYw== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/rangeset" "^0.19.0" + "@codemirror/state" "^0.19.3" + "@codemirror/view" "^0.19.0" + "@lezer/common" "^0.15.0" + style-mod "^4.0.0" + +"@codemirror/history@^0.19.0": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@codemirror/history/-/history-0.19.2.tgz#25e3fda755f77ac1223a6ae6e9d7899f5919265e" + integrity sha512-unhP4t3N2smzmHoo/Yio6ueWi+il8gm9VKrvi6wlcdGH5fOfVDNkmjHQ495SiR+EdOG35+3iNebSPYww0vN7ow== + dependencies: + "@codemirror/state" "^0.19.2" + "@codemirror/view" "^0.19.0" + +"@codemirror/lang-css@^0.19.0", "@codemirror/lang-css@^0.19.3": + version "0.19.3" + resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-0.19.3.tgz#7a17adf78c6fcdab4ad5ee4e360631c41e949e4a" + integrity sha512-tyCUJR42/UlfOPLb94/p7dN+IPsYSIzHbAHP2KQHANj0I+Orqp+IyIOS++M8TuCX4zkWh9dvi8s92yy/Tn8Ifg== + dependencies: + "@codemirror/autocomplete" "^0.19.0" + "@codemirror/highlight" "^0.19.6" + "@codemirror/language" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@lezer/css" "^0.15.2" + +"@codemirror/lang-html@^0.19.4": + version "0.19.4" + resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-0.19.4.tgz#e6eec28462f18842a0e108732a214a7416b5e333" + integrity sha512-GpiEikNuCBeFnS+/TJSeanwqaOfNm8Kkp9WpVNEPZCLyW1mAMCuFJu/3xlWYeWc778Hc3vJqGn3bn+cLNubgCA== + dependencies: + "@codemirror/autocomplete" "^0.19.0" + "@codemirror/highlight" "^0.19.6" + "@codemirror/lang-css" "^0.19.0" + "@codemirror/lang-javascript" "^0.19.0" + "@codemirror/language" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@lezer/common" "^0.15.0" + "@lezer/html" "^0.15.0" + +"@codemirror/lang-javascript@^0.19.0", "@codemirror/lang-javascript@^0.19.3": + version "0.19.6" + resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-0.19.6.tgz#bab4ea9ba65189e4bd77c9496275c82e2b6814e7" + integrity sha512-NgkoCIc3hdTNTBRIRuPqfUJ0WB798qEgwAgtjwYy6yoiK5CzbDS2z5CFW17h9RmaAx6t1m64iY2CZ3tC7r15Gw== + dependencies: + "@codemirror/autocomplete" "^0.19.0" + "@codemirror/highlight" "^0.19.7" + "@codemirror/language" "^0.19.0" + "@codemirror/lint" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + "@lezer/javascript" "^0.15.1" + +"@codemirror/language@^0.19.0", "@codemirror/language@^0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-0.19.7.tgz#9eef8e827692d93a701b18db9d46a42be34ecca6" + integrity sha512-pNNUtYWMIMG0lUSKyUXJr8U0rFiCKsKFXbA2Oj17PC+S1FY99hV0z1vcntW67ekAIZw9DMEUQnLsKBuIbAUX7Q== + dependencies: + "@codemirror/state" "^0.19.0" + "@codemirror/text" "^0.19.0" + "@codemirror/view" "^0.19.0" + "@lezer/common" "^0.15.5" + "@lezer/lr" "^0.15.0" + +"@codemirror/lint@^0.19.0": + version "0.19.3" + resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-0.19.3.tgz#84101d0967fea8df114a8f0f79965c22ccd3b3cc" + integrity sha512-+c39s05ybD2NjghxkPFsUbH/qBL0cdzKmtHbzUm0RVspeL2OiP7uHYJ6J5+Qr9RjMIPWzcqSauRqxfmCrctUfg== + dependencies: + "@codemirror/gutter" "^0.19.4" + "@codemirror/panel" "^0.19.0" + "@codemirror/rangeset" "^0.19.1" + "@codemirror/state" "^0.19.4" + "@codemirror/tooltip" "^0.19.5" + "@codemirror/view" "^0.19.0" + crelt "^1.0.5" + +"@codemirror/matchbrackets@^0.19.0", "@codemirror/matchbrackets@^0.19.3": + version "0.19.3" + resolved "https://registry.yarnpkg.com/@codemirror/matchbrackets/-/matchbrackets-0.19.3.tgz#1f430ada6fa21af2205280ff344ef57bb95dd3cb" + integrity sha512-ljkrBxaLgh8jesroUiBa57pdEwqJamxkukXrJpL9LdyFZVJaF+9TldhztRaMsMZO1XnCSSHQ9sg32iuHo7Sc2g== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + "@lezer/common" "^0.15.0" + +"@codemirror/panel@^0.19.0": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@codemirror/panel/-/panel-0.19.1.tgz#bf77d27b962cf16357139e50864d0eb69d634441" + integrity sha512-sYeOCMA3KRYxZYJYn5PNlt9yNsjy3zTNTrbYSfVgjgL9QomIVgOJWPO5hZ2sTN8lufO6lw0vTBsIPL9MSidmBg== + dependencies: + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/rangeset@^0.19.0", "@codemirror/rangeset@^0.19.1", "@codemirror/rangeset@^0.19.5": + version "0.19.6" + resolved "https://registry.yarnpkg.com/@codemirror/rangeset/-/rangeset-0.19.6.tgz#2562850cb4ce7dd30088f4d13a13860b67e7d384" + integrity sha512-wYtgGnW2Jtrh2nj7vpcBoEZib+jfyilrLN6w7YMTzzSRN8xXhYRorOUg4VQIa1JwFcMQrjSCkIdqXsDqOX1cYg== + dependencies: + "@codemirror/state" "^0.19.0" + +"@codemirror/state@^0.19.0", "@codemirror/state@^0.19.2", "@codemirror/state@^0.19.3", "@codemirror/state@^0.19.4", "@codemirror/state@^0.19.6": + version "0.19.6" + resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-0.19.6.tgz#d631f041d39ce41b7891b099fca26cb1fdb9763e" + integrity sha512-sqIQZE9VqwQj7D4c2oz9mfLhlT1ElAzGB5lO1lE33BPyrdNy1cJyCIOecT4cn4VeJOFrnjOeu+IftZ3zqdFETw== + dependencies: + "@codemirror/text" "^0.19.0" + +"@codemirror/text@^0.19.0", "@codemirror/text@^0.19.2": + version "0.19.6" + resolved "https://registry.yarnpkg.com/@codemirror/text/-/text-0.19.6.tgz#9adcbd8137f69b75518eacd30ddb16fd67bbac45" + integrity sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA== + +"@codemirror/tooltip@^0.19.12", "@codemirror/tooltip@^0.19.5": + version "0.19.13" + resolved "https://registry.yarnpkg.com/@codemirror/tooltip/-/tooltip-0.19.13.tgz#4af381aead14d9d7091258bdd6a5de7dc5f56915" + integrity sha512-7vgvjQjwFQ9hPejw2s+w3UR1XAYjQ5M0F9HRwutXkZHP1tBFV7LnNJ3xBD7F9SR9kAh8WgdL3BpUsEwX1aqoQg== + dependencies: + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/view@^0.19.0", "@codemirror/view@^0.19.22", "@codemirror/view@^0.19.23", "@codemirror/view@^0.19.32": + version "0.19.40" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-0.19.40.tgz#1be9cac1725568b7fba2252658a6f255b29339eb" + integrity sha512-0CQV99+/nIKTVVbDs0XjW4Rkp8TobzJBXRaUHF6mOroVjuIBBcolE1eAGVEU5LrCS44C798jiP4r/HhLDNS+rw== + dependencies: + "@codemirror/rangeset" "^0.19.5" + "@codemirror/state" "^0.19.3" + "@codemirror/text" "^0.19.0" + style-mod "^4.0.0" + w3c-keyname "^2.2.4" + +"@codesandbox/sandpack-client@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@codesandbox/sandpack-client/-/sandpack-client-1.12.1.tgz#fa3b66ecb08547c47e1cdf6248f5004bf5be68fb" + integrity sha512-p79Sk41hrp5ojeHbgTU/JWNS3UcRJKewq+oRD6O6g3MzlhUnE+xO94ui79Td4aeU4fq0jGHeLHIHCW04WMK4Yg== + dependencies: + codesandbox-import-utils "^1.2.3" + lodash.isequal "^4.5.0" + +"@codesandbox/sandpack-react@1.15.5": + version "1.15.5" + resolved "https://registry.yarnpkg.com/@codesandbox/sandpack-react/-/sandpack-react-1.15.5.tgz#6ac1b69e64ab66cad69e194df629a826140c3d2c" + integrity sha512-S5iVt9l36QzQfBAbk4mS+VUiVoHdwCFil5cdYHutCAfTAzPQn+vvR7ZYrwFl/LoBChh8nH4EAMN9XTowrzQJYw== + dependencies: + "@code-hike/classer" "^0.0.0-aa6efee" + "@codemirror/closebrackets" "^0.19.0" + "@codemirror/commands" "^0.19.6" + "@codemirror/comment" "^0.19.0" + "@codemirror/gutter" "^0.19.9" + "@codemirror/highlight" "^0.19.6" + "@codemirror/history" "^0.19.0" + "@codemirror/lang-css" "^0.19.3" + "@codemirror/lang-html" "^0.19.4" + "@codemirror/lang-javascript" "^0.19.3" + "@codemirror/language" "^0.19.7" + "@codemirror/matchbrackets" "^0.19.3" + "@codemirror/state" "^0.19.6" + "@codemirror/view" "^0.19.32" + "@codesandbox/sandpack-client" "^1.12.1" + "@react-hook/intersection-observer" "^3.1.1" + "@stitches/core" "^1.2.6" + clean-set "^1.1.2" + codesandbox-import-util-types "^2.2.3" + lodash.isequal "^4.5.0" + lz-string "^1.4.4" + react-devtools-inline "4.4.0" + react-is "^17.0.2" + +"@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + +"@docsearch/css@3.0.0-alpha.41": + version "3.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.41.tgz#c5c8e803541bd157ad86e764c2c1e9f1b5a68592" + integrity sha512-AP1jqcF/9jCrm4s0lcES3QAtHueyipKjd14L/pguk0CZYK7uI7hC0FWodmRmrgK3/HST9jiHa1waUMR6ZYedlQ== + +"@docsearch/react@3.0.0-alpha.41": + version "3.0.0-alpha.41" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0-alpha.41.tgz#07e85a664e85f251ce3d13153abce65a4d5997ab" + integrity sha512-UL0Gdter/NUea04lGuBGH0GzQ2/2q/hBfn7Rjo71rRKbjtfkQCM92leJ9tZ+9j9sFLoyuHb9XMm/B8vCjWwTEg== + dependencies: + "@algolia/autocomplete-core" "1.2.2" + "@algolia/autocomplete-preset-algolia" "1.2.2" + "@docsearch/css" "3.0.0-alpha.41" + algoliasearch "^4.0.0" + +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@headlessui/react@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.0.tgz#7e36e6bbc25a24b02011527ae157a000dda88b85" + integrity sha512-/nDsijOXRwXVLpUBEiYuWguIBSIN3ZbKyah+KPUiD8bdIKtX1U/k+qLYUEr7NCQnSF2e4w1dr8me42ECuG3cvw== + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.15" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" + integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@lezer/common@^0.15.0", "@lezer/common@^0.15.5": + version "0.15.11" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.11.tgz#965b5067036305f12e8a3efc344076850be1d3a8" + integrity sha512-vv0nSdIaVCRcJ8rPuDdsrNVfBOYe/4Szr/LhF929XyDmBndLDuWiCCHooGlGlJfzELyO608AyDhVsuX/ZG36NA== + +"@lezer/css@^0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@lezer/css/-/css-0.15.2.tgz#e96995da67df90bb4b191aaa8a486349cca5d8e7" + integrity sha512-tnMOMZY0Zs6JQeVjqfmREYMV0GnmZR1NitndLWioZMD6mA7VQF/PPKPmJX1f+ZgVZQc5Am0df9mX3aiJnNJlKQ== + dependencies: + "@lezer/lr" "^0.15.0" + +"@lezer/html@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@lezer/html/-/html-0.15.0.tgz#572c9444bc39c1afc0529a70e089abf7254edf5d" + integrity sha512-ErmgP/Vv0AhYJvs/Ekb9oue4IzBHemKLi7K8tJ0jgS+20Y8FGC9foK6knCXtEHqdPaxVGQH9PVp7gecLnzLd9Q== + dependencies: + "@lezer/lr" "^0.15.0" + +"@lezer/javascript@^0.15.1": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-0.15.2.tgz#50b70a02561b047947e050e0619b1aea7131dc5f" + integrity sha512-ytWvdJ1NAc0pfrNipGQs8otJVfjVibpIiFKH0fl99rKSA6cVlyQN/XTj/dEAQCfBfCBPAFdc30cuUe5CGZ0odA== + dependencies: + "@lezer/lr" "^0.15.0" + +"@lezer/lr@^0.15.0": + version "0.15.7" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-0.15.7.tgz#e7e6a8a106c6474b4586a66a3e60ba85f1faf368" + integrity sha512-rmUukgyKSm6xzXO4cK5hkpX3+ZTHF+bHDkEuhofAVUTS3J23YytUxGWsrDwBVvGbhvxW87kheb2mRYHRwKacDQ== + dependencies: + "@lezer/common" "^0.15.0" + +"@mdx-js/mdx@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-2.1.3.tgz#d5821920ebe546b45192f4c7a64dcc68a658f7f9" + integrity sha512-ahbb47HJIJ4xnifaL06tDJiSyLEy1EhFAStO7RZIm3GTa7yGW3NGhZaj+GUCveFgl5oI54pY4BgiLmYm97y+zg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/mdx" "^2.0.0" + estree-util-build-jsx "^2.0.0" + estree-util-is-identifier-name "^2.0.0" + estree-util-to-js "^1.1.0" + estree-walker "^3.0.0" + hast-util-to-estree "^2.0.0" + markdown-extensions "^1.0.0" + periscopic "^3.0.0" + remark-mdx "^2.0.0" + remark-parse "^10.0.0" + remark-rehype "^10.0.0" + unified "^10.0.0" + unist-util-position-from-estree "^1.0.0" + unist-util-stringify-position "^3.0.0" + unist-util-visit "^4.0.0" + vfile "^5.0.0" + +"@next/env@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.2-canary.7.tgz#98cf3ed5e7d6af93965c708799ac02cb46ca3831" + integrity sha512-uk5yDvh8ra8PlIczZBTZKyt5Rf6a6mH2tGB3hwRAXD5hVLd74LzBQza2aYMEcDlRafAknsbL0dnqI3CkFYat9w== + +"@next/eslint-plugin-next@12.0.3": + version "12.0.3" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.0.3.tgz#3945c251d551bacc3712d4a18d6ca56d2938f175" + integrity sha512-P7i+bMypneQcoRN+CX79xssvvIJCaw7Fndzbe7/lB0+LyRbVvGVyMUsFmLLbSxtZq4hvFMJ1p8wML/gsulMZWQ== + dependencies: + glob "7.1.7" + +"@next/swc-android-arm-eabi@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.2-canary.7.tgz#21811767685d8a58756bbfff23a4e61c8da65a15" + integrity sha512-pusM/ylasGBweiwhINtqWCgy5bOjLmIctFD0etpmh9+DqCg09yu58hJ1Dn/UTW8EbB1nkTP1z3dEoMKLh4fV2A== + +"@next/swc-android-arm64@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.3.2-canary.7.tgz#278e709c31ac5aceebd5e7f66efb81dae40ccab6" + integrity sha512-x11T0ocPE9xrnqMeDzUMepN3P8CHIN8iiLgiFkbTbKTbSciuH3juOvKggJO9APZRG5Ch5eePWcCy2gHedAbhnA== + +"@next/swc-darwin-arm64@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.2-canary.7.tgz#7858ecf88a4e252ef6b66c59e38449a79647e362" + integrity sha512-tcO9hDaMfcbiaZp1B+HZcLzGGs36dnmjQ0YXyn6C88HEUoKyxanYleVHtTmWHlgsxxjZdDd/RzOze1ycWs2oXw== + +"@next/swc-darwin-x64@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.2-canary.7.tgz#9225324b6de548ce7fd21c31d4725910c5b7d136" + integrity sha512-WjAyU67zj69nRM2GNAnBLvghq4EHTyDzMO02GjG6yexVhDvkE0OFlvh0BQLI3DIOz+B3RjJRcW3OoHi8XzW9UA== + +"@next/swc-freebsd-x64@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.2-canary.7.tgz#cf3374f2c3ddf370ed27cac84258760b282b6981" + integrity sha512-cQrdPCMhP1Mc+pIt16FlC5BVgcXzLXRlm7qZ7wBRKG6r/IIIn/qNRFgQQcB3iyvfNZo7lURLKcfsxNmMGclldQ== + +"@next/swc-linux-arm-gnueabihf@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.2-canary.7.tgz#b8e82ec743ceadfce25b7e74dfb447913b603c44" + integrity sha512-LEL+dUe10FhQHyXq9Mul5pOJwKDtrAylh9chktWf8eFr14j/YrfPbkLHv1+tCK8brDV3afVJMl0IpoCo75Yfyg== + +"@next/swc-linux-arm64-gnu@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.2-canary.7.tgz#1711f46f004553f3c8ab7546b49e65f88abbbfd6" + integrity sha512-mbDqHk3C76UGIzkOv+G5NslKiSYIXyWQwbkuty0+0wLVJttWjWi2dMN7DFJQPMNvBefU9/vxVJdUnGVGEZfUXw== + +"@next/swc-linux-arm64-musl@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.2-canary.7.tgz#e20b71ce215280bc2cadcf94f9755fdf3ffc0b24" + integrity sha512-gJ3VQHuqb3ABiOKPxfWAJQdO4mp3yNnWIAPN8n52F7Zu38udbHXvcbIylWfQW/Qah+RRf7P7y2txH2kC07QOPA== + +"@next/swc-linux-x64-gnu@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.2-canary.7.tgz#176cf50080f121537877dbdfb7d667be19333a8c" + integrity sha512-/PBLiC+JfMJIzwMCQaSQgnLoIOjdSjTA9zarj2Kk1eCLjH8/VnsfBWtmP7TdbgIRYnZ8QKb4HXSOq94ZQS/fkw== + +"@next/swc-linux-x64-musl@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.2-canary.7.tgz#96b858043d76b87f90b7da43afe13c5942dbceea" + integrity sha512-Bf3goHoUd0SB58sVTMva0ByoLM+aEhm5YJRqsi7SsOAu9EAQwYfWgY2Hx60ah5i1N4ihYK0xjs8kwlfdDVOuow== + +"@next/swc-win32-arm64-msvc@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.2-canary.7.tgz#dd1a36d4afbecc623e71426897cea683a3a75666" + integrity sha512-aPRQ4dY5MuLgHCVdY+/Grgg4oX38pG4S0sT8mpatK3oIdjhj3961cj33QpPAy6dhhCs8m0/eCWYmM9KKlAAUsg== + +"@next/swc-win32-ia32-msvc@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.2-canary.7.tgz#a7455ea57f88345a92c0067e82aa1b7aa5728088" + integrity sha512-hr+TBDICVezyn0HDK4QootalbcuLj9F8qUzZZAw3gHz16rUDpqpnlRjw3RC99AzkKL7qMsdR/+SwnBlBY7ZK7Q== + +"@next/swc-win32-x64-msvc@12.3.2-canary.7": + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.2-canary.7.tgz#4e0d6d567c254e2cf00738f876ddafe5b611ee03" + integrity sha512-1n29b6meb54h/Mw/1xPoJB682nWbtEsUQo7rFJ6G44Nj3fYFXe+XOWQxWu6Sl8yvdBXcZRhRCHuAZGqYtmqorQ== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@polka/url@^1.0.0-next.20": + version "1.0.0-next.21" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" + integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== + +"@react-hook/intersection-observer@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@react-hook/intersection-observer/-/intersection-observer-3.1.1.tgz#6b8fdb80d133c9c28bc8318368ecb3a1f8befc50" + integrity sha512-OTDx8/wFaRvzFtKl1dEUEXSOqK2zVJHporiTTdC2xO++0e9FEx9wIrPis5q3lqtXeZH9zYGLbk+aB75qNFbbuw== + dependencies: + "@react-hook/passive-layout-effect" "^1.2.0" + intersection-observer "^0.10.0" + +"@react-hook/passive-layout-effect@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz#c06dac2d011f36d61259aa1c6df4f0d5e28bc55e" + integrity sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg== + +"@rushstack/eslint-patch@^1.0.6": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" + integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A== + +"@stitches/core@^1.2.6": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@stitches/core/-/core-1.2.8.tgz#dce3b8fdc764fbc6dbea30c83b73bfb52cf96173" + integrity sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg== + +"@swc/helpers@0.4.11": + version "0.4.11" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.11.tgz#db23a376761b3d31c26502122f349a21b592c8de" + integrity sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw== + dependencies: + tslib "^2.4.0" + +"@types/acorn@^4.0.0": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.6.tgz#d61ca5480300ac41a7d973dd5b84d0a591154a22" + integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ== + dependencies: + "@types/estree" "*" + +"@types/body-scroll-lock@^2.6.1": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/body-scroll-lock/-/body-scroll-lock-2.6.2.tgz#ce56d17e1bf8383c08a074733c4e9e536a59ae61" + integrity sha512-PhoQPbwPYspXqf7lkwtF7aJzAwL88t+9E/e0b2X84tlHpU8ZuS9UNnLtkT0XhyZJYHpET5qRfIdZ0HBIxuc7HQ== + +"@types/classnames@^2.2.10": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd" + integrity sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A== + dependencies: + classnames "*" + +"@types/debounce@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.1.tgz#79b65710bc8b6d44094d286aecf38e44f9627852" + integrity sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA== + +"@types/debug@^4.0.0": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" + integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== + dependencies: + "@types/ms" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.0.tgz#7bfc979ab9f692b492017df42520f7f765e98df1" + integrity sha512-3qvGd0z8F2ENTGr/GG1yViqfiKmRfrXVx5sJyHGFu3z7m5g5utCQtGp/g29JnjflhtQJBv1WDQukHiT58xPcYQ== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + +"@types/github-slugger@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/github-slugger/-/github-slugger-1.3.0.tgz#16ab393b30d8ae2a111ac748a015ac05a1fc5524" + integrity sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g== + +"@types/hast@^2.0.0", "@types/hast@^2.3.2": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" + integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== + dependencies: + "@types/unist" "*" + +"@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/mdast@^3.0.0": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" + integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== + dependencies: + "@types/unist" "*" + +"@types/mdurl@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + +"@types/mdx-js__react@^1.5.2": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@types/mdx-js__react/-/mdx-js__react-1.5.5.tgz#fa6daa1a28336d77b6cf071aacc7e497600de9ee" + integrity sha512-k8pnaP6JXVlQh18HgL5X6sYFNC/qZnzO7R2+HsmwrwUd+JnnsU0d9lyyT0RQrHg1anxDU36S98TI/fsGtmYqqg== + dependencies: + "@types/react" "*" + +"@types/mdx@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.2.tgz#64be19baddba4323ae7893e077e98759316fe279" + integrity sha512-mJGfgj4aWpiKb8C0nnJJchs1sHBHn0HugkVfqqyQi7Wn6mBRksLeQsPOFvih/Pu8L1vlDzfe/LidhVHBeUk3aQ== + +"@types/ms@*": + version "0.7.31" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + +"@types/node@^14.6.4": + version "14.18.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.9.tgz#0e5944eefe2b287391279a19b407aa98bd14436d" + integrity sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/parse-numeric-range@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/parse-numeric-range/-/parse-numeric-range-0.0.1.tgz#1a807487565a753f486cb3ee4b2145937d49759d" + integrity sha512-nI3rPGKk8BxedokP2VilnW5JyZHYNjGCUDsAZ2JQgISgDflHNUO0wXMfGYP8CkihrKYDm5tilD52XfGhO/ZFCA== + +"@types/prop-types@*": + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + +"@types/react-dom@^18.0.5": + version "18.0.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a" + integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "17.0.38" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd" + integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/react@^18.0.9": + version "18.0.9" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878" + integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + +"@typescript-eslint/eslint-plugin@^5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz#6df092a20e0f9ec748b27f293a12cb39d0c1fe4d" + integrity sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw== + dependencies: + "@typescript-eslint/scope-manager" "5.36.2" + "@typescript-eslint/type-utils" "5.36.2" + "@typescript-eslint/utils" "5.36.2" + debug "^4.3.4" + functional-red-black-tree "^1.0.1" + ignore "^5.2.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^4.20.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" + integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== + dependencies: + "@typescript-eslint/scope-manager" "4.33.0" + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/typescript-estree" "4.33.0" + debug "^4.3.1" + +"@typescript-eslint/parser@^5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.2.tgz#3ddf323d3ac85a25295a55fcb9c7a49ab4680ddd" + integrity sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA== + dependencies: + "@typescript-eslint/scope-manager" "5.36.2" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/typescript-estree" "5.36.2" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" + integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + +"@typescript-eslint/scope-manager@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz#a75eb588a3879ae659514780831370642505d1cd" + integrity sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw== + dependencies: + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" + +"@typescript-eslint/type-utils@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz#752373f4babf05e993adf2cd543a763632826391" + integrity sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw== + dependencies: + "@typescript-eslint/typescript-estree" "5.36.2" + "@typescript-eslint/utils" "5.36.2" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" + integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== + +"@typescript-eslint/types@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.2.tgz#a5066e500ebcfcee36694186ccc57b955c05faf9" + integrity sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ== + +"@typescript-eslint/typescript-estree@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" + integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/typescript-estree@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz#0c93418b36c53ba0bc34c61fe9405c4d1d8fe560" + integrity sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w== + dependencies: + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.36.2.tgz#b01a76f0ab244404c7aefc340c5015d5ce6da74c" + integrity sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.36.2" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/typescript-estree" "5.36.2" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" + integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== + dependencies: + "@typescript-eslint/types" "4.33.0" + eslint-visitor-keys "^2.0.0" + +"@typescript-eslint/visitor-keys@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz#2f8f78da0a3bad3320d2ac24965791ac39dace5a" + integrity sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A== + dependencies: + "@typescript-eslint/types" "5.36.2" + eslint-visitor-keys "^3.3.0" + +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + +absolute-path@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7" + integrity sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA== + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx@^5.0.0, acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn-walk@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^7.0.0, acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.0.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + +acorn@^8.0.4: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.9.0.tgz#738019146638824dea25edcf299dcba1b0e7eb18" + integrity sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +algoliasearch@^4.0.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.12.0.tgz#30f2619b6e3a5b79b6aa0f18ab66fbce88240aba" + integrity sha512-fZOMMm+F3Bi5M/MoFIz7hiuyCitJza0Hu+r8Wzz4LIQClC6YGMRq7kT6NNU1fSSoFDSeJIwMfedbbi5G9dJoVQ== + dependencies: + "@algolia/cache-browser-local-storage" "4.12.0" + "@algolia/cache-common" "4.12.0" + "@algolia/cache-in-memory" "4.12.0" + "@algolia/client-account" "4.12.0" + "@algolia/client-analytics" "4.12.0" + "@algolia/client-common" "4.12.0" + "@algolia/client-personalization" "4.12.0" + "@algolia/client-search" "4.12.0" + "@algolia/logger-common" "4.12.0" + "@algolia/logger-console" "4.12.0" + "@algolia/requester-browser-xhr" "4.12.0" + "@algolia/requester-common" "4.12.0" + "@algolia/requester-node-http" "4.12.0" + "@algolia/transporter" "4.12.0" + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" + integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-includes@^3.1.3, array-includes@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" + integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array-iterate@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.4.tgz#add1522e9dd9749bb41152d08b845bd08d6af8b7" + integrity sha512-sNRaPGh9nnmdC8Zf+pT3UqP8rnWj5Hf9wiFGsX3wUQ2yVSIhO2ShFwCoceIPpB41QF6i2OEmrHmCo36xronCVA== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.flat@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" + integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +array.prototype.flatmap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446" + integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +astring@^1.8.0: + version "1.8.3" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.8.3.tgz#1a0ae738c7cc558f8e5ddc8e3120636f5cebcb85" + integrity sha512-sRpyiNrx2dEYIMmUXprS8nlpRg2Drs8m9ElX9vVEXaCB4XEAJhKfs7IcX0IwShjuOAjLR6wzIrgoptz1n19i1A== + +asyncro@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/asyncro/-/asyncro-3.0.0.tgz#3c7a732e263bc4a42499042f48d7d858e9c0134e" + integrity sha512-nEnWYfrBmA3taTiuiOoZYmgJ/CNrSoQLeLs29SeLcPu60yaw/mHDBHV0iOZ051fTvsTHxpCY+gXibqT9wbQYfg== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +autoprefixer@^10.4.2: + version "10.4.2" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b" + integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ== + dependencies: + browserslist "^4.19.1" + caniuse-lite "^1.0.30001297" + fraction.js "^4.1.2" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +autoprefixer@^9.6.1: + version "9.8.8" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" + integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + picocolors "^0.2.1" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + +axe-core@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.5.tgz#78d6911ba317a8262bfee292aeafcc1e04b49cc5" + integrity sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA== + +axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + +babel-eslint@10.x: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +binaryextensions@2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.3.0.tgz#1d269cbf7e6243ea886aa41453c3651ccbe13c22" + integrity sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg== + +body-parser@1.20.0, body-parser@^1.19.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" + integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.10.3" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +body-scroll-lock@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz#c1392d9217ed2c3e237fee1e910f6cdd80b7aaec" + integrity sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.12.0, browserslist@^4.19.1, browserslist@^4.6.4: + version "4.19.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + dependencies: + caniuse-lite "^1.0.30001286" + electron-to-chromium "^1.4.17" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + +browserslist@^4.20.2: + version "4.21.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" + integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== + dependencies: + caniuse-lite "^1.0.30001370" + electron-to-chromium "^1.4.202" + node-releases "^2.0.6" + update-browserslist-db "^1.0.5" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297: + version "1.0.30001301" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz#ebc9086026534cab0dab99425d9c3b4425e5f450" + integrity sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA== + +caniuse-lite@^1.0.30001370: + version "1.0.30001390" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz#158a43011e7068ef7fc73590e9fd91a7cece5e7f" + integrity sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g== + +caniuse-lite@^1.0.30001406: + version "1.0.30001410" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001410.tgz#b5a86366fbbf439d75dd3db1d21137a73e829f44" + integrity sha512-QoblBnuE+rG0lc3Ur9ltP5q47lbguipa/ncNMyyGuqPk44FxbScWAeEO+k5fSQ8WekdAK4mWqNs1rADDAiN5xQ== + +ccount@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" + integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +character-entities-html4@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" + integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + +chokidar@^3.4.0, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + +classnames@*, classnames@^2.2.6: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + +clean-set@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/clean-set/-/clean-set-1.1.2.tgz#76d8bf238c3e27827bfa73073ecdfdc767187070" + integrity sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + +codesandbox-import-util-types@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/codesandbox-import-util-types/-/codesandbox-import-util-types-1.3.7.tgz#7a6097e248a75424d13b06b74368cd76bd2b3e10" + integrity sha512-8oP3emA0jyEuVOM2FBTpo/AF4C9vxHn14saVWZf2CQ/QhMtonBlNPE98ElrHkW+PFNXiO7Ad52Qr73b03n8qlA== + +codesandbox-import-util-types@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/codesandbox-import-util-types/-/codesandbox-import-util-types-2.2.3.tgz#b354b2f732ad130e119ebd9ead3bda3be5981a54" + integrity sha512-Qj00p60oNExthP2oR3vvXmUGjukij+rxJGuiaKM6tyUmSyimdZsqHI/TUvFFClAffk9s7hxGnQgWQ8KCce27qQ== + +codesandbox-import-utils@^1.2.3: + version "1.3.8" + resolved "https://registry.yarnpkg.com/codesandbox-import-utils/-/codesandbox-import-utils-1.3.8.tgz#5576786439c5f37ebd3fee5751e06027a1edef84" + integrity sha512-S12zO49QEkldoYLGh5KbkHRLOacg5BCNTue2vlyZXSpuK3oQdArwC/G1hCLKryV460bW3Ecn5xdkpfkUcFeOwQ== + dependencies: + codesandbox-import-util-types "^1.3.7" + istextorbinary "2.2.1" + lz-string "^1.4.4" + +collapse-white-space@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.1.4, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + +comma-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98" + integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg== + +commander@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +confusing-browser-globals@^1.0.9: + version "1.0.11" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +core-js-pure@^3.20.2: + version "3.20.3" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.20.3.tgz#6cc4f36da06c61d95254efc54024fe4797fd5d02" + integrity sha512-Q2H6tQ5MtPtcC7f3HxJ48i4Q7T9ybPKgvWyuH7JXIoNa2pm0KuBnycsET/qw1SLLZYfbsbrZQNMeIOClb+6WIA== + +cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +crelt@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94" + integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA== + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + +css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" + +css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" + +cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^3.0.2: + version "3.0.10" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" + integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +damerau-levenshtein@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +date-fns@^2.16.1: + version "2.28.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" + integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== + +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + +debug@2.6.9, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.0, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +decode-named-character-reference@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" + integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== + dependencies: + character-entities "^2.0.0" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +diff@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" + integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +editions@^1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" + integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.17: + version "1.4.51" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz#a432f5a5d983ace79278a33057300cf949627e63" + integrity sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ== + +electron-to-chromium@^1.4.202: + version "1.4.241" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.241.tgz#5aa03ab94db590d8269f4518157c24b1efad34d6" + integrity sha512-e7Wsh4ilaioBZ5bMm6+F4V5c11dh56/5Jwz7Hl5Tu1J7cnB+Pqx5qIF2iC7HPpfyQMqGSvvLP5bBAIDd2gAtGw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.19.0, es-abstract@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3, es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + +eslint-config-next@12.0.3: + version "12.0.3" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.0.3.tgz#a85ad423997f098b41b61c279472e0642e200a9e" + integrity sha512-q+mX6jhk3HrCo39G18MLhiC6f8zJnTA00f30RSuVUWsv45SQUm6r62oXVqrbAgMEybe0yx/GDRvfA6LvSolw6Q== + dependencies: + "@next/eslint-plugin-next" "12.0.3" + "@rushstack/eslint-patch" "^1.0.6" + "@typescript-eslint/parser" "^4.20.0" + eslint-import-resolver-node "^0.3.4" + eslint-import-resolver-typescript "^2.4.0" + eslint-plugin-import "^2.22.1" + eslint-plugin-jsx-a11y "^6.4.1" + eslint-plugin-react "^7.23.1" + eslint-plugin-react-hooks "^4.2.0" + +eslint-config-react-app@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df" + integrity sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ== + dependencies: + confusing-browser-globals "^1.0.9" + +eslint-import-resolver-node@^0.3.4, eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-import-resolver-typescript@^2.4.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a" + integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ== + dependencies: + debug "^4.3.1" + glob "^7.1.7" + is-glob "^4.0.1" + resolve "^1.20.0" + tsconfig-paths "^3.9.0" + +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-flowtype@4.x: + version "4.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.7.0.tgz#903a6ea3eb5cbf4c7ba7fa73cc43fc39ab7e4a70" + integrity sha512-M+hxhSCk5QBEValO5/UqrS4UunT+MgplIJK5wA1sCtXjzBcZkpTGRwxmLHhGpbHcrmQecgt6ZL/KDdXWqGB7VA== + dependencies: + lodash "^4.17.15" + +eslint-plugin-import@2.x, eslint-plugin-import@^2.22.1: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.2" + has "^1.0.3" + is-core-module "^2.8.0" + is-glob "^4.0.3" + minimatch "^3.0.4" + object.values "^1.1.5" + resolve "^1.20.0" + tsconfig-paths "^3.12.0" + +eslint-plugin-jsx-a11y@6.x, eslint-plugin-jsx-a11y@^6.4.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" + integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== + dependencies: + "@babel/runtime" "^7.16.3" + aria-query "^4.2.2" + array-includes "^3.1.4" + ast-types-flow "^0.0.7" + axe-core "^4.3.5" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.7" + emoji-regex "^9.2.2" + has "^1.0.3" + jsx-ast-utils "^3.2.1" + language-tags "^1.0.5" + minimatch "^3.0.4" + +eslint-plugin-react-hooks@^0.0.0-experimental-fabef7a6b-20221215: + version "0.0.0-experimental-fabef7a6b-20221215" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-0.0.0-experimental-fabef7a6b-20221215.tgz#ceb8c59f1c363cc2f733b0be36661a8a722a89a1" + integrity sha512-y1lJAS4gWXyP6kXl2jA9ZJdFFfcMwNjMEZEEXn9LHOWEhnAgKgcqZ/NhNWAphiJLYOZ33kne1hbhDlGCcrdx5g== + +eslint-plugin-react-hooks@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" + integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA== + +eslint-plugin-react@7.x, eslint-plugin-react@^7.23.1: + version "7.28.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz#8f3ff450677571a659ce76efc6d80b6a525adbdf" + integrity sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw== + dependencies: + array-includes "^3.1.4" + array.prototype.flatmap "^1.2.5" + doctrine "^2.1.0" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.0.4" + object.entries "^1.1.5" + object.fromentries "^2.0.5" + object.hasown "^1.1.0" + object.values "^1.1.5" + prop-types "^15.7.2" + resolve "^2.0.0-next.3" + semver "^6.3.0" + string.prototype.matchall "^4.0.6" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@7.x: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.1.2" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-util-attach-comments@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-2.1.0.tgz#47d69900588bcbc6bf58c3798803ec5f1f3008de" + integrity sha512-rJz6I4L0GaXYtHpoMScgDIwM0/Vwbu5shbMeER596rB2D1EWF6+Gj0e0UKzJPZrpoOc87+Q2kgVFHfjAymIqmw== + dependencies: + "@types/estree" "^1.0.0" + +estree-util-build-jsx@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-2.2.0.tgz#d4307bbeee28c14eb4d63b75c9aad28fa61d84f5" + integrity sha512-apsfRxF9uLrqosApvHVtYZjISPvTJ+lBiIydpC+9wE6cF6ssbhnjyQLqaIjgzGxvC2Hbmec1M7g91PoBayYoQQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + estree-util-is-identifier-name "^2.0.0" + estree-walker "^3.0.0" + +estree-util-is-identifier-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.0.1.tgz#cf07867f42705892718d9d89eb2d85eaa8f0fcb5" + integrity sha512-rxZj1GkQhY4x1j/CSnybK9cGuMFQYFPLq0iNyopqf14aOVLFtMv7Esika+ObJWPWiOHuMOAHz3YkWoLYYRnzWQ== + +estree-util-to-js@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-1.1.0.tgz#3bd9bb86354063537cc3d81259be2f0d4c3af39f" + integrity sha512-490lbfCcpLk+ofK6HCgqDfYs4KAfq6QVvDw3+Bm1YoKRgiOjKiKYGAVQE1uwh7zVxBgWhqp4FDtp5SqunpUk1A== + dependencies: + "@types/estree-jsx" "^1.0.0" + astring "^1.8.0" + source-map "^0.7.0" + +estree-util-visit@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-1.2.0.tgz#aa0311a9c2f2aa56e9ae5e8b9d87eac14e4ec8f8" + integrity sha512-wdsoqhWueuJKsh5hqLw3j8lwFqNStm92VcwtAOAny8g/KS/l5Y8RISjR4k5W6skCj3Nirag/WUCMS0Nfy3sgsg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/unist" "^2.0.0" + +estree-walker@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.1.tgz#c2a9fb4a30232f5039b7c030b37ead691932debd" + integrity sha512-woY0RUD87WzMBUiZLx8NsYr23N5BKsOMZHhu2hoNRVh6NXGfoiT1KOL8G3UHlJAnEDGmfa5ubNA/AacfG+Kb0g== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +express@^4.17.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.0" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.10.3" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.11, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fault@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c" + integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ== + dependencies: + format "^0.2.0" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8" + integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +ga-lite@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/ga-lite/-/ga-lite-2.1.4.tgz#f0c1fd3234bc44d8b15db640a93b34dbce84d43a" + integrity sha512-OJqPbutD8SfgF8SebyLh0uHdpQH32d5TuviXao5yPjyO2sGW3Gm+ooDmOhvXITgPepwwKxzv7ZMVKk2/hg/+lg== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +github-slugger@^1.0.0, github-slugger@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.4.0.tgz#206eb96cdb22ee56fdc53a28d5a302338463444e" + integrity sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.3, glob@^7.1.7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.6.0, globals@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" + integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== + dependencies: + type-fest "^0.20.2" + +globby@^11.0.1, globby@^11.0.3, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + +gray-matter@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hast-util-is-element@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425" + integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ== + +hast-util-sanitize@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-3.0.2.tgz#b0b783220af528ba8fe6999f092d138908678520" + integrity sha512-+2I0x2ZCAyiZOO/sb4yNLFmdwPBnyJ4PBkVTUMKMqBwYNA+lXSgOmoRXlJFazoyid9QPogRRKgKhVEodv181sA== + dependencies: + xtend "^4.0.0" + +hast-util-to-estree@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-2.1.0.tgz#aeac70aad0102ae309570907b3f56a08231d5323" + integrity sha512-Vwch1etMRmm89xGgz+voWXvVHba2iiMdGMKmaMfYt35rbVtFDq8JNwwAIvi8zHMkO6Gvqo9oTMwJTmzVRfXh4g== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^2.0.0" + "@types/unist" "^2.0.0" + comma-separated-tokens "^2.0.0" + estree-util-attach-comments "^2.0.0" + estree-util-is-identifier-name "^2.0.0" + hast-util-whitespace "^2.0.0" + mdast-util-mdx-expression "^1.0.0" + mdast-util-mdxjs-esm "^1.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^0.3.0" + unist-util-position "^4.0.0" + zwitch "^2.0.0" + +hast-util-to-html@^7.0.0: + version "7.1.3" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz#9f339ca9bea71246e565fc79ff7dbfe98bb50f5e" + integrity sha512-yk2+1p3EJTEE9ZEUkgHsUSVhIpCsL/bvT8E5GzmWc+N1Po5gBw+0F8bo7dpxXR0nu0bQVxVZGX2lBGF21CmeDw== + dependencies: + ccount "^1.0.0" + comma-separated-tokens "^1.0.0" + hast-util-is-element "^1.0.0" + hast-util-whitespace "^1.0.0" + html-void-elements "^1.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + stringify-entities "^3.0.1" + unist-util-is "^4.0.0" + xtend "^4.0.0" + +hast-util-whitespace@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" + integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== + +hast-util-whitespace@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c" + integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg== + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +html-void-elements@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" + integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +husky@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +intersection-observer@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.10.0.tgz#4d11d63c1ff67e21e62987be24d55218da1a1a69" + integrity sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-absolute-url@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumeric@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" + integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ= + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + +is-core-module@^2.2.0, is-core-module@^2.8.0, is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-negative-zero@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-obj@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.0.0.tgz#06c0999fd7574edf5a906ba5644ad0feb3a84d22" + integrity sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw== + +is-reference@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.0.tgz#b1380c03d96ddf7089709781e3208fceb0c92cd6" + integrity sha512-Eo1W3wUoHWoCoVM4GVl/a+K0IgiqE5aIo4kJABFyMum1ZORlPkC+UC357sSQUL5w5QCE5kCC9upl75b7+7CY/Q== + dependencies: + "@types/estree" "*" + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-url@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-weakref@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-whitespace-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" + integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== + +is-word-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" + integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== + +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +istextorbinary@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53" + integrity sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw== + dependencies: + binaryextensions "2" + editions "^1.3.3" + textextensions "2" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" + integrity sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA== + dependencies: + array-includes "^3.1.3" + object.assign "^4.1.2" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + +kleur@^4.0.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" + integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== + +language-subtag-registry@~0.3.2: + version "0.3.21" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@2.0.4, lilconfig@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" + integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lint-staged@>=10: + version "12.3.1" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.1.tgz#d475b0c0d0a12d91dde58a429ac6268dea485f06" + integrity sha512-Ocht/eT+4/siWOZDJpNUKcKX2UeWW/pDbohJ4gRsrafAjBi79JK8kiNVk2ciIVNKdw0Q4ABptl2nr6uQAlRImw== + dependencies: + cli-truncate "^3.1.0" + colorette "^2.0.16" + commander "^8.3.0" + debug "^4.3.3" + execa "^5.1.1" + lilconfig "2.0.4" + listr2 "^4.0.1" + micromatch "^4.0.4" + normalize-path "^3.0.0" + object-inspect "^1.12.0" + string-argv "^0.3.1" + supports-color "^9.2.1" + yaml "^1.10.2" + +listr2@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.1.tgz#e050c1fd390276e191f582603d6e3531cd6fd2b3" + integrity sha512-D65Nl+zyYHL2jQBGmxtH/pU8koPZo5C8iCNE8EoB04RwPgQG1wuaKwVbeZv9LJpiH4Nxs0FCp+nNcG8OqpniiA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.2" + through "^2.3.8" + wrap-ansi "^7.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + +lodash@^4.17.15, lodash@^4.17.20: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +longest-streak@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" + integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== + +longest-streak@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.0.1.tgz#c97315b7afa0e7d9525db9a5a2953651432bdc5d" + integrity sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + +markdown-escapes@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" + integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== + +markdown-extensions@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3" + integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q== + +markdown-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" + integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== + dependencies: + repeat-string "^1.0.0" + +markdown-table@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c" + integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA== + +mdast-util-compact@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490" + integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA== + dependencies: + unist-util-visit "^2.0.0" + +mdast-util-definitions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-3.0.1.tgz#06af6c49865fc63d6d7d30125569e2f7ae3d0a86" + integrity sha512-BAv2iUm/e6IK/b2/t+Fx69EL/AGcq/IG2S+HxHjDJGfLJtd6i9SZUS76aC9cig+IEucsqxKTR0ot3m933R3iuA== + dependencies: + unist-util-visit "^2.0.0" + +mdast-util-definitions@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz#2c1d684b28e53f84938bb06317944bee8efa79db" + integrity sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + unist-util-visit "^4.0.0" + +mdast-util-find-and-replace@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz#249901ef43c5f41d6e8a8d446b3b63b17e592d7c" + integrity sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw== + dependencies: + escape-string-regexp "^5.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.0.0" + +mdast-util-from-markdown@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz#84df2924ccc6c995dec1e2368b2b208ad0a76268" + integrity sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + decode-named-character-reference "^1.0.0" + mdast-util-to-string "^3.1.0" + micromark "^3.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-decode-string "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-stringify-position "^3.0.0" + uvu "^0.5.0" + +mdast-util-frontmatter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-1.0.0.tgz#ef12469379782e4a0fd995fed60cc3b871e6c819" + integrity sha512-7itKvp0arEVNpCktOET/eLFAYaZ+0cNjVtFtIPxgQ5tV+3i+D4SDDTjTzPWl44LT59PC+xdx+glNTawBdF98Mw== + dependencies: + micromark-extension-frontmatter "^1.0.0" + +mdast-util-gfm-autolink-literal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz#4032dcbaddaef7d4f2f3768ed830475bb22d3970" + integrity sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg== + dependencies: + "@types/mdast" "^3.0.0" + ccount "^2.0.0" + mdast-util-find-and-replace "^2.0.0" + micromark-util-character "^1.0.0" + +mdast-util-gfm-footnote@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz#11d2d40a1a673a399c459e467fa85e00223191fe" + integrity sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-markdown "^1.3.0" + micromark-util-normalize-identifier "^1.0.0" + +mdast-util-gfm-strikethrough@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.1.tgz#a4a74c36864ec6a6e3bbd31e1977f29beb475789" + integrity sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-markdown "^1.3.0" + +mdast-util-gfm-table@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz#184e900979fe790745fc3dabf77a4114595fcd7f" + integrity sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag== + dependencies: + "@types/mdast" "^3.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^1.0.0" + mdast-util-to-markdown "^1.3.0" + +mdast-util-gfm-task-list-item@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz#6f35f09c6e2bcbe88af62fdea02ac199cc802c5c" + integrity sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-markdown "^1.3.0" + +mdast-util-gfm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz#16fcf70110ae689a06d77e8f4e346223b64a0ea6" + integrity sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ== + dependencies: + mdast-util-from-markdown "^1.0.0" + mdast-util-gfm-autolink-literal "^1.0.0" + mdast-util-gfm-footnote "^1.0.0" + mdast-util-gfm-strikethrough "^1.0.0" + mdast-util-gfm-table "^1.0.0" + mdast-util-gfm-task-list-item "^1.0.0" + mdast-util-to-markdown "^1.0.0" + +mdast-util-mdx-expression@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.3.0.tgz#fed063cc6320da1005c8e50338bb374d6dac69ba" + integrity sha512-9kTO13HaL/ChfzVCIEfDRdp1m5hsvsm6+R8yr67mH+KS2ikzZ0ISGLPTbTswOFpLLlgVHO9id3cul4ajutCvCA== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + mdast-util-to-markdown "^1.0.0" + +mdast-util-mdx-jsx@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-2.1.0.tgz#029f5a9c38485dbb5cf482059557ee7d788f1947" + integrity sha512-KzgzfWMhdteDkrY4mQtyvTU5bc/W4ppxhe9SzelO6QUUiwLAM+Et2Dnjjprik74a336kHdo0zKm7Tp+n6FFeRg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + ccount "^2.0.0" + mdast-util-to-markdown "^1.3.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-remove-position "^4.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + +mdast-util-mdx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-2.0.0.tgz#dd4f6c993cf27da32725e50a04874f595b7b63fb" + integrity sha512-M09lW0CcBT1VrJUaF/PYxemxxHa7SLDHdSn94Q9FhxjCQfuW7nMAWKWimTmA3OyDMSTH981NN1csW1X+HPSluw== + dependencies: + mdast-util-mdx-expression "^1.0.0" + mdast-util-mdx-jsx "^2.0.0" + mdast-util-mdxjs-esm "^1.0.0" + +mdast-util-mdxjs-esm@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-1.3.0.tgz#137345ef827169aeeeb6069277cd3e090830ce9a" + integrity sha512-7N5ihsOkAEGjFotIX9p/YPdl4TqUoMxL4ajNz7PbT89BqsdWJuBC9rvgt6wpbwTZqWWR0jKWqQbwsOWDBUZv4g== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + mdast-util-to-markdown "^1.0.0" + +mdast-util-to-hast@^12.1.0: + version "12.2.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.2.2.tgz#2bd8cf985a67c90c181eadcfdd8d31b8798ed9a1" + integrity sha512-lVkUttV9wqmdXFtEBXKcepvU/zfwbhjbkM5rxrquLW55dS1DfOrnAXCk5mg1be1sfY/WfMmayGy1NsbK1GLCYQ== + dependencies: + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + "@types/mdurl" "^1.0.0" + mdast-util-definitions "^5.0.0" + mdurl "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + trim-lines "^3.0.0" + unist-builder "^3.0.0" + unist-util-generated "^2.0.0" + unist-util-position "^4.0.0" + unist-util-visit "^4.0.0" + +mdast-util-to-hast@^9.0.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-9.1.2.tgz#10fa5ed9d45bf3755891e5801d0f32e2584a9423" + integrity sha512-OpkFLBC2VnNAb2FNKcKWu9FMbJhQKog+FCT8nuKmQNIKXyT1n3SIskE7uWDep6x+cA20QXlK5AETHQtYmQmxtQ== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + mdast-util-definitions "^3.0.0" + mdurl "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + +mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz#38b6cdc8dc417de642a469c4fc2abdf8c931bd1e" + integrity sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + longest-streak "^3.0.0" + mdast-util-to-string "^3.0.0" + micromark-util-decode-string "^1.0.0" + unist-util-visit "^4.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" + integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== + +mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz#56c506d065fbf769515235e577b5a261552d56e9" + integrity sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA== + +mdurl@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +metro-cache@0.72.2: + version "0.72.2" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.72.2.tgz#114e62dad3539c41cf5625045b9a6e5181499f20" + integrity sha512-0Yw3J32eYTp7x7bAAg+a9ScBG/mpib6Wq4WPSYvhoNilPFHzh7knLDMil3WGVCQlI1r+5xtpw/FDhNVKuypQqg== + dependencies: + metro-core "0.72.2" + rimraf "^2.5.4" + +metro-core@0.72.2: + version "0.72.2" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.72.2.tgz#ca975cfebce5c89f51dca905777bc3877008ae69" + integrity sha512-OXNH8UbKIhvpyHGJrdQYnPUmyPHSuVY4OO6pQxODdTW+uiO68PPPgIIVN67vlCAirZolxRFpma70N7m0sGCZyg== + dependencies: + lodash.throttle "^4.1.1" + metro-resolver "0.72.2" + +metro-resolver@0.72.2: + version "0.72.2" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.72.2.tgz#332ecd646d683a47923fc403e3df37a7cf96da3b" + integrity sha512-5KTWolUgA6ivLkg3DmFS2WltphBPQW7GT7An+6Izk/NU+y/6crmsoaLmNxjpZo4Fv+i/FxDSXqpbpQ6KrRWvlQ== + dependencies: + absolute-path "^0.0.0" + +micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz#edff4c72e5993d93724a3c206970f5a15b0585ad" + integrity sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-factory-destination "^1.0.0" + micromark-factory-label "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-factory-title "^1.0.0" + micromark-factory-whitespace "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-classify-character "^1.0.0" + micromark-util-html-tag-name "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + uvu "^0.5.0" + +micromark-extension-frontmatter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-1.0.0.tgz#612498e6dad87c132c95e25f0918e7cc0cd535f6" + integrity sha512-EXjmRnupoX6yYuUJSQhrQ9ggK0iQtQlpi6xeJzVD5xscyAI+giqco5fdymayZhJMbIFecjnE2yz85S9NzIgQpg== + dependencies: + fault "^2.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-extension-gfm-autolink-literal@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz#dc589f9c37eaff31a175bab49f12290edcf96058" + integrity sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-footnote@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz#cbfd8873b983e820c494498c6dac0105920818d5" + integrity sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg== + dependencies: + micromark-core-commonmark "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-strikethrough@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz#162232c284ffbedd8c74e59c1525bda217295e18" + integrity sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-classify-character "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-table@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz#7b708b728f8dc4d95d486b9e7a2262f9cddbcbb4" + integrity sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm-tagfilter@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz#fb2e303f7daf616db428bb6a26e18fda14a90a4d" + integrity sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA== + dependencies: + micromark-util-types "^1.0.0" + +micromark-extension-gfm-task-list-item@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz#7683641df5d4a09795f353574d7f7f66e47b7fc4" + integrity sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-gfm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz#40f3209216127a96297c54c67f5edc7ef2d1a2a2" + integrity sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA== + dependencies: + micromark-extension-gfm-autolink-literal "^1.0.0" + micromark-extension-gfm-footnote "^1.0.0" + micromark-extension-gfm-strikethrough "^1.0.0" + micromark-extension-gfm-table "^1.0.0" + micromark-extension-gfm-tagfilter "^1.0.0" + micromark-extension-gfm-task-list-item "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-extension-mdx-expression@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.3.tgz#cd3843573921bf55afcfff4ae0cd2e857a16dcfa" + integrity sha512-TjYtjEMszWze51NJCZmhv7MEBcgYRgb3tJeMAJ+HQCAaZHHRBaDCccqQzGizR/H4ODefP44wRTgOn2vE5I6nZA== + dependencies: + micromark-factory-mdx-expression "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-events-to-acorn "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-extension-mdx-jsx@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-1.0.3.tgz#9f196be5f65eb09d2a49b237a7b3398bba2999be" + integrity sha512-VfA369RdqUISF0qGgv2FfV7gGjHDfn9+Qfiv5hEwpyr1xscRj/CiVRkU7rywGFCO7JwJ5L0e7CJz60lY52+qOA== + dependencies: + "@types/acorn" "^4.0.0" + estree-util-is-identifier-name "^2.0.0" + micromark-factory-mdx-expression "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + vfile-message "^3.0.0" + +micromark-extension-mdx-md@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-1.0.0.tgz#382f5df9ee3706dd120b51782a211f31f4760d22" + integrity sha512-xaRAMoSkKdqZXDAoSgp20Azm0aRQKGOl0RrS81yGu8Hr/JhMsBmfs4wR7m9kgVUIO36cMUQjNyiyDKPrsv8gOw== + dependencies: + micromark-util-types "^1.0.0" + +micromark-extension-mdxjs-esm@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-1.0.3.tgz#630d9dc9db2c2fd470cac8c1e7a824851267404d" + integrity sha512-2N13ol4KMoxb85rdDwTAC6uzs8lMX0zeqpcyx7FhS7PxXomOnLactu8WI8iBNXW8AVyea3KIJd/1CKnUmwrK9A== + dependencies: + micromark-core-commonmark "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-events-to-acorn "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-position-from-estree "^1.1.0" + uvu "^0.5.0" + vfile-message "^3.0.0" + +micromark-extension-mdxjs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-1.0.0.tgz#772644e12fc8299a33e50f59c5aa15727f6689dd" + integrity sha512-TZZRZgeHvtgm+IhtgC2+uDMR7h8eTKF0QUX9YsgoL9+bADBpBY6SiLvWqnBlLbCEevITmTqmEuY3FoxMKVs1rQ== + dependencies: + acorn "^8.0.0" + acorn-jsx "^5.0.0" + micromark-extension-mdx-expression "^1.0.0" + micromark-extension-mdx-jsx "^1.0.0" + micromark-extension-mdx-md "^1.0.0" + micromark-extension-mdxjs-esm "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-destination@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz#fef1cb59ad4997c496f887b6977aa3034a5a277e" + integrity sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-label@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz#6be2551fa8d13542fcbbac478258fb7a20047137" + integrity sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-factory-mdx-expression@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-1.0.6.tgz#917e17d16e6e9c2551f3a862e6a9ebdd22056476" + integrity sha512-WRQIc78FV7KrCfjsEf/sETopbYjElh3xAmNpLkd1ODPqxEngP42eVRGbiPEQWpRV27LzqW+XVTvQAMIIRLPnNA== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-events-to-acorn "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-position-from-estree "^1.0.0" + uvu "^0.5.0" + vfile-message "^3.0.0" + +micromark-factory-space@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz#cebff49968f2b9616c0fcb239e96685cb9497633" + integrity sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-title@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz#7e09287c3748ff1693930f176e1c4a328382494f" + integrity sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-factory-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz#e991e043ad376c1ba52f4e49858ce0794678621c" + integrity sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-character@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.1.0.tgz#d97c54d5742a0d9611a68ca0cd4124331f264d86" + integrity sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg== + dependencies: + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-chunked@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06" + integrity sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-classify-character@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz#cbd7b447cb79ee6997dd274a46fc4eb806460a20" + integrity sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-combine-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz#91418e1e74fb893e3628b8d496085639124ff3d5" + integrity sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-decode-numeric-character-reference@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz#dcc85f13b5bd93ff8d2868c3dba28039d490b946" + integrity sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-decode-string@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz#942252ab7a76dec2dbf089cc32505ee2bc3acf02" + integrity sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-util-encode@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz#2c1c22d3800870ad770ece5686ebca5920353383" + integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA== + +micromark-util-events-to-acorn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.2.0.tgz#65785cb77299d791bfefdc6a5213ab57ceead115" + integrity sha512-WWp3bf7xT9MppNuw3yPjpnOxa8cj5ACivEzXJKu0WwnjBYfzaBvIAT9KfeyI0Qkll+bfQtfftSwdgTH6QhTOKw== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + estree-util-visit "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + vfile-location "^4.0.0" + vfile-message "^3.0.0" + +micromark-util-html-tag-name@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz#eb227118befd51f48858e879b7a419fc0df20497" + integrity sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA== + +micromark-util-normalize-identifier@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz#4a3539cb8db954bbec5203952bfe8cedadae7828" + integrity sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-resolve-all@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz#a7c363f49a0162e931960c44f3127ab58f031d88" + integrity sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw== + dependencies: + micromark-util-types "^1.0.0" + +micromark-util-sanitize-uri@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz#27dc875397cd15102274c6c6da5585d34d4f12b2" + integrity sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-util-subtokenize@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105" + integrity sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-util-symbol@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz#b90344db62042ce454f351cf0bebcc0a6da4920e" + integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ== + +micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20" + integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w== + +micromark@^3.0.0: + version "3.0.10" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.0.10.tgz#1eac156f0399d42736458a14b0ca2d86190b457c" + integrity sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + micromark-core-commonmark "^1.0.1" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + uvu "^0.5.0" + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@~1.25.0: + version "1.25.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392" + integrity sha1-wY29fHOl2/b0SgJNwNFloeexw5I= + +mime-types@2.1.13: + version "2.1.13" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88" + integrity sha1-4HqqnGxrmnyjASxpADrSWjnpKog= + dependencies: + mime-db "~1.25.0" + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + +mrmime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b" + integrity sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== + +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +next-remote-watch@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-remote-watch/-/next-remote-watch-1.0.0.tgz#34c5a015b1bd191f906479c00baabd1a33cf04f5" + integrity sha512-kV+pglCwcnKyqJIXPHUUrnZr9d3rCqCIEQWBkFYC02GDXHyKVmcFytoY6q0+wMIQqh/izIAQL1x6OKXZhksjLA== + dependencies: + body-parser "^1.19.0" + chalk "^4.0.0" + chokidar "^3.4.0" + commander "^5.0.0" + express "^4.17.1" + +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +next@12.3.2-canary.7: + version "12.3.2-canary.7" + resolved "https://registry.yarnpkg.com/next/-/next-12.3.2-canary.7.tgz#c739348174a3d7d97a638aa409376a96577a31d8" + integrity sha512-zUosveWzpeRVy7j4ANoJ4gu0TBrkLYwPlIUEXrqqs/zLpHMu+tanxA1R1ts2d7h/2dSmeVZgGcHiVcHj5uspEA== + dependencies: + "@next/env" "12.3.2-canary.7" + "@swc/helpers" "0.4.11" + caniuse-lite "^1.0.30001406" + postcss "8.4.14" + styled-jsx "5.0.7" + use-sync-external-store "1.2.0" + optionalDependencies: + "@next/swc-android-arm-eabi" "12.3.2-canary.7" + "@next/swc-android-arm64" "12.3.2-canary.7" + "@next/swc-darwin-arm64" "12.3.2-canary.7" + "@next/swc-darwin-x64" "12.3.2-canary.7" + "@next/swc-freebsd-x64" "12.3.2-canary.7" + "@next/swc-linux-arm-gnueabihf" "12.3.2-canary.7" + "@next/swc-linux-arm64-gnu" "12.3.2-canary.7" + "@next/swc-linux-arm64-musl" "12.3.2-canary.7" + "@next/swc-linux-x64-gnu" "12.3.2-canary.7" + "@next/swc-linux-x64-musl" "12.3.2-canary.7" + "@next/swc-win32-arm64-msvc" "12.3.2-canary.7" + "@next/swc-win32-ia32-msvc" "12.3.2-canary.7" + "@next/swc-win32-x64-msvc" "12.3.2-canary.7" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +nlcst-to-string@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz#9315dfab80882bbfd86ddf1b706f53622dc400cc" + integrity sha512-3x3jwTd6UPG7vi5k4GEzvxJ5rDA7hVUIRNHPblKuMVP9Z3xmlsd9cgLcpAMkc5uPOBna82EeshROFhsPkbnTZg== + +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +npm-run-all@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + +object-inspect@^1.11.0, object-inspect@^1.12.0, object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" + integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.fromentries@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251" + integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.hasown@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5" + integrity sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + +parse-entities@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.0.tgz#f67c856d4e3fe19b1a445c3fabe78dcdc1053eeb" + integrity sha512-5nk9Fn03x3rEhGaX1FU6IDwG/k+GxLXlFAkgrbM1asuAFl3BhdQWvASaIsmwWypRNcZKHPYnIuOSfIWEyEQnPQ== + dependencies: + "@types/unist" "^2.0.0" + character-entities "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-latin@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-4.3.0.tgz#1a70fc5601743baa06c5f12253c334fc94b4a917" + integrity sha512-TYKL+K98dcAWoCw/Ac1yrPviU8Trk+/gmjQVaoWEFDZmVD4KRg6c/80xKqNNFQObo2mTONgF8trzAf2UTwKafw== + dependencies: + nlcst-to-string "^2.0.0" + unist-util-modify-children "^2.0.0" + unist-util-visit-children "^1.0.0" + +parse-numeric-range@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +patch-package@^6.2.2: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +periscopic@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.0.4.tgz#b3fbed0d1bc844976b977173ca2cd4a0ef4fa8d1" + integrity sha512-SFx68DxCv0Iyo6APZuw/AKewkkThGwssmU0QWtTlvov3VAtPX+QJ4CadwSaz8nrT5jPIuxdvJWB4PnD2KNDxQg== + dependencies: + estree-walker "^3.0.0" + is-reference "^3.0.0" + +picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pidtree@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + +postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + +postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + +postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + +postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== + dependencies: + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-flexbugs-fixes@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" + integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== + dependencies: + postcss "^7.0.26" + +postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + +postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + +postcss-font-variant@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" + integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== + dependencies: + postcss "^7.0.2" + +postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + +postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-initial@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.4.tgz#9d32069a10531fe2ecafa0b6ac750ee0bc7efc53" + integrity sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg== + dependencies: + postcss "^7.0.2" + +postcss-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" + integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== + dependencies: + camelcase-css "^2.0.1" + +postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-load-config@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.1.tgz#2f53a17f2f543d9e63864460af42efdac0d41f87" + integrity sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg== + dependencies: + lilconfig "^2.0.4" + yaml "^1.10.2" + +postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + +postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" + +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== + dependencies: + postcss-selector-parser "^6.0.6" + +postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== + dependencies: + postcss "^7.0.2" + +postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + +postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + +postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-preset-env@^6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + +postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== + dependencies: + postcss "^7.0.2" + +postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-not@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" + integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss@8.4.14: + version "8.4.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" + integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + +postcss@^8.4.5, postcss@^8.4.6: + version "8.4.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1" + integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== + dependencies: + nanoid "^3.2.0" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prop-types@^15.7.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-information@^5.0.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + +property-information@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22" + integrity sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@6.10.3: + version "6.10.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" + integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + dependencies: + side-channel "^1.0.4" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +"react-collapsed@npm:@gaearon/react-collapsed@3.1.0-forked.1": + version "3.1.0-forked.1" + resolved "https://registry.yarnpkg.com/@gaearon/react-collapsed/-/react-collapsed-3.1.0-forked.1.tgz#b287b81fc2af2971d7d7b523dc40b6cf116822ac" + integrity sha512-QkW55Upl4eeOtnDMOxasafDtDwaF+DpYKvHq8KZoNz9P477iUH8Ik1YFYuqtI7UA8mHm1/z66LD678dZCXwEEg== + dependencies: + raf "^3.4.1" + tiny-warning "^1.0.3" + +react-devtools-inline@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz#e032a6eb17a9977b682306f84b46e683adf4bf68" + integrity sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ== + dependencies: + es6-symbol "^3" + +react-dom@0.0.0-experimental-cb5084d1c-20220924: + version "0.0.0-experimental-cb5084d1c-20220924" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-cb5084d1c-20220924.tgz#7a8334c5cf4baeb5651ca76fc9eb92ebbaf223bf" + integrity sha512-0IHzPGHESn3uu8nI1w5596GX8bCGCE94DpaHkKSGWOlB6uhoVecU0fyOCkkNuB4cMc9WeOWiH2gsM100EWZDPQ== + dependencies: + loose-envify "^1.1.0" + scheduler "0.0.0-experimental-cb5084d1c-20220924" + +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react@0.0.0-experimental-cb5084d1c-20220924: + version "0.0.0-experimental-cb5084d1c-20220924" + resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-cb5084d1c-20220924.tgz#ab661af674be824ae2989467506443b8bc4318d3" + integrity sha512-66AdfxkJrwCaCEKT0LQRd9J9GQ9T+yN7Wx9XT+tNxKycYQ0Exm+DZxOTedagWDlsFMXroyhrTWzgdC18R2PaOQ== + dependencies: + loose-envify "^1.1.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reading-time@^1.2.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" + integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regexp.prototype.flags@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +regexpp@^3.1.0, regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +remark-external-links@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/remark-external-links/-/remark-external-links-7.0.1.tgz#c71ca81ea4cca48f067a9659645e4e87a94e54d5" + integrity sha512-a98JnTMRln8GseQq0buE4Aq6yYjYF4aRIlrPVxL9PT1pcy+yMJij24dEYAqvdluF9GHgNs/De+8y6kzqsjH1jQ== + dependencies: + extend "^3.0.0" + is-absolute-url "^3.0.0" + mdast-util-definitions "^3.0.0" + space-separated-tokens "^1.0.0" + unist-util-visit "^2.0.0" + +remark-frontmatter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-4.0.1.tgz#84560f7ccef114ef076d3d3735be6d69f8922309" + integrity sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-frontmatter "^1.0.0" + micromark-extension-frontmatter "^1.0.0" + unified "^10.0.0" + +remark-gfm@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f" + integrity sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-gfm "^2.0.0" + micromark-extension-gfm "^2.0.0" + unified "^10.0.0" + +remark-html@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/remark-html/-/remark-html-12.0.0.tgz#f39d2a5e173cce777981cb4171b4ea860313d72a" + integrity sha512-M104NMHs48+uswChJkCDXCdabzxAinpHikpt6kS3gmGMyIvPZ5kn53tB9shFsL2O4HUJ9DIEsah1SX1Ve5FXHA== + dependencies: + hast-util-sanitize "^3.0.0" + hast-util-to-html "^7.0.0" + mdast-util-to-hast "^9.0.0" + xtend "^4.0.1" + +remark-images@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/remark-images/-/remark-images-2.0.0.tgz#7621a406364c3a0a6e4250c3ee63909cc14a2388" + integrity sha512-1X6XTBQZW489HSwU0k+aU3xAlVe3TyPll6N2Mt1onwINTIqcTk9QTC57937Z8NQDJ8h7gKGXy9d4TJug2dm8lg== + dependencies: + is-url "^1.2.2" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +remark-mdx@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-2.1.3.tgz#6273e8b94d27ade35407a63bc8cdd04592f7be9f" + integrity sha512-3SmtXOy9+jIaVctL8Cs3VAQInjRLGOwNXfrBB9KCT+EpJpKD3PQiy0x8hUNGyjQmdyOs40BqgPU7kYtH9uoR6w== + dependencies: + mdast-util-mdx "^2.0.0" + micromark-extension-mdxjs "^1.0.0" + +remark-parse@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-10.0.1.tgz#6f60ae53edbf0cf38ea223fe643db64d112e0775" + integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + unified "^10.0.0" + +remark-parse@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" + integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== + dependencies: + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" + +remark-rehype@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-10.1.0.tgz#32dc99d2034c27ecaf2e0150d22a6dcccd9a6279" + integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw== + dependencies: + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-to-hast "^12.1.0" + unified "^10.0.0" + +remark-slug@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-7.0.1.tgz#9827ce6d6ee81ca82b79891b0e5931a8123ce63b" + integrity sha512-NRvYePr69LdeCkEGwL4KYAmq7kdWG5rEavCXMzUR4qndLoXHJAOLSUmPY6Qm4NJfKix7/EmgObyVaYivONAFhg== + dependencies: + "@types/hast" "^2.3.2" + "@types/mdast" "^3.0.0" + github-slugger "^1.0.0" + mdast-util-to-string "^3.0.0" + unified "^10.0.0" + unist-util-visit "^4.0.0" + +remark-stringify@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5" + integrity sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A== + dependencies: + ccount "^1.0.0" + is-alphanumeric "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + longest-streak "^2.0.1" + markdown-escapes "^1.0.0" + markdown-table "^2.0.0" + mdast-util-compact "^2.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + stringify-entities "^3.0.0" + unherit "^1.0.4" + xtend "^4.0.1" + +remark-unwrap-images@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/remark-unwrap-images/-/remark-unwrap-images-2.1.0.tgz#1dda005e8397c6f1792e4c8d38ed29602f9284cc" + integrity sha512-DpM7oEIXNjS3aDQpzxWrTWhUJcd5b8LznbSZnSPi1Yc3fJgLYJlx9uzkj5ekyp01PSBbSbPM2jq4959mcIetvA== + dependencies: + hast-util-whitespace "^1.0.0" + unist-util-visit "^2.0.0" + +remark@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/remark/-/remark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f" + integrity sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw== + dependencies: + remark-parse "^8.0.0" + remark-stringify "^8.0.0" + unified "^9.0.0" + +repeat-string@^1.0.0, repeat-string@^1.5.4: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retext-latin@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-2.0.4.tgz#ef5d34ae7641ae56b0675ea391095e8ee762b251" + integrity sha512-fOoSSoQgDZ+l/uS81oxI3alBghDUPja0JEl0TpQxI6MN+dhM6fLFumPJwMZ4PJTyL5FFAgjlsdv8IX+6IRuwMw== + dependencies: + parse-latin "^4.0.0" + unherit "^1.0.4" + +retext-smartypants@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-4.0.0.tgz#77478bd9775b4d7505122b0799594339e08d4fda" + integrity sha512-Mknd05zuIycr4Z/hNDxA8ktqv7pG7wYdTZc68a2MJF+Ibg/WloR5bbyrEjijwNwHRR+xWsovkLH4OQIz/mghdw== + dependencies: + nlcst-to-string "^2.0.0" + unist-util-visit "^2.0.0" + +retext-stringify@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-2.0.4.tgz#496d6c532f7dc6d15e4b262de0266e828f72efa9" + integrity sha512-xOtx5mFJBoT3j7PBtiY2I+mEGERNniofWktI1cKXvjMEJPOuqve0dghLHO1+gz/gScLn4zqspDGv4kk2wS5kSA== + dependencies: + nlcst-to-string "^2.0.0" + +retext@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/retext/-/retext-7.0.1.tgz#04b7965ab78fe6e5e3a489304545b460d41bf5aa" + integrity sha512-N0IaEDkvUjqyfn3/gwxVfI51IxfGzOiVXqPLWnKeCDbiQdxSg0zebzHPxXWnL7TeplAJ+RE4uqrXyYN//s9HjQ== + dependencies: + retext-latin "^2.0.0" + retext-stringify "^2.0.0" + unified "^8.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rss@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/rss/-/rss-1.2.2.tgz#50a1698876138133a74f9a05d2bdc8db8d27a921" + integrity sha1-UKFpiHYTgTOnT5oF0r3I240nqSE= + dependencies: + mime-types "2.1.13" + xml "1.0.1" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^7.5.2: + version "7.5.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.2.tgz#11e4a3a1dfad85dbf7fb6e33cbba17668497490b" + integrity sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w== + dependencies: + tslib "^2.1.0" + +sade@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scheduler@0.0.0-experimental-cb5084d1c-20220924: + version "0.0.0-experimental-cb5084d1c-20220924" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-cb5084d1c-20220924.tgz#9986680e1fbf7e4ccfe7606fef5920939d3a1abe" + integrity sha512-SeszKCdhM5OHxNMStjpKIAaloARUMZqIpDZjkZeGuRsyr7YlAqvlsCXyee4ZQOOU1pbr7BIvQntKy5Hil+DQJA== + dependencies: + loose-envify "^1.1.0" + +section-matter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.2.1, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.6.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.6" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" + integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== + +sirv@^1.0.7: + version "1.0.19" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" + integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== + dependencies: + "@polka/url" "^1.0.0-next.20" + mrmime "^1.0.0" + totalist "^1.0.0" + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.0: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + +space-separated-tokens@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b" + integrity sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +state-toggle@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" + integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +string-argv@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" + integrity sha512-7x54QnN21P+XL/v8SuNKvfgsUre6PXpN7mc77N3HlZv+f1SBRGmjxtOud2Z6FZ8DmdkD/IdjCaf9XXbnqmTZGQ== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.matchall@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa" + integrity sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + +string.prototype.padend@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz#997a6de12c92c7cb34dc8a201a6c53d9bd88a5f1" + integrity sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +stringify-entities@^3.0.0, stringify-entities@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.1.0.tgz#b8d3feac256d9ffcc9fa1fefdcf3ca70576ee903" + integrity sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + xtend "^4.0.0" + +stringify-entities@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.3.tgz#cfabd7039d22ad30f3cc435b0ca2c1574fc88ef8" + integrity sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +style-mod@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.0.0.tgz#97e7c2d68b592975f2ca7a63d0dd6fcacfe35a01" + integrity sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw== + +style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" + +styled-jsx@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.7.tgz#be44afc53771b983769ac654d355ca8d019dff48" + integrity sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.1.tgz#599dc9d45acf74c6176e0d880bab1d7d718fe891" + integrity sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ== + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +table@^6.0.9: + version "6.8.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" + integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +tailwindcss@^3.0.22: + version "3.0.23" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.23.tgz#c620521d53a289650872a66adfcb4129d2200d10" + integrity sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA== + dependencies: + arg "^5.0.1" + chalk "^4.1.2" + chokidar "^3.5.3" + color-name "^1.1.4" + cosmiconfig "^7.0.1" + detective "^5.2.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.11" + glob-parent "^6.0.2" + is-glob "^4.0.3" + normalize-path "^3.0.0" + object-hash "^2.2.0" + postcss "^8.4.6" + postcss-js "^4.0.0" + postcss-load-config "^3.1.0" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +textextensions@2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.6.0.tgz#d7e4ab13fe54e32e08873be40d51b74229b00fc4" + integrity sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ== + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +totalist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" + integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trim-trailing-lines@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" + integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== + +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= + +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + +trough@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.0.2.tgz#94a3aa9d5ce379fc561f6244905b3f36b7458d96" + integrity sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w== + +tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tslib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + +typescript@^4.0.2: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +unherit@^1.0.4: + version "1.1.3" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" + integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== + dependencies: + inherits "^2.0.0" + xtend "^4.0.0" + +unified@^10.0.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.1.tgz#345e349e3ab353ab612878338eb9d57b4dea1d46" + integrity sha512-v4ky1+6BN9X3pQrOdkFIPWAaeDsHPE1svRDxq7YpTc2plkIqFMwukfqM+l0ewpP9EfwARlt9pPFAeWYhHm8X9w== + dependencies: + "@types/unist" "^2.0.0" + bail "^2.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^5.0.0" + +unified@^8.0.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-8.4.2.tgz#13ad58b4a437faa2751a4a4c6a16f680c500fff1" + integrity sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +unified@^9.0.0: + version "9.2.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" + integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +unist-builder@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" + integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== + +unist-builder@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04" + integrity sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-generated@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" + integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== + +unist-util-generated@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113" + integrity sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw== + +unist-util-is@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" + integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== + +unist-util-is@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236" + integrity sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ== + +unist-util-modify-children@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-2.0.0.tgz#9c9c30d4e32502aabb3fde10d7872a17c86801e2" + integrity sha512-HGrj7JQo9DwZt8XFsX8UD4gGqOsIlCih9opG6Y+N11XqkBGKzHo8cvDi+MfQQgiZ7zXRUiQREYHhjOBHERTMdg== + dependencies: + array-iterate "^1.0.0" + +unist-util-position-from-estree@^1.0.0, unist-util-position-from-estree@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.1.tgz#96f4d543dfb0428edc01ebb928570b602d280c4c" + integrity sha512-xtoY50b5+7IH8tFbkw64gisG9tMSpxDjhX9TmaJJae/XuxQ9R/Kc8Nv1eOsf43Gt4KV/LkriMy9mptDr7XLcaw== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-position@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" + integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== + +unist-util-position@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.3.tgz#5290547b014f6222dff95c48d5c3c13a88fadd07" + integrity sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + +unist-util-remove-position@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-4.0.1.tgz#d5b46a7304ac114c8d91990ece085ca7c2c135c8" + integrity sha512-0yDkppiIhDlPrfHELgB+NLQD5mfjup3a8UYclHruTJWmY74je8g+CIFr79x5f6AkmzSwlvKLbs63hC0meOMowQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-visit "^4.0.0" + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-stringify-position@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz#d517d2883d74d0daa0b565adc3d10a02b4a8cde9" + integrity sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-visit-children@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-1.1.4.tgz#e8a087e58a33a2815f76ea1901c15dec2cb4b432" + integrity sha512-sA/nXwYRCQVRwZU2/tQWUqJ9JSFM1X3x7JIOsIgSzrFHcfVt6NkzDtKzyxg2cZWkCwGF9CO8x4QNZRJRMK8FeQ== + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit-parents@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz#44bbc5d25f2411e7dfc5cecff12de43296aa8521" + integrity sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + +unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +unist-util-visit@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.0.tgz#f41e407a9e94da31594e6b1c9811c51ab0b3d8f5" + integrity sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.0.0" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz#16279639cff1d0f800b14792de43d97df2d11b7d" + integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +use-sync-external-store@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uvu@^0.5.0: + version "0.5.6" + resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df" + integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA== + dependencies: + dequal "^2.0.0" + diff "^5.0.0" + kleur "^4.0.3" + sade "^1.7.3" + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vfile-location@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" + integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== + +vfile-location@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-4.0.1.tgz#06f2b9244a3565bef91f099359486a08b10d3a95" + integrity sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw== + dependencies: + "@types/unist" "^2.0.0" + vfile "^5.0.0" + +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile-message@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.0.tgz#5437035aa43185ff4b9210d32fada6c640e59143" + integrity sha512-4QJbBk+DkPEhBXq3f260xSaWtjE4gPKOfulzfMFF8ZNwaPZieWsg3iVlcmF04+eebzpcpeXOOFMfrYzJHVYg+g== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^3.0.0" + +vfile@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + +vfile@^5.0.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.0.tgz#4990c78cb3157005590ee8c930b71cd7fa6a006e" + integrity sha512-Tj44nY/48OQvarrE4FAjUfrv7GZOYzPbl5OD65HxVKwLJKMPU7zmfV8cCgCnzKWnSfYG2f3pxu+ALqs7j22xQQ== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + +w3c-keyname@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.4.tgz#4ade6916f6290224cdbd1db8ac49eab03d0eef6b" + integrity sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw== + +webpack-bundle-analyzer@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5" + integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ== + dependencies: + acorn "^8.0.4" + acorn-walk "^8.0.0" + chalk "^4.1.0" + commander "^7.2.0" + gzip-size "^6.0.0" + lodash "^4.17.20" + opener "^1.5.2" + sirv "^1.0.7" + ws "^7.3.1" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^7.3.1: + version "7.5.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== + +xml@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +zwitch@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.2.tgz#91f8d0e901ffa3d66599756dde7f57b17c95dce1" + integrity sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA== diff --git a/content/authors.yml b/content/authors.yml index e636436c5..39864f894 100644 --- a/content/authors.yml +++ b/content/authors.yml @@ -1,6 +1,9 @@ # Map of short name to more information. `name` will be used but if you don't # want to use your real name, just use whatever. If url is included, your name # will be a link to the provided url. +abernathyca: + name: Christine Abernathy + url: https://twitter.com/abernathyca acdlite: name: Andrew Clark url: https://twitter.com/acdlite @@ -28,6 +31,12 @@ flarnie: gaearon: name: Dan Abramov url: https://twitter.com/dan_abramov +gsathya: + name: Sathya Gunasekaran + url: https://twitter.com/_gsathya +huxpro: + name: Xuan Huang + url: https://twitter.com/Huxpro jaredly: name: Jared Forsyth url: https://twitter.com/jaredforsyth @@ -43,15 +52,33 @@ jingc: josephsavona: name: Joseph Savona url: https://twitter.com/en_JS +joshcstory: + name: Josh Story + url: https://twitter.com/joshcstory +jtannady: + name: Jesslyn Tannady + url: https://twitter.com/jtannady +kassens: + name: Jan Kassens + url: https://twitter.com/kassens keyanzhang: name: Keyan Zhang url: https://twitter.com/keyanzhang kmeht: name: Kunal Mehta url: https://github.com/kmeht +laurentan: + name: Lauren Tan + url: https://twitter.com/potetotes LoukaN: name: Lou Husson url: https://twitter.com/loukan42 +lunaruan: + name: Luna Ruan + url: https://twitter.com/lunaruan +mengdichen: + name: Mengdi Chen + url: https://twitter.com/mengdi_en matthewjohnston4: name: Matthew Johnston url: https://github.com/matthewathome @@ -61,12 +88,27 @@ nhunzaker: petehunt: name: Pete Hunt url: https://twitter.com/floydophone +rachelnabors: + name: Rachel Nabors + url: https://twitter.com/rachelnabors +reactteam: + name: The React Team + url: https://reactjs.org/community/team.html +rickhanlonii: + name: Rick Hanlon + url: https://twitter.com/rickhanlonii +robertzhang: + name: Robert Zhang + url: https://twitter.com/jiaxuanzhang01 schrockn: name: Nick Schrock url: https://twitter.com/schrockn sebmarkbage: name: Sebastian Markbåge url: https://twitter.com/sebmarkbage +sethwebster: + name: Seth Webster + url: https://twitter.com/sethwebster sophiebits: name: Sophie Alpert url: https://sophiebits.com/ @@ -93,4 +135,4 @@ zpao: url: https://twitter.com/zpao tomocchino: name: Tom Occhino - url: https://twitter.com/tomocchino \ No newline at end of file + url: https://twitter.com/tomocchino diff --git a/content/blog/2013-06-19-community-roundup-2.md b/content/blog/2013-06-19-community-roundup-2.md index 3071db80b..1612134b0 100644 --- a/content/blog/2013-06-19-community-roundup-2.md +++ b/content/blog/2013-06-19-community-roundup-2.md @@ -9,7 +9,7 @@ Since the launch we have received a lot of feedback and are actively working on [Andrew Greig](http://www.andrewgreig.com/) made a blog post that gives a high level description of what React is. -> I have been using Facebooks recently released JavaScript framework called React.js for the last few days and have managed to obtain a rather high level understanding of how it works and formed a good perspective on how it fits in to the entire javascript framework ecosystem. +> I have been using Facebook's recently released JavaScript framework called React.js for the last few days and have managed to obtain a rather high level understanding of how it works and formed a good perspective on how it fits in to the entire javascript framework ecosystem. > > Basically, React is not an MVC framework. It is not a replacement for Backbone or Knockout or Angular, instead it is designed to work with existing frameworks and help extend their functionality. > diff --git a/content/blog/2013-10-16-react-v0.5.0.md b/content/blog/2013-10-16-react-v0.5.0.md index 88b7f4d7b..81629c459 100644 --- a/content/blog/2013-10-16-react-v0.5.0.md +++ b/content/blog/2013-10-16-react-v0.5.0.md @@ -5,7 +5,7 @@ author: [zpao] This release is the result of several months of hard work from members of the team and the community. While there are no groundbreaking changes in core, we've worked hard to improve performance and memory usage. We've also worked hard to make sure we are being consistent in our usage of DOM properties. -The biggest change you'll notice as a developer is that we no longer support `class` in JSX as a way to provide CSS classes. Since this prop was being converted to `className` at the transform step, it caused some confusion when trying to access it in composite components. As a result we decided to make our DOM properties mirror their counterparts in the JS DOM API. There are [a few exceptions](https://github.com/facebook/react/blob/master/src/dom/DefaultDOMPropertyConfig.js#L156) where we deviate slightly in an attempt to be consistent internally. +The biggest change you'll notice as a developer is that we no longer support `class` in JSX as a way to provide CSS classes. Since this prop was being converted to `className` at the transform step, it caused some confusion when trying to access it in composite components. As a result we decided to make our DOM properties mirror their counterparts in the JS DOM API. There are [a few exceptions](https://github.com/facebook/react/blob/main/src/dom/DefaultDOMPropertyConfig.js#L156) where we deviate slightly in an attempt to be consistent internally. The other major change in v0.5 is that we've added an additional build - `react-with-addons` - which adds support for some extras that we've been working on including animations and two-way binding. [Read more about these addons in the docs](/docs/addons.html). @@ -34,7 +34,7 @@ It's been awesome to see the things that people are building with React, and we * Added `React.version`. * Added `React.isValidClass` - Used to determine if a value is a valid component constructor. * Removed `React.autoBind` - This was deprecated in v0.4 and now properly removed. -* Renamed `React.unmountAndReleaseReactRootNode` to `React.unmountComponentAtNode`. +* Renamed `React.unmountAndReleaseReactRootNode` to `React.unmountComponentAtNode`. * Began laying down work for refined performance analysis. * Better support for server-side rendering - [react-page](https://github.com/facebook/react-page) has helped improve the stability for server-side rendering. * Made it possible to use React in environments enforcing a strict [Content Security Policy](https://developer.mozilla.org/en-US/docs/Security/CSP/Introducing_Content_Security_Policy). This also makes it possible to use React to build Chrome extensions. diff --git a/content/blog/2014-02-16-react-v0.9-rc1.md b/content/blog/2014-02-16-react-v0.9-rc1.md index afc3822ba..d57699f2d 100644 --- a/content/blog/2014-02-16-react-v0.9-rc1.md +++ b/content/blog/2014-02-16-react-v0.9-rc1.md @@ -51,7 +51,7 @@ React.DOM.div(null, We believe this new behavior is more helpful and eliminates cases where unwanted whitespace was previously added. -In cases where you want to preserve the space adjacent to a newline, you can write a JS string like `{"Monkeys: "}` in your JSX source. We've included a script to do an automated codemod of your JSX source tree that preserves the old whitespace behavior by adding and removing spaces appropriately. You can [install jsx\_whitespace\_transformer from npm](https://github.com/facebook/react/blob/master/npm-jsx_whitespace_transformer/README.md) and run it over your source tree to modify files in place. The transformed JSX files will preserve your code's existing whitespace behavior. +In cases where you want to preserve the space adjacent to a newline, you can write a JS string like `{"Monkeys: "}` in your JSX source. We've included a script to do an automated codemod of your JSX source tree that preserves the old whitespace behavior by adding and removing spaces appropriately. You can [install jsx\_whitespace\_transformer from npm](https://github.com/facebook/react/blob/main/npm-jsx_whitespace_transformer/README.md) and run it over your source tree to modify files in place. The transformed JSX files will preserve your code's existing whitespace behavior. ## Changelog {#changelog} diff --git a/content/blog/2014-02-20-react-v0.9.md b/content/blog/2014-02-20-react-v0.9.md index bcb02397c..95bff4c78 100644 --- a/content/blog/2014-02-20-react-v0.9.md +++ b/content/blog/2014-02-20-react-v0.9.md @@ -59,7 +59,7 @@ React.DOM.div(null, We believe this new behavior is more helpful and eliminates cases where unwanted whitespace was previously added. -In cases where you want to preserve the space adjacent to a newline, you can write `{'Monkeys: '}` or `Monkeys:{' '}` in your JSX source. We've included a script to do an automated codemod of your JSX source tree that preserves the old whitespace behavior by adding and removing spaces appropriately. You can [install jsx\_whitespace\_transformer from npm](https://github.com/facebook/react/blob/master/npm-jsx_whitespace_transformer/README.md) and run it over your source tree to modify files in place. The transformed JSX files will preserve your code's existing whitespace behavior. +In cases where you want to preserve the space adjacent to a newline, you can write `{'Monkeys: '}` or `Monkeys:{' '}` in your JSX source. We've included a script to do an automated codemod of your JSX source tree that preserves the old whitespace behavior by adding and removing spaces appropriately. You can [install jsx\_whitespace\_transformer from npm](https://github.com/facebook/react/blob/main/npm-jsx_whitespace_transformer/README.md) and run it over your source tree to modify files in place. The transformed JSX files will preserve your code's existing whitespace behavior. ## Changelog {#changelog} diff --git a/content/blog/2014-05-06-flux.md b/content/blog/2014-05-06-flux.md index f4460545c..149c7d5fc 100644 --- a/content/blog/2014-05-06-flux.md +++ b/content/blog/2014-05-06-flux.md @@ -13,4 +13,4 @@ In Flux, the Dispatcher is a singleton that directs the flow of data and ensures When a user interacts with a React view, the view sends an action (usually represented as a JavaScript object with some fields) through the dispatcher, which notifies the various stores that hold the application's data and business logic. When the stores change state, they notify the views that something has updated. This works especially well with React's declarative model, which allows the stores to send updates without specifying how to transition views between states. -Flux is more of a pattern than a formal framework, so you can start using Flux immediately without a lot of new code. An [example of this architecture](https://github.com/facebook/flux/tree/master/examples/flux-todomvc) is available, along with more [detailed documentation](https://facebook.github.io/flux/docs/overview.html) and a [tutorial](https://facebook.github.io/flux/docs/todo-list.html). Look for more examples to come in the future. +Flux is more of a pattern than a formal framework, so you can start using Flux immediately without a lot of new code. An [example of this architecture](https://github.com/facebook/flux/tree/master/examples/flux-todomvc) is available, along with more [detailed documentation](https://facebook.github.io/flux/docs/overview.html) and a [tutorial](https://github.com/facebook/flux/tree/main/examples/flux-todomvc). Look for more examples to come in the future. diff --git a/content/blog/2014-10-28-react-v0.12.md b/content/blog/2014-10-28-react-v0.12.md index 824b2d6b0..661e6995a 100644 --- a/content/blog/2014-10-28-react-v0.12.md +++ b/content/blog/2014-10-28-react-v0.12.md @@ -48,7 +48,7 @@ As a result of this update, we no longer need to expose several internal modules We updated the license on React to the BSD 3-Clause license with an explicit patent grant. Previously we used the Apache 2 license. These licenses are very similar and our extra patent grant is equivalent to the grant provided in the Apache license. You can still use React with the confidence that we have granted the use of any patents covering it. This brings us in line with the same licensing we use across the majority of our open source projects at Facebook. -You can read the full text of the [LICENSE](https://github.com/facebook/react/blob/master/LICENSE) and [`PATENTS`](https://github.com/facebook/react/blob/master/PATENTS) files on GitHub. +You can read the full text of the [LICENSE](https://github.com/facebook/react/blob/main/LICENSE) and [`PATENTS`](https://github.com/facebook/react/blob/main/PATENTS) files on GitHub. - - - @@ -66,7 +66,7 @@ You can read the full text of the [LICENSE](https://github.com/facebook/react/bl #### New Features {#new-features} -* Spread operator (`{...}`) introduced to deprecate `this.transferPropsTo` +* Spread syntax (`{...}`) introduced to deprecate `this.transferPropsTo` * Added support for more HTML attributes: `acceptCharset`, `classID`, `manifest` #### Deprecations {#deprecations} diff --git a/content/blog/2015-02-24-react-v0.13-rc1.md b/content/blog/2015-02-24-react-v0.13-rc1.md index 6bf6e84f5..ac2dd4c5c 100644 --- a/content/blog/2015-02-24-react-v0.13-rc1.md +++ b/content/blog/2015-02-24-react-v0.13-rc1.md @@ -69,7 +69,7 @@ We've also published version `0.13.0-rc1` of the `react` and `react-tools` packa * `--target` option is available on the jsx command, allowing users to specify and ECMAScript version to target. * `es5` is the default. * `es3` restored the previous default behavior. An additional transform is added here to ensure the use of reserved words as properties is safe (eg `this.static` will become `this['static']` for IE8 compatibility). -* The transform for the call spread operator has also been enabled. +* The transform for the call spread syntax has also been enabled. ### JSX {#jsx} diff --git a/content/blog/2015-03-10-react-v0.13.md b/content/blog/2015-03-10-react-v0.13.md index 47a450d2e..df0064c32 100644 --- a/content/blog/2015-03-10-react-v0.13.md +++ b/content/blog/2015-03-10-react-v0.13.md @@ -81,7 +81,7 @@ We've also published version `0.13.0` of the `react` and `react-tools` packages * `--target` option is available on the jsx command, allowing users to specify and ECMAScript version to target. * `es5` is the default. * `es3` restores the previous default behavior. An additional transform is added here to ensure the use of reserved words as properties is safe (eg `this.static` will become `this['static']` for IE8 compatibility). -* The transform for the call spread operator has also been enabled. +* The transform for the call spread syntax has also been enabled. ### JSX {#jsx} diff --git a/content/blog/2015-03-26-introducing-react-native.md b/content/blog/2015-03-26-introducing-react-native.md index 017ce05a8..ea46742d0 100644 --- a/content/blog/2015-03-26-introducing-react-native.md +++ b/content/blog/2015-03-26-introducing-react-native.md @@ -13,4 +13,4 @@ For more details, see [Tom Occhino's post on the Facebook Engineering blog](http > > *It's worth noting that we're not chasing “write once, run anywhere.” Different platforms have different looks, feels, and capabilities, and as such, we should still be developing discrete apps for each platform, but the same set of engineers should be able to build applications for whatever platform they choose, without needing to learn a fundamentally different set of technologies for each. We call this approach “learn once, write anywhere.”* -To learn more, visit the [React Native website](https://facebook.github.io/react-native/). +To learn more, visit the [React Native website](https://reactnative.dev/). diff --git a/content/blog/2015-05-01-graphql-introduction.md b/content/blog/2015-05-01-graphql-introduction.md index e90ffc4df..ab2a9e2ac 100644 --- a/content/blog/2015-05-01-graphql-introduction.md +++ b/content/blog/2015-05-01-graphql-introduction.md @@ -3,7 +3,7 @@ title: "GraphQL Introduction" author: [schrockn] --- -At the React.js conference in late January 2015, we revealed our next major technology in the React family: [Relay](http://facebook.github.io/react/blog/2015/02/20/introducing-relay-and-graphql.html). +At the React.js conference in late January 2015, we revealed our next major technology in the React family: [Relay](/blog/2015/02/20/introducing-relay-and-graphql.html). Relay is a new way of structuring client applications that co-locates data-fetching requirements and React components. Instead of placing data fetching logic in some other part of the client application – or embedding this logic in a custom endpoint on the server – we instead co-locate a *declarative* data-fetching specification alongside the React component. The language of this declarative specification is GraphQL. diff --git a/content/blog/2015-09-10-react-v0.14-rc1.md b/content/blog/2015-09-10-react-v0.14-rc1.md index e69c93ab4..277fa807e 100644 --- a/content/blog/2015-09-10-react-v0.14-rc1.md +++ b/content/blog/2015-09-10-react-v0.14-rc1.md @@ -137,7 +137,7 @@ And these two changes did not warn in 0.13 but should be easy to find and clean - Due to the DOM node refs change mentioned above, `this.getDOMNode()` is now deprecated and `ReactDOM.findDOMNode(this)` can be used instead. Note that in most cases, calling `findDOMNode` is now unnecessary – see the example above in the “DOM node refs” section. - If you have a large codebase, you can use our [automated codemod script](https://github.com/facebook/react/blob/master/packages/react-codemod/README.md) to change your code automatically. + If you have a large codebase, you can use our [automated codemod script](https://github.com/facebook/react/blob/main/packages/react-codemod/README.md) to change your code automatically. - `setProps` and `replaceProps` are now deprecated. Instead, call ReactDOM.render again at the top level with the new props. - ES6 component classes must now extend `React.Component` in order to enable stateless function components. The [ES3 module pattern](/blog/2015/01/27/react-v0.13.0-beta-1.html#other-languages) will continue to work. diff --git a/content/blog/2016-07-11-introducing-reacts-error-code-system.md b/content/blog/2016-07-11-introducing-reacts-error-code-system.md index 1b4d5ff67..26b695e14 100644 --- a/content/blog/2016-07-11-introducing-reacts-error-code-system.md +++ b/content/blog/2016-07-11-introducing-reacts-error-code-system.md @@ -9,7 +9,7 @@ Prior to this release, we stripped out error messages at build-time and this is > Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings. -In order to make debugging in production easier, we're introducing an Error Code System in [15.2.0](https://github.com/facebook/react/releases/tag/v15.2.0). We developed a [gulp script](https://github.com/facebook/react/blob/master/scripts/error-codes/gulp-extract-errors.js) that collects all of our `invariant` error messages and folds them to a [JSON file](https://github.com/facebook/react/blob/master/scripts/error-codes/codes.json), and at build-time Babel uses the JSON to [rewrite](https://github.com/facebook/react/blob/master/scripts/error-codes/replace-invariant-error-codes.js) our `invariant` calls in production to reference the corresponding error IDs. Now when things go wrong in production, the error that React throws will contain a URL with an error ID and relevant information. The URL will point you to a page in our documentation where the original error message gets reassembled. +In order to make debugging in production easier, we're introducing an Error Code System in [15.2.0](https://github.com/facebook/react/releases/tag/v15.2.0). We developed a [script](https://github.com/facebook/react/blob/main/scripts/error-codes/extract-errors.js) that collects all of our `invariant` error messages and folds them to a [JSON file](https://github.com/facebook/react/blob/main/scripts/error-codes/codes.json), and at build-time Babel uses the JSON to [rewrite](https://github.com/facebook/react/blob/main/scripts/error-codes/transform-error-messages.js) our `invariant` calls in production to reference the corresponding error IDs. Now when things go wrong in production, the error that React throws will contain a URL with an error ID and relevant information. The URL will point you to a page in our documentation where the original error message gets reassembled. While we hope you don't see errors often, you can see how it works [here](/docs/error-decoder.html?invariant=109&args[]=Foo). This is what the same error from above will look like: diff --git a/content/blog/2016-07-13-mixins-considered-harmful.md b/content/blog/2016-07-13-mixins-considered-harmful.md index 2ea907ea8..b1108400f 100644 --- a/content/blog/2016-07-13-mixins-considered-harmful.md +++ b/content/blog/2016-07-13-mixins-considered-harmful.md @@ -607,7 +607,7 @@ var Button = React.createClass({ Sometimes people use mixins to selectively add logging to lifecycle methods in some components. In the future, we intend to provide an [official DevTools API](https://github.com/facebook/react/issues/5306) that would let you implement something similar without touching the components. However it’s still very much a work in progress. If you heavily depend on logging mixins for debugging, you might want to keep using those mixins for a little longer. -If you can’t accomplish something with a component, a higher-order component, or a utility module, it could be mean that React should provide this out of the box. [File an issue](https://github.com/facebook/react/issues/new) to tell us about your use case for mixins, and we’ll help you consider alternatives or perhaps implement your feature request. +If you can’t accomplish something with a component, a higher-order component, or a utility module, it could mean that React should provide this out of the box. [File an issue](https://github.com/facebook/react/issues/new) to tell us about your use case for mixins, and we’ll help you consider alternatives or perhaps implement your feature request. Mixins are not deprecated in the traditional sense. You can keep using them with `React.createClass()`, as we won’t be changing it further. Eventually, as ES6 classes gain more adoption and their usability problems in React are solved, we might split `React.createClass()` into a separate package because most people wouldn’t need it. Even in that case, your old mixins would keep working. diff --git a/content/blog/2016-11-16-react-v15.4.0.md b/content/blog/2016-11-16-react-v15.4.0.md index 4c9284595..9648dd497 100644 --- a/content/blog/2016-11-16-react-v15.4.0.md +++ b/content/blog/2016-11-16-react-v15.4.0.md @@ -5,7 +5,7 @@ author: [gaearon] Today we are releasing React 15.4.0. -We didn't announce the [previous](https://github.com/facebook/react/blob/master/CHANGELOG.md#1510-may-20-2016) [minor](https://github.com/facebook/react/blob/master/CHANGELOG.md#1520-july-1-2016) [releases](https://github.com/facebook/react/blob/master/CHANGELOG.md#1530-july-29-2016) on the blog because most of the changes were bug fixes. However, 15.4.0 is a special release, and we would like to highlight a few notable changes in it. +We didn't announce the [previous](https://github.com/facebook/react/blob/main/CHANGELOG.md#1510-may-20-2016) [minor](https://github.com/facebook/react/blob/main/CHANGELOG.md#1520-july-1-2016) [releases](https://github.com/facebook/react/blob/main/CHANGELOG.md#1530-july-29-2016) on the blog because most of the changes were bug fixes. However, 15.4.0 is a special release, and we would like to highlight a few notable changes in it. ### Separating React and React DOM {#separating-react-and-react-dom} diff --git a/content/blog/2017-05-18-whats-new-in-create-react-app.md b/content/blog/2017-05-18-whats-new-in-create-react-app.md index 9446de7e9..5fe30e07f 100644 --- a/content/blog/2017-05-18-whats-new-in-create-react-app.md +++ b/content/blog/2017-05-18-whats-new-in-create-react-app.md @@ -54,7 +54,7 @@ Newly created projects are built as [Progressive Web Apps](https://developers.go New apps automatically have these features, but you can easily convert an existing project to a Progressive Web App by following [our migration guide](https://github.com/facebookincubator/create-react-app/releases/tag/v1.0.0). -We will be adding [more documentation](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) on this topic in the coming weeks. Please feel free to [ask any questions](https://github.com/facebookincubator/create-react-app/issues/new) on the issue tracker! +We will be adding [more documentation](https://github.com/facebookincubator/create-react-app/blob/main/packages/react-scripts/template/README.md#making-a-progressive-web-app) on this topic in the coming weeks. Please feel free to [ask any questions](https://github.com/facebookincubator/create-react-app/issues/new) on the issue tracker! ### Jest 20 {#jest-20} diff --git a/content/blog/2017-09-08-dom-attributes-in-react-16.md b/content/blog/2017-09-08-dom-attributes-in-react-16.md index 31c66e53e..21a6e3032 100644 --- a/content/blog/2017-09-08-dom-attributes-in-react-16.md +++ b/content/blog/2017-09-08-dom-attributes-in-react-16.md @@ -29,10 +29,10 @@ In React 16, we are making a change. Now, any unknown attributes will end up in React has always provided a JavaScript-centric API to the DOM. Since React components often take both custom and DOM-related props, it makes sense for React to use the `camelCase` convention just like the DOM APIs: ```js -<div tabIndex="-1" /> +<div tabIndex={-1} /> ``` -This has not changed. However, the way we enforced it in the past forced us to maintain a whitelist of all valid React DOM attributes in the bundle: +This has not changed. However, the way we enforced it in the past forced us to maintain an allowlist of all valid React DOM attributes in the bundle: ```js // ... @@ -47,18 +47,18 @@ This had two downsides: * You could not [pass a custom attribute](https://github.com/facebook/react/issues/140). This is useful for supplying browser-specific non-standard attributes, trying new DOM APIs, and integrating with opinionated third-party libraries. -* The attribute list kept growing over time, but most React canonical attribute names are already valid in the DOM. Removing most of the whitelist helped us reduce the bundle size a little bit. +* The attribute list kept growing over time, but most React canonical attribute names are already valid in the DOM. Removing most of the allowlist helped us reduce the bundle size a little bit. -With the new approach, both of these problems are solved. With React 16, you can now pass custom attributes to all HTML and SVG elements, and React doesn't have to include the whole attribute whitelist in the production version. +With the new approach, both of these problems are solved. With React 16, you can now pass custom attributes to all HTML and SVG elements, and React doesn't have to include the whole attribute allowlist in the production version. **Note that you should still use the canonical React naming for known attributes:** ```js // Yes, please -<div tabIndex="-1" /> +<div tabIndex={-1} /> // Warning: Invalid DOM property `tabindex`. Did you mean `tabIndex`? -<div tabindex="-1" /> +<div tabindex={-1} /> ``` In other words, the way you use DOM components in React hasn't changed, but now you have some new capabilities. @@ -120,7 +120,7 @@ Below is a detailed list of them. * **Known attributes with a different canonical React name:** ```js - <div tabindex="-1" /> + <div tabindex={-1} /> <div class="hi" /> ``` @@ -165,7 +165,7 @@ Below is a detailed list of them. React 15: Converts `NaN`s to strings and passes them through. React 16: Converts `NaN`s to strings and passes them through with a warning. -While testing this release, we have also [created an automatically generated table](https://github.com/facebook/react/blob/master/fixtures/attribute-behavior/AttributeTableSnapshot.md) for all known attributes to track potential regressions. +While testing this release, we have also [created an automatically generated table](https://github.com/facebook/react/blob/main/fixtures/attribute-behavior/AttributeTableSnapshot.md) for all known attributes to track potential regressions. ## Try It! {#try-it} diff --git a/content/blog/2017-11-28-react-v16.2.0-fragment-support.md b/content/blog/2017-11-28-react-v16.2.0-fragment-support.md index a8db2b0bd..6d0454a3e 100644 --- a/content/blog/2017-11-28-react-v16.2.0-fragment-support.md +++ b/content/blog/2017-11-28-react-v16.2.0-fragment-support.md @@ -127,7 +127,7 @@ render() { In React, this desugars to a `<React.Fragment/>` element, as in the example from the previous section. (Non-React frameworks that use JSX may compile to something different.) -Fragment syntax in JSX was inspired by prior art such as the `XMLList() <></>` constructor in [E4X](https://developer.mozilla.org/en-US/docs/Archive/Web/E4X/E4X_for_templating). Using a pair of empty tags is meant to represent the idea it won't add an actual element to the DOM. +Fragment syntax in JSX was inspired by prior art such as the `XMLList() <></>` constructor in [E4X](https://web.archive.org/web/20201019115828/https://developer.mozilla.org/en-US/docs/Archive/Web/E4X/E4X_for_templating). Using a pair of empty tags is meant to represent the idea it won't add an actual element to the DOM. ### Keyed Fragments {#keyed-fragments} diff --git a/content/blog/2017-12-15-improving-the-repository-infrastructure.md b/content/blog/2017-12-15-improving-the-repository-infrastructure.md index ae3cad65c..8c75e2c6f 100644 --- a/content/blog/2017-12-15-improving-the-repository-infrastructure.md +++ b/content/blog/2017-12-15-improving-the-repository-infrastructure.md @@ -215,7 +215,7 @@ To implement this, we have created a [second Jest config](https://github.com/fac This lets us run the same exact tests that we normally run against the source, but execute them using both development and production pre-built React bundles produced with Rollup and Google Closure Compiler. -Unlike the normal test run, the bundle test run depends on the build products so it is not great for quick iteration. However, it still runs on the CI server so if something breaks, the test will display as failed, and we will know it's not safe to merge into master. +Unlike the normal test run, the bundle test run depends on the build products so it is not great for quick iteration. However, it still runs on the CI server so if something breaks, the test will display as failed, and we will know it's not safe to merge into main. There are still some test files that we intentionally don't run against the bundles. Sometimes we want to mock an internal module or override a feature flag that isn't exposed to the public yet. For those cases, we blacklist a test file by renaming it from `MyModule-test.js` to `MyModule-test.internal.js`. @@ -239,7 +239,7 @@ We could get away with this because the code that touches the DOM is consolidate Still, it was hard to review DOM-related changes, and occasionally we would make mistakes. In particular, it was hard to remember all the edge cases that the code had to handle, why they were added, and when it was safe to remove them. We considered adding some automatic tests that run in the browser but we didn't want to slow down the development cycle and deal with a fragile CI. Additionally, automatic tests don't always catch DOM issues. For example, an input value displayed by the browser may not match what it reports as a DOM property. -We've chatted about this with [Brandon Dail](https://github.com/aweary), [Jason Quense](https://github.com/jquense), and [Nathan Hunzaker](https://github.com/nhunzaker). They were sending substantial patches to React DOM but were frustrated that we failed to review them timely. We decided to give them commit access, but asked them to [create a set of manual tests](https://github.com/facebook/react/pull/8589) for DOM-related areas like input management. The initial set of manual fixtures [kept growing](https://github.com/facebook/react/commits/master/fixtures/dom) over the year. +We've chatted about this with [Brandon Dail](https://github.com/aweary), [Jason Quense](https://github.com/jquense), and [Nathan Hunzaker](https://github.com/nhunzaker). They were sending substantial patches to React DOM but were frustrated that we failed to review them timely. We decided to give them commit access, but asked them to [create a set of manual tests](https://github.com/facebook/react/pull/8589) for DOM-related areas like input management. The initial set of manual fixtures [kept growing](https://github.com/facebook/react/commits/main/fixtures/dom) over the year. These fixtures are implemented as a React app located in [`fixtures/dom`](https://github.com/facebook/react/tree/d906de7f602df810c38aa622c83023228b047db6/fixtures/dom). Adding a fixture involves writing a React component with a description of the expected behavior, and links to the appropriate issues and browser quirks, like [in this example](https://github.com/facebook/react/pull/11760): @@ -350,15 +350,15 @@ We like to release updates to the open source community often. Unfortunately, th ### Branching Strategy {#branching-strategy} -Most of the time spent in the old release process was due to our branching strategy. The `master` branch was assumed to be unstable and would often contain breaking changes. Releases were done from a `stable` branch, and changes were manually cherry-picked into this branch prior to a release. We had [tooling to help automate](https://github.com/facebook/react/pull/7330) some of this process, but it was still [pretty complicated to use](https://github.com/facebook/react/blob/b5a2a1349d6e804d534f673612357c0be7e1d701/scripts/release-manager/Readme.md). +Most of the time spent in the old release process was due to our branching strategy. The `main` branch was assumed to be unstable and would often contain breaking changes. Releases were done from a `stable` branch, and changes were manually cherry-picked into this branch prior to a release. We had [tooling to help automate](https://github.com/facebook/react/pull/7330) some of this process, but it was still [pretty complicated to use](https://github.com/facebook/react/blob/b5a2a1349d6e804d534f673612357c0be7e1d701/scripts/release-manager/Readme.md). -As of version 16, we now release from the `master` branch. Experimental features and breaking changes are allowed, but must be hidden behind [feature flags](https://github.com/facebook/react/blob/cc52e06b490e0dc2482b345aa5d0d65fae931095/packages/shared/ReactFeatureFlags.js) so they can be removed during the build process. The new flat bundles and dead code elimination make it possible for us to do this without fear of leaking unwanted code into open source builds. +As of version 16, we now release from the `main` branch. Experimental features and breaking changes are allowed, but must be hidden behind [feature flags](https://github.com/facebook/react/blob/cc52e06b490e0dc2482b345aa5d0d65fae931095/packages/shared/ReactFeatureFlags.js) so they can be removed during the build process. The new flat bundles and dead code elimination make it possible for us to do this without fear of leaking unwanted code into open source builds. ### Automated Scripts {#automated-scripts} -After changing to a stable `master`, we created a new [release process checklist](https://github.com/facebook/react/issues/10620). Although much simpler than the previous process, this still involved dozens of steps and forgetting one could result in a broken release. +After changing to a stable `main`, we created a new [release process checklist](https://github.com/facebook/react/issues/10620). Although much simpler than the previous process, this still involved dozens of steps and forgetting one could result in a broken release. -To address this, we created a new [automated release process](https://github.com/facebook/react/pull/11223) that is [much easier to use](https://github.com/facebook/react/tree/master/scripts/release#react-release-script) and has several built-in checks to ensure that we release a working build. The new process is split into two steps: _build_ and _publish_. Here's what it looks like the first time you run it: +To address this, we created a new [automated release process](https://github.com/facebook/react/pull/11223) that is [much easier to use](https://github.com/facebook/react/tree/main/scripts/release#react-release-script) and has several built-in checks to ensure that we release a working build. The new process is split into two steps: _build_ and _publish_. Here's what it looks like the first time you run it: ![Release Script overview](../images/blog/release-script-build-overview.png) diff --git a/content/blog/2018-03-27-update-on-async-rendering.md b/content/blog/2018-03-27-update-on-async-rendering.md index 16ff0d6d0..cc8556f77 100644 --- a/content/blog/2018-03-27-update-on-async-rendering.md +++ b/content/blog/2018-03-27-update-on-async-rendering.md @@ -122,7 +122,7 @@ People often assume that `componentWillMount` and `componentWillUnmount` are alw For this reason, the recommended way to add listeners/subscriptions is to use the `componentDidMount` lifecycle: `embed:update-on-async-rendering/adding-event-listeners-after.js` -Sometimes it is important to update subscriptions in response to property changes. If you're using a library like Redux or MobX, the library's container component should handle this for you. For application authors, we've created a small library, [`create-subscription`](https://github.com/facebook/react/tree/master/packages/create-subscription), to help with this. We'll publish it along with React 16.3. +Sometimes it is important to update subscriptions in response to property changes. If you're using a library like Redux or MobX, the library's container component should handle this for you. For application authors, we've created a small library, [`create-subscription`](https://github.com/facebook/react/tree/main/packages/create-subscription), to help with this. We'll publish it along with React 16.3. Rather than passing a subscribable `dataSource` prop as we did in the example above, we could use `create-subscription` to pass in the subscribed value: diff --git a/content/blog/2018-06-07-you-probably-dont-need-derived-state.md b/content/blog/2018-06-07-you-probably-dont-need-derived-state.md index 6949be3ec..6223548e3 100644 --- a/content/blog/2018-06-07-you-probably-dont-need-derived-state.md +++ b/content/blog/2018-06-07-you-probably-dont-need-derived-state.md @@ -40,7 +40,7 @@ Problems arise when any of these constraints are changed. This typically comes i A common misconception is that `getDerivedStateFromProps` and `componentWillReceiveProps` are only called when props "change". These lifecycles are called any time a parent component rerenders, regardless of whether the props are "different" from before. Because of this, it has always been unsafe to _unconditionally_ override state using either of these lifecycles. **Doing so will cause state updates to be lost.** -Let’s consider an example to demonstrate the problem. Here is a `EmailInput` component that "mirrors" an email prop in state: +Let’s consider an example to demonstrate the problem. Here is an `EmailInput` component that "mirrors" an email prop in state: ```js class EmailInput extends Component { state = { email: this.props.email }; diff --git a/content/blog/2018-11-13-react-conf-recap.md b/content/blog/2018-11-13-react-conf-recap.md index 1d404fa94..dec8ab17a 100644 --- a/content/blog/2018-11-13-react-conf-recap.md +++ b/content/blog/2018-11-13-react-conf-recap.md @@ -21,6 +21,6 @@ On the morning of Day 2, Andrew Clark and Brian Vaughn presented Concurrent Rend <iframe width="560" height="315" src="https://www.youtube.com/embed/UcqRXTriUVI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> -In the afternoon, Parashuram N spoke in detail about React Native’s New Architecture, a long-term project that the React Native team has been working on over the past year and [announced in June](https://facebook.github.io/react-native/blog/2018/06/14/state-of-react-native-2018). We’re really excited about the potential of this project to improve performance, simplify interoperability with other libraries, and set a strong foundation for the future of React Native. +In the afternoon, Parashuram N spoke in detail about React Native’s New Architecture, a long-term project that the React Native team has been working on over the past year and [announced in June](https://reactnative.dev/blog/2018/06/14/state-of-react-native-2018). We’re really excited about the potential of this project to improve performance, simplify interoperability with other libraries, and set a strong foundation for the future of React Native. Now that the conference is over, all 28 conference talks are [available to stream online](https://www.youtube.com/playlist?list=PLPxbbTqCLbGE5AihOSExAa4wUM-P42EIJ). There are tons of great ones from both days. We can’t wait until next year! diff --git a/content/blog/2018-11-27-react-16-roadmap.md b/content/blog/2018-11-27-react-16-roadmap.md index 248a95647..fbb08d133 100644 --- a/content/blog/2018-11-27-react-16-roadmap.md +++ b/content/blog/2018-11-27-react-16-roadmap.md @@ -130,7 +130,7 @@ There is no documentation written for the Concurrent Mode yet. It is important t Concurrent Mode is *much* less polished than Hooks. Some APIs aren't properly "wired up" yet and don't do what they're expected to. At the time of writing this post, we don't recommend using it for anything except very early experimentation. We don't expect many bugs in Concurrent Mode itself, but note that components that produce warnings in [`<React.StrictMode>`](https://reactjs.org/docs/strict-mode.html) may not work correctly. On a separate note, we've seen that Concurrent Mode *surfaces* performance problems in other code which can sometimes be mistaken for performance issues in Concurrent Mode itself. For example, a stray `setInterval(fn, 1)` call that runs every millisecond would have a worse effect in Concurrent Mode. We plan to publish more guidance about diagnosing and fixing issues like this as part of this release's documentation. -Concurrent Mode is a big part of our vision for React. For CPU-bound work, it allows non-blocking rendering and keeps your app responsive while rendering complex component trees. That's demoed in the first part of [our JSConf Iceland talk](/blog/2018/03/01/sneak-peek-beyond-react-16.html). Concurrent Mode also makes Suspense better. It lets you avoid flickering a loading indicator if the network is fast enough. It's hard to explain without seeing so [Andrew's talk](https://www.youtube.com/watch?v=ByBPyMBTzM0) is the best resource available today. Concurrent Mode relies on a cooperative main thread [scheduler](https://github.com/facebook/react/tree/master/packages/scheduler), and we are [collaborating with the Chrome team](https://www.youtube.com/watch?v=mDdgfyRB5kg) to eventually move this functionality into the browser itself. +Concurrent Mode is a big part of our vision for React. For CPU-bound work, it allows non-blocking rendering and keeps your app responsive while rendering complex component trees. That's demoed in the first part of [our JSConf Iceland talk](/blog/2018/03/01/sneak-peek-beyond-react-16.html). Concurrent Mode also makes Suspense better. It lets you avoid flickering a loading indicator if the network is fast enough. It's hard to explain without seeing so [Andrew's talk](https://www.youtube.com/watch?v=ByBPyMBTzM0) is the best resource available today. Concurrent Mode relies on a cooperative main thread [scheduler](https://github.com/facebook/react/tree/main/packages/scheduler), and we are [collaborating with the Chrome team](https://www.youtube.com/watch?v=mDdgfyRB5kg) to eventually move this functionality into the browser itself. **Status in React DOM:** A *very* unstable version of Concurrent Mode is available behind an `unstable_` prefix in React 16.6 but we don't recommend trying it unless you're willing to often run into walls or missing features. The 16.7 alphas include `React.ConcurrentMode` and `ReactDOM.createRoot` without an `unstable_` prefix, but we'll likely keep the prefix in 16.7, and only document and mark Concurrent Mode as stable in this future minor release. @@ -175,7 +175,7 @@ function App() { // provide Suspense integrations with similar APIs. ``` -There is no official documentation for how to fetch data with Suspense yet, but you can find some early information in [this talk](https://youtu.be/ByBPyMBTzM0?t=1312) and [this small demo](https://github.com/facebook/react/blob/master/packages/react-devtools/CHANGELOG.md#suspense-toggle). We'll write documentation for React Cache (and how to write your own Suspense-compatible library) closer to this React release, but if you're curious, you can find its very early source code [here](https://github.com/facebook/react/blob/master/packages/react-cache/src/ReactCache.js). +There is no official documentation for how to fetch data with Suspense yet, but you can find some early information in [this talk](https://youtu.be/ByBPyMBTzM0?t=1312) and [this small demo](https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md#suspense-toggle). We'll write documentation for React Cache (and how to write your own Suspense-compatible library) closer to this React release, but if you're curious, you can find its very early source code [here](https://github.com/facebook/react/blob/main/packages/react-cache/src/ReactCache.js). The low-level Suspense mechanism (suspending rendering and showing a fallback) is expected to be stable even in React 16.6. We've used it for code splitting in production for months. However, the higher-level APIs for data fetching are very unstable. React Cache is rapidly changing, and will change at least a few more times. There are some low-level APIs that are [missing](https://github.com/reactjs/rfcs/pull/89) for a good higher-level API to be possible. We don't recommend using React Cache anywhere except very early experiments. Note that React Cache itself isn't strictly tied to React releases, but the current alphas lack basic features as cache invalidation, and you'll run into a wall very soon. We expect to have something usable with this React release. diff --git a/content/blog/2019-02-23-is-react-translated-yet.md b/content/blog/2019-02-23-is-react-translated-yet.md index f88698a66..66e410ff5 100644 --- a/content/blog/2019-02-23-is-react-translated-yet.md +++ b/content/blog/2019-02-23-is-react-translated-yet.md @@ -41,7 +41,7 @@ This approach appealed to us for several reasons: We started off with an initial trial period of three languages: Spanish, Japanese, and Simplified Chinese. This allowed us to work out any kinks in our process and make sure future translations are set up for success. I wanted to give the translation teams freedom to choose whatever tools they felt comfortable with. The only requirement is a [checklist](https://github.com/reactjs/reactjs.org-translation/blob/master/PROGRESS.template.md) that outlines the order of importance for translating pages. -After the trial period, we were ready to accept more languages. I created [a script](https://github.com/reactjs/reactjs.org-translation/blob/master/scripts/create.js) to automate the creation of the new language repo, and a site, [Is React Translated Yet?](https://isreacttranslatedyet.com), to track progress on the different translations. We started *10* new translations on our first day alone! +After the trial period, we were ready to accept more languages. I created [a script](https://github.com/reactjs/reactjs.org-translation/blob/master/scripts/create.js) to automate the creation of the new language repo, and a site, [Is React Translated Yet?](https://translations.reactjs.org), to track progress on the different translations. We started *10* new translations on our first day alone! Because of the automation, the rest of the maintenance went mostly smoothly. We eventually created a [Slack channel](https://rt-slack-invite.herokuapp.com) to make it easier for translators to share information, and I released a guide solidifying the [responsibilities of maintainers](https://github.com/reactjs/reactjs.org-translation/blob/master/maintainer-guide.md). Allowing translators to talk with each other was a great boon -- for example, the Arabic, Persian, and Hebrew translations were able to talk to each other in order to get [right-to-left text](https://en.wikipedia.org/wiki/Right-to-left) working! @@ -55,7 +55,7 @@ Creating the [sync script](https://github.com/reactjs/reactjs.org-translation/bl The problem was finding a place for the bot to run. I'm a frontend developer for a reason -- Heroku and its ilk are alien to me and *endlessly* frustrating. In fact, until this past Tuesday, I was running the script by hand on my local machine! -The biggest challenge was space. Each fork of the repo is around 100MB -- which takes minutes to clone on my local machine. We have *32* forks, and the free tiers or most deployment platforms I checked limited you to 512MB of storage. +The biggest challenge was space. Each fork of the repo is around 100MB -- which takes minutes to clone on my local machine. We have *32* forks, and the free tiers of most deployment platforms I checked limited you to 512MB of storage. After lots of notepad calculations, I found a solution: delete each repo once we've finished the script and limit the concurrency of `sync` scripts that run at once to be within the storage requirements. Luckily, Heroku dynos have a much faster Internet connection and are able to clone even the React repo quickly. diff --git a/content/blog/2019-08-08-react-v16.9.0.md b/content/blog/2019-08-08-react-v16.9.0.md index 9442f2b40..aa2adcd95 100644 --- a/content/blog/2019-08-08-react-v16.9.0.md +++ b/content/blog/2019-08-08-react-v16.9.0.md @@ -149,7 +149,7 @@ These estimates were too optimistic, and we've needed to adjust them. **tldr:** We shipped Hooks on time, but we're regrouping Concurrent Mode and Suspense for Data Fetching into a single release that we intend to release later this year. -In February, we [shipped a stable 16.8 release](/blog/2019/02/06/react-v16.8.0.html) including React Hooks, with React Native support coming [a month later](https://facebook.github.io/react-native/blog/2019/03/12/releasing-react-native-059). However, we underestimated the follow-up work for this release, including the lint rules, developer tools, examples, and more documentation. This shifted the timeline by a few months. +In February, we [shipped a stable 16.8 release](/blog/2019/02/06/react-v16.8.0.html) including React Hooks, with React Native support coming [a month later](https://reactnative.dev/blog/2019/03/12/releasing-react-native-059). However, we underestimated the follow-up work for this release, including the lint rules, developer tools, examples, and more documentation. This shifted the timeline by a few months. Now that React Hooks are rolled out, the work on Concurrent Mode and Suspense for Data Fetching is in full swing. The [new Facebook website that's currently in active development](https://twitter.com/facebook/status/1123322299418124289) is built on top of these features. Testing them with real code helped discover and address many issues before they can affect the open source users. Some of these fixes involved an internal redesign of these features, which has also caused the timeline to slip. diff --git a/content/blog/2019-08-15-new-react-devtools.md b/content/blog/2019-08-15-new-react-devtools.md index b9f4b2eee..84ac0ac73 100644 --- a/content/blog/2019-08-15-new-react-devtools.md +++ b/content/blog/2019-08-15-new-react-devtools.md @@ -12,7 +12,7 @@ It also offers full support for React Hooks, including inspecting nested objects ![DevTools version 4 screenshot](../images/blog/devtools-v4-screenshot.png) -[Visit the interactive tutorial](https://react-devtools-tutorial.now.sh/) to try out the new version or [see the changelog](https://github.com/facebook/react/blob/master/packages/react-devtools/CHANGELOG.md#400-august-15-2019) for demo videos and more details. +[Visit the interactive tutorial](https://react-devtools-tutorial.now.sh/) to try out the new version or [see the changelog](https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md#400-august-15-2019) for demo videos and more details. ## Which versions of React are supported? {#which-versions-of-react-are-supported} @@ -23,8 +23,8 @@ It also offers full support for React Hooks, including inspecting nested objects * `16.x`: Supported **`react-native`** -* `0`-`0.61`: Not supported -* `0.62`: Will be supported (when 0.62 is released) +* `0`-`0.61.x`: Not supported +* `0.62`: Supported ## How do I get the new DevTools? {#how-do-i-get-the-new-devtools} diff --git a/content/blog/2019-10-22-react-release-channels.md b/content/blog/2019-10-22-react-release-channels.md index c52c98c7d..2b65f752b 100644 --- a/content/blog/2019-10-22-react-release-channels.md +++ b/content/blog/2019-10-22-react-release-channels.md @@ -20,8 +20,8 @@ We would like to make it even easier for developers to test prerelease builds of Each of React's release channels is designed for a distinct use case: - [**Latest**](#latest-channel) is for stable, semver React releases. It's what you get when you install React from npm. This is the channel you're already using today. **Use this for all user-facing React applications.** -- [**Next**](#next-channel) tracks the master branch of the React source code repository. Think of these as release candidates for the next minor semver release. Use this for integration testing between React and third party projects. -- [**Experimental**](#experimental-channel) includes experimental APIs and features that aren't available in the stable releases. These also track the master branch, but with additional feature flags turned on. Use this to try out upcoming features before they are released. +- [**Next**](#next-channel) tracks the main branch of the React source code repository. Think of these as release candidates for the next minor semver release. Use this for integration testing between React and third party projects. +- [**Experimental**](#experimental-channel) includes experimental APIs and features that aren't available in the stable releases. These also track the main branch, but with additional feature flags turned on. Use this to try out upcoming features before they are released. All releases are published to npm, but only Latest uses [semantic versioning](/docs/faq-versioning.html). Prereleases (those in the Next and Experimental channels) have versions generated from a hash of their contents, e.g. `0.0.0-1022ee0ec` for Next and `0.0.0-experimental-1022ee0ec` for Experimental. @@ -39,7 +39,7 @@ You can expect updates to Latest to be extremely stable. Versions follow the sem ### Next Channel {#next-channel} -The Next channel is a prerelease channel that tracks the master branch of the React repository. We use prereleases in the Next channel as release candidates for the Latest channel. You can think of Next as a superset of Latest that is updated more frequently. +The Next channel is a prerelease channel that tracks the main branch of the React repository. We use prereleases in the Next channel as release candidates for the Latest channel. You can think of Next as a superset of Latest that is updated more frequently. The degree of change between the most recent Next release and the most recent Latest release is approximately the same as you would find between two minor semver releases. However, **the Next channel does not conform to semantic versioning.** You should expect occasional breaking changes between successive releases in the Next channel. @@ -58,13 +58,13 @@ If you're the author of a third party React framework, library, developer tool, - Set up a cron job using your preferred continuous integration platform. Cron jobs are supported by both [CircleCI](https://circleci.com/docs/2.0/triggers/#scheduled-builds) and [Travis CI](https://docs.travis-ci.com/user/cron-jobs/). - In the cron job, update your React packages to the most recent React release in the Next channel, using `next` tag on npm. Using the npm cli: - ``` + ```console npm update react@next react-dom@next ``` Or yarn: - ``` + ```console yarn upgrade react@next react-dom@next ``` - Run your test suite against the updated packages. @@ -75,7 +75,7 @@ A project that uses this workflow is Next.js. (No pun intended! Seriously!) You ### Experimental Channel {#experimental-channel} -Like Next, the Experimental channel is a prerelease channel that tracks the master branch of the React repository. Unlike Next, Experimental releases include additional features and APIs that are not ready for wider release. +Like Next, the Experimental channel is a prerelease channel that tracks the main branch of the React repository. Unlike Next, Experimental releases include additional features and APIs that are not ready for wider release. Usually, an update to Next is accompanied by a corresponding update to Experimental. They are based on the same source revision, but are built using a different set of feature flags. @@ -99,4 +99,4 @@ If a feature is not documented, they may be accompanied by an [RFC](https://gith We will post to the React blog when we're ready to announce new experiments, but that doesn't mean we will publicize every experiment. -You can always refer to our public GitHub repository's [history](https://github.com/facebook/react/commits/master) for a comprehensive list of changes. +You can always refer to our public GitHub repository's [history](https://github.com/facebook/react/commits/main) for a comprehensive list of changes. diff --git a/content/blog/2020-02-26-react-v16.13.0.md b/content/blog/2020-02-26-react-v16.13.0.md index a893093c8..3a1700952 100644 --- a/content/blog/2020-02-26-react-v16.13.0.md +++ b/content/blog/2020-02-26-react-v16.13.0.md @@ -21,7 +21,7 @@ Warning: Cannot update a component from inside the function body of a different **This warning will help you find application bugs caused by unintentional state changes.** In the rare case that you intentionally want to change the state of another component as a result of rendering, you can wrap the `setState` call into `useEffect`. -### Warnings for conflicting style rules +### Warnings for conflicting style rules {#warnings-for-conflicting-style-rules} When dynamically applying a `style` that contains longhand and shorthand versions of CSS properties, particular combinations of updates can cause inconsistent styling. For example: @@ -34,7 +34,7 @@ When dynamically applying a `style` that contains longhand and shorthand version </div> ``` -You might expect this `<div>` to always have a red background, no matter the value of `toggle`. However, on alternating the value of `toggle` between `true` and `false`, the background color start as `red`, then alternates between `transparent` and `blue`, [as you can see in this demo](https://codesandbox.io/s/suspicious-sunset-g3jub). +You might expect this `<div>` to always have a red background, no matter the value of `toggle`. However, on alternating the value of `toggle` between `true` and `false`, the background color start as `red`, then alternates between `transparent` and `blue`, [as you can see in this demo](https://codesandbox.io/s/serene-dijkstra-dr0vev). **React now detects conflicting style rules and logs a warning.** To fix the issue, don't mix shorthand and longhand versions of the same CSS property in the `style` prop. @@ -204,6 +204,6 @@ Refer to the documentation for [detailed installation instructions](/docs/instal - Adjust `SuspenseList` CPU bound heuristic ([@sebmarkbage](https://github.com/sebmarkbage) in [#17455](https://github.com/facebook/react/pull/17455)) - Add missing event plugin priorities ([@trueadm](https://github.com/trueadm) in [#17914](https://github.com/facebook/react/pull/17914)) - Fix `isPending` only being true when transitioning from inside an input event ([@acdlite](https://github.com/acdlite) in [#17382](https://github.com/facebook/react/pull/17382)) -- Fix `React.memo` components dropping updates when interrupted by a higher priority update ([@acdlite](<(https://github.com/acdlite)>) in [#18091](https://github.com/facebook/react/pull/18091)) +- Fix `React.memo` components dropping updates when interrupted by a higher priority update ([@acdlite](https://github.com/acdlite) in [#18091](https://github.com/facebook/react/pull/18091)) - Don't warn when suspending at the wrong priority ([@gaearon](https://github.com/gaearon) in [#17971](https://github.com/facebook/react/pull/17971)) - Fix a bug with rebasing updates ([@acdlite](https://github.com/acdlite) and [@sebmarkbage](https://github.com/sebmarkbage) in [#17560](https://github.com/facebook/react/pull/17560), [#17510](https://github.com/facebook/react/pull/17510), [#17483](https://github.com/facebook/react/pull/17483), [#17480](https://github.com/facebook/react/pull/17480)) diff --git a/content/blog/2020-08-10-react-v17-rc.md b/content/blog/2020-08-10-react-v17-rc.md new file mode 100644 index 000000000..c6abdd2ee --- /dev/null +++ b/content/blog/2020-08-10-react-v17-rc.md @@ -0,0 +1,376 @@ +--- +title: "React v17.0 Release Candidate: No New Features" +author: [gaearon,rachelnabors] +--- + +Today, we are publishing the first Release Candidate for React 17. It has been two and a half years since [the previous major release of React](/blog/2017/09/26/react-v16.0.html), which is a long time even by our standards! In this blog post, we will describe the role of this major release, what changes you can expect in it, and how you can try this release. + +## No New Features {#no-new-features} + +The React 17 release is unusual because it doesn't add any new developer-facing features. Instead, this release is primarily focused on **making it easier to upgrade React itself**. + +We're actively working on the new React features, but they're not a part of this release. The React 17 release is a key part of our strategy to roll them out without leaving anyone behind. + +In particular, **React 17 is a "stepping stone" release** that makes it safer to embed a tree managed by one version of React inside a tree managed by a different version of React. + +## Gradual Upgrades {#gradual-upgrades} + +For the past seven years, React upgrades have been "all-or-nothing". Either you stay on an old version, or you upgrade your whole app to a new version. There was no in-between. + +This has worked out so far, but we are running into the limits of the "all-or-nothing" upgrade strategy. Some API changes, for example, deprecating the [legacy context API](/docs/legacy-context.html), are impossible to do in an automated way. Even though most apps written today don't ever use them, we still support them in React. We have to choose between supporting them in React indefinitely or leaving some apps behind on an old version of React. Both of these options aren't great. + +So we wanted to provide another option. + +**React 17 enables gradual React upgrades.** When you upgrade from React 15 to 16 (or, soon, from React 16 to 17), you would usually upgrade your whole app at once. This works well for many apps. But it can get increasingly challenging if the codebase was written more than a few years ago and isn't actively maintained. And while it's possible to use two versions of React on the page, until React 17 this has been fragile and caused problems with events. + +We're fixing many of those problems with React 17. This means that **when React 18 and the next future versions come out, you will now have more options**. The first option will be to upgrade your whole app at once, like you might have done before. But you will also have an option to upgrade your app piece by piece. For example, you might decide to migrate most of your app to React 18, but keep some lazy-loaded dialog or a subroute on React 17. + +This doesn't mean you *have to* do gradual upgrades. For most apps, upgrading all at once is still the best solution. Loading two versions of React -- even if one of them is loaded lazily on demand -- is still not ideal. However, for larger apps that aren't actively maintained, this option may make sense to consider, and React 17 enables those apps to not get left behind. + +To enable gradual updates, we've needed to make some changes to the React event system. React 17 is a major release because these changes are potentially breaking. In practice, we've only had to change fewer than twenty components out of 100,000+ so **we expect that most apps can upgrade to React 17 without too much trouble**. [Tell us](https://github.com/facebook/react/issues) if you run into problems. + +### Demo of Gradual Upgrades {#demo-of-gradual-upgrades} + +We've prepared an [example repository](https://github.com/reactjs/react-gradual-upgrade-demo/) demonstrating how to lazy-load an older version of React if necessary. This demo uses Create React App, but it should be possible to follow a similar approach with any other tool. We welcome demos using other tooling as pull requests. + +>Note +> +>We've **postponed other changes** until after React 17. The goal of this release is to enable gradual upgrades. If upgrading to React 17 were too difficult, it would defeat its purpose. + +## Changes to Event Delegation {#changes-to-event-delegation} + +Technically, it has always been possible to nest apps developed with different versions of React. However, it was rather fragile because of how the React event system worked. + + +In React components, you usually write event handlers inline: + +```js +<button onClick={handleClick}> +``` + +The vanilla DOM equivalent to this code is something like: + +```js +myButton.addEventListener('click', handleClick); +``` + +However, for most events, React doesn't actually attach them to the DOM nodes on which you declare them. Instead, React attaches one handler per event type directly at the `document` node. This is called [event delegation](https://davidwalsh.name/event-delegate). In addition to its performance benefits on large application trees, it also makes it easier to add new features like [replaying events](https://twitter.com/dan_abramov/status/1200118229697486849). + +React has been doing event delegation automatically since its first release. When a DOM event fires on the document, React figures out which component to call, and then the React event "bubbles" upwards through your components. But behind the scenes, the native event has already bubbled up to the `document` level, where React installs its event handlers. + +However, this is a problem for gradual upgrades. + +If you have multiple React versions on the page, they all register event handlers at the top. This breaks `e.stopPropagation()`: if a nested tree has stopped propagation of an event, the outer tree would still receive it. This made it difficult to nest different versions of React. This concern is not hypothetical -- for example, the Atom editor [ran into this](https://github.com/facebook/react/pull/8117) four years ago. + +This is why we're changing how React attaches events to the DOM under the hood. + +**In React 17, React will no longer attach event handlers at the `document` level. Instead, it will attach them to the root DOM container into which your React tree is rendered:** + +```js +const rootNode = document.getElementById('root'); +ReactDOM.render(<App />, rootNode); +``` + +In React 16 and earlier, React would do `document.addEventListener()` for most events. React 17 will call `rootNode.addEventListener()` under the hood instead. + +![A diagram showing how React 17 attaches events to the roots rather than to the document](../images/blog/react-v17-rc/react_17_delegation.png) + +Thanks to this change, **it is now safer to embed a React tree managed by one version inside a tree managed by a different React version**. Note that for this to work, both of the versions would need to be 17 or higher, which is why upgrading to React 17 is important. In a way, React 17 is a "stepping stone" release that makes next gradual upgrades feasible. + +This change also **makes it easier to embed React into apps built with other technologies**. For example, if the outer "shell" of your app is written in jQuery, but the newer code inside of it is written with React, `e.stopPropagation()` inside the React code would now prevent it from reaching the jQuery code -- as you would expect. This also works in the other direction. If you no longer like React and want to rewrite your app -- for example, in jQuery -- you can start converting the outer shell from React to jQuery without breaking the event propagation. + +We've confirmed that [numerous](https://github.com/facebook/react/issues/7094) [problems](https://github.com/facebook/react/issues/8693) [reported](https://github.com/facebook/react/issues/12518) [over](https://github.com/facebook/react/issues/13451) [the](https://github.com/facebook/react/issues/4335) [years](https://github.com/facebook/react/issues/1691) [on](https://github.com/facebook/react/issues/285#issuecomment-253502585) [our](https://github.com/facebook/react/pull/8117) [issue](https://github.com/facebook/react/issues/11530) [tracker](https://github.com/facebook/react/issues/7128) related to integrating React with non-React code have been fixed by the new behavior. + +>Note +> +>You might be wondering whether this breaks [Portals](/docs/portals.html) outside of the root container. The answer is that React *also* listens to events on portal containers, so this is not an issue. + +#### Fixing Potential Issues {#fixing-potential-issues} + +As with any breaking change, it is likely some code would need to be adjusted. At Facebook, we had to adjust about 10 modules in total (out of many thousands) to work with this change. + +For example, if you add manual DOM listeners with `document.addEventListener(...)`, you might expect them to catch all React events. In React 16 and earlier, even if you call `e.stopPropagation()` in a React event handler, your custom `document` listeners would still receive them because the native event is *already* at the document level. With React 17, the propagation *would* stop (as requested!), so your `document` handlers would not fire: + +```js +document.addEventListener('click', function() { + // This custom handler will no longer receive clicks + // from React components that called e.stopPropagation() +}); +``` + +You can fix code like this by converting your listener to use the [capture phase](https://javascript.info/bubbling-and-capturing#capturing). To do this, you can pass `{ capture: true }` as the third argument to `document.addEventListener`: + +```js +document.addEventListener('click', function() { + // Now this event handler uses the capture phase, + // so it receives *all* click events below! +}, { capture: true }); +``` + +Note how this strategy is more resilient overall -- for example, it will probably fix existing bugs in your code that happen when `e.stopPropagation()` is called outside of a React event handler. In other words, **event propagation in React 17 works closer to the regular DOM**. + +## Other Breaking Changes {#other-breaking-changes} + +We've kept the breaking changes in React 17 to the minimum. For example, it doesn't remove any of the methods that have been deprecated in the previous releases. However, it does include a few other breaking changes that have been relatively safe in our experience. In total, we've had to adjust fewer than 20 out of 100,000+ our components because of them. + +### Aligning with Browsers {#aligning-with-browsers} + +We've made a couple of smaller changes related to the event system: + +* The `onScroll` event **no longer bubbles** to prevent [common confusion](https://github.com/facebook/react/issues/15723). +* React `onFocus` and `onBlur` events have switched to using the native `focusin` and `focusout` events under the hood, which more closely match React's existing behavior and sometimes provide extra information. +* Capture phase events (e.g. `onClickCapture`) now use real browser capture phase listeners. + +These changes align React closer with the browser behavior and improve interoperability. + +>Note +> +>Although React 17 switched from `focus` to `focusin` *under the hood* for the `onFocus` event, note that this has **not** affected the bubbling behavior. In React, `onFocus` event has always bubbled, and it continues to do so in React 17 because generally it is a more useful default. See [this sandbox](https://codesandbox.io/s/strange-albattani-7tqr7?file=/src/App.js) for the different checks you can add for different particular use cases. + +### No Event Pooling {#no-event-pooling} + +React 17 removes the "event pooling" optimization from React. It doesn't improve performance in modern browsers and confuses even experienced React users: + +```js +function handleChange(e) { + setData(data => ({ + ...data, + // This crashes in React 16 and earlier: + text: e.target.value + })); +} +``` + +This is because React reused the event objects between different events for performance in old browsers, and set all event fields to `null` in between them. With React 16 and earlier, you have to call `e.persist()` to properly use the event, or read the property you need earlier. + +**In React 17, this code works as you would expect. The old event pooling optimization has been fully removed, so you can read the event fields whenever you need them.** + +This is a behavior change, which is why we're marking it as breaking, but in practice we haven't seen it break anything at Facebook. (Maybe it even fixed a few bugs!) Note that `e.persist()` is still available on the React event object, but now it doesn't do anything. + +### Effect Cleanup Timing {#effect-cleanup-timing} + +We are making the timing of the `useEffect` cleanup function more consistent. + +```js{3-5} +useEffect(() => { + // This is the effect itself. + return () => { + // This is its cleanup. + }; +}); +``` + +Most effects don't need to delay screen updates, so React runs them asynchronously soon after the update has been reflected on the screen. (For the rare cases where you need an effect to block paint, e.g. to measure and position a tooltip, prefer `useLayoutEffect`.) + +However, when a component is unmounting, effect *cleanup* functions used to run synchronously (similar to `componentWillUnmount` being synchronous in classes). We've found that this is not ideal for larger apps because it slows down large screen transitions (e.g. switching tabs). + +**In React 17, the effect cleanup function always runs asynchronously -- for example, if the component is unmounting, the cleanup runs _after_ the screen has been updated.** + +This mirrors how the effects themselves run more closely. In the rare cases where you might want to rely on the synchronous execution, you can switch to `useLayoutEffect` instead. + +>Note +> +>You might be wondering whether this means that you'll now be unable to fix warnings about `setState` on unmounted components. Don't worry -- React specifically checks for this case, and does *not* fire `setState` warnings in the short gap between unmounting and the cleanup. **So code cancelling requests or intervals can almost always stay the same.** + +Additionally, React 17 will always execute all effect cleanup functions (for all components) before it runs any new effects. React 16 only guaranteed this ordering for effects within a component. + +#### Potential Issues {#potential-issues} + +We've only seen a couple of components break with this change, although reusable libraries may need to test it more thoroughly. One example of problematic code may look like this: + +```js +useEffect(() => { + someRef.current.someSetupMethod(); + return () => { + someRef.current.someCleanupMethod(); + }; +}); +``` + +The problem is that `someRef.current` is mutable, so by the time the cleanup function runs, it may have been set to `null`. The solution is to capture any mutable values *inside* the effect: + +```js +useEffect(() => { + const instance = someRef.current; + instance.someSetupMethod(); + return () => { + instance.someCleanupMethod(); + }; +}); +``` + +We don't expect this to be a common problem because [our `eslint-plugin-react-hooks/exhaustive-deps` lint rule](https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks) (make sure you use it!) has always warned about this. + +### Consistent Errors for Returning Undefined {#consistent-errors-for-returning-undefined} + +In React 16 and earlier, returning `undefined` has always been an error: + +```js +function Button() { + return; // Error: Nothing was returned from render +} +``` + +This is in part because it’s easy to return `undefined` unintentionally: + +```js +function Button() { + // We forgot to write return, so this component returns undefined. + // React surfaces this as an error instead of ignoring it. + <button />; +} +``` + +Previously, React only did this for class and function components, but did not check the return values of `forwardRef` and `memo` components. This was due to a coding mistake. + +**In React 17, the behavior for `forwardRef` and `memo` components is consistent with regular function and class components. Returning `undefined` from them is an error.** + +```js +let Button = forwardRef(() => { + // We forgot to write return, so this component returns undefined. + // React 17 surfaces this as an error instead of ignoring it. + <button />; +}); + +let Button = memo(() => { + // We forgot to write return, so this component returns undefined. + // React 17 surfaces this as an error instead of ignoring it. + <button />; +}); +``` + +For the cases where you want to render nothing intentionally, return `null` instead. + +### Native Component Stacks {#native-component-stacks} + +When you throw an error in the browser, the browser gives you a stack trace with JavaScript function names and their locations. However, JavaScript stacks are often not enough to diagnose a problem because the React tree hierarchy can be just as important. You want to know not just that a `Button` threw an error, but *where in the React tree* that `Button` is. + +To solve this, React 16 started printing "component stacks" when you have an error. Still, they used to be inferior to the native JavaScript stacks. In particular, they were not clickable in the console because React didn't know where the function was declared in the source code. Additionally, they were [mostly useless in production](https://github.com/facebook/react/issues/12757). Unlike regular minified JavaScript stacks which can be automatically restored to the original function names with a sourcemap, with React component stacks you had to choose between production stacks and bundle size. + +**In React 17, the component stacks are generated using a different mechanism that stitches them together from the regular native JavaScript stacks. This lets you get the fully symbolicated React component stack traces in a production environment.** + +The way React implements this is somewhat unorthodox. Currently, the browsers don't provide a way to get a function's stack frame (source file and location). So when React catches an error, it will now *reconstruct* its component stack by throwing (and catching) a temporary error from inside each of the components above, when it is possible. This adds a small performance penalty for crashes, but it only happens once per component type. + +If you're curious, you can read more details in [this pull request](https://github.com/facebook/react/pull/18561), but for the most part this exact mechanism shouldn't affect your code. From your perspective, the new feature is that component stacks are now clickable (because they rely on the native browser stack frames), and that you can decode them in production like you would with regular JavaScript errors. + +The part that constitutes a breaking change is that for this to work, React re-executes parts of some of the React functions and React class constructors above in the stack after an error is captured. Since render functions and class constructors shouldn't have side effects (which is also important for server rendering), this should not pose any practical problems. + +### Removing Private Exports {#removing-private-exports} + +Finally, the last notable breaking change is that we've removed some React internals that were previously exposed to other projects. In particular, [React Native for Web](https://github.com/necolas/react-native-web) used to depend on some internals of the event system, but that dependency was fragile and used to break. + +**In React 17, these private exports have been removed. As far as we're aware, React Native for Web was the only project using them, and they have already completed a migration to a different approach that doesn't depend on those private exports.** + +This means that the older versions of React Native for Web won't be compatible with React 17, but the newer versions will work with it. In practice, this doesn't change much because React Native for Web had to release new versions to adapt to internal React changes anyway. + +Additionally, we've removed the `ReactTestUtils.SimulateNative` helper methods. They have never been documented, didn't do quite what their names implied, and didn't work with the changes we've made to the event system. If you want a convenient way to fire native browser events in tests, check out the [React Testing Library](https://testing-library.com/docs/dom-testing-library/api-events) instead. + +## Installation {#installation} + +We encourage you to try React 17.0 Release Candidate soon and [raise any issues](https://github.com/facebook/react/issues) for the problems you might encounter in the migration. **Keep in mind that a release candidate is more likely to contain bugs than a stable release, so don't deploy it to production yet.** + +To install React 17 RC with npm, run: + +```bash +npm install react@17.0.0-rc.3 react-dom@17.0.0-rc.3 +``` + +To install React 17 RC with Yarn, run: + +```bash +yarn add react@17.0.0-rc.3 react-dom@17.0.0-rc.3 +``` + +We also provide UMD builds of React via a CDN: + +```html +<script crossorigin src="https://unpkg.com/react@17.0.0-rc.3/umd/react.production.min.js"></script> +<script crossorigin src="https://unpkg.com/react-dom@17.0.0-rc.3/umd/react-dom.production.min.js"></script> +``` + +Refer to the documentation for [detailed installation instructions](/docs/installation.html). + +## Changelog {#changelog} + +### React {#react} + +* Add `react/jsx-runtime` and `react/jsx-dev-runtime` for the [new JSX transform](https://babeljs.io/blog/2020/03/16/7.9.0#a-new-jsx-transform-11154-https-githubcom-babel-babel-pull-11154). ([@lunaruan](https://github.com/lunaruan) in [#18299](https://github.com/facebook/react/pull/18299)) +* Build component stacks from native error frames. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18561](https://github.com/facebook/react/pull/18561)) +* Allow to specify `displayName` on context for improved stacks. ([@eps1lon](https://github.com/eps1lon) in [#18224](https://github.com/facebook/react/pull/18224)) +* Prevent `'use strict'` from leaking in the UMD bundles. ([@koba04](https://github.com/koba04) in [#19614](https://github.com/facebook/react/pull/19614)) +* Stop using `fb.me` for redirects. ([@cylim](https://github.com/cylim) in [#19598](https://github.com/facebook/react/pull/19598)) + +### React DOM {#react-dom} + +* Delegate events to roots instead of `document`. ([@trueadm](https://github.com/trueadm) in [#18195](https://github.com/facebook/react/pull/18195) and [others](https://github.com/facebook/react/pulls?q=is%3Apr+author%3Atrueadm+modern+event+is%3Amerged)) +* Clean up all effects before running any next effects. ([@bvaughn](https://github.com/bvaughn) in [#17947](https://github.com/facebook/react/pull/17947)) +* Run `useEffect` cleanup functions asynchronously. ([@bvaughn](https://github.com/bvaughn) in [#17925](https://github.com/facebook/react/pull/17925)) +* Use browser `focusin` and `focusout` for `onFocus` and `onBlur`. ([@trueadm](https://github.com/trueadm) in [#19186](https://github.com/facebook/react/pull/19186)) +* Make all `Capture` events use the browser capture phase. ([@trueadm](https://github.com/trueadm) in [#19221](https://github.com/facebook/react/pull/19221)) +* Don't emulate bubbling of the `onScroll` event. ([@gaearon](https://github.com/gaearon) in [#19464](https://github.com/facebook/react/pull/19464)) +* Throw if `forwardRef` or `memo` component returns `undefined`. ([@gaearon](https://github.com/gaearon) in [#19550](https://github.com/facebook/react/pull/19550)) +* Remove event pooling. ([@trueadm](https://github.com/trueadm) in [#18969](https://github.com/facebook/react/pull/18969)) +* Stop exposing internals that won’t be needed by React Native Web. ([@necolas](https://github.com/necolas) in [#18483](https://github.com/facebook/react/pull/18483)) +* Attach all known event listeners when the root mounts. ([@gaearon](https://github.com/gaearon) in [#19659](https://github.com/facebook/react/pull/19659)) +* Disable `console` in the second render pass of DEV mode double render. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18547](https://github.com/facebook/react/pull/18547)) +* Deprecate the undocumented and misleading `ReactTestUtils.SimulateNative` API. ([@gaearon](https://github.com/gaearon) in [#13407](https://github.com/facebook/react/pull/13407)) +* Rename private field names used in the internals. ([@gaearon](https://github.com/gaearon) in [#18377](https://github.com/facebook/react/pull/18377)) +* Don't call User Timing API in development. ([@gaearon](https://github.com/gaearon) in [#18417](https://github.com/facebook/react/pull/18417)) +* Disable console during the repeated render in Strict Mode. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18547](https://github.com/facebook/react/pull/18547)) +* In Strict Mode, double-render components without Hooks too. ([@eps1lon](https://github.com/eps1lon) in [#18430](https://github.com/facebook/react/pull/18430)) +* Allow calling `ReactDOM.flushSync` during lifecycle methods (but warn). ([@sebmarkbage](https://github.com/sebmarkbage) in [#18759](https://github.com/facebook/react/pull/18759)) +* Add the `code` property to the keyboard event objects. ([@bl00mber](https://github.com/bl00mber) in [#18287](https://github.com/facebook/react/pull/18287)) +* Add the `disableRemotePlayback` property for `video` elements. ([@tombrowndev](https://github.com/tombrowndev) in [#18619](https://github.com/facebook/react/pull/18619)) +* Add the `enterKeyHint` property for `input` elements. ([@eps1lon](https://github.com/eps1lon) in [#18634](https://github.com/facebook/react/pull/18634)) +* Warn when no `value` is provided to `<Context.Provider>`. ([@charlie1404](https://github.com/charlie1404) in [#19054](https://github.com/facebook/react/pull/19054)) +* Warn when `memo` or `forwardRef` components return `undefined`. ([@bvaughn](https://github.com/bvaughn) in [#19550](https://github.com/facebook/react/pull/19550)) +* Improve the error message for invalid updates. ([@JoviDeCroock](https://github.com/JoviDeCroock) in [#18316](https://github.com/facebook/react/pull/18316)) +* Exclude forwardRef and memo from stack frames. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18559](https://github.com/facebook/react/pull/18559)) +* Improve the error message when switching between controlled and uncontrolled inputs. ([@vcarl](https://github.com/vcarl) in [#17070](https://github.com/facebook/react/pull/17070)) +* Keep `onTouchStart`, `onTouchMove`, and `onWheel` passive. ([@gaearon](https://github.com/gaearon) in [#19654](https://github.com/facebook/react/pull/19654)) +* Fix `setState` hanging in development inside a closed iframe. ([@gaearon](https://github.com/gaearon) in [#19220](https://github.com/facebook/react/pull/19220)) +* Fix rendering bailout for lazy components with `defaultProps`. ([@jddxf](https://github.com/jddxf) in [#18539](https://github.com/facebook/react/pull/18539)) +* Fix a false positive warning when `dangerouslySetInnerHTML` is `undefined`. ([@eps1lon](https://github.com/eps1lon) in [#18676](https://github.com/facebook/react/pull/18676)) +* Fix Test Utils with non-standard `require` implementation. ([@just-boris](https://github.com/just-boris) in [#18632](https://github.com/facebook/react/pull/18632)) +* Fix `onBeforeInput` reporting an incorrect `event.type`. ([@eps1lon](https://github.com/eps1lon) in [#19561](https://github.com/facebook/react/pull/19561)) +* Fix `event.relatedTarget` reported as `undefined` in Firefox. ([@claytercek](https://github.com/claytercek) in [#19607](https://github.com/facebook/react/pull/19607)) +* Fix "unspecified error" in IE11. ([@hemakshis](https://github.com/hemakshis) in [#19664](https://github.com/facebook/react/pull/19664)) +* Fix rendering into a shadow root. ([@Jack-Works](https://github.com/Jack-Works) in [#15894](https://github.com/facebook/react/pull/15894)) +* Fix `movementX/Y` polyfill with capture events. ([@gaearon](https://github.com/gaearon) in [#19672](https://github.com/facebook/react/pull/19672)) +* Use delegation for `onSubmit` and `onReset` events. ([@gaearon](https://github.com/gaearon) in [#19333](https://github.com/facebook/react/pull/19333)) +* Improve memory usage. ([@trueadm](https://github.com/trueadm) in [#18970](https://github.com/facebook/react/pull/18970)) + +### React DOM Server {#react-dom-server} + +* Make `useCallback` behavior consistent with `useMemo` for the server renderer. ([@alexmckenley](https://github.com/alexmckenley) in [#18783](https://github.com/facebook/react/pull/18783)) +* Fix state leaking when a function component throws. ([@pmaccart](https://github.com/pmaccart) in [#19212](https://github.com/facebook/react/pull/19212)) + +### React Test Renderer {#react-test-renderer} + +* Improve `findByType` error message. ([@henryqdineen](https://github.com/henryqdineen) in [#17439](https://github.com/facebook/react/pull/17439)) + +### Concurrent Mode (Experimental) {#concurrent-mode-experimental} + +* Revamp the priority batching heuristics. ([@acdlite](https://github.com/acdlite) in [#18796](https://github.com/facebook/react/pull/18796)) +* Add the `unstable_` prefix before the experimental APIs. ([@acdlite](https://github.com/acdlite) in [#18825](https://github.com/facebook/react/pull/18825)) +* Remove `unstable_discreteUpdates` and `unstable_flushDiscreteUpdates`. ([@trueadm](https://github.com/trueadm) in [#18825](https://github.com/facebook/react/pull/18825)) +* Remove the `timeoutMs` argument. ([@acdlite](https://github.com/acdlite) in [#19703](https://github.com/facebook/react/pull/19703)) +* Disable `<div hidden />` prerendering in favor of a different future API. ([@acdlite](https://github.com/acdlite) in [#18917](https://github.com/facebook/react/pull/18917)) +* Add `unstable_expectedLoadTime` to Suspense for CPU-bound trees. ([@acdlite](https://github.com/acdlite) in [#19936](https://github.com/facebook/react/pull/19936)) +* Add an experimental `unstable_useOpaqueIdentifier` Hook. ([@lunaruan](https://github.com/lunaruan) in [#17322](https://github.com/facebook/react/pull/17322)) +* Add an experimental `unstable_startTransition` API. ([@rickhanlonii](https://github.com/rickhanlonii) in [#19696](https://github.com/facebook/react/pull/19696)) +* Using `act` in the test renderer no longer flushes Suspense fallbacks. ([@acdlite](https://github.com/acdlite) in [#18596](https://github.com/facebook/react/pull/18596)) +* Use global render timeout for CPU Suspense. ([@sebmarkbage](https://github.com/sebmarkbage) in [#19643](https://github.com/facebook/react/pull/19643)) +* Clear the existing root content before mounting. ([@bvaughn](https://github.com/bvaughn) in [#18730](https://github.com/facebook/react/pull/18730)) +* Fix a bug with error boundaries. ([@acdlite](https://github.com/acdlite) in [#18265](https://github.com/facebook/react/pull/18265)) +* Fix a bug causing dropped updates in a suspended tree. ([@acdlite](https://github.com/acdlite) in [#18384](https://github.com/facebook/react/pull/18384) and [#18457](https://github.com/facebook/react/pull/18457)) +* Fix a bug causing dropped render phase updates. ([@acdlite](https://github.com/acdlite) in [#18537](https://github.com/facebook/react/pull/18537)) +* Fix a bug in SuspenseList. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18412](https://github.com/facebook/react/pull/18412)) +* Fix a bug causing Suspense fallback to show too early. ([@acdlite](https://github.com/acdlite) in [#18411](https://github.com/facebook/react/pull/18411)) +* Fix a bug with class components inside SuspenseList. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18448](https://github.com/facebook/react/pull/18448)) +* Fix a bug with inputs that may cause updates to be dropped. ([@jddxf](https://github.com/jddxf) in [#18515](https://github.com/facebook/react/pull/18515) and [@acdlite](https://github.com/acdlite) in [#18535](https://github.com/facebook/react/pull/18535)) +* Fix a bug causing Suspense fallback to get stuck. ([@acdlite](https://github.com/acdlite) in [#18663](https://github.com/facebook/react/pull/18663)) +* Don't cut off the tail of a SuspenseList if hydrating. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18854](https://github.com/facebook/react/pull/18854)) +* Fix a bug in `useMutableSource` that may happen when `getSnapshot` changes. ([@bvaughn](https://github.com/bvaughn) in [#18297](https://github.com/facebook/react/pull/18297)) +* Fix a tearing bug in `useMutableSource`. ([@bvaughn](https://github.com/bvaughn) in [#18912](https://github.com/facebook/react/pull/18912)) +* Warn if calling setState outside of render but before commit. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18838](https://github.com/facebook/react/pull/18838)) diff --git a/content/blog/2020-09-22-introducing-the-new-jsx-transform.md b/content/blog/2020-09-22-introducing-the-new-jsx-transform.md new file mode 100644 index 000000000..623fe2f83 --- /dev/null +++ b/content/blog/2020-09-22-introducing-the-new-jsx-transform.md @@ -0,0 +1,267 @@ +--- +title: "Introducing the New JSX Transform" +author: [lunaruan] +--- + +Although React 17 [doesn't contain new features](/blog/2020/08/10/react-v17-rc.html), it will provide support for a new version of the JSX transform. In this post, we will describe what it is and how to try it. + +## What's a JSX Transform? {#whats-a-jsx-transform} + +Browsers don't understand JSX out of the box, so most React users rely on a compiler like Babel or TypeScript to **transform JSX code into regular JavaScript**. Many preconfigured toolkits like Create React App or Next.js also include a JSX transform under the hood. + +Together with the React 17 release, we've wanted to make a few improvements to the JSX transform, but we didn't want to break existing setups. This is why we [worked with Babel](https://babeljs.io/blog/2020/03/16/7.9.0#a-new-jsx-transform-11154httpsgithubcombabelbabelpull11154) to **offer a new, rewritten version of the JSX transform** for people who would like to upgrade. + +Upgrading to the new transform is completely optional, but it has a few benefits: + +* With the new transform, you can **use JSX without importing React**. +* Depending on your setup, its compiled output may **slightly improve the bundle size**. +* It will enable future improvements that **reduce the number of concepts** you need to learn React. + +**This upgrade will not change the JSX syntax and is not required.** The old JSX transform will keep working as usual, and there are no plans to remove the support for it. + + +[React 17 RC](/blog/2020/08/10/react-v17-rc.html) already includes support for the new transform, so go give it a try! To make it easier to adopt, **we've also backported its support** to React 16.14.0, React 15.7.0, and React 0.14.10. You can find the upgrade instructions for different tools [below](#how-to-upgrade-to-the-new-jsx-transform). + +Now let's take a closer look at the differences between the old and the new transform. + +## What’s Different in the New Transform? {#whats-different-in-the-new-transform} + +When you use JSX, the compiler transforms it into React function calls that the browser can understand. **The old JSX transform** turned JSX into `React.createElement(...)` calls. + +For example, let's say your source code looks like this: + +```js +import React from 'react'; + +function App() { + return <h1>Hello World</h1>; +} +``` + +Under the hood, the old JSX transform turns it into regular JavaScript: + +```js +import React from 'react'; + +function App() { + return React.createElement('h1', null, 'Hello world'); +} +``` + +>Note +> +>**Your source code doesn't need to change in any way.** We're describing how the JSX transform turns your JSX source code into the JavaScript code a browser can understand. + +However, this is not perfect: + +* Because JSX was compiled into `React.createElement`, `React` needed to be in scope if you used JSX. +* There are some [performance improvements and simplifications](https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#motivation) that `React.createElement` does not allow. + +To solve these issues, React 17 introduces two new entry points to the React package that are intended to only be used by compilers like Babel and TypeScript. Instead of transforming JSX to `React.createElement`, **the new JSX transform** automatically imports special functions from those new entry points in the React package and calls them. + +Let's say that your source code looks like this: + +```js +function App() { + return <h1>Hello World</h1>; +} +``` + +This is what the new JSX transform compiles it to: + +```js +// Inserted by a compiler (don't import it yourself!) +import {jsx as _jsx} from 'react/jsx-runtime'; + +function App() { + return _jsx('h1', { children: 'Hello world' }); +} +``` + +Note how our original code **did not need to import React** to use JSX anymore! (But we would still need to import React in order to use Hooks or other exports that React provides.) + +**This change is fully compatible with all of the existing JSX code**, so you won't have to change your components. If you're curious, you can check out the [technical RFC](https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#detailed-design) for more details about how the new transform works. + +> Note +> +> The functions inside `react/jsx-runtime` and `react/jsx-dev-runtime` must only be used by the compiler transform. If you need to manually create elements in your code, you should keep using `React.createElement`. It will continue to work and is not going away. + +## How to Upgrade to the New JSX Transform {#how-to-upgrade-to-the-new-jsx-transform} + +If you aren't ready to upgrade to the new JSX transform or if you are using JSX for another library, don't worry. The old transform will not be removed and will continue to be supported. + +If you want to upgrade, you will need two things: + +* **A version of React that supports the new transform** ([React 17 RC](/blog/2020/08/10/react-v17-rc.html) and higher supports it, but we've also released React 16.14.0, React 15.7.0, and React 0.14.10 for people who are still on the older major versions). +* **A compatible compiler** (see instructions for different tools below). + +Since the new JSX transform doesn't require React to be in scope, [we've also prepared an automated script](#removing-unused-react-imports) that will remove the unnecessary imports from your codebase. + +### Create React App {#create-react-app} + +Create React App [4.0.0](https://github.com/facebook/create-react-app/releases/tag/v4.0.0)+ uses the new transform for compatible React versions. + +### Next.js {#nextjs} + +Next.js [v9.5.3](https://github.com/vercel/next.js/releases/tag/v9.5.3)+ uses the new transform for compatible React versions. + +### Gatsby {#gatsby} + +Gatsby [v2.24.5](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/CHANGELOG.md#22452-2020-08-28)+ uses the new transform for compatible React versions. + +>Note +> +>If you get [this Gatsby error](https://github.com/gatsbyjs/gatsby/issues/26979) after upgrading to React 17 RC, run `npm update` to fix it. + +### Manual Babel Setup {#manual-babel-setup} + +Support for the new JSX transform is available in Babel [v7.9.0](https://babeljs.io/blog/2020/03/16/7.9.0) and above. + +First, you'll need to update to the latest Babel and plugin transform. + +If you are using `@babel/plugin-transform-react-jsx`: + +```bash +# for npm users +npm update @babel/core @babel/plugin-transform-react-jsx +``` + +```bash +# for yarn users +yarn upgrade @babel/core @babel/plugin-transform-react-jsx +``` + +If you are using `@babel/preset-react`: + +```bash +# for npm users +npm update @babel/core @babel/preset-react +``` + +```bash +# for yarn users +yarn upgrade @babel/core @babel/preset-react +``` + +Currently, the old transform `{"runtime": "classic"}` is the default option. To enable the new transform, you can pass `{"runtime": "automatic"}` as an option to `@babel/plugin-transform-react-jsx` or `@babel/preset-react`: + +```js +// If you are using @babel/preset-react +{ + "presets": [ + ["@babel/preset-react", { + "runtime": "automatic" + }] + ] +} +``` + +```js +// If you're using @babel/plugin-transform-react-jsx +{ + "plugins": [ + ["@babel/plugin-transform-react-jsx", { + "runtime": "automatic" + }] + ] +} +``` + +Starting from Babel 8, `"automatic"` will be the default runtime for both plugins. For more information, check out the Babel documentation for [@babel/plugin-transform-react-jsx](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx) and [@babel/preset-react](https://babeljs.io/docs/en/babel-preset-react). + +> Note +> +> If you use JSX with a library other than React, you can use [the `importSource` option](https://babeljs.io/docs/en/babel-preset-react#importsource) to import from that library instead -- as long as it provides the necessary entry points. Alternatively, you can keep using the classic transform which will continue to be supported. +> +> If you're a library author and you are implementing the `/jsx-runtime` entry point for your library, keep in mind that [there is a case](https://github.com/facebook/react/issues/20031#issuecomment-710346866) in which even the new transform has to fall back to `createElement` for backwards compatibility. In that case, it will auto-import `createElement` directly from the *root* entry point specified by `importSource`. + +### ESLint {#eslint} + +If you are using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react), the `react/jsx-uses-react` and `react/react-in-jsx-scope` rules are no longer necessary and can be turned off or removed. + +```js +{ + // ... + "rules": { + // ... + "react/jsx-uses-react": "off", + "react/react-in-jsx-scope": "off" + } +} +``` + +### TypeScript {#typescript} + +TypeScript supports the new JSX transform in [v4.1](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#jsx-factories) and up. + +### Flow {#flow} + +Flow supports the new JSX transform in [v0.126.0](https://github.com/facebook/flow/releases/tag/v0.126.0) and up, by adding `react.runtime=automatic` to your Flow configuration options. + +## Removing Unused React Imports {#removing-unused-react-imports} + +Because the new JSX transform will automatically import the necessary `react/jsx-runtime` functions, React will no longer need to be in scope when you use JSX. This might lead to unused React imports in your code. It doesn't hurt to keep them, but if you'd like to remove them, we recommend running a [“codemod”](https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb) script to remove them automatically: + +```bash +cd your_project +npx react-codemod update-react-imports +``` + +>Note +> +>If you're getting errors when running the codemod, try specifying a different JavaScript dialect when `npx react-codemod update-react-imports` asks you to choose one. In particular, at this moment the "JavaScript with Flow" setting supports newer syntax than the "JavaScript" setting even if you don't use Flow. [File an issue](https://github.com/reactjs/react-codemod/issues) if you run into problems. +> +>Keep in mind that the codemod output will not always match your project's coding style, so you might want to run [Prettier](https://prettier.io/) after the codemod finishes for consistent formatting. + +Running this codemod will: + +* Remove all unused React imports as a result of upgrading to the new JSX transform. +* Change all default React imports (i.e. `import React from "react"`) to destructured named imports (ex. `import { useState } from "react"`) which is the preferred style going into the future. This codemod **will not** affect the existing namespace imports (i.e. `import * as React from "react"`) which is also a valid style. The default imports will keep working in React 17, but in the longer term we encourage moving away from them. + +For example, + +```js +import React from 'react'; + +function App() { + return <h1>Hello World</h1>; +} +``` + +will be replaced with + +```js +function App() { + return <h1>Hello World</h1>; +} +``` + +If you use some other import from React — for example, a Hook — then the codemod will convert it to a named import. + +For example, + +```js +import React from 'react'; + +function App() { + const [text, setText] = React.useState('Hello World'); + return <h1>{text}</h1>; +} +``` + +will be replaced with + +```js +import { useState } from 'react'; + +function App() { + const [text, setText] = useState('Hello World'); + return <h1>{text}</h1>; +} +``` + +In addition to cleaning up unused imports, this will also help you prepare for a future major version of React (not React 17) which will support ES Modules and not have a default export. + +## Thanks {#thanks} + +We'd like to thank Babel, TypeScript, Create React App, Next.js, Gatsby, ESLint, and Flow maintainers for their help implementing and integrating the new JSX transform. We also want to thank the React community for their feedback and discussion on the related [technical RFC](https://github.com/reactjs/rfcs/pull/107). diff --git a/content/blog/2020-10-20-react-v17.md b/content/blog/2020-10-20-react-v17.md new file mode 100644 index 000000000..e25282140 --- /dev/null +++ b/content/blog/2020-10-20-react-v17.md @@ -0,0 +1,169 @@ +--- +title: "React v17.0" +author: [gaearon,rachelnabors] +--- + +Today, we are releasing React 17! We've written at length about the role of the React 17 release and the changes it contains in [the React 17 RC blog post](/blog/2020/08/10/react-v17-rc.html). This post is a brief summary of it, so if you've already read the RC post, you can skip this one. + +## No New Features {#no-new-features} + +The React 17 release is unusual because it doesn't add any new developer-facing features. Instead, this release is primarily focused on **making it easier to upgrade React itself**. + +In particular, React 17 is a “stepping stone” release that makes it safer to embed a tree managed by one version of React inside a tree managed by a different version of React. + +It also makes it easier to embed React into apps built with other technologies. + +## Gradual Upgrades {#gradual-upgrades} + +**React 17 enables gradual React upgrades.** When you upgrade from React 15 to 16 (or, this time, from React 16 to 17), you would usually upgrade your whole app at once. This works well for many apps. But it can get increasingly challenging if the codebase was written more than a few years ago and isn’t actively maintained. And while it’s possible to use two versions of React on the page, until React 17 this has been fragile and caused problems with events. + +We’re fixing many of those problems with React 17. This means that **when React 18 and the next future versions come out, you will now have more options**. The first option will be to upgrade your whole app at once, like you might have done before. But you will also have an option to upgrade your app piece by piece. For example, you might decide to migrate most of your app to React 18, but keep some lazy-loaded dialog or a subroute on React 17. + +This doesn't mean you *have to* do gradual upgrades. **For most apps, upgrading all at once is still the best solution.** Loading two versions of React -- even if one of them is loaded lazily on demand -- is still not ideal. However, for larger apps that aren't actively maintained, this option makes sense to consider, and React 17 lets those apps not get left behind. + +We've prepared an [example repository](https://github.com/reactjs/react-gradual-upgrade-demo/) demonstrating how to lazy-load an older version of React if necessary. This demo uses Create React App, but it should be possible to follow a similar approach with any other tool. We welcome demos using other tooling as pull requests. + +>Note +> +>We've **postponed other changes** until after React 17. The goal of this release is to enable gradual upgrades. If upgrading to React 17 were too difficult, it would defeat its purpose. + +## Changes to Event Delegation {#changes-to-event-delegation} + +To enable gradual updates, we've needed to make some changes to the React event system. React 17 is a major release because these changes are potentially breaking. You can check out our [versioning FAQ](/docs/faq-versioning.html#breaking-changes) to learn more about our commitment to stability. + +In React 17, React will no longer attach event handlers at the `document` level under the hood. Instead, it will attach them to the root DOM container into which your React tree is rendered: + +```js +const rootNode = document.getElementById('root'); +ReactDOM.render(<App />, rootNode); +``` + +In React 16 and earlier, React would do `document.addEventListener()` for most events. React 17 will call `rootNode.addEventListener()` under the hood instead. + +![A diagram showing how React 17 attaches events to the roots rather than to the document](../images/blog/react-v17-rc/react_17_delegation.png) + +We've confirmed that [numerous](https://github.com/facebook/react/issues/7094) [problems](https://github.com/facebook/react/issues/8693) [reported](https://github.com/facebook/react/issues/12518) [over](https://github.com/facebook/react/issues/13451) [the](https://github.com/facebook/react/issues/4335) [years](https://github.com/facebook/react/issues/1691) [on](https://github.com/facebook/react/issues/285#issuecomment-253502585) [our](https://github.com/facebook/react/pull/8117) [issue](https://github.com/facebook/react/issues/11530) [tracker](https://github.com/facebook/react/issues/7128) related to integrating React with non-React code have been fixed by the new behavior. + +If you run into issues with this change, [here's a common way to resolve them](/blog/2020/08/10/react-v17-rc.html#fixing-potential-issues). + +## Other Breaking Changes {#other-breaking-changes} + +[The React 17 RC blog post](/blog/2020/08/10/react-v17-rc.html#other-breaking-changes) describes the rest of the breaking changes in React 17. + +We've only had to change fewer than twenty components out of 100,000+ in the Facebook product code to work with these changes, so **we expect that most apps can upgrade to React 17 without too much trouble**. Please [tell us](https://github.com/facebook/react/issues) if you run into problems. + +## New JSX Transform {#new-jsx-transform} + +React 17 supports the [new JSX transform](/blog/2020/09/22/introducing-the-new-jsx-transform.html). We've also backported support for it to React 16.14.0, React 15.7.0, and 0.14.10. Note that it is completely opt-in, and you don't have to use it. The classic JSX transform will keep working, and there are no plans to stop supporting it. + +## React Native {#react-native} + +React Native has a separate release schedule. We landed the support for React 17 in React Native 0.64. As always, you can track the release discussions on the React Native Community releases [issue tracker](https://github.com/react-native-community/releases). + +## Installation {#installation} + +To install React 17 with npm, run: + +```bash +npm install react@17.0.0 react-dom@17.0.0 +``` + +To install React 17 with Yarn, run: + +```bash +yarn add react@17.0.0 react-dom@17.0.0 +``` + +We also provide UMD builds of React via a CDN: + +```html +<script crossorigin src="https://unpkg.com/react@17.0.0/umd/react.production.min.js"></script> +<script crossorigin src="https://unpkg.com/react-dom@17.0.0/umd/react-dom.production.min.js"></script> +``` + +Refer to the documentation for [detailed installation instructions](/docs/installation.html). + +## Changelog {#changelog} + +### React {#react} + +* Add `react/jsx-runtime` and `react/jsx-dev-runtime` for the [new JSX transform](https://babeljs.io/blog/2020/03/16/7.9.0#a-new-jsx-transform-11154-https-githubcom-babel-babel-pull-11154). ([@lunaruan](https://github.com/lunaruan) in [#18299](https://github.com/facebook/react/pull/18299)) +* Build component stacks from native error frames. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18561](https://github.com/facebook/react/pull/18561)) +* Allow to specify `displayName` on context for improved stacks. ([@eps1lon](https://github.com/eps1lon) in [#18224](https://github.com/facebook/react/pull/18224)) +* Prevent `'use strict'` from leaking in the UMD bundles. ([@koba04](https://github.com/koba04) in [#19614](https://github.com/facebook/react/pull/19614)) +* Stop using `fb.me` for redirects. ([@cylim](https://github.com/cylim) in [#19598](https://github.com/facebook/react/pull/19598)) + +### React DOM {#react-dom} + +* Delegate events to roots instead of `document`. ([@trueadm](https://github.com/trueadm) in [#18195](https://github.com/facebook/react/pull/18195) and [others](https://github.com/facebook/react/pulls?q=is%3Apr+author%3Atrueadm+modern+event+is%3Amerged)) +* Clean up all effects before running any next effects. ([@bvaughn](https://github.com/bvaughn) in [#17947](https://github.com/facebook/react/pull/17947)) +* Run `useEffect` cleanup functions asynchronously. ([@bvaughn](https://github.com/bvaughn) in [#17925](https://github.com/facebook/react/pull/17925)) +* Use browser `focusin` and `focusout` for `onFocus` and `onBlur`. ([@trueadm](https://github.com/trueadm) in [#19186](https://github.com/facebook/react/pull/19186)) +* Make all `Capture` events use the browser capture phase. ([@trueadm](https://github.com/trueadm) in [#19221](https://github.com/facebook/react/pull/19221)) +* Don't emulate bubbling of the `onScroll` event. ([@gaearon](https://github.com/gaearon) in [#19464](https://github.com/facebook/react/pull/19464)) +* Throw if `forwardRef` or `memo` component returns `undefined`. ([@gaearon](https://github.com/gaearon) in [#19550](https://github.com/facebook/react/pull/19550)) +* Remove event pooling. ([@trueadm](https://github.com/trueadm) in [#18969](https://github.com/facebook/react/pull/18969)) +* Stop exposing internals that won’t be needed by React Native Web. ([@necolas](https://github.com/necolas) in [#18483](https://github.com/facebook/react/pull/18483)) +* Attach all known event listeners when the root mounts. ([@gaearon](https://github.com/gaearon) in [#19659](https://github.com/facebook/react/pull/19659)) +* Disable `console` in the second render pass of DEV mode double render. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18547](https://github.com/facebook/react/pull/18547)) +* Deprecate the undocumented and misleading `ReactTestUtils.SimulateNative` API. ([@gaearon](https://github.com/gaearon) in [#13407](https://github.com/facebook/react/pull/13407)) +* Rename private field names used in the internals. ([@gaearon](https://github.com/gaearon) in [#18377](https://github.com/facebook/react/pull/18377)) +* Don't call User Timing API in development. ([@gaearon](https://github.com/gaearon) in [#18417](https://github.com/facebook/react/pull/18417)) +* Disable console during the repeated render in Strict Mode. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18547](https://github.com/facebook/react/pull/18547)) +* In Strict Mode, double-render components without Hooks too. ([@eps1lon](https://github.com/eps1lon) in [#18430](https://github.com/facebook/react/pull/18430)) +* Allow calling `ReactDOM.flushSync` during lifecycle methods (but warn). ([@sebmarkbage](https://github.com/sebmarkbage) in [#18759](https://github.com/facebook/react/pull/18759)) +* Add the `code` property to the keyboard event objects. ([@bl00mber](https://github.com/bl00mber) in [#18287](https://github.com/facebook/react/pull/18287)) +* Add the `disableRemotePlayback` property for `video` elements. ([@tombrowndev](https://github.com/tombrowndev) in [#18619](https://github.com/facebook/react/pull/18619)) +* Add the `enterKeyHint` property for `input` elements. ([@eps1lon](https://github.com/eps1lon) in [#18634](https://github.com/facebook/react/pull/18634)) +* Warn when no `value` is provided to `<Context.Provider>`. ([@charlie1404](https://github.com/charlie1404) in [#19054](https://github.com/facebook/react/pull/19054)) +* Warn when `memo` or `forwardRef` components return `undefined`. ([@bvaughn](https://github.com/bvaughn) in [#19550](https://github.com/facebook/react/pull/19550)) +* Improve the error message for invalid updates. ([@JoviDeCroock](https://github.com/JoviDeCroock) in [#18316](https://github.com/facebook/react/pull/18316)) +* Exclude forwardRef and memo from stack frames. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18559](https://github.com/facebook/react/pull/18559)) +* Improve the error message when switching between controlled and uncontrolled inputs. ([@vcarl](https://github.com/vcarl) in [#17070](https://github.com/facebook/react/pull/17070)) +* Keep `onTouchStart`, `onTouchMove`, and `onWheel` passive. ([@gaearon](https://github.com/gaearon) in [#19654](https://github.com/facebook/react/pull/19654)) +* Fix `setState` hanging in development inside a closed iframe. ([@gaearon](https://github.com/gaearon) in [#19220](https://github.com/facebook/react/pull/19220)) +* Fix rendering bailout for lazy components with `defaultProps`. ([@jddxf](https://github.com/jddxf) in [#18539](https://github.com/facebook/react/pull/18539)) +* Fix a false positive warning when `dangerouslySetInnerHTML` is `undefined`. ([@eps1lon](https://github.com/eps1lon) in [#18676](https://github.com/facebook/react/pull/18676)) +* Fix Test Utils with non-standard `require` implementation. ([@just-boris](https://github.com/just-boris) in [#18632](https://github.com/facebook/react/pull/18632)) +* Fix `onBeforeInput` reporting an incorrect `event.type`. ([@eps1lon](https://github.com/eps1lon) in [#19561](https://github.com/facebook/react/pull/19561)) +* Fix `event.relatedTarget` reported as `undefined` in Firefox. ([@claytercek](https://github.com/claytercek) in [#19607](https://github.com/facebook/react/pull/19607)) +* Fix "unspecified error" in IE11. ([@hemakshis](https://github.com/hemakshis) in [#19664](https://github.com/facebook/react/pull/19664)) +* Fix rendering into a shadow root. ([@Jack-Works](https://github.com/Jack-Works) in [#15894](https://github.com/facebook/react/pull/15894)) +* Fix `movementX/Y` polyfill with capture events. ([@gaearon](https://github.com/gaearon) in [#19672](https://github.com/facebook/react/pull/19672)) +* Use delegation for `onSubmit` and `onReset` events. ([@gaearon](https://github.com/gaearon) in [#19333](https://github.com/facebook/react/pull/19333)) +* Improve memory usage. ([@trueadm](https://github.com/trueadm) in [#18970](https://github.com/facebook/react/pull/18970)) + +### React DOM Server {#react-dom-server} + +* Make `useCallback` behavior consistent with `useMemo` for the server renderer. ([@alexmckenley](https://github.com/alexmckenley) in [#18783](https://github.com/facebook/react/pull/18783)) +* Fix state leaking when a function component throws. ([@pmaccart](https://github.com/pmaccart) in [#19212](https://github.com/facebook/react/pull/19212)) + +### React Test Renderer {#react-test-renderer} + +* Improve `findByType` error message. ([@henryqdineen](https://github.com/henryqdineen) in [#17439](https://github.com/facebook/react/pull/17439)) + +### Concurrent Mode (Experimental) {#concurrent-mode-experimental} + +* Revamp the priority batching heuristics. ([@acdlite](https://github.com/acdlite) in [#18796](https://github.com/facebook/react/pull/18796)) +* Add the `unstable_` prefix before the experimental APIs. ([@acdlite](https://github.com/acdlite) in [#18825](https://github.com/facebook/react/pull/18825)) +* Remove `unstable_discreteUpdates` and `unstable_flushDiscreteUpdates`. ([@trueadm](https://github.com/trueadm) in [#18825](https://github.com/facebook/react/pull/18825)) +* Remove the `timeoutMs` argument. ([@acdlite](https://github.com/acdlite) in [#19703](https://github.com/facebook/react/pull/19703)) +* Disable `<div hidden />` prerendering in favor of a different future API. ([@acdlite](https://github.com/acdlite) in [#18917](https://github.com/facebook/react/pull/18917)) +* Add `unstable_expectedLoadTime` to Suspense for CPU-bound trees. ([@acdlite](https://github.com/acdlite) in [#19936](https://github.com/facebook/react/pull/19936)) +* Add an experimental `unstable_useOpaqueIdentifier` Hook. ([@lunaruan](https://github.com/lunaruan) in [#17322](https://github.com/facebook/react/pull/17322)) +* Add an experimental `unstable_startTransition` API. ([@rickhanlonii](https://github.com/rickhanlonii) in [#19696](https://github.com/facebook/react/pull/19696)) +* Using `act` in the test renderer no longer flushes Suspense fallbacks. ([@acdlite](https://github.com/acdlite) in [#18596](https://github.com/facebook/react/pull/18596)) +* Use global render timeout for CPU Suspense. ([@sebmarkbage](https://github.com/sebmarkbage) in [#19643](https://github.com/facebook/react/pull/19643)) +* Clear the existing root content before mounting. ([@bvaughn](https://github.com/bvaughn) in [#18730](https://github.com/facebook/react/pull/18730)) +* Fix a bug with error boundaries. ([@acdlite](https://github.com/acdlite) in [#18265](https://github.com/facebook/react/pull/18265)) +* Fix a bug causing dropped updates in a suspended tree. ([@acdlite](https://github.com/acdlite) in [#18384](https://github.com/facebook/react/pull/18384) and [#18457](https://github.com/facebook/react/pull/18457)) +* Fix a bug causing dropped render phase updates. ([@acdlite](https://github.com/acdlite) in [#18537](https://github.com/facebook/react/pull/18537)) +* Fix a bug in SuspenseList. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18412](https://github.com/facebook/react/pull/18412)) +* Fix a bug causing Suspense fallback to show too early. ([@acdlite](https://github.com/acdlite) in [#18411](https://github.com/facebook/react/pull/18411)) +* Fix a bug with class components inside SuspenseList. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18448](https://github.com/facebook/react/pull/18448)) +* Fix a bug with inputs that may cause updates to be dropped. ([@jddxf](https://github.com/jddxf) in [#18515](https://github.com/facebook/react/pull/18515) and [@acdlite](https://github.com/acdlite) in [#18535](https://github.com/facebook/react/pull/18535)) +* Fix a bug causing Suspense fallback to get stuck. ([@acdlite](https://github.com/acdlite) in [#18663](https://github.com/facebook/react/pull/18663)) +* Don't cut off the tail of a SuspenseList if hydrating. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18854](https://github.com/facebook/react/pull/18854)) +* Fix a bug in `useMutableSource` that may happen when `getSnapshot` changes. ([@bvaughn](https://github.com/bvaughn) in [#18297](https://github.com/facebook/react/pull/18297)) +* Fix a tearing bug in `useMutableSource`. ([@bvaughn](https://github.com/bvaughn) in [#18912](https://github.com/facebook/react/pull/18912)) +* Warn if calling setState outside of render but before commit. ([@sebmarkbage](https://github.com/sebmarkbage) in [#18838](https://github.com/facebook/react/pull/18838)) diff --git a/content/blog/2020-12-21-data-fetching-with-react-server-components.md b/content/blog/2020-12-21-data-fetching-with-react-server-components.md new file mode 100644 index 000000000..6a67eb176 --- /dev/null +++ b/content/blog/2020-12-21-data-fetching-with-react-server-components.md @@ -0,0 +1,24 @@ +--- +title: "Introducing Zero-Bundle-Size React Server Components" +author: [gaearon,laurentan,josephsavona,sebmarkbage] +--- + +2020 has been a long year. As it comes to an end we wanted to share a special Holiday Update on our research into zero-bundle-size **React Server Components**. + +To introduce React Server Components, we have prepared a talk and a demo. If you want, you can check them out during the holidays, or later when work picks back up in the new year. + +<br> + +<iframe width="560" height="315" src="https://www.youtube.com/embed/TQQPAU21ZUw" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +**React Server Components are still in research and development.** We are sharing this work in the spirit of transparency and to get initial feedback from the React community. There will be plenty of time for that, so **don't feel like you have to catch up right now!** + +If you want to check them out, we recommend to go in the following order: + +1. **Watch the talk** to learn about React Server Components and see the demo. + +2. **[Clone the demo](http://github.com/reactjs/server-components-demo)** to play with React Server Components on your computer. + +3. **[Read the RFC (with FAQ at the end)](https://github.com/reactjs/rfcs/pull/188)** for a deeper technical breakdown and to provide feedback. + +We are excited to hear from you on the RFC or in replies to the [@reactjs](https://twitter.com/reactjs) Twitter handle. Happy holidays, stay safe, and see you next year! diff --git a/content/blog/2021-06-08-the-plan-for-react-18.md b/content/blog/2021-06-08-the-plan-for-react-18.md new file mode 100644 index 000000000..795159da5 --- /dev/null +++ b/content/blog/2021-06-08-the-plan-for-react-18.md @@ -0,0 +1,63 @@ +--- +title: "The Plan for React 18" +author: [acdlite, bvaughn, abernathyca, gaearon, rachelnabors, rickhanlonii, sebmarkbage, sethwebster] +--- + +> Update Nov. 15th, 2021 +> +> React 18 is now in beta. More information about the status of the release is [available in the React 18 Working Group post](https://github.com/reactwg/react-18/discussions/112). + +The React team is excited to share a few updates: + +1. We’ve started work on the React 18 release, which will be our next major version. +2. We’ve created a Working Group to prepare the community for gradual adoption of new features in React 18. +3. We’ve published a React 18 Alpha so that library authors can try it and provide feedback. + +These updates are primarily aimed at maintainers of third-party libraries. If you’re learning, teaching, or using React to build user-facing applications, you can safely ignore this post. But you are welcome to follow the discussions in the React 18 Working Group if you're curious! + +## What’s coming in React 18 {#whats-coming-in-react-18} + +When it’s released, React 18 will include out-of-the-box improvements (like [automatic batching](https://github.com/reactwg/react-18/discussions/21)), new APIs (like [`startTransition`](https://github.com/reactwg/react-18/discussions/41)), and a [new streaming server renderer](https://github.com/reactwg/react-18/discussions/37) with built-in support for `React.lazy`. + +These features are possible thanks to a new opt-in mechanism we’re adding in React 18. It’s called “concurrent rendering” and it lets React prepare multiple versions of the UI at the same time. This change is mostly behind-the-scenes, but it unlocks new possibilities to improve both real and perceived performance of your app. + +If you've been following our research into the future of React (we don't expect you to!), you might have heard of something called “concurrent mode” or that it might break your app. In response to this feedback from the community, we’ve redesigned the upgrade strategy for gradual adoption. Instead of an all-or-nothing “mode”, concurrent rendering will only be enabled for updates triggered by one of the new features. In practice, this means **you will be able to adopt React 18 without rewrites and try the new features at your own pace.** + +## A gradual adoption strategy {#a-gradual-adoption-strategy} + +Since concurrency in React 18 is opt-in, there are no significant out-of-the-box breaking changes to component behavior. **You can upgrade to React 18 with minimal or no changes to your application code, with a level of effort comparable to a typical major React release**. Based on our experience converting several apps to React 18, we expect that many users will be able to upgrade within a single afternoon. + +We successfully shipped concurrent features to tens of thousands of components at Facebook, and in our experience, we've found that most React components “just work” without additional changes. We're committed to making sure this is a smooth upgrade for the entire community, so today we're announcing the React 18 Working Group. + +## Working with the community {#working-with-the-community} + +We’re trying something new for this release: We've invited a panel of experts, developers, library authors, and educators from across the React community to participate in our [React 18 Working Group](https://github.com/reactwg/react-18) to provide feedback, ask questions, and collaborate on the release. We couldn't invite everyone we wanted to this initial, small group, but if this experiment works out, we hope there will be more in the future! + +**The goal of the React 18 Working Group is to prepare the ecosystem for a smooth, gradual adoption of React 18 by existing applications and libraries.** The Working Group is hosted on [GitHub Discussions](https://github.com/reactwg/react-18/discussions) and is available for the public to read. Members of the working group can leave feedback, ask questions, and share ideas. The core team will also use the discussions repo to share our research findings. As the stable release gets closer, any important information will also be posted on this blog. + +For more information on upgrading to React 18, or additional resources about the release, see the [React 18 announcement post](https://github.com/reactwg/react-18/discussions/4). + +## Accessing the React 18 Working Group {#accessing-the-react-18-working-group} + +Everyone can read the discussions in the [React 18 Working Group repo](https://github.com/reactwg/react-18). + +Because we expect an initial surge of interest in the Working Group, only invited members will be allowed to create or comment on threads. However, the threads are fully visible to the public, so everyone has access to the same information. We believe this is a good compromise between creating a productive environment for working group members, while maintaining transparency with the wider community. + +As always, you can submit bug reports, questions, and general feedback to our [issue tracker](https://github.com/facebook/react/issues). + +## How to try React 18 Alpha today {#how-to-try-react-18-alpha-today} + +New alphas are [regularly published to npm using the `@alpha` tag](https://github.com/reactwg/react-18/discussions/9). These releases are built using the most recent commit to our main repo. When a feature or bugfix is merged, it will appear in an alpha the following weekday. + +There may be significant behavioral or API changes between alpha releases. Please remember that **alpha releases are not recommended for user-facing, production applications**. + +## Projected React 18 release timeline {#projected-react-18-release-timeline} + +We don't have a specific release date scheduled, but we expect it will take several months of feedback and iteration before React 18 is ready for most production applications. + +* Library Alpha: Available today +* Public Beta: At least several months +* Release Candidate (RC): At least several weeks after Beta +* General Availability: At least several weeks after RC + +More details about our projected release timeline are [available in the Working Group](https://github.com/reactwg/react-18/discussions/9). We'll post updates on this blog when we're closer to a public release. diff --git a/content/blog/2021-12-17-react-conf-2021-recap.md b/content/blog/2021-12-17-react-conf-2021-recap.md new file mode 100644 index 000000000..f5ccd245c --- /dev/null +++ b/content/blog/2021-12-17-react-conf-2021-recap.md @@ -0,0 +1,148 @@ +--- +title: "React Conf 2021 Recap" +author: [jtannady, rickhanlonii] +--- + +Last week we hosted our 6th React Conf. In previous years, we've used the React Conf stage to deliver industry changing announcements such as [_React Native_](https://engineering.fb.com/2015/03/26/android/react-native-bringing-modern-web-techniques-to-mobile/) and [_React Hooks_](https://reactjs.org/docs/hooks-intro.html). This year, we shared our multi-platform vision for React, starting with the release of React 18 and gradual adoption of concurrent features. + +This was the first time React Conf was hosted online, and it was streamed for free, translated to 8 different languages. Participants from all over the world joined our conference Discord and the replay event for accessibility in all timezones. Over 50,000 people registered, with over 60,000 views of 19 talks, and 5,000 participants in Discord across both events. + +All the talks are [available to stream online](https://www.youtube.com/watch?v=FZ0cG47msEk&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa). + +Here’s a summary of what was shared on stage: + +## React 18 and concurrent features {#react-18-and-concurrent-features} + +In the keynote, we shared our vision for the future of React starting with React 18. + +React 18 adds the long-awaited concurrent renderer and updates to Suspense without any major breaking changes. Apps can upgrade to React 18 and begin gradually adopting concurrent features with the amount of effort on par with any other major release. + +**This means there is no concurrent mode, only concurrent features.** + +In the keynote, we also shared our vision for Suspense, Server Components, new React working groups, and our long-term many-platform vision for React Native. + +Watch the full keynote from [Andrew Clark](https://twitter.com/acdlite), [Juan Tejada](https://twitter.com/_jstejada), [Lauren Tan](https://twitter.com/potetotes), and [Rick Hanlon](https://twitter.com/rickhanlonii) here: + +<iframe style="margin-top:10px" width="560" height="315" src="https://www.youtube.com/embed/FZ0cG47msEk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +## React 18 for Application Developers {#react-18-for-application-developers} + +In the keynote, we also announced that the React 18 RC is available to try now. Pending further feedback, this is the exact version of React that we will publish to stable early next year. + +To try the React 18 RC, upgrade your dependencies: + +```bash +npm install react@rc react-dom@rc +``` + +and switch to the new `createRoot` API: + +```js +// before +const container = document.getElementById('root'); +ReactDOM.render(<App />, container); + +// after +const container = document.getElementById('root'); +const root = ReactDOM.createRoot(container); +root.render(<App/>); +``` + +For a demo of upgrading to React 18, see [Shruti Kapoor](https://twitter.com/shrutikapoor08)’s talk here: + +<iframe style="margin-top:10px" width="560" height="315" src="https://www.youtube.com/embed/ytudH8je5ko" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +## Streaming Server Rendering with Suspense {#streaming-server-rendering-with-suspense} + +React 18 also includes improvements to server-side rendering performance using Suspense. + +Streaming server rendering lets you generate HTML from React components on the server, and stream that HTML to your users. In React 18, you can use `Suspense` to break down your app into smaller independent units which can be streamed independently of each other without blocking the rest of the app. This means users will see your content sooner and be able to start interacting with it much faster. + +For a deep dive, see [Shaundai Person](https://twitter.com/shaundai)’s talk here: + +<iframe style="margin-top:10px" width="560" height="315" src="https://www.youtube.com/embed/pj5N-Khihgc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +## The first React working group {#the-first-react-working-group} + +For React 18, we created our first Working Group to collaborate with a panel of experts, developers, library maintainers, and educators. Together we worked to create our gradual adoption strategy and refine new APIs such as `useId`, `useSyncExternalStore`, and `useInsertionEffect`. + +For an overview of this work, see [Aakansha' Doshi](https://twitter.com/aakansha1216)'s talk: + +<iframe style="margin-top:10px" width="560" height="315" src="https://www.youtube.com/embed/qn7gRClrC9U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +## React Developer Tooling {#react-developer-tooling} + +To support the new features in this release, we also announced the newly formed React DevTools team and a new Timeline Profiler to help developers debug their React apps. + +For more information and a demo of new DevTools features, see [Brian Vaughn](https://twitter.com/brian_d_vaughn)’s talk: + +<iframe style="margin-top:10px" width="560" height="315" src="https://www.youtube.com/embed/oxDfrke8rZg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +## React without memo {#react-without-memo} + +Looking further into the future, [Xuan Huang (黄玄)](https://twitter.com/Huxpro) shared an update from our React Labs research into an auto-memoizing compiler. Check out this talk for more information and a demo of the compiler prototype: + +<iframe style="margin-top:10px" width="560" height="315" src="https://www.youtube.com/embed/lGEMwh32soc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +## React docs keynote {#react-docs-keynote} + +[Rachel Nabors](https://twitter.com/rachelnabors) kicked off a section of talks about learning and designing with React with a keynote about our investment in React's [new docs](https://beta.reactjs.org/): + +<iframe style="margin-top:10px" width="560" height="315" src="https://www.youtube.com/embed/mneDaMYOKP8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +# And more... {#and-more} + +**We also heard talks on learning and designing with React:** + +* Debbie O'Brien: [Things I learnt from the new React docs](https://youtu.be/-7odLW_hG7s). +* Sarah Rainsberger: [Learning in the Browser](https://youtu.be/5X-WEQflCL0). +* Linton Ye: [The ROI of Designing with React](https://youtu.be/7cPWmID5XAk). +* Delba de Oliveira: [Interactive playgrounds with React](https://youtu.be/zL8cz2W0z34). + +**Talks from the Relay, React Native, and PyTorch teams:** + +* Robert Balicki: [Re-introducing Relay](https://youtu.be/lhVGdErZuN4). +* Eric Rozell and Steven Moyes: [React Native Desktop](https://youtu.be/9L4FFrvwJwY). +* Roman Rädle: [On-device Machine Learning for React Native](https://youtu.be/NLj73vrc2I8) + +**And talks from the community on accessibility, tooling, and Server Components:** + +* Daishi Kato: [React 18 for External Store Libraries](https://youtu.be/oPfSC5bQPR8). +* Diego Haz: [Building Accessible Components in React 18](https://youtu.be/dcm8fjBfro8). +* Tafu Nakazaki: [Accessible Japanese Form Components with React](https://youtu.be/S4a0QlsH0pU). +* Lyle Troxell: [UI tools for artists](https://youtu.be/b3l4WxipFsE). +* Helen Lin: [Hydrogen + React 18](https://youtu.be/HS6vIYkSNks). + +# Thank you {#thank-you} + +This was our first year planning a conference ourselves, and we have a lot of people to thank. + +First, thanks to all of our speakers [Aakansha Doshi](https://twitter.com/aakansha1216), [Andrew Clark](https://twitter.com/acdlite), [Brian Vaughn](https://twitter.com/brian_d_vaughn), [Daishi Kato](https://twitter.com/dai_shi), [Debbie O'Brien](https://twitter.com/debs_obrien), [Delba de Oliveira](https://twitter.com/delba_oliveira), [Diego Haz](https://twitter.com/diegohaz), [Eric Rozell](https://twitter.com/EricRozell), [Helen Lin](https://twitter.com/wizardlyhel), [Juan Tejada](https://twitter.com/_jstejada), [Lauren Tan](https://twitter.com/potetotes), [Linton Ye](https://twitter.com/lintonye), [Lyle Troxell](https://twitter.com/lyle), [Rachel Nabors](https://twitter.com/rachelnabors), [Rick Hanlon](https://twitter.com/rickhanlonii), [Robert Balicki](https://twitter.com/StatisticsFTW), [Roman Rädle](https://twitter.com/raedle), [Sarah Rainsberger](https://twitter.com/sarah11918), [Shaundai Person](https://twitter.com/shaundai), [Shruti Kapoor](https://twitter.com/shrutikapoor08), [Steven Moyes](https://twitter.com/moyessa), [Tafu Nakazaki](https://twitter.com/hawaiiman0), and [Xuan Huang (黄玄)](https://twitter.com/Huxpro). + +Thanks to everyone who helped provide feedback on talks including [Andrew Clark](https://twitter.com/acdlite), [Dan Abramov](https://twitter.com/dan_abramov), [Dave McCabe](https://twitter.com/mcc_abe), [Eli White](https://twitter.com/Eli_White), [Joe Savona](https://twitter.com/en_JS), [Lauren Tan](https://twitter.com/potetotes), [Rachel Nabors](https://twitter.com/rachelnabors), and [Tim Yung](https://twitter.com/yungsters). + +Thanks to [Lauren Tan](https://twitter.com/potetotes) for setting up the conference Discord and serving as our Discord admin. + +Thanks to [Seth Webster](https://twitter.com/sethwebster) for feedback on overall direction and making sure we were focused on diversity and inclusion. + +Thanks to [Rachel Nabors](https://twitter.com/rachelnabors) for spearheading our moderation effort, and [Aisha Blake](https://twitter.com/AishaBlake) for creating our moderation guide, leading our moderation team, training the translators and moderators, and helping to moderate both events. + +Thanks to our moderators [Jesslyn Tannady](https://twitter.com/jtannady), [Suzie Grange](https://twitter.com/missuze), [Becca Bailey](https://twitter.com/beccaliz), [Luna Wei](https://twitter.com/lunaleaps), [Joe Previte](https://twitter.com/jsjoeio), [Nicola Corti](https://twitter.com/Cortinico), [Gijs Weterings](https://twitter.com/gweterings), [Claudio Procida](https://twitter.com/claudiopro), Julia Neumann, Mengdi Chen, Jean Zhang, Ricky Li, and [Xuan Huang (黄玄)](https://twitter.com/Huxpro). + +Thanks to [Manjula Dube](https://twitter.com/manjula_dube), [Sahil Mhapsekar](https://twitter.com/apheri0), and Vihang Patel from [React India](https://www.reactindia.io/), and [Jasmine Xie](https://twitter.com/jasmine_xby), [QiChang Li](https://twitter.com/QCL15), and [YanLun Li](https://twitter.com/anneincoding) from [React China](https://twitter.com/ReactChina) for helping moderate our replay event and keep it engaging for the community. + +Thanks to Vercel for publishing their [Virtual Event Starter Kit](https://vercel.com/virtual-event-starter-kit), which the conference website was built on, and to [Lee Robinson](https://twitter.com/leeerob) and [Delba de Oliveira](https://twitter.com/delba_oliveira) for sharing their experience running Next.js Conf. + +Thanks to [Leah Silber](https://twitter.com/wifelette) for sharing her experience running conferences, learnings from running [RustConf](https://rustconf.com/), and for her book [Event Driven](https://leanpub.com/eventdriven/) and the advice it contains for running conferences. + +Thanks to [Kevin Lewis](https://twitter.com/_phzn) and [Rachel Nabors](https://twitter.com/rachelnabors) for sharing their experience running Women of React Conf. + +Thanks to [Aakansha Doshi](https://twitter.com/aakansha1216), [Laurie Barth](https://twitter.com/laurieontech), [Michael Chan](https://twitter.com/chantastic), and [Shaundai Person](https://twitter.com/shaundai) for their advice and ideas throughout planning. + +Thanks to [Dan Lebowitz](https://twitter.com/lebo) for help designing and building the conference website and tickets. + +Thanks to Laura Podolak Waddell, Desmond Osei-Acheampong, Mark Rossi, Josh Toberman and others on the Facebook Video Productions team for recording the videos for the Keynote and Meta employee talks. + +Thanks to our partner HitPlay for helping to organize the conference, editing all the videos in the stream, translating all the talks, and moderating the Discord in multiple languages. + +Finally, thanks to all of our participants for making this a great React Conf! diff --git a/content/blog/2022-03-08-react-18-upgrade-guide.md b/content/blog/2022-03-08-react-18-upgrade-guide.md new file mode 100644 index 000000000..40637eacd --- /dev/null +++ b/content/blog/2022-03-08-react-18-upgrade-guide.md @@ -0,0 +1,303 @@ +--- +title: "How to Upgrade to React 18" +author: [rickhanlonii] +--- + +As we shared in the [release post](/blog/2022/03/29/react-v18.html), React 18 introduces features powered by our new concurrent renderer, with a gradual adoption strategy for existing applications. In this post, we will guide you through the steps for upgrading to React 18. + +Please [report any issues](https://github.com/facebook/react/issues/new/choose) you encounter while upgrading to React 18. + +*Note for React Native users: React 18 will ship in a future version of React Native. This is because React 18 relies on the New React Native Architecture to benefit from the new capabilities presented in this blogpost. For more information, see the [React Conf keynote here](https://www.youtube.com/watch?v=FZ0cG47msEk&t=1530s).* + +## Installing {#installing} + +To install the latest version of React: + +```bash +npm install react react-dom +``` + +Or if you’re using yarn: + +```bash +yarn add react react-dom +``` + +## Updates to Client Rendering APIs {#updates-to-client-rendering-apis} + +When you first install React 18, you will see a warning in the console: + +> ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot + +React 18 introduces a new root API which provides better ergonomics for managing roots. The new root API also enables the new concurrent renderer, which allows you to opt-into concurrent features. + +```js +// Before +import { render } from 'react-dom'; +const container = document.getElementById('app'); +render(<App tab="home" />, container); + +// After +import { createRoot } from 'react-dom/client'; +const container = document.getElementById('app'); +const root = createRoot(container); // createRoot(container!) if you use TypeScript +root.render(<App tab="home" />); +``` + +We’ve also changed `unmountComponentAtNode` to `root.unmount`: + +```js +// Before +unmountComponentAtNode(container); + +// After +root.unmount(); +``` + +We've also removed the callback from render, since it usually does not have the expected result when using Suspense: + +```js +// Before +const container = document.getElementById('app'); +render(<App tab="home" />, container, () => { + console.log('rendered'); +}); + +// After +function AppWithCallbackAfterRender() { + useEffect(() => { + console.log('rendered'); + }); + + return <App tab="home" /> +} + +const container = document.getElementById('app'); +const root = createRoot(container); +root.render(<AppWithCallbackAfterRender />); +``` + +> Note: +> +> There is no one-to-one replacement for the old render callback API — it depends on your use case. See the working group post for [Replacing render with createRoot](https://github.com/reactwg/react-18/discussions/5) for more information. + +Finally, if your app uses server-side rendering with hydration, upgrade `hydrate` to `hydrateRoot`: + +```js +// Before +import { hydrate } from 'react-dom'; +const container = document.getElementById('app'); +hydrate(<App tab="home" />, container); + +// After +import { hydrateRoot } from 'react-dom/client'; +const container = document.getElementById('app'); +const root = hydrateRoot(container, <App tab="home" />); +// Unlike with createRoot, you don't need a separate root.render() call here. +``` + +For more information, see the [working group discussion here](https://github.com/reactwg/react-18/discussions/5). + +> Note +> +> **If your app doesn't work after upgrading, check whether it's wrapped in `<StrictMode>`.** [Strict Mode has gotten stricter in React 18](#updates-to-strict-mode), and not all your components may be resilient to the new checks it adds in development mode. If removing Strict Mode fixes your app, you can remove it during the upgrade, and then add it back (either at the top or for a part of the tree) after you fix the issues that it's pointing out. + +## Updates to Server Rendering APIs {#updates-to-server-rendering-apis} + +In this release, we’re revamping our `react-dom/server` APIs to fully support Suspense on the server and Streaming SSR. As part of these changes, we're deprecating the old Node streaming API, which does not support incremental Suspense streaming on the server. + +Using this API will now warn: + +* `renderToNodeStream`: **Deprecated ⛔️️** + +Instead, for streaming in Node environments, use: +* `renderToPipeableStream`: **New ✨** + +We're also introducing a new API to support streaming SSR with Suspense for modern edge runtime environments, such as Deno and Cloudflare workers: +* `renderToReadableStream`: **New ✨** + +The following APIs will continue working, but with limited support for Suspense: +* `renderToString`: **Limited** ⚠️ +* `renderToStaticMarkup`: **Limited** ⚠️ + +Finally, this API will continue to work for rendering e-mails: +* `renderToStaticNodeStream` + +For more information on the changes to server rendering APIs, see the working group post on [Upgrading to React 18 on the server](https://github.com/reactwg/react-18/discussions/22), a [deep dive on the new Suspense SSR Architecture](https://github.com/reactwg/react-18/discussions/37), and [Shaundai Person’s](https://twitter.com/shaundai) talk on [Streaming Server Rendering with Suspense](https://www.youtube.com/watch?v=pj5N-Khihgc) at React Conf 2021. + +## Updates to TypeScript definitions + +If your project uses TypeScript, you will need to update your `@types/react` and `@types/react-dom` dependencies to the latest versions. The new types are safer and catch issues that used to be ignored by the type checker. The most notable change is that the `children` prop now needs to be listed explicitly when defining props, for example: + +```typescript{3} +interface MyButtonProps { + color: string; + children?: React.ReactNode; +} +``` + +See the [React 18 typings pull request](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56210) for a full list of type-only changes. It links to example fixes in library types so you can see how to adjust your code. You can use the [automated migration script](https://github.com/eps1lon/types-react-codemod) to help port your application code to the new and safer typings faster. + +If you find a bug in the typings, please [file an issue](https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/new?category=issues-with-a-types-package) in the DefinitelyTyped repo. + +## Automatic Batching {#automatic-batching} + +React 18 adds out-of-the-box performance improvements by doing more batching by default. Batching is when React groups multiple state updates into a single re-render for better performance. Before React 18, we only batched updates inside React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default: + +```js +// Before React 18 only React events were batched + +function handleClick() { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +} + +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will render twice, once for each state update (no batching) +}, 1000); +``` + + +Starting in React 18 with `createRoot`, all updates will be automatically batched, no matter where they originate from. This means that updates inside of timeouts, promises, native event handlers or any other event will batch the same way as updates inside of React events: + +```js +// After React 18 updates inside of timeouts, promises, +// native event handlers or any other event are batched. + +function handleClick() { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +} + +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +}, 1000); +``` + +This is a breaking change, but we expect this to result in less work rendering, and therefore better performance in your applications. To opt-out of automatic batching, you can use `flushSync`: + +```js +import { flushSync } from 'react-dom'; + +function handleClick() { + flushSync(() => { + setCounter(c => c + 1); + }); + // React has updated the DOM by now + flushSync(() => { + setFlag(f => !f); + }); + // React has updated the DOM by now +} +``` + +For more information, see the [Automatic batching deep dive](https://github.com/reactwg/react-18/discussions/21). + +## New APIs for Libraries {#new-apis-for-libraries} + +In the React 18 Working Group we worked with library maintainers to create new APIs needed to support concurrent rendering for use cases specific to their use case in areas like styles, and external stores. To support React 18, some libraries may need to switch to one of the following APIs: + +* `useSyncExternalStore` is a new hook that allows external stores to support concurrent reads by forcing updates to the store to be synchronous. This new API is recommended for any library that integrates with state external to React. For more information, see the [useSyncExternalStore overview post](https://github.com/reactwg/react-18/discussions/70) and [useSyncExternalStore API details](https://github.com/reactwg/react-18/discussions/86). +* `useInsertionEffect` is a new hook that allows CSS-in-JS libraries to address performance issues of injecting styles in render. Unless you've already built a CSS-in-JS library we don't expect you to ever use this. This hook will run after the DOM is mutated, but before layout effects read the new layout. This solves an issue that already exists in React 17 and below, but is even more important in React 18 because React yields to the browser during concurrent rendering, giving it a chance to recalculate layout. For more information, see the [Library Upgrade Guide for `<style>`](https://github.com/reactwg/react-18/discussions/110). + +React 18 also introduces new APIs for concurrent rendering such as `startTransition`, `useDeferredValue` and `useId`, which we share more about in the [release post](/blog/2022/03/29/react-v18.html). + +## Updates to Strict Mode {#updates-to-strict-mode} + +In the future, we'd like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React would unmount and remount trees using the same component state as before. + +This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects assume they are only mounted or destroyed once. + +To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount. + +Before this change, React would mount the component and create the effects: + +``` +* React mounts the component. + * Layout effects are created. + * Effect effects are created. +``` + +With Strict Mode in React 18, React will simulate unmounting and remounting the component in development mode: + +``` +* React mounts the component. + * Layout effects are created. + * Effect effects are created. +* React simulates unmounting the component. + * Layout effects are destroyed. + * Effects are destroyed. +* React simulates mounting the component with the previous state. + * Layout effect setup code runs + * Effect setup code runs +``` + +For more information, see the Working Group posts for [Adding Reusable State to StrictMode](https://github.com/reactwg/react-18/discussions/19) and [How to support Reusable State in Effects](https://github.com/reactwg/react-18/discussions/18). + +## Configuring Your Testing Environment {#configuring-your-testing-environment} + +When you first update your tests to use `createRoot`, you may see this warning in your test console: + +> The current testing environment is not configured to support act(...) + +To fix this, set `globalThis.IS_REACT_ACT_ENVIRONMENT` to `true` before running your test: + +```js +// In your test setup file +globalThis.IS_REACT_ACT_ENVIRONMENT = true; +``` + +The purpose of the flag is to tell React that it's running in a unit test-like environment. React will log helpful warnings if you forget to wrap an update with `act`. + +You can also set the flag to `false` to tell React that `act` isn't needed. This can be useful for end-to-end tests that simulate a full browser environment. + +Eventually, we expect testing libraries will configure this for you automatically. For example, the [next version of React Testing Library has built-in support for React 18](https://github.com/testing-library/react-testing-library/issues/509#issuecomment-917989936) without any additional configuration. + +[More background on the `act` testing API and related changes](https://github.com/reactwg/react-18/discussions/102) is available in the working group. + +## Dropping Support for Internet Explorer {#dropping-support-for-internet-explorer} + +In this release, React is dropping support for Internet Explorer, which is [going out of support on June 15, 2022](https://blogs.windows.com/windowsexperience/2021/05/19/the-future-of-internet-explorer-on-windows-10-is-in-microsoft-edge). We’re making this change now because new features introduced in React 18 are built using modern browser features such as microtasks which cannot be adequately polyfilled in IE. + +If you need to support Internet Explorer we recommend you stay with React 17. + +## Deprecations {#deprecations} + +* `react-dom`: `ReactDOM.render` has been deprecated. Using it will warn and run your app in React 17 mode. +* `react-dom`: `ReactDOM.hydrate` has been deprecated. Using it will warn and run your app in React 17 mode. +* `react-dom`: `ReactDOM.unmountComponentAtNode` has been deprecated. +* `react-dom`: `ReactDOM.renderSubtreeIntoContainer` has been deprecated. +* `react-dom/server`: `ReactDOMServer.renderToNodeStream` has been deprecated. + +## Other Breaking Changes {#other-breaking-changes} + +* **Consistent useEffect timing**: React now always synchronously flushes effect functions if the update was triggered during a discrete user input event such as a click or a keydown event. Previously, the behavior wasn't always predictable or consistent. +* **Stricter hydration errors**: Hydration mismatches due to missing or extra text content are now treated like errors instead of warnings. React will no longer attempt to "patch up" individual nodes by inserting or deleting a node on the client in an attempt to match the server markup, and will revert to client rendering up to the closest `<Suspense>` boundary in the tree. This ensures the hydrated tree is consistent and avoids potential privacy and security holes that can be caused by hydration mismatches. +* **Suspense trees are always consistent:** If a component suspends before it's fully added to the tree, React will not add it to the tree in an incomplete state or fire its effects. Instead, React will throw away the new tree completely, wait for the asynchronous operation to finish, and then retry rendering again from scratch. React will render the retry attempt concurrently, and without blocking the browser. +* **Layout Effects with Suspense**: When a tree re-suspends and reverts to a fallback, React will now clean up layout effects, and then re-create them when the content inside the boundary is shown again. This fixes an issue which prevented component libraries from correctly measuring layout when used with Suspense. +* **New JS Environment Requirements**: React now depends on modern browsers features including `Promise`, `Symbol`, and `Object.assign`. If you support older browsers and devices such as Internet Explorer which do not provide modern browser features natively or have non-compliant implementations, consider including a global polyfill in your bundled application. + +## Other Notable Changes {#other-notable-changes} + +### React {#react} + +* **Components can now render `undefined`:** React no longer warns if you return `undefined` from a component. This makes the allowed component return values consistent with values that are allowed in the middle of a component tree. We suggest to use a linter to prevent mistakes like forgetting a `return` statement before JSX. +* **In tests, `act` warnings are now opt-in:** If you're running end-to-end tests, the `act` warnings are unnecessary. We've introduced an [opt-in](https://github.com/reactwg/react-18/discussions/102) mechanism so you can enable them only for unit tests where they are useful and beneficial. +* **No warning about `setState` on unmounted components:** Previously, React warned about memory leaks when you call `setState` on an unmounted component. This warning was added for subscriptions, but people primarily run into it in scenarios where setting state is fine, and workarounds make the code worse. We've [removed](https://github.com/facebook/react/pull/22114) this warning. +* **No suppression of console logs:** When you use Strict Mode, React renders each component twice to help you find unexpected side effects. In React 17, we've suppressed console logs for one of the two renders to make the logs easier to read. In response to [community feedback](https://github.com/facebook/react/issues/21783) about this being confusing, we've removed the suppression. Instead, if you have React DevTools installed, the second log's renders will be displayed in grey, and there will be an option (off by default) to suppress them completely. +* **Improved memory usage:** React now cleans up more internal fields on unmount, making the impact from unfixed memory leaks that may exist in your application code less severe. + +### React DOM Server {#react-dom-server} + +* **`renderToString`:** Will no longer error when suspending on the server. Instead, it will emit the fallback HTML for the closest `<Suspense>` boundary and then retry rendering the same content on the client. It is still recommended that you switch to a streaming API like `renderToPipeableStream` or `renderToReadableStream` instead. +* **`renderToStaticMarkup`:** Will no longer error when suspending on the server. Instead, it will emit the fallback HTML for the closest `<Suspense>` boundary. + +## Changelog {#changelog} + +You can view the [full changelog here](https://github.com/facebook/react/blob/main/CHANGELOG.md). diff --git a/content/blog/2022-03-29-react-v18.md b/content/blog/2022-03-29-react-v18.md new file mode 100644 index 000000000..5a5020df2 --- /dev/null +++ b/content/blog/2022-03-29-react-v18.md @@ -0,0 +1,331 @@ +--- +title: "React v18.0" +author: [reactteam] +--- + +React 18 is now available on npm! + +In our last post, we shared step-by-step instructions for [upgrading your app to React 18](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html). In this post, we'll give an overview of what's new in React 18, and what it means for the future. + +Our latest major version includes out-of-the-box improvements like automatic batching, new APIs like startTransition, and streaming server-side rendering with support for Suspense. + +Many of the features in React 18 are built on top of our new concurrent renderer, a behind-the-scenes change that unlocks powerful new capabilities. Concurrent React is opt-in — it's only enabled when you use a concurrent feature — but we think it will have a big impact on the way people build applications. + +We've spent years researching and developing support for concurrency in React, and we've taken extra care to provide a gradual adoption path for existing users. Last summer, [we formed the React 18 Working Group](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html) to gather feedback from experts in the community and ensure a smooth upgrade experience for the entire React ecosystem. + +In case you missed it, we shared a lot of this vision at React Conf 2021: + +* In [the keynote](https://www.youtube.com/watch?v=FZ0cG47msEk&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa), we explain how React 18 fits into our mission to make it easy for developers to build great user experiences +* [Shruti Kapoor](https://twitter.com/shrutikapoor08) [demonstrated how to use the new features in React 18](https://www.youtube.com/watch?v=ytudH8je5ko&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=2) +* [Shaundai Person](https://twitter.com/shaundai) gave us an overview of [streaming server rendering with Suspense](https://www.youtube.com/watch?v=pj5N-Khihgc&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=3) + +Below is a full overview of what to expect in this release, starting with Concurrent Rendering. + +*Note for React Native users: React 18 will ship in React Native with the New React Native Architecture. For more information, see the [React Conf keynote here](https://www.youtube.com/watch?v=FZ0cG47msEk&t=1530s).* + +## What is Concurrent React? {#what-is-concurrent-react} + +The most important addition in React 18 is something we hope you never have to think about: concurrency. We think this is largely true for application developers, though the story may be a bit more complicated for library maintainers. + +Concurrency is not a feature, per se. It's a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time. You can think of concurrency as an implementation detail — it's valuable because of the features that it unlocks. React uses sophisticated techniques in its internal implementation, like priority queues and multiple buffering. But you won't see those concepts anywhere in our public APIs. + +When we design APIs, we try to hide implementation details from developers. As a React developer, you focus on *what* you want the user experience to look like, and React handles *how* to deliver that experience. So we don’t expect React developers to know how concurrency works under the hood. + +However, Concurrent React is more important than a typical implementation detail — it's a foundational update to React's core rendering model. So while it's not super important to know how concurrency works, it may be worth knowing what it is at a high level. + +A key property of Concurrent React is that rendering is interruptible. When you first upgrade to React 18, before adding any concurrent features, updates are rendered the same as in previous versions of React — in a single, uninterrupted, synchronous transaction. With synchronous rendering, once an update starts rendering, nothing can interrupt it until the user can see the result on screen. + +In a concurrent render, this is not always the case. React may start rendering an update, pause in the middle, then continue later. It may even abandon an in-progress render altogether. React guarantees that the UI will appear consistent even if a render is interrupted. To do this, it waits to perform DOM mutations until the end, once the entire tree has been evaluated. With this capability, React can prepare new screens in the background without blocking the main thread. This means the UI can respond immediately to user input even if it’s in the middle of a large rendering task, creating a fluid user experience. + +Another example is reusable state. Concurrent React can remove sections of the UI from the screen, then add them back later while reusing the previous state. For example, when a user tabs away from a screen and back, React should be able to restore the previous screen in the same state it was in before. In an upcoming minor, we're planning to add a new component called `<Offscreen>` that implements this pattern. Similarly, you’ll be able to use Offscreen to prepare new UI in the background so that it’s ready before the user reveals it. + +Concurrent rendering is a powerful new tool in React and most of our new features are built to take advantage of it, including Suspense, transitions, and streaming server rendering. But React 18 is just the beginning of what we aim to build on this new foundation. + +## Gradually Adopting Concurrent Features {#gradually-adopting-concurrent-features} + +Technically, concurrent rendering is a breaking change. Because concurrent rendering is interruptible, components behave slightly differently when it is enabled. + +In our testing, we've upgraded thousands of components to React 18. What we've found is that nearly all existing components "just work" with concurrent rendering, without any changes. However, some of them may require some additional migration effort. Although the changes are usually small, you'll still have the ability to make them at your own pace. The new rendering behavior in React 18 is **only enabled in the parts of your app that use new features.** + +The overall upgrade strategy is to get your application running on React 18 without breaking existing code. Then you can gradually start adding concurrent features at your own pace. You can use [`<StrictMode>`](/docs/strict-mode.html) to help surface concurrency-related bugs during development. Strict Mode doesn't affect production behavior, but during development it will log extra warnings and double-invoke functions that are expected to be idempotent. It won't catch everything, but it's effective at preventing the most common types of mistakes. + +After you upgrade to React 18, you’ll be able to start using concurrent features immediately. For example, you can use startTransition to navigate between screens without blocking user input. Or useDeferredValue to throttle expensive re-renders. + +However, long term, we expect the main way you’ll add concurrency to your app is by using a concurrent-enabled library or framework. In most cases, you won’t interact with concurrent APIs directly. For example, instead of developers calling startTransition whenever they navigate to a new screen, router libraries will automatically wrap navigations in startTransition. + +It may take some time for libraries to upgrade to be concurrent compatible. We’ve provided new APIs to make it easier for libraries to take advantage of concurrent features. In the meantime, please be patient with maintainers as we work to gradually migrate the React ecosystem. + +For more info, see our previous post: [How to upgrade to React 18](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html). + +## Suspense in Data Frameworks {#suspense-in-data-frameworks} + +In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy. + +In the future, we may expose additional primitives that could make it easier to access your data with Suspense, perhaps without the use of an opinionated framework. However, Suspense works best when it’s deeply integrated into your application’s architecture: your router, your data layer, and your server rendering environment. So even long term, we expect that libraries and frameworks will play a crucial role in the React ecosystem. + +As in previous versions of React, you can also use Suspense for code splitting on the client with React.lazy. But our vision for Suspense has always been about much more than loading code — the goal is to extend support for Suspense so that eventually, the same declarative Suspense fallback can handle any asynchronous operation (loading code, data, images, etc). + +## Server Components is Still in Development {#server-components-is-still-in-development} + +[**Server Components**](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html) is an upcoming feature that allows developers to build apps that span the server and client, combining the rich interactivity of client-side apps with the improved performance of traditional server rendering. Server Components is not inherently coupled to Concurrent React, but it’s designed to work best with concurrent features like Suspense and streaming server rendering. + +Server Components is still experimental, but we expect to release an initial version in a minor 18.x release. In the meantime, we’re working with frameworks like Next.js, Hydrogen, and Remix to advance the proposal and get it ready for broad adoption. + +## What's New in React 18 {#whats-new-in-react-18} + +### New Feature: Automatic Batching {#new-feature-automatic-batching} + +Batching is when React groups multiple state updates into a single re-render for better performance. Without automatic batching, we only batched updates inside React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default. With automatic batching, these updates will be batched automatically: + + +```js +// Before: only React events were batched. +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will render twice, once for each state update (no batching) +}, 1000); + +// After: updates inside of timeouts, promises, +// native event handlers or any other event are batched. +setTimeout(() => { + setCount(c => c + 1); + setFlag(f => !f); + // React will only re-render once at the end (that's batching!) +}, 1000); +``` + +For more info, see this post for [Automatic batching for fewer renders in React 18](https://github.com/reactwg/react-18/discussions/21). + +### New Feature: Transitions {#new-feature-transitions} + +A transition is a new concept in React to distinguish between urgent and non-urgent updates. + +* **Urgent updates** reflect direct interaction, like typing, clicking, pressing, and so on. +* **Transition updates** transition the UI from one view to another. + +Urgent updates like typing, clicking, or pressing, need immediate response to match our intuitions about how physical objects behave. Otherwise they feel "wrong". However, transitions are different because the user doesn’t expect to see every intermediate value on screen. + +For example, when you select a filter in a dropdown, you expect the filter button itself to respond immediately when you click. However, the actual results may transition separately. A small delay would be imperceptible and often expected. And if you change the filter again before the results are done rendering, you only care to see the latest results. + +Typically, for the best user experience, a single user input should result in both an urgent update and a non-urgent one. You can use startTransition API inside an input event to inform React which updates are urgent and which are "transitions": + + +```js +import {startTransition} from 'react'; + +// Urgent: Show what was typed +setInputValue(input); + +// Mark any state updates inside as transitions +startTransition(() => { + // Transition: Show the results + setSearchQuery(input); +}); +``` + + +Updates wrapped in startTransition are handled as non-urgent and will be interrupted if more urgent updates like clicks or key presses come in. If a transition gets interrupted by the user (for example, by typing multiple characters in a row), React will throw out the stale rendering work that wasn’t finished and render only the latest update. + + +* `useTransition`: a hook to start transitions, including a value to track the pending state. +* `startTransition`: a method to start transitions when the hook cannot be used. + +Transitions will opt in to concurrent rendering, which allows the update to be interrupted. If the content re-suspends, transitions also tell React to continue showing the current content while rendering the transition content in the background (see the [Suspense RFC](https://github.com/reactjs/rfcs/blob/main/text/0213-suspense-in-react-18.md) for more info). + +[See docs for transitions here](/docs/react-api.html#transitions). + +### New Suspense Features {#new-suspense-features} + +Suspense lets you declaratively specify the loading state for a part of the component tree if it's not yet ready to be displayed: + +```js +<Suspense fallback={<Spinner />}> + <Comments /> +</Suspense> +``` + +Suspense makes the "UI loading state" a first-class declarative concept in the React programming model. This lets us build higher-level features on top of it. + +We introduced a limited version of Suspense several years ago. However, the only supported use case was code splitting with React.lazy, and it wasn't supported at all when rendering on the server. + +In React 18, we've added support for Suspense on the server and expanded its capabilities using concurrent rendering features. + +Suspense in React 18 works best when combined with the transition API. If you suspend during a transition, React will prevent already-visible content from being replaced by a fallback. Instead, React will delay the render until enough data has loaded to prevent a bad loading state. + +For more, see the RFC for [Suspense in React 18](https://github.com/reactjs/rfcs/blob/main/text/0213-suspense-in-react-18.md). + +### New Client and Server Rendering APIs {#new-client-and-server-rendering-apis} + +In this release we took the opportunity to redesign the APIs we expose for rendering on the client and server. These changes allow users to continue using the old APIs in React 17 mode while they upgrade to the new APIs in React 18. + +#### React DOM Client {#react-dom-client} + +These new APIs are now exported from `react-dom/client`: + +* `createRoot`: New method to create a root to `render` or `unmount`. Use it instead of `ReactDOM.render`. New features in React 18 don't work without it. +* `hydrateRoot`: New method to hydrate a server rendered application. Use it instead of `ReactDOM.hydrate` in conjunction with the new React DOM Server APIs. New features in React 18 don't work without it. + +Both `createRoot` and `hydrateRoot` accept a new option called `onRecoverableError` in case you want to be notified when React recovers from errors during rendering or hydration for logging. By default, React will use [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError), or `console.error` in the older browsers. + +[See docs for React DOM Client here](/docs/react-dom-client.html). + +#### React DOM Server {#react-dom-server} + +These new APIs are now exported from `react-dom/server` and have full support for streaming Suspense on the server: + +* `renderToPipeableStream`: for streaming in Node environments. +* `renderToReadableStream`: for modern edge runtime environments, such as Deno and Cloudflare workers. + +The existing `renderToString` method keeps working but is discouraged. + +[See docs for React DOM Server here](/docs/react-dom-server.html). + +### New Strict Mode Behaviors {#new-strict-mode-behaviors} + +In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React would unmount and remount trees using the same component state as before. + +This feature will give React apps better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects assume they are only mounted or destroyed once. + +To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount. + +Before this change, React would mount the component and create the effects: + +``` +* React mounts the component. + * Layout effects are created. + * Effects are created. +``` + + +With Strict Mode in React 18, React will simulate unmounting and remounting the component in development mode: + +``` +* React mounts the component. + * Layout effects are created. + * Effects are created. +* React simulates unmounting the component. + * Layout effects are destroyed. + * Effects are destroyed. +* React simulates mounting the component with the previous state. + * Layout effects are created. + * Effects are created. +``` + +[See docs for ensuring reusable state here](/docs/strict-mode.html#ensuring-reusable-state). + +### New Hooks {#new-hooks} + +#### useId {#useid} + +`useId` is a new hook for generating unique IDs on both the client and server, while avoiding hydration mismatches. It is primarily useful for component libraries integrating with accessibility APIs that require unique IDs. This solves an issue that already exists in React 17 and below, but it's even more important in React 18 because of how the new streaming server renderer delivers HTML out-of-order. [See docs here](/docs/hooks-reference.html#useid). + +> Note +> +> `useId` is **not** for generating [keys in a list](/docs/lists-and-keys.html#keys). Keys should be generated from your data. + +#### useTransition {#usetransition} + +`useTransition` and `startTransition` let you mark some state updates as not urgent. Other state updates are considered urgent by default. React will allow urgent state updates (for example, updating a text input) to interrupt non-urgent state updates (for example, rendering a list of search results). [See docs here](/docs/hooks-reference.html#usetransition) + +#### useDeferredValue {#usedeferredvalue} + +`useDeferredValue` lets you defer re-rendering a non-urgent part of the tree. It is similar to debouncing, but has a few advantages compared to it. There is no fixed time delay, so React will attempt the deferred render right after the first render is reflected on the screen. The deferred render is interruptible and doesn't block user input. [See docs here](/docs/hooks-reference.html#usedeferredvalue). + +#### useSyncExternalStore {#usesyncexternalstore} + +`useSyncExternalStore` is a new hook that allows external stores to support concurrent reads by forcing updates to the store to be synchronous. It removes the need for useEffect when implementing subscriptions to external data sources, and is recommended for any library that integrates with state external to React. [See docs here](/docs/hooks-reference.html#usesyncexternalstore). + +> Note +> +> `useSyncExternalStore` is intended to be used by libraries, not application code. + +#### useInsertionEffect {#useinsertioneffect} + +`useInsertionEffect` is a new hook that allows CSS-in-JS libraries to address performance issues of injecting styles in render. Unless you’ve already built a CSS-in-JS library we don’t expect you to ever use this. This hook will run after the DOM is mutated, but before layout effects read the new layout. This solves an issue that already exists in React 17 and below, but is even more important in React 18 because React yields to the browser during concurrent rendering, giving it a chance to recalculate layout. [See docs here](/docs/hooks-reference.html#useinsertioneffect). + +> Note +> +> `useInsertionEffect` is intended to be used by libraries, not application code. + +## How to Upgrade {#how-to-upgrade} + +See [How to Upgrade to React 18](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html) for step-by-step instructions and a full list of breaking and notable changes. + +## Changelog {#changelog} + +### React {#react} + +* Add `useTransition` and `useDeferredValue` to separate urgent updates from transitions. ([#10426](https://github.com/facebook/react/pull/10426), [#10715](https://github.com/facebook/react/pull/10715), [#15593](https://github.com/facebook/react/pull/15593), [#15272](https://github.com/facebook/react/pull/15272), [#15578](https://github.com/facebook/react/pull/15578), [#15769](https://github.com/facebook/react/pull/15769), [#17058](https://github.com/facebook/react/pull/17058), [#18796](https://github.com/facebook/react/pull/18796), [#19121](https://github.com/facebook/react/pull/19121), [#19703](https://github.com/facebook/react/pull/19703), [#19719](https://github.com/facebook/react/pull/19719), [#19724](https://github.com/facebook/react/pull/19724), [#20672](https://github.com/facebook/react/pull/20672), [#20976](https://github.com/facebook/react/pull/20976) by [@acdlite](https://github.com/acdlite), [@lunaruan](https://github.com/lunaruan), [@rickhanlonii](https://github.com/rickhanlonii), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `useId` for generating unique IDs. ([#17322](https://github.com/facebook/react/pull/17322), [#18576](https://github.com/facebook/react/pull/18576), [#22644](https://github.com/facebook/react/pull/22644), [#22672](https://github.com/facebook/react/pull/22672), [#21260](https://github.com/facebook/react/pull/21260) by [@acdlite](https://github.com/acdlite), [@lunaruan](https://github.com/lunaruan), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `useSyncExternalStore` to help external store libraries integrate with React. ([#15022](https://github.com/facebook/react/pull/15022), [#18000](https://github.com/facebook/react/pull/18000), [#18771](https://github.com/facebook/react/pull/18771), [#22211](https://github.com/facebook/react/pull/22211), [#22292](https://github.com/facebook/react/pull/22292), [#22239](https://github.com/facebook/react/pull/22239), [#22347](https://github.com/facebook/react/pull/22347), [#23150](https://github.com/facebook/react/pull/23150) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), and [@drarmstr](https://github.com/drarmstr)) +* Add `startTransition` as a version of `useTransition` without pending feedback. ([#19696](https://github.com/facebook/react/pull/19696) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Add `useInsertionEffect` for CSS-in-JS libraries. ([#21913](https://github.com/facebook/react/pull/21913) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Make Suspense remount layout effects when content reappears. ([#19322](https://github.com/facebook/react/pull/19322), [#19374](https://github.com/facebook/react/pull/19374), [#19523](https://github.com/facebook/react/pull/19523), [#20625](https://github.com/facebook/react/pull/20625), [#21079](https://github.com/facebook/react/pull/21079) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), and [@lunaruan](https://github.com/lunaruan)) +* Make `<StrictMode>` re-run effects to check for restorable state. ([#19523](https://github.com/facebook/react/pull/19523) , [#21418](https://github.com/facebook/react/pull/21418) by [@bvaughn](https://github.com/bvaughn) and [@lunaruan](https://github.com/lunaruan)) +* Assume Symbols are always available. ([#23348](https://github.com/facebook/react/pull/23348) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Remove `object-assign` polyfill. ([#23351](https://github.com/facebook/react/pull/23351) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Remove unsupported `unstable_changedBits` API. ([#20953](https://github.com/facebook/react/pull/20953) by [@acdlite](https://github.com/acdlite)) +* Allow components to render undefined. ([#21869](https://github.com/facebook/react/pull/21869) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Flush `useEffect` resulting from discrete events like clicks synchronously. ([#21150](https://github.com/facebook/react/pull/21150) by [@acdlite](https://github.com/acdlite)) +* Suspense `fallback={undefined}` now behaves the same as `null` and isn't ignored. ([#21854](https://github.com/facebook/react/pull/21854) by [@rickhanlonii](https://github.com/rickhanlonii)) +* Consider all `lazy()` resolving to the same component equivalent. ([#20357](https://github.com/facebook/react/pull/20357) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Don't patch console during first render. ([#22308](https://github.com/facebook/react/pull/22308) by [@lunaruan](https://github.com/lunaruan)) +* Improve memory usage. ([#21039](https://github.com/facebook/react/pull/21039) by [@bgirard](https://github.com/bgirard)) +* Improve messages if string coercion throws (Temporal.*, Symbol, etc.) ([#22064](https://github.com/facebook/react/pull/22064) by [@justingrant](https://github.com/justingrant)) +* Use `setImmediate` when available over `MessageChannel`. ([#20834](https://github.com/facebook/react/pull/20834) by [@gaearon](https://github.com/gaearon)) +* Fix context failing to propagate inside suspended trees. ([#23095](https://github.com/facebook/react/pull/23095) by [@gaearon](https://github.com/gaearon)) +* Fix `useReducer` observing incorrect props by removing the eager bailout mechanism. ([#22445](https://github.com/facebook/react/pull/22445) by [@josephsavona](https://github.com/josephsavona)) +* Fix `setState` being ignored in Safari when appending iframes. ([#23111](https://github.com/facebook/react/pull/23111) by [@gaearon](https://github.com/gaearon)) +* Fix a crash when rendering `ZonedDateTime` in the tree. ([#20617](https://github.com/facebook/react/pull/20617) by [@dimaqq](https://github.com/dimaqq)) +* Fix a crash when document is set to `null` in tests. ([#22695](https://github.com/facebook/react/pull/22695) by [@SimenB](https://github.com/SimenB)) +* Fix `onLoad` not triggering when concurrent features are on. ([#23316](https://github.com/facebook/react/pull/23316) by [@gnoff](https://github.com/gnoff)) +* Fix a warning when a selector returns `NaN`. ([#23333](https://github.com/facebook/react/pull/23333) by [@hachibeeDI](https://github.com/hachibeeDI)) +* Fix a crash when document is set to `null` in tests. ([#22695](https://github.com/facebook/react/pull/22695) by [@SimenB](https://github.com/SimenB)) +* Fix the generated license header. ([#23004](https://github.com/facebook/react/pull/23004) by [@vitaliemiron](https://github.com/vitaliemiron)) +* Add `package.json` as one of the entry points. ([#22954](https://github.com/facebook/react/pull/22954) by [@Jack](https://github.com/Jack-Works)) +* Allow suspending outside a Suspense boundary. ([#23267](https://github.com/facebook/react/pull/23267) by [@acdlite](https://github.com/acdlite)) +* Log a recoverable error whenever hydration fails. ([#23319](https://github.com/facebook/react/pull/23319) by [@acdlite](https://github.com/acdlite)) + +### React DOM {#react-dom} + +* Add `createRoot` and `hydrateRoot`. ([#10239](https://github.com/facebook/react/pull/10239), [#11225](https://github.com/facebook/react/pull/11225), [#12117](https://github.com/facebook/react/pull/12117), [#13732](https://github.com/facebook/react/pull/13732), [#15502](https://github.com/facebook/react/pull/15502), [#15532](https://github.com/facebook/react/pull/15532), [#17035](https://github.com/facebook/react/pull/17035), [#17165](https://github.com/facebook/react/pull/17165), [#20669](https://github.com/facebook/react/pull/20669), [#20748](https://github.com/facebook/react/pull/20748), [#20888](https://github.com/facebook/react/pull/20888), [#21072](https://github.com/facebook/react/pull/21072), [#21417](https://github.com/facebook/react/pull/21417), [#21652](https://github.com/facebook/react/pull/21652), [#21687](https://github.com/facebook/react/pull/21687), [#23207](https://github.com/facebook/react/pull/23207), [#23385](https://github.com/facebook/react/pull/23385) by [@acdlite](https://github.com/acdlite), [@bvaughn](https://github.com/bvaughn), [@gaearon](https://github.com/gaearon), [@lunaruan](https://github.com/lunaruan), [@rickhanlonii](https://github.com/rickhanlonii), [@trueadm](https://github.com/trueadm), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add selective hydration. ([#14717](https://github.com/facebook/react/pull/14717), [#14884](https://github.com/facebook/react/pull/14884), [#16725](https://github.com/facebook/react/pull/16725), [#16880](https://github.com/facebook/react/pull/16880), [#17004](https://github.com/facebook/react/pull/17004), [#22416](https://github.com/facebook/react/pull/22416), [#22629](https://github.com/facebook/react/pull/22629), [#22448](https://github.com/facebook/react/pull/22448), [#22856](https://github.com/facebook/react/pull/22856), [#23176](https://github.com/facebook/react/pull/23176) by [@acdlite](https://github.com/acdlite), [@gaearon](https://github.com/gaearon), [@salazarm](https://github.com/salazarm), and [@sebmarkbage](https://github.com/sebmarkbage)) +* Add `aria-description` to the list of known ARIA attributes. ([#22142](https://github.com/facebook/react/pull/22142) by [@mahyareb](https://github.com/mahyareb)) +* Add `onResize` event to video elements. ([#21973](https://github.com/facebook/react/pull/21973) by [@rileyjshaw](https://github.com/rileyjshaw)) +* Add `imageSizes` and `imageSrcSet` to known props. ([#22550](https://github.com/facebook/react/pull/22550) by [@eps1lon](https://github.com/eps1lon)) +* Allow non-string `<option>` children if `value` is provided. ([#21431](https://github.com/facebook/react/pull/21431) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix `aspectRatio` style not being applied. ([#21100](https://github.com/facebook/react/pull/21100) by [@gaearon](https://github.com/gaearon)) +* Warn if `renderSubtreeIntoContainer` is called. ([#23355](https://github.com/facebook/react/pull/23355) by [@acdlite](https://github.com/acdlite)) + +### React DOM Server {#react-dom-server-1} + +* Add the new streaming renderer. ([#14144](https://github.com/facebook/react/pull/14144), [#20970](https://github.com/facebook/react/pull/20970), [#21056](https://github.com/facebook/react/pull/21056), [#21255](https://github.com/facebook/react/pull/21255), [#21200](https://github.com/facebook/react/pull/21200), [#21257](https://github.com/facebook/react/pull/21257), [#21276](https://github.com/facebook/react/pull/21276), [#22443](https://github.com/facebook/react/pull/22443), [#22450](https://github.com/facebook/react/pull/22450), [#23247](https://github.com/facebook/react/pull/23247), [#24025](https://github.com/facebook/react/pull/24025), [#24030](https://github.com/facebook/react/pull/24030) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix context providers in SSR when handling multiple requests. ([#23171](https://github.com/facebook/react/pull/23171) by [@frandiox](https://github.com/frandiox)) +* Revert to client render on text mismatch. ([#23354](https://github.com/facebook/react/pull/23354) by [@acdlite](https://github.com/acdlite)) +* Deprecate `renderToNodeStream`. ([#23359](https://github.com/facebook/react/pull/23359) by [@sebmarkbage](https://github.com/sebmarkbage)) +* Fix a spurious error log in the new server renderer. ([#24043](https://github.com/facebook/react/pull/24043) by [@eps1lon](https://github.com/eps1lon)) +* Fix a bug in the new server renderer. ([#22617](https://github.com/facebook/react/pull/22617) by [@shuding](https://github.com/shuding)) +* Ignore function and symbol values inside custom elements on the server. ([#21157](https://github.com/facebook/react/pull/21157) by [@sebmarkbage](https://github.com/sebmarkbage)) + +### React DOM Test Utils {#react-dom-test-utils} + +* Throw when `act` is used in production. ([#21686](https://github.com/facebook/react/pull/21686) by [@acdlite](https://github.com/acdlite)) +* Support disabling spurious act warnings with `global.IS_REACT_ACT_ENVIRONMENT`. ([#22561](https://github.com/facebook/react/pull/22561) by [@acdlite](https://github.com/acdlite)) +* Expand act warning to cover all APIs that might schedule React work. ([#22607](https://github.com/facebook/react/pull/22607) by [@acdlite](https://github.com/acdlite)) +* Make `act` batch updates. ([#21797](https://github.com/facebook/react/pull/21797) by [@acdlite](https://github.com/acdlite)) +* Remove warning for dangling passive effects. ([#22609](https://github.com/facebook/react/pull/22609) by [@acdlite](https://github.com/acdlite)) + +### React Refresh {#react-refresh} + +* Track late-mounted roots in Fast Refresh. ([#22740](https://github.com/facebook/react/pull/22740) by [@anc95](https://github.com/anc95)) +* Add `exports` field to `package.json`. ([#23087](https://github.com/facebook/react/pull/23087) by [@otakustay](https://github.com/otakustay)) + +### Server Components (Experimental) {#server-components-experimental} + +* Add Server Context support. ([#23244](https://github.com/facebook/react/pull/23244) by [@salazarm](https://github.com/salazarm)) +* Add `lazy` support. ([#24068](https://github.com/facebook/react/pull/24068) by [@gnoff](https://github.com/gnoff)) +* Update webpack plugin for webpack 5 ([#22739](https://github.com/facebook/react/pull/22739) by [@michenly](https://github.com/michenly)) +* Fix a mistake in the Node loader. ([#22537](https://github.com/facebook/react/pull/22537) by [@btea](https://github.com/btea)) +* Use `globalThis` instead of `window` for edge environments. ([#22777](https://github.com/facebook/react/pull/22777) by [@huozhi](https://github.com/huozhi)) + diff --git a/content/blog/2022-06-15-react-labs-what-we-have-been-working-on-june-2022.md b/content/blog/2022-06-15-react-labs-what-we-have-been-working-on-june-2022.md new file mode 100644 index 000000000..110c51c46 --- /dev/null +++ b/content/blog/2022-06-15-react-labs-what-we-have-been-working-on-june-2022.md @@ -0,0 +1,70 @@ +--- +title: "React Labs: What We've Been Working On – June 2022" +author: [acdlite,gaearon,kassens,josephsavona,joshcstory,laurentan,lunaruan,mengdichen,rickhanlonii,robertzhang,gsathya,sebmarkbage,huxpro] +--- + +[React 18](https://reactjs.org/blog/2022/03/29/react-v18.html) was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring. + +We typically have a number of projects being worked on at any time, ranging from the more experimental to the clearly defined. Looking ahead, we’d like to start regularly sharing more about what we’ve been working on with the community across these projects. + +To set expectations, this is not a roadmap with clear timelines. Many of these projects are under active research and are difficult to put concrete ship dates on. They may possibly never even ship in their current iteration depending on what we learn. Instead, we want to share with you the problem spaces we’re actively thinking about, and what we’ve learned so far. + +## Server Components {#server-components} + +We announced an [experimental demo of React Server Components](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html) (RSC) in December 2020. Since then we’ve been finishing up its dependencies in React 18, and working on changes inspired by experimental feedback. + +In particular, we’re abandoning the idea of having forked I/O libraries (eg react-fetch), and instead adopting an async/await model for better compatibility. This doesn’t technically block RSC’s release because you can also use routers for data fetching. Another change is that we’re also moving away from the file extension approach in favor of [annotating boundaries](https://github.com/reactjs/rfcs/pull/189#issuecomment-1116482278). + +We’re working together with Vercel and Shopify to unify bundler support for shared semantics in both Webpack and Vite. Before launch, we want to make sure that the semantics of RSCs are the same across the whole React ecosystem. This is the major blocker for reaching stable. + +## Asset Loading {#asset-loading} + +Currently, assets like scripts, external styles, fonts, and images are typically preloaded and loaded using external systems. This can make it tricky to coordinate across new environments like streaming, server components, and more. +We’re looking at adding APIs to preload and load deduplicated external assets through React APIs that work in all React environments. + +We’re also looking at having these support Suspense so you can have images, CSS, and fonts that block display until they’re loaded but don’t block streaming and concurrent rendering. This can help avoid [“popcorning“](https://twitter.com/sebmarkbage/status/1516852731251724293) as the visuals pop and layout shifts. + +## Static Server Rendering Optimizations {#static-server-rendering-optimizations} + +Static Site Generation (SSG) and Incremental Static Regeneration (ISR) are great ways to get performance for cacheable pages, but we think we can add features to improve performance of dynamic Server Side Rendering (SSR) – especially when most but not all of the content is cacheable. We're exploring ways to optimize server rendering utilizing compilation and static passes. + +## React Optimizing Compiler {#react-compiler} + +We gave an [early preview](https://www.youtube.com/watch?v=lGEMwh32soc) of React Forget at React Conf 2021. It’s a compiler that automatically generates the equivalent of `useMemo` and `useCallback` calls to minimize the cost of re-rendering, while retaining React’s programming model. + +Recently, we finished a rewrite of the compiler to make it more reliable and capable. This new architecture allows us to analyze and memoize more complex patterns such as the use of [local mutations](https://beta.reactjs.org/learn/keeping-components-pure#local-mutation-your-components-little-secret), and opens up many new compile-time optimization opportunities beyond just being on par with memoization hooks. + +We’re also working on a playground for exploring many aspects of the compiler. While the goal of the playground is to make development of the compiler easier, we think that it will make it easier to try it out and build intuition for what the compiler does. It reveals various insights into how it works under the hood, and live renders the compiler’s outputs as you type. This will be shipped together with the compiler when it’s released. + +## Offscreen {#offscreen} + +Today, if you want to hide and show a component, you have two options. One is to add or remove it from the tree completely. The problem with this approach is that the state of your UI is lost each time you unmount, including state stored in the DOM, like scroll position. + +The other option is to keep the component mounted and toggle the appearance visually using CSS. This preserves the state of your UI, but it comes at a performance cost, because React must keep rendering the hidden component and all of its children whenever it receives new updates. + +Offscreen introduces a third option: hide the UI visually, but deprioritize its content. The idea is similar in spirit to the `content-visibility` CSS property: when content is hidden, it doesn't need to stay in sync with the rest of the UI. React can defer the rendering work until the rest of the app is idle, or until the content becomes visible again. + +Offscreen is a low level capability that unlocks high level features. Similar to React's other concurrent features like `startTransition`, in most cases you won't interact with the Offscreen API directly, but instead via an opinionated framework to implement patterns like: + +* **Instant transitions.** Some routing frameworks already prefetch data to speed up subsequent navigations, like when hovering over a link. With Offscreen, they'll also be able to prerender the next screen in the background. +* **Reusable state.** Similarly, when navigating between routes or tabs, you can use Offscreen to preserve the state of the previous screen so you can switch back and pick up where you left off. +* **Virtualized list rendering.** When displaying large lists of items, virtualized list frameworks will prerender more rows than are currently visible. You can use Offscreen to prerender the hidden rows at a lower priority than the visible items in the list. +* **Backgrounded content.** We're also exploring a related feature for deprioritizing content in the background without hiding it, like when displaying a modal overlay. + +## Transition Tracing {#transition-tracing} + +Currently, React has two profiling tools. The [original Profiler](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) shows an overview of all the commits in a profiling session. For each commit, it also shows all components that rendered and the amount of time it took for them to render. We also have a beta version of a [Timeline Profiler](https://github.com/reactwg/react-18/discussions/76) introduced in React 18 that shows when components schedule updates and when React works on these updates. Both of these profilers help developers identify performance problems in their code. + +We’ve realized that developers don’t find knowing about individual slow commits or components out of context that useful. It’s more useful to know about what actually causes the slow commits. And that developers want to be able to track specific interactions (eg a button click, an initial load, or a page navigation) to watch for performance regressions and to understand why an interaction was slow and how to fix it. + +We previously tried to solve this issue by creating an [Interaction Tracing API](https://gist.github.com/bvaughn/8de925562903afd2e7a12554adcdda16), but it had some fundamental design flaws that reduced the accuracy of tracking why an interaction was slow and sometimes resulted in interactions never ending. We ended up [removing this API](https://github.com/facebook/react/pull/20037) because of these issues. + +We are working on a new version for the Interaction Tracing API (tentatively called Transition Tracing because it is initiated via `startTransition`) that solves these problems. + +## New React Docs {#new-react-docs} + +Last year, we announced the [beta version](https://beta.reactjs.org/) of the new React documentation website. The new learning materials teach Hooks first and has new diagrams, illustrations, as well as many interactive examples and challenges. We took a break from that work to focus on the React 18 release, but now that React 18 is out, we’re actively working to finish and ship the new documentation. + +We are currently writing a detailed section about effects, as we’ve heard that is one of the more challenging topics for both new and experienced React users. [Synchronizing with Effects](https://beta.reactjs.org/learn/synchronizing-with-effects) is the first published page in the series, and there are more to come in the following weeks. When we first started writing a detailed section about effects, we’ve realized that many common effect patterns can be simplified by adding a new primitive to React. We’ve shared some initial thoughts on that in the [useEvent RFC](https://github.com/reactjs/rfcs/pull/220). It is currently in early research, and we are still iterating on the idea. We appreciate the community’s comments on the RFC so far, as well as the [feedback](https://github.com/reactjs/reactjs.org/issues/3308) and contributions to the ongoing documentation rewrite. We’d specifically like to thank [Harish Kumar](https://github.com/harish-sethuraman) for submitting and reviewing many improvements to the new website implementation. + +*Thanks to [Sophie Alpert](https://twitter.com/sophiebits) for reviewing this blog post!* diff --git a/content/community/conferences.md b/content/community/conferences.md index 0772b69e1..cb47a118b 100644 --- a/content/community/conferences.md +++ b/content/community/conferences.md @@ -12,501 +12,674 @@ Do you know of a local React.js conference? Add it here! (Please keep the list c ## Upcoming Conferences {#upcoming-conferences} -### ReactConf Japan 2020 {#reactconfjp-2020} -March 21, 2020 in Tokyo, Japan -[Website](https://reactconf.jp/) - [Twitter](https://twitter.com/reactjp) +### RemixConf 2023 {#remixconf-2023} +May, 2023. Salt Lake City, UT -### Reactathon 2020 {#reactathon-2020} -March 30 - 31, 2020 in San Francisco, CA +[Website](https://remix.run/conf/2023) - [Twitter](https://twitter.com/remix_run) -[Website](https://www.reactathon.com) - [Twitter](https://twitter.com/reactathon) - [Facebook](https://www.facebook.com/events/575942819854160/) +### App.js Conf 2023 {#appjs-conf-2023} +May 10 - 12, 2023. In-person in Kraków, Poland + remote -### React Summit Amsterdam 2020 {#react-summit-2020} -April 15-17, 2020 in Amsterdam, The Netherlands +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) -[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactamsterdam) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) +### Render(ATL) 2023 🍑 {#render-atlanta-2023} +May 31 - June 2, 2023. Atlanta, GA, USA -### App.js Conf 2020 {#appjsonf2020} -April 23 - 24, 2020 in Kraków, Poland +[Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/) -[Website](http://appjs.co/react) - [Twitter](https://twitter.com/appjsconf) +### React Summit 2023 {#react-summit-2023} +June 2 & 6, 2023. In-person in Amsterdam, Netherlands + remote first interactivity (hybrid event) -### React Day Bangalore 2020 {#react-day-bangalore-2020} -April 25, 2020 in Bangalore, India +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) -[Website](https://reactday.in) - [Twitter](https://twitter.com/ReactDayIn) - [LinkedIn](https://www.linkedin.com/company/react-day/) +### ReactNext 2023 {#reactnext} +June 27th, 2023. Tel Aviv, Israel -### Byteconf React 2020 {#byteconf-react-2020} -May 1, 2020. Streamed online on YouTube. +[Website](https://www.react-next.com/) - [Facebook](https://www.facebook.com/ReactNextConf) - [Youtube](https://www.youtube.com/@ReactNext) -[Website](https://www.bytesized.xyz) - [Twitter](https://twitter.com/bytesizedcode) - [YouTube](https://www.youtube.com/channel/UC046lFvJZhiwSRWsoH8SFjg) +### React Nexus 2023 {#react-nexus-2023} +July 07 & 08, 2023. In-person in Bangalore, India -### render(ATL) 2020 {#render-atlanta-2020} -May 4-6, 2020. Atlanta, GA, USA. +[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) -[Website](https://renderatl.com) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) +### React India 2023 {#react-india-2023} +Oct 5 - 7, 2023. In-person in Goa, India (hybrid event) + Oct 3 2023 - remote day -### ReactEurope 2020 {#reacteurope-2020} -May 14-15, 2020 in Paris, France +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Youtube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) -[Website](https://www.react-europe.org) - [Twitter](https://twitter.com/ReactEurope) - [Facebook](https://www.facebook.com/ReactEurope) - [Videos](https://www.youtube.com/c/ReacteuropeOrgConf) +## Past Conferences {#past-conferences} -### React Finland 2020 {#react-finland-2020} -May 26-29 in Helsinki, Finland +### React Day Berlin 2022 {#react-day-berlin-2022} +December 2, 2022. In-person in Berlin, Germany + remote (hybrid event) -[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/c/ReactConferences) -### React Next 2020 {#react-next-2020} -June 15, 2020. Tel Aviv, Israel. +### Remix Conf Europe 2022 {#remixconfeu-2022} +November 18, 2022, 7am PST / 10am EST / 4pm CET - remote event -[Website](https://react-next.com/) - [Twitter](https://twitter.com/reactnext) - [Facebook](https://www.facebook.com/ReactNext2016/) +[Website](https://remixconf.eu/) - [Twitter](https://twitter.com/remixconfeu) -### React Loop 2020 {#react-loop-2020} -June 19, 2020. Chicago, Illinois, USA. +### React Global Online Summit 22.2 by Geekle {#react-global-v2-2022} +November 8 - 9, 2022 - Online Summit -[Website](https://reactloop.com) - [Twitter](https://twitter.com/ReactLoop) +[Website](https://events.geekle.us/react3/) - [LinkedIn](https://www.linkedin.com/posts/geekle-us_event-react-reactjs-activity-6964904611207864320-gpDx?utm_source=share&utm_medium=member_desktop) -### React Week NY 2020 {#react-week-ny-2020} -July 17, 2020. New York City, USA. +### React Advanced 2022 {#react-advanced-2022} +October 21 & 25, 2022. In-person in London, UK + remote (hybrid event) -[Website](https://reactweek.nyc/) - [Twitter](https://twitter.com/reactweek) - [Facebook](https://www.facebook.com/reactweek) +[Website](https://www.reactadvanced.com/) - [Twitter](https://twitter.com/ReactAdvanced) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Videos](https://www.youtube.com/c/ReactConferences) -### React La Conferencia 2020 {#react-la-conferencia-2020} -July 18, 2020. Medellín, Colombia. +### ReactJS Day 2022 {#reactjs-day-2022} +October 21, 2022 in Verona, Italy -[Website](https://reactlaconf.co/) - [Twitter](https://twitter.com/reactlaconf) +[Website](https://2022.reactjsday.it/) - [Twitter](https://twitter.com/reactjsday) - [LinkedIn](https://www.linkedin.com/company/grusp/) - [Facebook](https://www.facebook.com/reactjsday/) - [Videos](https://www.youtube.com/c/grusp) -### Chain React 2020 {#chain-react-2020} -July 29-30, 2020. Portland, Oregon, USA. +### React Brussels 2022 {#react-brussels-2022} +October 14, 2022. In-person in Brussels, Belgium + remote (hybrid event) -[Website](https://infinite.red/ChainReactConf) - [Twitter](https://twitter.com/ChainReactConf) +[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact) - [LinkedIn](https://www.linkedin.com/events/6938421827153088512/) - [Facebook](https://www.facebook.com/events/1289080838167252/) - [Videos](https://www.youtube.com/channel/UCvES7lMpnx-t934qGxD4w4g) -### ComponentsConf 2020 {#components20} -September 1, 2020 in Melbourne, Australia +### React Alicante 2022 {#react-alicante-2022} +September 29 - October 1, 2022. In-person in Alicante, Spain + remote (hybrid event) -[Website](https://www.componentsconf.com.au/) - [Twitter](https://twitter.com/ComponentsConf) - [Facebook](https://www.facebook.com/ComponentsConf/) - [LinkedIn](https://www.linkedin.com/company/componentsconf/) - [YouTube](https://www.youtube.com/ComponentsConf) +[Website](https://reactalicante.es/) - [Twitter](https://twitter.com/reactalicante) - [Facebook](https://www.facebook.com/ReactAlicante) - [Videos](https://www.youtube.com/channel/UCaSdUaITU1Cz6PvC97A7e0w) +### React India 2022 {#react-india-2022} +September 22 - 24, 2022. In-person in Goa, India + remote (hybrid event) -### React Native EU 2020 {#react-native-eu-2020} -September 5-6, 2020 in Wrocław, Poland +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Videos](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) -[Website](https://www.react-native.eu/) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu/) - [YouTube](https://www.youtube.com/watch?v=m0GfmlGFh3E&list=PLZ3MwD-soTTHy9_88QPLF8DEJkvoB5Tl-) - [Instagram](https://www.instagram.com/reactnative_eu/) +### React Finland 2022 {#react-finland-2022} +September 12 - 16, 2022. In-person in Helsinki, Finland -### React India 2020 {#react-india-2020} -November 6, 2020 in Mumbai, India +[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) - [Schedule](https://react-finland.fi/schedule/) - [Speakers](https://react-finland.fi/speakers/) -[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia/) - [LinkedIn](https://www.linkedin.com/showcase/14545585) - [YouTube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w/videos) +### React Native EU 2022: Powered by {callstack} {#react-native-eu-2022-powered-by-callstack} +September 1-2, 2022 - Remote event -## Past Conferences {#past-conferences} +[Website](https://www.react-native.eu/?utm_campaign=React_Native_EU&utm_source=referral&utm_content=reactjs_community_conferences) - +[Twitter](https://twitter.com/react_native_eu) - +[Linkedin](https://www.linkedin.com/showcase/react-native-eu) - +[Facebook](https://www.facebook.com/reactnativeeu/) - +[Instagram](https://www.instagram.com/reactnative_eu/) -### React.js Conf 2015 {#reactjs-conf-2015} -January 28 & 29 in Facebook HQ, CA +### ReactNext 2022 {#react-next-2022} +June 28, 2022. Tel-Aviv, Israel -[Website](http://conf2015.reactjs.org/) - [Schedule](http://conf2015.reactjs.org/schedule.html) - [Videos](https://www.youtube.com/playlist?list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr) +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Videos](https://www.youtube.com/c/ReactNext) -<iframe title="React.js Conf 2015 Keynote" width="650" height="315" src="//www.youtube-nocookie.com/embed/KVZ-P-ZI6W4?list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr" frameborder="0" allowfullscreen></iframe> +### React Norway 2022 {#react-norway-2022} +June 24, 2022. In-person at Farris Bad Hotel in Larvik, Norway and online (hybrid event). -### ReactEurope 2015 {#reacteurope-2015} -July 2 & 3 in Paris, France +[Website](https://reactnorway.com/) - [Twitter](https://twitter.com/ReactNorway) -[Website](http://www.react-europe.org/) - [Schedule](http://www.react-europe.org/#schedule) - [Videos](https://www.youtube.com/channel/UCorlLn2oZfgOJ-FUcF2eZ1A/playlists) +### React Summit 2022 {#react-summit-2022} +June 17 & 21, 2022. In-person in Amsterdam, Netherlands + remote first interactivity (hybrid event) -### Reactive 2015 {#reactive-2015} -November 2-4 in Bratislava, Slovakia +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) -[Website](https://reactive2015.com/) - [Schedule](https://reactive2015.com/schedule_speakers.html#schedule) +### App.js Conf 2022 {#appjs-conf-2022} +June 8 - 10, 2022. In-person in Kraków, Poland + remote -### React.js Conf 2016 {#reactjs-conf-2016} -February 22 & 23 in San Francisco, CA +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) -[Website](http://conf2016.reactjs.org/) - [Schedule](http://conf2016.reactjs.org/schedule.html) - [Videos](https://www.youtube.com/playlist?list=PLb0IAmt7-GS0M8Q95RIc2lOM6nc77q1IY) +### React Day Bangalore 2022 {#reactday-bangalore-2022} +June 8 - 9, 2022. Remote -### React Amsterdam 2016 {#react-amsterdam-2016} -April 16 in Amsterdam, The Netherlands +[Website](https://reactday.in/) - [Twitter](https://twitter.com/ReactDayIn) - [Linkedin](https://www.linkedin.com/company/react-day/) - [YouTube](https://www.youtube.com/reactify_in) -[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactamsterdam) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) +### render(ATL) 2022 🍑 {#render-atlanta-2022} +June 1 - 4, 2022. Atlanta, GA, USA -### ReactEurope 2016 {#reacteurope-2016} -June 2 & 3 in Paris, France +[Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/) -[Website](http://www.react-europe.org/) - [Schedule](http://www.react-europe.org/#schedule) - [Videos](https://www.youtube.com/channel/UCorlLn2oZfgOJ-FUcF2eZ1A/playlists) +### RemixConf 2022 {#remixconf-2022} +May 24 - 25, 2022. Salt Lake City, UT -### ReactRally 2016 {#reactrally-2016} -August 25-26 in Salt Lake City, UT +[Website](https://remix.run/conf/2022) - [Twitter](https://twitter.com/remix_run) - [YouTube](https://www.youtube.com/playlist?list=PLXoynULbYuEC36XutMMWEuTu9uuh171wx) -[Website](http://www.reactrally.com/) - [Schedule](http://www.reactrally.com/#/schedule) - [Videos](https://www.youtube.com/playlist?list=PLUD4kD-wL_zYSfU3tIYsb4WqfFQzO_EjQ) +### Reactathon 2022 {#reactathon-2022} +May 3 - 5, 2022. Berkeley, CA -### ReactNext 2016 {#reactnext-2016} -September 15 in Tel Aviv, Israel +[Website](https://reactathon.com) - [Twitter](https://twitter.com/reactathon) -[YouTube](https://www.youtube.com/watch?v=-YG5cljNXIA) -[Website](http://react-next.com/) - [Schedule](http://react-next.com/#schedule) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) +### < React Global > Online Summit 2022 by Geekle {#react-global-2022} +April 20 - 21, 2022 - Online Summit -### ReactNL 2016 {#reactnl-2016} -October 13 in Amsterdam, The Netherlands - [Schedule](http://reactnl.org/#program) +[Website](https://events.geekle.us/react2/) - [LinkedIn](https://www.linkedin.com/events/reactglobalonlinesummit-226887417664541614081/) -[Website](http://reactnl.org/) +### React Miami 2022 🌴 {#react-miami-2022} +April 18 - 19, 2022. Miami, Florida +[Website](https://www.reactmiami.com/) -### Reactive 2016 {#reactive-2016} -October 26-28 in Bratislava, Slovakia +### React Live 2022 {#react-live-2022} +April 1, 2022. Amsterdam, The Netherlands -[Website](https://reactiveconf.com/) +[Website](https://www.reactlive.nl/) - [Twitter](https://twitter.com/reactlivenl) -### React Remote Conf 2016 {#react-remote-conf-2016} -October 26-28 online +### AgentConf 2022 {#agent-conf-2022} -[Website](https://allremoteconfs.com/react-2016) - [Schedule](https://allremoteconfs.com/react-2016#schedule) +January 27 - 30, 2022. In-person in Dornbirn and Lech Austria -### Agent Conference 2017 {#agent-conference-2017} -January 20-21 in Dornbirn, Austria +[Website](https://agent.sh/) - [Twitter](https://twitter.com/AgentConf) - [Instagram](https://www.instagram.com/teamagent/) -[Website](http://agent.sh/) +### React Conf 2021 {#react-conf-2021} +December 8, 2021 - remote event (replay event on December 9) -### React Conf 2017 {#react-conf-2017} -March 13-14 in Santa Clara, CA +[Website](https://conf.reactjs.org/) -[Website](http://conf.reactjs.org/) - [Videos](https://www.youtube.com/watch?v=7HSd1sk07uU&list=PLb0IAmt7-GS3fZ46IGFirdqKTIxlws7e0) +### ReactEurope 2021 {#reacteurope-2021} +December 9-10, 2021 - remote event -### React London 2017 {#react-london-2017} -March 28th at the [QEII Centre, London](http://qeiicentre.london/) +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) -[Website](http://react.london/) - [Videos](https://www.youtube.com/watch?v=2j9rSur_mnk&list=PLW6ORi0XZU0CFjdoYeC0f5QReBG-NeNKJ) +### ReactNext 2021 {#react-next-2021} +December 15, 2021. Tel-Aviv, Israel -### React Amsterdam 2017 {#react-amsterdam-2017} -April 21st in Amsterdam, The Netherlands +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) -[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) +### React India 2021 {#react-india-2021} +November 12-13, 2021 - remote event -### ReactEurope 2017 {#reacteurope-2017} -May 18th & 19th in Paris, France +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia/) - [LinkedIn](https://www.linkedin.com/showcase/14545585) - [YouTube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w/videos) -[Website](http://www.react-europe.org/) - [Schedule](http://www.react-europe.org/#schedule) - [Videos](https://www.youtube.com/channel/UCorlLn2oZfgOJ-FUcF2eZ1A/playlists) +### < React Global > by Geekle {#react-global-2021} +November 3-4, 2021 - remote event -### Chain React 2017 {#chain-react-2017} -July 10-11 in Portland, Oregon USA +[Website](https://geekle.us/react) - [LinkedIn](https://www.linkedin.com/events/javascriptglobalsummit6721691514176720896/) - [YouTube](https://www.youtube.com/watch?v=0HhWIvPhbu0) -[Website](https://infinite.red/ChainReactConf) - [Twitter](https://twitter.com/chainreactconf) - [Videos](https://www.youtube.com/watch?v=cz5BzwgATpc&list=PLFHvL21g9bk3RxJ1Ut5nR_uTZFVOxu522) +### React Advanced 2021 {#react-advanced-2021} +October 22-23, 2021. In-person in London, UK + remote (hybrid event) -### React Rally 2017 {#react-rally-2017} -August 24-25 in Salt Lake City, Utah USA +[Website](https://reactadvanced.com) - [Twitter](https://twitter.com/reactadvanced) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Videos](https://youtube.com/c/ReactConferences) -[Website](http://www.reactrally.com) - [Twitter](https://twitter.com/reactrally) - [Videos](https://www.youtube.com/watch?v=f4KnHNCZcH4&list=PLUD4kD-wL_zZUhvAIHJjueJDPr6qHvkni) +### React Conf Brasil 2021 {#react-conf-brasil-2021} +October 16, 2021 - remote event -### React Native EU 2017 {#react-native-eu-2017} -September 6-7 in Wroclaw, Poland +[Website](http://reactconf.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Slack](https://react.now.sh) - [Facebook](https://facebook.com/reactconf) - [Instagram](https://instagram.com/reactconfbr) - [YouTube](https://www.youtube.com/channel/UCJL5eorStQfC0x1iiWhvqPA/videos) -[Website](http://react-native.eu/) - [Videos](https://www.youtube.com/watch?v=453oKJAqfy0&list=PLzUKC1ci01h_hkn7_KoFA-Au0DXLAQZR7) +### React Brussels 2021 {#react-brussels-2021} +October 15, 2021 - remote event -### ReactNext 2017 {#reactnext-2017} -September 8-10 in Tel Aviv, Israel +[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact) - [LinkedIn](https://www.linkedin.com/events/6805708233819336704/) -[Website](http://react-next.com/) - [Twitter](https://twitter.com/ReactNext) - [Videos (Hall A)](https://www.youtube.com/watch?v=eKXQw5kR86c&list=PLMYVq3z1QxSqq6D7jxVdqttOX7H_Brq8Z), [Videos (Hall B)](https://www.youtube.com/watch?v=1InokWxYGnE&list=PLMYVq3z1QxSqCZmaqgTXLsrcJ8mZmBF7T) +### render(ATL) 2021 {#render-atlanta-2021} +September 13-15, 2021. Atlanta, GA, USA -### ReactFoo 2017 {#reactfoo-2017} -September 14 in Bangalore, India +[Website](https://renderatl.com) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) -[Website](https://reactfoo.in/2017/) - [Videos](https://www.youtube.com/watch?v=3G6tMg29Wnw&list=PL279M8GbNsespKKm1L0NAzYLO6gU5LvfH) +### React Native EU 2021 {#react-native-eu-2021} +September 1-2, 2021 - remote event -### React Boston 2017 {#react-boston-2017} -September 23-24 in Boston, Massachusetts USA +[Website](https://www.react-native.eu/) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu/) - [Instagram](https://www.instagram.com/reactnative_eu/) -[Website](http://www.reactboston.com/) - [Twitter](https://twitter.com/ReactBoston) - [Videos](https://www.youtube.com/watch?v=2iPE5l3cl_s&list=PL-fCkV3wv4ub8zJMIhmrrLcQqSR5XPlIT) +### React Finland 2021 {#react-finland-2021} +August 30 - September 3, 2021 - remote event -### React Alicante 2017 {#react-alicante-2017} -September 28-30 in Alicante, Spain +[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) - [LinkedIn](https://www.linkedin.com/company/react-finland/) -[Website](http://reactalicante.es) - [Twitter](https://twitter.com/ReactAlicante) - [Videos](https://www.youtube.com/watch?v=UMZvRCWo6Dw&list=PLd7nkr8mN0sWvBH_s0foCE6eZTX8BmLUM) +### React Case Study Festival 2021 {#react-case-study-festival-2021} +April 27-28, 2021 - remote event -### ReactJS Day 2017 {#reactjs-day-2017} -October 6 in Verona, Italy +[Website](https://link.geekle.us/react/offsite) - [LinkedIn](https://www.linkedin.com/events/reactcasestudyfestival6721300943411015680/) - [Facebook](https://www.facebook.com/events/255715435820203) -[Website](http://2017.reactjsday.it) - [Twitter](https://twitter.com/reactjsday) - [Videos](https://www.youtube.com/watch?v=bUqqJPIgjNU&list=PLWK9j6ps_unl293VhhN4RYMCISxye3xH9) +### React Summit - Remote Edition 2021 {#react-summit-remote-2021} +April 14-16, 2021, 7am PST / 10am EST / 4pm CEST - remote event -### React Conf Brasil 2017 {#react-conf-brasil-2017} -October 7 in Sao Paulo, Brazil +[Website](https://remote.reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) -[Website](http://reactconfbr.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/reactconf/) +### React fwdays’21 {#react-fwdays-2021} +March 27, 2021 - remote event -### State.js Conference 2017 {#statejs-conference-2017} -October 13 in Stockholm, Sweden +[Website](https://fwdays.com/en/event/react-fwdays-2021) - [Twitter](https://twitter.com/fwdays) - [Facebook](https://www.facebook.com/events/1133828147054286) - [LinkedIn](https://www.linkedin.com/events/reactfwdays-21onlineconference6758046347334582273) - [Meetup](https://www.meetup.com/ru-RU/Fwdays/events/275764431/) -[Website](https://statejs.com/) +### React Next 2020 {#react-next-2020} +December 1-2, 2020 - remote event -### React Summit 2017 {#react-summit-2017} -October 21 in Lagos, Nigeria +[Website](https://react-next.com/) - [Twitter](https://twitter.com/reactnext) - [Facebook](https://www.facebook.com/ReactNext2016/) -[Website](https://reactsummit2017.splashthat.com/) - [Twitter](https://twitter.com/DevCircleLagos/) - [Facebook](https://www.facebook.com/groups/DevCLagos/) +### React Conf Brasil 2020 {#react-conf-brasil-2020} +November 21, 2020 - remote event -### ReactiveConf 2017 {#reactiveconf-2017} -October 25–27, Bratislava, Slovakia +[Website](https://reactconf.com.br/) - [Twitter](https://twitter.com/reactconfbr) - [Slack](https://react.now.sh/) -[Website](https://reactiveconf.com) - [Videos](https://www.youtube.com/watch?v=BOKxSFB2hOE&list=PLa2ZZ09WYepMB-I7AiDjDYR8TjO8uoNjs) +### React Summit 2020 {#react-summit-2020} +October 15-16, 2020, 7am PST / 10am EST / 4pm CEST - remote event -### React Seoul 2017 {#react-seoul-2017} -November 4 in Seoul, South Korea +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) -[Website](http://seoul.reactjs.kr/en) +### React Native EU 2020 {#react-native-eu-2020} +September 3-4, 2020 - remote event -### React Day Berlin 2017 {#react-day-berlin-2017} -December 2, Berlin, Germany +[Website](https://www.react-native.eu/) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu/) - [YouTube](https://www.youtube.com/watch?v=m0GfmlGFh3E&list=PLZ3MwD-soTTHy9_88QPLF8DEJkvoB5Tl-) - [Instagram](https://www.instagram.com/reactnative_eu/) -[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/watch?v=UnNLJvHKfSY&list=PL-3BrJ5CiIx5GoXci54-VsrO6GwLhSHEK) +### ReactEurope 2020 {#reacteurope-2020} +May 14-15, 2020 in Paris, France -### ReactFoo Pune {#reactfoo-pune} -January 19-20, Pune, India +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) -[Website](https://reactfoo.in/2018-pune/) - [Twitter](https://twitter.com/ReactFoo) +### Byteconf React 2020 {#byteconf-react-2020} +May 1, 2020. Streamed online on YouTube. -### AgentConf 2018 {#agentconf-2018} -January 25-28 in Dornbirn, Austria +[Website](https://www.bytesized.xyz) - [Twitter](https://twitter.com/bytesizedcode) - [YouTube](https://www.youtube.com/channel/UC046lFvJZhiwSRWsoH8SFjg) -[Website](http://agent.sh/) +### React Summit - Remote Edition 2020 {#react-summit-remote-2020} +3pm CEST time, April 17, 2020 - remote event -### ReactFest 2018 {#reactfest-2018} -March 8-9 in London, UK +[Website](https://remote.reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) -[Website](https://reactfest.uk/) - [Twitter](https://twitter.com/ReactFest) - [Videos](https://www.youtube.com/watch?v=YOCrJ5vRCnw&list=PLRgweB8YtNRt-Sf-A0y446wTJNUaAAmle) +### Reactathon 2020 {#reactathon-2020} +March 30 - 31, 2020 in San Francisco, CA -### Reactathon 2018 {#reactathon-2018} -March 20-22 in San Francisco, USA +[Website](https://www.reactathon.com) - [Twitter](https://twitter.com/reactathon) - [Facebook](https://www.facebook.com/events/575942819854160/) -[Website](https://www.reactathon.com/) - [Twitter](https://twitter.com/reactathon) - [Videos (fundamentals)](https://www.youtube.com/watch?v=knn364bssQU&list=PLRvKvw42Rc7OWK5s-YGGFSmByDzzgC0HP), [Videos (advanced day1)](https://www.youtube.com/watch?v=57hmk4GvJpk&list=PLRvKvw42Rc7N0QpX2Rc5CdrqGuxzwD_0H), [Videos (advanced day2)](https://www.youtube.com/watch?v=1hvQ8p8q0a0&list=PLRvKvw42Rc7Ne46QAjWNWFo1Jf0mQdnIW) +### ReactConf AU 2020 {#reactconfau} +February 27 & 28, 2020 in Sydney, Australia -### React Native Camp UA 2018 {#react-native-camp-ua-2018} -March 31 in Kiev, Ukraine +[Website](https://reactconfau.com/) - [Twitter](https://twitter.com/reactconfau) - [Facebook](https://www.facebook.com/reactconfau) - [Instagram](https://www.instagram.com/reactconfau/) -[Website](http://reactnative.com.ua/) - [Twitter](https://twitter.com/reactnativecamp) - [Facebook](https://www.facebook.com/reactnativecamp/) +### React Barcamp Cologne 2020 {#react-barcamp-cologne-2020} +February 1-2, 2020 in Cologne, Germany -### React Amsterdam 2018 {#react-amsterdam-2018} -April 13 in Amsterdam, The Netherlands +[Website](https://react-barcamp.de/) - [Twitter](https://twitter.com/ReactBarcamp) - [Facebook](https://www.facebook.com/reactbarcamp) -[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactamsterdam) - [Facebook](https://www.facebook.com/reactamsterdam) +### React Day Berlin 2019 {#react-day-berlin-2019} +December 6, 2019 in Berlin, Germany -### React Finland 2018 {#react-finland-2018} -April 24-26 in Helsinki, Finland +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/reactdayberlin) -[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) +### React Summit 2019 {#reactsummit2019} +November 30, 2019 in Lagos, Nigeria -### <React.NotAConf /> 2018 {#reactnotaconf--2018} -April 28 in Sofia, Bulgaria +[Website](https://reactsummit2019.splashthat.com) -[Twitter](https://twitter.com/react_summit) -[Website](http://react-not-a-conf.com/) - [Twitter](https://twitter.com/reactnotaconf) - [Facebook](https://www.facebook.com/groups/1614950305478021/) +### React Conf Brasil 2019 {#react-conf-2019} +October 19, 2019 in São Paulo, BR -### ReactEurope 2018 {#reacteurope-2018} -May 17-18 in Paris, France +[Website](https://reactconf.com.br/) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Slack](https://react.now.sh/) -[Website](https://www.react-europe.org) - [Twitter](https://twitter.com/ReactEurope) - [Facebook](https://www.facebook.com/ReactEurope) +### React Advanced 2019 {#react-advanced-2019} +October 25, 2019 in London, UK -### ReactFoo Mumbai {#reactfoo-mumbai} -May 26 in Mumbai, India +[Website](https://reactadvanced.com) - [Twitter](http://twitter.com/reactadvanced) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Videos](https://youtube.com/c/ReactConferences) -[Website](https://reactfoo.in/2018-mumbai/) - [Twitter](https://twitter.com/reactfoo) - [Past talks](https://hasgeek.tv) +### React Conf 2019 {#react-conf-2019} +October 24-25, 2019 in Henderson, Nevada USA -### Chain React 2018 {#chain-react-2018} -July 11-13 in Portland, Oregon USA +[Website](https://conf.reactjs.org/) - [Twitter](https://twitter.com/reactjs) -[Website](https://infinite.red/ChainReactConf) - [Twitter](https://twitter.com/chainreactconf) +### React Alicante 2019 {#react-alicante-2019} +September 26-28, 2019 in Alicante, Spain -### React Rally {#react-rally} -August 16-17 in Salt Lake City, Utah USA +[Website](http://reactalicante.es/) - [Twitter](https://twitter.com/reactalicante) - [Facebook](https://www.facebook.com/ReactAlicante) -[Website](http://www.reactrally.com) - [Twitter](https://twitter.com/reactrally) +### React India 2019 {#react-india-2019} +September 26-28, 2019 in Goa, India -### React DEV Conf China {#react-dev-conf-china} -August 18 in Guangzhou, China +[Website](https://www.reactindia.io/) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) -[Website](https://react.w3ctech.com) +### React Boston 2019 {#react-boston-2019} +September 21-22, 2019 in Boston, Massachusetts USA -### ReactFoo Delhi {#reactfoo-delhi} -August 18 in Delhi, India +[Website](https://www.reactboston.com/) - [Twitter](https://twitter.com/reactboston) -[Website](https://reactfoo.in/2018-delhi/) - [Twitter](https://twitter.com/reactfoo) - [Past talks](https://hasgeek.tv) +### React Live 2019 {#react-live-2019} +September 13th, 2019. Amsterdam, The Netherlands -### Byteconf React 2018 {#byteconf-react-2018} -August 31 streamed online, via Twitch +[Website](https://www.reactlive.nl/) - [Twitter](https://twitter.com/reactlivenl) -[Website](https://byteconf.com) - [Twitch](https://twitch.tv/byteconf) - [Twitter](https://twitter.com/byteconf) +### React New York 2019 {#react-new-york-2019} +September 13th, 2019. New York, USA -### React Native EU 2018 {#react-native-eu-2018} +[Website](https://reactnewyork.com/) - [Twitter](https://twitter.com/reactnewyork) + +### ComponentsConf 2019 {#componentsconf-2019} +September 6, 2019 in Melbourne, Australia + +[Website](https://www.componentsconf.com.au/) - [Twitter](https://twitter.com/componentsconf) + +### React Native EU 2019 {#react-native-eu-2019} September 5-6 in Wrocław, Poland [Website](https://react-native.eu) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu) -### React Alicante 2018 {#react-alicante-2018} -September 13-15 in Alicante, Spain +### React Conf Iran 2019 {#react-conf-iran-2019} +August 29, 2019. Tehran, Iran. -[Website](http://reactalicante.es) - [Twitter](https://twitter.com/ReactAlicante) +[Website](https://reactconf.ir/) - [Videos](https://www.youtube.com/playlist?list=PL-VNqZFI5Nf-Nsj0rD3CWXGPkH-DI_0VY) - [Highlights](https://github.com/ReactConf/react-conf-highlights) -### React Boston 2018 {#react-boston-2018} -September 29-30 in Boston, Massachusetts USA +### React Rally 2019 {#react-rally-2019} +August 22-23, 2019. Salt Lake City, USA. -[Website](http://www.reactboston.com/) - [Twitter](https://twitter.com/ReactBoston) +[Website](https://www.reactrally.com/) - [Twitter](https://twitter.com/ReactRally) - [Instagram](https://www.instagram.com/reactrally/) -### ReactJS Day 2018 {#reactjs-day-2018} -October 5 in Verona, Italy +### Chain React 2019 {#chain-react-2019} +July 11-12, 2019. Portland, OR, USA. -[Website](http://2018.reactjsday.it) - [Twitter](https://twitter.com/reactjsday) +[Website](https://infinite.red/ChainReactConf) -### React Conf Brasil 2018 {#react-conf-brasil-2018} -October 20 in Sao Paulo, Brazil +### React Loop 2019 {#react-loop-2019} +June 21, 2019 Chicago, Illinois USA -[Website](http://reactconfbr.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/reactconf) +[Website](https://reactloop.com) - [Twitter](https://twitter.com/ReactLoop) -### React Conf 2018 {#react-conf-2018} -October 25-26 in Henderson, Nevada USA +### React Norway 2019 {#react-norway-2019} +June 12, 2019. Larvik, Norway -[Website](https://conf.reactjs.org/) +[Website](https://reactnorway.com) - [Twitter](https://twitter.com/ReactNorway) -### ReactNext 2018 {#reactnext-2018} -November 4 in Tel Aviv, Israel +### ReactNext 2019 {#react-next-2019} +June 11, 2019. Tel Aviv, Israel -[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Facebook](https://facebook.com/ReactNext2016) +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) -### React Day Berlin 2018 {#react-day-berlin-2018} -November 30, Berlin, Germany +### React Conf Armenia 2019 {#react-conf-am-19} +May 25, 2019 in Yerevan, Armenia -[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/channel/UC1EYHmQYBUJjkmL6OtK4rlw) +[Website](https://reactconf.am/) - [Twitter](https://twitter.com/ReactConfAM) - [Facebook](https://www.facebook.com/reactconf.am/) - [YouTube](https://www.youtube.com/c/JavaScriptConferenceArmenia) - [CFP](http://bit.ly/speakReact) -### React Iran 2019 {#react-iran-2019} -January 31, 2019 in Tehran, Iran +### ReactEurope 2019 {#reacteurope-2019} +May 23-24, 2019 in Paris, France -[Website](http://reactiran.com) - [Instagram](https://www.instagram.com/reactiran/) +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) -### Reactathon 2019 {#reactathon-2019} -March 30-31, 2019 in San Francisco, USA +### <React.NotAConf /> 2019 {#reactnotaconf--2019} +May 11 in Sofia, Bulgaria -[Website](https://www.reactathon.com/) - [Twitter](https://twitter.com/reactathon) +[Website](http://react-not-a-conf.com/) - [Twitter](https://twitter.com/reactnotaconf) - [Facebook](https://www.facebook.com/events/780891358936156) + +### ReactJS Girls Conference {#reactjs-girls-conference} +May 3, 2019 in London, UK + +[Website](https://reactjsgirls.com/) - [Twitter](https://twitter.com/reactjsgirls) + +### React Finland 2019 {#react-finland-2019} +April 24-26 in Helsinki, Finland + +[Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) + +### React Amsterdam 2019 {#react-amsterdam-2019} +April 12, 2019 in Amsterdam, The Netherlands + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) ### App.js Conf 2019 {#appjs-conf-2019} April 4-5, 2019 in Kraków, Poland [Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) -### React Amsterdam 2019 {#react-amsterdam-2019} -April 12, 2019 in Amsterdam, The Netherlands +### Reactathon 2019 {#reactathon-2019} +March 30-31, 2019 in San Francisco, USA -[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactamsterdam) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) +[Website](https://www.reactathon.com/) - [Twitter](https://twitter.com/reactathon) -### React Finland 2019 {#react-finland-2019} +### React Iran 2019 {#react-iran-2019} +January 31, 2019 in Tehran, Iran + +[Website](http://reactiran.com) - [Instagram](https://www.instagram.com/reactiran/) + +### React Day Berlin 2018 {#react-day-berlin-2018} +November 30, Berlin, Germany + +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/channel/UC1EYHmQYBUJjkmL6OtK4rlw) + +### ReactNext 2018 {#reactnext-2018} +November 4 in Tel Aviv, Israel + +[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Facebook](https://facebook.com/ReactNext2016) + +### React Conf 2018 {#react-conf-2018} +October 25-26 in Henderson, Nevada USA + +[Website](https://conf.reactjs.org/) + +### React Conf Brasil 2018 {#react-conf-brasil-2018} +October 20 in Sao Paulo, Brazil + +[Website](http://reactconfbr.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/reactconf) + +### ReactJS Day 2018 {#reactjs-day-2018} +October 5 in Verona, Italy + +[Website](http://2018.reactjsday.it) - [Twitter](https://twitter.com/reactjsday) + +### React Boston 2018 {#react-boston-2018} +September 29-30 in Boston, Massachusetts USA + +[Website](http://www.reactboston.com/) - [Twitter](https://twitter.com/ReactBoston) + +### React Alicante 2018 {#react-alicante-2018} +September 13-15 in Alicante, Spain + +[Website](http://reactalicante.es) - [Twitter](https://twitter.com/ReactAlicante) + +### React Native EU 2018 {#react-native-eu-2018} +September 5-6 in Wrocław, Poland + +[Website](https://react-native.eu) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu) + +### Byteconf React 2018 {#byteconf-react-2018} +August 31 streamed online, via Twitch + +[Website](https://byteconf.com) - [Twitch](https://twitch.tv/byteconf) - [Twitter](https://twitter.com/byteconf) + +### ReactFoo Delhi {#reactfoo-delhi} +August 18 in Delhi, India + +[Website](https://reactfoo.in/2018-delhi/) - [Twitter](https://twitter.com/reactfoo) - [Past talks](https://hasgeek.tv) + +### React DEV Conf China {#react-dev-conf-china} +August 18 in Guangzhou, China + +[Website](https://react.w3ctech.com) + +### React Rally {#react-rally} +August 16-17 in Salt Lake City, Utah USA + +[Website](http://www.reactrally.com) - [Twitter](https://twitter.com/reactrally) + +### Chain React 2018 {#chain-react-2018} +July 11-13 in Portland, Oregon USA + +[Website](https://infinite.red/ChainReactConf) - [Twitter](https://twitter.com/chainreactconf) + +### ReactFoo Mumbai {#reactfoo-mumbai} +May 26 in Mumbai, India + +[Website](https://reactfoo.in/2018-mumbai/) - [Twitter](https://twitter.com/reactfoo) - [Past talks](https://hasgeek.tv) + + +### ReactEurope 2018 {#reacteurope-2018} +May 17-18 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### <React.NotAConf /> 2018 {#reactnotaconf--2018} +April 28 in Sofia, Bulgaria + +[Website](http://react-not-a-conf.com/) - [Twitter](https://twitter.com/reactnotaconf) - [Facebook](https://www.facebook.com/groups/1614950305478021/) + +### React Finland 2018 {#react-finland-2018} April 24-26 in Helsinki, Finland [Website](https://react-finland.fi/) - [Twitter](https://twitter.com/ReactFinland) -### ReactJS Girls Conference {#reactjs-girls-conference} -May 3, 2019 in London, UK +### React Amsterdam 2018 {#react-amsterdam-2018} +April 13 in Amsterdam, The Netherlands -[Website](https://reactjsgirls.com/) - [Twitter](https://twitter.com/reactjsgirls) +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) -### <React.NotAConf /> 2019 {#reactnotaconf--2019} -May 11 in Sofia, Bulgaria +### React Native Camp UA 2018 {#react-native-camp-ua-2018} +March 31 in Kiev, Ukraine -[Website](http://react-not-a-conf.com/) - [Twitter](https://twitter.com/reactnotaconf) - [Facebook](https://www.facebook.com/events/780891358936156) +[Website](http://reactnative.com.ua/) - [Twitter](https://twitter.com/reactnativecamp) - [Facebook](https://www.facebook.com/reactnativecamp/) -### ReactEurope 2019 {#reacteurope-2019} -May 23-24, 2019 in Paris, France +### Reactathon 2018 {#reactathon-2018} +March 20-22 in San Francisco, USA -[Website](https://www.react-europe.org) - [Twitter](https://twitter.com/ReactEurope) - [Facebook](https://www.facebook.com/ReactEurope) - [Videos](https://www.youtube.com/c/ReacteuropeOrgConf) +[Website](https://www.reactathon.com/) - [Twitter](https://twitter.com/reactathon) - [Videos (fundamentals)](https://www.youtube.com/watch?v=knn364bssQU&list=PLRvKvw42Rc7OWK5s-YGGFSmByDzzgC0HP), [Videos (advanced day1)](https://www.youtube.com/watch?v=57hmk4GvJpk&list=PLRvKvw42Rc7N0QpX2Rc5CdrqGuxzwD_0H), [Videos (advanced day2)](https://www.youtube.com/watch?v=1hvQ8p8q0a0&list=PLRvKvw42Rc7Ne46QAjWNWFo1Jf0mQdnIW) -### React Conf Armenia 2019 {#react-conf-am-19} -May 25, 2019 in Yerevan, Armenia +### ReactFest 2018 {#reactfest-2018} +March 8-9 in London, UK -[Website](https://reactconf.am/) - [Twitter](https://twitter.com/ReactConfAM) - [Facebook](https://www.facebook.com/reactconf.am/) - [YouTube](https://www.youtube.com/c/JavaScriptConferenceArmenia) - [CFP](http://bit.ly/speakReact) +[Website](https://reactfest.uk/) - [Twitter](https://twitter.com/ReactFest) - [Videos](https://www.youtube.com/watch?v=YOCrJ5vRCnw&list=PLRgweB8YtNRt-Sf-A0y446wTJNUaAAmle) -### ReactNext 2019 {#react-next-2019} -June 11, 2019. Tel Aviv, Israel +### AgentConf 2018 {#agentconf-2018} +January 25-28 in Dornbirn, Austria -[Website](https://react-next.com) - [Twitter](https://twitter.com/ReactNext) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) +[Website](http://agent.sh/) -### React Norway 2019 {#react-norway-2019} -June 12, 2019. Larvik, Norway +### ReactFoo Pune {#reactfoo-pune} +January 19-20, Pune, India -[Website](https://reactnorway.com) - [Twitter](https://twitter.com/ReactNorway) +[Website](https://reactfoo.in/2018-pune/) - [Twitter](https://twitter.com/ReactFoo) -### React Loop 2019 {#react-loop-2019} -June 21, 2019 Chicago, Illinois USA +### React Day Berlin 2017 {#react-day-berlin-2017} +December 2, Berlin, Germany -[Website](https://reactloop.com) - [Twitter](https://twitter.com/ReactLoop) +[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/watch?v=UnNLJvHKfSY&list=PL-3BrJ5CiIx5GoXci54-VsrO6GwLhSHEK) -### Chain React 2019 {#chain-react-2019} -July 11-12, 2019. Portland, OR, USA. +### React Seoul 2017 {#react-seoul-2017} +November 4 in Seoul, South Korea -[Website](https://infinite.red/ChainReactConf) +[Website](http://seoul.reactjs.kr/en) -### React Rally 2019 {#react-rally-2019} -August 22-23, 2019. Salt Lake City, USA. +### ReactiveConf 2017 {#reactiveconf-2017} +October 25–27, Bratislava, Slovakia -[Website](https://www.reactrally.com/) - [Twitter](https://twitter.com/ReactRally) - [Instagram](https://www.instagram.com/reactrally/) +[Website](https://reactiveconf.com) - [Videos](https://www.youtube.com/watch?v=BOKxSFB2hOE&list=PLa2ZZ09WYepMB-I7AiDjDYR8TjO8uoNjs) -### React Conf Iran 2019 {#react-conf-iran-2019} -August 29, 2019. Tehran, Iran. +### React Summit 2017 {#react-summit-2017} +October 21 in Lagos, Nigeria -[Website](https://reactconf.ir/) - [Videos](https://www.youtube.com/playlist?list=PL-VNqZFI5Nf-Nsj0rD3CWXGPkH-DI_0VY) - [Highlights](https://github.com/ReactConf/react-conf-highlights) +[Website](https://reactsummit2017.splashthat.com/) - [Twitter](https://twitter.com/DevCircleLagos/) - [Facebook](https://www.facebook.com/groups/DevCLagos/) -### React Native EU 2019 {#react-native-eu-2019} -September 5-6 in Wrocław, Poland +### State.js Conference 2017 {#statejs-conference-2017} +October 13 in Stockholm, Sweden -[Website](https://react-native.eu) - [Twitter](https://twitter.com/react_native_eu) - [Facebook](https://www.facebook.com/reactnativeeu) +[Website](https://statejs.com/) -### ComponentsConf 2019 {#componentsconf-2019} -September 6, 2019 in Melbourne, Australia +### React Conf Brasil 2017 {#react-conf-brasil-2017} +October 7 in Sao Paulo, Brazil -[Website](https://www.componentsconf.com.au/) - [Twitter](https://twitter.com/componentsconf) +[Website](http://reactconfbr.com.br) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/reactconf/) -### React New York 2019 {#react-new-york-2019} -September 13th, 2019. New York, USA +### ReactJS Day 2017 {#reactjs-day-2017} +October 6 in Verona, Italy -[Website](https://reactnewyork.com/) - [Twitter](https://twitter.com/reactnewyork) +[Website](http://2017.reactjsday.it) - [Twitter](https://twitter.com/reactjsday) - [Videos](https://www.youtube.com/watch?v=bUqqJPIgjNU&list=PLWK9j6ps_unl293VhhN4RYMCISxye3xH9) -### React Live 2019 {#react-live-2019} -September 13th, 2019. Amsterdam, The Netherlands +### React Alicante 2017 {#react-alicante-2017} +September 28-30 in Alicante, Spain -[Website](https://www.reactlive.nl/) - [Twitter](https://twitter.com/reactlivenl) +[Website](http://reactalicante.es) - [Twitter](https://twitter.com/ReactAlicante) - [Videos](https://www.youtube.com/watch?v=UMZvRCWo6Dw&list=PLd7nkr8mN0sWvBH_s0foCE6eZTX8BmLUM) -### React Boston 2019 {#react-boston-2019} -September 21-22, 2019 in Boston, Massachusetts USA +### React Boston 2017 {#react-boston-2017} +September 23-24 in Boston, Massachusetts USA -[Website](https://www.reactboston.com/) - [Twitter](https://twitter.com/reactboston) +[Website](http://www.reactboston.com/) - [Twitter](https://twitter.com/ReactBoston) - [Videos](https://www.youtube.com/watch?v=2iPE5l3cl_s&list=PL-fCkV3wv4ub8zJMIhmrrLcQqSR5XPlIT) -### React India 2019 {#react-india-2019} -September 26-28, 2019 in Goa, India +### ReactFoo 2017 {#reactfoo-2017} +September 14 in Bangalore, India -[Website](https://www.reactindia.io/) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) +[Website](https://reactfoo.in/2017/) - [Videos](https://www.youtube.com/watch?v=3G6tMg29Wnw&list=PL279M8GbNsespKKm1L0NAzYLO6gU5LvfH) -### React Alicante 2019 {#react-alicante-2019} -September 26-28, 2019 in Alicante, Spain +### ReactNext 2017 {#reactnext-2017} +September 8-10 in Tel Aviv, Israel -[Website](http://reactalicante.es/) - [Twitter](https://twitter.com/reactalicante) - [Facebook](https://www.facebook.com/ReactAlicante) +[Website](http://react-next.com/) - [Twitter](https://twitter.com/ReactNext) - [Videos (Hall A)](https://www.youtube.com/watch?v=eKXQw5kR86c&list=PLMYVq3z1QxSqq6D7jxVdqttOX7H_Brq8Z), [Videos (Hall B)](https://www.youtube.com/watch?v=1InokWxYGnE&list=PLMYVq3z1QxSqCZmaqgTXLsrcJ8mZmBF7T) -### React Conf 2019 {#react-conf-2019} -October 24-25, 2019 in Henderson, Nevada USA +### React Native EU 2017 {#react-native-eu-2017} +September 6-7 in Wroclaw, Poland -[Website](https://conf.reactjs.org/) - [Twitter](https://twitter.com/reactjs) +[Website](http://react-native.eu/) - [Videos](https://www.youtube.com/watch?v=453oKJAqfy0&list=PLzUKC1ci01h_hkn7_KoFA-Au0DXLAQZR7) -### React Advanced 2019 {#react-advanced-2019} -October 25, 2019 in London, UK +### React Rally 2017 {#react-rally-2017} +August 24-25 in Salt Lake City, Utah USA -[Website](https://reactadvanced.com) - [Twitter](http://twitter.com/reactadvanced) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Videos](https://youtube.com/c/ReactConferences) +[Website](http://www.reactrally.com) - [Twitter](https://twitter.com/reactrally) - [Videos](https://www.youtube.com/watch?v=f4KnHNCZcH4&list=PLUD4kD-wL_zZUhvAIHJjueJDPr6qHvkni) -### React Conf Brasil 2019 {#react-conf-2019} -October 19, 2019 in São Paulo, BR +### Chain React 2017 {#chain-react-2017} +July 10-11 in Portland, Oregon USA -[Website](https://reactconf.com.br/) - [Twitter](https://twitter.com/reactconfbr) - [Facebook](https://www.facebook.com/ReactAdvanced) - [Slack](https://react.now.sh/) +[Website](https://infinite.red/ChainReactConf) - [Twitter](https://twitter.com/chainreactconf) - [Videos](https://www.youtube.com/watch?v=cz5BzwgATpc&list=PLFHvL21g9bk3RxJ1Ut5nR_uTZFVOxu522) -### React Summit 2019 {#reactsummit2019} -November 30, 2019 in Lagos, Nigeria +### ReactEurope 2017 {#reacteurope-2017} +May 18th & 19th in Paris, France -[Website](https://reactsummit2019.splashthat.com) -[Twitter](https://twitter.com/react_summit) +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) -### React Day Berlin 2019 {#react-day-berlin-2019} -December 6, 2019 in Berlin, Germany +### React Amsterdam 2017 {#react-amsterdam-2017} +April 21st in Amsterdam, The Netherlands -[Website](https://reactday.berlin) - [Twitter](https://twitter.com/reactdayberlin) - [Facebook](https://www.facebook.com/reactdayberlin/) - [Videos](https://www.youtube.com/reactdayberlin) +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Videos](https://youtube.com/c/ReactConferences) -### React Barcamp Cologne 2020 {#react-barcamp-cologne-2020} -February 1-2, 2020 in Cologne, Germany +### React London 2017 {#react-london-2017} +March 28th at the [QEII Centre, London](http://qeiicentre.london/) -[Website](https://react-barcamp.de/) - [Twitter](https://twitter.com/ReactBarcamp) - [Facebook](https://www.facebook.com/reactbarcamp) +[Website](http://react.london/) - [Videos](https://www.youtube.com/watch?v=2j9rSur_mnk&list=PLW6ORi0XZU0CFjdoYeC0f5QReBG-NeNKJ) -### ReactConf AU 2020 {#reactconfau} -February 27 & 28, 2020 in Sydney, Australia +### React Conf 2017 {#react-conf-2017} +March 13-14 in Santa Clara, CA -[Website](https://reactconfau.com/) - [Twitter](https://twitter.com/reactconfau) - [Facebook](https://www.facebook.com/reactconfau) - [Instagram](https://www.instagram.com/reactconfau/) +[Website](http://conf.reactjs.org/) - [Videos](https://www.youtube.com/watch?v=7HSd1sk07uU&list=PLb0IAmt7-GS3fZ46IGFirdqKTIxlws7e0) + +### Agent Conference 2017 {#agent-conference-2017} +January 20-21 in Dornbirn, Austria + +[Website](http://agent.sh/) + +### React Remote Conf 2016 {#react-remote-conf-2016} +October 26-28 online + +[Website](https://allremoteconfs.com/react-2016) - [Schedule](https://allremoteconfs.com/react-2016#schedule) + +### Reactive 2016 {#reactive-2016} +October 26-28 in Bratislava, Slovakia + +[Website](https://reactiveconf.com/) + +### ReactNL 2016 {#reactnl-2016} +October 13 in Amsterdam, The Netherlands + +[Website](http://reactnl.org/) - [Schedule](http://reactnl.org/#program) + +### ReactNext 2016 {#reactnext-2016} +September 15 in Tel Aviv, Israel + +[Website](http://react-next.com/) - [Schedule](http://react-next.com/#schedule) - [Videos](https://www.youtube.com/channel/UC3BT8hh3yTTYxbLQy_wbk2w) + +### ReactRally 2016 {#reactrally-2016} +August 25-26 in Salt Lake City, UT + +[Website](http://www.reactrally.com/) - [Schedule](http://www.reactrally.com/#/schedule) - [Videos](https://www.youtube.com/playlist?list=PLUD4kD-wL_zYSfU3tIYsb4WqfFQzO_EjQ) + +### ReactEurope 2016 {#reacteurope-2016} +June 2 & 3 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### React Amsterdam 2016 {#react-amsterdam-2016} +April 16 in Amsterdam, The Netherlands + +[Website](https://reactsummit.com) - [Twitter](https://twitter.com/reactsummit) - [Facebook](https://www.facebook.com/reactamsterdam) - [Videos](https://youtube.com/c/ReactConferences) + +### React.js Conf 2016 {#reactjs-conf-2016} +February 22 & 23 in San Francisco, CA + +[Website](http://conf2016.reactjs.org/) - [Schedule](http://conf2016.reactjs.org/schedule.html) - [Videos](https://www.youtube.com/playlist?list=PLb0IAmt7-GS0M8Q95RIc2lOM6nc77q1IY) + +### Reactive 2015 {#reactive-2015} +November 2-4 in Bratislava, Slovakia + +[Website](https://reactive2015.com/) - [Schedule](https://reactive2015.com/schedule_speakers.html#schedule) + +### ReactEurope 2015 {#reacteurope-2015} +July 2 & 3 in Paris, France + +[Videos](https://www.youtube.com/c/ReacteuropeOrgConf) + +### React.js Conf 2015 {#reactjs-conf-2015} +January 28 & 29 in Facebook HQ, CA + +[Website](http://conf2015.reactjs.org/) - [Schedule](http://conf2015.reactjs.org/schedule.html) - [Videos](https://www.youtube.com/playlist?list=PLb0IAmt7-GS1cbw4qonlQztYV1TAW0sCr) diff --git a/content/community/courses.md b/content/community/courses.md index 47e33eccf..7e532bbd1 100644 --- a/content/community/courses.md +++ b/content/community/courses.md @@ -8,7 +8,7 @@ permalink: community/courses.html ## Free Courses {#free-courses} -- [Glitch: React Starter Kit](https://glitch.com/culture/react-starter-kit/) - A free, 5-part video course with interactive code examples that will help you learn React. +- [Glitch: React Starter Kit](https://glitch.com/glimmer/post/react-starter-kit) - A free, 5-part video course with interactive code examples that will help you learn React. - [Codecademy: React 101](https://www.codecademy.com/learn/react-101) - Codecademy's introductory course for React. @@ -16,21 +16,24 @@ permalink: community/courses.html - [React Crash Course 2018](https://www.youtube.com/watch?v=Ke90Tje7VS0) - A beginner-friendly crash course through the most important React topics. -- [React Armory: Learn React by Itself](https://reactarmory.com/guides/learn-react-by-itself) - With React Armory, you can learn React without the buzzwords. +- [Frontend Armory: React Fundamentals](https://frontarm.com/courses/react-fundamentals/) - Learn React without the buzzwords. -- [The Road to Learn React](https://www.robinwieruch.de/the-road-to-learn-react/) - Build a real world application in plain React without complicated tooling. - -- [Egghead.io: The Beginner's Guide to ReactJS](https://egghead.io/courses/the-beginner-s-guide-to-reactjs) - Free course for React newbies and those looking to get a better understanding of React fundamentals. +- [Egghead.io: The Beginner's Guide to ReactJS](https://egghead.io/courses/the-beginner-s-guide-to-react) - Free course for React newbies and those looking to get a better understanding of React fundamentals. - [Free React Bootcamp](https://tylermcginnis.com/free-react-bootcamp/) - Recordings from three days of a free online React bootcamp. - [Scrimba: Learn React for free](https://scrimba.com/g/glearnreact) - 48 hands-on video tutorials building react apps. +- [University of Helsinki: Full Stack Open MOOC](https://fullstackopen.com/en/) - Learn to build web applications with React. Available in English, Spanish, Chinese and Finnish. + + ## Paid Courses {#paid-courses} +- [Meta Front-End Developer Professional Certificate](https://www.coursera.org/professional-certificates/meta-front-end-developer) - Launch your career as a front-end developer. Build job-ready skills for an in-demand career and earn a credential from Meta. No degree or prior experience required to get started. + - [Egghead.io](https://egghead.io/browse/frameworks/react) - Short instructional videos on React and many other topics. -- [Frontend Masters](https://frontendmasters.com/courses/) - Video courses on React and other frontend frameworks. +- [Frontend Masters](https://frontendmasters.com/learn/react/) - Video courses on React. - [Fullstack React](https://www.fullstackreact.com/) - The up-to-date, in-depth, complete guide to React and friends. @@ -46,6 +49,12 @@ permalink: community/courses.html - [React Training: Advanced React.js](https://courses.reacttraining.com/p/advanced-react) - Take your React skills to the next level. -- [Tyler McGinnis](https://tylermcginnis.com/courses) - Tyler McGinnis provides access to his courses for a monthly fee. Courses include "React Fundamentals" and "Universal React". +- [Tyler McGinnis](https://ui.dev/) - Tyler McGinnis provides access to his courses for a monthly fee. Courses include "React Fundamentals" and "Universal React". - [Mastering React](https://codewithmosh.com/p/mastering-react/) - Build professional interactive apps with React. + +- [React Tutorial](https://react-tutorial.app) - Learn React step by step in an interactive environment with flashcards. + +- [Road to React](https://www.roadtoreact.com/) - Your journey to master React in JavaScript. + +- [Epic React](https://epicreact.dev/) - Confidently Ship Well-Architected Production Ready React Apps Like a Pro diff --git a/content/community/examples.md b/content/community/examples.md index 3a5ccaa67..5c70b3d59 100644 --- a/content/community/examples.md +++ b/content/community/examples.md @@ -6,24 +6,20 @@ sectionid: community permalink: community/examples.html --- -There are many example projects created by the React community. Feel free to add your own project. If you add a project, please commit to keeping it up to date with the latest versions of React. +There are many example projects created by the React community. We're keeping this page focused on the ones that use React without third-party state management libraries. +If you add a project, please commit to keeping it up to date with the latest versions of React. + +## Small Examples {#small-examples} -* **[Zeldog](https://yannsainty.github.io/Zeldog/)** A Zelda like game with a dog fighting ducks and more to eat pizza ! * **[Calculator](https://github.com/ahfarmer/calculator)** Implementation of the iOS calculator built in React -* **[Emoji Search](https://github.com/ahfarmer/emoji-search)** Simple React app for searching emoji -* **[React Powered Hacker News Client](https://github.com/insin/react-hn)** A React & `react-router`-powered implementation of Hacker News using its Firebase API -* **[Pokedex](https://github.com/alik0211/pokedex)** The list of Pokémon with live search -* **[Shopping Cart](https://github.com/jeffersonRibeiro/react-shopping-cart)** Simple e-commerce cart application built using React -* **[Progressive Web Tetris](https://github.com/skidding/flatris)** Besides a beautiful, mobile-friendly implementation of Tetris, this project is a playground for integrating and experimenting with web technologies. -* **[Product Comparison Page](https://github.com/Rhymond/product-compare-react)** Simple Product Compare page built in React -* **[Hacker News Clone React/GraphQL](https://github.com/clintonwoo/hackernews-react-graphql)** Hacker News clone rewritten with universal JavaScript, using React and GraphQL -* **[Bitcoin Price Index](https://github.com/mrkjlchvz/bitcoin-price-index)** Simple Bitcoin price index data from CoinDesk API -* **[Builder Book](https://github.com/builderbook/builderbook)** Open-source web app to write and host documentation or sell books. Built with React, Material-UI, Next, Express, Mongoose, MongoDB. -* **[GFonts Space](https://github.com/pankajladhar/GFontsSpace)** A space which allows user to play with Google fonts. Built with React, Redux and React-Router. -* **[Course Learn Page](https://github.com/ulearnpro/ulearn)** Open Source LMS script in Laravel 5.8 and ReactJS 16.9 -* **[Speedy math](https://github.com/pankajladhar/speedy-math)** An application which allows kids to practice basic Mathematics i.e Addition, Subtraction, Multiply, Comparison. It is a PWA (Progressive web app) with offline support and install as App features. -* **[Unit Converter](https://github.com/KarthikeyanRanasthala/react-unit-converter)** Minimal Yet Responsive Unit Converter Built With React, Material-UI & Convert-Units. +* **[Emoji Search](https://github.com/ahfarmer/emoji-search)** React app for searching emoji +* **[Snap Shot](https://github.com/Yog9/SnapShot)** A photo gallery with search * **[BMI Calculator](https://github.com/GermaVinsmoke/bmi-calculator)** A React Hooks app for calculating BMI -* **[ReactJS Hangman Game](https://github.com/vetrivelcsamy/reactjs-hangman)** ReactJS Hangman Game Find a Programming Language. -* **[Radix Converter](https://github.com/kumom/radix-converter)** A small tool that converts numbers in radix 2 to 36 with arbitrary precision. +* **[Image Compressor](https://github.com/RaulB-masai/react-image-compressor)** An offline image compressor built with React and browser-image-compression +* **[Counter App](https://github.com/arnab-datta/counter-app)** A small shopping cart example +* **[Tutorial Solutions](https://github.com/harman052/react-tutorial-solutions)** Solutions to challenges mentioned at the end of React tutorial + +## Complete Apps {#complete-apps} + +* **[Builder Book](https://github.com/builderbook/builderbook)** Open-source web app to write and host documentation or sell books. Built with React, Material-UI, Next, Express, Mongoose, MongoDB diff --git a/content/community/external-resources.md b/content/community/external-resources.md index d30064fef..a255fff46 100644 --- a/content/community/external-resources.md +++ b/content/community/external-resources.md @@ -18,4 +18,6 @@ There are many wonderful curated resources the React community has put together. - [Awesome React Talks](https://github.com/tiaanduplessis/awesome-react-talks) - A curated list of React talks. +- [Awesome React Videos](https://www.awesomereact.com) - A website highlighting the best React videos. + - [Hero35 React Hub](https://hero35.com/topic/react) - A website with _all_ React conferences and talks, categorized & curated. diff --git a/content/community/meetups.md b/content/community/meetups.md index 5bb7b7594..c969bbe90 100644 --- a/content/community/meetups.md +++ b/content/community/meetups.md @@ -8,7 +8,11 @@ permalink: community/meetups.html Do you have a local React.js meetup? Add it here! (Please keep the list alphabetical) +## Albania {#albania} +* [Tirana](https://www.meetup.com/React-User-Group-Albania/) + ## Argentina {#argentina} +* [Buenos Aires](https://www.meetup.com/es/React-en-Buenos-Aires) * [Rosario](https://www.meetup.com/es/reactrosario) ## Australia {#australia} @@ -40,23 +44,34 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Bolivia](https://www.meetup.com/ReactBolivia/) ## Canada {#canada} +* [Halifax, NS](https://www.meetup.com/Halifax-ReactJS-Meetup/) * [Montreal, QC - React Native](https://www.meetup.com/fr-FR/React-Native-MTL/) * [Vancouver, BC](https://www.meetup.com/ReactJS-Vancouver-Meetup/) * [Ottawa, ON](https://www.meetup.com/Ottawa-ReactJS-Meetup/) +* [Toronto, ON](https://www.meetup.com/Toronto-React-Native/events/) + +## Chile {#chile} +* [Santiago](https://www.meetup.com/es-ES/react-santiago/) ## China {#china} * [Beijing](https://www.meetup.com/Beijing-ReactJS-Meetup/) ## Colombia {#colombia} +* [Bogotá](https://www.meetup.com/meetup-group-iHIeHykY/) * [Medellin](https://www.meetup.com/React-Medellin/) +* [Cali](https://www.meetup.com/reactcali/) ## Denmark {#denmark} * [Aalborg](https://www.meetup.com/Aalborg-React-React-Native-Meetup/) * [Aarhus](https://www.meetup.com/Aarhus-ReactJS-Meetup/) +## Egypt {#egypt} +* [Cairo](https://www.meetup.com/react-cairo/) + ## England (UK) {#england-uk} * [Manchester](https://www.meetup.com/Manchester-React-User-Group/) * [React.JS Girls London](https://www.meetup.com/ReactJS-Girls-London/) +* [React London : Bring Your Own Project](https://www.meetup.com/React-London-Bring-Your-Own-Project/) ## France {#france} * [Nantes](https://www.meetup.com/React-Nantes/) @@ -73,6 +88,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [React Berlin](https://www.meetup.com/React-Open-Source/) ## Greece {#greece} +* [Athens](https://www.meetup.com/React-To-React-Athens-MeetUp/) * [Thessaloniki](https://www.meetup.com/Thessaloniki-ReactJS-Meetup/) ## Hungary {#hungary} @@ -80,6 +96,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet ## India {#india} * [Bangalore](https://www.meetup.com/ReactJS-Bangalore/) +* [Bangalore](https://www.meetup.com/React-Native-Bangalore-Meetup) * [Chandigarh](https://www.meetup.com/Chandigarh-React-Developers/) * [Chennai](https://www.meetup.com/React-Chennai/) * [Delhi NCR](https://www.meetup.com/React-Delhi-NCR/) @@ -95,6 +112,12 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet ## Israel {#israel} * [Tel Aviv](https://www.meetup.com/ReactJS-Israel/) +## Italy {#italy} +* [Milan](https://www.meetup.com/React-JS-Milano/) + +## Kenya {#kenya} +* [Nairobi - Reactdevske](https://kommunity.com/reactjs-developer-community-kenya-reactdevske) + ## Malaysia {#malaysia} * [Kuala Lumpur](https://www.kl-react.com/) * [Penang](https://www.facebook.com/groups/reactpenang/) @@ -125,6 +148,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet ## Poland {#poland} * [Warsaw](https://www.meetup.com/React-js-Warsaw/) +* [Wrocław](https://www.meetup.com/ReactJS-Wroclaw/) ## Portugal {#portugal} * [Lisbon](https://www.meetup.com/JavaScript-Lisbon/) @@ -132,21 +156,19 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet ## Scotland (UK) {#scotland-uk} * [Edinburgh](https://www.meetup.com/React-Scotland/) -## Singapore {#singapore} -* [Singapore - React Knowledgeable](https://reactknowledgeable.org/) - ## Spain {#spain} * [Barcelona](https://www.meetup.com/ReactJS-Barcelona/) * [Canarias](https://www.meetup.com/React-Canarias/) ## Sweden {#sweden} * [Goteborg](https://www.meetup.com/ReactJS-Goteborg/) +* [Stockholm](https://www.meetup.com/Stockholm-ReactJS-Meetup/) ## Switzerland {#switzerland} * [Zurich](https://www.meetup.com/Zurich-ReactJS-Meetup/) ## Turkey {#turkey} -* [Istanbul](https://www.meetup.com/ReactJS-Istanbul/) +* [Istanbul](https://kommunity.com/reactjs-istanbul) ## Ukraine {#ukraine} * [Kyiv](https://www.meetup.com/Kyiv-ReactJS-Meetup) @@ -163,7 +185,9 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Cleveland, OH - ReactJS](https://www.meetup.com/Cleveland-React/) * [Columbus, OH - ReactJS](https://www.meetup.com/ReactJS-Columbus-meetup/) * [Dallas, TX - ReactJS](https://www.meetup.com/ReactDallas/) +* [Dallas, TX - [Remote] React JS](https://www.meetup.com/React-JS-Group/) * [Detroit, MI - Detroit React User Group](https://www.meetup.com/Detroit-React-User-Group/) +* [Indianapolis, IN - React.Indy](https://www.meetup.com/React-Indy) * [Irvine, CA - ReactJS](https://www.meetup.com/ReactJS-OC/) * [Kansas City, MO - ReactJS](https://www.meetup.com/Kansas-City-React-Meetup/) * [Las Vegas, NV - ReactJS](https://www.meetup.com/ReactVegas/) @@ -176,6 +200,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [New York, NY - React Ladies](https://www.meetup.com/React-Ladies/) * [New York, NY - React Native](https://www.meetup.com/React-Native-NYC/) * [New York, NY - useReactNYC](https://www.meetup.com/useReactNYC/) +* [Omaha, NE - ReactJS/React Native](https://www.meetup.com/omaha-react-meetup-group/) * [Palo Alto, CA - React Native](https://www.meetup.com/React-Native-Silicon-Valley/) * [Philadelphia, PA - ReactJS](https://www.meetup.com/Reactadelphia/) * [Phoenix, AZ - ReactJS](https://www.meetup.com/ReactJS-Phoenix/) diff --git a/content/community/podcasts.md b/content/community/podcasts.md index 233c7df12..012caa315 100644 --- a/content/community/podcasts.md +++ b/content/community/podcasts.md @@ -16,7 +16,11 @@ Podcasts dedicated to React and individual podcast episodes with React discussio - [React 30](https://react30.com/) - A 30-minute podcast all about React (moved to [The React Podcast](https://reactpodcast.simplecast.fm/)). -- [React Native Radio](https://devchat.tv/react-native-radio) +- [React Native Radio](https://reactnativeradio.com) - Exploring React Native Together, hosted by [Infinite Red](https://infinite.red) + +- [React Wednesdays](https://www.telerik.com/react-wednesdays) - Weekly live streams with the best and brightest in the React world + +- [The React Native Show](https://callstack.com/podcast-react-native-show) - Discuss everything React Native, hosted by [Callstack](https://callstack.com/?utm_campaign=Podcast&utm_source=reactjs_org&utm_medium=community_podcasts) ## Episodes {#episodes} diff --git a/content/community/support.md b/content/community/support.md index cb31a529b..e6ec00f64 100644 --- a/content/community/support.md +++ b/content/community/support.md @@ -12,7 +12,7 @@ React has a community of millions of developers. On this page we've listed some React-related communities that you can be a part of; see the other pages in this section for additional online and in-person learning materials. -Before participating in React's communities, [please read our Code of Conduct](https://github.com/facebook/react/blob/master/CODE_OF_CONDUCT.md). We have adopted the [Contributor Covenant](https://www.contributor-covenant.org/) and we expect that all community members adhere to the guidelines within. +Before participating in React's communities, [please read our Code of Conduct](https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md). We have adopted the [Contributor Covenant](https://www.contributor-covenant.org/) and we expect that all community members adhere to the guidelines within. ## Stack Overflow {#stack-overflow} @@ -28,7 +28,6 @@ Each community consists of many thousands of React users. * [Hashnode's React community](https://hashnode.com/n/reactjs) * [Reactiflux online chat](https://discord.gg/reactiflux) * [Reddit's React community](https://www.reddit.com/r/reactjs/) -* [Spectrum's React community](https://spectrum.chat/react) ## News {#news} diff --git a/content/community/team.md b/content/community/team.md index 3cb4bad95..acd38b590 100644 --- a/content/community/team.md +++ b/content/community/team.md @@ -6,7 +6,7 @@ sectionid: community permalink: community/team.html --- -React development is led by a small dedicated team working full time at Facebook. It also receives contributions from people all over the world. +React development is led by a small dedicated team working full time at Meta. It also receives contributions from people all over the world. ## Meet the React Team {#meet-the-react-team} @@ -22,14 +22,6 @@ Current members of the React team are listed in alphabetical order below. Andrew got started with web development by making sites with WordPress, and eventually tricked himself into doing JavaScript. His favorite pastime is karaoke. Andrew is either a Disney villain or a Disney princess, depending on the day. -### Brian Vaughn {#brian-vaughn} - -![Brian Vaughn](../images/team/bvaughn.jpg) - -[@bvaughn on GitHub](https://github.com/bvaughn) · [@brian\_d\_vaughn on Twitter](https://twitter.com/brian_d_vaughn) - -Brian studied art in college and did programming on the side to pay for his education. Eventually, he realized that he enjoys working on open source. Brian has one [one-person band](https://soundcloud.com/brianvaughn/) and two [two-person](https://soundcloud.com/pilotlessdrone) [bands](https://soundcloud.com/pinwurm). He also takes care of the cutest cat in the world. - ### Dan Abramov {#dan-abramov} ![Dan Abramov](../images/team/gaearon.jpg) @@ -38,13 +30,35 @@ Brian studied art in college and did programming on the side to pay for his educ Dan got into programming after he accidentally discovered Visual Basic inside Microsoft PowerPoint. He has found his true calling in turning [Sebastian](#sebastian-markbage)'s tweets into long-form blog posts. Dan occasionally wins at Fortnite by hiding in a bush until the game ends. -### Dominic Gannaway {#dominic-gannaway} +### Jason Bonta {#jason-bonta} + +![Jason Bonta](../images/team/jasonbonta.jpg) + +Jason likes having large volumes of Amazon packages delivered to the office so that he can build forts. Despite literally walling himself off from his team at times and not understanding how for-of loops work, we appreciate him for the unique qualities he brings to his work. + +### Joe Savona {#joe-savona} + +![Joe Savona](../images/team/joe.jpg) + +[@josephsavona on GitHub](https://github.com/josephsavona) · [@en_JS on Twitter](https://twitter.com/en_JS) + +Joe was planning to major in math and philosophy but got into computer science after writing physics simulations in Matlab. Prior to React, he worked on Relay, RSocket.js, and the Skip programming language. While he’s not building some sort of reactive system he enjoys running, studying Japanese, and spending time with his family. -![Dominic](../images/team/trueadm.jpg) +### Josh Story {#josh-story} -[@trueadm on GitHub](https://github.com/trueadm) · [@trueadm on Twitter](https://twitter.com/trueadm) +![Josh Story](../images/team/josh.jpg) -Dominic is interested in travelling, drum and bass, stand-up comedy and spending time with the family. He most enjoys hacking on new unexplored ideas around UIs and accessibility and has a passion for tweaking and optimizing code to get the most performance out of things. +[@gnoff on GitHub](https://github.com/gnoff) · [@joshcstory on Twitter](https://twitter.com/joshcstory) + +Josh majored in Mathematics and discovered programming while in college. His first professional developer job was to program insurance rate calculations in Microsoft Excel, the paragon of Reactive Programming which must be why he now works on React. In between that time Josh has been an IC, Manager, and Executive at a few startups. outside of work he likes to push his limits with cooking. + +### Lauren Tan {#lauren-tan} + +![Lauren](../images/team/lauren.jpg) + +[@poteto on GitHub](https://github.com/poteto) · [@potetotes on Twitter](https://twitter.com/potetotes) + +Lauren’s programming career peaked when she first discovered the `<marquee>` tag. She’s been chasing that high ever since. When she’s not adding bugs into React, she enjoys dropping cheeky memes in chat, and playing all too many video games with her partner, and her dog Zelda. ### Luna Ruan {#luna-ruan} @@ -54,21 +68,37 @@ Dominic is interested in travelling, drum and bass, stand-up comedy and spending Luna learned programming because she thought it meant creating video games. Instead, she ended up working on the Pinterest web app, and now on React itself. Luna doesn't want to make video games anymore, but she plans to do creative writing if she ever gets bored. -### Nicolas Gallagher {#nicolas-gallagher} +### Mofei Zhang {#mofei-zhang} + +![Mofei](../images/team/mofei-zhang.png) + +[@mofeiZ on GitHub](https://github.com/mofeiZ)] + +Mofei started programming when she realized it can help her cheat in video games. She focused on operating systems in undergrad / grad school, but now finds herself happily tinkering on React. Outside of work, she enjoys debugging bouldering problems and planning her next backpacking trip(s). + +### Rick Hanlon {#rick-hanlon} -![Nicolas](../images/team/necolas.jpg) +![Ricky](../images/team/rickhanlonii.jpg) -[@necolas on GitHub](https://github.com/necolas) · [@necolas on Twitter](https://twitter.com/necolas) +[@rickhanlonii on GitHub](https://github.com/rickhanlonii) · [@rickhanlonii on Twitter](https://twitter.com/rickhanlonii) -Nicolas has majored in anthropology. He started in web development by building a website for a friend, and eventually found himself rebuilding the Twitter website with React. Nicolas describes himself as boring on paper, not much better in real life. +Ricky majored in theoretical math and somehow found himself on the React Native team for a couple years before joining the React team. When he's not programming you can find him snowboarding, biking, climbing, golfing, or closing GitHub issues that do not match the issue template. -### Rachel Nabors {#rachel-nabors} +### Samuel Susla {#samuel-susla} -![Rachel](../images/team/rnabors.jpg) +![Samuel Susla](../images/team/sam.jpg) -[@rachelnabors on GitHub](https://github.com/rachelnabors) · [@rachelnabors on Twitter](https://twitter.com/rachelnabors) +[@sammy-SC on GitHub](https://github.com/sammy-SC) · [@SamuelSusla on Twitter](https://twitter.com/SamuelSusla) -Rachel wrote a [book about UI animation](https://abookapart.com/products/animation-at-work) once and worked with MDN and the W3C on the web animations API. Now she is busy with education materials and community engineering on the React team. Secretly, she is an award-winning cartoonist for teenage girls. Catch her making fancy tea with lukewarm water in the microkitchen. +Samuel’s interest in programming started with the movie Matrix. He still has Matrix screen saver. Before working on React, he was focused on writing iOS apps. Outside of work, Samuel enjoys playing beach volleyball, squash, badminton and spending time with his family. + +### Sathya Gunasekaran {#sathya-gunasekaran} + +![Sathya Gunasekaran](../images/team/sathya.jpg) + +[@gsathya on GitHub](https://github.com/gsathya) · [@_gsathya on Twitter](https://twitter.com/_gsathya) + +Sathya hated the Dragon Book in school but somehow ended up working on compilers all his career. When he's not compiling React components, he's either drinking coffee or eating yet another Dosa. ### Sebastian Markbåge {#sebastian-markbage} @@ -78,13 +108,37 @@ Rachel wrote a [book about UI animation](https://abookapart.com/products/animati Sebastian majored in psychology. He's usually quiet. Even when he says something, it often doesn't make sense to the rest of us until a few months later. The correct way to pronounce his surname is "mark-boa-geh" but he settled for "mark-beige" out of pragmatism -- and that's how he approaches React. -### Sunil Pai {#sunil-pai} +### Sebastian Silbermann {#sebastian-silbermann} + +![Sebastian Silbermann](../images/team/sebsilbermann.jpg) + +[@eps1lon on GitHub](https://github.com/eps1lon) · [@sebsilbermann on Twitter](https://twitter.com/sebsilbermann) + +Sebastian learned programming to make the browser games he played during class more enjoyable. Eventually this lead to contributing to as much open source code as possible. Outside of coding he's busy making sure people don't confuse him with the other Sebastians and Zilberman of the React community. + +### Seth Webster {#seth-webster} + +![Seth](../images/team/seth.jpg) -![Sunil](../images/team/threepointone.jpg) +[@sethwebster on GitHub](https://github.com/sethwebster) · [@sethwebster on Twitter](https://twitter.com/sethwebster) -[@threepointone on GitHub](https://github.com/threepointone) · [@threepointone on Twitter](https://twitter.com/threepointone) +Seth started programming as a kid growing up in Tucson, AZ. After school, he was bitten by the music bug and was a touring musician for about 10 years before returning to *work*, starting with Intuit. In his spare time, he loves [taking pictures](https://www.sethwebster.com) and flying for animal rescues in the northeastern United States. -Sunil started writing JavaScript for IE6 and still has some regrets about it. He worked in consulting, design, and product firms, before joining the React team to help others do the same. Sunil plays a blue Les Paul guitar, loves London, and would appreciate a response to his last ping, please and thank you! +### Sophie Alpert {#sophie-alpert} + +![Sophie](../images/team/sophiebits.jpg) + +[@sophiebits on GitHub](https://github.com/sophiebits) · [@sophiebits on Twitter](https://twitter.com/sophiebits) + +Four days after React was released, Sophie rewrote the entirety of her then-current project to use it, which she now realizes was perhaps a bit reckless. After she became the project's #1 committer, she wondered why she wasn't getting paid by Facebook like everyone else was and joined the team officially to lead React through its adolescent years. Though she quit that job years ago, somehow she's still in the team's group chats and “providing value”. + +### Tianyu Yao {#tianyu-yao} + +![Tianyu Yao](../images/team/tianyu.jpg) + +[@tyao1 on GitHub](https://github.com/tyao1) · [@tianyu0 on Twitter](https://twitter.com/tianyu0) + +Tianyu’s interest in computers started as a kid because he loves video games. So he majored in computer science and still plays childish games like League of Legends. When he is not in front of a computer, he enjoys playing with his two kittens, hiking and kayaking. ### Yuzhi Zheng {#yuzhi-zheng} @@ -92,7 +146,7 @@ Sunil started writing JavaScript for IE6 and still has some regrets about it. He [@yuzhi on GitHub](https://github.com/yuzhi) · [@yuzhiz on Twitter](https://twitter.com/yuzhiz) -Yuzhi studied Computer Science in school. She liked the instant gratification of seeing code come to life without having to physically be in a laboratory. Now she manages the React team. Before management, she used to work on the Relay data fetching framework. In her spare time, Yuzhi enjoys optimizing her life via gardening and home improvement projects. +Yuzhi studied Computer Science in school. She liked the instant gratification of seeing code come to life without having to physically be in a laboratory. Now she’s a manager in the React org. Before management, she used to work on the Relay data fetching framework. In her spare time, Yuzhi enjoys optimizing her life via gardening and home improvement projects. ## Acknowledgements {#acknowledgements} @@ -103,12 +157,14 @@ React was originally created by [Jordan Walke](https://github.com/jordwalke). To * [Alex Krolick](https://github.com/alexkrolick) * [Alexey Pyltsyn](https://github.com/lex111) * [Brandon Dail](https://github.com/aweary) +* [Brian Vaughn](https://github.com/bvaughn) * [Caleb Meredith](https://github.com/calebmer) * [Chang Yan](https://github.com/cyan33) * [Cheng Lou](https://github.com/chenglou) * [Christoph Nakazawa](https://github.com/cpojer) * [Christopher Chedeau](https://github.com/vjeux) * [Clement Hoang](https://github.com/clemmy) +* [Dominic Gannaway](https://github.com/trueadm) * [Flarnie Marchan](https://github.com/flarnie) * [Jason Quense](https://github.com/jquense) * [Jesse Beach](https://github.com/jessebeach) @@ -118,17 +174,22 @@ React was originally created by [Jordan Walke](https://github.com/jordwalke). To * [Joe Critchley](https://github.com/joecritch) * [Jeff Morrison](https://github.com/jeffmo) * [Keyan Zhang](https://github.com/keyz) +* [Marco Salazar](https://github.com/salazarm) * [Nat Alison](https://github.com/tesseralis) * [Nathan Hunzaker](https://github.com/nhunzaker) +* [Nicolas Gallagher](https://github.com/necolas) * [Paul O'Shannessy](https://github.com/zpao) * [Pete Hunt](https://github.com/petehunt) * [Philipp Spiess](https://github.com/philipp-spiess) +* [Rachel Nabors](https://github.com/rachelnabors) +* [Robert Zhang](https://github.com/robertzhidealx) * [Sander Spies](https://github.com/sanderspies) -* [Sophia Shoemaker](https://github.com/mrscobbler) -* [Sophie Alpert](https://github.com/sophiebits) * [Sasha Aickin](https://github.com/aickin) +* [Sophia Shoemaker](https://github.com/mrscobbler) +* [Sunil Pai](https://github.com/threepointone) * [Tim Yung](https://github.com/yungsters) +* [Xuan Huang](https://github.com/huxpro) This list is not exhaustive. -We'd like to give special thanks to [Tom Occhino](https://github.com/tomocchino) and [Adam Wolff](https://github.com/wolffiex) for their guidance and support over the years. We'd also like to thank all the volunteers who [translated React into other languages](https://isreacttranslatedyet.com/). +We'd like to give special thanks to [Tom Occhino](https://github.com/tomocchino) and [Adam Wolff](https://github.com/wolffiex) for their guidance and support over the years. We'd also like to thank all the volunteers who [translated React into other languages](https://translations.reactjs.org/). diff --git a/content/community/videos.md b/content/community/videos.md index 01e54f753..d0c99e4da 100644 --- a/content/community/videos.md +++ b/content/community/videos.md @@ -10,6 +10,21 @@ redirect_from: Videos dedicated to the discussion of React and the React ecosystem. +### React Conf 2021 {#react-conf-2021} + +A playlist of videos from React Conf 2021. +<iframe title="React Conf 2021" width="650" height="366" src="https://www.youtube-nocookie.com/embed/videoseries?list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa" frameborder="0" allowfullscreen></iframe> + +### React Conf 2019 {#react-conf-2019} + +A playlist of videos from React Conf 2019. +<iframe title="React Conf 2019" width="650" height="366" src="https://www.youtube-nocookie.com/embed/playlist?list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh" frameborder="0" allowfullscreen></iframe> + +### React Conf 2018 {#react-conf-2018} + +A playlist of videos from React Conf 2018. +<iframe title="React Conf 2018" width="650" height="366" src="https://www.youtube-nocookie.com/embed/playlist?list=PLPxbbTqCLbGE5AihOSExAa4wUM-P42EIJ" frameborder="0" allowfullscreen></iframe> + ### React.js Conf 2017 {#reactjs-conf-2017} A playlist of videos from React.js Conf 2017. diff --git a/content/docs/accessibility.md b/content/docs/accessibility.md index 19878166a..6f0e93281 100644 --- a/content/docs/accessibility.md +++ b/content/docs/accessibility.md @@ -226,7 +226,7 @@ class Parent extends React.Component { this.inputElement.current.focus(); ``` -When using a HOC to extend components, it is recommended to [forward the ref](/docs/forwarding-refs.html) to the wrapped component using the `forwardRef` function of React. If a third party HOC does not implement ref forwarding, the above pattern can still be used as a fallback. +When using a [HOC](/docs/higher-order-components.html) to extend components, it is recommended to [forward the ref](/docs/forwarding-refs.html) to the wrapped component using the `forwardRef` function of React. If a third party HOC does not implement ref forwarding, the above pattern can still be used as a fallback. A great focus management example is the [react-aria-modal](https://github.com/davidtheclark/react-aria-modal). This is a relatively rare example of a fully accessible modal window. Not only does it set initial focus on the cancel button (preventing the keyboard user from accidentally activating the success action) and trap keyboard focus inside the modal, it also resets focus back to the element that initially triggered the modal. @@ -376,7 +376,7 @@ These are toolboxes filled with HTML attributes that are fully supported in JSX Each type of widget has a specific design pattern and is expected to function in a certain way by users and user agents alike: -- [WAI-ARIA Authoring Practices - Design Patterns and Widgets](https://www.w3.org/TR/wai-aria-practices/#aria_ex) +- [ARIA Authoring Practices Guide (APG) - Design Patterns and Examples](https://www.w3.org/WAI/ARIA/apg/patterns/) - [Heydon Pickering - ARIA Examples](https://heydonworks.com/article/practical-aria-examples/) - [Inclusive Components](https://inclusive-components.design/) @@ -404,7 +404,7 @@ Ensure that all readable text on your website has sufficient color contrast to r - [Everything About Color Contrast And Why You Should Rethink It](https://www.smashingmagazine.com/2014/10/color-contrast-tips-and-tools-for-accessibility/) - [A11yProject - What is Color Contrast](https://a11yproject.com/posts/what-is-color-contrast/) -It can be tedious to manually calculate the proper color combinations for all cases in your website so instead, you can [calculate an entire accessible color palette with Colorable](https://jxnblk.com/colorable/). +It can be tedious to manually calculate the proper color combinations for all cases in your website so instead, you can [calculate an entire accessible color palette with Colorable](https://colorable.jxnblk.com/). Both the aXe and WAVE tools mentioned below also include color contrast tests and will report on contrast errors. @@ -454,7 +454,7 @@ Deque Systems offers [aXe-core](https://github.com/dequelabs/axe-core) for autom [The Accessibility Engine](https://www.deque.com/products/axe/) or aXe, is an accessibility inspector browser extension built on `aXe-core`. -You can also use the [react-axe](https://github.com/dylanb/react-axe) module to report these accessibility findings directly to the console while developing and debugging. +You can also use the [@axe-core/react](https://github.com/dequelabs/axe-core-npm/tree/develop/packages/react) module to report these accessibility findings directly to the console while developing and debugging. #### WebAIM WAVE {#webaim-wave} diff --git a/content/docs/add-react-to-a-website.md b/content/docs/add-react-to-a-website.md index fbbe71a9c..4f441f164 100644 --- a/content/docs/add-react-to-a-website.md +++ b/content/docs/add-react-to-a-website.md @@ -25,7 +25,11 @@ next: create-a-new-react-app.html Δεν θα χρειαστούν περίπλοκα εργαλεία ούτε απαιτήσεις εγκατάστασης. **Για να ολοκληρώσετε αυτήν την ενότητα, το μόνο που θα χρειαστείτε είναι μια σύνδεση στο διαδίκτυο και ένα λεπτό από τον χρόνο σας.** +<<<<<<< HEAD Προαιρετικό: [Κάντε λήψη του πλήρους παραδείγματος (2 KB συμπιεσμένο)](https://gist.github.com/gaearon/6668a1f6986742109c00a581ce704605/archive/f6c882b6ae18bde42dcf6fdb751aae93495a2275.zip) +======= +Optional: [Download the full example (2KB zipped)](https://gist.github.com/gaearon/6668a1f6986742109c00a581ce704605/archive/87f0b6f34238595b44308acfb86df6ea43669c08.zip) +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### Βήμα 1: Προσθήκη ενός DOM container στην HTML {#step-1-add-a-dom-container-to-the-html} @@ -52,10 +56,17 @@ next: create-a-new-react-app.html ```html{5,6,9} <!-- ... άλλη HTML ... --> +<<<<<<< HEAD <!-- Φόρτωση του React. --> <!-- Σημείωση: Όταν κάνετε deploy, αντικαταστήστε το "development.js" με το "production.min.js". --> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> +======= + <!-- Load React. --> + <!-- Note: when deploying, replace "development.js" with "production.min.js". --> + <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> + <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script> +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a <!-- Φόρτωση του React component μας. --> <script src="like_button.js"></script> @@ -75,16 +86,28 @@ next: create-a-new-react-app.html > >Αυτός ο κώδικας ορίζει ένα React component που ονομάζεται `LikeButton`. Μην ανησυχείτε αν δεν τον κατανοείτε ακόμη. Θα καλύψουμε τα δομικά στοιχεία του React αργότερα στο [πρακτικό tutorial](/tutorial/tutorial.html) και τον [οδηγό βασικών εννοιών](/docs/hello-world.html). Για την ώρα, ας τον κάνουμε να εμφανιστεί στην οθόνη! +<<<<<<< HEAD Μετά από **[τον κώδικα starter](https://gist.github.com/gaearon/0b180827c190fe4fd98b4c7f570ea4a8/raw/b9157ce933c79a4559d2aa9ff3372668cce48de7/LikeButton.js)**, προσθέστε δύο σειρές στο κάτω μέρος του `like_button.js`: ```js{3,4} // ... ο κώδικας starter που επικολλήσατε ... +======= +After **[the starter code](https://gist.github.com/gaearon/0b180827c190fe4fd98b4c7f570ea4a8/raw/b9157ce933c79a4559d2aa9ff3372668cce48de7/LikeButton.js)**, add three lines to the bottom of `like_button.js`: + +```js{3,4,5} +// ... the starter code you pasted ... +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a const domContainer = document.querySelector('#like_button_container'); -ReactDOM.render(e(LikeButton), domContainer); +const root = ReactDOM.createRoot(domContainer); +root.render(e(LikeButton)); ``` +<<<<<<< HEAD Αυτές οι δύο σειρές κώδικα εντοπίζουν το `<div>` που προσθέσαμε στην HTML μας στο 1ο βήμα και, έπειτα, εμφανίζουν εντός του το React component μας με το κουμπί "Μου αρέσει". +======= +These three lines of code find the `<div>` we added to our HTML in the first step, create a React app with it, and then display our "Like" button React component inside of it. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### Αυτό ήταν όλο! {#thats-it} @@ -94,7 +117,11 @@ ReactDOM.render(e(LikeButton), domContainer); **[Προβολή του πλήρους πηγαίου κώδικα του παραδείγματος](https://gist.github.com/gaearon/6668a1f6986742109c00a581ce704605)** +<<<<<<< HEAD **[Λήψη του πλήρους παραδείγματος (2 KB συμπιεσμένο)](https://gist.github.com/gaearon/6668a1f6986742109c00a581ce704605/archive/f6c882b6ae18bde42dcf6fdb751aae93495a2275.zip)** +======= +**[Download the full example (2KB zipped)](https://gist.github.com/gaearon/6668a1f6986742109c00a581ce704605/archive/87f0b6f34238595b44308acfb86df6ea43669c08.zip)** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### Συμβουλή: Επαναχρησιμοποίηση ενός Component {#tip-reuse-a-component} @@ -102,7 +129,11 @@ ReactDOM.render(e(LikeButton), domContainer); [Προβολή του πλήρους πηγαίου κώδικα του παραδείγματος](https://gist.github.com/gaearon/faa67b76a6c47adbab04f739cba7ceda) +<<<<<<< HEAD [Λήψη του πλήρους παραδείγματος (2 KB συμπιεσμένο)](https://gist.github.com/gaearon/faa67b76a6c47adbab04f739cba7ceda/archive/9d0dd0ee941fea05fd1357502e5aa348abb84c12.zip) +======= +[Download the full example (2KB zipped)](https://gist.github.com/gaearon/faa67b76a6c47adbab04f739cba7ceda/archive/279839cb9891bd41802ebebc5365e9dec08eeb9f.zip) +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a >Σημείωση > @@ -115,15 +146,19 @@ ReactDOM.render(e(LikeButton), domContainer); Εάν έχετε ήδη ελαχιστοποιήσει τα application scripts, **ο ιστότοπός σας θα είναι έτοιμος για παραγωγή**, εφόσον διασφαλίσετε ότι η deployed HTML φορτώνει τις εκδόσεις του React με κατάληξη `production.min.js`: ```js -<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script> -<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> +<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script> +<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script> ``` Εάν δεν περιλαμβάνετε ένα βήμα ελαχιστοποίησης για τα scripts σας, [εδώ μπορείτε να βρείτε έναν τρόπο για να δημιουργήσετε ένα](https://gist.github.com/gaearon/42a2ffa41b8319948f9be4076286e1f3). ## Προαιρετικό: Δοκιμή του React με το JSX {#optional-try-react-with-jsx} +<<<<<<< HEAD Στα παραπάνω παραδείγματα, βασιστήκαμε αποκλειστικά σε features που υποστηρίζονται εγγενώς από τα προγράμματα περιήγησης. Αυτός είναι ο λόγος που χρησιμοποιήσαμε ένα JavaScript function call για να πούμε στο React τι να εμφανίσει: +======= +In the examples above, we only relied on features that are natively supported by browsers. This is why we used a JavaScript function call to tell React what to display: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```js const e = React.createElement; @@ -149,7 +184,11 @@ return ( Αυτά τα δύο snippets κώδικα είναι ισοδύναμα. Ενώ το **JSX είναι [εξ ολοκλήρου προαιρετικό](/docs/react-without-jsx.html)**, πολλοί το θεωρούν χρήσιμο για τη σύνταξη κώδικα UI - τόσο με το React όσο και με άλλες βιβλιοθήκες. +<<<<<<< HEAD Μπορείτε να πειραματιστείτε με το JSX χρησιμοποιώντας [αυτόν τον online μετατροπέα](https://babeljs.io/en/repl#?babili=false&browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=DwIwrgLhD2B2AEcDCAbAlgYwNYF4DeAFAJTw4B88EAFmgM4B0tAphAMoQCGETBe86WJgBMAXJQBOYJvAC-RGWQBQ8FfAAyaQYuAB6cFDhkgA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.4.3). +======= +You can play with JSX using [this online converter](https://babeljs.io/en/repl#?babili=false&browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=DwIwrgLhD2B2AEcDCAbAlgYwNYF4DeAFAJTw4B88EAFmgM4B0tAphAMoQCGETBe86WJgBMAXJQBOYJvAC-RGWQBQ8FfAAyaQYuAB6cFDhkgA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.15.7). +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### Γρήγορη δοκιμή του JSX {#quickly-try-jsx} @@ -159,7 +198,11 @@ return ( <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> ``` +<<<<<<< HEAD Τώρα, μπορείτε να χρησιμοποιήσετε το JSX σε οποιοδήποτε `<script>` tag προσθέτοντας το `type="text/babel"` attribute σε αυτό. Ακολουθεί [ένα παράδειγμα αρχείου HTML με JSX](https://raw.githubusercontent.com/reactjs/reactjs.org/master/static/html/single-file-example.html), το οποίο μπορείτε να κάνετε λήψη και να πειραματιστείτε μαζί του. +======= +Now you can use JSX in any `<script>` tag by adding `type="text/babel"` attribute to it. Here is [an example HTML file with JSX](https://raw.githubusercontent.com/reactjs/reactjs.org/main/static/html/single-file-example.html) that you can download and play with. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Αυτή η προσέγγιση είναι ιδανική για εκμάθηση και δημιουργία απλών demos. Ωστόσο, καθιστά τον ιστότοπό σας πιο αργό και **δεν είναι κατάλληλη για το στάδιο της παραγωγής**. Όταν είστε έτοιμος να προχωρήσετε, αφαιρέστε αυτό το νέο `<script>` tag και τα `type="text/babel"` attributes που προσθέσατε. Αντ' αυτού, στην επόμενη ενότητα θα ρυθμίσετε ένα JSX preprocessor για την αυτόματη μετατροπή όλων των `<script>` tags σας. @@ -183,8 +226,8 @@ return ( Δημιουργήστε έναν φάκελο με την ονομασία `src` και εκτελέστε αυτήν την εντολή τερματικού: -``` -npx babel --watch src --out-dir . --presets react-app/prod +```console +npx babel --watch src --out-dir . --presets react-app/prod ``` >Σημείωση diff --git a/content/docs/addons-animation.md b/content/docs/addons-animation.md index f83dd63f3..fd724dbef 100644 --- a/content/docs/addons-animation.md +++ b/content/docs/addons-animation.md @@ -50,7 +50,7 @@ class TodoList extends React.Component { render() { const items = this.state.items.map((item, i) => ( - <div key={item} onClick={() => this.handleRemove(i)}> + <div key={i} onClick={() => this.handleRemove(i)}> {item} </div> )); diff --git a/content/docs/addons-perf.md b/content/docs/addons-perf.md index a07b1d05a..221ce2e86 100644 --- a/content/docs/addons-perf.md +++ b/content/docs/addons-perf.md @@ -27,8 +27,8 @@ In addition to giving you an overview of your app's overall performance, `Perf` See these articles for an introduction to React performance tooling: - ["How to Benchmark React Components"](https://medium.com/code-life/how-to-benchmark-react-components-the-quick-and-dirty-guide-f595baf1014c) - - ["Performance Engineering with React"](https://benchling.engineering/performance-engineering-with-react/) - - ["A Deep Dive into React Perf Debugging"](https://benchling.engineering/deep-dive-react-perf-debugging/) + - ["Performance Engineering with React"](https://benchling.engineering/performance-engineering-with-react-e03013e53285) + - ["A Deep Dive into React Perf Debugging"](https://benchling.engineering/a-deep-dive-into-react-perf-debugging-fd2063f5a667) ### Development vs. Production Builds {#development-vs-production-builds} diff --git a/content/docs/addons-pure-render-mixin.md b/content/docs/addons-pure-render-mixin.md index 872445309..264f3e3db 100644 --- a/content/docs/addons-pure-render-mixin.md +++ b/content/docs/addons-pure-render-mixin.md @@ -39,6 +39,6 @@ Under the hood, the mixin implements [shouldComponentUpdate](/docs/component-spe > Note: > -> This only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only mix into components which have simple props and state, or use `forceUpdate()` when you know deep data structures have changed. Or, consider using [immutable objects](https://facebook.github.io/immutable-js/) to facilitate fast comparisons of nested data. +> This only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only mix into components which have simple props and state, or use `forceUpdate()` when you know deep data structures have changed. Or, consider using [immutable objects](https://immutable-js.com/) to facilitate fast comparisons of nested data. > > Furthermore, `shouldComponentUpdate` skips updates for the whole component subtree. Make sure all the children components are also "pure". diff --git a/content/docs/addons-shallow-renderer.md b/content/docs/addons-shallow-renderer.md index 96ccc07b8..27abaddab 100644 --- a/content/docs/addons-shallow-renderer.md +++ b/content/docs/addons-shallow-renderer.md @@ -59,7 +59,11 @@ expect(result.props.children).toEqual([ Μπορείτε να σκεφτείτε το shallowRenderer σαν ένα "σημείο" όπου κάνετε render το component που δοκιμάζετε, και από το οποίο μπορείτε να εξάγετε το αποτέλεσμα του component. +<<<<<<< HEAD Το `shallowRenderer.render()` είναι παρόμοιο με το [`ReactDOM.render()`](/docs/react-dom.html#render) αλλά δεν απαιτεί το DOM και κάνει render μόνο σε βάθος ενός επιπέδου. Αυτό σημαίνει ότι μπορείτε να δοκιμάσετε components απομονωμένα από το πως έχουν εφαρμοστεί τα childen τους. +======= +`shallowRenderer.render()` is similar to [`root.render()`](/docs/react-dom-client.html#createroot) but it doesn't require DOM and only renders a single level deep. This means you can test components isolated from how their children are implemented. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### `shallowRenderer.getRenderOutput()` {#shallowrenderergetrenderoutput} diff --git a/content/docs/addons-test-utils.md b/content/docs/addons-test-utils.md index a75028612..0756c4843 100644 --- a/content/docs/addons-test-utils.md +++ b/content/docs/addons-test-utils.md @@ -19,9 +19,17 @@ var ReactTestUtils = require('react-dom/test-utils'); // ES5 with npm > Σημείωση: > +<<<<<<< HEAD > Συνιστούμε τη χρήση του [`react-testing-library`](https://testing-library.com/react) το οποίο είναι σχεδιασμένο να επιτρέπει και να ενθαρρύνει το γράψιμο tests τα οποία χρησιμοποιούν τα components με τον ίδιο τρόπο που τα χρησιμοποιούν και οι χρήστες. > > Εναλλακτικά, το Airbnb έχει ανακοινώσει ενα testing utility, το [Enzyme](https://airbnb.io/enzyme/), το οποίο διευκολύνει στο assert, manipulate, και traverse του output των React Components. +======= +> We recommend using [React Testing Library](https://testing-library.com/react) which is designed to enable and encourage writing tests that use your components as the end users do. +> +> For React versions <= 16, the [Enzyme](https://airbnb.io/enzyme/) library makes it easy to assert, manipulate, and traverse your React Components' output. + + +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a - [`act()`](#act) - [`mockComponent()`](#mockcomponent) @@ -87,7 +95,7 @@ class Counter extends React.Component { ```js{3,20-22,29-31} import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { act } from 'react-dom/test-utils'; import Counter from './Counter'; @@ -106,7 +114,7 @@ afterEach(() => { it('can render and update a counter', () => { // Τεστάρει το πρώτο render και το componentDidMount act(() => { - ReactDOM.render(<Counter />, container); + ReactDOM.createRoot(container).render(<Counter />); }); const button = container.querySelector('button'); const label = container.querySelector('p'); @@ -150,10 +158,14 @@ mockComponent( > Σημείωση: > <<<<<<< HEAD +<<<<<<< HEAD > Το `mockComponent()` είναι ένα legacy API. Συνιστούμε να χρησιμοποιήσετε το [shallow rendering](/docs/shallow-renderer.html) ή το [`jest.mock()`](https://facebook.github.io/jest/docs/en/tutorial-react-native.html#mock-native-modules-using-jestmock). ======= > `mockComponent()` is a legacy API. We recommend using [`jest.mock()`](https://facebook.github.io/jest/docs/en/tutorial-react-native.html#mock-native-modules-using-jestmock) instead. >>>>>>> ddbd064d41d719f9ec0c2f6a4227f797a5828310 +======= +> `mockComponent()` is a legacy API. We recommend using [`jest.mock()`](https://jestjs.io/docs/tutorial-react-native#mock-native-modules-using-jestmock) instead. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a * * * @@ -314,7 +326,7 @@ renderIntoDocument(element) ```js const domContainer = document.createElement('div'); -ReactDOM.render(element, domContainer); +ReactDOM.createRoot(domContainer).render(element); ``` > Σημείωση: diff --git a/content/docs/addons-update.md b/content/docs/addons-update.md index 0e0241074..4e9a37d4b 100644 --- a/content/docs/addons-update.md +++ b/content/docs/addons-update.md @@ -21,7 +21,7 @@ var update = require('react-addons-update'); // ES5 with npm React lets you use whatever style of data management you want, including mutation. However, if you can use immutable data in performance-critical parts of your application it's easy to implement a fast [`shouldComponentUpdate()`](/docs/react-component.html#shouldcomponentupdate) method to significantly speed up your app. -Dealing with immutable data in JavaScript is more difficult than in languages designed for it, like [Clojure](https://clojure.org/). However, we've provided a simple immutability helper, `update()`, that makes dealing with this type of data much easier, *without* fundamentally changing how your data is represented. You can also take a look at Facebook's [Immutable-js](https://facebook.github.io/immutable-js/docs/) and the [Advanced Performance](/docs/advanced-performance.html) section for more detail on Immutable-js. +Dealing with immutable data in JavaScript is more difficult than in languages designed for it, like [Clojure](https://clojure.org/). However, we've provided a simple immutability helper, `update()`, that makes dealing with this type of data much easier, *without* fundamentally changing how your data is represented. You can also take a look at Facebook's [Immutable-js](https://immutable-js.com/docs/latest@main/) and the [Advanced Performance](/docs/advanced-performance.html) section for more detail on Immutable-js. ### The Main Idea {#the-main-idea} diff --git a/content/docs/cdn-links.md b/content/docs/cdn-links.md index 00b73209d..e15c4bccd 100644 --- a/content/docs/cdn-links.md +++ b/content/docs/cdn-links.md @@ -3,24 +3,28 @@ id: cdn-links title: CDN Links permalink: docs/cdn-links.html prev: create-a-new-react-app.html -next: hello-world.html +next: release-channels.html --- Τόσο το React όσο και το ReactDOM είναι διαθέσιμα μέσω CDN. ```html -<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> -<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> +<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> +<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> ``` Οι παραπάνω εκδόσεις προορίζονται μόνο για ανάπτυξη (Development) και δεν είναι κατάλληλες για παραγωγή (Production). Ελαχιστοποιημένες (Minified) και βελτιστοποιημένες (Optimized) εκδόσεις παραγωγής (Production) του React διατίθενται στη διεύθυνση: ```html -<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> -<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> +<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> +<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> ``` +<<<<<<< HEAD Για να φορτώσετε μια συγκεκριμένη έκδοση του `react` και `react-dom`, αντικαταστήστε το `16` με τον αριθμό έκδοσης. +======= +To load a specific version of `react` and `react-dom`, replace `18` with the version number. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### Γιατί το `crossorigin` χαρακτηριστικό (Attribute)? {#why-the-crossorigin-attribute} diff --git a/content/docs/code-splitting.md b/content/docs/code-splitting.md index e56509b8b..10c745b2c 100644 --- a/content/docs/code-splitting.md +++ b/content/docs/code-splitting.md @@ -6,12 +6,7 @@ permalink: docs/code-splitting.html ## Bundling {#bundling} -Most React apps will have their files "bundled" using tools like -[Webpack](https://webpack.js.org/), [Rollup](https://rollupjs.org/) or -[Browserify](http://browserify.org/). -Bundling is the process of following imported files and merging them into a -single file: a "bundle". This bundle can then be included on a webpage to load -an entire app at once. +Most React apps will have their files "bundled" using tools like [Webpack](https://webpack.js.org/), [Rollup](https://rollupjs.org/) or [Browserify](http://browserify.org/). Bundling is the process of following imported files and merging them into a single file: a "bundle". This bundle can then be included on a webpage to load an entire app at once. #### Example {#example} @@ -45,38 +40,22 @@ console.log(add(16, 26)); // 42 > > Your bundles will end up looking a lot different than this. -If you're using [Create React App](https://github.com/facebookincubator/create-react-app), [Next.js](https://github.com/zeit/next.js/), [Gatsby](https://www.gatsbyjs.org/), or a similar tool, you will have a Webpack setup out of the box to bundle your -app. +If you're using [Create React App](https://create-react-app.dev/), [Next.js](https://nextjs.org/), [Gatsby](https://www.gatsbyjs.org/), or a similar tool, you will have a Webpack setup out of the box to bundle your app. -If you aren't, you'll need to setup bundling yourself. For example, see the -[Installation](https://webpack.js.org/guides/installation/) and -[Getting Started](https://webpack.js.org/guides/getting-started/) guides on the -Webpack docs. +If you aren't, you'll need to set up bundling yourself. For example, see the [Installation](https://webpack.js.org/guides/installation/) and [Getting Started](https://webpack.js.org/guides/getting-started/) guides on the Webpack docs. ## Code Splitting {#code-splitting} -Bundling is great, but as your app grows, your bundle will grow too. Especially -if you are including large third-party libraries. You need to keep an eye on -the code you are including in your bundle so that you don't accidentally make -it so large that your app takes a long time to load. +Bundling is great, but as your app grows, your bundle will grow too. Especially if you are including large third-party libraries. You need to keep an eye on the code you are including in your bundle so that you don't accidentally make it so large that your app takes a long time to load. -To avoid winding up with a large bundle, it's good to get ahead of the problem -and start "splitting" your bundle. -Code-Splitting is a feature -supported by bundlers like [Webpack](https://webpack.js.org/guides/code-splitting/), [Rollup](https://rollupjs.org/guide/en/#code-splitting) and Browserify (via -[factor-bundle](https://github.com/browserify/factor-bundle)) which can create -multiple bundles that can be dynamically loaded at runtime. +To avoid winding up with a large bundle, it's good to get ahead of the problem and start "splitting" your bundle. Code-Splitting is a feature +supported by bundlers like [Webpack](https://webpack.js.org/guides/code-splitting/), [Rollup](https://rollupjs.org/guide/en/#code-splitting) and Browserify (via [factor-bundle](https://github.com/browserify/factor-bundle)) which can create multiple bundles that can be dynamically loaded at runtime. -Code-splitting your app can help you "lazy-load" just the things that are -currently needed by the user, which can dramatically improve the performance of -your app. While you haven't reduced the overall amount of code in your app, -you've avoided loading code that the user may never need, and reduced the amount -of code needed during the initial load. +Code-splitting your app can help you "lazy-load" just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you haven't reduced the overall amount of code in your app, you've avoided loading code that the user may never need, and reduced the amount of code needed during the initial load. ## `import()` {#import} -The best way to introduce code-splitting into your app is through the dynamic -`import()` syntax. +The best way to introduce code-splitting into your app is through the dynamic `import()` syntax. **Before:** @@ -94,23 +73,14 @@ import("./math").then(math => { }); ``` -When Webpack comes across this syntax, it automatically starts code-splitting -your app. If you're using Create React App, this is already configured for you -and you can [start using it](https://facebook.github.io/create-react-app/docs/code-splitting) immediately. It's also supported -out of the box in [Next.js](https://github.com/zeit/next.js/#dynamic-import). +When Webpack comes across this syntax, it automatically starts code-splitting your app. If you're using Create React App, this is already configured for you and you can [start using it](https://create-react-app.dev/docs/code-splitting/) immediately. It's also supported out of the box in [Next.js](https://nextjs.org/docs/advanced-features/dynamic-import). -If you're setting up Webpack yourself, you'll probably want to read Webpack's -[guide on code splitting](https://webpack.js.org/guides/code-splitting/). Your Webpack config should look vaguely [like this](https://gist.github.com/gaearon/ca6e803f5c604d37468b0091d9959269). +If you're setting up Webpack yourself, you'll probably want to read Webpack's [guide on code splitting](https://webpack.js.org/guides/code-splitting/). Your Webpack config should look vaguely [like this](https://gist.github.com/gaearon/ca6e803f5c604d37468b0091d9959269). -When using [Babel](https://babeljs.io/), you'll need to make sure that Babel can -parse the dynamic import syntax but is not transforming it. For that you will need [babel-plugin-syntax-dynamic-import](https://yarnpkg.com/en/package/babel-plugin-syntax-dynamic-import). +When using [Babel](https://babeljs.io/), you'll need to make sure that Babel can parse the dynamic import syntax but is not transforming it. For that you will need [@babel/plugin-syntax-dynamic-import](https://classic.yarnpkg.com/en/package/@babel/plugin-syntax-dynamic-import). ## `React.lazy` {#reactlazy} -> Note: -> -> `React.lazy` and Suspense are not yet available for server-side rendering. If you want to do code-splitting in a server rendered app, we recommend [Loadable Components](https://github.com/gregberge/loadable-components). It has a nice [guide for bundle splitting with server-side rendering](https://loadable-components.com/docs/server-side-rendering/). - The `React.lazy` function lets you render a dynamic import as a regular component. **Before:** @@ -169,6 +139,52 @@ function MyComponent() { } ``` +### Avoiding fallbacks {#avoiding-fallbacks} +Any component may suspend as a result of rendering, even components that were already shown to the user. In order for screen content to always be consistent, if an already shown component suspends, React has to hide its tree up to the closest `<Suspense>` boundary. However, from the user's perspective, this can be disorienting. + +Consider this tab switcher: + +```js +import React, { Suspense } from 'react'; +import Tabs from './Tabs'; +import Glimmer from './Glimmer'; + +const Comments = React.lazy(() => import('./Comments')); +const Photos = React.lazy(() => import('./Photos')); + +function MyComponent() { + const [tab, setTab] = React.useState('photos'); + + function handleTabSelect(tab) { + setTab(tab); + }; + + return ( + <div> + <Tabs onTabSelect={handleTabSelect} /> + <Suspense fallback={<Glimmer />}> + {tab === 'photos' ? <Photos /> : <Comments />} + </Suspense> + </div> + ); +} + +``` + +In this example, if tab gets changed from `'photos'` to `'comments'`, but `Comments` suspends, the user will see a glimmer. This makes sense because the user no longer wants to see `Photos`, the `Comments` component is not ready to render anything, and React needs to keep the user experience consistent, so it has no choice but to show the `Glimmer` above. + +However, sometimes this user experience is not desirable. In particular, it is sometimes better to show the "old" UI while the new UI is being prepared. You can use the new [`startTransition`](/docs/react-api.html#starttransition) API to make React do this: + +```js +function handleTabSelect(tab) { + startTransition(() => { + setTab(tab); + }); +} +``` + +Here, you tell React that setting tab to `'comments'` is not an urgent update, but is a [transition](/docs/react-api.html#transitions) that may take some time. React will then keep the old UI in place and interactive, and will switch to showing `<Comments />` when it is ready. See [Transitions](/docs/react-api.html#transitions) for more info. + ### Error boundaries {#error-boundaries} If the other module fails to load (for example, due to network failure), it will trigger an error. You can handle these errors to show a nice user experience and manage recovery with [Error Boundaries](/docs/error-boundaries.html). Once you've created your Error Boundary, you can use it anywhere above your lazy components to display an error state when there's a network error. @@ -196,21 +212,15 @@ const MyComponent = () => ( ## Route-based code splitting {#route-based-code-splitting} -Deciding where in your app to introduce code splitting can be a bit tricky. You -want to make sure you choose places that will split bundles evenly, but won't -disrupt the user experience. +Deciding where in your app to introduce code splitting can be a bit tricky. You want to make sure you choose places that will split bundles evenly, but won't disrupt the user experience. -A good place to start is with routes. Most people on the web are used to -page transitions taking some amount of time to load. You also tend to be -re-rendering the entire page at once so your users are unlikely to be -interacting with other elements on the page at the same time. +A good place to start is with routes. Most people on the web are used to page transitions taking some amount of time to load. You also tend to be re-rendering the entire page at once so your users are unlikely to be interacting with other elements on the page at the same time. -Here's an example of how to setup route-based code splitting into your app using -libraries like [React Router](https://reacttraining.com/react-router/) with `React.lazy`. +Here's an example of how to setup route-based code splitting into your app using libraries like [React Router](https://reactrouter.com/) with `React.lazy`. ```js import React, { Suspense, lazy } from 'react'; -import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; const Home = lazy(() => import('./routes/Home')); const About = lazy(() => import('./routes/About')); @@ -218,10 +228,10 @@ const About = lazy(() => import('./routes/About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> - <Switch> - <Route exact path="/" component={Home}/> - <Route path="/about" component={About}/> - </Switch> + <Routes> + <Route path="/" element={<Home />} /> + <Route path="/about" element={<About />} /> + </Routes> </Suspense> </Router> ); diff --git a/content/docs/codebase-overview.md b/content/docs/codebase-overview.md index 010fdfb5d..5203c47e7 100644 --- a/content/docs/codebase-overview.md +++ b/content/docs/codebase-overview.md @@ -15,18 +15,12 @@ If you want to [contribute to React](/docs/how-to-contribute.html) we hope that We don't necessarily recommend any of these conventions in React apps. Many of them exist for historical reasons and might change with time. -### External Dependencies {#external-dependencies} - -React has almost no external dependencies. Usually, a `require()` points to a file in React's own codebase. However, there are a few relatively rare exceptions. - -The [fbjs repository](https://github.com/facebook/fbjs) exists because React shares some small utilities with libraries like [Relay](https://github.com/facebook/relay), and we keep them in sync. We don't depend on equivalent small modules in the Node ecosystem because we want Facebook engineers to be able to make changes to them whenever necessary. None of the utilities inside fbjs are considered to be public API, and they are only intended for use by Facebook projects such as React. - ### Top-Level Folders {#top-level-folders} After cloning the [React repository](https://github.com/facebook/react), you will see a few top-level folders in it: -* [`packages`](https://github.com/facebook/react/tree/master/packages) contains metadata (such as `package.json`) and the source code (`src` subdirectory) for all packages in the React repository. **If your change is related to the code, the `src` subdirectory of each package is where you'll spend most of your time.** -* [`fixtures`](https://github.com/facebook/react/tree/master/fixtures) contains a few small React test applications for contributors. +* [`packages`](https://github.com/facebook/react/tree/main/packages) contains metadata (such as `package.json`) and the source code (`src` subdirectory) for all packages in the React repository. **If your change is related to the code, the `src` subdirectory of each package is where you'll spend most of your time.** +* [`fixtures`](https://github.com/facebook/react/tree/main/fixtures) contains a few small React test applications for contributors. * `build` is the build output of React. It is not in the repository but it will appear in your React clone after you [build it](/docs/how-to-contribute.html#development-workflow) for the first time. The documentation is hosted [in a separate repository from React](https://github.com/reactjs/reactjs.org). @@ -41,33 +35,11 @@ For example, a test for [`setInnerHTML.js`](https://github.com/facebook/react/bl ### Warnings and Invariants {#warnings-and-invariants} -The React codebase uses the `warning` module to display warnings: - -```js -var warning = require('warning'); - -warning( - 2 + 2 === 4, - 'Math is not working today.' -); -``` - -**The warning is shown when the `warning` condition is `false`.** - -One way to think about it is that the condition should reflect the normal situation rather than the exceptional one. - -It is a good idea to avoid spamming the console with duplicate warnings: +The React codebase uses `console.error` to display warnings: ```js -var warning = require('warning'); - -var didWarnAboutMath = false; -if (!didWarnAboutMath) { - warning( - 2 + 2 === 4, - 'Math is not working today.' - ); - didWarnAboutMath = true; +if (__DEV__) { + console.error('Something is wrong.'); } ``` @@ -120,46 +92,13 @@ ReactRef.detachRefs = function( When possible, new code should use Flow annotations. You can run `yarn flow` locally to check your code with Flow. -### Dynamic Injection {#dynamic-injection} - -React uses dynamic injection in some modules. While it is always explicit, it is still unfortunate because it hinders understanding of the code. The main reason it exists is because React originally only supported DOM as a target. React Native started as a React fork. We had to add dynamic injection to let React Native override some behaviors. - -You may see modules declaring their dynamic dependencies like this: - -```js -// Dynamically injected -var textComponentClass = null; - -// Relies on dynamically injected value -function createInstanceForText(text) { - return new textComponentClass(text); -} - -var ReactHostComponent = { - createInstanceForText, - - // Provides an opportunity for dynamic injection - injection: { - injectTextComponentClass: function(componentClass) { - textComponentClass = componentClass; - }, - }, -}; - -module.exports = ReactHostComponent; -``` - -The `injection` field is not handled specially in any way. But by convention, it means that this module wants to have some (presumably platform-specific) dependencies injected into it at runtime. - -There are multiple injection points in the codebase. In the future, we intend to get rid of the dynamic injection mechanism and wire up all the pieces statically during the build. - ### Multiple Packages {#multiple-packages} React is a [monorepo](https://danluu.com/monorepo/). Its repository contains multiple separate packages so that their changes can be coordinated together, and issues live in one place. ### React Core {#react-core} -The "core" of React includes all the [top-level `React` APIs](/docs/top-level-api.html#react), for example: +The "core" of React includes all the [top-level `React` APIs](/docs/react-api.html#react), for example: * `React.createElement()` * `React.Component` @@ -167,25 +106,25 @@ The "core" of React includes all the [top-level `React` APIs](/docs/top-level-ap **React core only includes the APIs necessary to define components.** It does not include the [reconciliation](/docs/reconciliation.html) algorithm or any platform-specific code. It is used both by React DOM and React Native components. -The code for React core is located in [`packages/react`](https://github.com/facebook/react/tree/master/packages/react) in the source tree. It is available on npm as the [`react`](https://www.npmjs.com/package/react) package. The corresponding standalone browser build is called `react.js`, and it exports a global called `React`. +The code for React core is located in [`packages/react`](https://github.com/facebook/react/tree/main/packages/react) in the source tree. It is available on npm as the [`react`](https://www.npmjs.com/package/react) package. The corresponding standalone browser build is called `react.js`, and it exports a global called `React`. ### Renderers {#renderers} -React was originally created for the DOM but it was later adapted to also support native platforms with [React Native](https://facebook.github.io/react-native/). This introduced the concept of "renderers" to React internals. +React was originally created for the DOM but it was later adapted to also support native platforms with [React Native](https://reactnative.dev/). This introduced the concept of "renderers" to React internals. **Renderers manage how a React tree turns into the underlying platform calls.** -Renderers are also located in [`packages/`](https://github.com/facebook/react/tree/master/packages/): +Renderers are also located in [`packages/`](https://github.com/facebook/react/tree/main/packages/): -* [React DOM Renderer](https://github.com/facebook/react/tree/master/packages/react-dom) renders React components to the DOM. It implements [top-level `ReactDOM` APIs](/docs/react-dom.html) and is available as [`react-dom`](https://www.npmjs.com/package/react-dom) npm package. It can also be used as standalone browser bundle called `react-dom.js` that exports a `ReactDOM` global. -* [React Native Renderer](https://github.com/facebook/react/tree/master/packages/react-native-renderer) renders React components to native views. It is used internally by React Native. -* [React Test Renderer](https://github.com/facebook/react/tree/master/packages/react-test-renderer) renders React components to JSON trees. It is used by the [Snapshot Testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) feature of [Jest](https://facebook.github.io/jest) and is available as [react-test-renderer](https://www.npmjs.com/package/react-test-renderer) npm package. +* [React DOM Renderer](https://github.com/facebook/react/tree/main/packages/react-dom) renders React components to the DOM. It implements [top-level `ReactDOM` APIs](/docs/react-dom.html) and is available as [`react-dom`](https://www.npmjs.com/package/react-dom) npm package. It can also be used as standalone browser bundle called `react-dom.js` that exports a `ReactDOM` global. +* [React Native Renderer](https://github.com/facebook/react/tree/main/packages/react-native-renderer) renders React components to native views. It is used internally by React Native. +* [React Test Renderer](https://github.com/facebook/react/tree/main/packages/react-test-renderer) renders React components to JSON trees. It is used by the [Snapshot Testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) feature of [Jest](https://facebook.github.io/jest) and is available as [react-test-renderer](https://www.npmjs.com/package/react-test-renderer) npm package. -The only other officially supported renderer is [`react-art`](https://github.com/facebook/react/tree/master/packages/react-art). It used to be in a separate [GitHub repository](https://github.com/reactjs/react-art) but we moved it into the main source tree for now. +The only other officially supported renderer is [`react-art`](https://github.com/facebook/react/tree/main/packages/react-art). It used to be in a separate [GitHub repository](https://github.com/reactjs/react-art) but we moved it into the main source tree for now. >**Note:** > ->Technically the [`react-native-renderer`](https://github.com/facebook/react/tree/master/packages/react-native-renderer) is a very thin layer that teaches React to interact with React Native implementation. The real platform-specific code managing the native views lives in the [React Native repository](https://github.com/facebook/react-native) together with its components. +>Technically the [`react-native-renderer`](https://github.com/facebook/react/tree/main/packages/react-native-renderer) is a very thin layer that teaches React to interact with React Native implementation. The real platform-specific code managing the native views lives in the [React Native repository](https://github.com/facebook/react-native) together with its components. ### Reconcilers {#reconcilers} @@ -213,13 +152,11 @@ Its main goals are: You can read more about React Fiber Architecture [here](https://github.com/acdlite/react-fiber-architecture) and [here](https://blog.ag-grid.com/inside-fiber-an-in-depth-overview-of-the-new-reconciliation-algorithm-in-react). While it has shipped with React 16, the async features are not enabled by default yet. -Its source code is located in [`packages/react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler). +Its source code is located in [`packages/react-reconciler`](https://github.com/facebook/react/tree/main/packages/react-reconciler). ### Event System {#event-system} -React implements a synthetic event system which is agnostic of the renderers and works both with React DOM and React Native. Its source code is located in [`packages/legacy-events`](https://github.com/facebook/react/tree/master/packages/legacy-events). - -There is a [video with a deep code dive into it](https://www.youtube.com/watch?v=dRo_egw7tBc) (66 mins). +React implements a layer over native events to smooth out cross-browser differences. Its source code is located in [`packages/react-dom/src/events`](https://github.com/facebook/react/tree/main/packages/react-dom/src/events). ### What Next? {#what-next} diff --git a/content/docs/components-and-props.md b/content/docs/components-and-props.md index 245b57f5b..54f87d8ca 100644 --- a/content/docs/components-and-props.md +++ b/content/docs/components-and-props.md @@ -16,7 +16,20 @@ prev: rendering-elements.html next: state-and-lifecycle.html --- +<<<<<<< HEAD Τα components σας επιτρέπουν να χωρίσετε το UI σε ανεξάρτητα, επαναχρησιμοποιούμενα κομμάτια και να σκεφτείτε κάθε κομμάτι μεμονωμένα. Αυτή η σελίδα παρέχει μια εισαγωγή στην ιδέα των components. Μπορείτε να βρείτε [εδώ μια αναλυτική αναφορά του component API](/docs/react-component.html). +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Your First Component](https://beta.reactjs.org/learn/your-first-component) +> - [Passing Props to a Component](https://beta.reactjs.org/learn/passing-props-to-a-component) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. This page provides an introduction to the idea of components. You can find a [detailed component API reference here](/docs/react-component.html). +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Εννοιολογικά, τα components είναι σαν συναρτήσεις JavaScript. Δέχονται αυθαίρετες εισόδους (που ονομάζονται "props") και επιστρέφουν React elements που περιγράφουν τι πρέπει να εμφανίζεται στην οθόνη. @@ -64,26 +77,35 @@ const element = <Welcome name="Sara" />; Για παράδειγμα, αυτός ο κώδικας κάνει render "Hello, Sara" στη σελίδα: -```js{1,5} +```js{1,6} function Welcome(props) { return <h1>Hello, {props.name}</h1>; } +const root = ReactDOM.createRoot(document.getElementById('root')); const element = <Welcome name="Sara" />; -ReactDOM.render( - element, - document.getElementById('root') -); +root.render(element); ``` +<<<<<<< HEAD **[Δοκιμάστε το στο CodePen](codepen://components-and-props/rendering-a-component)** +======= +**[Try it on CodePen](https://codepen.io/gaearon/pen/YGYmEG?editors=1010)** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Ας ανακεφαλαιώσουμε τι συμβαίνει σε αυτό το παράδειγμα: +<<<<<<< HEAD 1. Θα καλέσουμε το `ReactDOM.render()` με το `<Welcome name="Sara" />` element. 2. Το React καλεί το `Welcome` component με το `{name: 'Sara'}` ως τα props. 3. Το `Welcome` component μας επειστρέφει ως αποτέλεσμα ένα `<h1>Hello, Sara</h1>` element. 4. Το React DOM ενημερώνει αποδοτικά το DOM για να ταιριάζει το `<h1>Hello, Sara</h1>`. +======= +1. We call `root.render()` with the `<Welcome name="Sara" />` element. +2. React calls the `Welcome` component with `{name: 'Sara'}` as the props. +3. Our `Welcome` component returns a `<h1>Hello, Sara</h1>` element as the result. +4. React DOM efficiently updates the DOM to match `<h1>Hello, Sara</h1>`. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a >**Σημείωση:** Πάντα να ξεκινάτε τα ονόματα των components με κεφαλαίο γράμμα. > @@ -111,14 +133,13 @@ function App() { </div> ); } - -ReactDOM.render( - <App />, - document.getElementById('root') -); ``` +<<<<<<< HEAD **[Δοκιμάστε το στο CodePen](codepen://components-and-props/composing-components)** +======= +**[Try it on CodePen](https://codepen.io/gaearon/pen/KgQKPr?editors=1010)** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Συνήθως, οι νέες React εφαρμογές έχουν ένα μόνο `App` component στην κορυφή. Ωστόσο, εαν ενσωματώσετε το React σε μια υπάρχουσα εφαρμογή, μπορεί να ξεκινήσετε από τη βάση προς τα πάνω με ένα μικρό component όπως το `Button` και να φτάσετε σταδιακά στην κορυφή της view ιεραρχίας. @@ -152,7 +173,11 @@ function Comment(props) { } ``` +<<<<<<< HEAD [Δοκιμάστε το στο CodePen](codepen://components-and-props/extracting-components) +======= +**[Try it on CodePen](https://codepen.io/gaearon/pen/VKQwEo?editors=1010)** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Αποδέχεται το `author` (ένα object), το `text` (ένα string), και το `date` (ένα date) ως props, και περιγράφει ένα σχόλιο σε έναν ιστότοπο social media. @@ -231,9 +256,15 @@ function Comment(props) { } ``` +<<<<<<< HEAD [Δοκιμάστε το στο CodePen](codepen://components-and-props/extracting-components-continued) Η εξαγωγή των components ίσως μοιάζει αρχικά με εργασία, αλλά μια παλέτα επαναχρησιμοποιούμενων components αποδίδει σε μεγαλύτερες εφαρμογές. Ένας καλός κανόνας είναι ότι εαν ένα μέρος του UI σας χρησιμοποιείται αρκετές φορές (`Button`, `Panel`, `Avatar`), ή είναι αρκετά σύνθετο από μόνο του (`App`, `FeedStory`, `Comment`), είναι ένας καλός υποψήφιος να είναι ένα επαναχρησιμοποιήσιμο component. +======= +**[Try it on CodePen](https://codepen.io/gaearon/pen/rrJNJY?editors=1010)** + +Extracting components might seem like grunt work at first, but having a palette of reusable components pays off in larger apps. A good rule of thumb is that if a part of your UI is used several times (`Button`, `Panel`, `Avatar`), or is complex enough on its own (`App`, `FeedStory`, `Comment`), it is a good candidate to be extracted to a separate component. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ## Τα Props είναι Read-Only {#props-are-read-only} diff --git a/content/docs/composition-vs-inheritance.md b/content/docs/composition-vs-inheritance.md index aef6e4200..9c69883e7 100644 --- a/content/docs/composition-vs-inheritance.md +++ b/content/docs/composition-vs-inheritance.md @@ -170,4 +170,8 @@ class SignUpDialog extends React.Component { Τα props και η σύνθεση σας δίνουν όλη την ευελιξία που χρειάζεστε για να προσαρμόσετε την εμφάνιση και τη συμπεριφορά ενός component με έναν σαφή και ασφαλή τρόπο. Θυμηθείτε ότι τα components ενδέχεται να δέχονται αυθαίρετα props, συμπεριλαμβανομένων των primitive values, React elements ή συναρτήσεις. +<<<<<<< HEAD Εάν θέλετε να επαναχρησιμοποιήσετε κάποια λειτουργία εκτός του UI μεταξύ των components, σας συνιστούμε να την εξαγάγετε σε ένα ξεχωριστό JavaScript module. Τα components μπορούν να το κάνουν import και να χρησιμοποιούν αυτή τη συνάρτηση, object ή κλάση, χωρίς να το επεκτείνουν. +======= +If you want to reuse non-UI functionality between components, we suggest extracting it into a separate JavaScript module. The components may import it and use that function, object, or class, without extending it. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a diff --git a/content/docs/concurrent-mode-adoption.md b/content/docs/concurrent-mode-adoption.md deleted file mode 100644 index 347a7a110..000000000 --- a/content/docs/concurrent-mode-adoption.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -id: concurrent-mode-adoption -title: Adopting Concurrent Mode (Experimental) -permalink: docs/concurrent-mode-adoption.html -prev: concurrent-mode-patterns.html -next: concurrent-mode-reference.html ---- - -<style> -.scary > blockquote { - background-color: rgba(237, 51, 21, 0.2); - border-left-color: #ed3315; -} -</style> - -<div class="scary"> - ->Caution: -> ->This page describes **experimental features that are not yet available in a stable release**. Don't rely on experimental builds of React in production apps. These features may change significantly and without a warning before they become a part of React. -> ->This documentation is aimed at early adopters and people who are curious. **If you're new to React, don't worry about these features** -- you don't need to learn them right now. - -</div> - -- [Installation](#installation) - - [Who Is This Experimental Release For?](#who-is-this-experimental-release-for) - - [Enabling Concurrent Mode](#enabling-concurrent-mode) -- [What to Expect](#what-to-expect) - - [Migration Step: Blocking Mode](#migration-step-blocking-mode) - - [Why So Many Modes?](#why-so-many-modes) - - [Feature Comparison](#feature-comparison) - -## Installation {#installation} - -Concurrent Mode is only available in the [experimental builds](/blog/2019/10/22/react-release-channels.html#experimental-channel) of React. To install them, run: - -``` -npm install react@experimental react-dom@experimental -``` - -**There are no semantic versioning guarantees for the experimental builds.** -APIs may be added, changed, or removed with any `@experimental` release. - -**Experimental releases will have frequent breaking changes.** - -You can try these builds on personal projects or in a branch, but we don't recommend running them in production. At Facebook, we *do* run them in production, but that's because we're also there to fix bugs when something breaks. You've been warned! - -### Who Is This Experimental Release For? {#who-is-this-experimental-release-for} - -This release is primarily aimed at early adopters, library authors, and curious people. - -We're using this code in production (and it works for us) but there are still some bugs, missing features, and gaps in the documentation. We'd like to hear more about what breaks in Concurrent Mode so we can better prepare it for an official stable release in the future. - -### Enabling Concurrent Mode {#enabling-concurrent-mode} - -Normally, when we add features to React, you can start using them immediately. Fragments, Context, and even Hooks are examples of such features. You can use them in new code without making any changes to the existing code. - -Concurrent Mode is different. It introduces semantic changes to how React works. Otherwise, the [new features](/docs/concurrent-mode-patterns.html) enabled by it *wouldn't be possible*. This is why they're grouped into a new "mode" rather than released one by one in isolation. - -You can't opt into Concurrent Mode on a per-subtree basis. Instead, to opt in, you have to do it in the place where today you call `ReactDOM.render()`. - -**This will enable Concurrent Mode for the whole `<App />` tree:** - -```js -import ReactDOM from 'react-dom'; - -// If you previously had: -// -// ReactDOM.render(<App />, document.getElementById('root')); -// -// You can opt into Concurrent Mode by writing: - -ReactDOM.createRoot( - document.getElementById('root') -).render(<App />); -``` - ->Note: -> ->Concurrent Mode APIs such as `createRoot` only exist in the experimental builds of React. - -In Concurrent Mode, the lifecycle methods [previously marked](/blog/2018/03/27/update-on-async-rendering.html) as "unsafe" actually *are* unsafe, and lead to bugs even more than in today's React. We don't recommend trying Concurrent Mode until your app is [Strict Mode](/docs/strict-mode.html)-compatible. - -## What to Expect {#what-to-expect} - -If you have a large existing app, or if your app depends on a lot of third-party packages, please don't expect that you can use the Concurrent Mode immediately. **For example, at Facebook we are using Concurrent Mode for the new website, but we're not planning to enable it on the old website.** This is because our old website still uses unsafe lifecycle methods in the product code, incompatible third-party libraries, and patterns that don't work well with the Concurrent Mode. - -In our experience, code that uses idiomatic React patterns and doesn't rely on external state management solutions is the easiest to get running in the Concurrent Mode. We will describe common problems we've seen and the solutions to them separately in the coming weeks. - -### Migration Step: Blocking Mode {#migration-step-blocking-mode} - -For older codebases, Concurrent Mode might be a step too far. This is why we also provide a new "Blocking Mode" in the experimental React builds. You can try it by substituting `createRoot` with `createBlockingRoot`. It only offers a *small subset* of the Concurrent Mode features, but it is closer to how React works today and can serve as a migration step. - -To recap: - -* **Legacy Mode:** `ReactDOM.render(<App />, rootNode)`. This is what React apps use today. There are no plans to remove the legacy mode in the observable future — but it won't be able to support these new features. -* **Blocking Mode:** `ReactDOM.createBlockingRoot(rootNode).render(<App />)`. It is currently experimental. It is intended as a first migration step for apps that want to get a subset of Concurrent Mode features. -* **Concurrent Mode:** `ReactDOM.createRoot(rootNode).render(<App />)`. It is currently experimental. In the future, after it stabilizes, we intend to make it the default React mode. This mode enables *all* the new features. - -### Why So Many Modes? {#why-so-many-modes} - -We think it is better to offer a [gradual migration strategy](/docs/faq-versioning.html#commitment-to-stability) than to make huge breaking changes — or to let React stagnate into irrelevance. - -In practice, we expect that most apps using Legacy Mode today should be able to migrate at least to the Blocking Mode (if not Concurrent Mode). This fragmentation can be annoying for libraries that aim to support all Modes in the short term. However, gradually moving the ecosystem away from the Legacy Mode will also *solve* problems that affect major libraries in the React ecosystem, such as [confusing Suspense behavior when reading layout](https://github.com/facebook/react/issues/14536) and [lack of consistent batching guarantees](https://github.com/facebook/react/issues/15080). There's a number of bugs that can't be fixed in Legacy Mode without changing semantics, but don't exist in Blocking and Concurrent Modes. - -You can think of the Blocking Mode as a "gracefully degraded" version of the Concurrent Mode. **As a result, in longer term we should be able to converge and stop thinking about different Modes altogether.** But for now, Modes are an important migration strategy. They let everyone decide when a migration is worth it, and upgrade at their own pace. - -### Feature Comparison {#feature-comparison} - -<style> - #feature-table table { border-collapse: collapse; } - #feature-table th { padding-right: 30px; } - #feature-table tr { border-bottom: 1px solid #eee; } -</style> - -<div id="feature-table"> - -| |Legacy Mode |Blocking Mode |Concurrent Mode | -|--- |--- |--- |--- | -|[String Refs](/docs/refs-and-the-dom.html#legacy-api-string-refs) |✅ |🚫** |🚫** | -|[Legacy Context](/docs/legacy-context.html) |✅ |🚫** |🚫** | -|[findDOMNode](/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage) |✅ |🚫** |🚫** | -|[Suspense](/docs/concurrent-mode-suspense.html#what-is-suspense-exactly) |✅ |✅ |✅ | -|[SuspenseList](/docs/concurrent-mode-patterns.html#suspenselist) |🚫 |✅ |✅ | -|Suspense SSR + Hydration |🚫 |✅ |✅ | -|Progressive Hydration |🚫 |✅ |✅ | -|Selective Hydration |🚫 |🚫 |✅ | -|Cooperative Multitasking |🚫 |🚫 |✅ | -|Automatic batching of multiple setStates |🚫* |✅ |✅ | -|[Priority-based Rendering](/docs/concurrent-mode-patterns.html#splitting-high-and-low-priority-state) |🚫 |🚫 |✅ | -|[Interruptible Prerendering](/docs/concurrent-mode-intro.html#interruptible-rendering) |🚫 |🚫 |✅ | -|[useTransition](/docs/concurrent-mode-patterns.html#transitions) |🚫 |🚫 |✅ | -|[useDeferredValue](/docs/concurrent-mode-patterns.html#deferring-a-value) |🚫 |🚫 |✅ | -|[Suspense Reveal "Train"](/docs/concurrent-mode-patterns.html#suspense-reveal-train) |🚫 |🚫 |✅ | - -</div> - -\*: Legacy mode has automatic batching in React-managed events but it's limited to one browser task. Non-React events must opt-in using `unstable_batchedUpdates`. In Blocking Mode and Concurrent Mode, all `setState`s are batched by default. - -\*\*: Warns in development. diff --git a/content/docs/concurrent-mode-intro.md b/content/docs/concurrent-mode-intro.md deleted file mode 100644 index b2980ce3d..000000000 --- a/content/docs/concurrent-mode-intro.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -id: concurrent-mode-intro -title: Introducing Concurrent Mode (Experimental) -permalink: docs/concurrent-mode-intro.html -next: concurrent-mode-suspense.html ---- - -<style> -.scary > blockquote { - background-color: rgba(237, 51, 21, 0.2); - border-left-color: #ed3315; -} -</style> - -<div class="scary"> - ->Caution: -> ->This page describes **experimental features that are [not yet available](/docs/concurrent-mode-adoption.html) in a stable release**. Don't rely on experimental builds of React in production apps. These features may change significantly and without a warning before they become a part of React. -> ->This documentation is aimed at early adopters and people who are curious. **If you're new to React, don't worry about these features** -- you don't need to learn them right now. - -</div> - -This page provides a theoretical overview of Concurrent Mode. **For a more practical introduction, you might want to check out the next sections:** - -* [Suspense for Data Fetching](/docs/concurrent-mode-suspense.html) describes a new mechanism for fetching data in React components. -* [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html) shows some UI patterns made possible by Concurrent Mode and Suspense. -* [Adopting Concurrent Mode](/docs/concurrent-mode-adoption.html) explains how you can try Concurrent Mode in your project. -* [Concurrent Mode API Reference](/docs/concurrent-mode-reference.html) documents the new APIs available in experimental builds. - -## What Is Concurrent Mode? {#what-is-concurrent-mode} - -Concurrent Mode is a set of new features that help React apps stay responsive and gracefully adjust to the user's device capabilities and network speed. - -These features are still experimental and are subject to change. They are not yet a part of a stable React release, but you can try them in an experimental build. - -## Blocking vs Interruptible Rendering {#blocking-vs-interruptible-rendering} - -**To explain Concurrent Mode, we'll use version control as a metaphor.** If you work on a team, you probably use a version control system like Git and work on branches. When a branch is ready, you can merge your work into master so that other people can pull it. - -Before version control existed, the development workflow was very different. There was no concept of branches. If you wanted to edit some files, you had to tell everyone not to touch those files until you've finished your work. You couldn't even start working on them concurrently with that person — you were literally *blocked* by them. - -This illustrates how UI libraries, including React, typically work today. Once they start rendering an update, including creating new DOM nodes and running the code inside components, they can't interrupt this work. We'll call this approach "blocking rendering". - -In Concurrent Mode, rendering is not blocking. It is interruptible. This improves the user experience. It also unlocks new features that weren't possible before. Before we look at concrete examples in the [next](/docs/concurrent-mode-suspense.html) [chapters](/docs/concurrent-mode-patterns.html), we'll do a high-level overview of new features. - -### Interruptible Rendering {#interruptible-rendering} - -Consider a filterable product list. Have you ever typed into a list filter and felt that it stutters on every key press? Some of the work to update the product list might be unavoidable, such as creating new DOM nodes or the browser performing layout. However, *when* and *how* we perform that work plays a big role. - -A common way to work around the stutter is to "debounce" the input. When debouncing, we only update the list *after* the user stops typing. However, it can be frustrating that the UI doesn't update while we're typing. As an alternative, we could "throttle" the input, and update the list with a certain maximum frequency. But then on lower-powered devices we'd still end up with stutter. Both debouncing and throttling create a suboptimal user experience. - -The reason for the stutter is simple: once rendering begins, it can't be interrupted. So the browser can't update the text input right after the key press. No matter how good a UI library (such as React) might look on a benchmark, if it uses blocking rendering, a certain amount of work in your components will always cause stutter. And, often, there is no easy fix. - -**Concurrent Mode fixes this fundamental limitation by making rendering interruptible.** This means when the user presses another key, React doesn't need to block the browser from updating the text input. Instead, it can let the browser paint an update to the input, and then continue rendering the updated list *in memory*. When the rendering is finished, React updates the DOM, and changes are reflected on the screen. - -Conceptually, you can think of this as React preparing every update "on a branch". Just like you can abandon work in branches or switch between them, React in Concurrent Mode can interrupt an ongoing update to do something more important, and then come back to what it was doing earlier. This technique might also remind you of [double buffering](https://wiki.osdev.org/Double_Buffering) in video games. - -Concurrent Mode techniques reduce the need for debouncing and throttling in UI. Because rendering is interruptible, React doesn't need to artificially *delay* work to avoid stutter. It can start rendering right away, but interrupt this work when needed to keep the app responsive. - -### Intentional Loading Sequences {#intentional-loading-sequences} - -We've said before that Concurrent Mode is like React working "on a branch". Branches are useful not only for short-term fixes, but also for long-running features. Sometimes you might work on a feature, but it could take weeks before it's in a "good enough state" to merge into master. This side of our version control metaphor applies to rendering too. - -Imagine we're navigating between two screens in an app. Sometimes, we might not have enough code and data loaded to show a "good enough" loading state to the user on the new screen. Transitioning to an empty screen or a large spinner can be a jarring experience. However, it's also common that the necessary code and data doesn't take too long to fetch. **Wouldn't it be nicer if React could stay on the old screen for a little longer, and "skip" the "bad loading state" before showing the new screen?** - -While this is possible today, it can be difficult to orchestrate. In Concurrent Mode, this feature is built-in. React starts preparing the new screen in memory first — or, as our metaphor goes, "on a different branch". So React can wait before updating the DOM so that more content can load. In Concurrent Mode, we can tell React to keep showing the old screen, fully interactive, with an inline loading indicator. And when the new screen is ready, React can take us to it. - -### Concurrency {#concurrency} - -Let's recap the two examples above and see how Concurrent Mode unifies them. **In Concurrent Mode, React can work on several state updates *concurrently*** — just like branches let different team members work independently: - -* For CPU-bound updates (such as creating DOM nodes and running component code), concurrency means that a more urgent update can "interrupt" rendering that has already started. -* For IO-bound updates (such as fetching code or data from the network), concurrency means that React can start rendering in memory even before all the data arrives, and skip showing jarring empty loading states. - -Importantly, the way you *use* React is the same. Concepts like components, props, and state fundamentally work the same way. When you want to update the screen, you set the state. - -React uses a heuristic to decide how "urgent" an update is, and lets you adjust it with a few lines of code so that you can achieve the desired user experience for every interaction. - -## Putting Research into Production {#putting-research-into-production} - -There is a common theme around Concurrent Mode features. **Its mission is to help integrate the findings from the Human-Computer Interaction research into real UIs.** - -For example, research shows that displaying too many intermediate loading states when transitioning between screens makes a transition feel *slower*. This is why Concurrent Mode shows new loading states on a fixed "schedule" to avoid jarring and too frequent updates. - -Similarly, we know from research that interactions like hover and text input need to be handled within a very short period of time, while clicks and page transitions can wait a little longer without feeling laggy. The different "priorities" that Concurrent Mode uses internally roughly correspond to the interaction categories in the human perception research. - -Teams with a strong focus on user experience sometimes solve similar problems with one-off solutions. However, those solutions rarely survive for a long time, as they're hard to maintain. With Concurrent Mode, our goal is to bake the UI research findings into the abstraction itself, and provide idiomatic ways to use them. As a UI library, React is well-positioned to do that. - -## Next Steps {#next-steps} - -Now you know what Concurrent Mode is all about! - -On the next pages, you'll learn more details about specific topics: - -* [Suspense for Data Fetching](/docs/concurrent-mode-suspense.html) describes a new mechanism for fetching data in React components. -* [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html) shows some UI patterns made possible by Concurrent Mode and Suspense. -* [Adopting Concurrent Mode](/docs/concurrent-mode-adoption.html) explains how you can try Concurrent Mode in your project. -* [Concurrent Mode API Reference](/docs/concurrent-mode-reference.html) documents the new APIs available in experimental builds. diff --git a/content/docs/concurrent-mode-patterns.md b/content/docs/concurrent-mode-patterns.md deleted file mode 100644 index 3f863e497..000000000 --- a/content/docs/concurrent-mode-patterns.md +++ /dev/null @@ -1,937 +0,0 @@ ---- -id: concurrent-mode-patterns -title: Concurrent UI Patterns (Experimental) -permalink: docs/concurrent-mode-patterns.html -prev: concurrent-mode-suspense.html -next: concurrent-mode-adoption.html ---- - -<style> -.scary > blockquote { - background-color: rgba(237, 51, 21, 0.2); - border-left-color: #ed3315; -} -</style> - -<div class="scary"> - ->Caution: -> ->This page describes **experimental features that are [not yet available](/docs/concurrent-mode-adoption.html) in a stable release**. Don't rely on experimental builds of React in production apps. These features may change significantly and without a warning before they become a part of React. -> ->This documentation is aimed at early adopters and people who are curious. **If you're new to React, don't worry about these features** -- you don't need to learn them right now. For example, if you're looking for a data fetching tutorial that works today, read [this article](https://www.robinwieruch.de/react-hooks-fetch-data/) instead. - -</div> - -Usually, when we update the state, we expect to see changes on the screen immediately. This makes sense because we want to keep our app responsive to user input. However, there are cases where we might prefer to **defer an update from appearing on the screen**. - -For example, if we switch from one page to another, and none of the code or data for the next screen has loaded yet, it might be frustrating to immediately see a blank page with a loading indicator. We might prefer to stay longer on the previous screen. Implementing this pattern has historically been difficult in React. Concurrent Mode offers a new set of tools to do that. - -- [Transitions](#transitions) - - [Wrapping setState in a Transition](#wrapping-setstate-in-a-transition) - - [Adding a Pending Indicator](#adding-a-pending-indicator) - - [Reviewing the Changes](#reviewing-the-changes) - - [Where Does the Update Happen?](#where-does-the-update-happen) - - [Transitions Are Everywhere](#transitions-are-everywhere) - - [Baking Transitions Into the Design System](#baking-transitions-into-the-design-system) -- [The Three Steps](#the-three-steps) - - [Default: Receded → Skeleton → Complete](#default-receded-skeleton-complete) - - [Preferred: Pending → Skeleton → Complete](#preferred-pending-skeleton-complete) - - [Wrap Lazy Features in `<Suspense>`](#wrap-lazy-features-in-suspense) - - [Suspense Reveal “Train”](#suspense-reveal-train) - - [Delaying a Pending Indicator](#delaying-a-pending-indicator) - - [Recap](#recap) -- [Other Patterns](#other-patterns) - - [Splitting High and Low Priority State](#splitting-high-and-low-priority-state) - - [Deferring a Value](#deferring-a-value) - - [SuspenseList](#suspenselist) -- [Next Steps](#next-steps) - -## Transitions {#transitions} - -Let's revisit [this demo](https://codesandbox.io/s/infallible-feather-xjtbu) from the previous page about [Suspense for Data Fetching](/docs/concurrent-mode-suspense.html). - -When we click the "Next" button to switch the active profile, the existing page data immediately disappears, and we see the loading indicator for the whole page again. We can call this an "undesirable" loading state. **It would be nice if we could "skip" it and wait for some content to load before transitioning to the new screen.** - -React offers a new built-in `useTransition()` Hook to help with this. - -We can use it in three steps. - -First, we'll make sure that we're actually using Concurrent Mode. We'll talk more about [adopting Concurrent Mode](/docs/concurrent-mode-adoption.html) later, but for now it's sufficient to know that we need to use `ReactDOM.createRoot()` rather than `ReactDOM.render()` for this feature to work: - -```js -const rootElement = document.getElementById("root"); -// Opt into Concurrent Mode -ReactDOM.createRoot(rootElement).render(<App />); -``` - -Next, we'll add an import for the `useTransition` Hook from React: - -```js -import React, { useState, useTransition, Suspense } from "react"; -``` - -Finally, we'll use it inside the `App` component: - -```js{3-5} -function App() { - const [resource, setResource] = useState(initialResource); - const [startTransition, isPending] = useTransition({ - timeoutMs: 3000 - }); - // ... -``` - -**By itself, this code doesn't do anything yet.** We will need to use this Hook's return values to set up our state transition. There are two values returned from `useTransition`: - -* `startTransition` is a function. We'll use it to tell React *which* state update we want to defer. -* `isPending` is a boolean. It's React telling us whether that transition is ongoing at the moment. - -We will use them right below. - -Note we passed a configuration object to `useTransition`. Its `timeoutMs` property specifies **how long we're willing to wait for the transition to finish**. By passing `{timeoutMs: 3000}`, we say "If the next profile takes more than 3 seconds to load, show the big spinner -- but before that timeout it's okay to keep showing the previous screen". - -### Wrapping setState in a Transition {#wrapping-setstate-in-a-transition} - -Our "Next" button click handler sets the state that switches the current profile in the state: - -```js{4} -<button - onClick={() => { - const nextUserId = getNextId(resource.userId); - setResource(fetchProfileData(nextUserId)); - }} -> -``` - - We'll wrap that state update into `startTransition`. That's how we tell React **we don't mind React delaying that state update** if it leads to an undesirable loading state: - -```js{3,6} -<button - onClick={() => { - startTransition(() => { - const nextUserId = getNextId(resource.userId); - setResource(fetchProfileData(nextUserId)); - }); - }} -> -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/musing-driscoll-6nkie)** - -Press "Next" a few times. Notice it already feels very different. **Instead of immediately seeing an empty screen on click, we now keep seeing the previous page for a while.** When the data has loaded, React transitions us to the new screen. - -If we make our API responses take 5 seconds, [we can confirm](https://codesandbox.io/s/relaxed-greider-suewh) that now React "gives up" and transitions anyway to the next screen after 3 seconds. This is because we passed `{timeoutMs: 3000}` to `useTransition()`. For example, if we passed `{timeoutMs: 60000}` instead, it would wait a whole minute. - -### Adding a Pending Indicator {#adding-a-pending-indicator} - -There's still something that feels broken about [our last example](https://codesandbox.io/s/musing-driscoll-6nkie). Sure, it's nice not to see a "bad" loading state. **But having no indication of progress at all feels even worse!** When we click "Next", nothing happens and it feels like the app is broken. - -Our `useTransition()` call returns two values: `startTransition` and `isPending`. - -```js - const [startTransition, isPending] = useTransition({ timeoutMs: 3000 }); -``` - -We've already used `startTransition` to wrap the state update. Now we're going to use `isPending` too. React gives this boolean to us so we can tell whether **we're currently waiting for this transition to finish**. We'll use it to indicate that something is happening: - -```js{4,14} -return ( - <> - <button - disabled={isPending} - onClick={() => { - startTransition(() => { - const nextUserId = getNextId(resource.userId); - setResource(fetchProfileData(nextUserId)); - }); - }} - > - Next - </button> - {isPending ? " Loading..." : null} - <ProfilePage resource={resource} /> - </> -); -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/jovial-lalande-26yep)** - -Now, this feels a lot better! When we click Next, it gets disabled because clicking it multiple times doesn't make sense. And the new "Loading..." tells the user that the app didn't freeze. - -### Reviewing the Changes {#reviewing-the-changes} - -Let's take another look at all the changes we've made since the [original example](https://codesandbox.io/s/infallible-feather-xjtbu): - -```js{3-5,9,11,14,19} -function App() { - const [resource, setResource] = useState(initialResource); - const [startTransition, isPending] = useTransition({ - timeoutMs: 3000 - }); - return ( - <> - <button - disabled={isPending} - onClick={() => { - startTransition(() => { - const nextUserId = getNextId(resource.userId); - setResource(fetchProfileData(nextUserId)); - }); - }} - > - Next - </button> - {isPending ? " Loading..." : null} - <ProfilePage resource={resource} /> - </> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/jovial-lalande-26yep)** - -It took us only seven lines of code to add this transition: - -* We've imported the `useTransition` Hook and used it the component that updates the state. -* We've passed `{timeoutMs: 3000}` to stay on the previous screen for at most 3 seconds. -* We've wrapped our state update into `startTransition` to tell React it's okay to delay it. -* We're using `isPending` to communicate the state transition progress to the user and to disable the button. - -As a result, clicking "Next" doesn't perform an immediate state transition to an "undesirable" loading state, but instead stays on the previous screen and communicates progress there. - -### Where Does the Update Happen? {#where-does-the-update-happen} - -This wasn't very difficult to implement. However, if you start thinking about how this could possibly work, it might become a little mindbending. If we set the state, how come we don't see the result right away? *Where* is the next `<ProfilePage>` rendering? - -Clearly, both "versions" of `<ProfilePage>` exist at the same time. We know the old one exists because we see it on the screen and even display a progress indicator on it. And we know the new version also exists *somewhere*, because it's the one that we're waiting for! - -**But how can two versions of the same component exist at the same time?** - -This gets at the root of what Concurrent Mode is. We've [previously said](/docs/concurrent-mode-intro.html#intentional-loading-sequences) it's a bit like React working on state update on a "branch". Another way we can conceptualize is that wrapping a state update in `startTransition` begins rendering it *"in a different universe"*, much like in science fiction movies. We don't "see" that universe directly -- but we can get a signal from it that tells us something is happening (`isPending`). When the update is ready, our "universes" merge back together, and we see the result on the screen! - -Play a bit more with the [demo](https://codesandbox.io/s/jovial-lalande-26yep), and try to imagine it happening. - -Of course, two versions of the tree rendering *at the same time* is an illusion, just like the idea that all programs run on your computer at the same time is an illusion. An operating system switches between different applications very fast. Similarly, React can switch between the version of the tree you see on the screen and the version that it's "preparing" to show next. - -An API like `useTransition` lets you focus on the desired user experience, and not think about the mechanics of how it's implemented. Still, it can be a helpful metaphor to imagine that updates wrapped in `startTransition` happen "on a branch" or "in a different world". - -### Transitions Are Everywhere {#transitions-are-everywhere} - -As we learned from the [Suspense walkthrough](/docs/concurrent-mode-suspense.html), any component can "suspend" any time if some data it needs is not ready yet. We can strategically place `<Suspense>` boundaries in different parts of the tree to handle this, but it won't always be enough. - -Let's get back to our [first Suspense demo](https://codesandbox.io/s/frosty-hermann-bztrp) where there was just one profile. Currently, it fetches the data only once. We'll add a "Refresh" button to check for server updates. - -Our first attempt might look like this: - -```js{6-8,13-15} -const initialResource = fetchUserAndPosts(); - -function ProfilePage() { - const [resource, setResource] = useState(initialResource); - - function handleRefreshClick() { - setResource(fetchUserAndPosts()); - } - - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails resource={resource} /> - <button onClick={handleRefreshClick}> - Refresh - </button> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline resource={resource} /> - </Suspense> - </Suspense> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/boring-shadow-100tf)** - -In this example, we start data fetching at the load *and* every time you press "Refresh". We put the result of calling `fetchUserAndPosts()` into state so that components below can start reading the new data from the request we just kicked off. - -We can see in [this example](https://codesandbox.io/s/boring-shadow-100tf) that pressing "Refresh" works. The `<ProfileDetails>` and `<ProfileTimeline>` components receive a new `resource` prop that represents the fresh data, they "suspend" because we don't have a response yet, and we see the fallbacks. When the response loads, we can see the updated posts (our fake API adds them every 3 seconds). - -However, the experience feels really jarring. We were browsing a page, but it got replaced by a loading state right as we were interacting with it. It's disorienting. **Just like before, to avoid showing an undesirable loading state, we can wrap the state update in a transition:** - -```js{2-5,9-11,21} -function ProfilePage() { - const [startTransition, isPending] = useTransition({ - // Wait 10 seconds before fallback - timeoutMs: 10000 - }); - const [resource, setResource] = useState(initialResource); - - function handleRefreshClick() { - startTransition(() => { - setResource(fetchProfileData()); - }); - } - - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails resource={resource} /> - <button - onClick={handleRefreshClick} - disabled={isPending} - > - {isPending ? "Refreshing..." : "Refresh"} - </button> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline resource={resource} /> - </Suspense> - </Suspense> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/sleepy-field-mohzb)** - -This feels a lot better! Clicking "Refresh" doesn't pull us away from the page we're browsing anymore. We see something is loading "inline", and when the data is ready, it's displayed. - -### Baking Transitions Into the Design System {#baking-transitions-into-the-design-system} - -We can now see that the need for `useTransition` is *very* common. Pretty much any button click or interaction that can lead to a component suspending needs to be wrapped in `useTransition` to avoid accidentally hiding something the user is interacting with. - -This can lead to a lot of repetitive code across components. This is why **we generally recommend to bake `useTransition` into the *design system* components of your app**. For example, we can extract the transition logic into our own `<Button>` component: - -```js{7-9,20,24} -function Button({ children, onClick }) { - const [startTransition, isPending] = useTransition({ - timeoutMs: 10000 - }); - - function handleClick() { - startTransition(() => { - onClick(); - }); - } - - const spinner = ( - // ... - ); - - return ( - <> - <button - onClick={handleClick} - disabled={isPending} - > - {children} - </button> - {isPending ? spinner : null} - </> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/modest-ritchie-iufrh)** - -Note that the button doesn't care *what* state we're updating. It's wrapping *any* state updates that happen during its `onClick` handler into a transition. Now that our `<Button>` takes care of setting up the transition, the `<ProfilePage>` component doesn't need to set up its own: - -```js{4-6,11-13} -function ProfilePage() { - const [resource, setResource] = useState(initialResource); - - function handleRefreshClick() { - setResource(fetchProfileData()); - } - - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails resource={resource} /> - <Button onClick={handleRefreshClick}> - Refresh - </Button> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline resource={resource} /> - </Suspense> - </Suspense> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/modest-ritchie-iufrh)** - -When a button gets clicked, it starts a transition and calls `props.onClick()` inside of it -- which triggers `handleRefreshClick` in the `<ProfilePage>` component. We start fetching the fresh data, but it doesn't trigger a fallback because we're inside a transition, and the 10 second timeout specified in the `useTransition` call hasn't passed yet. While a transition is pending, the button displays an inline loading indicator. - -We can see now how Concurrent Mode helps us achieve a good user experience without sacrificing isolation and modularity of components. React coordinates the transition. - -## The Three Steps {#the-three-steps} - -By now we have discussed all of the different visual states that an update may go through. In this section, we will give them names and talk about the progression between them. - -<br> - -<img src="../images/docs/cm-steps-simple.png" alt="Three steps" /> - -At the very end, we have the **Complete** state. That's where we want to eventually get to. It represents the moment when the next screen is fully rendered and isn't loading more data. - -But before our screen can be Complete, we might need to load some data or code. When we're on the next screen, but some parts of it are still loading, we call that a **Skeleton** state. - -Finally, there are two primary ways that lead us to the Skeleton state. We will illustrate the difference between them with a concrete example. - -### Default: Receded → Skeleton → Complete {#default-receded-skeleton-complete} - -Open [this example](https://codesandbox.io/s/prod-grass-g1lh5) and click "Open Profile". You will see several visual states one by one: - -* **Receded**: For a second, you will see the `<h1>Loading the app...</h1>` fallback. -* **Skeleton:** You will see the `<ProfilePage>` component with `<h2>Loading posts...</h2>` inside. -* **Complete:** You will see the `<ProfilePage>` component with no fallbacks inside. Everything was fetched. - -How do we separate the Receded and the Skeleton states? The difference between them is that the **Receded** state feels like "taking a step back" to the user, while the **Skeleton** state feels like "taking a step forward" in our progress to show more content. - -In this example, we started our journey on the `<HomePage>`: - -```js -<Suspense fallback={...}> - {/* previous screen */} - <HomePage /> -</Suspense> -``` - -After the click, React started rendering the next screen: - -```js -<Suspense fallback={...}> - {/* next screen */} - <ProfilePage> - <ProfileDetails /> - <Suspense fallback={...}> - <ProfileTimeline /> - </Suspense> - </ProfilePage> -</Suspense> -``` - -Both `<ProfileDetails>` and `<ProfileTimeline>` need data to render, so they suspend: - -```js{4,6} -<Suspense fallback={...}> - {/* next screen */} - <ProfilePage> - <ProfileDetails /> {/* suspends! */} - <Suspense fallback={<h2>Loading posts...</h2>}> - <ProfileTimeline /> {/* suspends! */} - </Suspense> - </ProfilePage> -</Suspense> -``` - -When a component suspends, React needs to show the closest fallback. But the closest fallback to `<ProfileDetails>` is at the top level: - -```js{2,3,7} -<Suspense fallback={ - // We see this fallback now because of <ProfileDetails> - <h1>Loading the app...</h1> -}> - {/* next screen */} - <ProfilePage> - <ProfileDetails /> {/* suspends! */} - <Suspense fallback={...}> - <ProfileTimeline /> - </Suspense> - </ProfilePage> -</Suspense> -``` - -This is why when we click the button, it feels like we've "taken a step back". The `<Suspense>` boundary which was previously showing useful content (`<HomePage />`) had to "recede" to showing the fallback (`<h1>Loading the app...</h1>`). We call that a **Receded** state. - -As we load more data, React will retry rendering, and `<ProfileDetails>` can render successfully. Finally, we're in the **Skeleton** state. We see the new page with missing parts: - -```js{6,7,9} -<Suspense fallback={...}> - {/* next screen */} - <ProfilePage> - <ProfileDetails /> - <Suspense fallback={ - // We see this fallback now because of <ProfileTimeline> - <h2>Loading posts...</h2> - }> - <ProfileTimeline /> {/* suspends! */} - </Suspense> - </ProfilePage> -</Suspense> -``` - -Eventually, they load too, and we get to the **Complete** state. - -This scenario (Receded → Skeleton → Complete) is the default one. However, the Receded state is not very pleasant because it "hides" existing information. This is why React lets us opt into a different sequence (**Pending** → Skeleton → Complete) with `useTransition`. - -### Preferred: Pending → Skeleton → Complete {#preferred-pending-skeleton-complete} - -When we `useTransition`, React will let us "stay" on the previous screen -- and show a progress indicator there. We call that a **Pending** state. It feels much better than the Receded state because none of our existing content disappears, and the page stays interactive. - -You can compare these two examples to feel the difference: - -* Default: [Receded → Skeleton → Complete](https://codesandbox.io/s/prod-grass-g1lh5) -* **Preferred: [Pending → Skeleton → Complete](https://codesandbox.io/s/focused-snow-xbkvl)** - -The only difference between these two examples is that the first uses regular `<button>`s, but the second one uses our custom `<Button>` component with `useTransition`. - -### Wrap Lazy Features in `<Suspense>` {#wrap-lazy-features-in-suspense} - -Open [this example](https://codesandbox.io/s/nameless-butterfly-fkw5q). When you press a button, you'll see the Pending state for a second before moving on. This transition feels nice and fluid. - -We will now add a brand new feature to the profile page -- a list of fun facts about a person: - -```js{8,13-25} -function ProfilePage({ resource }) { - return ( - <> - <ProfileDetails resource={resource} /> - <Suspense fallback={<h2>Loading posts...</h2>}> - <ProfileTimeline resource={resource} /> - </Suspense> - <ProfileTrivia resource={resource} /> - </> - ); -} - -function ProfileTrivia({ resource }) { - const trivia = resource.trivia.read(); - return ( - <> - <h2>Fun Facts</h2> - <ul> - {trivia.map(fact => ( - <li key={fact.id}>{fact.text}</li> - ))} - </ul> - </> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/focused-mountain-uhkzg)** - -If you press "Open Profile" now, you can tell something is wrong. It takes a whole seven seconds to make the transition now! This is because our trivia API is too slow. Let's say we can't make the API faster. How can we improve the user experience with this constraint? - -If we don't want to stay in the Pending state for too long, our first instinct might be to set `timeoutMs` in `useTransition` to something smaller, like `3000`. You can try this [here](https://codesandbox.io/s/practical-kowalevski-kpjg4). This lets us escape the prolonged Pending state, but we still don't have anything useful to show! - -There is a simpler way to solve this. **Instead of making the transition shorter, we can "disconnect" the slow component from the transition** by wrapping it into `<Suspense>`: - -```js{8,10} -function ProfilePage({ resource }) { - return ( - <> - <ProfileDetails resource={resource} /> - <Suspense fallback={<h2>Loading posts...</h2>}> - <ProfileTimeline resource={resource} /> - </Suspense> - <Suspense fallback={<h2>Loading fun facts...</h2>}> - <ProfileTrivia resource={resource} /> - </Suspense> - </> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/condescending-shape-s6694)** - -This reveals an important insight. React always prefers to go to the Skeleton state as soon as possible. Even if we use transitions with long timeouts everywhere, React will not stay in the Pending state for longer than necessary to avoid the Receded state. - -**If some feature isn't a vital part of the next screen, wrap it in `<Suspense>` and let it load lazily.** This ensures we can show the rest of the content as soon as possible. Conversely, if a screen is *not worth showing* without some component, such as `<ProfileDetails>` in our example, do *not* wrap it in `<Suspense>`. Then the transitions will "wait" for it to be ready. - -### Suspense Reveal "Train" {#suspense-reveal-train} - -When we're already on the next screen, sometimes the data needed to "unlock" different `<Suspense>` boundaries arrives in quick succession. For example, two different responses might arrive after 1000ms and 1050ms, respectively. If you've already waited for a second, waiting another 50ms is not going to be perceptible. This is why React reveals `<Suspense>` boundaries on a schedule, like a "train" that arrives periodically. This trades a small delay for reducing the layout thrashing and the number of visual changes presented to the user. - -You can see a demo of this [here](https://codesandbox.io/s/admiring-mendeleev-y54mk). The "posts" and "fun facts" responses come within 100ms of each other. But React coalesces them and "reveals" their Suspense boundaries together. - -### Delaying a Pending Indicator {#delaying-a-pending-indicator} - -Our `Button` component will immediately show the Pending state indicator on click: - -```js{2,13} -function Button({ children, onClick }) { - const [startTransition, isPending] = useTransition({ - timeoutMs: 10000 - }); - - // ... - - return ( - <> - <button onClick={handleClick} disabled={isPending}> - {children} - </button> - {isPending ? spinner : null} - </> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/floral-thunder-iy826)** - -This signals to the user that some work is happening. However, if the transition is relatively short (less than 500ms), it might be too distracting and make the transition itself feel *slower*. - -One possible solution to this is to *delay the spinner itself* from displaying: - -```css -.DelayedSpinner { - animation: 0s linear 0.5s forwards makeVisible; - visibility: hidden; -} - -@keyframes makeVisible { - to { - visibility: visible; - } -} -``` - -```js{2-4,10} -const spinner = ( - <span className="DelayedSpinner"> - {/* ... */} - </span> -); - -return ( - <> - <button onClick={handleClick}>{children}</button> - {isPending ? spinner : null} - </> -); -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/gallant-spence-l6wbk)** - -With this change, even though we're in the Pending state, we don't display any indication to the user until 500ms has passed. This may not seem like much of an improvement when the API responses are slow. But compare how it feels [before](https://codesandbox.io/s/thirsty-liskov-1ygph) and [after](https://codesandbox.io/s/hardcore-http-s18xr) when the API call is fast. Even though the rest of the code hasn't changed, suppressing a "too fast" loading state improves the perceived performance by not calling attention to the delay. - -### Recap {#recap} - -The most important things we learned so far are: - -* By default, our loading sequence is Receded → Skeleton → Complete. -* The Receded state doesn't feel very nice because it hides existing content. -* With `useTransition`, we can opt into showing a Pending state first instead. This will keep us on the previous screen while the next screen is being prepared. -* If we don't want some component to delay the transition, we can wrap it in its own `<Suspense>` boundary. -* Instead of doing `useTransition` in every other component, we can build it into our design system. - -## Other Patterns {#other-patterns} - -Transitions are probably the most common Concurrent Mode pattern you'll encounter, but there are a few more patterns you might find useful. - -### Splitting High and Low Priority State {#splitting-high-and-low-priority-state} - -When you design React components, it is usually best to find the "minimal representation" of state. For example, instead of keeping `firstName`, `lastName`, and `fullName` in state, it's usually better keep only `firstName` and `lastName`, and then calculate `fullName` during rendering. This lets us avoid mistakes where we update one state but forget the other state. - -However, in Concurrent Mode there are cases where you might *want* to "duplicate" some data in different state variables. Consider this tiny translation app: - -```js -const initialQuery = "Hello, world"; -const initialResource = fetchTranslation(initialQuery); - -function App() { - const [query, setQuery] = useState(initialQuery); - const [resource, setResource] = useState(initialResource); - - function handleChange(e) { - const value = e.target.value; - setQuery(value); - setResource(fetchTranslation(value)); - } - - return ( - <> - <input - value={query} - onChange={handleChange} - /> - <Suspense fallback={<p>Loading...</p>}> - <Translation resource={resource} /> - </Suspense> - </> - ); -} - -function Translation({ resource }) { - return ( - <p> - <b>{resource.read()}</b> - </p> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/brave-villani-ypxvf)** - -Notice how when you type into the input, the `<Translation>` component suspends, and we see the `<p>Loading...</p>` fallback until we get fresh results. This is not ideal. It would be better if we could see the *previous* translation for a bit while we're fetching the next one. - -In fact, if we open the console, we'll see a warning: - -``` -Warning: App triggered a user-blocking update that suspended. - -The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes. - -Refer to the documentation for useTransition to learn how to implement this pattern. -``` - -As we mentioned earlier, if some state update causes a component to suspend, that state update should be wrapped in a transition. Let's add `useTransition` to our component: - -```js{4-6,10,13} -function App() { - const [query, setQuery] = useState(initialQuery); - const [resource, setResource] = useState(initialResource); - const [startTransition, isPending] = useTransition({ - timeoutMs: 5000 - }); - - function handleChange(e) { - const value = e.target.value; - startTransition(() => { - setQuery(value); - setResource(fetchTranslation(value)); - }); - } - - // ... - -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/zen-keldysh-rifos)** - -Try typing into the input now. Something's wrong! The input is updating very slowly. - -We've fixed the first problem (suspending outside of a transition). But now because of the transition, our state doesn't update immediately, and it can't "drive" a controlled input! - -The answer to this problem **is to split the state in two parts:** a "high priority" part that updates immediately, and a "low priority" part that may wait for a transition. - -In our example, we already have two state variables. The input text is in `query`, and we read the translation from `resource`. We want changes to the `query` state to happen immediately, but changes to the `resource` (i.e. fetching a new translation) should trigger a transition. - -So the correct fix is to put `setQuery` (which doesn't suspend) *outside* the transition, but `setResource` (which will suspend) *inside* of it. - -```js{4,5} -function handleChange(e) { - const value = e.target.value; - - // Outside the transition (urgent) - setQuery(value); - - startTransition(() => { - // Inside the transition (may be delayed) - setResource(fetchTranslation(value)); - }); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/lively-smoke-fdf93)** - -With this change, it works as expected. We can type into the input immediately, and the translation later "catches up" to what we have typed. - -### Deferring a Value {#deferring-a-value} - -By default, React always renders a consistent UI. Consider code like this: - -```js -<> - <ProfileDetails user={user} /> - <ProfileTimeline user={user} /> -</> -``` - -React guarantees that whenever we look at these components on the screen, they will reflect data from the same `user`. If a different `user` is passed down because of a state update, you would see them changing together. You can't ever record a screen and find a frame where they would show values from different `user`s. (If you ever run into a case like this, file a bug!) - -This makes sense in the vast majority of situations. Inconsistent UI is confusing and can mislead users. (For example, it would be terrible if a messenger's Send button and the conversation picker pane "disagreed" about which thread is currently selected.) - -However, sometimes it might be helpful to intentionally introduce an inconsistency. We could do it manually by "splitting" the state like above, but React also offers a built-in Hook for this: - -```js -import { useDeferredValue } from 'react'; - -const deferredValue = useDeferredValue(value, { - timeoutMs: 5000 -}); -``` - -To demonstrate this feature, we'll use [the profile switcher example](https://codesandbox.io/s/musing-ramanujan-bgw2o). Click the "Next" button and notice how it takes 1 second to do a transition. - -Let's say that fetching the user details is very fast and only takes 300 milliseconds. Currently, we're waiting a whole second because we need both user details and posts to display a consistent profile page. But what if we want to show the details faster? - -If we're willing to sacrifice consistency, we could **pass potentially stale data to the components that delay our transition**. That's what `useDeferredValue()` lets us do: - -```js{2-4,10,11,21} -function ProfilePage({ resource }) { - const deferredResource = useDeferredValue(resource, { - timeoutMs: 1000 - }); - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails resource={resource} /> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline - resource={deferredResource} - isStale={deferredResource !== resource} - /> - </Suspense> - </Suspense> - ); -} - -function ProfileTimeline({ isStale, resource }) { - const posts = resource.posts.read(); - return ( - <ul style={{ opacity: isStale ? 0.7 : 1 }}> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/vigorous-keller-3ed2b)** - -The tradeoff we're making here is that `<ProfileTimeline>` will be inconsistent with other components and potentially show an older item. Click "Next" a few times, and you'll notice it. But thanks to that, we were able to cut down the transition time from 1000ms to 300ms. - -Whether or not it's an appropriate tradeoff depends on the situation. But it's a handy tool, especially when the content doesn't change noticeably between items, and the user might not even realize they were looking at a stale version for a second. - -It's worth noting that `useDeferredValue` is not *only* useful for data fetching. It also helps when an expensive component tree causes an interaction (e.g. typing in an input) to be sluggish. Just like we can "defer" a value that takes too long to fetch (and show its old value despite others components updating), we can do this with trees that take too long to render. - -For example, consider a filterable list like this: - -```js -function App() { - const [text, setText] = useState("hello"); - - function handleChange(e) { - setText(e.target.value); - } - - return ( - <div className="App"> - <label> - Type into the input:{" "} - <input value={text} onChange={handleChange} /> - </label> - ... - <MySlowList text={text} /> - </div> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/pensive-shirley-wkp46)** - -In this example, **every item in `<MySlowList>` has an artificial slowdown -- each of them blocks the thread for a few milliseconds**. We'd never do this in a real app, but this helps us simulate what can happen in a deep component tree with no single obvious place to optimize. - -We can see how typing in the input causes stutter. Now let's add `useDeferredValue`: - -```js{3-5,18} -function App() { - const [text, setText] = useState("hello"); - const deferredText = useDeferredValue(text, { - timeoutMs: 5000 - }); - - function handleChange(e) { - setText(e.target.value); - } - - return ( - <div className="App"> - <label> - Type into the input:{" "} - <input value={text} onChange={handleChange} /> - </label> - ... - <MySlowList text={deferredText} /> - </div> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/infallible-dewdney-9fkv9)** - -Now typing has a lot less stutter -- although we pay for this by showing the results with a lag. - -How is this different from debouncing? Our example has a fixed artificial delay (3ms for every one of 80 items), so there is always a delay, no matter how fast our computer is. However, the `useDeferredValue` value only "lags behind" if the rendering takes a while. There is no minimal lag imposed by React. With a more realistic workload, you can expect the lag to adjust to the user’s device. On fast machines, the lag would be smaller or non-existent, and on slow machines, it would be more noticeable. In both cases, the app would remain responsive. That’s the advantage of this mechanism over debouncing or throttling, which always impose a minimal delay and can't avoid blocking the thread while rendering. - -Even though there is an improvement in responsiveness, this example isn't as compelling yet because Concurrent Mode is missing some crucial optimizations for this use case. Still, it is interesting to see that features like `useDeferredValue` (or `useTransition`) are useful regardless of whether we're waiting for network or for computational work to finish. - -### SuspenseList {#suspenselist} - -`<SuspenseList>` is the last pattern that's related to orchestrating loading states. - -Consider this example: - -```js{5-10} -function ProfilePage({ resource }) { - return ( - <> - <ProfileDetails resource={resource} /> - <Suspense fallback={<h2>Loading posts...</h2>}> - <ProfileTimeline resource={resource} /> - </Suspense> - <Suspense fallback={<h2>Loading fun facts...</h2>}> - <ProfileTrivia resource={resource} /> - </Suspense> - </> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/proud-tree-exg5t)** - -The API call duration in this example is randomized. If you keep refreshing it, you will notice that sometimes the posts arrive first, and sometimes the "fun facts" arrive first. - -This presents a problem. If the response for fun facts arrives first, we'll see the fun facts below the `<h2>Loading posts...</h2>` fallback for posts. We might start reading them, but then the *posts* response will come back, and shift all the facts down. This is jarring. - -One way we could fix it is by putting them both in a single boundary: - -```js -<Suspense fallback={<h2>Loading posts and fun facts...</h2>}> - <ProfileTimeline resource={resource} /> - <ProfileTrivia resource={resource} /> -</Suspense> -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/currying-violet-5jsiy)** - -The problem with this is that now we *always* wait for both of them to be fetched. However, if it's the *posts* that came back first, there's no reason to delay showing them. When fun facts load later, they won't shift the layout because they're already below the posts. - -Other approaches to this, such as composing Promises in a special way, are increasingly difficult to pull off when the loading states are located in different components down the tree. - -To solve this, we will import `SuspenseList`: - -```js -import { SuspenseList } from 'react'; -``` - -`<SuspenseList>` coordinates the "reveal order" of the closest `<Suspense>` nodes below it: - -```js{3,11} -function ProfilePage({ resource }) { - return ( - <SuspenseList revealOrder="forwards"> - <ProfileDetails resource={resource} /> - <Suspense fallback={<h2>Loading posts...</h2>}> - <ProfileTimeline resource={resource} /> - </Suspense> - <Suspense fallback={<h2>Loading fun facts...</h2>}> - <ProfileTrivia resource={resource} /> - </Suspense> - </SuspenseList> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/black-wind-byilt)** - -The `revealOrder="forwards"` option means that the closest `<Suspense>` nodes inside this list **will only "reveal" their content in the order they appear in the tree -- even if the data for them arrives in a different order**. `<SuspenseList>` has other interesting modes: try changing `"forwards"` to `"backwards"` or `"together"` and see what happens. - -You can control how many loading states are visible at once with the `tail` prop. If we specify `tail="collapsed"`, we'll see *at most one* fallback at the time. You can play with it [here](https://codesandbox.io/s/adoring-almeida-1zzjh). - -Keep in mind that `<SuspenseList>` is composable, like anything in React. For example, you can create a grid by putting several `<SuspenseList>` rows inside a `<SuspenseList>` table. - -## Next Steps {#next-steps} - -Concurrent Mode offers a powerful UI programming model and a set of new composable primitives to help you orchestrate delightful user experiences. - -It's a result of several years of research and development, but it's not finished. In the section on [adopting Concurrent Mode](/docs/concurrent-mode-adoption.html), we'll describe how you can try it and what you can expect. diff --git a/content/docs/concurrent-mode-reference.md b/content/docs/concurrent-mode-reference.md deleted file mode 100644 index 663af1b3b..000000000 --- a/content/docs/concurrent-mode-reference.md +++ /dev/null @@ -1,214 +0,0 @@ ---- -id: concurrent-mode-reference -title: Concurrent Mode API Reference (Experimental) -permalink: docs/concurrent-mode-reference.html -prev: concurrent-mode-adoption.html ---- - -<style> -.scary > blockquote { - background-color: rgba(237, 51, 21, 0.2); - border-left-color: #ed3315; -} -</style> - -<div class="scary"> - ->Caution: -> ->This page describes **experimental features that are [not yet available](/docs/concurrent-mode-adoption.html) in a stable release**. Don't rely on experimental builds of React in production apps. These features may change significantly and without a warning before they become a part of React. -> ->This documentation is aimed at early adopters and people who are curious. **If you're new to React, don't worry about these features** -- you don't need to learn them right now. - -</div> - -This page is an API reference for the React [Concurrent Mode](/docs/concurrent-mode-intro.html). If you're looking for a guided introduction instead, check out [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html). - -**Note: This is a Community Preview and not the final stable version. There will likely be future changes to these APIs. Use at your own risk!** - -- [Enabling Concurrent Mode](#concurrent-mode) - - [`createRoot`](#createroot) - - [`createBlockingRoot`](#createblockingroot) -- [Suspense](#suspense) - - [`Suspense`](#suspensecomponent) - - [`SuspenseList`](#suspenselist) - - [`useTransition`](#usetransition) - - [`useDeferredValue`](#usedeferredvalue) - -## Enabling Concurrent Mode {#concurrent-mode} - -### `createRoot` {#createroot} - -```js -ReactDOM.createRoot(rootNode).render(<App />); -``` - -Replaces `ReactDOM.render(<App />, rootNode)` and enables Concurrent Mode. - -For more information on Concurrent Mode, check out the [Concurrent Mode documentation.](/docs/concurrent-mode-intro.html) - -### `createBlockingRoot` {#createblockingroot} - -```js -ReactDOM.createBlockingRoot(rootNode).render(<App />) -``` - -Replaces `ReactDOM.render(<App />, rootNode)` and enables [Blocking Mode](/docs/concurrent-mode-adoption.html#migration-step-blocking-mode). - -Opting into Concurrent Mode introduces semantic changes to how React works. This means that you can't use Concurrent Mode in just a few components. Because of this, some apps may not be able to migrate directly to Concurrent Mode. - -Blocking Mode only contains a small subset of Concurrent Mode features and is intended as an intermediary migration step for apps that are unable to migrate directly. - -## Suspense API {#suspense} - -### `Suspense` {#suspensecomponent} - -```js -<Suspense fallback={<h1>Loading...</h1>}> - <ProfilePhoto /> - <ProfileDetails /> -</Suspense> -``` - -`Suspense` lets your components "wait" for something before they can render, showing a fallback while waiting. - -In this example, `ProfileDetails` is waiting for an asynchronous API call to fetch some data. While we wait for `ProfileDetails` and `ProfilePhoto`, we will show the `Loading...` fallback instead. It is important to note that until all children inside `<Suspense>` has loaded, we will continue to show the fallback. - -`Suspense` takes two props: -* **fallback** takes a loading indicator. The fallback is shown until all of the children of the `Suspense` component have finished rendering. -* **unstable_avoidThisFallback** takes a boolean. It tells React whether to "skip" revealing this boundary during the initial load. This API will likely be removed in a future release. - -### `<SuspenseList>` {#suspenselist} - -```js -<SuspenseList revealOrder="forwards"> - <Suspense fallback={'Loading...'}> - <ProfilePicture id={1} /> - </Suspense> - <Suspense fallback={'Loading...'}> - <ProfilePicture id={2} /> - </Suspense> - <Suspense fallback={'Loading...'}> - <ProfilePicture id={3} /> - </Suspense> - ... -</SuspenseList> -``` - -`SuspenseList` helps coordinate many components that can suspend by orchestrating the order in which these components are revealed to the user. - -When multiple components need to fetch data, this data may arrive in an unpredictable order. However, if you wrap these items in a `SuspenseList`, React will not show an item in the list until previous items have been displayed (this behavior is adjustable). - -`SuspenseList` takes two props: -* **revealOrder (forwards, backwards, together)** defines the order in which the `SuspenseList` children should be revealed. - * `together` reveals *all* of them when they're ready instead of one by one. -* **tail (collapsed, hidden)** dictates how unloaded items in a `SuspenseList` is shown. - * By default, `SuspenseList` will show all fallbacks in the list. - * `collapsed` shows only the next fallback in the list. - * `hidden` doesn't show any unloaded items. - -Note that `SuspenseList` only operates on the closest `Suspense` and `SuspenseList` components below it. It does not search for boundaries deeper than one level. However, it is possible to nest multiple `SuspenseList` components in each other to build grids. - -### `useTransition` {#usetransition} - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; - -const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG); -``` - -`useTransition` allows components to avoid undesirable loading states by waiting for content to load before **transitioning to the next screen**. It also allows components to defer slower, data fetching updates until subsequent renders so that more crucial updates can be rendered immediately. - -The `useTransition` hook returns two values in an array. -* `startTransition` is a function that takes a callback. We can use it to tell React which state we want to defer. -* `isPending` is a boolean. It's React's way of informing us whether we're waiting for the transition to finish. - -**If some state update causes a component to suspend, that state update should be wrapped in a transition.** - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; - -function App() { - const [resource, setResource] = useState(initialResource); - const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG); - return ( - <> - <button - disabled={isPending} - onClick={() => { - startTransition(() => { - const nextUserId = getNextId(resource.userId); - setResource(fetchProfileData(nextUserId)); - }); - }} - > - Next - </button> - {isPending ? " Loading..." : null} - <Suspense fallback={<Spinner />}> - <ProfilePage resource={resource} /> - </Suspense> - </> - ); -} -``` - -In this code, we've wrapped our data fetching with `startTransition`. This allows us to start fetching the profile data right away, while deferring the render of the next profile page and its associated `Spinner` for 2 seconds (the time shown in `timeoutMs`). - -The `isPending` boolean lets React know that our component is transitioning, so we are able to let the user know this by showing some loading text on the previous profile page. - -**For an in-depth look at transitions, you can read [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html#transitions).** - -#### useTransition Config {#usetransition-config} - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; -``` - -`useTransition` accepts an **optional Suspense Config** with a `timeoutMs`. This timeout (in milliseconds) tells React how long to wait before showing the next state (the new Profile Page in the above example). - -**Note: We recommend that you share Suspense Config between different modules.** - - -### `useDeferredValue` {#usedeferredvalue} - -```js -const deferredValue = useDeferredValue(value, { timeoutMs: 2000 }); -``` - -Returns a deferred version of the value that may "lag behind" it for at most `timeoutMs`. - -This is commonly used to keep the interface responsive when you have something that renders immediately based on user input and something that needs to wait for a data fetch. - -A good example of this is a text input. - -```js -function App() { - const [text, setText] = useState("hello"); - const deferredText = useDeferredValue(text, { timeoutMs: 2000 }); - - return ( - <div className="App"> - {/* Keep passing the current text to the input */} - <input value={text} onChange={handleChange} /> - ... - {/* But the list is allowed to "lag behind" when necessary */} - <MySlowList text={deferredText} /> - </div> - ); - } -``` - -This allows us to start showing the new text for the `input` immediately, which allows the webpage to feel responsive. Meanwhile, `MySlowList` "lags behind" for up to 2 seconds according to the `timeoutMs` before updating, allowing it to render with the current text in the background. - -**For an in-depth look at deferring values, you can read [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html#deferring-a-value).** - -#### useDeferredValue Config {#usedeferredvalue-config} - -```js -const SUSPENSE_CONFIG = { timeoutMs: 2000 }; -``` - -`useDeferredValue` accepts an **optional Suspense Config** with a `timeoutMs`. This timeout (in milliseconds) tells React how long the deferred value is allowed to lag behind. - -React will always try to use a shorter lag when network and device allows it. diff --git a/content/docs/concurrent-mode-suspense.md b/content/docs/concurrent-mode-suspense.md deleted file mode 100644 index 12ad112b2..000000000 --- a/content/docs/concurrent-mode-suspense.md +++ /dev/null @@ -1,736 +0,0 @@ ---- -id: concurrent-mode-suspense -title: Suspense for Data Fetching (Experimental) -permalink: docs/concurrent-mode-suspense.html -prev: concurrent-mode-intro.html -next: concurrent-mode-patterns.html ---- - -<style> -.scary > blockquote { - background-color: rgba(237, 51, 21, 0.2); - border-left-color: #ed3315; -} -</style> - -<div class="scary"> - ->Caution: -> ->This page describes **experimental features that are [not yet available](/docs/concurrent-mode-adoption.html) in a stable release**. Don't rely on experimental builds of React in production apps. These features may change significantly and without a warning before they become a part of React. -> ->This documentation is aimed at early adopters and people who are curious. **If you're new to React, don't worry about these features** -- you don't need to learn them right now. For example, if you're looking for a data fetching tutorial that works today, read [this article](https://www.robinwieruch.de/react-hooks-fetch-data/) instead. - -</div> - -React 16.6 added a `<Suspense>` component that lets you "wait" for some code to load and declaratively specify a loading state (like a spinner) while we're waiting: - -```jsx -const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded - -// Show a spinner while the profile is loading -<Suspense fallback={<Spinner />}> - <ProfilePage /> -</Suspense> -``` - -Suspense for Data Fetching is a new feature that lets you also use `<Suspense>` to **declaratively "wait" for anything else, including data.** This page focuses on the data fetching use case, but it can also wait for images, scripts, or other asynchronous work. - -- [What Is Suspense, Exactly?](#what-is-suspense-exactly) - - [What Suspense Is Not](#what-suspense-is-not) - - [What Suspense Lets You Do](#what-suspense-lets-you-do) -- [Using Suspense in Practice](#using-suspense-in-practice) - - [What If I Don’t Use Relay?](#what-if-i-dont-use-relay) - - [For Library Authors](#for-library-authors) -- [Traditional Approaches vs Suspense](#traditional-approaches-vs-suspense) - - [Approach 1: Fetch-on-Render (not using Suspense)](#approach-1-fetch-on-render-not-using-suspense) - - [Approach 2: Fetch-Then-Render (not using Suspense)](#approach-2-fetch-then-render-not-using-suspense) - - [Approach 3: Render-as-You-Fetch (using Suspense)](#approach-3-render-as-you-fetch-using-suspense) -- [Start Fetching Early](#start-fetching-early) - - [We’re Still Figuring This Out](#were-still-figuring-this-out) -- [Suspense and Race Conditions](#suspense-and-race-conditions) - - [Race Conditions with useEffect](#race-conditions-with-useeffect) - - [Race Conditions with componentDidUpdate](#race-conditions-with-componentdidupdate) - - [The Problem](#the-problem) - - [Solving Race Conditions with Suspense](#solving-race-conditions-with-suspense) -- [Handling Errors](#handling-errors) -- [Next Steps](#next-steps) - -## What Is Suspense, Exactly? {#what-is-suspense-exactly} - -Suspense lets your components "wait" for something before they can render. In [this example](https://codesandbox.io/s/frosty-hermann-bztrp), two components wait for an asynchronous API call to fetch some data: - -```js -const resource = fetchProfileData(); - -function ProfilePage() { - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails /> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline /> - </Suspense> - </Suspense> - ); -} - -function ProfileDetails() { - // Try to read user info, although it might not have loaded yet - const user = resource.user.read(); - return <h1>{user.name}</h1>; -} - -function ProfileTimeline() { - // Try to read posts, although they might not have loaded yet - const posts = resource.posts.read(); - return ( - <ul> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-hermann-bztrp)** - -This demo is a teaser. Don't worry if it doesn't quite make sense yet. We'll talk more about how it works below. Keep in mind that Suspense is more of a *mechanism*, and particular APIs like `fetchProfileData()` or `resource.posts.read()` in the above example are not very important. If you're curious, you can find their definitions right in the [demo sandbox](https://codesandbox.io/s/frosty-hermann-bztrp). - -Suspense is not a data fetching library. It's a **mechanism for data fetching libraries** to communicate to React that *the data a component is reading is not ready yet*. React can then wait for it to be ready and update the UI. At Facebook, we use Relay and its [new Suspense integration](https://relay.dev/docs/en/experimental/step-by-step). We expect that other libraries like Apollo can provide similar integrations. - -In the long term, we intend Suspense to become the primary way to read asynchronous data from components -- no matter where that data is coming from. - -### What Suspense Is Not {#what-suspense-is-not} - -Suspense is significantly different from existing approaches to these problems, so reading about it for the first time often leads to misconceptions. Let's clarify the most common ones: - - * **It is not a data fetching implementation.** It does not assume that you use GraphQL, REST, or any other particular data format, library, transport, or protocol. - - * **It is not a ready-to-use client.** You can't "replace" `fetch` or Relay with Suspense. But you can use a library that's integrated with Suspense (for example, [new Relay APIs](https://relay.dev/docs/en/experimental/api-reference)). - - * **It does not couple data fetching to the view layer.** It helps orchestrate displaying the loading states in your UI, but it doesn't tie your network logic to React components. - -### What Suspense Lets You Do {#what-suspense-lets-you-do} - -So what's the point of Suspense? There are a few ways we can answer this: - -* **It lets data fetching libraries deeply integrate with React.** If a data fetching library implements Suspense support, using it from React components feels very natural. - -* **It lets you orchestrate intentionally designed loading states.** It doesn't say _how_ the data is fetched, but it lets you closely control the visual loading sequence of your app. - -* **It helps you avoid race conditions.** Even with `await`, asynchronous code is often error-prone. Suspense feels more like reading data *synchronously* — as if it were already loaded. - -## Using Suspense in Practice {#using-suspense-in-practice} - -At Facebook, so far we have only used the Relay integration with Suspense in production. **If you're looking for a practical guide to get started today, [check out the Relay Guide](https://relay.dev/docs/en/experimental/step-by-step)!** It demonstrates patterns that have already worked well for us in production. - -**The code demos on this page use a "fake" API implementation rather than Relay.** This makes them easier to understand if you're not familiar with GraphQL, but they won't tell you the "right way" to build an app with Suspense. This page is more conceptual and is intended to help you see *why* Suspense works in a certain way, and which problems it solves. - -### What If I Don't Use Relay? {#what-if-i-dont-use-relay} - -If you don't use Relay today, you might have to wait before you can really try Suspense in your app. So far, it's the only implementation that we tested in production and are confident in. - -Over the next several months, many libraries will appear with different takes on Suspense APIs. **If you prefer to learn when things are more stable, you might prefer to ignore this work for now, and come back when the Suspense ecosystem is more mature.** - -You can also write your own integration for a data fetching library, if you'd like. - -### For Library Authors {#for-library-authors} - -We expect to see a lot of experimentation in the community with other libraries. There is one important thing to note for data fetching library authors. - -Although it's technically doable, Suspense is **not** currently intended as a way to start fetching data when a component renders. Rather, it lets components express that they're "waiting" for data that is *already being fetched*. **[Building Great User Experiences with Concurrent Mode and Suspense](/blog/2019/11/06/building-great-user-experiences-with-concurrent-mode-and-suspense.html) describes why this matters and how to implement this pattern in practice.** - -Unless you have a solution that helps prevent waterfalls, we suggest to prefer APIs that favor or enforce fetching before render. For a concrete example, you can look at how [Relay Suspense API](https://relay.dev/docs/en/experimental/api-reference#usepreloadedquery) enforces preloading. Our messaging about this hasn't been very consistent in the past. Suspense for Data Fetching is still experimental, so you can expect our recommendations to change over time as we learn more from production usage and understand the problem space better. - -## Traditional Approaches vs Suspense {#traditional-approaches-vs-suspense} - -We could introduce Suspense without mentioning the popular data fetching approaches. However, this makes it more difficult to see which problems Suspense solves, why these problems are worth solving, and how Suspense is different from the existing solutions. - -Instead, we'll look at Suspense as a logical next step in a sequence of approaches: - -* **Fetch-on-render (for example, `fetch` in `useEffect`):** Start rendering components. Each of these components may trigger data fetching in their effects and lifecycle methods. This approach often leads to "waterfalls". -* **Fetch-then-render (for example, Relay without Suspense):** Start fetching all the data for the next screen as early as possible. When the data is ready, render the new screen. We can't do anything until the data arrives. -* **Render-as-you-fetch (for example, Relay with Suspense):** Start fetching all the required data for the next screen as early as possible, and start rendering the new screen *immediately — before we get a network response*. As data streams in, React retries rendering components that still need data until they're all ready. - ->Note -> ->This is a bit simplified, and in practice solutions tend to use a mix of different approaches. Still, we will look at them in isolation to better contrast their tradeoffs. - -To compare these approaches, we'll implement a profile page with each of them. - -### Approach 1: Fetch-on-Render (not using Suspense) {#approach-1-fetch-on-render-not-using-suspense} - -A common way to fetch data in React apps today is to use an effect: - -```js -// In a function component: -useEffect(() => { - fetchSomething(); -}, []); - -// Or, in a class component: -componentDidMount() { - fetchSomething(); -} -``` - -We call this approach "fetch-on-render" because it doesn't start fetching until *after* the component has rendered on the screen. This leads to a problem known as a "waterfall". - -Consider these `<ProfilePage>` and `<ProfileTimeline>` components: - -```js{4-6,22-24} -function ProfilePage() { - const [user, setUser] = useState(null); - - useEffect(() => { - fetchUser().then(u => setUser(u)); - }, []); - - if (user === null) { - return <p>Loading profile...</p>; - } - return ( - <> - <h1>{user.name}</h1> - <ProfileTimeline /> - </> - ); -} - -function ProfileTimeline() { - const [posts, setPosts] = useState(null); - - useEffect(() => { - fetchPosts().then(p => setPosts(p)); - }, []); - - if (posts === null) { - return <h2>Loading posts...</h2>; - } - return ( - <ul> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/fragrant-glade-8huj6)** - -If you run this code and watch the console logs, you'll notice the sequence is: - -1. We start fetching user details -2. We wait... -3. We finish fetching user details -4. We start fetching posts -5. We wait... -6. We finish fetching posts - -If fetching user details takes three seconds, we'll only *start* fetching the posts after three seconds! That's a "waterfall": an unintentional *sequence* that should have been parallelized. - -Waterfalls are common in code that fetches data on render. They're possible to solve, but as the product grows, many people prefer to use a solution that guards against this problem. - -### Approach 2: Fetch-Then-Render (not using Suspense) {#approach-2-fetch-then-render-not-using-suspense} - -Libraries can prevent waterfalls by offering a more centralized way to do data fetching. For example, Relay solves this problem by moving the information about the data a component needs to statically analyzable *fragments*, which later get composed into a single query. - -On this page, we don't assume knowledge of Relay, so we won't be using it for this example. Instead, we'll write something similar manually by combining our data fetching methods: - -```js -function fetchProfileData() { - return Promise.all([ - fetchUser(), - fetchPosts() - ]).then(([user, posts]) => { - return {user, posts}; - }) -} -``` - -In this example, `<ProfilePage>` waits for both requests but starts them in parallel: - -```js{1,2,8-13} -// Kick off fetching as early as possible -const promise = fetchProfileData(); - -function ProfilePage() { - const [user, setUser] = useState(null); - const [posts, setPosts] = useState(null); - - useEffect(() => { - promise.then(data => { - setUser(data.user); - setPosts(data.posts); - }); - }, []); - - if (user === null) { - return <p>Loading profile...</p>; - } - return ( - <> - <h1>{user.name}</h1> - <ProfileTimeline posts={posts} /> - </> - ); -} - -// The child doesn't trigger fetching anymore -function ProfileTimeline({ posts }) { - if (posts === null) { - return <h2>Loading posts...</h2>; - } - return ( - <ul> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/wandering-morning-ev6r0)** - -The event sequence now becomes like this: - -1. We start fetching user details -2. We start fetching posts -3. We wait... -4. We finish fetching user details -5. We finish fetching posts - -We've solved the previous network "waterfall", but accidentally introduced a different one. We wait for *all* data to come back with `Promise.all()` inside `fetchProfileData`, so now we can't render profile details until the posts have been fetched too. We have to wait for both. - -Of course, this is possible to fix in this particular example. We could remove the `Promise.all()` call, and wait for both Promises separately. However, this approach gets progressively more difficult as the complexity of our data and component tree grows. It's hard to write reliable components when arbitrary parts of the data tree may be missing or stale. So fetching all data for the new screen and *then* rendering is often a more practical option. - -### Approach 3: Render-as-You-Fetch (using Suspense) {#approach-3-render-as-you-fetch-using-suspense} - -In the previous approach, we fetched data before we called `setState`: - -1. Start fetching -2. Finish fetching -3. Start rendering - -With Suspense, we still start fetching first, but we flip the last two steps around: - -1. Start fetching -2. **Start rendering** -3. **Finish fetching** - -**With Suspense, we don't wait for the response to come back before we start rendering.** In fact, we start rendering *pretty much immediately* after kicking off the network request: - -```js{2,17,23} -// This is not a Promise. It's a special object from our Suspense integration. -const resource = fetchProfileData(); - -function ProfilePage() { - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails /> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline /> - </Suspense> - </Suspense> - ); -} - -function ProfileDetails() { - // Try to read user info, although it might not have loaded yet - const user = resource.user.read(); - return <h1>{user.name}</h1>; -} - -function ProfileTimeline() { - // Try to read posts, although they might not have loaded yet - const posts = resource.posts.read(); - return ( - <ul> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-hermann-bztrp)** - -Here's what happens when we render `<ProfilePage>` on the screen: - -1. We've already kicked off the requests in `fetchProfileData()`. It gave us a special "resource" instead of a Promise. In a realistic example, it would be provided by our data library's Suspense integration, like Relay. -2. React tries to render `<ProfilePage>`. It returns `<ProfileDetails>` and `<ProfileTimeline>` as children. -3. React tries to render `<ProfileDetails>`. It calls `resource.user.read()`. None of the data is fetched yet, so this component "suspends". React skips over it, and tries rendering other components in the tree. -4. React tries to render `<ProfileTimeline>`. It calls `resource.posts.read()`. Again, there's no data yet, so this component also "suspends". React skips over it too, and tries rendering other components in the tree. -5. There's nothing left to try rendering. Because `<ProfileDetails>` suspended, React shows the closest `<Suspense>` fallback above it in the tree: `<h1>Loading profile...</h1>`. We're done for now. - -This `resource` object represents the data that isn't there yet, but might eventually get loaded. When we call `read()`, we either get the data, or the component "suspends". - -**As more data streams in, React will retry rendering, and each time it might be able to progress "deeper".** When `resource.user` is fetched, the `<ProfileDetails>` component will render successfully and we'll no longer need the `<h1>Loading profile...</h1>` fallback. Eventually, we'll get all the data, and there will be no fallbacks on the screen. - -This has an interesting implication. Even if we use a GraphQL client that collects all data requirements in a single request, *streaming the response lets us show more content sooner*. Because we render-*as-we-fetch* (as opposed to *after* fetching), if `user` appears in the response earlier than `posts`, we'll be able to "unlock" the outer `<Suspense>` boundary before the response even finishes. We might have missed this earlier, but even the fetch-then-render solution contained a waterfall: between fetching and rendering. Suspense doesn't inherently suffer from this waterfall, and libraries like Relay take advantage of this. - -Note how we eliminated the `if (...)` "is loading" checks from our components. This doesn't only remove boilerplate code, but it also simplifies making quick design changes. For example, if we wanted profile details and posts to always "pop in" together, we could delete the `<Suspense>` boundary between them. Or we could make them independent from each other by giving each *its own* `<Suspense>` boundary. Suspense lets us change the granularity of our loading states and orchestrate their sequencing without invasive changes to our code. - -## Start Fetching Early {#start-fetching-early} - -If you're working on a data fetching library, there's a crucial aspect of Render-as-You-Fetch you don't want to miss. **We kick off fetching _before_ rendering.** Look at this code example closer: - -```js -// Start fetching early! -const resource = fetchProfileData(); - -// ... - -function ProfileDetails() { - // Try to read user info - const user = resource.user.read(); - return <h1>{user.name}</h1>; -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/frosty-hermann-bztrp)** - -Note that the `read()` call in this example doesn't *start* fetching. It only tries to read the data that is **already being fetched**. This difference is crucial to creating fast applications with Suspense. We don't want to delay loading data until a component starts rendering. As a data fetching library author, you can enforce this by making it impossible to get a `resource` object without also starting a fetch. Every demo on this page using our "fake API" enforces this. - -You might object that fetching "at the top level" like in this example is impractical. What are we going to do if we navigate to another profile's page? We might want to fetch based on props. The answer to this is **we want to start fetching in the event handlers instead**. Here is a simplified example of navigating between user's pages: - -```js{1,2,10,11} -// First fetch: as soon as possible -const initialResource = fetchProfileData(0); - -function App() { - const [resource, setResource] = useState(initialResource); - return ( - <> - <button onClick={() => { - const nextUserId = getNextId(resource.userId); - // Next fetch: when the user clicks - setResource(fetchProfileData(nextUserId)); - }}> - Next - </button> - <ProfilePage resource={resource} /> - </> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/infallible-feather-xjtbu)** - -With this approach, we can **fetch code and data in parallel**. When we navigate between pages, we don't need to wait for a page's code to load to start loading its data. We can start fetching both code and data at the same time (during the link click), delivering a much better user experience. - -This poses a question of how do we know *what* to fetch before rendering the next screen. There are several ways to solve this (for example, by integrating data fetching closer with your routing solution). If you work on a data fetching library, [Building Great User Experiences with Concurrent Mode and Suspense](/blog/2019/11/06/building-great-user-experiences-with-concurrent-mode-and-suspense.html) presents a deep dive on how to accomplish this and why it's important. - -### We're Still Figuring This Out {#were-still-figuring-this-out} - -Suspense itself as a mechanism is flexible and doesn't have many constraints. Product code needs to be more constrained to ensure no waterfalls, but there are different ways to provide these guarantees. Some questions that we're currently exploring include: - -* Fetching early can be cumbersome to express. How do we make it easier to avoid waterfalls? -* When we fetch data for a page, can the API encourage including data for instant transitions *from* it? -* What is the lifetime of a response? Should caching be global or local? Who manages the cache? -* Can Proxies help express lazy-loaded APIs without inserting `read()` calls everywhere? -* What would the equivalent of composing GraphQL queries look like for arbitrary Suspense data? - -Relay has its own answers to some of these questions. There is certainly more than a single way to do it, and we're excited to see what new ideas the React community comes up with. - -## Suspense and Race Conditions {#suspense-and-race-conditions} - -Race conditions are bugs that happen due to incorrect assumptions about the order in which our code may run. Fetching data in the `useEffect` Hook or in class lifecycle methods like `componentDidUpdate` often leads to them. Suspense can help here, too — let's see how. - -To demonstrate the issue, we will add a top-level `<App>` component that renders our `<ProfilePage>` with a button that lets us **switch between different profiles**: - -```js{9-11} -function getNextId(id) { - // ... -} - -function App() { - const [id, setId] = useState(0); - return ( - <> - <button onClick={() => setId(getNextId(id))}> - Next - </button> - <ProfilePage id={id} /> - </> - ); -} -``` - -Let's compare how different data fetching strategies deal with this requirement. - -### Race Conditions with `useEffect` {#race-conditions-with-useeffect} - -First, we'll try a version of our original "fetch in effect" example. We'll modify it to pass an `id` parameter from the `<ProfilePage>` props to `fetchUser(id)` and `fetchPosts(id)`: - -```js{1,5,6,14,19,23,24} -function ProfilePage({ id }) { - const [user, setUser] = useState(null); - - useEffect(() => { - fetchUser(id).then(u => setUser(u)); - }, [id]); - - if (user === null) { - return <p>Loading profile...</p>; - } - return ( - <> - <h1>{user.name}</h1> - <ProfileTimeline id={id} /> - </> - ); -} - -function ProfileTimeline({ id }) { - const [posts, setPosts] = useState(null); - - useEffect(() => { - fetchPosts(id).then(p => setPosts(p)); - }, [id]); - - if (posts === null) { - return <h2>Loading posts...</h2>; - } - return ( - <ul> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/nervous-glade-b5sel)** - -Note how we also changed the effect dependencies from `[]` to `[id]` — because we want the effect to re-run when the `id` changes. Otherwise, we wouldn't refetch new data. - -If we try this code, it might seem like it works at first. However, if we randomize the delay time in our "fake API" implementation and press the "Next" button fast enough, we'll see from the console logs that something is going very wrong. **Requests from the previous profiles may sometimes "come back" after we've already switched the profile to another ID -- and in that case they can overwrite the new state with a stale response for a different ID.** - -This problem is possible to fix (you could use the effect cleanup function to either ignore or cancel stale requests), but it's unintuitive and difficult to debug. - -### Race Conditions with `componentDidUpdate` {#race-conditions-with-componentdidupdate} - -One might think that this is a problem specific to `useEffect` or Hooks. Maybe if we port this code to classes or use convenient syntax like `async` / `await`, it will solve the problem? - -Let's try that: - -```js -class ProfilePage extends React.Component { - state = { - user: null, - }; - componentDidMount() { - this.fetchData(this.props.id); - } - componentDidUpdate(prevProps) { - if (prevProps.id !== this.props.id) { - this.fetchData(this.props.id); - } - } - async fetchData(id) { - const user = await fetchUser(id); - this.setState({ user }); - } - render() { - const { id } = this.props; - const { user } = this.state; - if (user === null) { - return <p>Loading profile...</p>; - } - return ( - <> - <h1>{user.name}</h1> - <ProfileTimeline id={id} /> - </> - ); - } -} - -class ProfileTimeline extends React.Component { - state = { - posts: null, - }; - componentDidMount() { - this.fetchData(this.props.id); - } - componentDidUpdate(prevProps) { - if (prevProps.id !== this.props.id) { - this.fetchData(this.props.id); - } - } - async fetchData(id) { - const posts = await fetchPosts(id); - this.setState({ posts }); - } - render() { - const { posts } = this.state; - if (posts === null) { - return <h2>Loading posts...</h2>; - } - return ( - <ul> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); - } -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/trusting-clarke-8twuq)** - -This code is deceptively easy to read. - -Unfortunately, neither using a class nor the `async` / `await` syntax helped us solve this problem. This version suffers from exactly the same race conditions, for the same reasons. - -### The Problem {#the-problem} - -React components have their own "lifecycle". They may receive props or update state at any point in time. However, each asynchronous request *also* has its own "lifecycle". It starts when we kick it off, and finishes when we get a response. The difficulty we're experiencing is "synchronizing" several processes in time that affect each other. This is hard to think about. - -### Solving Race Conditions with Suspense {#solving-race-conditions-with-suspense} - -Let's rewrite this example again, but using Suspense only: - -```js -const initialResource = fetchProfileData(0); - -function App() { - const [resource, setResource] = useState(initialResource); - return ( - <> - <button onClick={() => { - const nextUserId = getNextId(resource.userId); - setResource(fetchProfileData(nextUserId)); - }}> - Next - </button> - <ProfilePage resource={resource} /> - </> - ); -} - -function ProfilePage({ resource }) { - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails resource={resource} /> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline resource={resource} /> - </Suspense> - </Suspense> - ); -} - -function ProfileDetails({ resource }) { - const user = resource.user.read(); - return <h1>{user.name}</h1>; -} - -function ProfileTimeline({ resource }) { - const posts = resource.posts.read(); - return ( - <ul> - {posts.map(post => ( - <li key={post.id}>{post.text}</li> - ))} - </ul> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/infallible-feather-xjtbu)** - -In the previous Suspense example, we only had one `resource`, so we held it in a top-level variable. Now that we have multiple resources, we moved it to the `<App>`'s component state: - -```js{4} -const initialResource = fetchProfileData(0); - -function App() { - const [resource, setResource] = useState(initialResource); -``` - -When we click "Next", the `<App>` component kicks off a request for the next profile, and passes *that* object down to the `<ProfilePage>` component: - -```js{4,8} - <> - <button onClick={() => { - const nextUserId = getNextId(resource.userId); - setResource(fetchProfileData(nextUserId)); - }}> - Next - </button> - <ProfilePage resource={resource} /> - </> -``` - -Again, notice that **we're not waiting for the response to set the state. It's the other way around: we set the state (and start rendering) immediately after kicking off a request**. As soon as we have more data, React "fills in" the content inside `<Suspense>` components. - -This code is very readable, but unlike the examples earlier, the Suspense version doesn't suffer from race conditions. You might be wondering why. The answer is that in the Suspense version, we don't have to think about *time* as much in our code. Our original code with race conditions needed to set the state *at the right moment later*, or otherwise it would be wrong. But with Suspense, we set the state *immediately* -- so it's harder to mess it up. - -## Handling Errors {#handling-errors} - -When we write code with Promises, we might use `catch()` to handle errors. How does this work with Suspense, given that we don't *wait* for Promises to start rendering? - -With Suspense, handling fetching errors works the same way as handling rendering errors -- you can render an [error boundary](/docs/error-boundaries.html) anywhere to "catch" errors in components below. - -First, we'll define an error boundary component to use across our project: - -```js -// Error boundaries currently have to be classes. -class ErrorBoundary extends React.Component { - state = { hasError: false, error: null }; - static getDerivedStateFromError(error) { - return { - hasError: true, - error - }; - } - render() { - if (this.state.hasError) { - return this.props.fallback; - } - return this.props.children; - } -} -``` - -And then we can put it anywhere in the tree to catch errors: - -```js{5,9} -function ProfilePage() { - return ( - <Suspense fallback={<h1>Loading profile...</h1>}> - <ProfileDetails /> - <ErrorBoundary fallback={<h2>Could not fetch posts.</h2>}> - <Suspense fallback={<h1>Loading posts...</h1>}> - <ProfileTimeline /> - </Suspense> - </ErrorBoundary> - </Suspense> - ); -} -``` - -**[Try it on CodeSandbox](https://codesandbox.io/s/adoring-goodall-8wbn7)** - -It would catch both rendering errors *and* errors from Suspense data fetching. We can have as many error boundaries as we like but it's best to [be intentional](https://aweary.dev/fault-tolerance-react/) about their placement. - -## Next Steps {#next-steps} - -We've now covered the basics of Suspense for Data Fetching! Importantly, we now better understand *why* Suspense works this way, and how it fits into the data fetching space. - -Suspense answers some questions, but it also poses new questions of its own: - -* If some component "suspends", does the app freeze? How to avoid this? -* What if we want to show a spinner in a different place than "above" the component in a tree? -* If we intentionally *want* to show an inconsistent UI for a small period of time, can we do that? -* Instead of showing a spinner, can we add a visual effect like "greying out" the current screen? -* Why does our [last Suspense example](https://codesandbox.io/s/infallible-feather-xjtbu) log a warning when clicking the "Next" button? - -To answer these questions, we will refer to the next section on [Concurrent UI Patterns](/docs/concurrent-mode-patterns.html). diff --git a/content/docs/conditional-rendering.md b/content/docs/conditional-rendering.md index 130ca2943..9a6a59506 100644 --- a/content/docs/conditional-rendering.md +++ b/content/docs/conditional-rendering.md @@ -8,7 +8,20 @@ redirect_from: - "tips/false-in-jsx.html" --- +<<<<<<< HEAD Στο React, μπορείτε να δημιουργήσετε ξεχωριστά components που ενσωματώνουν τη συμπεριφορά που χρειάζεστε. Στη συνέχεια, μπορείτε να κάνετε render μόνο μαερικά από αυτά, ανάλογα με το state της εφαρμογής σας. +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Conditional Rendering](https://beta.reactjs.org/learn/conditional-rendering) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + +In React, you can create distinct components that encapsulate behavior you need. Then, you can render only some of them, depending on the state of your application. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Το rendering υπό συνθήκες στο React λειτουργεί με τον ίδιο τρόπο που λειτουργούν οι συνθήκες στη JavaScript. Χρησιμοποιήστε τα JavaScript operators όπως το [`if`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) ή το [conditional operator](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) για να δημιουργήσετε components που αντιπροσωπεύουν το τρέχον state και αφήστε το React να ενημερώσει το UI για να τα ταιριάξει. @@ -35,11 +48,9 @@ function Greeting(props) { return <GuestGreeting />; } -ReactDOM.render( - // Try changing to isLoggedIn={true}: - <Greeting isLoggedIn={false} />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +// Try changing to isLoggedIn={true}: +root.render(<Greeting isLoggedIn={false} />); ``` [**Δοκιμάστε το στο CodePen**](https://codepen.io/gaearon/pen/ZpVxNq?editors=0011) @@ -110,10 +121,8 @@ class LoginControl extends React.Component { } } -ReactDOM.render( - <LoginControl />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<LoginControl />); ``` [**Δοκιμάστε το στο CodePen**](https://codepen.io/gaearon/pen/QKzAgB?editors=0010) @@ -122,7 +131,11 @@ ReactDOM.render( ### If σε μια γραμμή με το λογικό Οperator && {#inline-if-with-logical--operator} +<<<<<<< HEAD Μπορείτε να [ενσωματώσετε οποιεσδήποτε εκφράσεις μέσα στο JSX](/docs/introducing-jsx.html#embedding-expressions-in-jsx) περικλείοντας τα σε άγκιστρα. Αυτό περιλαμβάνει τον λογικό operator της JavaScript `&&`. Μπορεί να είναι βολικό για να συμπεριλάβει υπό συνθήκη ένα component: +======= +You may [embed expressions in JSX](/docs/introducing-jsx.html#embedding-expressions-in-jsx) by wrapping them in curly braces. This includes the JavaScript logical `&&` operator. It can be handy for conditionally including an element: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```js{6-10} function Mailbox(props) { @@ -140,10 +153,9 @@ function Mailbox(props) { } const messages = ['React', 'Re: React', 'Re:Re: React']; -ReactDOM.render( - <Mailbox unreadMessages={messages} />, - document.getElementById('root') -); + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Mailbox unreadMessages={messages} />); ``` [**Δοκιμάστε το στο CodePen**](https://codepen.io/gaearon/pen/ozJddz?editors=0010) @@ -152,6 +164,19 @@ ReactDOM.render( Επομένως, αν η κατάσταση είναι `true`, το component αμέσως μετά το `&&` θα εμφανιστεί στο output. Εάν είναι `false`, το React θα το αγνοήσει. +Note that returning a falsy expression will still cause the element after `&&` to be skipped but will return the falsy expression. In the example below, `<div>0</div>` will be returned by the render method. + +```javascript{2,5} +render() { + const count = 0; + return ( + <div> + {count && <h1>Messages: {count}</h1>} + </div> + ); +} +``` + ### Inline If-Else with Conditional Operator {#inline-if-else-with-conditional-operator} Μια άλλη μέθοδος για να κάνετε rendering υπό συνθήκες τα components σε μια γραμμή, είναι η χρήση του conditional operator στη JavaScript [`condition ? true : false`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator). @@ -176,11 +201,10 @@ render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> - {isLoggedIn ? ( - <LogoutButton onClick={this.handleLogoutClick} /> - ) : ( - <LoginButton onClick={this.handleLoginClick} /> - )} + {isLoggedIn + ? <LogoutButton onClick={this.handleLogoutClick} /> + : <LoginButton onClick={this.handleLoginClick} /> + } </div> ); } @@ -232,10 +256,8 @@ class Page extends React.Component { } } -ReactDOM.render( - <Page />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Page />); ``` [**Δοκιμάστε το στο CodePen**](https://codepen.io/gaearon/pen/Xjoqwm?editors=0010) diff --git a/content/docs/context.md b/content/docs/context.md index c6882fe08..1ba8e8651 100644 --- a/content/docs/context.md +++ b/content/docs/context.md @@ -4,9 +4,18 @@ title: Context permalink: docs/context.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Passing Data Deeply with Context](https://beta.reactjs.org/learn/passing-data-deeply-with-context) +> - [`useContext`](https://beta.reactjs.org/reference/react/useContext) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + Context provides a way to pass data through the component tree without having to pass props down manually at every level. -In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree. +In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree. - [When to Use Context](#when-to-use-context) - [Before You Use Context](#before-you-use-context) @@ -80,7 +89,7 @@ function Page(props) { With this change, only the top-most Page component needs to know about the `Link` and `Avatar` components' use of `user` and `avatarSize`. -This *inversion of control* can make your code cleaner in many cases by reducing the amount of props you need to pass through your application and giving more control to the root components. However, this isn't the right choice in every case: moving more complexity higher in the tree makes those higher-level components more complicated and forces the lower-level components to be more flexible than you may want. +This *inversion of control* can make your code cleaner in many cases by reducing the amount of props you need to pass through your application and giving more control to the root components. Such inversion, however, isn't the right choice in every case; moving more complexity higher in the tree makes those higher-level components more complicated and forces the lower-level components to be more flexible than you may want. You're not limited to a single child for a component. You may pass multiple children, or even have multiple separate "slots" for children, [as documented here](/docs/composition-vs-inheritance.html#containment): @@ -118,7 +127,7 @@ const MyContext = React.createContext(defaultValue); Creates a Context object. When React renders a component that subscribes to this Context object it will read the current context value from the closest matching `Provider` above it in the tree. -The `defaultValue` argument is **only** used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing `undefined` as a Provider value does not cause consuming components to use `defaultValue`. +The `defaultValue` argument is **only** used when a component does not have a matching Provider above it in the tree. This default value can be helpful for testing components in isolation without wrapping them. Note: passing `undefined` as a Provider value does not cause consuming components to use `defaultValue`. ### `Context.Provider` {#contextprovider} @@ -128,7 +137,7 @@ The `defaultValue` argument is **only** used when a component does not have a ma Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes. -Accepts a `value` prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree. +The Provider component accepts a `value` prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree. All consumers that are descendants of a Provider will re-render whenever the Provider's `value` prop changes. The propagation from Provider to its descendant consumers (including [`.contextType`](#classcontexttype) and [`useContext`](/docs/hooks-reference.html#usecontext)) is not subject to the `shouldComponentUpdate` method, so the consumer is updated even when an ancestor component skips an update. @@ -162,7 +171,7 @@ class MyClass extends React.Component { MyClass.contextType = MyContext; ``` -The `contextType` property on a class can be assigned a Context object created by [`React.createContext()`](#reactcreatecontext). This lets you consume the nearest current value of that Context type using `this.context`. You can reference this in any of the lifecycle methods including the render function. +The `contextType` property on a class can be assigned a Context object created by [`React.createContext()`](#reactcreatecontext). Using this property lets you consume the nearest current value of that Context type using `this.context`. You can reference this in any of the lifecycle methods including the render function. > Note: > @@ -189,7 +198,7 @@ class MyClass extends React.Component { </MyContext.Consumer> ``` -A React component that subscribes to context changes. This lets you subscribe to a context within a [function component](/docs/components-and-props.html#function-and-class-components). +A React component that subscribes to context changes. Using this component lets you subscribe to a context within a [function component](/docs/components-and-props.html#function-and-class-components). Requires a [function as a child](/docs/render-props.html#using-props-other-than-render). The function receives the current context value and returns a React node. The `value` argument passed to the function will be equal to the `value` prop of the closest Provider for this context above in the tree. If there is no Provider for this context above, the `value` argument will be equal to the `defaultValue` that was passed to `createContext()`. diff --git a/content/docs/create-a-new-react-app.md b/content/docs/create-a-new-react-app.md index 80b051150..065f32045 100644 --- a/content/docs/create-a-new-react-app.md +++ b/content/docs/create-a-new-react-app.md @@ -39,7 +39,11 @@ next: cdn-links.html Το [Create React App](https://github.com/facebookincubator/create-react-app) είναι ένα άνετο περιβάλλον για την **εκμάθηση του React** και αποτελεί τον καλύτερο τρόπο για να ξεκινήσετε τη δημιουργία **μιας νέας εφαρμογής [single-page](/docs/glossary.html#single-page-application)** στο React. +<<<<<<< HEAD Ρυθμίζει το περιβάλλον προγραμματισμού έτσι ώστε να μπορείτε να χρησιμοποιήσετε τα πιο πρόσφατα features της JavaScript, παρέχει μια θετική εμπειρία για τον προγραμματιστή και βελτιστοποιεί την εφαρμογή σας για παραγωγή. Θα χρειαστεί να έχετε στο μηχάνημά σας τα [Node >= 8.10 και npm >= 5.6](https://nodejs.org/en/). Για τη δημιουργία ενός project, εκτελέστε: +======= +It sets up your development environment so that you can use the latest JavaScript features, provides a nice developer experience, and optimizes your app for production. You’ll need to have [Node >= 14.0.0 and npm >= 5.6](https://nodejs.org/en/) on your machine. To create a project, run: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```bash npx create-react-app my-app @@ -73,7 +77,13 @@ npm start - Το **[Neutrino](https://neutrinojs.org/)** συνδυάζει τη δύναμη του [webpack](https://webpack.js.org/) με την απλότητα των presets και περιλαμβάνει ένα preset για [εφαρμογές React](https://neutrinojs.org/packages/react/) και [React components](https://neutrinojs.org/packages/react-components/). +<<<<<<< HEAD - Το **[Parcel](https://parceljs.org/)** είναι ένα γρήγορο bundler διαδικτυακών εφαρμογών που δεν απαιτεί καμία διαμόρφωση και [λειτουργεί με το React](https://parceljs.org/recipes.html#react). +======= +- **[Nx](https://nx.dev/react)** is a toolkit for full-stack monorepo development, with built-in support for React, Next.js, [Express](https://expressjs.com/), and more. + +- **[Parcel](https://parceljs.org/)** is a fast, zero configuration web application bundler that [works with React](https://parceljs.org/recipes/react/). +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a - Το **[Razzle](https://github.com/jaredpalmer/razzle)** είναι ένα server-rendering framework που δεν απαιτεί καμία διαμόρφωση, αλλά προσφέρει μεγαλύτερη ευελιξία από το Next.js. diff --git a/content/docs/design-principles.md b/content/docs/design-principles.md index 9e10f39d5..97a6ce91f 100644 --- a/content/docs/design-principles.md +++ b/content/docs/design-principles.md @@ -92,7 +92,7 @@ There is an internal joke in the team that React should have been called "Schedu Providing a good developer experience is important to us. -For example, we maintain [React DevTools](https://github.com/facebook/react-devtools) which let you inspect the React component tree in Chrome and Firefox. We have heard that it brings a big productivity boost both to the Facebook engineers and to the community. +For example, we maintain [React DevTools](https://github.com/facebook/react/tree/main/packages/react-devtools) which let you inspect the React component tree in Chrome and Firefox. We have heard that it brings a big productivity boost both to the Facebook engineers and to the community. We also try to go an extra mile to provide helpful developer warnings. For example, React warns you in development if you nest tags in a way that the browser doesn't understand, or if you make a common typo in the API. Developer warnings and the related checks are the main reason why the development version of React is slower than the production version. @@ -126,7 +126,7 @@ We do, however, provide some global configuration on the build level. For exampl ### Beyond the DOM {#beyond-the-dom} -We see the value of React in the way it allows us to write components that have fewer bugs and compose together well. DOM is the original rendering target for React but [React Native](https://facebook.github.io/react-native/) is just as important both to Facebook and the community. +We see the value of React in the way it allows us to write components that have fewer bugs and compose together well. DOM is the original rendering target for React but [React Native](https://reactnative.dev/) is just as important both to Facebook and the community. Being renderer-agnostic is an important design constraint of React. It adds some overhead in the internal representations. On the other hand, any improvements to the core translate across platforms. diff --git a/content/docs/error-boundaries.md b/content/docs/error-boundaries.md index d8ff0314b..a3f4b61f0 100644 --- a/content/docs/error-boundaries.md +++ b/content/docs/error-boundaries.md @@ -62,16 +62,16 @@ Then you can use it as a regular component: Error boundaries work like a JavaScript `catch {}` block, but for components. Only class components can be error boundaries. In practice, most of the time you’ll want to declare an error boundary component once and use it throughout your application. -Note that **error boundaries only catch errors in the components below them in the tree**. An error boundary can’t catch an error within itself. If an error boundary fails trying to render the error message, the error will propagate to the closest error boundary above it. This, too, is similar to how catch {} block works in JavaScript. +Note that **error boundaries only catch errors in the components below them in the tree**. An error boundary can’t catch an error within itself. If an error boundary fails trying to render the error message, the error will propagate to the closest error boundary above it. This, too, is similar to how the `catch {}` block works in JavaScript. ## Live Demo {#live-demo} -Check out [this example of declaring and using an error boundary](https://codepen.io/gaearon/pen/wqvxGa?editors=0010) with [React 16](/blog/2017/09/26/react-v16.0.html). +Check out [this example of declaring and using an error boundary](https://codepen.io/gaearon/pen/wqvxGa?editors=0010). ## Where to Place Error Boundaries {#where-to-place-error-boundaries} -The granularity of error boundaries is up to you. You may wrap top-level route components to display a “Something went wrong” message to the user, just like server-side frameworks often handle crashes. You may also wrap individual widgets in an error boundary to protect them from crashing the rest of the application. +The granularity of error boundaries is up to you. You may wrap top-level route components to display a “Something went wrong” message to the user, just like how server-side frameworks often handle crashes. You may also wrap individual widgets in an error boundary to protect them from crashing the rest of the application. ## New Behavior for Uncaught Errors {#new-behavior-for-uncaught-errors} @@ -97,7 +97,7 @@ You can also see the filenames and line numbers in the component stack trace. Th <img src="../images/docs/error-boundaries-stack-trace-line-numbers.png" style="max-width:100%" alt="Error caught by Error Boundary component with line numbers"> -If you don’t use Create React App, you can add [this plugin](https://www.npmjs.com/package/babel-plugin-transform-react-jsx-source) manually to your Babel configuration. Note that it’s intended only for development and **must be disabled in production**. +If you don’t use Create React App, you can add [this plugin](https://www.npmjs.com/package/@babel/plugin-transform-react-jsx-source) manually to your Babel configuration. Note that it’s intended only for development and **must be disabled in production**. > Note > @@ -130,7 +130,7 @@ Error boundaries **do not** catch errors inside event handlers. React doesn't need error boundaries to recover from errors in event handlers. Unlike the render method and lifecycle methods, the event handlers don't happen during rendering. So if they throw, React still knows what to display on the screen. -If you need to catch an error inside event handler, use the regular JavaScript `try` / `catch` statement: +If you need to catch an error inside an event handler, use the regular JavaScript `try` / `catch` statement: ```js{9-13,17-20} class MyComponent extends React.Component { diff --git a/content/docs/faq-ajax.md b/content/docs/faq-ajax.md index 102e1c07e..a421ea82c 100644 --- a/content/docs/faq-ajax.md +++ b/content/docs/faq-ajax.md @@ -72,7 +72,7 @@ class MyComponent extends React.Component { return ( <ul> {items.map(item => ( - <li key={item.name}> + <li key={item.id}> {item.name} {item.price} </li> ))} @@ -82,3 +82,50 @@ class MyComponent extends React.Component { } } ``` + +Here is the equivalent with [Hooks](https://reactjs.org/docs/hooks-intro.html): + +```js +function MyComponent() { + const [error, setError] = useState(null); + const [isLoaded, setIsLoaded] = useState(false); + const [items, setItems] = useState([]); + + // Note: the empty deps array [] means + // this useEffect will run once + // similar to componentDidMount() + useEffect(() => { + fetch("https://api.example.com/items") + .then(res => res.json()) + .then( + (result) => { + setIsLoaded(true); + setItems(result); + }, + // Note: it's important to handle errors here + // instead of a catch() block so that we don't swallow + // exceptions from actual bugs in components. + (error) => { + setIsLoaded(true); + setError(error); + } + ) + }, []) + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <div>Loading...</div>; + } else { + return ( + <ul> + {items.map(item => ( + <li key={item.id}> + {item.name} {item.price} + </li> + ))} + </ul> + ); + } +} +``` diff --git a/content/docs/faq-functions.md b/content/docs/faq-functions.md index f03b2ba76..807d5fc60 100644 --- a/content/docs/faq-functions.md +++ b/content/docs/faq-functions.md @@ -37,14 +37,13 @@ class Foo extends Component { } ``` -#### Class Properties (Stage 3 Proposal) {#class-properties-stage-3-proposal} +#### Class Properties (ES2022) {#class-properties-es2022} ```jsx class Foo extends Component { - // Note: this syntax is experimental and not standardized yet. handleClick = () => { console.log('Click happened'); - } + }; render() { return <button onClick={this.handleClick}>Click Me</button>; } @@ -152,7 +151,6 @@ const A = 65 // ASCII character code class Alphabet extends React.Component { constructor(props) { super(props); - this.handleClick = this.handleClick.bind(this); this.state = { justClicked: null, letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i)) @@ -290,9 +288,6 @@ class Searchbox extends React.Component { } handleChange(e) { - // React pools events, so we read the value before debounce. - // Alternately we could call `event.persist()` and pass the entire event. - // For more info see reactjs.org/docs/events.html#event-pooling this.emitChangeDebounced(e.target.value); } diff --git a/content/docs/faq-structure.md b/content/docs/faq-structure.md index 3031cabf0..74d67fd56 100644 --- a/content/docs/faq-structure.md +++ b/content/docs/faq-structure.md @@ -12,7 +12,11 @@ category: FAQ #### Ομαδοποίηση κατά χαρακτηριστικά ή διαδρομές {#grouping-by-features-or-routes} +<<<<<<< HEAD Ένας κοινός τρόπος για τη δομή των project είναι να τοποθετήσετε τα CSS, JS και tests μαζί σε φακέλους ομαδοποιημένους ανά χαρακτηριστικό ή διαδρομή. +======= +One common way to structure projects is to locate CSS, JS, and tests together inside folders grouped by feature or route. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ``` common/ diff --git a/content/docs/faq-styling.md b/content/docs/faq-styling.md index 7436c3b07..30936a0ec 100644 --- a/content/docs/faq-styling.md +++ b/content/docs/faq-styling.md @@ -42,10 +42,10 @@ CSS classes are generally better for performance than inline styles. ### What is CSS-in-JS? {#what-is-css-in-js} -"CSS-in-JS" refers to a pattern where CSS is composed using JavaScript instead of defined in external files. Read a comparison of CSS-in-JS libraries [here](https://github.com/MicheleBertoli/css-in-js). +"CSS-in-JS" refers to a pattern where CSS is composed using JavaScript instead of defined in external files. _Note that this functionality is not a part of React, but provided by third-party libraries._ React does not have an opinion about how styles are defined; if in doubt, a good starting point is to define your styles in a separate `*.css` file as usual and refer to them using [`className`](/docs/dom-elements.html#classname). ### Can I do animations in React? {#can-i-do-animations-in-react} -React can be used to power animations. See [React Transition Group](https://reactcommunity.org/react-transition-group/) and [React Motion](https://github.com/chenglou/react-motion) or [React Spring](https://github.com/react-spring/react-spring), for example. +React can be used to power animations. See [React Transition Group](https://reactcommunity.org/react-transition-group/), [React Motion](https://github.com/chenglou/react-motion), [React Spring](https://github.com/react-spring/react-spring), or [Framer Motion](https://framer.com/motion), for example. diff --git a/content/docs/faq-versioning.md b/content/docs/faq-versioning.md index c32677eb7..d273b1b7c 100644 --- a/content/docs/faq-versioning.md +++ b/content/docs/faq-versioning.md @@ -22,7 +22,7 @@ Minor releases are the most common type of release. ### Breaking Changes {#breaking-changes} -Breaking changes are inconvenient for everyone, so we try to minimize the number of major releases – for example, React 15 was released in April 2016 and React 16 was released in September 2017; React 17 isn't expected until sometime in 2020. +Breaking changes are inconvenient for everyone, so we try to minimize the number of major releases – for example, React 15 was released in April 2016 and React 16 was released in September 2017, and React 17 was released in October 2020. Instead, we release new features in minor versions. That means that minor releases are often more interesting and compelling than majors, despite their unassuming name. diff --git a/content/docs/forms.md b/content/docs/forms.md index f537c2299..af2bce8f9 100644 --- a/content/docs/forms.md +++ b/content/docs/forms.md @@ -9,7 +9,21 @@ redirect_from: - docs/forms-zh-CN.html --- +<<<<<<< HEAD Tα HTML form elements λειτουργούν λίγο διαφορετικά από τα υπόλοιπα DOM elements στο React, λόγω του ότι τα form elements συνήθως διατηρούν εσωτερικό state. Για παράδειγμα, η επόμενη φόρμα σε απλή HTML δέχεται απλά ένα όνομα: +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [`<input>`](https://beta.reactjs.org/reference/react-dom/components/input) +> - [`<select>`](https://beta.reactjs.org/reference/react-dom/components/select) +> - [`<textarea>`](https://beta.reactjs.org/reference/react-dom/components/textarea) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +HTML form elements work a bit differently from other DOM elements in React, because form elements naturally keep some internal state. For example, this form in plain HTML accepts a single name: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```html <form> @@ -31,7 +45,7 @@ Tα HTML form elements λειτουργούν λίγο διαφορετικά α Για παράδειγμα, εάν θέλουμε να κάνουμε το προηγούμενο παράδειγμα να καταγράψει το όνομα όταν υποβληθεί, μπορούμε να γράψουμε τη φόρμα ως controlled component: -```javascript{4,10-12,24} +```javascript{4,10-12,21,24} class NameForm extends React.Component { constructor(props) { super(props); @@ -68,6 +82,7 @@ class NameForm extends React.Component { Καθώς το `value` attribute έχει οριστεί στο form element, η εμφανιζόμενη τιμή θα είναι πάντα το `this.state.value`, καθιστώντας το React state ως την πηγή της αλήθειας. Καθώς το `handleChange` τρέχει σε κάθε πάτημα κάθε πλήκτρου, έτσι ώστε να ενημερωθεί το React state, η εμφανιζόμενη τιμή θα ενημερώνεται όσο ο χρήστης πληκτρολογεί. +<<<<<<< HEAD Με ένα controlled component, κάθε μεταβολή του state θα συνδέεται και με μία function διαχείρισης του. Με αυτό τον τρόπο είναι πολύ απλό να αλλαχτεί ή να ελέγχθει ότι έχει εισάγει ο χρήστης στη φόρμα. Για παράδειγμα, εάν θέλαμε όλα τα ονόματα της φόρμας να είναι σε κεφαλαία μορφή, θα γράφαμε την `handleChange` ως εξής: ```javascript{2} @@ -75,6 +90,9 @@ handleChange(event) { this.setState({value: event.target.value.toUpperCase()}); } ``` +======= +With a controlled component, the input's value is always driven by the React state. While this means you have to type a bit more code, you can now pass the value to other UI elements too, or reset it from other event handlers. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ## The textarea Tag {#the-textarea-tag} @@ -220,7 +238,7 @@ class Reservation extends React.Component { handleInputChange(event) { const target = event.target; - const value = target.name === 'isGoing' ? target.checked : target.value; + const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name; this.setState({ @@ -276,15 +294,19 @@ this.setState(partialState); ## Controlled Input Null Τιμή{#controlled-input-null-value} +<<<<<<< HEAD Ο καθορισμός της τιμής prop σε ένα [controlled component](/docs/forms.html#controlled-components) εμποδίζει τον χρήστη να αλλάξει το input εκτός αν το επιθυμεί. Εάν έχεις ορίσει ένα `value` αλλά το input είναι ακόμη επεξεργάσιμο, ενδέχεται να ορίσετε τυχαία το `value` σε `undefined` ή `null`. +======= +Specifying the `value` prop on a [controlled component](/docs/forms.html#controlled-components) prevents the user from changing the input unless you desire so. If you've specified a `value` but the input is still editable, you may have accidentally set `value` to `undefined` or `null`. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Ο ακόλουθος κώδικας το καταδεικνύει αυτό. (Το input είναι κλειδωμένο στην αρχή αλλά γίνεται επεξεργάσιμο μετά από μια μικρή καθυστέρηση.) ```javascript -ReactDOM.render(<input value="hi" />, mountNode); +ReactDOM.createRoot(mountNode).render(<input value="hi" />); setTimeout(function() { - ReactDOM.render(<input value={null} />, mountNode); + ReactDOM.createRoot(mountNode).render(<input value={null} />); }, 1000); ``` diff --git a/content/docs/forwarding-refs.md b/content/docs/forwarding-refs.md index 3318d8499..0578e49d8 100644 --- a/content/docs/forwarding-refs.md +++ b/content/docs/forwarding-refs.md @@ -4,6 +4,15 @@ title: Forwarding Refs permalink: docs/forwarding-refs.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Manipulating the DOM with Refs](https://beta.reactjs.org/learn/manipulating-the-dom-with-refs) +> - [`forwardRef`](https://beta.reactjs.org/reference/react/forwardRef) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + Ref forwarding is a technique for automatically passing a [ref](/docs/refs-and-the-dom.html) through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries. The most common scenarios are described below. ## Forwarding refs to DOM components {#forwarding-refs-to-dom-components} diff --git a/content/docs/getting-started.md b/content/docs/getting-started.md index a813af511..b64b85363 100644 --- a/content/docs/getting-started.md +++ b/content/docs/getting-started.md @@ -18,6 +18,12 @@ redirect_from: - "docs/environments.html" --- +> Try the new React documentation. +> +> The new [Quick Start](https://beta.reactjs.org/learn) teaches modern React and includes live examples. +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + This page is an overview of the React documentation and related resources. **React** is a JavaScript library for building user interfaces. Learn what React is all about on [our homepage](/) or [in the tutorial](/tutorial/tutorial.html). @@ -36,9 +42,9 @@ React has been designed from the start for gradual adoption, and **you can use a ### Online Playgrounds {#online-playgrounds} -If you're interested in playing around with React, you can use an online code playground. Try a Hello World template on [CodePen](codepen://hello-world), [CodeSandbox](https://codesandbox.io/s/new), [Glitch](https://glitch.com/edit/#!/remix/starter-react-template), or [Stackblitz](https://stackblitz.com/fork/react). +If you're interested in playing around with React, you can use an online code playground. Try a Hello World template on [CodePen](codepen://hello-world), [CodeSandbox](https://codesandbox.io/s/new), or [Stackblitz](https://stackblitz.com/fork/react). -If you prefer to use your own text editor, you can also [download this HTML file](https://raw.githubusercontent.com/reactjs/reactjs.org/master/static/html/single-file-example.html), edit it, and open it from the local filesystem in your browser. It does a slow runtime code transformation, so we'd only recommend using this for simple demos. +If you prefer to use your own text editor, you can also [download this HTML file](https://raw.githubusercontent.com/reactjs/reactjs.org/main/static/html/single-file-example.html), edit it, and open it from the local filesystem in your browser. It does a slow runtime code transformation, so we'd only recommend using this for simple demos. ### Add React to a Website {#add-react-to-a-website} @@ -115,7 +121,7 @@ The [React blog](/blog/) is the official source for the updates from the React t You can also follow the [@reactjs account](https://twitter.com/reactjs) on Twitter, but you won't miss anything essential if you only read the blog. -Not every React release deserves its own blog post, but you can find a detailed changelog for every release in the [`CHANGELOG.md` file in the React repository](https://github.com/facebook/react/blob/master/CHANGELOG.md), as well as on the [Releases](https://github.com/facebook/react/releases) page. +Not every React release deserves its own blog post, but you can find a detailed changelog for every release in the [`CHANGELOG.md` file in the React repository](https://github.com/facebook/react/blob/main/CHANGELOG.md), as well as on the [Releases](https://github.com/facebook/react/releases) page. ## Versioned Documentation {#versioned-documentation} diff --git a/content/docs/handling-events.md b/content/docs/handling-events.md index 454bc1eff..df2b9f302 100644 --- a/content/docs/handling-events.md +++ b/content/docs/handling-events.md @@ -8,7 +8,20 @@ redirect_from: - "docs/events-ko-KR.html" --- +<<<<<<< HEAD Η διαχείριση των events με τα React elements είναι παρόμοια με τη διαχείριση των events στα DOM elements. Υπάρχουν μερικές διαφορές στο συντακτικό: +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Responding to Events](https://beta.reactjs.org/learn/responding-to-events) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + +Handling events with React elements is very similar to handling events on DOM elements. There are some syntax differences: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a * Στο React τα events ονομάζονται χρησιμοποιώντας camelCase, αντί για lowercase. * Με το JSX περνάτε μια συνάρτηση ως event handler, αντί για ένα string. @@ -29,32 +42,40 @@ redirect_from: </button> ``` +<<<<<<< HEAD Ακόμη μια διαφορά είναι ότι δεν μπορείτε να επιστρέψετε `false` για να σταματήσετε την προκαθορισμένη συμπεριφορά στο React. Θα πρέπει να καλέσετε τη μέθοδο `preventDefault`. Για παράδειγμα, με απλό HTML, για να σταματήσετε τη προκαθορισμένη συμπεριφορά του link, που ανοίγει μια νέα σελίδα, μπορείτε να γράψετε: +======= +Another difference is that you cannot return `false` to prevent default behavior in React. You must call `preventDefault` explicitly. For example, with plain HTML, to prevent the default form behavior of submitting, you can write: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```html -<a href="#" onclick="console.log('The link was clicked.'); return false"> - Click me -</a> +<form onsubmit="console.log('You clicked submit.'); return false"> + <button type="submit">Submit</button> +</form> ``` Στο React αυτό θα μπορούσε να είναι: -```js{2-5,8} -function ActionLink() { - function handleClick(e) { +```js{3} +function Form() { + function handleSubmit(e) { e.preventDefault(); - console.log('The link was clicked.'); + console.log('You clicked submit.'); } return ( - <a href="#" onClick={handleClick}> - Click me - </a> + <form onSubmit={handleSubmit}> + <button type="submit">Submit</button> + </form> ); } ``` +<<<<<<< HEAD Εδώ το `e` είναι ένα synthetic event. Το React ορίζει αυτά τα synthetic events σύμφωνα με το [W3C spec](https://www.w3.org/TR/DOM-Level-3-Events/), οπότε δεν χρειάζεται να ανησυχείτε για τη συμβατότητα μεταξύ των browsers. Δείτε τον οδηγό αναφοράς του [`SyntheticEvent`](/docs/events.html) για να μάθετε περισσότερα. +======= +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. React events do not work exactly the same as native events. See the [`SyntheticEvent`](/docs/events.html) reference guide to learn more. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Όταν χρησιμοποιείτε το React, γενικά δεν θα χρειαστεί να καλείτε το `addEventListener` για να προσθέσετε listeners σε ένα DOM element μετά τη δημιουργία του. Αντί για αυτό, απλά προσθέστε ένα listener όταν το element γίνεται αρχικά rendered. @@ -71,8 +92,8 @@ class Toggle extends React.Component { } handleClick() { - this.setState(state => ({ - isToggleOn: !state.isToggleOn + this.setState(prevState => ({ + isToggleOn: !prevState.isToggleOn })); } @@ -84,11 +105,6 @@ class Toggle extends React.Component { ); } } - -ReactDOM.render( - <Toggle />, - document.getElementById('root') -); ``` [**Δοκιμάστε το στο Codepen**](https://codepen.io/gaearon/pen/xEmzGg?editors=0010) @@ -97,15 +113,23 @@ ReactDOM.render( Αυτή η συμπεριφορά δεν σχετίζεται με το React. Αποτελεί μέρος του [πως λειτουργούν οι συναρτήσεις στη JavaScript](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/). Γενικά, για να αναφερθείτε σε μια μέθοδο χωρίς να την ακολουθούν `()` όπως το `onClick={this.handleClick}`, θα πρέπει να κάνετε bind αυτή τη μέθοδο. +<<<<<<< HEAD Αν το να καλείτε το `bind` είναι ενοχλητικό, υπάρχουν δύο τρόποι που μπορείτε να το αποφύγετε. Αν χρησιμοποιείτε το πειραματικό [public class fields syntax](https://babeljs.io/docs/plugins/transform-class-properties/), μπορείτε να χρησιμοποιήσετε πεδία κλάσεων για να κάνετε bind τα callbacks: ```js{2-6} class LoggingButton extends React.Component { // Αυτή η σύνταξη εξασφαλίζει ότι το `this` είναι bound μέσα στο handleClick. // Προσοχή: αυτό το συντακτικό είναι *πειραματικό* +======= +If calling `bind` annoys you, there are two ways you can get around this. You can use [public class fields syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields#public_instance_fields) to correctly bind callbacks: + +```js{2-6} +class LoggingButton extends React.Component { + // This syntax ensures `this` is bound within handleClick. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a handleClick = () => { console.log('this is:', this); - } + }; render() { return ( @@ -130,7 +154,7 @@ class LoggingButton extends React.Component { render() { // Αυτή η σύνταξη εξασφαλίζει ότι το `this` είναι bound στο handleClick return ( - <button onClick={(e) => this.handleClick(e)}> + <button onClick={() => this.handleClick()}> Click me </button> ); diff --git a/content/docs/hello-world.md b/content/docs/hello-world.md index 3215a81d4..e4dbedb8c 100644 --- a/content/docs/hello-world.md +++ b/content/docs/hello-world.md @@ -8,16 +8,14 @@ next: introducing-jsx.html Το μικρότερο παράδειγμα React μοιάζει με αυτό: -```js -ReactDOM.render( - <h1>Hello, world!</h1>, - document.getElementById('root') -); +```jsx +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<h1>Hello, world!</h1>); ``` Εμφανίζει μια επικεφαλίδα λέγοντας "Hello, world!" στη σελίδα. -[](codepen://hello-world) +**[Try it on CodePen](https://codepen.io/gaearon/pen/rrpgNB?editors=1010)** Κάντε κλικ στον παραπάνω σύνδεσμο για να ανοίξετε έναν online editor. Μη διστάσετε να κάνετε κάποιες αλλαγές και δείτε πώς επηρεάζουν την έξοδο. Οι περισσότερες σελίδες σε αυτόν τον οδηγό θα έχουν επεξεργάσιμα παραδείγματα όπως αυτή. @@ -39,7 +37,11 @@ ReactDOM.render( >Σημείωση > +<<<<<<< HEAD >Αυτός ο οδηγός περιστασιακά χρησιμοποιεί ορισμένες από τις νεότερες συντάξεις JavaScript στα παραδείγματα. Εάν δεν έχετε δουλέψει με JavaScript τα τελευταία χρόνια, [αυτά τα τρία σημεία](https://gist.github.com/gaearon/683e676101005de0add59e8bb345340c) θα πρέπει να σας βοηθήσουν περισσότερο. +======= +>This guide occasionally uses some newer JavaScript syntax in the examples. If you haven't worked with JavaScript in the last few years, [these three points](https://gist.github.com/gaearon/683e676101005de0add59e8bb345340c) should get you most of the way. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ## Ας Ξεκινήσουμε! {#lets-get-started} diff --git a/content/docs/higher-order-components.md b/content/docs/higher-order-components.md index b10392c9b..9e56b7dea 100644 --- a/content/docs/higher-order-components.md +++ b/content/docs/higher-order-components.md @@ -14,7 +14,7 @@ const EnhancedComponent = higherOrderComponent(WrappedComponent); Whereas a component transforms props into UI, a higher-order component transforms a component into another component. -HOCs are common in third-party React libraries, such as Redux's [`connect`](https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md#connect) and Relay's [`createFragmentContainer`](http://facebook.github.io/relay/docs/en/fragment-container.html). +HOCs are common in third-party React libraries, such as Redux's [`connect`](https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md#connect) and Relay's [`createFragmentContainer`](https://relay.dev/docs/v10.1.3/fragment-container/#createfragmentcontainer). In this document, we'll discuss why higher-order components are useful, and how to write your own. @@ -297,7 +297,7 @@ The `compose` utility function is provided by many third-party libraries includi ## Convention: Wrap the Display Name for Easy Debugging {#convention-wrap-the-display-name-for-easy-debugging} -The container components created by HOCs show up in the [React Developer Tools](https://github.com/facebook/react-devtools) like any other component. To ease debugging, choose a display name that communicates that it's the result of a HOC. +The container components created by HOCs show up in the [React Developer Tools](https://github.com/facebook/react/tree/main/packages/react-devtools) like any other component. To ease debugging, choose a display name that communicates that it's the result of a HOC. The most common technique is to wrap the display name of the wrapped component. So if your higher-order component is named `withSubscription`, and the wrapped component's display name is `CommentList`, use the display name `WithSubscription(CommentList)`: @@ -320,7 +320,7 @@ Higher-order components come with a few caveats that aren't immediately obvious ### Don't Use HOCs Inside the render Method {#dont-use-hocs-inside-the-render-method} -React's diffing algorithm (called reconciliation) uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from `render` is identical (`===`) to the component from the previous render, React recursively updates the subtree by diffing it with the new one. If they're not equal, the previous subtree is unmounted completely. +React's diffing algorithm (called [Reconciliation](/docs/reconciliation.html)) uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from `render` is identical (`===`) to the component from the previous render, React recursively updates the subtree by diffing it with the new one. If they're not equal, the previous subtree is unmounted completely. Normally, you shouldn't need to think about this. But it matters for HOCs because it means you can't apply a HOC to a component within the render method of a component: diff --git a/content/docs/hooks-custom.md b/content/docs/hooks-custom.md index 6def0773e..6ce910eb4 100644 --- a/content/docs/hooks-custom.md +++ b/content/docs/hooks-custom.md @@ -6,6 +6,14 @@ next: hooks-reference.html prev: hooks-rules.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Reusing Logic with Custom Hooks](https://beta.reactjs.org/learn/reusing-logic-with-custom-hooks) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + *Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. Building your own Hooks lets you extract component logic into reusable functions. diff --git a/content/docs/hooks-effect.md b/content/docs/hooks-effect.md index 86b3b6649..0c8b1df6e 100644 --- a/content/docs/hooks-effect.md +++ b/content/docs/hooks-effect.md @@ -6,6 +6,16 @@ next: hooks-rules.html prev: hooks-state.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Synchronizing with Effects](https://beta.reactjs.org/learn/synchronizing-with-effects) +> - [You Might Not Need an Effect](https://beta.reactjs.org/learn/you-might-not-need-an-effect) +> - [`useEffect`](https://beta.reactjs.org/reference/react/useEffect) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + *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: diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index b7448c7a0..bb88abc99 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -71,7 +71,7 @@ Starting with 16.8.0, React includes a stable implementation of React Hooks for: 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 0.59](https://facebook.github.io/react-native/blog/2019/03/12/releasing-react-native-059) and above support Hooks. +[React Native 0.59](https://reactnative.dev/blog/2019/03/12/releasing-react-native-059) and above support Hooks. ### Do I need to rewrite all my class components? {#do-i-need-to-rewrite-all-my-class-components} @@ -97,8 +97,6 @@ You can't use Hooks *inside* a class component, but you can definitely mix class 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`, `getDerivedStateFromError` and `componentDidCatch` lifecycles yet, but we plan to add them soon. -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? {#do-hooks-replace-render-props-and-higher-order-components} Often, render props and higher-order components render only a single child. We think Hooks are a simpler way to serve this use case. There is still a place for both patterns (for example, a virtual scroller component might have a `renderItem` prop, or a visual container component might have its own DOM structure). But in most cases, Hooks will be sufficient and can help reduce nesting in your tree. @@ -150,7 +148,7 @@ We'll test it using React DOM. To make sure that the behavior matches what happe ```js{3,20-22,29-31} import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { act } from 'react-dom/test-utils'; import Counter from './Counter'; @@ -169,7 +167,7 @@ afterEach(() => { it('can render and update a counter', () => { // Test first render and effect act(() => { - ReactDOM.render(<Counter />, container); + ReactDOM.createRoot(container).render(<Counter />); }); const button = container.querySelector('button'); const label = container.querySelector('p'); @@ -333,54 +331,22 @@ This is a rare use case. If you need it, you can [use a mutable ref](#is-there-s ### How to get the previous props or state? {#how-to-get-the-previous-props-or-state} -Currently, you can do it manually [with a ref](#is-there-something-like-instance-variables): - -```js{6,8} -function Counter() { - const [count, setCount] = useState(0); - - const prevCountRef = useRef(); - useEffect(() => { - prevCountRef.current = count; - }); - const prevCount = prevCountRef.current; - - return <h1>Now: {count}, before: {prevCount}</h1>; -} -``` - -This might be a bit convoluted but you can extract it into a custom Hook: +There are two cases in which you might want to get previous props or state. -```js{3,7} -function Counter() { - const [count, setCount] = useState(0); - const prevCount = usePrevious(count); - return <h1>Now: {count}, before: {prevCount}</h1>; -} +Sometimes, you need previous props to **clean up an effect.** For example, you might have an effect that subscribes to a socket based on the `userId` prop. If the `userId` prop changes, you want to unsubscribe from the _previous_ `userId` and subscribe to the _next_ one. You don't need to do anything special for this to work: -function usePrevious(value) { - const ref = useRef(); - useEffect(() => { - ref.current = value; - }); - return ref.current; -} +```js +useEffect(() => { + ChatAPI.subscribeToSocket(props.userId); + return () => ChatAPI.unsubscribeFromSocket(props.userId); +}, [props.userId]); ``` -Note how this would work for props, state, or any other calculated value. +In the above example, if `userId` changes from `3` to `4`, `ChatAPI.unsubscribeFromSocket(3)` will run first, and then `ChatAPI.subscribeToSocket(4)` will run. There is no need to get "previous" `userId` because the cleanup function will capture it in a closure. -```js{5} -function Counter() { - const [count, setCount] = useState(0); +Other times, you might need to **adjust state based on a change in props or other state**. This is rarely needed and is usually a sign you have some duplicate or redundant state. However, in the rare case that you need this pattern, you can [store previous state or props in state and update them during rendering](#how-do-i-implement-getderivedstatefromprops). - const calculation = count + 100; - const prevCalculation = usePrevious(calculation); - // ... -``` - -It's possible that in the future React will provide a `usePrevious` Hook out of the box since it's a relatively common use case. - -See also [the recommended pattern for derived state](#how-do-i-implement-getderivedstatefromprops). +We have previously suggested a custom Hook called `usePrevious` to hold the previous value. However, we've found that most use cases fall into the two patterns described above. If your use case is different, you can [hold a value in a ref](#is-there-something-like-instance-variables) and manually update it when needed. Avoid reading and updating refs during rendering because this makes your component's behavior difficult to predict and understand. ### Why am I seeing stale props or state inside my function? {#why-am-i-seeing-stale-props-or-state-inside-my-function} @@ -580,7 +546,7 @@ Depending on your use case, there are a few more options described below. Let's see why this matters. -If you specify a [list of dependencies](/docs/hooks-reference.html#conditionally-firing-an-effect) as the last argument to `useEffect`, `useMemo`, `useCallback`, or `useImperativeHandle`, it must include all values that are used inside the callback and participate in the React data flow. That includes props, state, and anything derived from them. +If you specify a [list of dependencies](/docs/hooks-reference.html#conditionally-firing-an-effect) as the last argument to `useEffect`, `useLayoutEffect`, `useMemo`, `useCallback`, or `useImperativeHandle`, it must include all values that are used inside the callback and participate in the React data flow. That includes props, state, and anything derived from them. It is **only** safe to omit a function from the dependency list if nothing in it (or the functions called by it) references props, state, or values derived from them. This example has a bug: @@ -589,7 +555,7 @@ function ProductPage({ productId }) { const [product, setProduct] = useState(null); async function fetchProduct() { - const response = await fetch('http://myapi/product' + productId); // Uses productId prop + const response = await fetch('http://myapi/product/' + productId); // Uses productId prop const json = await response.json(); setProduct(json); } @@ -610,7 +576,7 @@ function ProductPage({ productId }) { useEffect(() => { // By moving this function inside the effect, we can clearly see the values it uses. async function fetchProduct() { - const response = await fetch('http://myapi/product' + productId); + const response = await fetch('http://myapi/product/' + productId); const json = await response.json(); setProduct(json); } @@ -914,8 +880,6 @@ Note that you can still choose whether to pass the application *state* down as p >Note > >We recommend to [pass `dispatch` down in context](#how-to-avoid-passing-callbacks-down) rather than individual callbacks in props. The approach below is only mentioned here for completeness and as an escape hatch. -> ->Also note that this pattern might cause problems in the [concurrent mode](/blog/2018/03/27/update-on-async-rendering.html). We plan to provide more ergonomic alternatives in the future, but the safest solution right now is to always invalidate the callback if some value it depends on changes. In some rare cases you might need to memoize a callback with [`useCallback`](/docs/hooks-reference.html#usecallback) but the memoization doesn't work very well because the inner function has to be re-created too often. If the function you're memoizing is an event handler and isn't used during rendering, you can use [ref as an instance variable](#is-there-something-like-instance-variables), and save the last committed value into it manually: diff --git a/content/docs/hooks-intro.md b/content/docs/hooks-intro.md index a58d16d9e..b6c8f22a3 100644 --- a/content/docs/hooks-intro.md +++ b/content/docs/hooks-intro.md @@ -32,7 +32,7 @@ This new function `useState` is the first "Hook" we'll learn about, but this exa >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 supports Hooks since [the 0.59 release of React Native](https://facebook.github.io/react-native/blog/2019/03/12/releasing-react-native-059). +>React Native has supported Hooks since [the 0.59 release of React Native](https://reactnative.dev/blog/2019/03/12/releasing-react-native-059). ## Video Introduction {#video-introduction} @@ -80,7 +80,7 @@ We'll discuss this more in [Using the Effect Hook](/docs/hooks-effect.html#tip-u ### Classes confuse both people and machines {#classes-confuse-both-people-and-machines} -In addition to making code reuse and code organization more difficult, we've found that classes can be a large barrier to learning React. You have to understand how `this` works in JavaScript, which is very different from how it works in most languages. You have to remember to bind the event handlers. Without unstable [syntax proposals](https://babeljs.io/docs/en/babel-plugin-transform-class-properties/), the code is very verbose. People can understand props, state, and top-down data flow perfectly well but still struggle with classes. The distinction between function and class components in React and when to use each one leads to disagreements even between experienced React developers. +In addition to making code reuse and code organization more difficult, we've found that classes can be a large barrier to learning React. You have to understand how `this` works in JavaScript, which is very different from how it works in most languages. You have to remember to bind the event handlers. Without [ES2022 public class fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields#public_instance_fields), the code is very verbose. People can understand props, state, and top-down data flow perfectly well but still struggle with classes. The distinction between function and class components in React and when to use each one leads to disagreements even between experienced React developers. Additionally, React has been out for about five years, and we want to make sure it stays relevant in the next five years. As [Svelte](https://svelte.dev/), [Angular](https://angular.io/), [Glimmer](https://glimmerjs.com/), and others show, [ahead-of-time compilation](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) of components has a lot of future potential. Especially if it's not limited to templates. Recently, we've been experimenting with [component folding](https://github.com/facebook/react/issues/7323) using [Prepack](https://prepack.io/), and we've seen promising early results. However, we found that class components can encourage unintentional patterns that make these optimizations fall back to a slower path. Classes present issues for today's tools, too. For example, classes don't minify very well, and they make hot reloading flaky and unreliable. We want to present an API that makes it more likely for code to stay on the optimizable path. @@ -96,9 +96,9 @@ To solve these problems, **Hooks let you use more of React's features without cl We know that React developers are focused on shipping products and don't have time to look into every new API that's being released. Hooks are very new, and it might be better to wait for more examples and tutorials before considering learning or adopting them. -We also understand that the bar for adding a new primitive to React is extremely high. For curious readers, we have prepared a [detailed RFC](https://github.com/reactjs/rfcs/pull/68) that dives into motivation with more details, and provides extra perspective on the specific design decisions and related prior art. +We also understand that the bar for adding a new primitive to React is extremely high. For curious readers, we have prepared a [detailed RFC](https://github.com/reactjs/rfcs/pull/68) that dives into the motivation with more details, and provides extra perspective on the specific design decisions and related prior art. -**Crucially, Hooks work side-by-side with existing code so you can adopt them gradually.** There is no rush to migrate to Hooks. We recommend avoiding any "big rewrites", especially for existing, complex class components. It takes a bit of a mindshift to start "thinking in Hooks". In our experience, it's best to practice using Hooks in new and non-critical components first, and ensure that everybody on your team feels comfortable with them. After you give Hooks a try, please feel free to [send us feedback](https://github.com/facebook/react/issues/new), positive or negative. +**Crucially, Hooks work side-by-side with existing code so you can adopt them gradually.** There is no rush to migrate to Hooks. We recommend avoiding any "big rewrites", especially for existing, complex class components. It takes a bit of a mind shift to start "thinking in Hooks". In our experience, it's best to practice using Hooks in new and non-critical components first, and ensure that everybody on your team feels comfortable with them. After you give Hooks a try, please feel free to [send us feedback](https://github.com/facebook/react/issues/new), positive or negative. 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. diff --git a/content/docs/hooks-overview.md b/content/docs/hooks-overview.md index df958dc6d..d299a8b6d 100644 --- a/content/docs/hooks-overview.md +++ b/content/docs/hooks-overview.md @@ -229,7 +229,7 @@ function FriendListItem(props) { } ``` -The state of these components is completely independent. Hooks are a way to reuse *stateful logic*, not state itself. In fact, each *call* to a Hook has a completely isolated state -- so you can even use the same custom Hook twice in one component. +The state of each component is completely independent. Hooks are a way to reuse *stateful logic*, not state itself. In fact, each *call* to a Hook has a completely isolated state -- so you can even use the same custom Hook twice in one component. Custom Hooks are more of a convention than a feature. If a function's name starts with "`use`" and it calls other Hooks, we say it is a custom Hook. The `useSomething` naming convention is how our linter plugin is able to find bugs in the code using Hooks. diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md index 1aa14c7f2..79dcf143a 100644 --- a/content/docs/hooks-reference.md +++ b/content/docs/hooks-reference.md @@ -24,11 +24,21 @@ If you're new to Hooks, you might want to check out [the overview](/docs/hooks-o - [`useImperativeHandle`](#useimperativehandle) - [`useLayoutEffect`](#uselayouteffect) - [`useDebugValue`](#usedebugvalue) + - [`useDeferredValue`](#usedeferredvalue) + - [`useTransition`](#usetransition) + - [`useId`](#useid) +- [Library Hooks](#library-hooks) + - [`useSyncExternalStore`](#usesyncexternalstore) + - [`useInsertionEffect`](#useinsertioneffect) ## Basic Hooks {#basic-hooks} ### `useState` {#usestate} +> Try the new React documentation for [`useState`](https://beta.reactjs.org/reference/react/useState). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```js const [state, setState] = useState(initialState); ``` @@ -76,6 +86,7 @@ If your update function returns the exact same value as the current state, the s > Unlike the `setState` method found in class components, `useState` does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax: > > ```js +> const [state, setState] = useState({}); > setState(prevState => { > // Object.assign would also work > return {...prevState, ...updatedValues}; @@ -101,8 +112,20 @@ If you update a State Hook to the same value as the current state, React will ba Note that React may still need to render that specific component again before bailing out. That shouldn't be a concern because React won't unnecessarily go "deeper" into the tree. If you're doing expensive calculations while rendering, you can optimize them with `useMemo`. +#### Batching of state updates {#batching-of-state-updates} + +React may group several state updates into a single re-render to improve performance. Normally, this improves performance and shouldn't affect your application's behavior. + +Before React 18, only updates inside React event handlers were batched. Starting with React 18, [batching is enabled for all updates by default](/blog/2022/03/08/react-18-upgrade-guide.html#automatic-batching). Note that React makes sure that updates from several *different* user-initiated events -- for example, clicking a button twice -- are always processed separately and do not get batched. This prevents logical mistakes. + +In the rare case that you need to force the DOM update to be applied synchronously, you may wrap it in [`flushSync`](/docs/react-dom.html#flushsync). However, this can hurt performance so do this only where needed. + ### `useEffect` {#useeffect} +> Try the new React documentation for [`useEffect`](https://beta.reactjs.org/reference/react/useEffect). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```js useEffect(didUpdate); ``` @@ -137,7 +160,13 @@ Unlike `componentDidMount` and `componentDidUpdate`, the function passed to `use However, not all effects can be deferred. For example, a DOM mutation that is visible to the user must fire synchronously before the next paint so that the user does not perceive a visual inconsistency. (The distinction is conceptually similar to passive versus active event listeners.) For these types of effects, React provides one additional Hook called [`useLayoutEffect`](#uselayouteffect). It has the same signature as `useEffect`, and only differs in when it is fired. -Although `useEffect` is deferred until after the browser has painted, it's guaranteed to fire before any new renders. React will always flush a previous render's effects before starting a new update. +Additionally, starting in React 18, the function passed to `useEffect` will fire synchronously **before** layout and paint when it's the result of a discrete user input such as a click, or when it's the result of an update wrapped in [`flushSync`](/docs/react-dom.html#flushsync). This behavior allows the result of the effect to be observed by the event system, or by the caller of [`flushSync`](/docs/react-dom.html#flushsync). + +> Note +> +> This only affects the timing of when the function passed to `useEffect` is called - updates scheduled inside these effects are still deferred. This is different than [`useLayoutEffect`](#uselayouteffect), which fires the function and processes the updates inside of it immediately. + +Even in cases where `useEffect` is deferred until after the browser has painted, it's guaranteed to fire before any new renders. React will always flush a previous render's effects before starting a new update. #### Conditionally firing an effect {#conditionally-firing-an-effect} @@ -176,6 +205,11 @@ The array of dependencies is not passed as arguments to the effect function. Con ### `useContext` {#usecontext} +> Try the new React documentation for [`useContext`](https://beta.reactjs.org/reference/react/useContext). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + ```js const value = useContext(MyContext); ``` @@ -248,6 +282,11 @@ The following Hooks are either variants of the basic ones from the previous sect ### `useReducer` {#usereducer} +> Try the new React documentation for [`useReducer`](https://beta.reactjs.org/reference/react/useReducer). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + ```js const [state, dispatch] = useReducer(reducer, initialArg, init); ``` @@ -351,6 +390,10 @@ Note that React may still need to render that specific component again before ba ### `useCallback` {#usecallback} +> Try the new React documentation for [`useCallback`](https://beta.reactjs.org/reference/react/useCallback). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```js const memoizedCallback = useCallback( () => { @@ -374,6 +417,11 @@ Pass an inline callback and an array of dependencies. `useCallback` will return ### `useMemo` {#usememo} +> Try the new React documentation for [`useMemo`](https://beta.reactjs.org/reference/react/useMemo). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + ```js const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ``` @@ -396,6 +444,11 @@ If no array is provided, a new value will be computed on every render. ### `useRef` {#useref} +> Try the new React documentation for [`useRef`](https://beta.reactjs.org/reference/react/useRef). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + ```js const refContainer = useRef(initialValue); ``` @@ -433,6 +486,11 @@ Keep in mind that `useRef` *doesn't* notify you when its content changes. Mutati ### `useImperativeHandle` {#useimperativehandle} +> Try the new React documentation for [`useImperativeHandle`](https://beta.reactjs.org/reference/react/useImperativeHandle). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + ```js useImperativeHandle(ref, createHandle, [deps]) ``` @@ -456,6 +514,11 @@ In this example, a parent component that renders `<FancyInput ref={inputRef} />` ### `useLayoutEffect` {#uselayouteffect} +> Try the new React documentation for [`useLayoutEffect`](https://beta.reactjs.org/reference/react/useLayoutEffect). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + The signature is identical to `useEffect`, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside `useLayoutEffect` will be flushed synchronously, before the browser has a chance to paint. Prefer the standard `useEffect` when possible to avoid blocking visual updates. @@ -470,6 +533,11 @@ Prefer the standard `useEffect` when possible to avoid blocking visual updates. ### `useDebugValue` {#usedebugvalue} +> Try the new React documentation for [`useDebugValue`](https://beta.reactjs.org/reference/react/useDebugValue). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + ```js useDebugValue(value) ``` @@ -507,3 +575,223 @@ For example a custom Hook that returned a `Date` value could avoid calling the ` ```js useDebugValue(date, date => date.toDateString()); ``` + +### `useDeferredValue` {#usedeferredvalue} + +> Try the new React documentation for [`useDeferredValue`](https://beta.reactjs.org/reference/react/useDeferredValue). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + +```js +const deferredValue = useDeferredValue(value); +``` + +`useDeferredValue` accepts a value and returns a new copy of the value that will defer to more urgent updates. If the current render is the result of an urgent update, like user input, React will return the previous value and then render the new value after the urgent render has completed. + +This hook is similar to user-space hooks which use debouncing or throttling to defer updates. The benefits to using `useDeferredValue` is that React will work on the update as soon as other work finishes (instead of waiting for an arbitrary amount of time), and like [`startTransition`](/docs/react-api.html#starttransition), deferred values can suspend without triggering an unexpected fallback for existing content. + +#### Memoizing deferred children {#memoizing-deferred-children} +`useDeferredValue` only defers the value that you pass to it. If you want to prevent a child component from re-rendering during an urgent update, you must also memoize that component with [`React.memo`](/docs/react-api.html#reactmemo) or [`React.useMemo`](/docs/hooks-reference.html#usememo): + +```js +function Typeahead() { + const query = useSearchQuery(''); + const deferredQuery = useDeferredValue(query); + + // Memoizing tells React to only re-render when deferredQuery changes, + // not when query changes. + const suggestions = useMemo(() => + <SearchSuggestions query={deferredQuery} />, + [deferredQuery] + ); + + return ( + <> + <SearchInput query={query} /> + <Suspense fallback="Loading results..."> + {suggestions} + </Suspense> + </> + ); +} +``` + +Memoizing the children tells React that it only needs to re-render them when `deferredQuery` changes and not when `query` changes. This caveat is not unique to `useDeferredValue`, and it's the same pattern you would use with similar hooks that use debouncing or throttling. + +### `useTransition` {#usetransition} + +> Try the new React documentation for [`useTransition`](https://beta.reactjs.org/reference/react/useTransition). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + +```js +const [isPending, startTransition] = useTransition(); +``` + +Returns a stateful value for the pending state of the transition, and a function to start it. + +`startTransition` lets you mark updates in the provided callback as transitions: + +```js +startTransition(() => { + setCount(count + 1); +}); +``` + +`isPending` indicates when a transition is active to show a pending state: + +```js +function App() { + const [isPending, startTransition] = useTransition(); + const [count, setCount] = useState(0); + + function handleClick() { + startTransition(() => { + setCount(c => c + 1); + }); + } + + return ( + <div> + {isPending && <Spinner />} + <button onClick={handleClick}>{count}</button> + </div> + ); +} +``` + +> Note: +> +> Updates in a transition yield to more urgent updates such as clicks. +> +> Updates in a transition will not show a fallback for re-suspended content. This allows the user to continue interacting with the current content while rendering the update. + +### `useId` {#useid} + +> Try the new React documentation for [`useId`](https://beta.reactjs.org/reference/react/useId). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + +```js +const id = useId(); +``` + +`useId` is a hook for generating unique IDs that are stable across the server and client, while avoiding hydration mismatches. + +> Note +> +> `useId` is **not** for generating [keys in a list](/docs/lists-and-keys.html#keys). Keys should be generated from your data. + +For a basic example, pass the `id` directly to the elements that need it: + +```js +function Checkbox() { + const id = useId(); + return ( + <> + <label htmlFor={id}>Do you like React?</label> + <input id={id} type="checkbox" name="react"/> + </> + ); +}; +``` + +For multiple IDs in the same component, append a suffix using the same `id`: + +```js +function NameFields() { + const id = useId(); + return ( + <div> + <label htmlFor={id + '-firstName'}>First Name</label> + <div> + <input id={id + '-firstName'} type="text" /> + </div> + <label htmlFor={id + '-lastName'}>Last Name</label> + <div> + <input id={id + '-lastName'} type="text" /> + </div> + </div> + ); +} +``` + +> Note: +> +> `useId` generates a string that includes the `:` token. This helps ensure that the token is unique, but is not supported in CSS selectors or APIs like `querySelectorAll`. +> +> `useId` supports an `identifierPrefix` to prevent collisions in multi-root apps. To configure, see the options for [`hydrateRoot`](/docs/react-dom-client.html#hydrateroot) and [`ReactDOMServer`](/docs/react-dom-server.html). + +## Library Hooks {#library-hooks} + +The following Hooks are provided for library authors to integrate libraries deeply into the React model, and are not typically used in application code. + +### `useSyncExternalStore` {#usesyncexternalstore} + +> Try the new React documentation for [`useSyncExternalStore`](https://beta.reactjs.org/reference/react/useSyncExternalStore). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + +```js +const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]); +``` + +`useSyncExternalStore` is a hook recommended for reading and subscribing from external data sources in a way that's compatible with concurrent rendering features like selective hydration and time slicing. + +This method returns the value of the store and accepts three arguments: +- `subscribe`: function to register a callback that is called whenever the store changes. +- `getSnapshot`: function that returns the current value of the store. +- `getServerSnapshot`: function that returns the snapshot used during server rendering. + +The most basic example simply subscribes to the entire store: + +```js +const state = useSyncExternalStore(store.subscribe, store.getSnapshot); +``` + +However, you can also subscribe to a specific field: + +```js +const selectedField = useSyncExternalStore( + store.subscribe, + () => store.getSnapshot().selectedField, +); +``` + +When server rendering, you must serialize the store value used on the server, and provide it to `useSyncExternalStore`. React will use this snapshot during hydration to prevent server mismatches: + +```js +const selectedField = useSyncExternalStore( + store.subscribe, + () => store.getSnapshot().selectedField, + () => INITIAL_SERVER_SNAPSHOT.selectedField, +); +``` + +> Note: +> +> `getSnapshot` must return a cached value. If getSnapshot is called multiple times in a row, it must return the same exact value unless there was a store update in between. +> +> A shim is provided for supporting multiple React versions published as `use-sync-external-store/shim`. This shim will prefer `useSyncExternalStore` when available, and fallback to a user-space implementation when it's not. +> +> As a convenience, we also provide a version of the API with automatic support for memoizing the result of getSnapshot published as `use-sync-external-store/with-selector`. + +### `useInsertionEffect` {#useinsertioneffect} + +> Try the new React documentation for [`useInsertionEffect`](https://beta.reactjs.org/reference/react/useInsertionEffect). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +```js +useInsertionEffect(didUpdate); +``` + +The signature is identical to `useEffect`, but it fires synchronously _before_ all DOM mutations. Use this to inject styles into the DOM before reading layout in [`useLayoutEffect`](#uselayouteffect). Since this hook is limited in scope, this hook does not have access to refs and cannot schedule updates. + +> Note: +> +> `useInsertionEffect` should be limited to css-in-js library authors. Prefer [`useEffect`](#useeffect) or [`useLayoutEffect`](#uselayouteffect) instead. diff --git a/content/docs/hooks-rules.md b/content/docs/hooks-rules.md index dee3d9ffd..82c964f30 100644 --- a/content/docs/hooks-rules.md +++ b/content/docs/hooks-rules.md @@ -12,7 +12,7 @@ Hooks are JavaScript functions, but you need to follow two rules when using them ### Only Call Hooks at the Top Level {#only-call-hooks-at-the-top-level} -**Don't call Hooks inside loops, conditions, or nested functions.** Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That's what allows React to correctly preserve the state of Hooks between multiple `useState` and `useEffect` calls. (If you're curious, we'll explain this in depth [below](#explanation).) +**Don't call Hooks inside loops, conditions, or nested functions.** Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That's what allows React to correctly preserve the state of Hooks between multiple `useState` and `useEffect` calls. (If you're curious, we'll explain this in depth [below](#explanation).) ### Only Call Hooks from React Functions {#only-call-hooks-from-react-functions} @@ -27,6 +27,8 @@ 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: +This plugin is included by default in [Create React App](/docs/create-a-new-react-app.html#create-react-app). + ```bash npm install eslint-plugin-react-hooks --save-dev ``` @@ -46,8 +48,6 @@ npm install eslint-plugin-react-hooks --save-dev } ``` -This plugin is included by default in [Create React App](/docs/create-a-new-react-app.html#create-react-app). - **You can skip to the next page explaining how to write [your own Hooks](/docs/hooks-custom.html) now.** On this page, we'll continue by explaining the reasoning behind these rules. ## Explanation {#explanation} diff --git a/content/docs/hooks-state.md b/content/docs/hooks-state.md index 0b4d15b65..961a7d3d2 100644 --- a/content/docs/hooks-state.md +++ b/content/docs/hooks-state.md @@ -6,6 +6,15 @@ next: hooks-effect.html prev: hooks-overview.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [State: A Component's Memory](https://beta.reactjs.org/learn/state-a-components-memory) +> - [`useState`](https://beta.reactjs.org/reference/react/useState) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + *Hooks* are a new addition in React 16.8. They let you use state and other React features without writing a class. The [introduction page](/docs/hooks-intro.html) used this example to get familiar with Hooks: diff --git a/content/docs/how-to-contribute.md b/content/docs/how-to-contribute.md index 2eb71ec21..86fdb0c87 100644 --- a/content/docs/how-to-contribute.md +++ b/content/docs/how-to-contribute.md @@ -11,9 +11,9 @@ redirect_from: React is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody on [facebook.com](https://www.facebook.com). We're still working out the kinks to make contributing to this project as easy and transparent as possible, but we're not quite there yet. Hopefully this document makes the process for contributing clear and answers some questions that you may have. -### [Code of Conduct](https://github.com/facebook/react/blob/master/CODE_OF_CONDUCT.md) {#code-of-conduct} +### [Code of Conduct](https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md) {#code-of-conduct} -Facebook has adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](https://github.com/facebook/react/blob/master/CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated. +Facebook has adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated. ### Open Development {#open-development} @@ -23,19 +23,19 @@ All work on React happens directly on [GitHub](https://github.com/facebook/react React follows [semantic versioning](https://semver.org/). We release patch versions for critical bugfixes, minor versions for new features or non-essential changes, and major versions for any breaking changes. When we make breaking changes, we also introduce deprecation warnings in a minor version so that our users learn about the upcoming changes and migrate their code in advance. Learn more about our commitment to stability and incremental migration in [our versioning policy](/docs/faq-versioning.html). -Every significant change is documented in the [changelog file](https://github.com/facebook/react/blob/master/CHANGELOG.md). +Every significant change is documented in the [changelog file](https://github.com/facebook/react/blob/main/CHANGELOG.md). ### Branch Organization {#branch-organization} -Submit all changes directly to the [`master branch`](https://github.com/facebook/react/tree/master). We don't use separate branches for development or for upcoming releases. We do our best to keep `master` in good shape, with all tests passing. +Submit all changes directly to the [`main branch`](https://github.com/facebook/react/tree/main). We don't use separate branches for development or for upcoming releases. We do our best to keep `main` in good shape, with all tests passing. -Code that lands in `master` must be compatible with the latest stable release. It may contain additional features, but no breaking changes. We should be able to release a new minor version from the tip of `master` at any time. +Code that lands in `main` must be compatible with the latest stable release. It may contain additional features, but no breaking changes. We should be able to release a new minor version from the tip of `main` at any time. ### Feature Flags {#feature-flags} -To keep the `master` branch in a releasable state, breaking changes and experimental features must be gated behind a feature flag. +To keep the `main` branch in a releasable state, breaking changes and experimental features must be gated behind a feature flag. -Feature flags are defined in [`packages/shared/ReactFeatureFlags.js`](https://github.com/facebook/react/blob/master/packages/shared/ReactFeatureFlags.js). Some builds of React may enable different sets of feature flags; for example, the React Native build may be configured differently than React DOM. These flags are found in [`packages/shared/forks`](https://github.com/facebook/react/tree/master/packages/shared/forks). Feature flags are statically typed by Flow, so you can run `yarn flow` to confirm that you've updated all the necessary files. +Feature flags are defined in [`packages/shared/ReactFeatureFlags.js`](https://github.com/facebook/react/blob/main/packages/shared/ReactFeatureFlags.js). Some builds of React may enable different sets of feature flags; for example, the React Native build may be configured differently than React DOM. These flags are found in [`packages/shared/forks`](https://github.com/facebook/react/tree/main/packages/shared/forks). Feature flags are statically typed by Flow, so you can run `yarn flow` to confirm that you've updated all the necessary files. React's build system will strip out disabled feature branches before publishing. A continuous integration job runs on every commit to check for changes in bundle size. You can use the change in size as a signal that a feature was gated correctly. @@ -70,7 +70,7 @@ If you're only fixing a bug, it's fine to submit a pull request right away but w Working on your first Pull Request? You can learn how from this free video series: -**[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)** +**[How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)** To help you get your feet wet and get you familiar with our contribution process, we have a list of **[good first issues](https://github.com/facebook/react/issues?q=is:open+is:issue+label:"good+first+issue")** that contain bugs that have a relatively limited scope. This is a great place to get started. @@ -84,12 +84,12 @@ The core team is monitoring for pull requests. We will review your pull request **Before submitting a pull request,** please make sure the following is done: -1. Fork [the repository](https://github.com/facebook/react) and create your branch from `master`. +1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. -5. Run `yarn test-prod` to test in the production environment. It supports the same options as `yarn test`. -6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect". +5. Run `yarn test --prod` to test in the production environment. +6. If you need a debugger, run `yarn test --debug --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) typechecks (`yarn flow`). @@ -103,7 +103,7 @@ In order to accept your pull request, we need you to submit a CLA. You only need ### Contribution Prerequisites {#contribution-prerequisites} -* You have [Node](https://nodejs.org) installed at v8.0.0+ and [Yarn](https://yarnpkg.com/en/) at v1.2.0+. +* You have [Node](https://nodejs.org) installed at LTS and [Yarn](https://yarnpkg.com/en/) at v1.2.0+. * You have [JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html) installed. * You have `gcc` installed or are comfortable installing a compiler if needed. Some of our dependencies may require a compilation step. On OS X, the Xcode Command Line Tools will cover this. On Ubuntu, `apt-get install build-essential` will install the required packages. Similar commands should work on other Linux distros. Windows will require some additional steps, see the [`node-gyp` installation instructions](https://github.com/nodejs/node-gyp#installation) for details. * You are familiar with Git. @@ -117,32 +117,40 @@ Then, you can run several commands: * `yarn linc` is like `yarn lint` but faster because it only checks files that differ in your branch. * `yarn test` runs the complete test suite. * `yarn test --watch` runs an interactive test watcher. +* `yarn test --prod` runs tests in the production environment. * `yarn test <pattern>` runs tests with matching filenames. -* `yarn test-prod` runs tests in the production environment. It supports all the same options as `yarn test`. * `yarn debug-test` is just like `yarn test` but with a debugger. Open `chrome://inspect` and press "Inspect". * `yarn flow` runs the [Flow](https://flowtype.org/) typechecks. * `yarn build` creates a `build` folder with all the packages. * `yarn build react/index,react-dom/index --type=UMD` creates UMD builds of just React and ReactDOM. -We recommend running `yarn test` (or its variations above) to make sure you don't introduce any regressions as you work on your change. However it can be handy to try your build of React in a real project. +We recommend running `yarn test` (or its variations above) to make sure you don't introduce any regressions as you work on your change. However, it can be handy to try your build of React in a real project. First, run `yarn build`. This will produce pre-built bundles in `build` folder, as well as prepare npm packages inside `build/packages`. The easiest way to try your changes is to run `yarn build react/index,react-dom/index --type=UMD` and then open `fixtures/packaging/babel-standalone/dev.html`. This file already uses `react.development.js` from the `build` folder so it will pick up your changes. -If you want to try your changes in your existing React project, you may copy `build/dist/react.development.js`, `build/dist/react-dom.development.js`, or any other build products into your app and use them instead of the stable version. If your project uses React from npm, you may delete `react` and `react-dom` in its dependencies and use `yarn link` to point them to your local `build` folder: +If you want to try your changes in your existing React project, you may copy `build/node_modules/react/umd/react.development.js`, `build/node_modules/react-dom/umd/react-dom.development.js`, or any other build products into your app and use them instead of the stable version. + +If your project uses React from npm, you may delete `react` and `react-dom` in its dependencies and use `yarn link` to point them to your local `build` folder. Note that **instead of `--type=UMD` you'll want to pass `--type=NODE` when building**. You'll also need to build the `scheduler` package: ```sh -cd ~/path_to_your_react_clone/build/node_modules/react +cd ~/path_to_your_react_clone/ +yarn build react/index,react/jsx,react-dom/index,scheduler --type=NODE + +cd build/node_modules/react yarn link -cd ~/path_to_your_react_clone/build/node_modules/react-dom +cd build/node_modules/react-dom yarn link -cd /path/to/your/project + +cd ~/path/to/your/project yarn link react react-dom ``` Every time you run `yarn build` in the React folder, the updated versions will appear in your project's `node_modules`. You can then rebuild your project to try your changes. +If some package is still missing (e.g. maybe you use `react-dom/server` in your project), you can always do a full build with `yarn build`. Note that running `yarn build` without options takes a long time. + We still require that your pull request contains unit tests for any new functionality. This way we can ensure that we don't break your code in the future. ### Style Guide {#style-guide} diff --git a/content/docs/implementation-notes.md b/content/docs/implementation-notes.md index b345f7d5d..ff67f062f 100644 --- a/content/docs/implementation-notes.md +++ b/content/docs/implementation-notes.md @@ -32,10 +32,11 @@ The reconciler itself doesn't have a public API. [Renderers](/docs/codebase-over Let's consider the first time you mount a component: ```js -ReactDOM.render(<App />, rootEl); +const root = ReactDOM.createRoot(rootEl); +root.render(<App />); ``` -React DOM will pass `<App />` along to the reconciler. Remember that `<App />` is a React element, that is, a description of *what* to render. You can think about it as a plain object: +`root.render` will pass `<App />` along to the reconciler. Remember that `<App />` is a React element, that is, a description of *what* to render. You can think about it as a plain object: ```js console.log(<App />); @@ -236,9 +237,9 @@ This is working but still far from how the reconciler is really implemented. The The key feature of React is that you can re-render everything, and it won't recreate the DOM or reset the state: ```js -ReactDOM.render(<App />, rootEl); +root.render(<App />); // Should reuse the existing DOM: -ReactDOM.render(<App />, rootEl); +root.render(<App />); ``` However, our implementation above only knows how to mount the initial tree. It can't perform updates on it because it doesn't store all the necessary information, such as all the `publicInstance`s, or which DOM `node`s correspond to which components. @@ -412,7 +413,7 @@ If you're struggling to imagine how an internal instance tree is structured in m <img src="../images/docs/implementation-notes-tree.png" width="500" style="max-width: 100%" alt="React DevTools tree" /> -To complete this refactoring, we will introduce a function that mounts a complete tree into a container node, just like `ReactDOM.render()`. It returns a public instance, also like `ReactDOM.render()`: +To complete this refactoring, we will introduce a function that mounts a complete tree into a container node and a public instance: ```js function mountTree(element, containerNode) { diff --git a/content/docs/integrating-with-other-libraries.md b/content/docs/integrating-with-other-libraries.md index 5bc8b2570..78a49b46c 100644 --- a/content/docs/integrating-with-other-libraries.md +++ b/content/docs/integrating-with-other-libraries.md @@ -190,9 +190,9 @@ class Chosen extends React.Component { ## Integrating with Other View Libraries {#integrating-with-other-view-libraries} -React can be embedded into other applications thanks to the flexibility of [`ReactDOM.render()`](/docs/react-dom.html#render). +React can be embedded into other applications thanks to the flexibility of [`createRoot()`](/docs/react-dom-client.html#createRoot). -Although React is commonly used at startup to load a single root React component into the DOM, `ReactDOM.render()` can also be called multiple times for independent parts of the UI which can be as small as a button, or as large as an app. +Although React is commonly used at startup to load a single root React component into the DOM, `createRoot()` can also be called multiple times for independent parts of the UI which can be as small as a button, or as large as an app. In fact, this is exactly how React is used at Facebook. This lets us write applications in React piece by piece, and combine them with our existing server-generated templates and other client-side code. @@ -216,15 +216,9 @@ function Button() { return <button id="btn">Say Hello</button>; } -ReactDOM.render( - <Button />, - document.getElementById('container'), - function() { - $('#btn').click(function() { - alert('Hello!'); - }); - } -); +$('#btn').click(function() { + alert('Hello!'); +}); ``` From here you could start moving more logic into the component and begin adopting more common React practices. For example, in components it is best not to rely on IDs because the same component can be rendered multiple times. Instead, we will use the [React event system](/docs/handling-events.html) and register the click handler directly on the React `<button>` element: @@ -240,36 +234,34 @@ function HelloButton() { } return <Button onClick={handleClick} />; } - -ReactDOM.render( - <HelloButton />, - document.getElementById('container') -); ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/RVKbvW?editors=1010) -You can have as many such isolated components as you like, and use `ReactDOM.render()` to render them to different DOM containers. Gradually, as you convert more of your app to React, you will be able to combine them into larger components, and move some of the `ReactDOM.render()` calls up the hierarchy. +You can have as many such isolated components as you like, and use `ReactDOM.createRoot()` to render them to different DOM containers. Gradually, as you convert more of your app to React, you will be able to combine them into larger components, and move some of the `ReactDOM.createRoot()` calls up the hierarchy. ### Embedding React in a Backbone View {#embedding-react-in-a-backbone-view} [Backbone](https://backbonejs.org/) views typically use HTML strings, or string-producing template functions, to create the content for their DOM elements. This process, too, can be replaced with rendering a React component. -Below, we will create a Backbone view called `ParagraphView`. It will override Backbone's `render()` function to render a React `<Paragraph>` component into the DOM element provided by Backbone (`this.el`). Here, too, we are using [`ReactDOM.render()`](/docs/react-dom.html#render): +Below, we will create a Backbone view called `ParagraphView`. It will override Backbone's `render()` function to render a React `<Paragraph>` component into the DOM element provided by Backbone (`this.el`). Here, too, we are using [`ReactDOM.createRoot()`](/docs/react-dom-client.html#createroot): -```js{1,5,8,12} +```js{7,11,15} function Paragraph(props) { return <p>{props.text}</p>; } const ParagraphView = Backbone.View.extend({ + initialize(options) { + this.reactRoot = ReactDOM.createRoot(this.el); + }, render() { const text = this.model.get('text'); - ReactDOM.render(<Paragraph text={text} />, this.el); + this.reactRoot.render(<Paragraph text={text} />); return this; }, remove() { - ReactDOM.unmountComponentAtNode(this.el); + this.reactRoot.unmount(); Backbone.View.prototype.remove.call(this); } }); @@ -277,7 +269,7 @@ const ParagraphView = Backbone.View.extend({ [**Try it on CodePen**](https://codepen.io/gaearon/pen/gWgOYL?editors=0010) -It is important that we also call `ReactDOM.unmountComponentAtNode()` in the `remove` method so that React unregisters event handlers and other resources associated with the component tree when it is detached. +It is important that we also call `root.unmount()` in the `remove` method so that React unregisters event handlers and other resources associated with the component tree when it is detached. When a component is removed *from within* a React tree, the cleanup is performed automatically, but because we are removing the entire tree by hand, we must call this method. @@ -428,10 +420,8 @@ function Example(props) { } const model = new Backbone.Model({ firstName: 'Frodo' }); -ReactDOM.render( - <Example model={model} />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Example model={model} />); ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/PmWwwa?editors=0010) diff --git a/content/docs/introducing-jsx.md b/content/docs/introducing-jsx.md index 3f7284ee9..28c948e8c 100644 --- a/content/docs/introducing-jsx.md +++ b/content/docs/introducing-jsx.md @@ -6,7 +6,20 @@ prev: hello-world.html next: rendering-elements.html --- +<<<<<<< HEAD Εξετάστε τη δήλωση αυτής της μεταβλητής +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Writing Markup with JSX](https://beta.reactjs.org/learn/writing-markup-with-jsx) +> - [JavaScript in JSX with Curly Braces](https://beta.reactjs.org/learn/javascript-in-jsx-with-curly-braces) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +Consider this variable declaration: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```js const element = <h1>Hello, world!</h1>; @@ -35,11 +48,6 @@ const element = <h1>Hello, world!</h1>; ```js{1,2} const name = 'Josh Perez'; const element = <h1>Hello, {name}</h1>; - -ReactDOM.render( - element, - document.getElementById('root') -); ``` Μπορείτε να βάλετε οποιαδήποτε έγκυρη [έκφραση της JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions) μέσα σε αγκύλες στο JSX. Για παράδειγμα οι `2 + 2`, `user.firstName` ή `fotmatName(user)` είναι έγκυρες εκφράσεις της JavaScript. @@ -61,14 +69,13 @@ const element = ( Hello, {formatName(user)}! </h1> ); - -ReactDOM.render( - element, - document.getElementById('root') -); ``` +<<<<<<< HEAD **[Δοκιμάστε το στο CodePen](codepen://introducing-jsx)** +======= +**[Try it on CodePen](https://codepen.io/gaearon/pen/PGEjdG?editors=1010)** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Διαχωρίζουμε το JSX σε πολλές γραμμές για ευκολία στην ανάγνωση. Ενώ δεν απαιτείται, όταν το κάνετε αυτό, προτείνουμε να το κλείσετε σε παρενθέσεις για να αποφύγετε τις παγίδες [της αυτόματης εισαγωγής ερωτηματικού](https://stackoverflow.com/q/2846283). @@ -92,7 +99,7 @@ function getGreeting(user) { Μπορείτε να χρησιμοποιήσετε εισαγωγικά για να καθορίσετε τα string literals ως attributes: ```js -const element = <div tabIndex="0"></div>; +const element = <a href="https://www.reactjs.org"> link </a>; ``` Μπορείτε επίσης να χρησιμοποιήσετε άγκιστρα για να ενσωματώσετε μια έκφραση JavaScript σε ένα argument: @@ -182,4 +189,8 @@ const element = { >**Tip:** > +<<<<<<< HEAD >Προτείνουμε τη χρήση του ["Babel" language definition](https://babeljs.io/docs/editors) για τον editor της επιλογής σας, ώστε ο κώδικας ES6 και JSX να επισημαίνονται σωστά. +======= +>We recommend using the ["Babel" language definition](https://babeljs.io/docs/en/next/editors) for your editor of choice so that both ES6 and JSX code is properly highlighted. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a diff --git a/content/docs/jsx-in-depth.md b/content/docs/jsx-in-depth.md index 19597a5e2..04a3858c1 100644 --- a/content/docs/jsx-in-depth.md +++ b/content/docs/jsx-in-depth.md @@ -227,11 +227,11 @@ If you pass no value for a prop, it defaults to `true`. These two JSX expression <MyTextBox autocomplete={true} /> ``` -In general, we don't recommend using this because it can be confused with the [ES6 object shorthand](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) `{foo}` which is short for `{foo: foo}` rather than `{foo: true}`. This behavior is just there so that it matches the behavior of HTML. +In general, we don't recommend *not* passing a value for a prop, because it can be confused with the [ES6 object shorthand](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) `{foo}` which is short for `{foo: foo}` rather than `{foo: true}`. This behavior is just there so that it matches the behavior of HTML. ### Spread Attributes {#spread-attributes} -If you already have `props` as an object, and you want to pass it in JSX, you can use `...` as a "spread" operator to pass the whole props object. These two components are equivalent: +If you already have `props` as an object, and you want to pass it in JSX, you can use `...` as a "spread" syntax to pass the whole props object. These two components are equivalent: ```js{7} function App1() { @@ -244,7 +244,7 @@ function App2() { } ``` -You can also pick specific props that your component will consume while passing all other props using the spread operator. +You can also pick specific props that your component will consume while passing all other props using the spread syntax. ```js{2} const Button = props => { diff --git a/content/docs/legacy-event-pooling.md b/content/docs/legacy-event-pooling.md new file mode 100644 index 000000000..8fb19c3b3 --- /dev/null +++ b/content/docs/legacy-event-pooling.md @@ -0,0 +1,37 @@ +--- +id: legacy-event-pooling +title: Event Pooling +permalink: docs/legacy-event-pooling.html +--- + +>Note +> +>This page is only relevant for React 16 and earlier, and for React Native. +> +>React 17 on the web **does not** use event pooling. +> +>[Read more](/blog/2020/08/10/react-v17-rc.html#no-event-pooling) about this change in React 17. + +The [`SyntheticEvent`](/docs/events.html) objects are pooled. This means that the `SyntheticEvent` object will be reused and all properties will be nullified after the event handler has been called. For example, this won't work: + +```javascript +function handleChange(e) { + // This won't work because the event object gets reused. + setTimeout(() => { + console.log(e.target.value); // Too late! + }, 100); +} +``` + +If you need to access event object's properties after the event handler has run, you need to call `e.persist()`: + +```javascript +function handleChange(e) { + // Prevents React from resetting its properties: + e.persist(); + + setTimeout(() => { + console.log(e.target.value); // Works + }, 100); +} +``` diff --git a/content/docs/lifting-state-up.md b/content/docs/lifting-state-up.md index 3c15b1918..74885b423 100644 --- a/content/docs/lifting-state-up.md +++ b/content/docs/lifting-state-up.md @@ -9,7 +9,19 @@ redirect_from: - "docs/flux-todo-list.html" --- +<<<<<<< HEAD Συχνά, πολλαπλά components πρέπει να μεταβάλλονται ανάλογα με κάποιες κοινές αλλαγές στο state. Για αυτό συνιστούμε τη μεταφορά του state στον πλησιέστερο κοινό πρόγονο τους. Ας δούμε πώς λειτουργεί αυτό στην πράξη. +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Sharing State Between Components](https://beta.reactjs.org/learn/sharing-state-between-components) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor. Let's see how this works in action. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Σε αυτή την ενότητα, θα δημιουργήσουμε έναν υπολογιστή θερμοκρασίας που υπολογίζει εάν το νερό θα βράσει σε μια δεδομένη θερμοκρασία. @@ -324,7 +336,11 @@ class Calculator extends React.Component { Εάν υπάρχει κάτι το οποίο μπορεί να υπολογιστεί είτε από τα props είτε από το state, τότε δεν χρειάζεται να σώζεται στο state. Για παράδειγμα, αντί να σώζεται το `celsiusValue` και το `fahrenheitValue`, αποθηκεύουμε απλά την τελευταία τιμή του `temperature` και του `scale`. Η τιμή του άλλου input μπορεί πάντα να υπολογιστεί από αυτά τα δύο μέσα στη `render()` μέθοδο. Αυτό μας επιτρέπει να καθαρίσουμε ή να στρογγυλοποίησουμε τη τιμή του άλλου πεδίου με ακρίβεια. +<<<<<<< HEAD Όταν βλέπετε ότι πηγαίνει κάτι λάθος με το UI, μπορείτε να χρησιμοποιείτε τα [React Developer Tools](https://github.com/facebook/react/tree/master/packages/react-devtools) έτσι ώστε να εντοπίζετε τα props και να ανέβετε στο δέντρο των components μέχρις ότου βρείτε το υπεύθυνο component για την ανανέωση του state. Αυτό σας επιτρέπει να εντοπίσετε τα σφάλματα στην πηγή τους: +======= +When you see something wrong in the UI, you can use [React Developer Tools](https://github.com/facebook/react/tree/main/packages/react-devtools) to inspect the props and move up the tree until you find the component responsible for updating the state. This lets you trace the bugs to their source: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a <img src="../images/docs/react-devtools-state.gif" alt="Monitoring State in React DevTools" max-width="100%" height="100%"> diff --git a/content/docs/lists-and-keys.md b/content/docs/lists-and-keys.md index f34e9e5d6..b0c3b5b41 100644 --- a/content/docs/lists-and-keys.md +++ b/content/docs/lists-and-keys.md @@ -6,6 +6,15 @@ prev: conditional-rendering.html next: forms.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Rendering Lists](https://beta.reactjs.org/learn/rendering-lists) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + First, let's review how you transform lists in JavaScript. Given the code below, we use the [`map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) function to take an array of `numbers` and double their values. We assign the new array returned by `map()` to the variable `doubled` and log it: @@ -33,13 +42,10 @@ const listItems = numbers.map((number) => ); ``` -We include the entire `listItems` array inside a `<ul>` element, and [render it to the DOM](/docs/rendering-elements.html#rendering-an-element-into-the-dom): +Then, we can include the entire `listItems` array inside a `<ul>` element: ```javascript{2} -ReactDOM.render( - <ul>{listItems}</ul>, - document.getElementById('root') -); +<ul>{listItems}</ul> ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/GjPyQr?editors=0011) @@ -64,10 +70,8 @@ function NumberList(props) { } const numbers = [1, 2, 3, 4, 5]; -ReactDOM.render( - <NumberList numbers={numbers} />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<NumberList numbers={numbers} />); ``` When you run this code, you'll be given a warning that a key should be provided for list items. A "key" is a special string attribute you need to include when creating lists of elements. We'll discuss why it's important in the next section. @@ -86,12 +90,6 @@ function NumberList(props) { <ul>{listItems}</ul> ); } - -const numbers = [1, 2, 3, 4, 5]; -ReactDOM.render( - <NumberList numbers={numbers} />, - document.getElementById('root') -); ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/jrXYRR?editors=0011) @@ -130,7 +128,7 @@ const todoItems = todos.map((todo, index) => ); ``` -We don't recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. Check out Robin Pokorny's article for an [in-depth explanation on the negative impacts of using an index as a key](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318). If you choose not to assign an explicit key to list items then React will default to using indexes as keys. +We don't recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. Check out Robin Pokorny's article for an [in-depth explanation on the negative impacts of using an index as a key](https://robinpokorny.com/blog/index-as-a-key-is-an-anti-pattern/). If you choose not to assign an explicit key to list items then React will default to using indexes as keys. Here is an [in-depth explanation about why keys are necessary](/docs/reconciliation.html#recursing-on-children) if you're interested in learning more. @@ -165,12 +163,6 @@ function NumberList(props) { </ul> ); } - -const numbers = [1, 2, 3, 4, 5]; -ReactDOM.render( - <NumberList numbers={numbers} />, - document.getElementById('root') -); ``` **Example: Correct Key Usage** @@ -185,8 +177,7 @@ function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // Correct! Key should be specified inside the array. - <ListItem key={number.toString()} - value={number} /> + <ListItem key={number.toString()} value={number} /> ); return ( <ul> @@ -194,12 +185,6 @@ function NumberList(props) { </ul> ); } - -const numbers = [1, 2, 3, 4, 5]; -ReactDOM.render( - <NumberList numbers={numbers} />, - document.getElementById('root') -); ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/ZXeOGM?editors=0010) @@ -208,7 +193,7 @@ A good rule of thumb is that elements inside the `map()` call need keys. ### Keys Must Only Be Unique Among Siblings {#keys-must-only-be-unique-among-siblings} -Keys used within arrays should be unique among their siblings. However they don't need to be globally unique. We can use the same keys when we produce two different arrays: +Keys used within arrays should be unique among their siblings. However, they don't need to be globally unique. We can use the same keys when we produce two different arrays: ```js{2,5,11,12,19,21} function Blog(props) { @@ -240,10 +225,9 @@ const posts = [ {id: 1, title: 'Hello World', content: 'Welcome to learning React!'}, {id: 2, title: 'Installation', content: 'You can install React from npm.'} ]; -ReactDOM.render( - <Blog posts={posts} />, - document.getElementById('root') -); + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Blog posts={posts} />); ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/NRZYGN?editors=0010) diff --git a/content/docs/nav.yml b/content/docs/nav.yml index 09f73ca6a..d0acf873b 100644 --- a/content/docs/nav.yml +++ b/content/docs/nav.yml @@ -92,6 +92,8 @@ title: React.Component - id: react-dom title: ReactDOM + - id: react-dom-client + title: ReactDOMClient - id: react-dom-server title: ReactDOMServer - id: dom-elements @@ -133,19 +135,6 @@ title: Testing Recipes - id: testing-environments title: Testing Environments -- title: Concurrent Mode (Experimental) - isOrdered: true - items: - - id: concurrent-mode-intro - title: Introducing Concurrent Mode - - id: concurrent-mode-suspense - title: Suspense for Data Fetching - - id: concurrent-mode-patterns - title: Concurrent UI Patterns - - id: concurrent-mode-adoption - title: Adopting Concurrent Mode - - id: concurrent-mode-reference - title: Concurrent Mode API Reference - title: Contributing items: - id: how-to-contribute diff --git a/content/docs/optimizing-performance.md b/content/docs/optimizing-performance.md index 030cb059e..c32023555 100644 --- a/content/docs/optimizing-performance.md +++ b/content/docs/optimizing-performance.md @@ -43,8 +43,8 @@ Remember that this is only necessary before deploying to production. For normal We offer production-ready versions of React and React DOM as single files: ```html -<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> -<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> +<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> +<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> ``` Remember that only React files ending with `.production.min.js` are suitable for production. @@ -75,10 +75,10 @@ For the most efficient Browserify production build, install a few plugins: ``` # If you use npm -npm install --save-dev envify terser uglifyify +npm install --save-dev envify terser uglifyify # If you use Yarn -yarn add --dev envify terser uglifyify +yarn add --dev envify terser uglifyify ``` To create a production build, make sure that you add these transforms **(the order matters)**: @@ -156,32 +156,6 @@ You can learn more about this in [webpack documentation](https://webpack.js.org/ Remember that you only need to do this for production builds. You shouldn't apply `TerserPlugin` in development because it will hide useful React warnings, and make the builds much slower. -## Profiling Components with the Chrome Performance Tab {#profiling-components-with-the-chrome-performance-tab} - -In the **development** mode, you can visualize how components mount, update, and unmount, using the performance tools in supported browsers. For example: - -<center><img src="../images/blog/react-perf-chrome-timeline.png" style="max-width:100%" alt="React components in Chrome timeline" /></center> - -To do this in Chrome: - -1. Temporarily **disable all Chrome extensions, especially React DevTools**. They can significantly skew the results! - -2. Make sure you're running the application in the development mode. - -3. Open the Chrome DevTools **[Performance](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool)** tab and press **Record**. - -4. Perform the actions you want to profile. Don't record more than 20 seconds or Chrome might hang. - -5. Stop recording. - -6. React events will be grouped under the **User Timing** label. - -For a more detailed walkthrough, check out [this article by Ben Schwarz](https://calibreapp.com/blog/react-performance-profiling-optimization). - -Note that **the numbers are relative so components will render faster in production**. Still, this should help you realize when unrelated UI gets updated by mistake, and how deep and how often your UI updates occur. - -Currently Chrome, Edge, and IE are the only browsers supporting this feature, but we use the standard [User Timing API](https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API) so we expect more browsers to add support for it. - ## Profiling Components with the DevTools Profiler {#profiling-components-with-the-devtools-profiler} `react-dom` 16.5+ and `react-native` 0.57+ provide enhanced profiling capabilities in DEV mode with the React DevTools Profiler. @@ -199,9 +173,14 @@ If you haven't yet installed the React DevTools, you can find them here: > A production profiling bundle of `react-dom` is also available as `react-dom/profiling`. > Read more about how to use this bundle at [fb.me/react-profiling](https://fb.me/react-profiling) +> Note +> +> Before React 17, we use the standard [User Timing API](https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API) to profile components with the chrome performance tab. +> For a more detailed walkthrough, check out [this article by Ben Schwarz](https://calibreapp.com/blog/react-performance-profiling-optimization). + ## Virtualize Long Lists {#virtualize-long-lists} -If your application renders long lists of data (hundreds or thousands of rows), we recommended using a technique known as "windowing". This technique only renders a small subset of your rows at any given time, and can dramatically reduce the time it takes to re-render the components as well as the number of DOM nodes created. +If your application renders long lists of data (hundreds or thousands of rows), we recommend using a technique known as "windowing". This technique only renders a small subset of your rows at any given time, and can dramatically reduce the time it takes to re-render the components as well as the number of DOM nodes created. [react-window](https://react-window.now.sh/) and [react-virtualized](https://bvaughn.github.io/react-virtualized/) are popular windowing libraries. They provide several reusable components for displaying lists, grids, and tabular data. You can also create your own windowing component, like [Twitter did](https://medium.com/@paularmstrong/twitter-lite-and-high-performance-react-progressive-web-apps-at-scale-d28a00e780a3), if you want something more tailored to your application's specific use case. @@ -371,7 +350,7 @@ function updateColorMap(colormap) { `updateColorMap` now returns a new object, rather than mutating the old one. `Object.assign` is in ES6 and requires a polyfill. -There is a JavaScript proposal to add [object spread properties](https://github.com/sebmarkbage/ecmascript-rest-spread) to make it easier to update objects without mutation as well: +[Object spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) makes it easier to update objects without mutation as well: ```js function updateColorMap(colormap) { @@ -379,6 +358,8 @@ function updateColorMap(colormap) { } ``` +This feature was added to JavaScript in ES2018. + If you're using Create React App, both `Object.assign` and the object spread syntax are available by default. When you deal with deeply nested objects, updating them in an immutable way can feel convoluted. If you run into this problem, check out [Immer](https://github.com/mweststrate/immer) or [immutability-helper](https://github.com/kolodny/immutability-helper). These libraries let you write highly readable code without losing the benefits of immutability. diff --git a/content/docs/portals.md b/content/docs/portals.md index 650121396..7261ee04f 100644 --- a/content/docs/portals.md +++ b/content/docs/portals.md @@ -4,6 +4,14 @@ title: Portals permalink: docs/portals.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [`createPortal`](https://beta.reactjs.org/reference/react-dom/createPortal) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. ```js @@ -46,7 +54,7 @@ A typical use case for portals is when a parent component has an `overflow: hidd > > When working with portals, remember that [managing keyboard focus](/docs/accessibility.html#programmatically-managing-focus) becomes very important. > -> For modal dialogs, ensure that everyone can interact with them by following the [WAI-ARIA Modal Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal). +> For modal dialogs, ensure that everyone can interact with them by following the [WAI-ARIA Modal Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/). [**Try it on CodePen**](https://codepen.io/gaearon/pen/yzMaBd) @@ -97,7 +105,7 @@ class Modal extends React.Component { render() { return ReactDOM.createPortal( this.props.children, - this.el, + this.el ); } } @@ -146,7 +154,8 @@ function Child() { ); } -ReactDOM.render(<Parent />, appRoot); +const root = ReactDOM.createRoot(appRoot); +root.render(<Parent />); ``` [**Try it on CodePen**](https://codepen.io/gaearon/pen/jGBWpE) diff --git a/content/docs/react-without-es6.md b/content/docs/react-without-es6.md index 8b54d0981..bb2276f6f 100644 --- a/content/docs/react-without-es6.md +++ b/content/docs/react-without-es6.md @@ -134,7 +134,7 @@ var SayHello = createReactClass({ This means writing ES6 classes comes with a little more boilerplate code for event handlers, but the upside is slightly better performance in large applications. -If the boilerplate code is too unattractive to you, you may enable the **experimental** [Class Properties](https://babeljs.io/docs/plugins/transform-class-properties/) syntax proposal with Babel: +If the boilerplate code is too unattractive to you, you may use [ES2022 Class Properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields#public_instance_fields) syntax: ```javascript @@ -143,11 +143,11 @@ class SayHello extends React.Component { super(props); this.state = {message: 'Hello!'}; } - // WARNING: this syntax is experimental! + // Using an arrow here binds the method: handleClick = () => { alert(this.state.message); - } + }; render() { return ( @@ -159,9 +159,7 @@ class SayHello extends React.Component { } ``` -Please note that the syntax above is **experimental** and the syntax may change, or the proposal might not make it into the language. - -If you'd rather play it safe, you have a few options: +You also have a few other options: * Bind methods in the constructor. * Use arrow functions, e.g. `onClick={(e) => this.handleClick(e)}`. @@ -216,10 +214,8 @@ var TickTock = createReactClass({ } }); -ReactDOM.render( - <TickTock />, - document.getElementById('example') -); +const root = ReactDOM.createRoot(document.getElementById('example')); +root.render(<TickTock />); ``` If a component is using multiple mixins and several mixins define the same lifecycle method (i.e. several mixins want to do some cleanup when the component is destroyed), all of the lifecycle methods are guaranteed to be called. Methods defined on mixins run in the order mixins were listed, followed by a method call on the component. diff --git a/content/docs/react-without-jsx.md b/content/docs/react-without-jsx.md index 57a4c19f0..6b9329bff 100644 --- a/content/docs/react-without-jsx.md +++ b/content/docs/react-without-jsx.md @@ -17,10 +17,8 @@ class Hello extends React.Component { } } -ReactDOM.render( - <Hello toWhat="World" />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Hello toWhat="World" />); ``` μπορεί να "μεταφραστεί" σε αυτόν τον κώδικα που δεν χρησιμοποιεί JSX: @@ -32,10 +30,8 @@ class Hello extends React.Component { } } -ReactDOM.render( - React.createElement(Hello, {toWhat: 'World'}, null), - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(React.createElement(Hello, {toWhat: 'World'}, null)); ``` Αν είστε περίεργοι να δείτε περισσότερα παραδείγματα για το πώς το JSX μετατρέπεται σε JavaScript, μπορείτε να δοκιμάσετε [τον online Babel compiler](babel://jsx-simple-example). @@ -47,10 +43,8 @@ ReactDOM.render( ```js const e = React.createElement; -ReactDOM.render( - e('div', null, 'Hello World'), - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(e('div', null, 'Hello World')); ``` Εάν χρησιμοποιείτε αυτήν τη "σύντομη φόρμα" (shorthand form) για το `React.createElement`, μπορεί να είναι εξίσου βολικό να χρησιμοποιήσετε το React χωρίς JSX diff --git a/content/docs/reconciliation.md b/content/docs/reconciliation.md index ab8886b8d..56dfc9ac6 100644 --- a/content/docs/reconciliation.md +++ b/content/docs/reconciliation.md @@ -4,6 +4,14 @@ title: Reconciliation permalink: docs/reconciliation.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Preserving and Resetting State](https://beta.reactjs.org/learn/preserving-and-resetting-state) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + React provides a declarative API so that you don't have to worry about exactly what changes on every update. This makes writing applications a lot easier, but it might not be obvious how this is implemented within React. This article explains the choices we made in React's "diffing" algorithm so that component updates are predictable while being fast enough for high-performance apps. ## Motivation {#motivation} @@ -27,7 +35,7 @@ When diffing two trees, React first compares the two root elements. The behavior Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. Going from `<a>` to `<img>`, or from `<Article>` to `<Comment>`, or from `<Button>` to `<div>` - any of those will lead to a full rebuild. -When tearing down a tree, old DOM nodes are destroyed. Component instances receive `componentWillUnmount()`. When building up a new tree, new DOM nodes are inserted into the DOM. Component instances receive `componentWillMount()` and then `componentDidMount()`. Any state associated with the old tree is lost. +When tearing down a tree, old DOM nodes are destroyed. Component instances receive `componentWillUnmount()`. When building up a new tree, new DOM nodes are inserted into the DOM. Component instances receive `UNSAFE_componentWillMount()` and then `componentDidMount()`. Any state associated with the old tree is lost. Any components below the root will also get unmounted and have their state destroyed. For example, when diffing: @@ -43,6 +51,12 @@ Any components below the root will also get unmounted and have their state destr This will destroy the old `Counter` and remount a new one. +>Note: +> +>This method is considered legacy and you should [avoid it](/blog/2018/03/27/update-on-async-rendering.html) in new code: +> +>- `UNSAFE_componentWillMount()` + ### DOM Elements Of The Same Type {#dom-elements-of-the-same-type} When comparing two React DOM elements of the same type, React looks at the attributes of both, keeps the same underlying DOM node, and only updates the changed attributes. For example: @@ -69,10 +83,17 @@ After handling the DOM node, React then recurses on the children. ### Component Elements Of The Same Type {#component-elements-of-the-same-type} -When a component updates, the instance stays the same, so that state is maintained across renders. React updates the props of the underlying component instance to match the new element, and calls `componentWillReceiveProps()` and `componentWillUpdate()` on the underlying instance. +When a component updates, the instance stays the same, so that state is maintained across renders. React updates the props of the underlying component instance to match the new element, and calls `UNSAFE_componentWillReceiveProps()`, `UNSAFE_componentWillUpdate()` and `componentDidUpdate()` on the underlying instance. Next, the `render()` method is called and the diff algorithm recurses on the previous result and the new result. +>Note: +> +>These methods are considered legacy and you should [avoid them](/blog/2018/03/27/update-on-async-rendering.html) in new code: +> +>- `UNSAFE_componentWillUpdate()` +>- `UNSAFE_componentWillReceiveProps()` + ### Recursing On Children {#recursing-on-children} By default, when recursing on the children of a DOM node, React just iterates over both lists of children at the same time and generates a mutation whenever there's a difference. diff --git a/content/docs/reference-dom-elements.md b/content/docs/reference-dom-elements.md index 52e780b96..aa6d136d0 100644 --- a/content/docs/reference-dom-elements.md +++ b/content/docs/reference-dom-elements.md @@ -14,6 +14,19 @@ redirect_from: - "tips/dangerously-set-inner-html.html" --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Common components (e.g. `<div>`)](https://beta.reactjs.org/reference/react-dom/components/common) +> - [`<input>`](https://beta.reactjs.org/reference/react-dom/components/input) +> - [`<option>`](https://beta.reactjs.org/reference/react-dom/components/option) +> - [`<progress>`](https://beta.reactjs.org/reference/react-dom/components/progress) +> - [`<select>`](https://beta.reactjs.org/reference/react-dom/components/select) +> - [`<textarea>`](https://beta.reactjs.org/reference/react-dom/components/textarea) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + React implements a browser-independent DOM system for performance and cross-browser compatibility. We took the opportunity to clean up a few rough edges in browser DOM implementations. In React, all DOM properties and attributes (including event handlers) should be camelCased. For example, the HTML attribute `tabindex` corresponds to the attribute `tabIndex` in React. The exception is `aria-*` and `data-*` attributes, which should be lowercased. For example, you can keep `aria-label` as `aria-label`. @@ -56,7 +69,8 @@ The `onChange` event behaves as you would expect it to: whenever a form field is ### selected {#selected} -The `selected` attribute is supported by `<option>` components. You can use it to set whether the component is selected. This is useful for building controlled components. +If you want to mark an `<option>` as selected, reference the value of that option in the `value` of its `<select>` instead. +Check out ["The select Tag"](/docs/forms.html#the-select-tag) for detailed instructions. ### style {#style} @@ -116,11 +130,11 @@ Normally, there is a warning when an element with children is also marked as `co If you use server-side React rendering, normally there is a warning when the server and the client render different content. However, in some rare cases, it is very hard or impossible to guarantee an exact match. For example, timestamps are expected to differ on the server and on the client. -If you set `suppressHydrationWarning` to `true`, React will not warn you about mismatches in the attributes and the content of that element. It only works one level deep, and is intended to be used as an escape hatch. Don't overuse it. You can read more about hydration in the [`ReactDOM.hydrate()` documentation](/docs/react-dom.html#hydrate). +If you set `suppressHydrationWarning` to `true`, React will not warn you about mismatches in the attributes and the content of that element. It only works one level deep, and is intended to be used as an escape hatch. Don't overuse it. You can read more about hydration in the [`ReactDOM.hydrateRoot()` documentation](/docs/react-dom-client.html#hydrateroot). ### value {#value} -The `value` attribute is supported by `<input>` and `<textarea>` components. You can use it to set the value of the component. This is useful for building controlled components. `defaultValue` is the uncontrolled equivalent, which sets the value of the component when it is first mounted. +The `value` attribute is supported by `<input>`, `<select>` and `<textarea>` components. You can use it to set the value of the component. This is useful for building controlled components. `defaultValue` is the uncontrolled equivalent, which sets the value of the component when it is first mounted. ## All Supported HTML Attributes {#all-supported-html-attributes} @@ -129,7 +143,7 @@ As of React 16, any standard [or custom](/blog/2017/09/08/dom-attributes-in-reac React has always provided a JavaScript-centric API to the DOM. Since React components often take both custom and DOM-related props, React uses the `camelCase` convention just like the DOM APIs: ```js -<div tabIndex="-1" /> // Just like node.tabIndex DOM API +<div tabIndex={-1} /> // Just like node.tabIndex DOM API <div className="Button" /> // Just like node.className DOM API <input readOnly={true} /> // Just like node.readOnly DOM API ``` diff --git a/content/docs/reference-events.md b/content/docs/reference-events.md index 705d6b306..9b82f7682 100644 --- a/content/docs/reference-events.md +++ b/content/docs/reference-events.md @@ -6,13 +6,31 @@ layout: docs category: Reference --- +<<<<<<< HEAD Αυτός ο οδηγός καταγράφει το `SyntheticEvent` wrapper που αποτελεί κομμάτι του Event System του React. Δείτε τον οδηγό [Διαχείριση των Events](/docs/handling-events.html) για να μάθετε περισσότερα. +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Common components (e.g. `<div>`)](https://beta.reactjs.org/reference/react-dom/components/common) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +This reference guide documents the `SyntheticEvent` wrapper that forms part of React's Event System. See the [Handling Events](/docs/handling-events.html) guide to learn more. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ## Επισκόπηση {#overview} +<<<<<<< HEAD Οι event handlers θα λάβουν instances του `SyntheticEvent`, ένα cross-browser wrapper γυρω απο το native event του browser. Έχει το ιδιο interface με το native event του browser, συμπεριλαμβανομένου του `stopPropagation()` και του `preventDefault()`, με τη διαφορά πως τα events αυτά έχουν ακριβώς την ιδια συμπεριφορά σε ολους τους browsers. Εαν για κάποιο λογο χρειάζεστε το event του browser, απλώς χρησιμοποιήστε το `nativeEvent` attribute για να το πάρετε. Κάθε `SyntheticEvent` αντικείμενο έχει τα ακόλουθα attributes: +======= +Your event handlers will be passed instances of `SyntheticEvent`, a cross-browser wrapper around the browser's native event. It has the same interface as the browser's native event, including `stopPropagation()` and `preventDefault()`, except the events work identically across all browsers. + +If you find that you need the underlying browser event for some reason, simply use the `nativeEvent` attribute to get it. The synthetic events are different from, and do not map directly to, the browser's native events. For example in `onMouseLeave` `event.nativeEvent` will point to a `mouseout` event. The specific mapping is not part of the public API and may change at any time. Every `SyntheticEvent` object has the following attributes: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```javascript boolean bubbles @@ -34,6 +52,7 @@ string type > Σημείωση: > +<<<<<<< HEAD > Απο την v0.14, επιστρέφοντας `false` από ένα event handler δε θα σταματάει πλεον τη διάδοση του event. Αντί για αυτό, το `e.stopPropagation()` είτε το `e.preventDefault()` πρέπει να καλούνται, ανάλογα την περίπτωση. ### Event Pooling {#event-pooling} @@ -60,10 +79,17 @@ function onClick(event) { this.setState({eventType: event.type}); } ``` +======= +> As of v17, `e.persist()` doesn't do anything because the `SyntheticEvent` is no longer [pooled](/docs/legacy-event-pooling.html). +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a > Σημείωση: > +<<<<<<< HEAD > Αν θέλετε να έχετε πρόσβαση στις ιδιότητες του event με ασύγχρονο τρόπο, μπορείτε να καλέσετε την `event.persist()` στο event, η οποία θα αφαιρέσει το synthetic event από το pool και θα επιτρέψει αναφορές στο event να διατηρούνται στον κώδικα. +======= +> As of v0.14, returning `false` from an event handler will no longer stop event propagation. Instead, `e.stopPropagation()` or `e.preventDefault()` should be triggered manually, as appropriate. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ## Υποστηριζόμενα Events {#supported-events} @@ -167,10 +193,83 @@ These focus events work on all elements in the React DOM, not just form elements Ιδιότητες: -```javascript +```js DOMEventTarget relatedTarget ``` +#### onFocus {#onfocus} + +The `onFocus` event is called when the element (or some element inside of it) receives focus. For example, it's called when the user clicks on a text input. + +```javascript +function Example() { + return ( + <input + onFocus={(e) => { + console.log('Focused on input'); + }} + placeholder="onFocus is triggered when you click this input." + /> + ) +} +``` + +#### onBlur {#onblur} + +The `onBlur` event handler is called when focus has left the element (or left some element inside of it). For example, it's called when the user clicks outside of a focused text input. + +```javascript +function Example() { + return ( + <input + onBlur={(e) => { + console.log('Triggered because this input lost focus'); + }} + placeholder="onBlur is triggered when you click this input and then you click outside of it." + /> + ) +} +``` + +#### Detecting Focus Entering and Leaving {#detecting-focus-entering-and-leaving} + +You can use the `currentTarget` and `relatedTarget` to differentiate if the focusing or blurring events originated from _outside_ of the parent element. Here is a demo you can copy and paste that shows how to detect focusing a child, focusing the element itself, and focus entering or leaving the whole subtree. + +```javascript +function Example() { + return ( + <div + tabIndex={1} + onFocus={(e) => { + if (e.currentTarget === e.target) { + console.log('focused self'); + } else { + console.log('focused child', e.target); + } + if (!e.currentTarget.contains(e.relatedTarget)) { + // Not triggered when swapping focus between children + console.log('focus entered self'); + } + }} + onBlur={(e) => { + if (e.currentTarget === e.target) { + console.log('unfocused self'); + } else { + console.log('unfocused child', e.target); + } + if (!e.currentTarget.contains(e.relatedTarget)) { + // Not triggered when swapping focus between children + console.log('focus left self'); + } + }} + > + <input id="1" /> + <input id="2" /> + </div> + ); +} +``` + * * * ### Form Events {#form-events} @@ -305,7 +404,15 @@ DOMTouchList touches onScroll ``` +<<<<<<< HEAD Ιδιότητες: +======= +>Note +> +>Starting with React 17, the `onScroll` event **does not bubble** in React. This matches the browser behavior and prevents the confusion when a nested scrollable element fires events on a distant parent. + +Properties: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```javascript number detail diff --git a/content/docs/reference-glossary.md b/content/docs/reference-glossary.md index bdf3587a5..eb008b226 100644 --- a/content/docs/reference-glossary.md +++ b/content/docs/reference-glossary.md @@ -39,13 +39,9 @@ JSX is a syntax extension to JavaScript. It is similar to a template language, b React DOM uses camelCase property naming convention instead of HTML attribute names. For example, `tabindex` becomes `tabIndex` in JSX. The attribute `class` is also written as `className` since `class` is a reserved word in JavaScript: -```js -const name = 'Clementine'; -ReactDOM.render( - <h1 className="hello">My name is {name}!</h1>, - document.getElementById('root') -); -``` +```jsx +<h1 className="hello">My name is Clementine!</h1> +``` ## [Elements](/docs/rendering-elements.html) {#elements} diff --git a/content/docs/reference-javascript-environment-requirements.md b/content/docs/reference-javascript-environment-requirements.md index 59ff2280c..6fa5ba4ac 100644 --- a/content/docs/reference-javascript-environment-requirements.md +++ b/content/docs/reference-javascript-environment-requirements.md @@ -6,14 +6,22 @@ category: Reference permalink: docs/javascript-environment-requirements.html --- +<<<<<<< HEAD Το React 16 βασίζεται στους collection types [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) και [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). Σε περίπτωση που υποστηρίζετε παλιότερους browsers και συσκευές που δεν παρέχουν αυτές τις υλοποιήσεις (π.χ. IE < 11) ή δεν ειναι συμβατές (π.χ. IE 11), εξετάστε το ενδεχόμενο να συμπεριλάβετε ένα global polyfill στο bundle της εφαρμογής σας, όπως για παράδειγμα το [core-js](https://github.com/zloirock/core-js) ή το [babel-polyfill](https://babeljs.io/docs/usage/polyfill/). Ένα polyfilled περιβάλλον για το React 16 με χρήση του core-js που να υποστηρίζει παλιότερους browsers ενδέχεται να μοιάζει κάπως έτσι : +======= +React 18 supports all modern browsers (Edge, Firefox, Chrome, Safari, etc). -```js -import 'core-js/es/map'; -import 'core-js/es/set'; +If you support older browsers and devices such as Internet Explorer which do not provide modern browser features natively or have non-compliant implementations, consider including a global polyfill in your bundled application. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a + +Here is a list of the modern features React 18 uses: +- [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +- [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) +- [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) +<<<<<<< HEAD import React from 'react'; import ReactDOM from 'react-dom'; @@ -29,3 +37,6 @@ ReactDOM.render( ```js import 'raf/polyfill'; ``` +======= +The correct polyfill for these features depend on your environment. For many users, you can configure your [Browserlist](https://github.com/browserslist/browserslist) settings. For others, you may need to import polyfills like [`core-js`](https://github.com/zloirock/core-js) directly. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a diff --git a/content/docs/reference-profiler.md b/content/docs/reference-profiler.md index 1a20b6991..e361f7fa4 100644 --- a/content/docs/reference-profiler.md +++ b/content/docs/reference-profiler.md @@ -49,7 +49,7 @@ render( ``` `Profiler` components can also be nested to measure different components within the same subtree: -```js{2,6,8} +```js{3,5,8} render( <App> <Profiler id="Panel" onRender={callback}> diff --git a/content/docs/reference-pure-render-mixin.md b/content/docs/reference-pure-render-mixin.md index 1b5b56b9c..8f1ac1217 100644 --- a/content/docs/reference-pure-render-mixin.md +++ b/content/docs/reference-pure-render-mixin.md @@ -46,6 +46,6 @@ Under the hood, the mixin implements [shouldComponentUpdate](/docs/component-spe > Note: > -> This only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only mix into components which have simple props and state, or use `forceUpdate()` when you know deep data structures have changed. Or, consider using [immutable objects](https://facebook.github.io/immutable-js/) to facilitate fast comparisons of nested data. +> This only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only mix into components which have simple props and state, or use `forceUpdate()` when you know deep data structures have changed. Or, consider using [immutable objects](https://immutable-js.com/) to facilitate fast comparisons of nested data. > > Furthermore, `shouldComponentUpdate` skips updates for the whole component subtree. Make sure all the children components are also "pure". diff --git a/content/docs/reference-react-component.md b/content/docs/reference-react-component.md index 9a4375da0..3340dd731 100644 --- a/content/docs/reference-react-component.md +++ b/content/docs/reference-react-component.md @@ -15,7 +15,15 @@ redirect_from: - "tips/use-react-with-other-libraries.html" --- +<<<<<<< HEAD Αυτή η σελίδα περιλαμβάνει ένα αναλυτικό API reference για τον ορισμό του React component class. Υποθέτει πως έχετε γνώση από τις βασικές έννοιες του React, όπως για παράδειγμα [Components and Props](/docs/components-and-props.html), αλλά και τα [State and Lifecycle](/docs/state-and-lifecycle.html). Εάν όχι, διαβάστε τα προτού ξεκινήσετε. +======= +> Try the new React documentation for [`Component`](https://beta.reactjs.org/reference/react/Component). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +This page contains a detailed API reference for the React component class definition. It assumes you're familiar with fundamental React concepts, such as [Components and Props](/docs/components-and-props.html), as well as [State and Lifecycle](/docs/state-and-lifecycle.html). If you're not, read them first. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ## Overview {#overview} @@ -40,7 +48,11 @@ class Welcome extends React.Component { ### The Component Lifecycle {#the-component-lifecycle} +<<<<<<< HEAD Κάθε component έχει αρκετά "lifecycle methods" που μπορείτε να κάνετε override για να τρέξετε κώδικα σε συγκεκριμένα χρονικά σημεία. **Μπορείτε να χρησιμοποιήσετε [αυτό το lifecycle διάγραμμα](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) σαν ένα cheat sheet.** Στη παρακάτω λίστα, τα lifecycle methods με την πιο κοινή χρήση είναι σημειωμένα με **bold**. Τα υπόλοιπα υπάρχουν για σχετικά σπάνιες περιπτώσεις. +======= +Each component has several "lifecycle methods" that you can override to run code at particular times in the process. **You can use [this lifecycle diagram](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) as a cheat sheet.** In the list below, commonly used lifecycle methods are marked as **bold**. The rest of them exist for relatively rare use cases. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a #### Mounting {#mounting} @@ -54,7 +66,11 @@ class Welcome extends React.Component { >Σημείωση: > +<<<<<<< HEAD >Αυτές οι μέθοδοι θεωρούνται legacy και συνηστούμε να τις [αποφύγετε](/blog/2018/03/27/update-on-async-rendering.html) όταν γράφετε καινούργιο κώδικα: +======= +>This method is considered legacy and you should [avoid it](/blog/2018/03/27/update-on-async-rendering.html) in new code: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a > >- [`UNSAFE_componentWillMount()`](#unsafe_componentwillmount) @@ -112,7 +128,11 @@ class Welcome extends React.Component { ### Συχνά χρησιμοποιούμενες Lifecycle Μέθοδοι {#commonly-used-lifecycle-methods} +<<<<<<< HEAD Οι μέθοδοι σε αυτή την ενότητα καλύπτουν τη πλειοψηφία των use cases που θα συναντήσετε όταν δημιουργείτε React components. **Για μια οπτική παραπομπή, δείτε αυτό [το lifecycle διάγραμμα](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/).** +======= +The methods in this section cover the vast majority of use cases you'll encounter creating React components. **For a visual reference, check out [this lifecycle diagram](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/).** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### `render()` {#render} @@ -124,11 +144,19 @@ render() Οταν καλείται, πρέπει να εξετάζει τα `this.props` και `this.state` και να επιστρέφει έναν από τους παρακάτω τύπους: +<<<<<<< HEAD - **React elements.** Συνήθως δημιουργούνται με [JSX](/docs/introducing-jsx.html). Για παράδειγμα, το `<div />` και το `<MyComponent />` είναι React elements που δίνουν εντολή στο React να κάνει render ένα DOM node, ή κάποιο άλλο user-defined component, αντίστοιχα. - **Arrays and fragments.** Επιτρέπουν να επιστρέψετε διάφορα elements από το render. Δείτε το documentation για τα [fragments](/docs/fragments.html) για περισσότερες λεπτομέρειες. - **Portals**. Επιτρέπουν να κάνετε render παιδιά σε ένα διαφορετικό DOM subtree. Δείτε το documentation για τα [portals](/docs/portals.html) για περισσότερες λεπτομέρειες. - **String and numbers.** Αυτά γίνονται rendered σαν text nodes στο DOM. - **Booleans or `null`**. Δεν κάνουν τίποτα render . (Κυρίως υπάρχουν για να υποστηρίζουν το `return test && <Child />` πρότυπο, όπου το `test` είναι boolean.) +======= +- **React elements.** Typically created via [JSX](/docs/introducing-jsx.html). For example, `<div />` and `<MyComponent />` are React elements that instruct React to render a DOM node, or another user-defined component, respectively. +- **Arrays and fragments.** Let you return multiple elements from render. See the documentation on [fragments](/docs/fragments.html) for more details. +- **Portals**. Let you render children into a different DOM subtree. See the documentation on [portals](/docs/portals.html) for more details. +- **String and numbers.** These are rendered as text nodes in the DOM. +- **Booleans or `null` or `undefined`**. Render nothing. (Mostly exists to support `return test && <Child />` pattern, where `test` is boolean). +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Η `render()` συνάρτηση πρέπει να είναι pure, δηλαδή να μην τροποποιεί το component state, να επιστρέφει το ίδιο αποτέλεσμα κάθε φορά που καλείται, και να μην αλληλεπιδρά απευθείας με τον browser. @@ -250,7 +278,11 @@ componentWillUnmount() ### Σπάνια χρησιμοποιούμενες Lifecycle Methods {#rarely-used-lifecycle-methods} +<<<<<<< HEAD Οι μέθοδοι σε αυτή την ενότητα αντιστοιχούν σε σπάνιες περιπτώσεις. Είναι χρήσιμες μια στο τόσο, αλλά τα περισσότερα απο τα components σας δε θα τις χρειαστούν. **Μπορείτε να δείτε τις περισσότερες απο αυτές στο [lifecycle diagram](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) εάν κάνετε κλικ στο "Show less common lifecycles" checkbox στην κορυφή.** +======= +The methods in this section correspond to uncommon use cases. They're handy once in a while, but most of your components probably don't need any of them. **You can see most of the methods below on [this lifecycle diagram](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) if you click the "Show less common lifecycles" checkbox at the top of it.** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### `shouldComponentUpdate()` {#shouldcomponentupdate} @@ -280,12 +312,21 @@ shouldComponentUpdate(nextProps, nextState) static getDerivedStateFromProps(props, state) ``` +<<<<<<< HEAD Το `getDerivedStateFromProps` καλείται ακριβώς πριν να κληθεί η render μέθοδος, τόσο στο initial mount αλλά και στα επόμενα updates. Πρέπει να επιστρέψει ενα αντικείμενο για να κάνει update το state, ή null για να μην κάνει update τίποτα. +======= +`getDerivedStateFromProps` is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or `null` to update nothing. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Αυτή η μέθοδος υπάρχει για [σπάνιες περιπτωσεις](/blog/2018/06/07/you-probably-dont-need-derived-state.html#when-to-use-derived-state) όπου το state βασίζεται σε αλλαγές στα props κατά το πέρασμα του χρόνου. Για παράδειγμα, μπορεί να αποδειχθεί βολικό για την υλοποίηση ενος `<Transition>` component το οποίο συγκρίνει τα προηγουμενα με τα επόμενα children για να αποφασίσει ποια θα κάνει animate in και out. +<<<<<<< HEAD Το deriving του state οδηγεί σε verbose κώδικα και κάνει τα components δυσνόητα. [Σιγουρευτείτε πως είστε εξοικοιωμένοι με πιο απλές λύσεις:](/blog/2018/06/07/you-probably-dont-need-derived-state.html) +======= +Deriving state leads to verbose code and makes your components difficult to think about. +[Make sure you're familiar with simpler alternatives:](/blog/2018/06/07/you-probably-dont-need-derived-state.html) +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a * Εαν χρειαστει να **εκτελέσετε καποιο side effect** (για παράδειγμα, data fetching ή κάποιο animation) ως απάντηση σε αλλαγές των props, χρησιμοποιήστε το [`componentDidUpdate`](#componentdidupdate). @@ -306,7 +347,11 @@ static getDerivedStateFromProps(props, state) getSnapshotBeforeUpdate(prevProps, prevState) ``` +<<<<<<< HEAD Το `getSnapshotBeforeUpdate()` καλείται ακριβώς πριν το πιο πρόσφατο rendered output γίνει committed π.χ. στο DOM. Επιτρέπει το component σας να μπορεί να κάνει capture κάποιες πληροφοριες από το DOM (π.χ. τη θεση του scrolling) πριν από κάποια ενδεχόμενη αλλαγή. Όποία τιμή επιστραφεί από αυτή τη μέθοδο θα περαστεί σαν παράμετρος στο `componentDidUpdate()`. +======= +`getSnapshotBeforeUpdate()` is invoked right before the most recently rendered output is committed to e.g. the DOM. It enables your component to capture some information from the DOM (e.g. scroll position) before it is potentially changed. Any value returned by this lifecycle method will be passed as a parameter to `componentDidUpdate()`. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Η συγκεκριμενη περίπτωση δεν είναι συνηθισμένη, αλλά μπορεί να εμφανιστεί σε UIs όπως ενα chat thread όπου χρειάζεται να χειρίζεστε το scroll position με συγκεκριμένο τρόπο. @@ -330,9 +375,15 @@ getSnapshotBeforeUpdate(prevProps, prevState) Για περισσότερες πληροφορίες, δείτε το [*Error Handling in React 16*](/blog/2017/07/26/error-handling-in-react-16.html). +<<<<<<< HEAD > Σημείωση > > Τα error boundaries πιάνουν errors μόνο στα components που βρίσκονται **κάτω** από αυτά στο δεντρο. Ενα error boundary δεν μπορεί να πιάσει ένα errοr μέσα του. +======= +> Note +> +> Error boundaries only catch errors in the components **below** them in the tree. An error boundary can’t catch an error within itself. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ### `static getDerivedStateFromError()` {#static-getderivedstatefromerror} ```javascript @@ -419,10 +470,23 @@ class ErrorBoundary extends React.Component { } ``` +<<<<<<< HEAD > Σημείωση > > Στην περίπτωση ενός error, μπορείτε να κάνετε render ένα fallback UI με το `componentDidCatch()` καλώντας το `setState`, αλλά αυτό θα καταργηθεί σε καποια μελλοντική έκδοση. > Χρησιμοποιήστε `static getDerivedStateFromError()` για να χειριστείτε fallback rendering. +======= +Production and development builds of React slightly differ in the way `componentDidCatch()` handles errors. + +On development, the errors will bubble up to `window`, this means that any `window.onerror` or `window.addEventListener('error', callback)` will intercept the errors that have been caught by `componentDidCatch()`. + +On production, instead, the errors will not bubble up, which means any ancestor error handler will only receive errors not explicitly caught by `componentDidCatch()`. + +> Note +> +> In the event of an error, you can render a fallback UI with `componentDidCatch()` by calling `setState`, but this will be deprecated in a future release. +> Use `static getDerivedStateFromError()` to handle fallback rendering instead. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a * * * @@ -506,12 +570,16 @@ UNSAFE_componentWillUpdate(nextProps, nextState) ### `setState()` {#setstate} ```javascript -setState(updater, [callback]) +setState(updater[, callback]) ``` `setState()` κάνει enqueue τις αλλαγές στο state του component και ενημερώνει το React πως αυτό το component και τα παιδια του χρειάζεται να γινει re-rendered με το ανανεωμένο state. Αυτή είναι η κύρια μέθοδος που χρησιμοποιείτε για να κάνετε update το user interface σε απόκριση των event handlers και απαντησεις απο servers. +<<<<<<< HEAD Σκεφτείτε το `setState()` σα μια *αιτηση* παρά σαν μια άμεση εντολή για να γίνει update το component. Για ακόμα καλύτερο performance, το React μπορεί να την καθυστερήσει, και κατόπιν να κάνει update αρκετά components σε ενα πέρασμα. To React δεν εγγυάται πως οι αλλαγές στο state θα γινουν άμεσα. +======= +Think of `setState()` as a *request* rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. In the rare case that you need to force the DOM update to be applied synchronously, you may wrap it in [`flushSync`](/docs/react-dom.html#flushsync), but this may hurt performance. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a `setState()` δεν κάνει άμέσα update το component. Μπορεί να συσσωρευσει ειτε και να καθυστερησει το update. Το αποτελεσμα αυτόυ είναι η αναγνωση της `this.state` ακριβώς αφου καλεσετε την `setState()` να αποτελει εναν πιθανο κινδυνο. Απεναντιας, χρησιμοποιήστε την `componentDidUpdate` είτε την `setState` callback (`setState(updater, callback)`), οπου και οι δυο είναι εγγυημενες πως θα κληθουν αφου γινει το update. Εαν χρειάζεστε να σετάρετε το state με βαση το προηγουμενο state, διαβάστε για την `updater` παραμετρο παρακάτω. @@ -595,7 +663,11 @@ component.forceUpdate(callback) ### `defaultProps` {#defaultprops} +<<<<<<< HEAD `defaultProps` can be defined as a property on the component class itself, to set the default props for the class. This is used for undefined props, but not for null props. Για παράδειγμα: +======= +`defaultProps` can be defined as a property on the component class itself, to set the default props for the class. This is used for `undefined` props, but not for `null` props. For example: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```js class CustomButton extends React.Component { @@ -615,7 +687,11 @@ CustomButton.defaultProps = { } ``` +<<<<<<< HEAD Εαν `props.color` εχει σεταριστεί στη τιμη null, τότε θα παραμείνει null: +======= +If `props.color` is set to `null`, it will remain `null`: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ```js render() { diff --git a/content/docs/reference-react-dom-client.md b/content/docs/reference-react-dom-client.md new file mode 100644 index 000000000..76b47ffec --- /dev/null +++ b/content/docs/reference-react-dom-client.md @@ -0,0 +1,95 @@ +--- +id: react-dom-client +title: ReactDOMClient +layout: docs +category: Reference +permalink: docs/react-dom-client.html +--- + +The `react-dom/client` package provides client-specific methods used for initializing an app on the client. Most of your components should not need to use this module. + +```js +import * as ReactDOM from 'react-dom/client'; +``` + +If you use ES5 with npm, you can write: + +```js +var ReactDOM = require('react-dom/client'); +``` + +## Overview {#overview} + +The following methods can be used in client environments: + +- [`createRoot()`](#createroot) +- [`hydrateRoot()`](#hydrateroot) + +### Browser Support {#browser-support} + +React supports all modern browsers, although [some polyfills are required](/docs/javascript-environment-requirements.html) for older versions. + +> Note +> +> We do not support older browsers that don't support ES5 methods or microtasks such as Internet Explorer. You may find that your apps do work in older browsers if polyfills such as [es5-shim and es5-sham](https://github.com/es-shims/es5-shim) are included in the page, but you're on your own if you choose to take this path. + +## Reference {#reference} + +### `createRoot()` {#createroot} + +> Try the new React documentation for [`createRoot`](https://beta.reactjs.org/reference/react-dom/client/createRoot). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +```javascript +createRoot(container[, options]); +``` + +Create a React root for the supplied `container` and return the root. The root can be used to render a React element into the DOM with `render`: + +```javascript +const root = createRoot(container); +root.render(element); +``` + +`createRoot` accepts two options: +- `onRecoverableError`: optional callback called when React automatically recovers from errors. +- `identifierPrefix`: optional prefix React uses for ids generated by `React.useId`. Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix used on the server. + +The root can also be unmounted with `unmount`: + +```javascript +root.unmount(); +``` + +> Note: +> +> `createRoot()` controls the contents of the container node you pass in. Any existing DOM elements inside are replaced when render is called. Later calls use React’s DOM diffing algorithm for efficient updates. +> +> `createRoot()` does not modify the container node (only modifies the children of the container). It may be possible to insert a component to an existing DOM node without overwriting the existing children. +> +> Using `createRoot()` to hydrate a server-rendered container is not supported. Use [`hydrateRoot()`](#hydrateroot) instead. + +* * * + +### `hydrateRoot()` {#hydrateroot} + +> Try the new React documentation for [`hydrateRoot`](https://beta.reactjs.org/reference/react-dom/client/hydrateRoot). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + +```javascript +hydrateRoot(container, element[, options]) +``` + +Same as [`createRoot()`](#createroot), but is used to hydrate a container whose HTML contents were rendered by [`ReactDOMServer`](/docs/react-dom-server.html). React will attempt to attach event listeners to the existing markup. + +`hydrateRoot` accepts two options: +- `onRecoverableError`: optional callback called when React automatically recovers from errors. +- `identifierPrefix`: optional prefix React uses for ids generated by `React.useId`. Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix used on the server. + + +> Note +> +> React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them. In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive. diff --git a/content/docs/reference-react-dom-server.md b/content/docs/reference-react-dom-server.md index 80c030377..efb06d3aa 100644 --- a/content/docs/reference-react-dom-server.md +++ b/content/docs/reference-react-dom-server.md @@ -10,60 +10,163 @@ The `ReactDOMServer` object enables you to render components to static markup. T ```js // ES modules -import ReactDOMServer from 'react-dom/server'; +import * as ReactDOMServer from 'react-dom/server'; // CommonJS var ReactDOMServer = require('react-dom/server'); ``` ## Overview {#overview} -The following methods can be used in both the server and browser environments: +These methods are only available in the **environments with [Node.js Streams](https://nodejs.org/api/stream.html):** + +- [`renderToPipeableStream()`](#rendertopipeablestream) +- [`renderToNodeStream()`](#rendertonodestream) (Deprecated) +- [`renderToStaticNodeStream()`](#rendertostaticnodestream) + +These methods are only available in the **environments with [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API)** (this includes browsers, Deno, and some modern edge runtimes): + +- [`renderToReadableStream()`](#rendertoreadablestream) + +The following methods can be used in the environments that don't support streams: - [`renderToString()`](#rendertostring) - [`renderToStaticMarkup()`](#rendertostaticmarkup) -These additional methods depend on a package (`stream`) that is **only available on the server**, and won't work in the browser. +## Reference {#reference} -- [`renderToNodeStream()`](#rendertonodestream) -- [`renderToStaticNodeStream()`](#rendertostaticnodestream) +### `renderToPipeableStream()` {#rendertopipeablestream} -* * * +> Try the new React documentation for [`renderToPipeableStream`](https://beta.reactjs.org/reference/react-dom/server/renderToPipeableStream). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) -## Reference {#reference} +```javascript +ReactDOMServer.renderToPipeableStream(element, options) +``` -### `renderToString()` {#rendertostring} +Render a React element to its initial HTML. Returns a stream with a `pipe(res)` method to pipe the output and `abort()` to abort the request. Fully supports Suspense and streaming of HTML with "delayed" content blocks "popping in" via inline `<script>` tags later. [Read more](https://github.com/reactwg/react-18/discussions/37) + +If you call [`ReactDOM.hydrateRoot()`](/docs/react-dom-client.html#hydrateroot) on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. ```javascript -ReactDOMServer.renderToString(element) +let didError = false; +const stream = renderToPipeableStream( + <App />, + { + onShellReady() { + // The content above all Suspense boundaries is ready. + // If something errored before we started streaming, we set the error code appropriately. + res.statusCode = didError ? 500 : 200; + res.setHeader('Content-type', 'text/html'); + stream.pipe(res); + }, + onShellError(error) { + // Something errored before we could complete the shell so we emit an alternative shell. + res.statusCode = 500; + res.send( + '<!doctype html><p>Loading...</p><script src="clientrender.js"></script>' + ); + }, + onAllReady() { + // If you don't want streaming, use this instead of onShellReady. + // This will fire after the entire page content is ready. + // You can use this for crawlers or static generation. + + // res.statusCode = didError ? 500 : 200; + // res.setHeader('Content-type', 'text/html'); + // stream.pipe(res); + }, + onError(err) { + didError = true; + console.error(err); + }, + } +); ``` -Render a React element to its initial HTML. React will return an HTML string. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes. +See the [full list of options](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-dom/src/server/ReactDOMFizzServerNode.js#L36-L46). -If you call [`ReactDOM.hydrate()`](/docs/react-dom.html#hydrate) on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. +> Note: +> +> This is a Node.js-specific API. Environments with [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API), like Deno and modern edge runtimes, should use [`renderToReadableStream`](#rendertoreadablestream) instead. +> * * * -### `renderToStaticMarkup()` {#rendertostaticmarkup} +### `renderToReadableStream()` {#rendertoreadablestream} + +> Try the new React documentation for [`renderToReadableStream`](https://beta.reactjs.org/reference/react-dom/server/renderToReadableStream). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) ```javascript -ReactDOMServer.renderToStaticMarkup(element) +ReactDOMServer.renderToReadableStream(element, options); ``` -Similar to [`renderToString`](#rendertostring), except this doesn't create extra DOM attributes that React uses internally, such as `data-reactroot`. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save some bytes. +Streams a React element to its initial HTML. Returns a Promise that resolves to a [Readable Stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). Fully supports Suspense and streaming of HTML. [Read more](https://github.com/reactwg/react-18/discussions/127) + +If you call [`ReactDOM.hydrateRoot()`](/docs/react-dom-client.html#hydrateroot) on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. -If you plan to use React on the client to make the markup interactive, do not use this method. Instead, use [`renderToString`](#rendertostring) on the server and [`ReactDOM.hydrate()`](/docs/react-dom.html#hydrate) on the client. +```javascript +let controller = new AbortController(); +let didError = false; +try { + let stream = await renderToReadableStream( + <html> + <body>Success</body> + </html>, + { + signal: controller.signal, + onError(error) { + didError = true; + console.error(error); + } + } + ); + + // This is to wait for all Suspense boundaries to be ready. You can uncomment + // this line if you want to buffer the entire HTML instead of streaming it. + // You can use this for crawlers or static generation: + + // await stream.allReady; + + return new Response(stream, { + status: didError ? 500 : 200, + headers: {'Content-Type': 'text/html'}, + }); +} catch (error) { + return new Response( + '<!doctype html><p>Loading...</p><script src="clientrender.js"></script>', + { + status: 500, + headers: {'Content-Type': 'text/html'}, + } + ); +} +``` + +See the [full list of options](https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js#L27-L35). + +> Note: +> +> This API depends on [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API). For Node.js, use [`renderToPipeableStream`](#rendertopipeablestream) instead. +> * * * -### `renderToNodeStream()` {#rendertonodestream} +### `renderToNodeStream()` (Deprecated) {#rendertonodestream} + +> Try the new React documentation for [`renderToNodeStream`](https://beta.reactjs.org/reference/react-dom/server/renderToNodeStream). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) ```javascript ReactDOMServer.renderToNodeStream(element) ``` -Render a React element to its initial HTML. Returns a [Readable stream](https://nodejs.org/api/stream.html#stream_readable_streams) that outputs an HTML string. The HTML output by this stream is exactly equal to what [`ReactDOMServer.renderToString`](#rendertostring) would return. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes. +Render a React element to its initial HTML. Returns a [Node.js Readable stream](https://nodejs.org/api/stream.html#stream_readable_streams) that outputs an HTML string. The HTML output by this stream is exactly equal to what [`ReactDOMServer.renderToString`](#rendertostring) would return. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes. -If you call [`ReactDOM.hydrate()`](/docs/react-dom.html#hydrate) on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. +If you call [`ReactDOM.hydrateRoot()`](/docs/react-dom-client.html#hydrateroot) on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. > Note: > @@ -75,6 +178,10 @@ If you call [`ReactDOM.hydrate()`](/docs/react-dom.html#hydrate) on a node that ### `renderToStaticNodeStream()` {#rendertostaticnodestream} +> Try the new React documentation for [`renderToStaticNodeStream`](https://beta.reactjs.org/reference/react-dom/server/renderToStaticNodeStream). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript ReactDOMServer.renderToStaticNodeStream(element) ``` @@ -83,10 +190,48 @@ Similar to [`renderToNodeStream`](#rendertonodestream), except this doesn't crea The HTML output by this stream is exactly equal to what [`ReactDOMServer.renderToStaticMarkup`](#rendertostaticmarkup) would return. -If you plan to use React on the client to make the markup interactive, do not use this method. Instead, use [`renderToNodeStream`](#rendertonodestream) on the server and [`ReactDOM.hydrate()`](/docs/react-dom.html#hydrate) on the client. +If you plan to use React on the client to make the markup interactive, do not use this method. Instead, use [`renderToNodeStream`](#rendertonodestream) on the server and [`ReactDOM.hydrateRoot()`](/docs/react-dom-client.html#hydrateroot) on the client. > Note: > > Server-only. This API is not available in the browser. > > The stream returned from this method will return a byte stream encoded in utf-8. If you need a stream in another encoding, take a look at a project like [iconv-lite](https://www.npmjs.com/package/iconv-lite), which provides transform streams for transcoding text. + +* * * + +### `renderToString()` {#rendertostring} + +> Try the new React documentation for [`renderToString`](https://beta.reactjs.org/reference/react-dom/server/renderToString). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +```javascript +ReactDOMServer.renderToString(element) +``` + +Render a React element to its initial HTML. React will return an HTML string. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes. + +If you call [`ReactDOM.hydrateRoot()`](/docs/react-dom-client.html#hydrateroot) on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience. + +> Note +> +> This API has limited Suspense support and does not support streaming. +> +> On the server, it is recommended to use either [`renderToPipeableStream`](#rendertopipeablestream) (for Node.js) or [`renderToReadableStream`](#rendertoreadablestream) (for Web Streams) instead. + +* * * + +### `renderToStaticMarkup()` {#rendertostaticmarkup} + +> Try the new React documentation for [`renderToStaticMarkup`](https://beta.reactjs.org/reference/react-dom/server/renderToStaticMarkup). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +```javascript +ReactDOMServer.renderToStaticMarkup(element) +``` + +Similar to [`renderToString`](#rendertostring), except this doesn't create extra DOM attributes that React uses internally, such as `data-reactroot`. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save some bytes. + +If you plan to use React on the client to make the markup interactive, do not use this method. Instead, use [`renderToString`](#rendertostring) on the server and [`ReactDOM.hydrateRoot()`](/docs/react-dom-client.html#hydrateroot) on the client. diff --git a/content/docs/reference-react-dom.md b/content/docs/reference-react-dom.md index f440ef7ed..27f1e9243 100644 --- a/content/docs/reference-react-dom.md +++ b/content/docs/reference-react-dom.md @@ -6,36 +6,105 @@ category: Reference permalink: docs/react-dom.html --- -If you load React from a `<script>` tag, these top-level APIs are available on the `ReactDOM` global. If you use ES6 with npm, you can write `import ReactDOM from 'react-dom'`. If you use ES5 with npm, you can write `var ReactDOM = require('react-dom')`. +The `react-dom` package provides DOM-specific methods that can be used at the top level of your app and as an escape hatch to get outside the React model if you need to. + +```js +import * as ReactDOM from 'react-dom'; +``` + +If you use ES5 with npm, you can write: + +```js +var ReactDOM = require('react-dom'); +``` + +The `react-dom` package also provides modules specific to client and server apps: +- [`react-dom/client`](/docs/react-dom-client.html) +- [`react-dom/server`](/docs/react-dom-server.html) ## Overview {#overview} -The `react-dom` package provides DOM-specific methods that can be used at the top level of your app and as an escape hatch to get outside of the React model if you need to. Most of your components should not need to use this module. +The `react-dom` package exports these methods: +- [`createPortal()`](#createportal) +- [`flushSync()`](#flushsync) +These `react-dom` methods are also exported, but are considered legacy: - [`render()`](#render) - [`hydrate()`](#hydrate) -- [`unmountComponentAtNode()`](#unmountcomponentatnode) - [`findDOMNode()`](#finddomnode) -- [`createPortal()`](#createportal) +- [`unmountComponentAtNode()`](#unmountcomponentatnode) + +> Note: +> +> Both `render` and `hydrate` have been replaced with new [client methods](/docs/react-dom-client.html) in React 18. These methods will warn that your app will behave as if it's running React 17 (learn more [here](https://reactjs.org/link/switch-to-createroot)). ### Browser Support {#browser-support} -React supports all popular browsers, including Internet Explorer 9 and above, although [some polyfills are required](/docs/javascript-environment-requirements.html) for older browsers such as IE 9 and IE 10. +React supports all modern browsers, although [some polyfills are required](/docs/javascript-environment-requirements.html) for older versions. > Note > -> We don't support older browsers that don't support ES5 methods, but you may find that your apps do work in older browsers if polyfills such as [es5-shim and es5-sham](https://github.com/es-shims/es5-shim) are included in the page. You're on your own if you choose to take this path. - -* * * +> We do not support older browsers that don't support ES5 methods or microtasks such as Internet Explorer. You may find that your apps do work in older browsers if polyfills such as [es5-shim and es5-sham](https://github.com/es-shims/es5-shim) are included in the page, but you're on your own if you choose to take this path. ## Reference {#reference} +### `createPortal()` {#createportal} + +> Try the new React documentation for [`createPortal`](https://beta.reactjs.org/reference/react-dom/createPortal). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +```javascript +createPortal(child, container) +``` + +Creates a portal. Portals provide a way to [render children into a DOM node that exists outside the hierarchy of the DOM component](/docs/portals.html). + +### `flushSync()` {#flushsync} + +> Try the new React documentation for [`flushSync`](https://beta.reactjs.org/reference/react-dom/flushSync). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +```javascript +flushSync(callback) +``` + +Force React to flush any updates inside the provided callback synchronously. This ensures that the DOM is updated immediately. + +```javascript +// Force this state update to be synchronous. +flushSync(() => { + setCount(count + 1); +}); +// By this point, DOM is updated. +``` + +> Note: +> +> `flushSync` can significantly hurt performance. Use sparingly. +> +> `flushSync` may force pending Suspense boundaries to show their `fallback` state. +> +> `flushSync` may also run pending effects and synchronously apply any updates they contain before returning. +> +> `flushSync` may also flush updates outside the callback when necessary to flush the updates inside the callback. For example, if there are pending updates from a click, React may flush those before flushing the updates inside the callback. + +## Legacy Reference {#legacy-reference} ### `render()` {#render} +> Try the new React documentation for [`render`](https://beta.reactjs.org/reference/react-dom/render). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript -ReactDOM.render(element, container[, callback]) +render(element, container[, callback]) ``` +> Note: +> +> `render` has been replaced with `createRoot` in React 18. See [createRoot](/docs/react-dom-client.html#createroot) for more info. + Render a React element into the DOM in the supplied `container` and return a [reference](/docs/more-about-refs.html) to the component (or returns `null` for [stateless components](/docs/components-and-props.html#function-and-class-components)). If the React element was previously rendered into `container`, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React element. @@ -44,24 +113,32 @@ If the optional callback is provided, it will be executed after the component is > Note: > -> `ReactDOM.render()` controls the contents of the container node you pass in. Any existing DOM elements inside are replaced when first called. Later calls use React’s DOM diffing algorithm for efficient updates. +> `render()` controls the contents of the container node you pass in. Any existing DOM elements inside are replaced when first called. Later calls use React’s DOM diffing algorithm for efficient updates. > -> `ReactDOM.render()` does not modify the container node (only modifies the children of the container). It may be possible to insert a component to an existing DOM node without overwriting the existing children. +> `render()` does not modify the container node (only modifies the children of the container). It may be possible to insert a component to an existing DOM node without overwriting the existing children. > -> `ReactDOM.render()` currently returns a reference to the root `ReactComponent` instance. However, using this return value is legacy +> `render()` currently returns a reference to the root `ReactComponent` instance. However, using this return value is legacy > and should be avoided because future versions of React may render components asynchronously in some cases. If you need a reference to the root `ReactComponent` instance, the preferred solution is to attach a -> [callback ref](/docs/more-about-refs.html#the-ref-callback-attribute) to the root element. +> [callback ref](/docs/refs-and-the-dom.html#callback-refs) to the root element. > -> Using `ReactDOM.render()` to hydrate a server-rendered container is deprecated and will be removed in React 17. Use [`hydrate()`](#hydrate) instead. +> Using `render()` to hydrate a server-rendered container is deprecated. Use [`hydrateRoot()`](/docs/react-dom-client.html#hydrateroot) instead. * * * ### `hydrate()` {#hydrate} +> Try the new React documentation for [`hydrate`](https://beta.reactjs.org/reference/react-dom/hydrate). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript -ReactDOM.hydrate(element, container[, callback]) +hydrate(element, container[, callback]) ``` +> Note: +> +> `hydrate` has been replaced with `hydrateRoot` in React 18. See [hydrateRoot](/docs/react-dom-client.html#hydrateroot) for more info. + Same as [`render()`](#render), but is used to hydrate a container whose HTML contents were rendered by [`ReactDOMServer`](/docs/react-dom-server.html). React will attempt to attach event listeners to the existing markup. React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them. In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive. @@ -76,22 +153,34 @@ Remember to be mindful of user experience on slow connections. The JavaScript co ### `unmountComponentAtNode()` {#unmountcomponentatnode} +> Try the new React documentation for [`unmountComponentAtNode`](https://beta.reactjs.org/reference/react-dom/unmountComponentAtNode). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript -ReactDOM.unmountComponentAtNode(container) +unmountComponentAtNode(container) ``` +> Note: +> +> `unmountComponentAtNode` has been replaced with `root.unmount()` in React 18. See [createRoot](/docs/react-dom-client.html#createroot) for more info. + Remove a mounted React component from the DOM and clean up its event handlers and state. If no component was mounted in the container, calling this function does nothing. Returns `true` if a component was unmounted and `false` if there was no component to unmount. * * * ### `findDOMNode()` {#finddomnode} +> Try the new React documentation for [`findDOMNode`](https://beta.reactjs.org/reference/react-dom/findDOMNode). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + > Note: > > `findDOMNode` is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction. [It has been deprecated in `StrictMode`.](/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage) ```javascript -ReactDOM.findDOMNode(component) +findDOMNode(component) ``` If this component has been mounted into the DOM, this returns the corresponding native browser DOM element. This method is useful for reading values out of the DOM, such as form field values and performing DOM measurements. **In most cases, you can attach a ref to the DOM node and avoid using `findDOMNode` at all.** @@ -104,11 +193,3 @@ When a component renders to `null` or `false`, `findDOMNode` returns `null`. Whe > `findDOMNode` cannot be used on function components. * * * - -### `createPortal()` {#createportal} - -```javascript -ReactDOM.createPortal(child, container) -``` - -Creates a portal. Portals provide a way to [render children into a DOM node that exists outside the hierarchy of the DOM component](/docs/portals.html). diff --git a/content/docs/reference-react.md b/content/docs/reference-react.md index 3de9b28ee..d668564b4 100644 --- a/content/docs/reference-react.md +++ b/content/docs/reference-react.md @@ -65,6 +65,13 @@ Suspense lets components "wait" for something before rendering. Today, Suspense - [`React.lazy`](#reactlazy) - [`React.Suspense`](#reactsuspense) +### Transitions {#transitions} + +*Transitions* are a new concurrent feature introduced in React 18. They allow you to mark updates as transitions, which tells React that they can be interrupted and avoid going back to Suspense fallbacks for already visible content. + +- [`React.startTransition`](#starttransition) +- [`React.useTransition`](/docs/hooks-reference.html#usetransition) + ### Hooks {#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: @@ -81,6 +88,12 @@ Suspense lets components "wait" for something before rendering. Today, Suspense - [`useImperativeHandle`](/docs/hooks-reference.html#useimperativehandle) - [`useLayoutEffect`](/docs/hooks-reference.html#uselayouteffect) - [`useDebugValue`](/docs/hooks-reference.html#usedebugvalue) + - [`useDeferredValue`](/docs/hooks-reference.html#usedeferredvalue) + - [`useTransition`](/docs/hooks-reference.html#usetransition) + - [`useId`](/docs/hooks-reference.html#useid) +- [Library Hooks](/docs/hooks-reference.html#library-hooks) + - [`useSyncExternalStore`](/docs/hooks-reference.html#usesyncexternalstore) + - [`useInsertionEffect`](/docs/hooks-reference.html#useinsertioneffect) * * * @@ -88,6 +101,10 @@ Suspense lets components "wait" for something before rendering. Today, Suspense ### `React.Component` {#reactcomponent} +> Try the new React documentation for [`Component`](https://beta.reactjs.org/reference/react/Component). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + `React.Component` is the base class for React components when they are defined using [ES6 classes](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes): ```javascript @@ -104,13 +121,17 @@ See the [React.Component API Reference](/docs/react-component.html) for a list o ### `React.PureComponent` {#reactpurecomponent} +> Try the new React documentation for [`PureComponent`](https://beta.reactjs.org/reference/react/PureComponent). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + `React.PureComponent` is similar to [`React.Component`](#reactcomponent). The difference between them is that [`React.Component`](#reactcomponent) doesn't implement [`shouldComponentUpdate()`](/docs/react-component.html#shouldcomponentupdate), but `React.PureComponent` implements it with a shallow prop and state comparison. If your React component's `render()` function renders the same result given the same props and state, you can use `React.PureComponent` for a performance boost in some cases. > Note > -> `React.PureComponent`'s `shouldComponentUpdate()` only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend `PureComponent` when you expect to have simple props and state, or use [`forceUpdate()`](/docs/react-component.html#forceupdate) when you know deep data structures have changed. Or, consider using [immutable objects](https://facebook.github.io/immutable-js/) to facilitate fast comparisons of nested data. +> `React.PureComponent`'s `shouldComponentUpdate()` only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend `PureComponent` when you expect to have simple props and state, or use [`forceUpdate()`](/docs/react-component.html#forceupdate) when you know deep data structures have changed. Or, consider using [immutable objects](https://immutable-js.com/) to facilitate fast comparisons of nested data. > > Furthermore, `React.PureComponent`'s `shouldComponentUpdate()` skips prop updates for the whole component subtree. Make sure all the children components are also "pure". @@ -118,17 +139,21 @@ If your React component's `render()` function renders the same result given the ### `React.memo` {#reactmemo} +> Try the new React documentation for [`memo`](https://beta.reactjs.org/reference/react/memo). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript const MyComponent = React.memo(function MyComponent(props) { /* render using props */ }); ``` -`React.memo` is a [higher order component](/docs/higher-order-components.html). It's similar to [`React.PureComponent`](#reactpurecomponent) but for function components instead of classes. +`React.memo` is a [higher order component](/docs/higher-order-components.html). -If your function component renders the same result given the same props, you can wrap it in a call to `React.memo` for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result. +If your component renders the same result given the same props, you can wrap it in a call to `React.memo` for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result. -`React.memo` only affects props changes. If your function component wrapped in `React.memo` has a [`useState`](/docs/hooks-state.html) or [`useContext`](/docs/hooks-reference.html#usecontext) Hook in its implementation, it will still rerender when state or context change. +`React.memo` only checks for prop changes. If your function component wrapped in `React.memo` has a [`useState`](/docs/hooks-state.html), [`useReducer`](/docs/hooks-reference.html#usereducer) or [`useContext`](/docs/hooks-reference.html#usecontext) Hook in its implementation, it will still rerender when state or context change. By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument. @@ -156,6 +181,10 @@ This method only exists as a **[performance optimization](/docs/optimizing-perfo ### `createElement()` {#createelement} +> Try the new React documentation for [`createElement`](https://beta.reactjs.org/reference/react/createElement). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript React.createElement( type, @@ -172,15 +201,19 @@ Code written with [JSX](/docs/introducing-jsx.html) will be converted to use `Re ### `cloneElement()` {#cloneelement} +> Try the new React documentation for [`cloneElement`](https://beta.reactjs.org/reference/react/cloneElement). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ``` React.cloneElement( element, - [props], + [config], [...children] ) ``` -Clone and return a new React element using `element` as the starting point. The resulting element will have the original element's props with the new props merged in shallowly. New children will replace existing children. `key` and `ref` from the original element will be preserved. +Clone and return a new React element using `element` as the starting point. `config` should contain all new props, `key`, or `ref`. The resulting element will have the original element's props with the new props merged in shallowly. New children will replace existing children. `key` and `ref` from the original element will be preserved if no `key` and `ref` present in the `config`. `React.cloneElement()` is almost equivalent to: @@ -188,7 +221,7 @@ Clone and return a new React element using `element` as the starting point. The <element.type {...element.props} {...props}>{children}</element.type> ``` -However, it also preserves `ref`s. This means that if you get a child with a `ref` on it, you won't accidentally steal it from your ancestor. You will get the same `ref` attached to your new element. +However, it also preserves `ref`s. This means that if you get a child with a `ref` on it, you won't accidentally steal it from your ancestor. You will get the same `ref` attached to your new element. The new `ref` or `key` will replace old ones if present. This API was introduced as a replacement of the deprecated `React.addons.cloneWithProps()`. @@ -196,6 +229,10 @@ This API was introduced as a replacement of the deprecated `React.addons.cloneWi ### `createFactory()` {#createfactory} +> Try the new React documentation for [`createFactory`](https://beta.reactjs.org/reference/react/createFactory). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript React.createFactory(type) ``` @@ -210,6 +247,10 @@ You will not typically invoke `React.createFactory()` directly if you are using ### `isValidElement()` {#isvalidelement} +> Try the new React documentation for [`isValidElement`](https://beta.reactjs.org/reference/react/isValidElement). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + ```javascript React.isValidElement(object) ``` @@ -220,6 +261,10 @@ Verifies the object is a React element. Returns `true` or `false`. ### `React.Children` {#reactchildren} +> Try the new React documentation for [`Children`](https://beta.reactjs.org/reference/react/Children). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + `React.Children` provides utilities for dealing with the `this.props.children` opaque data structure. #### `React.Children.map` {#reactchildrenmap} @@ -278,6 +323,10 @@ Returns the `children` opaque data structure as a flat array with keys assigned ### `React.Fragment` {#reactfragment} +> Try the new React documentation for [`Fragment`](https://beta.reactjs.org/reference/react/Fragment). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + The `React.Fragment` component lets you return multiple elements in a `render()` method without creating an additional DOM element: ```javascript @@ -296,11 +345,19 @@ You can also use it with the shorthand `<></>` syntax. For more information, see ### `React.createRef` {#reactcreateref} +> Try the new React documentation for [`createRef`](https://beta.reactjs.org/reference/react/createRef). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + `React.createRef` creates a [ref](/docs/refs-and-the-dom.html) that can be attached to React elements via the ref attribute. `embed:16-3-release-blog-post/create-ref-example.js` ### `React.forwardRef` {#reactforwardref} +> Try the new React documentation for [`forwardRef`](https://beta.reactjs.org/reference/react/forwardRef). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + `React.forwardRef` creates a React component that forwards the [ref](/docs/refs-and-the-dom.html) attribute it receives to another component below in the tree. This technique is not very common but is particularly useful in two scenarios: * [Forwarding refs to DOM components](/docs/forwarding-refs.html#forwarding-refs-to-dom-components) @@ -318,6 +375,10 @@ For more information, see [forwarding refs](/docs/forwarding-refs.html). ### `React.lazy` {#reactlazy} +> Try the new React documentation for [`lazy`](https://beta.reactjs.org/reference/react/lazy). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + `React.lazy()` lets you define a component that is loaded dynamically. This helps reduce the bundle size to delay loading components that aren't used during the initial render. You can learn how to use it from our [code splitting documentation](/docs/code-splitting.html#reactlazy). You might also want to check out [this article](https://medium.com/@pomber/lazy-loading-and-preloading-components-in-react-16-6-804de091c82d) explaining how to use it in more detail. @@ -329,13 +390,15 @@ const SomeComponent = React.lazy(() => import('./SomeComponent')); Note that rendering `lazy` components requires that there's a `<React.Suspense>` component higher in the rendering tree. This is how you specify a loading indicator. -> **Note** +### `React.Suspense` {#reactsuspense} + +> Try the new React documentation for [`Suspense`](https://beta.reactjs.org/reference/react/Suspense). > -> Using `React.lazy`with dynamic import requires Promises to be available in the JS environment. This requires a polyfill on IE11 and below. +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) -### `React.Suspense` {#reactsuspense} +`React.Suspense` lets you specify the loading indicator in case some components in the tree below it are not yet ready to render. In the future we plan to let `Suspense` handle more scenarios such as data fetching. You can read about this in [our roadmap](/blog/2018/11/27/react-16-roadmap.html). -`React.Suspense` lets you specify the loading indicator in case some components in the tree below it are not yet ready to render. Today, lazy loading components is the **only** use case supported by `<React.Suspense>`: +Today, lazy loading components is the **only** use case supported by `<React.Suspense>`: ```js // This component is loaded dynamically @@ -355,8 +418,32 @@ function MyComponent() { It is documented in our [code splitting guide](/docs/code-splitting.html#reactlazy). Note that `lazy` components can be deep inside the `Suspense` tree -- it doesn't have to wrap every one of them. The best practice is to place `<Suspense>` where you want to see a loading indicator, but to use `lazy()` wherever you want to do code splitting. -While this is not supported today, in the future we plan to let `Suspense` handle more scenarios such as data fetching. You can read about this in [our roadmap](/blog/2018/11/27/react-16-roadmap.html). +> Note +> +> For content that is already shown to the user, switching back to a loading indicator can be disorienting. It is sometimes better to show the "old" UI while the new UI is being prepared. To do this, you can use the new transition APIs [`startTransition`](#starttransition) and [`useTransition`](/docs/hooks-reference.html#usetransition) to mark updates as transitions and avoid unexpected fallbacks. + +#### `React.Suspense` in Server Side Rendering {#reactsuspense-in-server-side-rendering} +During server side rendering Suspense Boundaries allow you to flush your application in smaller chunks by suspending. +When a component suspends we schedule a low priority task to render the closest Suspense boundary's fallback. If the component unsuspends before we flush the fallback then we send down the actual content and throw away the fallback. + +#### `React.Suspense` during hydration {#reactsuspense-during-hydration} +Suspense boundaries depend on their parent boundaries being hydrated before they can hydrate, but they can hydrate independently from sibling boundaries. Events on a boundary before it is hydrated will cause the boundary to hydrate at a higher priority than neighboring boundaries. [Read more](https://github.com/reactwg/react-18/discussions/130) + +### `React.startTransition` {#starttransition} ->Note: +> Try the new React documentation for [`startTransition`](https://beta.reactjs.org/reference/react/startTransition). +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +```js +React.startTransition(callback) +``` +`React.startTransition` lets you mark updates inside the provided callback as transitions. This method is designed to be used when [`React.useTransition`](/docs/hooks-reference.html#usetransition) is not available. + +> Note: +> +> Updates in a transition yield to more urgent updates such as clicks. +> +> Updates in a transition will not show a fallback for re-suspended content, allowing the user to continue interacting while rendering the update. > ->`React.lazy()` and `<React.Suspense>` are not yet supported by `ReactDOMServer`. This is a known limitation that will be resolved in the future. +> `React.startTransition` does not provide an `isPending` flag. To track the pending status of a transition see [`React.useTransition`](/docs/hooks-reference.html#usetransition). diff --git a/content/docs/refs-and-the-dom.md b/content/docs/refs-and-the-dom.md index 9653b64a0..9c6f6d0c5 100644 --- a/content/docs/refs-and-the-dom.md +++ b/content/docs/refs-and-the-dom.md @@ -1,6 +1,7 @@ --- id: refs-and-the-dom title: Refs and the DOM +permalink: docs/refs-and-the-dom.html redirect_from: - "docs/working-with-the-browser.html" - "docs/more-about-refs.html" @@ -8,9 +9,19 @@ redirect_from: - "docs/more-about-refs-zh-CN.html" - "tips/expose-component-functions.html" - "tips/children-undefined.html" -permalink: docs/refs-and-the-dom.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [Referencing Values with Refs](https://beta.reactjs.org/learn/referencing-values-with-refs) +> - [Manipulating the DOM with Refs](https://beta.reactjs.org/learn/manipulating-the-dom-with-refs) +> - [`useRef`](https://beta.reactjs.org/reference/react/useRef) +> - [`forwardRef`](https://beta.reactjs.org/reference/react/forwardRef) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + Refs provide a way to access DOM nodes or React elements created in the render method. In the typical React dataflow, [props](/docs/components-and-props.html) are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch. @@ -168,8 +179,8 @@ You can, however, **use the `ref` attribute inside a function component** as lon ```javascript{2,3,6,13} function CustomTextInput(props) { // textInput must be declared here so the ref can refer to it - let textInput = useRef(null); - + const textInput = useRef(null); + function handleClick() { textInput.current.focus(); } diff --git a/content/docs/release-channels.md b/content/docs/release-channels.md index 579376d37..aa05985e5 100644 --- a/content/docs/release-channels.md +++ b/content/docs/release-channels.md @@ -4,6 +4,8 @@ title: Release Channels permalink: docs/release-channels.html layout: docs category: installation +prev: cdn-links.html +next: hello-world.html --- React relies on a thriving open source community to file bug reports, open pull requests, and [submit RFCs](https://github.com/reactjs/rfcs). To encourage feedback we sometimes share special builds of React that include unreleased features. @@ -13,10 +15,10 @@ React relies on a thriving open source community to file bug reports, open pull Each of React's release channels is designed for a distinct use case: - [**Latest**](#latest-channel) is for stable, semver React releases. It's what you get when you install React from npm. This is the channel you're already using today. **Use this for all user-facing React applications.** -- [**Next**](#next-channel) tracks the master branch of the React source code repository. Think of these as release candidates for the next minor semver release. Use this for integration testing between React and third party projects. -- [**Experimental**](#experimental-channel) includes experimental APIs and features that aren't available in the stable releases. These also track the master branch, but with additional feature flags turned on. Use this to try out upcoming features before they are released. +- [**Next**](#next-channel) tracks the main branch of the React source code repository. Think of these as release candidates for the next minor semver release. Use this for integration testing between React and third party projects. +- [**Experimental**](#experimental-channel) includes experimental APIs and features that aren't available in the stable releases. These also track the main branch, but with additional feature flags turned on. Use this to try out upcoming features before they are released. -All releases are published to npm, but only Latest uses [semantic versioning](/docs/faq-versioning.html). Prereleases (those in the Next and Experimental channels) have versions generated from a hash of their contents, e.g. `0.0.0-1022ee0ec` for Next and `0.0.0-experimental-1022ee0ec` for Experimental. +All releases are published to npm, but only Latest uses [semantic versioning](/docs/faq-versioning.html). Prereleases (those in the Next and Experimental channels) have versions generated from a hash of their contents and the commit date, e.g. `0.0.0-68053d940-20210623` for Next and `0.0.0-experimental-68053d940-20210623` for Experimental. **The only officially supported release channel for user-facing applications is Latest**. Next and Experimental releases are provided for testing purposes only, and we provide no guarantees that behavior won't change between releases. They do not follow the semver protocol that we use for releases from Latest. @@ -32,13 +34,13 @@ You can expect updates to Latest to be extremely stable. Versions follow the sem ### Next Channel {#next-channel} -The Next channel is a prerelease channel that tracks the master branch of the React repository. We use prereleases in the Next channel as release candidates for the Latest channel. You can think of Next as a superset of Latest that is updated more frequently. +The Next channel is a prerelease channel that tracks the main branch of the React repository. We use prereleases in the Next channel as release candidates for the Latest channel. You can think of Next as a superset of Latest that is updated more frequently. The degree of change between the most recent Next release and the most recent Latest release is approximately the same as you would find between two minor semver releases. However, **the Next channel does not conform to semantic versioning.** You should expect occasional breaking changes between successive releases in the Next channel. **Do not use prereleases in user-facing applications.** -Releases in Next are published with the `next` tag on npm. Versions are generated from a hash of the build's contents, e.g. `0.0.0-1022ee0ec`. +Releases in Next are published with the `next` tag on npm. Versions are generated from a hash of the build's contents and the commit date, e.g. `0.0.0-68053d940-20210623`. #### Using the Next Channel for Integration Testing {#using-the-next-channel-for-integration-testing} @@ -51,13 +53,13 @@ If you're the author of a third party React framework, library, developer tool, - Set up a cron job using your preferred continuous integration platform. Cron jobs are supported by both [CircleCI](https://circleci.com/docs/2.0/triggers/#scheduled-builds) and [Travis CI](https://docs.travis-ci.com/user/cron-jobs/). - In the cron job, update your React packages to the most recent React release in the Next channel, using `next` tag on npm. Using the npm cli: - ``` + ```console npm update react@next react-dom@next ``` Or yarn: - ``` + ```console yarn upgrade react@next react-dom@next ``` - Run your test suite against the updated packages. @@ -68,13 +70,13 @@ A project that uses this workflow is Next.js. (No pun intended! Seriously!) You ### Experimental Channel {#experimental-channel} -Like Next, the Experimental channel is a prerelease channel that tracks the master branch of the React repository. Unlike Next, Experimental releases include additional features and APIs that are not ready for wider release. +Like Next, the Experimental channel is a prerelease channel that tracks the main branch of the React repository. Unlike Next, Experimental releases include additional features and APIs that are not ready for wider release. Usually, an update to Next is accompanied by a corresponding update to Experimental. They are based on the same source revision, but are built using a different set of feature flags. Experimental releases may be significantly different than releases to Next and Latest. **Do not use Experimental releases in user-facing applications.** You should expect frequent breaking changes between releases in the Experimental channel. -Releases in Experimental are published with the `experimental` tag on npm. Versions are generated from a hash of the build's contents, e.g. `0.0.0-experimental-1022ee0ec`. +Releases in Experimental are published with the `experimental` tag on npm. Versions are generated from a hash of the build's contents and the commit date, e.g. `0.0.0-experimental-68053d940-20210623`. #### What Goes Into an Experimental Release? {#what-goes-into-an-experimental-release} @@ -86,10 +88,10 @@ You may find it valuable to run integration tests against Experimental. This is #### How Can I Learn More About Experimental Features? {#how-can-i-learn-more-about-experimental-features} -Experimental features may or may not be documented. Usually, experiments aren't documented until they are close to shipping in Next or Stable. +Experimental features may or may not be documented. Usually, experiments aren't documented until they are close to shipping in Next or Latest. If a feature is not documented, they may be accompanied by an [RFC](https://github.com/reactjs/rfcs). We will post to the [React blog](/blog) when we're ready to announce new experiments, but that doesn't mean we will publicize every experiment. -You can always refer to our public GitHub repository's [history](https://github.com/facebook/react/commits/master) for a comprehensive list of changes. +You can always refer to our public GitHub repository's [history](https://github.com/facebook/react/commits/main) for a comprehensive list of changes. diff --git a/content/docs/render-props.md b/content/docs/render-props.md index 5547437c5..e776a7b16 100644 --- a/content/docs/render-props.md +++ b/content/docs/render-props.md @@ -41,7 +41,7 @@ class MouseTracker extends React.Component { render() { return ( - <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> + <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> <h1>Move the mouse around!</h1> <p>The current mouse position is ({this.state.x}, {this.state.y})</p> </div> @@ -74,7 +74,7 @@ class Mouse extends React.Component { render() { return ( - <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> + <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> {/* ...but how do we render something other than a <p>? */} <p>The current mouse position is ({this.state.x}, {this.state.y})</p> @@ -127,7 +127,7 @@ class MouseWithCat extends React.Component { render() { return ( - <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> + <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> {/* We could just swap out the <p> for a <Cat> here ... but then @@ -183,7 +183,7 @@ class Mouse extends React.Component { render() { return ( - <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> + <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> {/* Instead of providing a static representation of what <Mouse> renders, diff --git a/content/docs/rendering-elements.md b/content/docs/rendering-elements.md index 831ce635f..ed9a5645c 100644 --- a/content/docs/rendering-elements.md +++ b/content/docs/rendering-elements.md @@ -34,12 +34,20 @@ const element = <h1>Hello, world</h1>; Οι εφαρμογές χτισμένες με μόνο React συνήθως έχουν ένα μοναδικό ριζικό DOM node. Άμα ενσωματώνεις React σε μια υπάρχουσα εφαρμογή, μπορείς να έχεις όσα πολλά απομονωμένα ριζικά DOM nodes που θέλεις. +<<<<<<< HEAD Να κανείς render ένα React element σε ένα ριζικό DOM node, να τα περάσεις στο [`ReactDOM.render()`](/docs/react-dom.html#render): `embed:rendering-elements/render-an-element.js` [Δοκιμάστε το στο CodePen](codepen://rendering-elements/render-an-element) +======= +To render a React element, first pass the DOM element to [`ReactDOM.createRoot()`](/docs/react-dom-client.html#createroot), then pass the React element to `root.render()`: + +`embed:rendering-elements/render-an-element.js` + +**[Try it on CodePen](https://codepen.io/gaearon/pen/ZpvBNJ?editors=1010)** +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Εμφανίζει "Hello, world" στη σελίδα. @@ -47,19 +55,33 @@ const element = <h1>Hello, world</h1>; Ta React elements είναι [αμετάβλητα](https://en.wikipedia.org/wiki/Immutable_object). Όταν δημουργείς ένα element, δεν μπορείς να αλλάξεις τα παιδιά ή τα χαρακτηριστικά του. Ένα element είναι σαν ένα ενιαίο πλαίσιο σε μια ταινία: εκπροσωπεί το UI σε μια συγκεκριμένη χρονική στιγμή. +<<<<<<< HEAD Με τις γνώσεις μας μέχρι στιγμής, ο μόνος τρόπος να ενημερώσουμε το UI είναι να δημιουρήσουμε ένα καινούργιο element, και να δοθεί στο [`ReactDOM.render()`](/docs/react-dom.html#render). +======= +With our knowledge so far, the only way to update the UI is to create a new element, and pass it to `root.render()`. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a Σκεφτείτε αυτό το παράδειγμα με χρονομετρημένο ρολόι: `embed:rendering-elements/update-rendered-element.js` +<<<<<<< HEAD [Δοκιμάστε το στο CodePen](codepen://rendering-elements/update-rendered-element) Αυτό καλεί [`ReactDOM.render()`](/docs/react-dom.html#render) κάθε δευτερόλεπτο από μια [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval) επανάκληση. +======= +**[Try it on CodePen](https://codepen.io/gaearon/pen/gwoJZk?editors=1010)** + +It calls [`root.render()`](/docs/react-dom.html#render) every second from a [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval) callback. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a >**Σημείωση** > +<<<<<<< HEAD >Στη πρακτική, οι περισότερες εφαρμογές React καλούν [`ReactDOM.render()`](/docs/react-dom.html#render) μόνο μια φορά. Στα επόμενα τμήματα, θα μάθουμε πώς αυτός ο κώδικας γίνεται εγκλωβισμένος σε [stateful components](/docs/state-and-lifecycle.html). +======= +>In practice, most React apps only call `root.render()` once. In the next sections we will learn how such code gets encapsulated into [stateful components](/docs/state-and-lifecycle.html). +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a > >Σας προτείνουμε να μην παραλείψετε θέματα επειδή βασίζονται ο ένας στον άλλο. @@ -67,7 +89,11 @@ Ta React elements είναι [αμετάβλητα](https://en.wikipedia.org/wik Το React DOM συγρίνει το element και τα παιδιά του προηγούμενου, και μόνο εφαρμόζει οι ενημερώσεις απαραίτητες του DOM να φέρει το DOM στην επιθυμητή κατάσταση. +<<<<<<< HEAD Μπορείτε να επαληθεύσετε ελέγχοντας το [τελευταίο παράδειγμα](codepen://rendering-elements/update-rendered-element) με τα εργαλεία του προγράμματος περιήγησης: +======= +You can verify by inspecting the [last example](https://codepen.io/gaearon/pen/gwoJZk?editors=1010) with the browser tools: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a ![DOM inspector showing granular updates](../images/docs/granular-dom-updates.gif) diff --git a/content/docs/state-and-lifecycle.md b/content/docs/state-and-lifecycle.md index 5bf2eddb9..b3eed7661 100644 --- a/content/docs/state-and-lifecycle.md +++ b/content/docs/state-and-lifecycle.md @@ -9,10 +9,27 @@ next: handling-events.html --- Αυτή η σελίδα παρουσιάζει την έννοια του state και lifecycle σε ένα React component. Μπορείτε να βρείτε μια [λεπτομερή αναφορά για το component API εδώ](/docs/react-component.html). +<<<<<<< HEAD Λάβετε υπ'όψιν το παράδειγμα με το ρολόι που χτυπάει [σε μια από τις προηγούμενες ενότητες](/docs/rendering-elements.html#updating-the-rendered-element). Στα [Rendering Elements](/docs/rendering-elements.html#rendering-an-element-into-the-dom), έχουμε μάθει μόνο έναν τροπό για να κάνουμε ανανέωση το UI. Καλούμε `ReactDOM.render()` για να αλλάξουμε το rendered output: - -```js{8-11} +======= +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [State: A Component's Memory](https://beta.reactjs.org/learn/state-a-components-memory) +> - [Synchronizing with Effects](https://beta.reactjs.org/learn/synchronizing-with-effects) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + +This page introduces the concept of state and lifecycle in a React component. You can find a [detailed component API reference here](/docs/react-component.html). + +Consider the ticking clock example from [one of the previous sections](/docs/rendering-elements.html#updating-the-rendered-element). In [Rendering Elements](/docs/rendering-elements.html#rendering-an-element-into-the-dom), we have only learned one way to update the UI. We call `root.render()` to change the rendered output: +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a + +```js{10} +const root = ReactDOM.createRoot(document.getElementById('root')); + function tick() { const element = ( <div> @@ -20,10 +37,7 @@ function tick() { <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); - ReactDOM.render( - element, - document.getElementById('root') - ); + root.render(element); } setInterval(tick, 1000); @@ -35,7 +49,9 @@ setInterval(tick, 1000); Μπορούμε να ξεκινήσουμε περικλείοντας με το πως φένεται το ρολόι: -```js{3-6,12} +```js{5-8,13} +const root = ReactDOM.createRoot(document.getElementById('root')); + function Clock(props) { return ( <div> @@ -46,10 +62,7 @@ function Clock(props) { } function tick() { - ReactDOM.render( - <Clock date={new Date()} />, - document.getElementById('root') - ); + root.render(<Clock date={new Date()} />); } setInterval(tick, 1000); @@ -62,10 +75,7 @@ setInterval(tick, 1000); Ιδανικά θέλουμε να το γράψουμε μια φορά και το `Clock` να ενημερώνει τον εαυτό του: ```js{2} -ReactDOM.render( - <Clock />, - document.getElementById('root') -); +root.render(<Clock />); ``` Για να το υλοποιήσουμε, χρειάζεται να προσθέσουμε το "state" στο `Clock` component. @@ -158,10 +168,7 @@ class Clock extends React.Component { 3) Αφαιρέστε το `date` prop από το `<Clock />` στοιχείο: ```js{2} -ReactDOM.render( - <Clock />, - document.getElementById('root') -); +root.render(<Clock />); ``` Αργότερα θα προσθέσουμε τον κώδικα του χρονοδιακόπτη πίσω στο ίδιο component. @@ -185,10 +192,8 @@ class Clock extends React.Component { } } -ReactDOM.render( - <Clock />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Clock />); ``` [**Δοκιμάστε το στο CodePen**](https://codepen.io/gaearon/pen/KgQpJd?editors=0010) @@ -298,10 +303,8 @@ class Clock extends React.Component { } } -ReactDOM.render( - <Clock />, - document.getElementById('root') -); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(<Clock />); ``` [**Δοκιμάστε το στο CodePen**](https://codepen.io/gaearon/pen/amqdNA?editors=0010) @@ -310,7 +313,11 @@ ReactDOM.render( Ας ανασκοπήσουμε γρήγορα τι συμβαίνει και τη σειρά με την οποία καλούνται οι μέθοδοι: +<<<<<<< HEAD 1) Όταν το `<Clock />` έχει περαστεί στο `ReactDOM.render()`, το React καλεί τον constructor του `Clock` component. Δεδομένου ότι το `Clock` χρειάζεται να εμφανίσει την τρέχουσα ώρα, αρχικοποιεί το `this.state` με ένα αντικείμενο που περιλαμβάνει την τρέχουσα ώρα. Θα ενημερώσουμε αργότερα αυτό το state. +======= +1) When `<Clock />` is passed to `root.render()`, React calls the constructor of the `Clock` component. Since `Clock` needs to display the current time, it initializes `this.state` with an object including the current time. We will later update this state. +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a 2) Το React τότε καλεί την `render()` method του `Clock` component's . Με αυτό τον τρόπο το React ενημερώνεται για το τι πρέπει να εμφανίζεται στην οθόνη. Το React τότε ενημερώνει το DOM για να ταιριάζει με την render output του `Clock`'s. @@ -422,12 +429,15 @@ this.setState(function(state, props) { Ένα component μπορεί να επιλέξει να περάσει το state του ως props στα child components: ```js +<<<<<<< HEAD <h2>It is {this.state.date.toLocaleTimeString()}.</h2> ``` Αυτό επίσης λειτουργεί και για τα user-defined components: ```js +======= +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a <FormattedDate date={this.state.date} /> ``` @@ -457,11 +467,6 @@ function App() { </div> ); } - -ReactDOM.render( - <App />, - document.getElementById('root') -); ``` [**Δοκιμάστε το στο CodePen**](https://codepen.io/gaearon/pen/vXdGmd?editors=0010) diff --git a/content/docs/static-type-checking.md b/content/docs/static-type-checking.md index c95820240..41af5570e 100644 --- a/content/docs/static-type-checking.md +++ b/content/docs/static-type-checking.md @@ -312,15 +312,15 @@ declare module 'querystring' { You are now ready to code! We recommend to check out the following resources to learn more about TypeScript: -* [TypeScript Documentation: Basic Types](https://www.typescriptlang.org/docs/handbook/basic-types.html) -* [TypeScript Documentation: Migrating from Javascript](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html) +* [TypeScript Documentation: Everyday Types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html) +* [TypeScript Documentation: Migrating from JavaScript](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html) * [TypeScript Documentation: React and Webpack](https://www.typescriptlang.org/docs/handbook/react-&-webpack.html) -## Reason {#reason} +## ReScript {#rescript} -[Reason](https://reasonml.github.io/) is not a new language; it's a new syntax and toolchain powered by the battle-tested language, [OCaml](https://ocaml.org/). Reason gives OCaml a familiar syntax geared toward JavaScript programmers, and caters to the existing NPM/Yarn workflow folks already know. +[ReScript](https://rescript-lang.org/) is a typed language that compiles to JavaScript. Some of its core features are guaranteed 100% type coverage, first-class JSX support and [dedicated React bindings](https://rescript-lang.org/docs/react/latest/introduction) to allow integration in existing JS / TS React codebases. -Reason is developed at Facebook, and is used in some of its products like Messenger. It is still somewhat experimental but it has [dedicated React bindings](https://reasonml.github.io/reason-react/) maintained by Facebook and a [vibrant community](https://reasonml.github.io/docs/en/community.html). +You can find more infos on integrating ReScript in your existing JS / React codebase [here](https://rescript-lang.org/docs/manual/latest/installation#integrate-into-an-existing-js-project). ## Kotlin {#kotlin} diff --git a/content/docs/strict-mode.md b/content/docs/strict-mode.md index 0f48ba161..bd8c37567 100644 --- a/content/docs/strict-mode.md +++ b/content/docs/strict-mode.md @@ -4,6 +4,15 @@ title: Strict Mode permalink: docs/strict-mode.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [`StrictMode`](https://beta.reactjs.org/reference/react/StrictMode) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + + `StrictMode` is a tool for highlighting potential problems in an application. Like `Fragment`, `StrictMode` does not render any visible UI. It activates additional checks and warnings for its descendants. > Note: @@ -21,6 +30,7 @@ In the above example, strict mode checks will *not* be run against the `Header` * [Warning about deprecated findDOMNode usage](#warning-about-deprecated-finddomnode-usage) * [Detecting unexpected side effects](#detecting-unexpected-side-effects) * [Detecting legacy context API](#detecting-legacy-context-api) +* [Ensuring reusable state](#ensuring-reusable-state) Additional functionality will be added with future releases of React. @@ -55,7 +65,7 @@ Since object refs were largely added as a replacement for string refs, strict mo React used to support `findDOMNode` to search the tree for a DOM node given a class instance. Normally you don't need this because you can [attach a ref directly to a DOM node](/docs/refs-and-the-dom.html#creating-refs). -`findDOMNode` can also be used on class components but this was breaking abstraction levels by allowing a parent to demand that certain children was rendered. It creates a refactoring hazard where you can't change the implementation details of a component because a parent might be reaching into its DOM node. `findDOMNode` only returns the first child, but with the use of Fragments, it is possible for a component to render multiple DOM nodes. `findDOMNode` is a one time read API. It only gave you an answer when you asked for it. If a child component renders a different node, there is no way to handle this change. Therefore `findDOMNode` only worked if components always return a single DOM node that never changes. +`findDOMNode` can also be used on class components but this was breaking abstraction levels by allowing a parent to demand that certain children were rendered. It creates a refactoring hazard where you can't change the implementation details of a component because a parent might be reaching into its DOM node. `findDOMNode` only returns the first child, but with the use of Fragments, it is possible for a component to render multiple DOM nodes. `findDOMNode` is a one time read API. It only gave you an answer when you asked for it. If a child component renders a different node, there is no way to handle this change. Therefore `findDOMNode` only worked if components always return a single DOM node that never changes. You can instead make this explicit by passing a ref to your custom component and pass that along to the DOM using [ref forwarding](/docs/forwarding-refs.html#forwarding-refs-to-dom-components). @@ -97,13 +107,13 @@ Render phase lifecycles include the following class component methods: Because the above methods might be called more than once, it's important that they do not contain side-effects. Ignoring this rule can lead to a variety of problems, including memory leaks and invalid application state. Unfortunately, it can be difficult to detect these problems as they can often be [non-deterministic](https://en.wikipedia.org/wiki/Deterministic_algorithm). -Strict mode can't automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following methods: +Strict mode can't automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions: -* Class component `constructor` method -* The `render` method -* `setState` updater functions (the first argument) -* The static `getDerivedStateFromProps` lifecycle -* The `shouldComponentUpdate` method +* Class component `constructor`, `render`, and `shouldComponentUpdate` methods +* Class component static `getDerivedStateFromProps` method +* Function component bodies +* State updater functions (the first argument to `setState`) +* Functions passed to `useState`, `useMemo`, or `useReducer` > Note: > @@ -116,6 +126,12 @@ At first glance, this code might not seem problematic. But if `SharedApplication By intentionally double-invoking methods like the component constructor, strict mode makes patterns like this easier to spot. +> Note: +> +> In React 17, React automatically modifies the console methods like `console.log()` to silence the logs in the second call to lifecycle functions. However, it may cause undesired behavior in certain cases where [a workaround can be used](https://github.com/facebook/react/issues/20090#issuecomment-715927125). +> +> Starting from React 18, React does not suppress any logs. However, if you have React DevTools installed, the logs from the second call will appear slightly dimmed. React DevTools also offers a setting (off by default) to suppress them completely. + ### Detecting legacy context API {#detecting-legacy-context-api} The legacy context API is error-prone, and will be removed in a future major version. It still works for all 16.x releases but will show this warning message in strict mode: @@ -123,3 +139,59 @@ The legacy context API is error-prone, and will be removed in a future major ver ![](../images/blog/warn-legacy-context-in-strict-mode.png) Read the [new context API documentation](/docs/context.html) to help migrate to the new version. + + +### Ensuring reusable state {#ensuring-reusable-state} + +In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting. + +This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once. + +To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount. + +To demonstrate the development behavior you'll see in Strict Mode with this feature, consider what happens when React mounts a new component. Without this change, when a component mounts, React creates the effects: + +``` +* React mounts the component. + * Layout effects are created. + * Effects are created. +``` + +With Strict Mode starting in React 18, whenever a component mounts in development, React will simulate immediately unmounting and remounting the component: + +``` +* React mounts the component. + * Layout effects are created. + * Effects are created. +* React simulates effects being destroyed on a mounted component. + * Layout effects are destroyed. + * Effects are destroyed. +* React simulates effects being re-created on a mounted component. + * Layout effects are created + * Effect setup code runs +``` + +On the second mount, React will restore the state from the first mount. This feature simulates user behavior such as a user tabbing away from a screen and back, ensuring that code will properly handle state restoration. + +When the component unmounts, effects are destroyed as normal: + +``` +* React unmounts the component. + * Layout effects are destroyed. + * Effects are destroyed. +``` + +Unmounting and remounting includes: + +- `componentDidMount` +- `componentWillUnmount` +- `useEffect` +- `useLayoutEffect` +- `useInsertionEffect` + +> Note: +> +> This only applies to development mode, _production behavior is unchanged_. + +For help supporting common issues, see: + - [How to support Reusable State in Effects](https://github.com/reactwg/react-18/discussions/18) diff --git a/content/docs/testing-environments.md b/content/docs/testing-environments.md index c03714d01..cd2d3a7cc 100644 --- a/content/docs/testing-environments.md +++ b/content/docs/testing-environments.md @@ -29,7 +29,7 @@ A large portion of UI tests can be written with the above setup: using Jest as a If you're writing a library that tests mostly browser-specific behavior, and requires native browser behavior like layout or real inputs, you could use a framework like [mocha.](https://mochajs.org/) -In an environment where you _can't_ simulate a DOM (e.g. testing React Native components on Node.js), you could use [event simulation helpers](/docs/test-utils.html#simulate) to simulate interactions with elements. Alternately, you could use the `fireEvent` helper from [`@testing-library/react-native`](https://testing-library.com/docs/native-testing-library). +In an environment where you _can't_ simulate a DOM (e.g. testing React Native components on Node.js), you could use [event simulation helpers](/docs/test-utils.html#simulate) to simulate interactions with elements. Alternately, you could use the `fireEvent` helper from [`@testing-library/react-native`](https://testing-library.com/docs/react-native-testing-library/intro). Frameworks like [Cypress](https://www.cypress.io/), [puppeteer](https://github.com/GoogleChrome/puppeteer) and [webdriver](https://www.seleniumhq.org/projects/webdriver/) are useful for running [end-to-end tests](#end-to-end-tests-aka-e2e-tests). @@ -47,7 +47,7 @@ On Node.js, runners like Jest [support mocking modules](https://jestjs.io/docs/e ### Mocking timers {#mocking-timers} -Components might be using time-based functions like `setTimeout`, `setInterval`, or `Date.now`. In testing environments, it can be helpful to mock these functions out with replacements that let you manually "advance" time. This is great for making sure your tests run fast! Tests that are dependent on timers would still resolve in order, but quicker [<small>(example)</small>](/docs/testing-recipes.html#timers). Most frameworks, including [Jest](https://jestjs.io/docs/en/timer-mocks), [sinon](https://sinonjs.org/releases/v7.3.2/fake-timers/) and [lolex](https://github.com/sinonjs/lolex), let you mock timers in your tests. +Components might be using time-based functions like `setTimeout`, `setInterval`, or `Date.now`. In testing environments, it can be helpful to mock these functions out with replacements that let you manually "advance" time. This is great for making sure your tests run fast! Tests that are dependent on timers would still resolve in order, but quicker [<small>(example)</small>](/docs/testing-recipes.html#timers). Most frameworks, including [Jest](https://jestjs.io/docs/en/timer-mocks), [sinon](https://sinonjs.org/releases/latest/fake-timers) and [lolex](https://github.com/sinonjs/lolex), let you mock timers in your tests. Sometimes, you may not want to mock timers. For example, maybe you're testing an animation, or interacting with an endpoint that's sensitive to timing (like an API rate limiter). Libraries with timer mocks let you enable and disable them on a per test/suite basis, so you can explicitly choose how these tests would run. @@ -55,4 +55,4 @@ Sometimes, you may not want to mock timers. For example, maybe you're testing an End-to-end tests are useful for testing longer workflows, especially when they're critical to your business (such as payments or signups). For these tests, you'd probably want to test how a real browser renders the whole app, fetches data from the real API endpoints, uses sessions and cookies, navigates between different links. You might also likely want to make assertions not just on the DOM state, but on the backing data as well (e.g. to verify whether the updates have been persisted to the database). -In this scenario, you would use a framework like [Cypress](https://www.cypress.io/) or a library like [puppeteer](https://github.com/GoogleChrome/puppeteer) so you can navigate between multiple routes and assert on side effects not just in the browser, but potentially on the backend as well. +In this scenario, you would use a framework like [Cypress](https://www.cypress.io/), [Playwright](https://playwright.dev) or a library like [Puppeteer](https://pptr.dev/) so you can navigate between multiple routes and assert on side effects not just in the browser, but potentially on the backend as well. diff --git a/content/docs/testing-recipes.md b/content/docs/testing-recipes.md index a7f9e5ff7..73654835e 100644 --- a/content/docs/testing-recipes.md +++ b/content/docs/testing-recipes.md @@ -57,7 +57,7 @@ You may use a different pattern, but keep in mind that we want to execute the cl ### `act()` {#act} -When writing UI tests, tasks like rendering, user events, or data fetching can be considered as "units" of interaction with a user interface. React provides a helper called `act()` that makes sure all updates related to these "units" have been processed and applied to the DOM before you make any assertions: +When writing UI tests, tasks like rendering, user events, or data fetching can be considered as "units" of interaction with a user interface. `react-dom/test-utils` provides a helper called [`act()`](/docs/test-utils.html#act) that makes sure all updates related to these "units" have been processed and applied to the DOM before you make any assertions: ```js act(() => { @@ -253,7 +253,7 @@ export default function Map(props) { import React from "react"; import Map from "./map"; -function Contact(props) { +export default function Contact(props) { return ( <div> <address> @@ -377,7 +377,6 @@ let container = null; beforeEach(() => { // setup a DOM element as a render target container = document.createElement("div"); - // container *must* be attached to document so events work correctly. document.body.appendChild(container); }); @@ -394,7 +393,7 @@ it("changes value when clicked", () => { render(<Toggle onChange={onChange} />, container); }); - // get ahold of the button element, and trigger some clicks on it + // get a hold of the button element, and trigger some clicks on it const button = document.querySelector("[data-testid=toggle]"); expect(button.innerHTML).toBe("Turn on"); @@ -416,7 +415,7 @@ it("changes value when clicked", () => { }); ``` -Different DOM events and their properties are described in [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). Note that you need to pass `{ bubbles: true }` in each event you create for it to reach the React listener because React automatically delegates events to the document. +Different DOM events and their properties are described in [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). Note that you need to pass `{ bubbles: true }` in each event you create for it to reach the React listener because React automatically delegates events to the root. > Note: > @@ -466,13 +465,12 @@ import { act } from "react-dom/test-utils"; import Card from "./card"; -jest.useFakeTimers(); - let container = null; beforeEach(() => { // setup a DOM element as a render target container = document.createElement("div"); document.body.appendChild(container); + jest.useFakeTimers(); }); afterEach(() => { @@ -480,6 +478,7 @@ afterEach(() => { unmountComponentAtNode(container); container.remove(); container = null; + jest.useRealTimers(); }); it("should select null after timing out", () => { @@ -606,7 +605,7 @@ It's typically better to make more specific assertions than to use snapshots. Th ### Multiple Renderers {#multiple-renderers} -In rare cases, you may be running a test on a component that uses multiple renderers. For example, you may be running snapshot tests on a component with `react-test-renderer`, that internally uses `ReactDOM.render` inside a child component to render some content. In this scenario, you can wrap updates with `act()`s corresponding to their renderers. +In rare cases, you may be running a test on a component that uses multiple renderers. For example, you may be running snapshot tests on a component with `react-test-renderer`, that internally uses `render` from `react-dom` inside a child component to render some content. In this scenario, you can wrap updates with `act()`s corresponding to their renderers. ```jsx import { act as domAct } from "react-dom/test-utils"; diff --git a/content/docs/testing.md b/content/docs/testing.md index 5bccd1fc4..b8e242d9f 100644 --- a/content/docs/testing.md +++ b/content/docs/testing.md @@ -14,7 +14,7 @@ There are a few ways to test React components. Broadly, they divide into two cat * **Rendering component trees** in a simplified test environment and asserting on their output. * **Running a complete app** in a realistic browser environment (also known as “end-to-end” tests). -This documentation section focuses on testing strategies for the first case. While full end-to-end tests can be very useful to prevent regressions to important workflows, such tests are not concerned with React components in particular, and are out of scope of this section. +This documentation section focuses on testing strategies for the first case. While full end-to-end tests can be very useful to prevent regressions to important workflows, such tests are not concerned with React components in particular, and are out of the scope of this section. ### Tradeoffs {#tradeoffs} diff --git a/content/docs/thinking-in-react.md b/content/docs/thinking-in-react.md index 6e5e7ed94..1f2b65646 100644 --- a/content/docs/thinking-in-react.md +++ b/content/docs/thinking-in-react.md @@ -8,6 +8,12 @@ redirect_from: prev: composition-vs-inheritance.html --- +> Try the new React documentation. +> +> The updated [Thinking in React](https://beta.reactjs.org/learn/thinking-in-react) guide teaches modern React and includes live examples. +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + React is, in our opinion, the premier way to build big, fast Web apps with JavaScript. It has scaled very well for us at Facebook and Instagram. One of the many great parts of React is how it makes you think about apps as you build them. In this document, we'll walk you through the thought process of building a searchable product data table using React. @@ -39,9 +45,9 @@ But how do you know what should be its own component? Use the same techniques fo Since you're often displaying a JSON data model to a user, you'll find that if your model was built correctly, your UI (and therefore your component structure) will map nicely. That's because UI and data models tend to adhere to the same *information architecture*. Separate your UI into components, where each component matches one piece of your data model. -![Component diagram](../images/blog/thinking-in-react-components.png) +![Diagram showing nesting of components](../images/blog/thinking-in-react-components.png) -You'll see here that we have five components in our app. We've italicized the data each component represents. +You'll see here that we have five components in our app. We've italicized the data each component represents. The numbers in the image correspond to the numbers below. 1. **`FilterableProductTable` (orange):** contains the entirety of the example 2. **`SearchBar` (blue):** receives all *user input* @@ -70,9 +76,9 @@ To build a static version of your app that renders your data model, you'll want You can build top-down or bottom-up. That is, you can either start with building the components higher up in the hierarchy (i.e. starting with `FilterableProductTable`) or with the ones lower in it (`ProductRow`). In simpler examples, it's usually easier to go top-down, and on larger projects, it's easier to go bottom-up and write tests as you build. -At the end of this step, you'll have a library of reusable components that render your data model. The components will only have `render()` methods since this is a static version of your app. The component at the top of the hierarchy (`FilterableProductTable`) will take your data model as a prop. If you make a change to your underlying data model and call `ReactDOM.render()` again, the UI will be updated. You can see how your UI is updated and where to make changes. React's **one-way data flow** (also called *one-way binding*) keeps everything modular and fast. +At the end of this step, you'll have a library of reusable components that render your data model. The components will only have `render()` methods since this is a static version of your app. The component at the top of the hierarchy (`FilterableProductTable`) will take your data model as a prop. If you make a change to your underlying data model and call `root.render()` again, the UI will be updated. You can see how your UI is updated and where to make changes. React's **one-way data flow** (also called *one-way binding*) keeps everything modular and fast. -Refer to the [React docs](/docs/) if you need help executing this step. +Refer to the [React docs](/docs/getting-started.html) if you need help executing this step. ### A Brief Interlude: Props vs State {#a-brief-interlude-props-vs-state} @@ -84,7 +90,7 @@ To make your UI interactive, you need to be able to trigger changes to your unde To build your app correctly, you first need to think of the minimal set of mutable state that your app needs. The key here is [DRY: *Don't Repeat Yourself*](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). Figure out the absolute minimal representation of the state your application needs and compute everything else you need on-demand. For example, if you're building a TODO list, keep an array of the TODO items around; don't keep a separate state variable for the count. Instead, when you want to render the TODO count, take the length of the TODO items array. -Think of all of the pieces of data in our example application. We have: +Think of all the pieces of data in our example application. We have: * The original list of products * The search text the user has entered @@ -137,10 +143,10 @@ So far, we've built an app that renders correctly as a function of props and sta React makes this data flow explicit to help you understand how your program works, but it does require a little more typing than traditional two-way data binding. -If you try to type or check the box in the current version of the example, you'll see that React ignores your input. This is intentional, as we've set the `value` prop of the `input` to always be equal to the `state` passed in from `FilterableProductTable`. +If you try to type or check the box in the previous version of the example (step 4), you'll see that React ignores your input. This is intentional, as we've set the `value` prop of the `input` to always be equal to the `state` passed in from `FilterableProductTable`. Let's think about what we want to happen. We want to make sure that whenever the user changes the form, we update the state to reflect the user input. Since components should only update their own state, `FilterableProductTable` will pass callbacks to `SearchBar` that will fire whenever the state should be updated. We can use the `onChange` event on the inputs to be notified of it. The callbacks passed by `FilterableProductTable` will call `setState()`, and the app will be updated. ## And That's It {#and-thats-it} -Hopefully, this gives you an idea of how to think about building components and applications with React. While it may be a little more typing than you're used to, remember that code is read far more than it's written, and it's less difficult to read this modular, explicit code. As you start to build large libraries of components, you'll appreciate this explicitness and modularity, and with code reuse, your lines of code will start to shrink. :) +Hopefully, this gives you an idea of how to think about building components and applications with React. While it may be a little more typing than you're used to, remember that code is read far more often than it's written, and it's less difficult to read this modular, explicit code. As you start to build large libraries of components, you'll appreciate this explicitness and modularity, and with code reuse, your lines of code will start to shrink. :) diff --git a/content/docs/typechecking-with-proptypes.md b/content/docs/typechecking-with-proptypes.md index 9034ba9dc..63d4a3fdc 100644 --- a/content/docs/typechecking-with-proptypes.md +++ b/content/docs/typechecking-with-proptypes.md @@ -30,6 +30,8 @@ Greeting.propTypes = { }; ``` +In this example, we are using a class component, but the same functionality could also be applied to function components, or components created by [`React.memo`](/docs/react-api.html#reactmemo) or [`React.forwardRef`](/docs/react-api.html#reactforwardref). + `PropTypes` exports a range of validators that can be used to make sure the data you receive is valid. In this example, we're using `PropTypes.string`. When an invalid value is provided for a prop, a warning will be shown in the JavaScript console. For performance reasons, `propTypes` is only checked in development mode. ### PropTypes {#proptypes} @@ -59,7 +61,7 @@ MyComponent.propTypes = { // A React element type (ie. MyComponent). optionalElementType: PropTypes.elementType, - + // You can also declare that a prop is an instance of a class. This uses // JS's instanceof operator. optionalMessage: PropTypes.instanceOf(Message), @@ -86,7 +88,7 @@ MyComponent.propTypes = { color: PropTypes.string, fontSize: PropTypes.number }), - + // An object with warnings on extra properties optionalObjectWithStrictShape: PropTypes.exact({ name: PropTypes.string, @@ -97,7 +99,7 @@ MyComponent.propTypes = { // is shown if the prop isn't provided. requiredFunc: PropTypes.func.isRequired, - // A value of any data type + // A required value of any data type requiredAny: PropTypes.any.isRequired, // You can also specify a custom validator. It should return an Error @@ -171,13 +173,11 @@ Greeting.defaultProps = { }; // Renders "Hello, Stranger": -ReactDOM.render( - <Greeting />, - document.getElementById('example') -); +const root = ReactDOM.createRoot(document.getElementById('example')); +root.render(<Greeting />); ``` -If you are using a Babel transform like [transform-class-properties](https://babeljs.io/docs/plugins/transform-class-properties/) , you can also declare `defaultProps` as static property within a React component class. This syntax has not yet been finalized though and will require a compilation step to work within a browser. For more information, see the [class fields proposal](https://github.com/tc39/proposal-class-fields). +Since ES2022 you can also declare `defaultProps` as static property within a React component class. For more information, see the [class public static fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields#public_static_fields). This modern syntax will require a compilation step to work within older browsers. ```javascript class Greeting extends React.Component { @@ -194,3 +194,47 @@ class Greeting extends React.Component { ``` The `defaultProps` will be used to ensure that `this.props.name` will have a value if it was not specified by the parent component. The `propTypes` typechecking happens after `defaultProps` are resolved, so typechecking will also apply to the `defaultProps`. + +### Function Components {#function-components} + +If you are using function components in your regular development, you may want to make some small changes to allow PropTypes to be properly applied. + +Let's say you have a component like this: + +```javascript +export default function HelloWorldComponent({ name }) { + return ( + <div>Hello, {name}</div> + ) +} +``` + +To add PropTypes, you may want to declare the component in a separate function before exporting, like this: + +```javascript +function HelloWorldComponent({ name }) { + return ( + <div>Hello, {name}</div> + ) +} + +export default HelloWorldComponent +``` + +Then, you can add PropTypes directly to the `HelloWorldComponent`: + +```javascript +import PropTypes from 'prop-types' + +function HelloWorldComponent({ name }) { + return ( + <div>Hello, {name}</div> + ) +} + +HelloWorldComponent.propTypes = { + name: PropTypes.string +} + +export default HelloWorldComponent +``` diff --git a/content/docs/uncontrolled-components.md b/content/docs/uncontrolled-components.md index 54b729e96..024a99325 100644 --- a/content/docs/uncontrolled-components.md +++ b/content/docs/uncontrolled-components.md @@ -4,6 +4,16 @@ title: Uncontrolled Components permalink: docs/uncontrolled-components.html --- +> Try the new React documentation. +> +> These new documentation pages teach modern React and include live examples: +> +> - [`<input>`](https://beta.reactjs.org/reference/react-dom/components/input) +> - [`<select>`](https://beta.reactjs.org/reference/react-dom/components/select) +> - [`<textarea>`](https://beta.reactjs.org/reference/react-dom/components/textarea) +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + In most cases, we recommend using [controlled components](/docs/forms.html#controlled-components) to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself. To write an uncontrolled component, instead of writing an event handler for every state update, you can [use a ref](/docs/refs-and-the-dom.html) to get form values from the DOM. @@ -45,7 +55,7 @@ If it's still not clear which type of component you should use for a particular ### Default Values {#default-values} -In the React rendering lifecycle, the `value` attribute on form elements will override the value in the DOM. With an uncontrolled component, you often want React to specify the initial value, but leave subsequent updates uncontrolled. To handle this case, you can specify a `defaultValue` attribute instead of `value`. +In the React rendering lifecycle, the `value` attribute on form elements will override the value in the DOM. With an uncontrolled component, you often want React to specify the initial value, but leave subsequent updates uncontrolled. To handle this case, you can specify a `defaultValue` attribute instead of `value`. Changing the value of `defaultValue` attribute after a component has mounted will not cause any update of the value in the DOM. ```javascript{7} render() { diff --git a/content/docs/web-components.md b/content/docs/web-components.md index 1c5dcd278..c8394d20e 100644 --- a/content/docs/web-components.md +++ b/content/docs/web-components.md @@ -50,7 +50,8 @@ class XSearch extends HTMLElement { const name = this.getAttribute('name'); const url = 'https://www.google.com/search?q=' + encodeURIComponent(name); - ReactDOM.render(<a href={url}>{name}</a>, mountPoint); + const root = ReactDOM.createRoot(mountPoint); + root.render(<a href={url}>{name}</a>); } } customElements.define('x-search', XSearch); diff --git a/content/footerNav.yml b/content/footerNav.yml index 179a6e2db..edd2c3313 100644 --- a/content/footerNav.yml +++ b/content/footerNav.yml @@ -14,7 +14,7 @@ more: - title: Acknowledgements to: /acknowledgements.html - title: React Native - to: https://facebook.github.io/react-native/ + to: https://reactnative.dev/ external: true channels: diff --git a/content/home/examples/a-component-using-external-plugins.js b/content/home/examples/a-component-using-external-plugins.js index 305efe61c..d968bb7a5 100644 --- a/content/home/examples/a-component-using-external-plugins.js +++ b/content/home/examples/a-component-using-external-plugins.js @@ -36,7 +36,4 @@ class MarkdownEditor extends React.Component { } } -ReactDOM.render( - <MarkdownEditor />, - document.getElementById('markdown-example') -); +root.render(<MarkdownEditor />); diff --git a/content/home/examples/a-simple-component.js b/content/home/examples/a-simple-component.js index 90c31b960..6e9c5ae56 100644 --- a/content/home/examples/a-simple-component.js +++ b/content/home/examples/a-simple-component.js @@ -1,14 +1,7 @@ class HelloMessage extends React.Component { render() { - return ( - <div> - Hello {this.props.name} - </div> - ); + return <div>Hello {this.props.name}</div>; } } -ReactDOM.render( - <HelloMessage name="Taylor" />, - document.getElementById('hello-example') -); \ No newline at end of file +root.render(<HelloMessage name="Taylor" />); diff --git a/content/home/examples/a-stateful-component.js b/content/home/examples/a-stateful-component.js index 25d5a9145..bc329253b 100644 --- a/content/home/examples/a-stateful-component.js +++ b/content/home/examples/a-stateful-component.js @@ -27,7 +27,4 @@ class Timer extends React.Component { } } -ReactDOM.render( - <Timer />, - document.getElementById('timer-example') -); \ No newline at end of file +root.render(<Timer />); diff --git a/content/home/examples/an-application.js b/content/home/examples/an-application.js index 43f777dc2..c4313e852 100644 --- a/content/home/examples/an-application.js +++ b/content/home/examples/an-application.js @@ -34,7 +34,7 @@ class TodoApp extends React.Component { handleSubmit(e) { e.preventDefault(); - if (!this.state.text.length) { + if (this.state.text.length === 0) { return; } const newItem = { @@ -60,7 +60,4 @@ class TodoList extends React.Component { } } -ReactDOM.render( - <TodoApp />, - document.getElementById('todos-example') -); +root.render(<TodoApp />); diff --git a/content/home/marketing/learn-once-write-anywhere.md b/content/home/marketing/learn-once-write-anywhere.md index 420dc6871..3db0c18be 100644 --- a/content/home/marketing/learn-once-write-anywhere.md +++ b/content/home/marketing/learn-once-write-anywhere.md @@ -5,4 +5,8 @@ order: 2 Δεν κάνουμε υποθέσεις σχετικά με το υπόλοιπο stack της τεχνολογίας σας, ώστε να μπορείτε να αναπτύξετε νέες λειτουργίες στο React χωρίς να ξαναγράψετε τον κώδικα σας. +<<<<<<< HEAD Το React μπορεί επίσης να κάνει render στο server χρησιμοποιώντας Node, και mobile apps με χρήση του [React Native](https://facebook.github.io/react-native/). +======= +React can also render on the server using Node and power mobile apps using [React Native](https://reactnative.dev/). +>>>>>>> 63c77695a95902595b6c2cc084a5c3650b15210a diff --git a/content/images/blog/react-v17-rc/react_17_delegation.png b/content/images/blog/react-v17-rc/react_17_delegation.png new file mode 100644 index 000000000..c8b23c0d6 Binary files /dev/null and b/content/images/blog/react-v17-rc/react_17_delegation.png differ diff --git a/content/images/blog/thinking-in-react-components.png b/content/images/blog/thinking-in-react-components.png index c71a86bcb..a8b2b2e12 100644 Binary files a/content/images/blog/thinking-in-react-components.png and b/content/images/blog/thinking-in-react-components.png differ diff --git a/content/images/team/acdlite.jpg b/content/images/team/acdlite.jpg index ab54b793b..19ddb901f 100644 Binary files a/content/images/team/acdlite.jpg and b/content/images/team/acdlite.jpg differ diff --git a/content/images/team/bvaughn.jpg b/content/images/team/bvaughn.jpg deleted file mode 100644 index 227fe8d94..000000000 Binary files a/content/images/team/bvaughn.jpg and /dev/null differ diff --git a/content/images/team/gaearon.jpg b/content/images/team/gaearon.jpg index e152143b7..dff5a5173 100644 Binary files a/content/images/team/gaearon.jpg and b/content/images/team/gaearon.jpg differ diff --git a/content/images/team/jasonbonta.jpg b/content/images/team/jasonbonta.jpg new file mode 100644 index 000000000..139eb5ece Binary files /dev/null and b/content/images/team/jasonbonta.jpg differ diff --git a/content/images/team/joe.jpg b/content/images/team/joe.jpg new file mode 100644 index 000000000..0a533830b Binary files /dev/null and b/content/images/team/joe.jpg differ diff --git a/content/images/team/josh.jpg b/content/images/team/josh.jpg new file mode 100644 index 000000000..a3d1ff10d Binary files /dev/null and b/content/images/team/josh.jpg differ diff --git a/content/images/team/lauren.jpg b/content/images/team/lauren.jpg new file mode 100644 index 000000000..1485cf8ff Binary files /dev/null and b/content/images/team/lauren.jpg differ diff --git a/content/images/team/lunaruan.jpg b/content/images/team/lunaruan.jpg index 91daa3d17..f6de76d4e 100644 Binary files a/content/images/team/lunaruan.jpg and b/content/images/team/lunaruan.jpg differ diff --git a/content/images/team/mofei-zhang.png b/content/images/team/mofei-zhang.png new file mode 100644 index 000000000..9f957ada3 Binary files /dev/null and b/content/images/team/mofei-zhang.png differ diff --git a/content/images/team/necolas.jpg b/content/images/team/necolas.jpg deleted file mode 100644 index b7caaac06..000000000 Binary files a/content/images/team/necolas.jpg and /dev/null differ diff --git a/content/images/team/rickhanlonii.jpg b/content/images/team/rickhanlonii.jpg new file mode 100644 index 000000000..9165db5bc Binary files /dev/null and b/content/images/team/rickhanlonii.jpg differ diff --git a/content/images/team/rnabors.jpg b/content/images/team/rnabors.jpg deleted file mode 100644 index 4425c90db..000000000 Binary files a/content/images/team/rnabors.jpg and /dev/null differ diff --git a/content/images/team/sam.jpg b/content/images/team/sam.jpg new file mode 100644 index 000000000..1a23eb1d5 Binary files /dev/null and b/content/images/team/sam.jpg differ diff --git a/content/images/team/sathya.jpg b/content/images/team/sathya.jpg new file mode 100644 index 000000000..3d8ca7d58 Binary files /dev/null and b/content/images/team/sathya.jpg differ diff --git a/content/images/team/sebmarkbage.jpg b/content/images/team/sebmarkbage.jpg index 56a480ff4..b73e13cd7 100644 Binary files a/content/images/team/sebmarkbage.jpg and b/content/images/team/sebmarkbage.jpg differ diff --git a/content/images/team/sebsilbermann.jpg b/content/images/team/sebsilbermann.jpg new file mode 100644 index 000000000..f6fa04b37 Binary files /dev/null and b/content/images/team/sebsilbermann.jpg differ diff --git a/content/images/team/seth.jpg b/content/images/team/seth.jpg new file mode 100644 index 000000000..c665a0b00 Binary files /dev/null and b/content/images/team/seth.jpg differ diff --git a/content/images/team/sophiebits.jpg b/content/images/team/sophiebits.jpg new file mode 100644 index 000000000..da5548aca Binary files /dev/null and b/content/images/team/sophiebits.jpg differ diff --git a/content/images/team/threepointone.jpg b/content/images/team/threepointone.jpg deleted file mode 100644 index 9ad860171..000000000 Binary files a/content/images/team/threepointone.jpg and /dev/null differ diff --git a/content/images/team/tianyu.jpg b/content/images/team/tianyu.jpg new file mode 100644 index 000000000..68e423527 Binary files /dev/null and b/content/images/team/tianyu.jpg differ diff --git a/content/images/team/trueadm.jpg b/content/images/team/trueadm.jpg deleted file mode 100644 index 33a6b838f..000000000 Binary files a/content/images/team/trueadm.jpg and /dev/null differ diff --git a/content/images/tutorial/devtools.png b/content/images/tutorial/devtools.png index 6c4765703..59cea8db6 100644 Binary files a/content/images/tutorial/devtools.png and b/content/images/tutorial/devtools.png differ diff --git a/content/languages.yml b/content/languages.yml index 0e99ad33e..6f437c090 100644 --- a/content/languages.yml +++ b/content/languages.yml @@ -42,7 +42,7 @@ - name: Persian translated_name: فارسی code: fa - status: 0 + status: 1 - name: French translated_name: Français code: fr @@ -66,7 +66,7 @@ - name: Hungarian translated_name: magyar code: hu - status: 1 + status: 2 - name: Armenian translated_name: Հայերեն code: hy diff --git a/content/tutorial/tutorial.md b/content/tutorial/tutorial.md index 4db181562..5790b915c 100644 --- a/content/tutorial/tutorial.md +++ b/content/tutorial/tutorial.md @@ -12,6 +12,12 @@ redirect_from: - "docs/tutorial-zh-CN.html" --- +> Try the new React documentation. +> +> The updated [Tutorial](https://beta.reactjs.org/learn/tutorial-tic-tac-toe) teaches modern React and includes live examples. +> +> The new docs will soon replace this site, which will be archived. [Provide feedback.](https://github.com/reactjs/reactjs.org/issues/3308) + This tutorial doesn't assume any existing React knowledge. ## Before We Start the Tutorial {#before-we-start-the-tutorial} @@ -106,7 +112,7 @@ cd .. ```js import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import './index.css'; ``` @@ -170,7 +176,7 @@ JSX comes with the full power of JavaScript. You can put *any* JavaScript expres The `ShoppingList` component above only renders built-in DOM components like `<div />` and `<li />`. But you can compose and render custom React components too. For example, we can now refer to the whole shopping list by writing `<ShoppingList />`. Each React component is encapsulated and can operate independently; this allows you to build complex UIs from simple components. -## Inspecting the Starter Code {#inspecting-the-starter-code} +### Inspecting the Starter Code {#inspecting-the-starter-code} If you're going to work on the tutorial **in your browser,** open this code in a new tab: **[Starter Code](https://codepen.io/gaearon/pen/oWWQNa?editors=0010)**. If you're going to work on the tutorial **locally,** instead open `src/index.js` in your project folder (you have already touched this file during the [setup](#setup-option-2-local-development-environment)). @@ -235,7 +241,7 @@ First, change the button tag that is returned from the Square component's `rende class Square extends React.Component { render() { return ( - <button className="square" onClick={function() { alert('click'); }}> + <button className="square" onClick={function() { console.log('click'); }}> {this.props.value} </button> ); @@ -243,7 +249,7 @@ class Square extends React.Component { } ``` -If you click on a Square now, you should see an alert in your browser. +If you click on a Square now, you should see 'click' in your browser's devtools console. >Note > @@ -253,7 +259,7 @@ If you click on a Square now, you should see an alert in your browser. >class Square extends React.Component { > render() { > return ( -> <button className="square" onClick={() => alert('click')}> +> <button className="square" onClick={() => console.log('click')}> > {this.props.value} > </button> > ); @@ -261,7 +267,7 @@ If you click on a Square now, you should see an alert in your browser. >} >``` > ->Notice how with `onClick={() => alert('click')}`, we're passing *a function* as the `onClick` prop. React will only call this function after a click. Forgetting `() =>` and writing `onClick={alert('click')}` is a common mistake, and would fire the alert every time the component re-renders. +>Notice how with `onClick={() => console.log('click')}`, we're passing *a function* as the `onClick` prop. React will only call this function after a click. Forgetting `() =>` and writing `onClick={console.log('click')}` is a common mistake, and would fire every time the component re-renders. As a next step, we want the Square component to "remember" that it got clicked, and fill it with an "X" mark. To "remember" things, components use **state**. @@ -280,7 +286,7 @@ class Square extends React.Component { render() { return ( - <button className="square" onClick={() => alert('click')}> + <button className="square" onClick={() => console.log('click')}> {this.props.value} </button> ); @@ -454,7 +460,7 @@ When a Square is clicked, the `onClick` function provided by the Board is called 1. The `onClick` prop on the built-in DOM `<button>` component tells React to set up a click event listener. 2. When the button is clicked, React will call the `onClick` event handler that is defined in Square's `render()` method. 3. This event handler calls `this.props.onClick()`. The Square's `onClick` prop was specified by the Board. -4. Since the Board passed `onClick={() => this.handleClick(i)}` to Square, the Square calls `this.handleClick(i)` when clicked. +4. Since the Board passed `onClick={() => this.handleClick(i)}` to Square, the Square calls the Board's `handleClick(i)` when clicked. 5. We have not defined the `handleClick()` method yet, so our code crashes. If you click a square now, you should see a red error screen saying something like "this.handleClick is not a function". >Note @@ -524,7 +530,7 @@ Note how in `handleClick`, we call `.slice()` to create a copy of the `squares` ### Why Immutability Is Important {#why-immutability-is-important} -In the previous code example, we suggested that you use the `.slice()` method to create a copy of the `squares` array to modify instead of modifying the existing array. We'll now discuss immutability and why immutability is important to learn. +In the previous code example, we suggested that you create a copy of the `squares` array using the `slice()` method instead of modifying the existing array. We'll now discuss immutability and why immutability is important to learn. There are generally two approaches to changing data. The first approach is to *mutate* the data by directly changing the data's values. The second approach is to replace the data with a new copy which has the desired changes. @@ -542,7 +548,7 @@ var player = {score: 1, name: 'Jeff'}; var newPlayer = Object.assign({}, player, {score: 2}); // Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'} -// Or if you are using object spread syntax proposal, you can write: +// Or if you are using object spread syntax, you can write: // var newPlayer = {...player, score: 2}; ``` @@ -560,7 +566,7 @@ Detecting changes in immutable objects is considerably easier. If the immutable #### Determining When to Re-Render in React {#determining-when-to-re-render-in-react} -The main benefit of immutability is that it helps you build _pure components_ in React. Immutable data can easily determine if changes have been made which helps to determine when a component requires re-rendering. +The main benefit of immutability is that it helps you build _pure components_ in React. Immutable data can easily determine if changes have been made, which helps to determine when a component requires re-rendering. You can learn more about `shouldComponentUpdate()` and how you can build *pure components* by reading [Optimizing Performance](/docs/optimizing-performance.html#examples). @@ -1045,7 +1051,9 @@ Let's `map` over the `history` in the Game's `render` method: **[View the full code at this point](https://codepen.io/gaearon/pen/EmmGEa?editors=0010)** -For each move in the tic-tac-toes's game's history, we create a list item `<li>` which contains a button `<button>`. The button has a `onClick` handler which calls a method called `this.jumpTo()`. We haven't implemented the `jumpTo()` method yet. For now, we should see a list of the moves that have occurred in the game and a warning in the developer tools console that says: +As we iterate through `history` array, `step` variable refers to the current `history` element value, and `move` refers to the current `history` element index. We are only interested in `move` here, hence `step` is not getting assigned to anything. + +For each move in the tic-tac-toe game's history, we create a list item `<li>` which contains a button `<button>`. The button has a `onClick` handler which calls a method called `this.jumpTo()`. We haven't implemented the `jumpTo()` method yet. For now, we should see a list of the moves that have occurred in the game and a warning in the developer tools console that says: > Warning: > Each child in an array or iterator should have a unique "key" prop. Check the render method of "Game". @@ -1146,11 +1154,13 @@ Next, we'll define the `jumpTo` method in Game to update that `stepNumber`. We a } ``` +Notice in `jumpTo` method, we haven't updated `history` property of the state. That is because state updates are merged or in more simple words React will update only the properties mentioned in `setState` method leaving the remaining state as is. For more info **[see the documentation](/docs/state-and-lifecycle.html#state-updates-are-merged)**. + We will now make a few changes to the Game's `handleClick` method which fires when you click on a square. The `stepNumber` state we've added reflects the move displayed to the user now. After we make a new move, we need to update `stepNumber` by adding `stepNumber: history.length` as part of the `this.setState` argument. This ensures we don't get stuck showing the same move after a new one has been made. -We will also replace reading `this.state.history` with `this.state.history.slice(0, this.state.stepNumber + 1)`. This ensures that if we "go back in time" and then make a new move from that point, we throw away all the "future" history that would now become incorrect. +We will also replace reading `this.state.history` with `this.state.history.slice(0, this.state.stepNumber + 1)`. This ensures that if we "go back in time" and then make a new move from that point, we throw away all the "future" history that would now be incorrect. ```javascript{2,13} handleClick(i) { @@ -1195,7 +1205,7 @@ Congratulations! You've created a tic-tac-toe game that: * Stores a game's history as a game progresses, * Allows players to review a game's history and see previous versions of a game's board. -Nice work! We hope you now feel like you have a decent grasp on how React works. +Nice work! We hope you now feel like you have a decent grasp of how React works. Check out the final result here: **[Final Result](https://codepen.io/gaearon/pen/gWWZgR?editors=0010)**. diff --git a/content/versions.yml b/content/versions.yml index 2fd124a5f..b8fd08a78 100644 --- a/content/versions.yml +++ b/content/versions.yml @@ -1,54 +1,51 @@ +- title: '18.2.0' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1820-june-14-2022 +- title: '18.1.0' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1810-april-26-2022 +- title: '18.0.0' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1800-march-29-2022 +- title: '17.0.2' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1702-march-22-2021 + url: https://17.reactjs.org +- title: '17.0.1' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1701-october-22-2020 +- title: '17.0.0' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1700-october-20-2020 +- title: '16.14.0' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16140-october-14-2020 +- title: '16.13.1' + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16131-march-19-2020 - title: '16.13.0' - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#16130-february-26-2020 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16130-february-26-2020 - title: '16.12.0' - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#16120-november-14-2019 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16120-november-14-2019 - title: '16.11' - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#16110-october-22-2019 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16110-october-22-2019 - title: '16.10.2' - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#16102-october-3-2019 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16102-october-3-2019 - title: '16.10.1' - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#16101-september-28-2019 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16101-september-28-2019 - title: '16.10' - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#16100-september-27-2019 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#16100-september-27-2019 - title: '16.9' - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1690-august-8-2019 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1690-august-8-2019 - title: '16.8' - path: /version/16.8 - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1680-february-6-2019 - url: https://5d4b5feba32acd0008d0df98--reactjs.netlify.com/ + changelog: https://github.com/facebook/react/blob/main/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/ + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1670-december-19-2018 - title: '16.6' - path: /version/16.6 - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1660-october-23-2018 - url: https://5c11762d4be4d10008916ab1--reactjs.netlify.com/ + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1660-october-23-2018 - title: '16.5' - path: /version/16.5 - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1650-september-5-2018 - url: https://5bcf5863c6aed64970d6de5b--reactjs.netlify.com/ + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1650-september-5-2018 - title: '16.4' - path: /version/16.4 - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1640-may-23-2018 - url: https://5b90c17ac9659241e7f4c938--reactjs.netlify.com + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1640-may-23-2018 - title: '16.3' - path: /version/16.3 - url: https://5b05c94e0733d530fd1fafe0--reactjs.netlify.com - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1632-april-16-2018 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1632-april-16-2018 - title: '16.2' - path: /version/16.2 - url: https://5abc31d8be40f1556f06c4be--reactjs.netlify.com - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1620-november-28-2017 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1620-november-28-2017 - title: '16.1' - path: /version/16.1 - url: https://5a1dbcf14c4b93299e65b9a9--reactjs.netlify.com - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1611-november-13-2017 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1611-november-13-2017 - title: '16.0' - path: /version/16.0 - url: https://5a046bf5a6188f4b8fa4938a--reactjs.netlify.com - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1600-september-26-2017 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1600-september-26-2017 - title: '15.6' - path: /version/15.6 - url: https://react-legacy.netlify.com - changelog: https://github.com/facebook/react/blob/master/CHANGELOG.md#1562-september-25-2017 + changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1562-september-25-2017 diff --git a/content/warnings/invalid-aria-prop.md b/content/warnings/invalid-aria-prop.md index 53ebdd9bc..4aa18a0fd 100644 --- a/content/warnings/invalid-aria-prop.md +++ b/content/warnings/invalid-aria-prop.md @@ -8,4 +8,6 @@ The invalid-aria-prop warning will fire if you attempt to render a DOM element w 1. If you feel that you are using a valid prop, check the spelling carefully. `aria-labelledby` and `aria-activedescendant` are often misspelled. -2. React does not yet recognize the attribute you specified. This will likely be fixed in a future version of React. However, React currently strips all unknown attributes, so specifying them in your React app will not cause them to be rendered \ No newline at end of file +2. If you wrote `aria-role`, you may have meant `role`. + +3. Otherwise, if you're on the latest version of React DOM and verified that you're using a valid property name listed in the ARIA specification, please [report a bug](https://github.com/facebook/react/issues/new/choose). diff --git a/content/warnings/unknown-prop.md b/content/warnings/unknown-prop.md index 783d1b385..5b36059c8 100644 --- a/content/warnings/unknown-prop.md +++ b/content/warnings/unknown-prop.md @@ -33,7 +33,7 @@ function MyDiv(props) { } ``` -**Good:** The spread operator can be used to pull variables off props, and put the remaining props into a variable. +**Good:** The spread syntax can be used to pull variables off props, and put the remaining props into a variable. ```js function MyDiv(props) { diff --git a/examples/context/motivation-solution.js b/examples/context/motivation-solution.js index 94c6030a9..5cb4fc7f8 100644 --- a/examples/context/motivation-solution.js +++ b/examples/context/motivation-solution.js @@ -21,7 +21,7 @@ class App extends React.Component { // highlight-range{1,2} // A component in the middle doesn't have to // pass the theme down explicitly anymore. -function Toolbar(props) { +function Toolbar() { return ( <div> <ThemedButton /> diff --git a/examples/context/reference-caveats-problem.js b/examples/context/reference-caveats-problem.js index fa311c1d4..2191c3ad1 100644 --- a/examples/context/reference-caveats-problem.js +++ b/examples/context/reference-caveats-problem.js @@ -2,9 +2,9 @@ class App extends React.Component { render() { // highlight-range{2} return ( - <Provider value={{something: 'something'}}> + <MyContext.Provider value={{something: 'something'}}> <Toolbar /> - </Provider> + </MyContext.Provider> ); } } diff --git a/examples/context/reference-caveats-solution.js b/examples/context/reference-caveats-solution.js index deefabd46..a06f5da21 100644 --- a/examples/context/reference-caveats-solution.js +++ b/examples/context/reference-caveats-solution.js @@ -10,9 +10,9 @@ class App extends React.Component { render() { // highlight-range{2} return ( - <Provider value={this.state.value}> + <MyContext.Provider value={this.state.value}> <Toolbar /> - </Provider> + </MyContext.Provider> ); } } diff --git a/examples/context/theme-detailed-app.js b/examples/context/theme-detailed-app.js index 7edfe4db7..2e092d0d8 100644 --- a/examples/context/theme-detailed-app.js +++ b/examples/context/theme-detailed-app.js @@ -46,4 +46,7 @@ class App extends React.Component { } } -ReactDOM.render(<App />, document.root); +const root = ReactDOM.createRoot( + document.getElementById('root') +); +root.render(<App />); diff --git a/examples/context/updating-nested-context-app.js b/examples/context/updating-nested-context-app.js index 3644e89a6..883d35802 100644 --- a/examples/context/updating-nested-context-app.js +++ b/examples/context/updating-nested-context-app.js @@ -42,4 +42,7 @@ function Content() { ); } -ReactDOM.render(<App />, document.root); +const root = ReactDOM.createRoot( + document.getElementById('root') +); +root.render(<App />); diff --git a/examples/hello-world.js b/examples/hello-world.js index d0f87a59b..ea2dcf16a 100644 --- a/examples/hello-world.js +++ b/examples/hello-world.js @@ -1,4 +1,4 @@ -ReactDOM.render( - <h1>Hello, world!</h1>, +const root = ReactDOM.createRoot( document.getElementById('root') ); +root.render(<h1>Hello, world!</h1>); diff --git a/examples/introducing-jsx.js b/examples/introducing-jsx.js index 3f1a07f35..54d58b12a 100644 --- a/examples/introducing-jsx.js +++ b/examples/introducing-jsx.js @@ -9,4 +9,7 @@ const user = { const element = <h1>Hello, {formatName(user)}!</h1>; -ReactDOM.render(element, document.getElementById('root')); +const root = ReactDOM.createRoot( + document.getElementById('root') +); +root.render(element); diff --git a/examples/rendering-elements/render-an-element.js b/examples/rendering-elements/render-an-element.js index 024b35876..315b33cb9 100644 --- a/examples/rendering-elements/render-an-element.js +++ b/examples/rendering-elements/render-an-element.js @@ -1,2 +1,5 @@ +const root = ReactDOM.createRoot( + document.getElementById('root') +); const element = <h1>Hello, world</h1>; -ReactDOM.render(element, document.getElementById('root')); +root.render(element); diff --git a/examples/rendering-elements/update-rendered-element.js b/examples/rendering-elements/update-rendered-element.js index a4dabafc7..290e4edaa 100644 --- a/examples/rendering-elements/update-rendered-element.js +++ b/examples/rendering-elements/update-rendered-element.js @@ -1,3 +1,7 @@ +const root = ReactDOM.createRoot( + document.getElementById('root') +); + function tick() { const element = ( <div> @@ -6,7 +10,7 @@ function tick() { </div> ); // highlight-next-line - ReactDOM.render(element, document.getElementById('root')); + root.render(element); } setInterval(tick, 1000); diff --git a/examples/uncontrolled-components/input-type-file.js b/examples/uncontrolled-components/input-type-file.js index 75e055d0d..023f8a8be 100644 --- a/examples/uncontrolled-components/input-type-file.js +++ b/examples/uncontrolled-components/input-type-file.js @@ -6,12 +6,10 @@ class FileInput extends React.Component { this.fileInput = React.createRef(); } handleSubmit(event) { - // highlight-range{4} + // highlight-range{3} event.preventDefault(); alert( - `Selected file - ${ - this.fileInput.current.files[0].name - }` + `Selected file - ${this.fileInput.current.files[0].name}` ); } @@ -30,7 +28,7 @@ class FileInput extends React.Component { } } -ReactDOM.render( - <FileInput />, +const root = ReactDOM.createRoot( document.getElementById('root') ); +root.render(<FileInput />); diff --git a/flow-typed/gatsby-plugin-google-analytics.js b/flow-typed/gatsby-plugin-google-analytics.js new file mode 100644 index 000000000..9d7de7e9c --- /dev/null +++ b/flow-typed/gatsby-plugin-google-analytics.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +declare module 'gatsby-plugin-google-analytics' { + declare module.exports: { + trackCustomEvent: (...params: any) => void, + }; +} diff --git a/flow-typed/gatsby.js b/flow-typed/gatsby.js index 8fec2756a..b7b8bcaa9 100644 --- a/flow-typed/gatsby.js +++ b/flow-typed/gatsby.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + declare module 'gatsby' { declare module.exports: any; } diff --git a/flow-typed/glamor.js b/flow-typed/glamor.js index 1c87bfc20..973abe923 100644 --- a/flow-typed/glamor.js +++ b/flow-typed/glamor.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + declare module 'glamor' { declare module.exports: { css: { diff --git a/flow-typed/hex2rgba.js b/flow-typed/hex2rgba.js index 3d67cf8d9..1d7395e0c 100644 --- a/flow-typed/hex2rgba.js +++ b/flow-typed/hex2rgba.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + declare module 'hex2rgba' { declare module.exports: (hex: string, alpha?: number) => string; } diff --git a/flow-typed/react-helmet.js b/flow-typed/react-helmet.js index a614e9ad4..c2cbcb24f 100644 --- a/flow-typed/react-helmet.js +++ b/flow-typed/react-helmet.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + declare module 'react-helmet' { declare module.exports: any; } diff --git a/flow-typed/slugify.js b/flow-typed/slugify.js index 82a44ce41..c1fb6e19b 100644 --- a/flow-typed/slugify.js +++ b/flow-typed/slugify.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + declare module 'slugify' { declare module.exports: any; } diff --git a/gatsby-browser.js b/gatsby-browser.js index 83d72722d..a6d4cffe4 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/gatsby-config.js b/gatsby-config.js index 2637eac97..26f6ed853 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ @@ -62,13 +62,14 @@ module.exports = { options: { defaultText: '<b>Try it on CodePen</b>', directory: `${__dirname}/examples/`, - externals: [ - `//unpkg.com/react/umd/react.development.js`, - `//unpkg.com/react-dom/umd/react-dom.development.js`, - ], - dependencies: [`react`, `react-dom`], - redirectTemplate: `${__dirname}/src/templates/codepen-example.js`, target: '_blank', + codepen: { + redirectTemplate: `${__dirname}/src/templates/codepen-example.js`, + externals: [ + `//unpkg.com/react/umd/react.development.js`, + `//unpkg.com/react-dom/umd/react-dom.development.js`, + ], + }, }, }, { diff --git a/gatsby-node.js b/gatsby-node.js index 83d7ae0fc..b8943349e 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/gatsby/createPages.js b/gatsby/createPages.js index 883028963..38092c7f0 100644 --- a/gatsby/createPages.js +++ b/gatsby/createPages.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/gatsby/onCreateNode.js b/gatsby/onCreateNode.js index 0e6e9934d..66625420a 100644 --- a/gatsby/onCreateNode.js +++ b/gatsby/onCreateNode.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/gatsby/onCreatePage.js b/gatsby/onCreatePage.js index 7776438fc..81914eddc 100644 --- a/gatsby/onCreatePage.js +++ b/gatsby/onCreatePage.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/gatsby/onCreateWebpackConfig.js b/gatsby/onCreateWebpackConfig.js index 61f858bf0..f1d282d11 100644 --- a/gatsby/onCreateWebpackConfig.js +++ b/gatsby/onCreateWebpackConfig.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/package.json b/package.json index ca66694cb..201601466 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,12 @@ "eslint-config-react": "^1.1.7", "eslint-plugin-babel": "^4.1.2", "eslint-plugin-flowtype": "^2.39.1", - "eslint-plugin-jsx-a11y": "^6.0.2", + "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^2.3.1", - "eslint-plugin-react": "^7.4.0", + "eslint-plugin-react": "^7.21.5", "eslint-plugin-relay": "^0.0.19", "flow-bin": "^0.56.0", - "gatsby": "^2.0.0", + "gatsby": "^2.24.63", "gatsby-plugin-catch-links": "^2.0.0", "gatsby-plugin-feed": "^2.0.0", "gatsby-plugin-glamor": "^2.0.0", @@ -27,9 +27,9 @@ "gatsby-plugin-netlify": "^2.0.0", "gatsby-plugin-nprogress": "^2.0.0", "gatsby-plugin-react-helmet": "^3.0.0", - "gatsby-plugin-sharp": "^2.0.0", + "gatsby-plugin-sharp": "^2.4.12", "gatsby-plugin-twitter": "^2.0.0", - "gatsby-remark-code-repls": "^2.0.0", + "gatsby-remark-code-repls": "^3.0.0", "gatsby-remark-copy-linked-files": "^2.0.0", "gatsby-remark-embed-snippet": "^3.0.0", "gatsby-remark-external-links": "^0.0.4", @@ -39,16 +39,16 @@ "gatsby-remark-smartypants": "^2.0.0", "gatsby-source-filesystem": "^2.0.0", "gatsby-transformer-remark": "^2.0.0", - "gatsby-transformer-sharp": "^2.0.0", + "gatsby-transformer-sharp": "^2.3.18", "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", - "react": "^16.13.0", - "react-dom": "^16.13.0", + "prismjs": "^1.27.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", "react-helmet": "^5.2.0", "react-live": "1.8.0-0", "remarkable": "^1.7.1", @@ -58,13 +58,11 @@ "unist-util-visit": "^1.1.3" }, "engines": { - "node": ">8.4.0", + "node": "12.x.x || 14.x.x || 15.x.x || 16.x.x", "yarn": "^1.3.2" }, "homepage": "https://reactjs.org/", - "keywords": [ - "gatsby" - ], + "keywords": [], "license": "CC", "main": "n/a", "repository": { @@ -72,7 +70,7 @@ "url": "git+https://github.com/reactjs/reactjs.org.git" }, "scripts": { - "build": "gatsby build", + "build": "gatsby clean && gatsby build", "check-all": "npm-run-all prettier generate-ids --parallel lint flow", "ci-check": "npm-run-all prettier:diff --parallel lint flow", "dev": "gatsby develop -H 0.0.0.0", @@ -96,5 +94,8 @@ "npm-run-all": "^4.1.5", "recursive-readdir": "^2.2.1", "unist-util-map": "^1.0.3" + }, + "resolutions": { + "**/sharp": "^0.30.3" } } diff --git a/plugins/gatsby-remark-header-custom-ids/gatsby-client.js b/plugins/gatsby-remark-header-custom-ids/gatsby-client.js index 6b24d0b13..c0e8cf283 100644 --- a/plugins/gatsby-remark-header-custom-ids/gatsby-client.js +++ b/plugins/gatsby-remark-header-custom-ids/gatsby-client.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + let offsetY = 0; const getTargetOffset = hash => { diff --git a/plugins/gatsby-remark-header-custom-ids/gatsby-ssr.js b/plugins/gatsby-remark-header-custom-ids/gatsby-ssr.js index c616fc289..60bce60a4 100644 --- a/plugins/gatsby-remark-header-custom-ids/gatsby-ssr.js +++ b/plugins/gatsby-remark-header-custom-ids/gatsby-ssr.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + const React = require(`react`); const pluginDefaults = { diff --git a/plugins/gatsby-remark-header-custom-ids/index.js b/plugins/gatsby-remark-header-custom-ids/index.js index df6457073..aa24fdcc8 100644 --- a/plugins/gatsby-remark-header-custom-ids/index.js +++ b/plugins/gatsby-remark-header-custom-ids/index.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + /*! * Based on 'gatsby-remark-autolink-headers' * Original Author: Kyle Mathews <mathews.kyle@gmail.com> diff --git a/plugins/gatsby-remark-use-jsx/index.js b/plugins/gatsby-remark-use-jsx/index.js index c76a2276e..0bf0380ac 100644 --- a/plugins/gatsby-remark-use-jsx/index.js +++ b/plugins/gatsby-remark-use-jsx/index.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + const visit = require('unist-util-visit'); // Always treat JS blocks as JSX. diff --git a/plugins/gatsby-source-react-error-codes/gatsby-node.js b/plugins/gatsby-source-react-error-codes/gatsby-node.js index 0eef079ec..414e50d46 100644 --- a/plugins/gatsby-source-react-error-codes/gatsby-node.js +++ b/plugins/gatsby-source-react-error-codes/gatsby-node.js @@ -1,7 +1,11 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + const request = require('request-promise'); const errorCodesUrl = - 'https://raw.githubusercontent.com/facebook/react/master/scripts/error-codes/codes.json'; + 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'; exports.sourceNodes = async ({actions}) => { const {createNode} = actions; @@ -20,9 +24,7 @@ exports.sourceNodes = async ({actions}) => { }); } catch (error) { console.error( - `The gatsby-source-react-error-codes plugin has failed:\n${ - error.message - }`, + `The gatsby-source-react-error-codes plugin has failed:\n${error.message}`, ); process.exit(1); diff --git a/plugins/gatsby-transformer-authors-yaml/gatsby-node.js b/plugins/gatsby-transformer-authors-yaml/gatsby-node.js index bb5ca8bc4..04bfc98e2 100644 --- a/plugins/gatsby-transformer-authors-yaml/gatsby-node.js +++ b/plugins/gatsby-transformer-authors-yaml/gatsby-node.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + const readFileSync = require('fs').readFileSync; const resolve = require('path').resolve; const safeLoad = require('js-yaml').safeLoad; diff --git a/plugins/gatsby-transformer-home-example-code/gatsby-node.js b/plugins/gatsby-transformer-home-example-code/gatsby-node.js index 4e273ce2d..cf689036f 100644 --- a/plugins/gatsby-transformer-home-example-code/gatsby-node.js +++ b/plugins/gatsby-transformer-home-example-code/gatsby-node.js @@ -1,5 +1,8 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + const crypto = require('crypto'); -const path = require('path'); const createContentDigest = obj => crypto @@ -15,7 +18,7 @@ exports.onCreateNode = async ({actions, node, loadNodeContent}) => { if ( sourceInstanceName === 'content' && - relativeDirectory === path.join('home', 'examples') && + relativeDirectory === 'home/examples' && ext === '.js' ) { const code = await loadNodeContent(node); diff --git a/plugins/gatsby-transformer-versions-yaml/create-redirects.js b/plugins/gatsby-transformer-versions-yaml/create-redirects.js index af9c49a55..3314efaaf 100644 --- a/plugins/gatsby-transformer-versions-yaml/create-redirects.js +++ b/plugins/gatsby-transformer-versions-yaml/create-redirects.js @@ -1,6 +1,10 @@ -const {appendFile, exists, readFile, writeFile} = require('fs-extra'); +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ -const HEADER_COMMENT = `## Created with gatsby-transformer-versions-yaml`; +const readFileSync = require('fs').readFileSync; +const resolve = require('path').resolve; +const {writeFile} = require('fs-extra'); // Patterned after the 'gatsby-plugin-netlify' plug-in: // https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-netlify/src/create-redirects.js @@ -12,56 +16,53 @@ module.exports = async function writeRedirectsFile( return null; } - // Map redirect data to the format Netlify expects - // https://www.netlify.com/docs/redirects/ - redirects = redirects.map(redirect => { - const { - fromPath, - isPermanent, - redirectInBrowser, // eslint-disable-line no-unused-vars - toPath, - ...rest - } = redirect; + /** + * We will first read the old config to validate if the redirect already exists in the json + */ + const vercelConfigPath = resolve(__dirname, '../../vercel.json'); + const vercelConfigFile = readFileSync(vercelConfigPath); + const oldConfigContent = JSON.parse(vercelConfigFile); + /** + * Map data as vercel expects it to be + */ - // The order of these parameters is significant. - const pieces = [ - fromPath, - toPath, - isPermanent ? 301 : 302, // Status - ]; + let vercelRedirects = {}; - for (let key in rest) { - const value = rest[key]; + redirects.forEach(redirect => { + const {fromPath, isPermanent, toPath} = redirect; - if (typeof value === `string` && value.indexOf(` `) >= 0) { - console.warn( - `Invalid redirect value "${value}" specified for key "${key}". ` + - `Values should not contain spaces.`, - ); - } else { - pieces.push(`${key}=${value}`); - } - } - - return pieces.join(` `); + vercelRedirects[fromPath] = { + destination: toPath, + permanent: !!isPermanent, + }; }); - - let appendToFile = false; - - // Websites may also have statically defined redirects - // In that case we should append to them (not overwrite) - // Make sure we aren't just looking at previous build results though - const fileExists = await exists(redirectsFilePath); - if (fileExists) { - const fileContents = await readFile(redirectsFilePath); - if (fileContents.indexOf(HEADER_COMMENT) < 0) { - appendToFile = true; + /** + * Make sure we dont have the same redirect already + */ + oldConfigContent.redirects.forEach(data => { + if (vercelRedirects[data.source]) { + delete vercelRedirects[data.source]; } - } + }); - const data = `${HEADER_COMMENT}\n\n${redirects.join(`\n`)}`; + /** + * Serialize the object to array of objects + */ + let newRedirects = []; + Object.keys(vercelRedirects).forEach(value => + newRedirects.push({ + source: value, + destination: vercelRedirects[value].destination, + permanent: !!vercelRedirects[value].isPermanent, + }), + ); - return appendToFile - ? appendFile(redirectsFilePath, `\n\n${data}`) - : writeFile(redirectsFilePath, data); + /** + * We already have a vercel.json so we spread the new contents along with old ones + */ + const newContents = { + ...oldConfigContent, + redirects: [...oldConfigContent.redirects, ...newRedirects], + }; + return writeFile(redirectsFilePath, JSON.stringify(newContents, null, 2)); }; diff --git a/plugins/gatsby-transformer-versions-yaml/gatsby-node.js b/plugins/gatsby-transformer-versions-yaml/gatsby-node.js index e73b566f3..a71244a8a 100644 --- a/plugins/gatsby-transformer-versions-yaml/gatsby-node.js +++ b/plugins/gatsby-transformer-versions-yaml/gatsby-node.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + const readFileSync = require('fs').readFileSync; const resolve = require('path').resolve; const safeLoad = require('js-yaml').safeLoad; @@ -12,18 +16,16 @@ exports.onPostBuild = async ({store}) => { const versions = safeLoad(file); const {program} = store.getState(); - const redirectsFilePath = path.join( - program.directory, - 'public', - '_redirects', - ); + const redirectsFilePath = path.join(program.directory, 'vercel.json'); // versions.yml structure is [{path: string, url: string, ...}, ...] - createRedirects( - versions.filter(version => version.path && version.url).map(version => ({ - fromPath: version.path, - toPath: version.url, - })), + await createRedirects( + versions + .filter(version => version.path && version.url) + .map(version => ({ + fromPath: version.path, + toPath: version.url, + })), redirectsFilePath, ); }; diff --git a/scripts/generateHeadingIDs.js b/scripts/generateHeadingIDs.js index a8130edf0..2a051764f 100644 --- a/scripts/generateHeadingIDs.js +++ b/scripts/generateHeadingIDs.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + const fs = require('fs'); const GithubSlugger = require('github-slugger'); diff --git a/src/components/ButtonLink/ButtonLink.js b/src/components/ButtonLink/ButtonLink.js index 9367fe2b3..0732aa05f 100644 --- a/src/components/ButtonLink/ButtonLink.js +++ b/src/components/ButtonLink/ButtonLink.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/ButtonLink/index.js b/src/components/ButtonLink/index.js index f4ed7b7a0..4125bb2ec 100644 --- a/src/components/ButtonLink/index.js +++ b/src/components/ButtonLink/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/CodeEditor/CodeEditor.js b/src/components/CodeEditor/CodeEditor.js index 2abb8ad4b..dbf21abde 100644 --- a/src/components/CodeEditor/CodeEditor.js +++ b/src/components/CodeEditor/CodeEditor.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ @@ -248,14 +248,23 @@ class CodeEditor extends Component { _render() { const {compiled} = this.state; + const {containerNodeID} = this.props; + + // Until we upgrade Gatsby to React 18, fake the new root API. + const root = { + render: element => { + ReactDOM.render(element, document.getElementById(containerNodeID)); + }, + }; try { // Example code requires React, ReactDOM, and Remarkable to be within scope. // It also requires a "mountNode" variable for ReactDOM.render() // eslint-disable-next-line no-new-func - new Function('React', 'ReactDOM', 'Remarkable', compiled)( + new Function('React', 'ReactDOM', 'root', 'Remarkable', compiled)( React, ReactDOM, + root, Remarkable, ); } catch (error) { diff --git a/src/components/CodeEditor/index.js b/src/components/CodeEditor/index.js index 4cd1787dc..cc755885d 100644 --- a/src/components/CodeEditor/index.js +++ b/src/components/CodeEditor/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/CodeExample/CodeExample.js b/src/components/CodeExample/CodeExample.js index bc5d6ed10..6bb7e1ab5 100644 --- a/src/components/CodeExample/CodeExample.js +++ b/src/components/CodeExample/CodeExample.js @@ -1,10 +1,23 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @flow + */ + import React, {Component} from 'react'; -import PropTypes from 'prop-types'; import {colors, media} from 'theme'; import CodeEditor from '../CodeEditor/CodeEditor'; -class CodeExample extends Component { +type Props = {| + children: React$Node, + code: boolean, + containerNodeID: string, + id: string, + loaded: boolean, +|}; + +class CodeExample extends Component<Props> { render() { const {children, code, id, containerNodeID, loaded} = this.props; return ( @@ -68,10 +81,4 @@ class CodeExample extends Component { } } -CodeExample.propTypes = { - children: PropTypes.node, - code: PropTypes.string.isRequired, - loaded: PropTypes.bool.isRequired, -}; - export default CodeExample; diff --git a/src/components/CodeExample/index.js b/src/components/CodeExample/index.js index 6bff868c1..201835a15 100644 --- a/src/components/CodeExample/index.js +++ b/src/components/CodeExample/index.js @@ -1,3 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @flow + */ + import CodeExample from './CodeExample'; export default CodeExample; diff --git a/src/components/Container/Container.js b/src/components/Container/Container.js index 8ed280c58..c03560e55 100644 --- a/src/components/Container/Container.js +++ b/src/components/Container/Container.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -22,7 +22,6 @@ const Container = ({children}: {children: Node}) => ( paddingRight: 20, marginLeft: 'auto', marginRight: 'auto', - [media.greaterThan('medium')]: { width: '90%', }, diff --git a/src/components/Container/index.js b/src/components/Container/index.js index 6270e9c66..da0bada21 100644 --- a/src/components/Container/index.js +++ b/src/components/Container/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/ErrorDecoder/ErrorDecoder.js b/src/components/ErrorDecoder/ErrorDecoder.js index d179a09be..f9d2a7380 100644 --- a/src/components/ErrorDecoder/ErrorDecoder.js +++ b/src/components/ErrorDecoder/ErrorDecoder.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/components/ErrorDecoder/index.js b/src/components/ErrorDecoder/index.js index a3762d429..1a9113b35 100644 --- a/src/components/ErrorDecoder/index.js +++ b/src/components/ErrorDecoder/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/FeedbackForm/FeedbackForm.js b/src/components/FeedbackForm/FeedbackForm.js new file mode 100644 index 000000000..d38fab5a2 --- /dev/null +++ b/src/components/FeedbackForm/FeedbackForm.js @@ -0,0 +1,72 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @emails react-core + * @flow + */ + +// $FlowExpectedError +import React, {useState} from 'react'; +import {trackCustomEvent} from 'gatsby-plugin-google-analytics'; +import {sharedStyles} from 'theme'; + +const FeedbackForm = () => { + const [feedbackGiven, setFeedbackGiven] = useState(false); + + if (feedbackGiven) { + return 'Thanks for letting us know!'; + } else { + return ( + <span> + Is this page useful? + <button + css={[sharedStyles.articleLayout.feedbackButton, {marginLeft: '6px'}]} + aria-label="Yes" + onClick={e => { + e.preventDefault(); + trackCustomEvent({ + category: 'Feedback Button', + action: 'feedback', + label: window.location.pathname, + value: 1, + }); + setFeedbackGiven(true); + }}> + <svg + css={{ + transform: 'translateY(0.1em)', + }} + focusable="false" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 81.13 89.76"> + <path d="M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z" /> + </svg> + </button> + <button + css={[sharedStyles.articleLayout.feedbackButton, {marginLeft: '3px'}]} + aria-label="No" + onClick={e => { + e.preventDefault(); + trackCustomEvent({ + category: 'Feedback Button', + action: 'feedback', + label: window.location.pathname, + value: 0, + }); + setFeedbackGiven(true); + }}> + <svg + css={{ + transform: 'scale(-1, -1) translateY(-.6em)', + }} + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 81.13 89.76"> + <path d="M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z" /> + </svg> + </button> + </span> + ); + } +}; + +export default FeedbackForm; diff --git a/src/components/FeedbackForm/index.js b/src/components/FeedbackForm/index.js new file mode 100644 index 000000000..bc478da58 --- /dev/null +++ b/src/components/FeedbackForm/index.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @emails react-core + */ + +import FeedbackForm from './FeedbackForm'; + +export default FeedbackForm; diff --git a/src/components/Flex/Flex.js b/src/components/Flex/Flex.js index 4d7e74aef..da93fecaa 100644 --- a/src/components/Flex/Flex.js +++ b/src/components/Flex/Flex.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/components/Flex/index.js b/src/components/Flex/index.js index 478182639..8de05559a 100644 --- a/src/components/Flex/index.js +++ b/src/components/Flex/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 39422d958..66a057182 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/components/Header/index.js b/src/components/Header/index.js index 239c11525..057fc4c80 100644 --- a/src/components/Header/index.js +++ b/src/components/Header/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index dc727c082..143fb7dda 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -48,12 +48,15 @@ class Template extends Component<Props> { valign="stretch" css={{ flex: '1 0 auto', - marginTop: 60, - [media.between('medium', 'large')]: { - marginTop: 50, + marginTop: + 'calc(var(--header-height-large) + var(--survey-banner-height-normal) + var(--social-banner-height-normal))', + [media.size('medium')]: { + marginTop: + 'calc(var(--header-height-normal) + var(--survey-banner-height-normal) + var(--social-banner-height-normal))', }, - [media.lessThan('medium')]: { - marginTop: 40, + [media.lessThan('small')]: { + marginTop: + 'calc(var(--header-height-small) + var(--survey-banner-height-small) + var(--social-banner-height-small))', }, }}> {children} diff --git a/src/components/Layout/index.js b/src/components/Layout/index.js index 67d6127d3..c5e951e65 100644 --- a/src/components/Layout/index.js +++ b/src/components/Layout/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/LayoutFooter/ExternalFooterLink.js b/src/components/LayoutFooter/ExternalFooterLink.js index db061f4a3..600f0d1a4 100644 --- a/src/components/LayoutFooter/ExternalFooterLink.js +++ b/src/components/LayoutFooter/ExternalFooterLink.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/components/LayoutFooter/Footer.js b/src/components/LayoutFooter/Footer.js index bc347861f..06bc37697 100644 --- a/src/components/LayoutFooter/Footer.js +++ b/src/components/LayoutFooter/Footer.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -86,7 +86,7 @@ const Footer = ({layoutHasSidebar = false}: {layoutHasSidebar: boolean}) => ( <FooterNav layoutHasSidebar={layoutHasSidebar}> <MetaTitle onDark={true}>{navFooter.community.title}</MetaTitle> <ExternalFooterLink - href={`https://github.com/facebook/react/blob/master/CODE_OF_CONDUCT.md`}> + href={`https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md`}> Code of Conduct </ExternalFooterLink> {sectionListCommunity.map(section => ( @@ -100,6 +100,12 @@ const Footer = ({layoutHasSidebar = false}: {layoutHasSidebar: boolean}) => ( <FooterNav layoutHasSidebar={layoutHasSidebar}> <MetaTitle onDark={true}>{navFooter.more.title}</MetaTitle> <SectionLinks links={navFooter.more.items} /> + <ExternalFooterLink href="https://opensource.facebook.com/legal/privacy"> + Privacy + </ExternalFooterLink> + <ExternalFooterLink href="https://opensource.facebook.com/legal/terms"> + Terms + </ExternalFooterLink> </FooterNav> </div> <section @@ -122,7 +128,7 @@ const Footer = ({layoutHasSidebar = false}: {layoutHasSidebar: boolean}) => ( }, }}> <a - href="https://code.facebook.com/projects/" + href="https://opensource.facebook.com/projects/" target="_blank" rel="noopener"> <img @@ -139,7 +145,7 @@ const Footer = ({layoutHasSidebar = false}: {layoutHasSidebar: boolean}) => ( color: colors.subtleOnDark, paddingTop: 15, }}> - {`Copyright © ${new Date().getFullYear()} Facebook Inc.`} + {`Copyright © ${new Date().getFullYear()} Meta Platforms, Inc.`} </p> </section> </div> diff --git a/src/components/LayoutFooter/FooterLink.js b/src/components/LayoutFooter/FooterLink.js index 5469e4a05..5606c4e07 100644 --- a/src/components/LayoutFooter/FooterLink.js +++ b/src/components/LayoutFooter/FooterLink.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/components/LayoutFooter/FooterNav.js b/src/components/LayoutFooter/FooterNav.js index d94c7db6d..2a2deda1d 100644 --- a/src/components/LayoutFooter/FooterNav.js +++ b/src/components/LayoutFooter/FooterNav.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/components/LayoutFooter/SectionLinks.js b/src/components/LayoutFooter/SectionLinks.js index 6bc75889b..a5e30311a 100644 --- a/src/components/LayoutFooter/SectionLinks.js +++ b/src/components/LayoutFooter/SectionLinks.js @@ -1,7 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @flow + */ + import React from 'react'; import ExternalFooterLink from './ExternalFooterLink'; import FooterLink from './FooterLink'; +type Link = {| + title: string, + to: string, +|}; + +type Props = {| + links: Array<Link>, +|}; + const SectionLinks = ({links}: Props) => links.map(item => { if (item.external) { diff --git a/src/components/LayoutFooter/index.js b/src/components/LayoutFooter/index.js index 1ffbe907b..e301318f5 100644 --- a/src/components/LayoutFooter/index.js +++ b/src/components/LayoutFooter/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/LayoutHeader/DocSearch.js b/src/components/LayoutHeader/DocSearch.js index f9827fc51..3f9fc2af6 100644 --- a/src/components/LayoutHeader/DocSearch.js +++ b/src/components/LayoutHeader/DocSearch.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -44,7 +44,6 @@ class DocSearch extends Component<{}, State> { alignItems: 'center', paddingLeft: '0.25rem', paddingRight: '0.25rem', - [media.lessThan('expandedSearch')]: { justifyContent: 'flex-end', marginRight: 10, @@ -56,6 +55,7 @@ class DocSearch extends Component<{}, State> { // }, [media.greaterThan('expandedSearch')]: { minWidth: 100, + width: 'calc(100% / 5)', }, }}> <input diff --git a/src/components/LayoutHeader/Header.js b/src/components/LayoutHeader/Header.js index cfe3e77d0..e4ac955fd 100644 --- a/src/components/LayoutHeader/Header.js +++ b/src/components/LayoutHeader/Header.js @@ -1,10 +1,12 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow */ +import SurveyBanner from 'components/SurveyBanner'; +import SocialBanner from 'components/SocialBanner'; import Container from 'components/Container'; import HeaderLink from './HeaderLink'; import {Link} from 'gatsby'; @@ -19,6 +21,15 @@ import navHeader from '../../../content/headerNav.yml'; import logoSvg from 'icons/logo.svg'; +const ContainerWrapper = ({children}) => ( + <div + css={{ + backgroundColor: 'hsl(222, 14%, 10%)', + }}> + {children} + </div> +); + const Header = ({location}: {location: Location}) => ( <header css={{ @@ -33,18 +44,26 @@ const Header = ({location}: {location: Location}) => ( display: 'none', }, }}> + <ContainerWrapper> + <Container> + <div style={{position: 'relative'}}> + <SurveyBanner /> + <SocialBanner /> + </div> + </Container> + </ContainerWrapper> <Container> <div css={{ display: 'flex', flexDirection: 'row', alignItems: 'center', - height: 60, - [media.between('small', 'large')]: { - height: 50, + height: 'var(--header-height-large)', + [media.size('medium')]: { + height: 'var(--header-height-normal)', }, [media.lessThan('small')]: { - height: 40, + height: 'var(--header-height-small)', }, }}> <Link diff --git a/src/components/LayoutHeader/HeaderLink.js b/src/components/LayoutHeader/HeaderLink.js index 0534238fc..28c144975 100644 --- a/src/components/LayoutHeader/HeaderLink.js +++ b/src/components/LayoutHeader/HeaderLink.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/components/LayoutHeader/SearchSvg.js b/src/components/LayoutHeader/SearchSvg.js index e3604b322..6d407a1ae 100644 --- a/src/components/LayoutHeader/SearchSvg.js +++ b/src/components/LayoutHeader/SearchSvg.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/LayoutHeader/index.js b/src/components/LayoutHeader/index.js index 239c11525..057fc4c80 100644 --- a/src/components/LayoutHeader/index.js +++ b/src/components/LayoutHeader/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/MarkdownHeader/MarkdownHeader.js b/src/components/MarkdownHeader/MarkdownHeader.js index b638d8339..b04beda10 100644 --- a/src/components/MarkdownHeader/MarkdownHeader.js +++ b/src/components/MarkdownHeader/MarkdownHeader.js @@ -1,34 +1,37 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow */ import Flex from 'components/Flex'; +// $FlowFixMe Update Flow import React from 'react'; import {colors, fonts, media} from 'theme'; -const MarkdownHeader = ({title}: {title: string}) => ( - <Flex type="header" halign="space-between" valign="baseline"> - <h1 - css={{ - color: colors.dark, - marginBottom: 0, - marginTop: 40, - ...fonts.header, +const MarkdownHeader = ({title}: {title: string}) => { + return ( + <Flex type="header" halign="space-between" valign="baseline"> + <h1 + css={{ + color: colors.dark, + marginBottom: 0, + marginTop: 80, + ...fonts.header, - [media.size('medium')]: { - marginTop: 60, - }, + [media.size('medium')]: { + marginTop: 60, + }, - [media.greaterThan('large')]: { - marginTop: 80, - }, - }}> - {title} - </h1> - </Flex> -); + [media.lessThan('small')]: { + marginTop: 40, + }, + }}> + {title} + </h1> + </Flex> + ); +}; export default MarkdownHeader; diff --git a/src/components/MarkdownHeader/index.js b/src/components/MarkdownHeader/index.js index 9455e3c67..3bd950415 100644 --- a/src/components/MarkdownHeader/index.js +++ b/src/components/MarkdownHeader/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/MarkdownPage/MarkdownPage.js b/src/components/MarkdownPage/MarkdownPage.js index 4e4e082f8..df754178c 100644 --- a/src/components/MarkdownPage/MarkdownPage.js +++ b/src/components/MarkdownPage/MarkdownPage.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -9,13 +9,15 @@ import Container from 'components/Container'; import Flex from 'components/Flex'; import MarkdownHeader from 'components/MarkdownHeader'; import NavigationFooter from 'templates/components/NavigationFooter'; +// $FlowFixMe Update Flow import React from 'react'; import StickyResponsiveSidebar from 'components/StickyResponsiveSidebar'; import TitleAndMetaTags from 'components/TitleAndMetaTags'; +import FeedbackForm from 'components/FeedbackForm'; import findSectionForPath from 'utils/findSectionForPath'; import toCommaSeparatedList from 'utils/toCommaSeparatedList'; -import {sharedStyles} from 'theme'; import createCanonicalUrl from 'utils/createCanonicalUrl'; +import {sharedStyles, colors, media, fonts} from 'theme'; import type {Node} from 'types'; @@ -71,9 +73,17 @@ const MarkdownPage = ({ flex: '1 0 auto', position: 'relative', zIndex: 0, + '& h1, & h2, & h3, & h4, & h5, & h6': { + scrollMarginTop: fonts.header.fontSize, + + [media.lessThan('medium')]: { + scrollMarginTop: fonts.header[media.lessThan('medium')].fontSize, + }, + }, }}> <TitleAndMetaTags ogDescription={ogDescription} + ogType="article" canonicalUrl={createCanonicalUrl(markdownRemark.fields.slug)} title={`${titlePrefix}${titlePostfix}`} /> @@ -87,7 +97,7 @@ const MarkdownPage = ({ <div css={{marginTop: 15}}> {date}{' '} {hasAuthors && ( - <span> + <span css={{lineHeight: 1.75}}> by{' '} {toCommaSeparatedList(authors, author => ( <a @@ -110,11 +120,19 @@ const MarkdownPage = ({ {markdownRemark.fields.path && ( <div css={{marginTop: 80}}> + <span + css={{ + whiteSpace: 'nowrap', + paddingBottom: '1em', + marginRight: '36px', + display: 'inline-block', + color: colors.subtle, + }}> + <FeedbackForm /> + </span> <a css={sharedStyles.articleLayout.editLink} - href={`https://github.com/reactjs/reactjs.org/tree/master/${ - markdownRemark.fields.path - }`}> + href={`https://github.com/reactjs/reactjs.org/tree/main/${markdownRemark.fields.path}`}> Edit this page </a> </div> diff --git a/src/components/MarkdownPage/index.js b/src/components/MarkdownPage/index.js index 83f7c3fb9..f4a812837 100644 --- a/src/components/MarkdownPage/index.js +++ b/src/components/MarkdownPage/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/SocialBanner/SocialBanner.js b/src/components/SocialBanner/SocialBanner.js new file mode 100644 index 000000000..23613013c --- /dev/null +++ b/src/components/SocialBanner/SocialBanner.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @emails react-core + * @flow + */ + +// $FlowFixMe Update Flow +import React from 'react'; +import {colors, media} from 'theme'; + +const linkProps = { + href: 'https://beta.reactjs.org', + target: '_blank', + rel: 'noopener', +}; + +const bannerText = 'Try out a preview of the new React Docs!'; +const bannerLink = '👉 beta.reactjs.org'; + +export default function SocialBanner() { + return ( + <div + css={{ + display: 'var(--social-banner-display)', + height: 'var(--social-banner-height-normal)', + fontSize: 18, + fontWeight: 'bold', + [media.lessThan('large')]: { + fontSize: 16, + }, + [media.lessThan('small')]: { + height: 'var(--social-banner-height-small)', + fontSize: 14, + }, + }}> + <div + css={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%', + }}> + <span + css={{ + display: 'flex', + [media.lessThan('small')]: { + flexDirection: 'column', + lineHeight: 1.5, + textAlign: 'center', + }, + }}> + <span + css={{ + marginRight: '0.5rem', + }}> + {bannerText} + </span> + + <a + css={{ + color: colors.brand, + }} + {...linkProps} + target="_blank" + rel="noopener"> + <span css={{color: colors.brand}}>{bannerLink}</span> + </a> + </span> + </div> + </div> + ); +} diff --git a/src/components/SocialBanner/index.js b/src/components/SocialBanner/index.js new file mode 100644 index 000000000..f1cb9778c --- /dev/null +++ b/src/components/SocialBanner/index.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @emails react-core + */ + +import SocialBanner from './SocialBanner'; + +export default SocialBanner; diff --git a/src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js b/src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js index fc5d074d8..cdcbbfd14 100644 --- a/src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js +++ b/src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -43,7 +43,8 @@ class StickyResponsiveSidebar extends Component<Props, State> { render() { const {open} = this.state; const smallScreenSidebarStyles = { - top: 0, + top: + 'calc(var(--survey-banner-height-small) + var(--social-banner-height-small))', left: 0, bottom: 0, right: 0, @@ -124,7 +125,7 @@ class StickyResponsiveSidebar extends Component<Props, State> { }, [media.between('small', 'medium')]: { - marginTop: 0, + marginTop: 20, }, [media.between('medium', 'large')]: { diff --git a/src/components/StickyResponsiveSidebar/index.js b/src/components/StickyResponsiveSidebar/index.js index 7c0650c6a..c19b5ef39 100644 --- a/src/components/StickyResponsiveSidebar/index.js +++ b/src/components/StickyResponsiveSidebar/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/components/SurveyBanner/SurveyBanner.js b/src/components/SurveyBanner/SurveyBanner.js new file mode 100644 index 000000000..ee871afad --- /dev/null +++ b/src/components/SurveyBanner/SurveyBanner.js @@ -0,0 +1,189 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @emails react-core + * @flow + */ + +// $FlowFixMe Update Flow +import React from 'react'; +import {colors, fonts, media} from 'theme'; +import ExternalLinkSvg from 'templates/components/ExternalLinkSvg'; + +const linkProps = { + href: 'https://surveys.savanta.com/survey/selfserve/21e3/210643?list=2', + target: '_blank', + rel: 'noopener', +}; + +export default function SurveyBanner() { + return ( + <div + css={{ + display: 'var(--survey-banner-display)', + height: 'var(--survey-banner-height-normal)', + fontSize: 18, + [media.lessThan('large')]: { + fontSize: 16, + }, + [media.lessThan('small')]: { + height: 'var(--survey-banner-height-small)', + fontSize: 14, + }, + }}> + <div + css={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%', + }}> + <a + css={{ + display: 'flex', + marginRight: '1rem', + [media.lessThan('medium')]: { + display: 'none', + }, + }} + {...linkProps}> + <svg + xmlns="http://www.w3.org/2000/svg" + css={{ + width: 'auto', + height: 35, + }} + preserveAspectRatio="xMidYMid meet" + viewBox="0 0 134 58"> + <g> + <path + fill={colors.white} + d="m60.25002,1.61335l32.24974,1.38664l25.24993,5.24999l10.99997,7.99998l3.74999,8.24998l-1.5,8.24998l-6.24998,5.24999l-10.49997,5.24999l-8.99998,2.24999l0.25,2.99999l4.99999,3.74999c0.00018,0.11336 8.50016,3.11335 8.50016,3.11335c0,0 -11.49997,0.5 -11.50015,0.38664c0.00018,0.11336 -13.24979,-1.38664 -13.24996,-1.5c0.00018,0.11336 -4.49981,-2.63663 -4.49999,-2.74999c0.00018,0.11336 -2.74981,-1.63664 -2.74999,-1.75c0.00018,0.11336 -7.9998,1.36336 -7.99998,1.25c0.00018,0.11336 -25.24976,1.61336 -25.24993,1.5c0.00018,0.11336 -21.99976,-1.38664 -21.99994,-1.5c0.00018,0.11336 -17.74978,-3.38663 -17.74995,-3.49999c0.00018,0.11336 -9.7498,-6.38662 -9.74997,-6.49998c0.00018,0.11336 -3.24982,-6.38662 -3.24999,-6.49998c0.00018,0.11336 2.00017,-9.88662 1.99999,-9.99997c0.00018,0.11336 7.75016,-9.38662 7.74998,-9.49997c0.00018,0.11336 19.75012,-9.38662 19.74995,-9.49997c0.00018,0.11336 23.50012,-4.13663 23.49994,-4.24999" + /> + + <rect + transform="rotate(-5 37.25019073486327,27.62502670288089)" + height="18" + width="18" + y="18.62503" + x="25.7502" + fill="#ccc" + /> + <rect + transform="rotate(-5 66.00012207031251,28.125024795532198)" + height="18" + width="18" + y="19.37502" + x="56.00012" + fill="#ccc" + /> + <rect + transform="rotate(-5 91.75005340576159,25.875030517578093)" + height="18" + width="18" + y="19" + x="85.00004" + fill="#ccc" + /> + + <g transform="translate(0,58) scale(0.10000000149011612,-0.10000000149011612) "> + <path + fill={colors.white} + d="m570,574c-14,-2 -65,-9 -115,-15c-139,-18 -275,-69 -356,-134c-75,-60 -115,-163 -88,-226c41,-99 236,-151 564,-150c122,1 210,6 246,14c51,13 57,12 67,-4c28,-44 237,-67 326,-35l40,14l-45,6c-86,13 -100,18 -130,44c-29,24 -30,27 -13,34c18,8 18,8 0,5c-53,-6 -4,-72 69,-93c49,-14 49,-14 -51,-9c-117,7 -159,16 -189,45c-18,17 -26,18 -56,9c-18,-5 -114,-13 -211,-16c-165,-5 -197,-3 -363,23c-207,34 -284,116 -224,241c57,119 236,203 479,225c197,18 545,-20 671,-74c110,-47 157,-153 104,-234c-14,-22 -97,-73 -150,-92c-16,-6 -23,-11 -15,-11c25,-2 133,54 162,84c59,59 56,147 -9,211c-33,34 -97,68 -146,79c-124,27 -166,35 -257,44c-124,12 -275,19 -310,15z" + /> + <path + fill={colors.text} + d="m377.00009,403.25c-1,-10 -16,-47 -34,-82l-33,-63l-21,36c-24,40 -29,42 -56,21c-21,-16 -18,-22 43,-90l33,-38l19,24c10,13 35,49 56,79c20,30 48,67 62,82c13,15 23,30 20,32c-2,2 -23,7 -46,11c-38,6 -43,4 -43,-12z" + /> + <path + fill={colors.text} + d="m674.7493,403c-1,-10 -16,-47 -34,-82l-33,-63l-21,36c-24,40 -29,42 -56,21c-21,-16 -18,-22 43,-90l33,-38l19,24c10,13 35,49 56,79c20,30 48,67 62,82c13,15 23,30 20,32c-2,2 -23,7 -46,11c-38,6 -43,4 -43,-12z" + /> + <path + fill={colors.text} + d="m965.49854,402.99999c-1,-10 -16,-47 -34,-82l-33,-63l-21,36c-24,40 -29,42 -56,21c-21,-16 -18,-22 43,-90l33,-38l19,24c10,13 35,49 56,79c20,30 48,67 62,82c13,15 23,30 20,32c-2,2 -23,7 -46,11c-38,6 -43,4 -43,-12z" + /> + </g> + </g> + </svg> + </a> + + <span + css={{ + display: 'flex', + [media.lessThan('small')]: { + flexDirection: 'column', + lineHeight: 1.5, + }, + }}> + <span + css={{ + marginRight: '0.5rem', + }}> + We want to hear from you! + </span> + + <a + css={{ + color: '#ddd', + transition: 'color 200ms ease-out', + ':hover': { + color: colors.white, + }, + }} + {...linkProps} + target="_blank" + rel="noopener"> + <span css={{color: colors.brand}}> + Take our 2021 Community Survey! + </span> + <ExternalLinkSvg + cssProps={{ + verticalAlign: -2, + display: 'inline-block', + marginLeft: '0.5rem', + color: 'inherit', + }} + /> + </a> + </span> + + <div css={{display: 'flex', justifyContent: 'flex-end', flexGrow: 1}}> + <button + css={{ + background: 'transparent', + padding: '0.25rem 0.5rem', + borderRadius: '0.25rem', + border: 0, + backgroundColor: 'hsl(222, 14%, 30%)', + color: '#ddd', + cursor: 'pointer', + transition: 'color 200ms ease-out', + ':hover': { + color: colors.white, + }, + marginLeft: '2rem', + fontSize: fonts.small.fontSize, + }} + onClick={() => { + // See html.js + window.__dismissSurveyBanner(); + }}> + <svg + xmlns="http://www.w3.org/2000/svg" + css={{ + width: 10, + height: 10, + }} + viewBox="0 0 5.8 5.8" + alt="close"> + <path + d="M5.8 5.16L3.54 2.9 5.8.65 5.16 0 2.9 2.26.65 0 0 .65 2.26 2.9 0 5.16l.65.64L2.9 3.54 5.16 5.8l.64-.64z" + fill="currentColor"></path> + </svg> + </button> + </div> + </div> + </div> + ); +} diff --git a/src/components/SurveyBanner/index.js b/src/components/SurveyBanner/index.js new file mode 100644 index 000000000..bdb84cdfb --- /dev/null +++ b/src/components/SurveyBanner/index.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @emails react-core + */ + +import SurveyBanner from './SurveyBanner'; + +export default SurveyBanner; diff --git a/src/components/TitleAndMetaTags/TitleAndMetaTags.js b/src/components/TitleAndMetaTags/TitleAndMetaTags.js index e54e8c64c..ab6361a9d 100644 --- a/src/components/TitleAndMetaTags/TitleAndMetaTags.js +++ b/src/components/TitleAndMetaTags/TitleAndMetaTags.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -17,6 +17,7 @@ type Props = { title: string, ogDescription: string, canonicalUrl: string, + ogType: string, }; // only provide alternate links to pages in languages where 95-100% of core content has been translated @@ -45,11 +46,16 @@ const defaultPage = canonicalUrl => { return canonicalUrl.replace(urlRoot, 'https://reactjs.org'); }; -const TitleAndMetaTags = ({title, ogDescription, canonicalUrl}: Props) => { +const TitleAndMetaTags = ({ + title, + ogDescription, + canonicalUrl, + ogType = 'website', +}: Props) => { return ( <Helmet title={title}> <meta property="og:title" content={title} /> - <meta property="og:type" content="website" /> + <meta property="og:type" content={ogType} /> {canonicalUrl && <meta property="og:url" content={canonicalUrl} />} <meta property="og:image" content="https://reactjs.org/logo-og.png" /> <meta diff --git a/src/components/TitleAndMetaTags/index.js b/src/components/TitleAndMetaTags/index.js index f85ab40b7..b61c6cfa8 100644 --- a/src/components/TitleAndMetaTags/index.js +++ b/src/components/TitleAndMetaTags/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/css/algolia.css b/src/css/algolia.css index 60abf6118..e4842e6bf 100644 --- a/src/css/algolia.css +++ b/src/css/algolia.css @@ -1,4 +1,8 @@ -/* Styles forked from Algolia */ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Customized Algolia search result styles. + */ .searchbox { display: inline-block; diff --git a/src/css/reset.css b/src/css/reset.css index 2046181f5..955da30ac 100644 --- a/src/css/reset.css +++ b/src/css/reset.css @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + html { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, diff --git a/src/html.js b/src/html.js index b41c3d2ab..482fa4372 100644 --- a/src/html.js +++ b/src/html.js @@ -1,10 +1,24 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @flow + */ + import React from 'react'; const JS_NPM_URLS = [ 'https://unpkg.com/docsearch.js@2.4.1/dist/cdn/docsearch.min.js', ]; -export default class HTML extends React.Component { +type Props = {| + htmlAttributes: any, + headComponents: React$Node, + bodyAttributes: any, + body: string, + postBodyComponents: React$Node, +|}; + +export default class HTML extends React.Component<Props> { render() { return ( <html lang="en" {...this.props.htmlAttributes}> @@ -27,6 +41,112 @@ export default class HTML extends React.Component { {this.props.headComponents} </head> <body {...this.props.bodyAttributes}> + <script + dangerouslySetInnerHTML={{ + __html: ` + (function() { + /* + BE CAREFUL! + This code is not compiled by our transforms + so it needs to stay compatible with older browsers. + */ + + var activeSurveyBanner = null; + var socialBanner = null; + var snoozeStartDate = null; + var today = new Date(); + + function addTimes(date, days) { + var time = new Date(date); + time.setDate(time.getDate() + days); + return time; + } + + activeSurveyBanner = { + storageId: 'reactjs_banner_2021survey', + normalHeight: 50, + smallHeight: 75, + campaignStartDate: '2021-08-16T00:00:00Z', // the Z is for UTC + campaignEndDate: '2021-08-31T00:00:00Z', // the Z is for UTC + snoozeForDays: 7, + }; + + if (activeSurveyBanner) { + try { + if (localStorage[activeSurveyBanner.storageId]) { + snoozeStartDate = new Date( + parseInt(localStorage.getItem(activeSurveyBanner.storageId), 10), + ); + } + } catch (err) { + // Ignore. + } + + try { + // If it's too early or long past the campaign, don't show the banner: + if ( + today < new Date(activeSurveyBanner.campaignStartDate) || + today > new Date(activeSurveyBanner.campaignEndDate) + ) { + activeSurveyBanner = null; + // If we're in the campaign window, but the snooze has been set and it hasn't expired: + } else if ( + snoozeStartDate && + addTimes(snoozeStartDate, activeSurveyBanner.snoozeForDays) >= today + ) { + activeSurveyBanner = null; + } + } catch (err) { + // Ignore. + } + } + + activeSocialBanner = { + normalHeight: 50, + smallHeight: 75 + }; + + function updateStyles() { + document.documentElement.style.setProperty('--header-height-large', '60px'); + document.documentElement.style.setProperty('--header-height-normal', '50px'); + document.documentElement.style.setProperty('--header-height-small', '40px'); + if (activeSurveyBanner) { + document.documentElement.style.setProperty('--survey-banner-display', 'block'); + document.documentElement.style.setProperty('--survey-banner-height-normal', activeSurveyBanner.normalHeight + 'px'); + document.documentElement.style.setProperty('--survey-banner-height-small', activeSurveyBanner.smallHeight + 'px'); + } else { + document.documentElement.style.setProperty('--survey-banner-display', 'none'); + document.documentElement.style.setProperty('--survey-banner-height-normal', '0px'); + document.documentElement.style.setProperty('--survey-banner-height-small', '0px'); + } + if (activeSocialBanner) { + document.documentElement.style.setProperty('--social-banner-display', 'block'); + document.documentElement.style.setProperty('--social-banner-height-normal', activeSocialBanner.normalHeight + 'px'); + document.documentElement.style.setProperty('--social-banner-height-small', activeSocialBanner.smallHeight + 'px'); + } else { + document.documentElement.style.setProperty('--social-banner-display', 'none'); + document.documentElement.style.setProperty('--social-banner-height-normal', '0px'); + document.documentElement.style.setProperty('--social-banner-height-small', '0px'); + } + } + + updateStyles(); + window.__dismissSurveyBanner = function() { + if (activeSurveyBanner) { + try { + localStorage.setItem(activeSurveyBanner.storageId, Date.now().toString()); + } catch (err) { + // Ignore. + } + // Don't show for next navigations within the session. + activeSurveyBanner = null; + updateStyles(); + } + }; + })(); + `, + }} + /> <div id="___gatsby" dangerouslySetInnerHTML={{__html: this.props.body}} diff --git a/src/images/i_close.svg b/src/images/i_close.svg new file mode 100644 index 000000000..72965a47e --- /dev/null +++ b/src/images/i_close.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5.8 5.8"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon points="5.8 5.16 3.54 2.9 5.8 0.65 5.16 0 2.9 2.26 0.65 0 0 0.65 2.26 2.9 0 5.16 0.65 5.8 2.9 3.54 5.16 5.8 5.8 5.16"/></g></g></svg> \ No newline at end of file diff --git a/src/pages/404.js b/src/pages/404.js index b5977ce62..08684f156 100644 --- a/src/pages/404.js +++ b/src/pages/404.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/pages/acknowledgements.html.js b/src/pages/acknowledgements.html.js index 7a67b4c24..427561dac 100644 --- a/src/pages/acknowledgements.html.js +++ b/src/pages/acknowledgements.html.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ @@ -72,7 +72,7 @@ const Acknowlegements = ({data, location}) => ( <li> <a href="https://github.com/voronianski">Dmitri Voronianski</a>{' '} for letting us use the{' '} - <a href="https://labs.voronianski.com/oceanic-next-color-scheme/"> + <a href="https://labs.voronianski.dev/oceanic-next-color-scheme/"> Oceanic Next </a>{' '} color scheme on this website. diff --git a/src/pages/blog/all.html.js b/src/pages/blog/all.html.js index 35734b5b1..f74b093b4 100644 --- a/src/pages/blog/all.html.js +++ b/src/pages/blog/all.html.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/pages/docs/error-decoder.html.js b/src/pages/docs/error-decoder.html.js index e51abe51d..bc4c2236f 100644 --- a/src/pages/docs/error-decoder.html.js +++ b/src/pages/docs/error-decoder.html.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/pages/index.js b/src/pages/index.js index 0657d46e7..96354aab8 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ @@ -53,7 +53,10 @@ class Home extends Component { title="React – Μια JavaScript βιβλιοθήκη για τη δημιουργία user interfaces" canonicalUrl={createCanonicalUrl('/')} /> - <div css={{width: '100%'}}> + <div + css={{ + width: '100%', + }}> <header css={{ backgroundColor: colors.dark, diff --git a/src/pages/jsx-compiler.html.js b/src/pages/jsx-compiler.html.js index acdcfd9ef..1551f7c2a 100644 --- a/src/pages/jsx-compiler.html.js +++ b/src/pages/jsx-compiler.html.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/pages/languages.js b/src/pages/languages.js index f0309a3bb..5157baa63 100644 --- a/src/pages/languages.js +++ b/src/pages/languages.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/pages/versions.js b/src/pages/versions.js index c4e3532b3..9df25fb89 100644 --- a/src/pages/versions.js +++ b/src/pages/versions.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -40,8 +40,15 @@ const Versions = ({location}: Props) => ( on GitHub </a> .<br /> - Documentation for recent releases can also be found below. + Changelogs for recent releases can also be found below. </p> + <blockquote> + <p>Note</p> + <p> + The current docs are for React 18. For React 17, see{' '} + <a href="https://17.reactjs.org">https://17.reactjs.org.</a> + </p> + </blockquote> <p> See our FAQ for information about{' '} <a href="/docs/faq-versioning.html"> diff --git a/src/prism-styles.js b/src/prism-styles.js index 9772895d3..0fee91678 100644 --- a/src/prism-styles.js +++ b/src/prism-styles.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/site-constants.js b/src/site-constants.js index 620962069..2ee603580 100644 --- a/src/site-constants.js +++ b/src/site-constants.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @providesModule site-constants * @flow @@ -8,7 +8,7 @@ // NOTE: We can't just use `location.toString()` because when we are rendering // the SSR part in node.js we won't have a proper location. const urlRoot = 'https://reactjs.org'; -const version = '16.13.0'; +const version = '18.2.0'; const babelURL = 'https://unpkg.com/babel-standalone@6.26.0/babel.min.js'; export {babelURL, urlRoot, version}; diff --git a/src/templates/blog.js b/src/templates/blog.js index 57967afcc..edac15cdb 100644 --- a/src/templates/blog.js +++ b/src/templates/blog.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/codepen-example.js b/src/templates/codepen-example.js index 9b752aee6..92ba1d97b 100644 --- a/src/templates/codepen-example.js +++ b/src/templates/codepen-example.js @@ -1,8 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * @flow + */ + import React, {Component} from 'react'; import Container from 'components/Container'; import Layout from 'components/Layout'; import {colors} from 'theme'; +type Props = { + location: Location, + pageContext: {| + action: string, + payload: string, + |}, +}; + // Copied over styles from ButtonLink for the submit btn const primaryStyle = { backgroundColor: colors.brand, @@ -22,9 +36,11 @@ const primaryStyle = { fontSize: 16, }; -class CodepenExample extends Component { +class CodepenExample extends Component<Props> { + _form: HTMLFormElement | null = null; + componentDidMount() { - this.codepenForm.submit(); + ((this._form: any): HTMLFormElement).submit(); } render() { @@ -38,7 +54,7 @@ class CodepenExample extends Component { <form style={{paddingBottom: '50px'}} ref={form => { - this.codepenForm = form; + this._form = form; }} action={action} method="POST"> diff --git a/src/templates/community.js b/src/templates/community.js index 05f7ca910..bc39db331 100644 --- a/src/templates/community.js +++ b/src/templates/community.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/components/ChevronSvg/index.js b/src/templates/components/ChevronSvg/index.js index f6c8fe536..5d72445ba 100644 --- a/src/templates/components/ChevronSvg/index.js +++ b/src/templates/components/ChevronSvg/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/templates/components/ExternalLinkSvg/index.js b/src/templates/components/ExternalLinkSvg/index.js index 08a8f592d..23dec0b78 100644 --- a/src/templates/components/ExternalLinkSvg/index.js +++ b/src/templates/components/ExternalLinkSvg/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/components/MetaTitle/index.js b/src/templates/components/MetaTitle/index.js index 4a8adddd9..cb7f0807f 100644 --- a/src/templates/components/MetaTitle/index.js +++ b/src/templates/components/MetaTitle/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/components/NavigationFooter/NavigationFooter.js b/src/templates/components/NavigationFooter/NavigationFooter.js index 9ee0e9519..6672ec3cb 100644 --- a/src/templates/components/NavigationFooter/NavigationFooter.js +++ b/src/templates/components/NavigationFooter/NavigationFooter.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/components/NavigationFooter/index.js b/src/templates/components/NavigationFooter/index.js index bf8487899..61b9a4319 100644 --- a/src/templates/components/NavigationFooter/index.js +++ b/src/templates/components/NavigationFooter/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/components/Sidebar/ScrollSyncSection.js b/src/templates/components/Sidebar/ScrollSyncSection.js index 62e2b88d2..9fef1b5f4 100644 --- a/src/templates/components/Sidebar/ScrollSyncSection.js +++ b/src/templates/components/Sidebar/ScrollSyncSection.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the CC-BY-4.0 license found * in the LICENSE file in the root directory of this source tree. diff --git a/src/templates/components/Sidebar/Section.js b/src/templates/components/Sidebar/Section.js index d48ed8e40..5c2983175 100644 --- a/src/templates/components/Sidebar/Section.js +++ b/src/templates/components/Sidebar/Section.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ @@ -40,7 +40,8 @@ class Section extends React.Component { cssProps={{ [media.greaterThan('small')]: { color: isActive ? colors.text : colors.subtle, - + paddingRight: 7, + paddingLeft: 7, ':hover': { color: colors.text, }, diff --git a/src/templates/components/Sidebar/Sidebar.js b/src/templates/components/Sidebar/Sidebar.js index b4199931a..13ab56061 100644 --- a/src/templates/components/Sidebar/Sidebar.js +++ b/src/templates/components/Sidebar/Sidebar.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/components/Sidebar/index.js b/src/templates/components/Sidebar/index.js index e1d4ef6fe..18570020a 100644 --- a/src/templates/components/Sidebar/index.js +++ b/src/templates/components/Sidebar/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/docs.js b/src/templates/docs.js index b874201d7..6095d58bc 100644 --- a/src/templates/docs.js +++ b/src/templates/docs.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/templates/tutorial.js b/src/templates/tutorial.js index 79a4b2c8c..91220548c 100644 --- a/src/templates/tutorial.js +++ b/src/templates/tutorial.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core */ diff --git a/src/theme.js b/src/theme.js index a89e7d78d..65071aae6 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @providesModule theme * @flow @@ -18,7 +18,7 @@ const colors = { brand: '#61dafb', // electric blue brandLight: '#bbeffd', text: '#1a1a1a', // very dark grey / black substitute - subtle: '#6d6d6d', // light grey for text + subtle: '#6B6B6B', // light grey for text subtleOnDark: '#999', divider: '#ececec', // very light grey note: '#ffe564', // yellow @@ -55,9 +55,7 @@ const media = { if (SIZES[largeKey].max === Infinity) { return `@media (min-width: ${SIZES[smallKey].min}px)`; } else { - return `@media (min-width: ${SIZES[smallKey].min}px) and (max-width: ${ - SIZES[largeKey].max - }px)`; + return `@media (min-width: ${SIZES[smallKey].min}px) and (max-width: ${SIZES[largeKey].max}px)`; } } }, @@ -170,20 +168,46 @@ const sharedStyles = { zIndex: 2, }, }, - + feedbackButton: { + border: 0, + background: 'none', + cursor: 'pointer', + ':focus': { + color: colors.text, + borderColor: colors.text, + '& svg': { + fill: colors.text, + }, + }, + ':hover': { + color: colors.text, + borderColor: colors.text, + '& svg': { + fill: colors.text, + }, + }, + '& svg': { + height: '1.5em', + width: '1.5em', + fill: colors.subtle, + transition: 'fill 0.2s ease', + }, + }, editLink: { - color: colors.subtle, + color: colors.lighter, borderColor: colors.divider, - transition: 'all 0.2s ease', - transitionPropery: 'color, border-color', + transition: 'color 0.2s ease, border-color 0.2s ease', whiteSpace: 'nowrap', borderBottomWidth: 1, borderBottomStyle: 'solid', - ':hover': { color: colors.text, borderColor: colors.text, }, + ':focus': { + color: colors.text, + borderColor: colors.text, + }, }, }, diff --git a/src/types.js b/src/types.js index 782a5b6f6..2573f79eb 100644 --- a/src/types.js +++ b/src/types.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/utils/createCanonicalUrl.js b/src/utils/createCanonicalUrl.js index 0fafad59a..2ee9c80a3 100644 --- a/src/utils/createCanonicalUrl.js +++ b/src/utils/createCanonicalUrl.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/utils/createLink.js b/src/utils/createLink.js index eee6db08e..efefe0b2a 100644 --- a/src/utils/createLink.js +++ b/src/utils/createLink.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/utils/findSectionForPath.js b/src/utils/findSectionForPath.js index eedffc0ad..15953de23 100644 --- a/src/utils/findSectionForPath.js +++ b/src/utils/findSectionForPath.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/utils/isItemActive.js b/src/utils/isItemActive.js index 754db4b87..d4e29a2bd 100644 --- a/src/utils/isItemActive.js +++ b/src/utils/isItemActive.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/utils/loadScript.js b/src/utils/loadScript.js index 83fa80a64..ee2a1d212 100644 --- a/src/utils/loadScript.js +++ b/src/utils/loadScript.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * 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. diff --git a/src/utils/patchDOMForGoogleTranslate.js b/src/utils/patchDOMForGoogleTranslate.js index 35b9153a5..ad8445278 100644 --- a/src/utils/patchDOMForGoogleTranslate.js +++ b/src/utils/patchDOMForGoogleTranslate.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/utils/sectionList.js b/src/utils/sectionList.js index 01983501a..3f8ab386e 100644 --- a/src/utils/sectionList.js +++ b/src/utils/sectionList.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow @@ -12,19 +12,15 @@ import navDocs from '../../content/docs/nav.yml'; // $FlowExpectedError import navTutorial from '../../content/tutorial/nav.yml'; -const sectionListDocs = navDocs.map( - (item: Object): Object => ({ - ...item, - directory: 'docs', - }), -); +const sectionListDocs = navDocs.map((item: Object): Object => ({ + ...item, + directory: 'docs', +})); -const sectionListCommunity = navCommunity.map( - (item: Object): Object => ({ - ...item, - directory: 'community', - }), -); +const sectionListCommunity = navCommunity.map((item: Object): Object => ({ + ...item, + directory: 'community', +})); export { sectionListCommunity, diff --git a/src/utils/slugify.js b/src/utils/slugify.js index cf2ef3849..1bcd3bae6 100644 --- a/src/utils/slugify.js +++ b/src/utils/slugify.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/src/utils/toCommaSeparatedList.js b/src/utils/toCommaSeparatedList.js index e70da06f9..5dd03662a 100644 --- a/src/utils/toCommaSeparatedList.js +++ b/src/utils/toCommaSeparatedList.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * @emails react-core * @flow diff --git a/static/_redirects b/static/_redirects deleted file mode 100644 index 4e8eeea9d..000000000 --- a/static/_redirects +++ /dev/null @@ -1,7 +0,0 @@ -/html-jsx.html https://magic.reactjs.net/htmltojsx.htm 301 -/tips/controlled-input-null-value.html /docs/forms.html#controlled-input-null-value -/concurrent /docs/concurrent-mode-intro.html -/hooks /docs/hooks-intro.html -/tutorial /tutorial/tutorial.html -/your-story https://www.surveymonkey.co.uk/r/MVQV2R9 301 -/stories https://medium.com/react-community-stories 301 \ No newline at end of file diff --git a/static/html/single-file-example.html b/static/html/single-file-example.html index bdae8f02f..a89a8d4d8 100644 --- a/static/html/single-file-example.html +++ b/static/html/single-file-example.html @@ -3,20 +3,23 @@ <head> <meta charset="UTF-8" /> <title>Hello World</title> - <script src="https://unpkg.com/react@16/umd/react.development.js"></script> - <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> - + <script src="https://unpkg.com/react@18/umd/react.development.js"></script> + <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> + <!-- Don't use this in production: --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> + + function MyApp() { + return <h1>Hello, world!</h1>; + } - ReactDOM.render( - <h1>Hello, world!</h1>, - document.getElementById('root') - ); + const container = document.getElementById('root'); + const root = ReactDOM.createRoot(container); + root.render(<MyApp />); </script> <!-- diff --git a/static/js/jsfiddle-integration-babel.js b/static/js/jsfiddle-integration-babel.js index 5dc66b1d3..006c79c8a 100644 --- a/static/js/jsfiddle-integration-babel.js +++ b/static/js/jsfiddle-integration-babel.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + // Do not delete or move this file. // Many fiddles reference it so we have to keep it here. (function() { diff --git a/static/js/jsfiddle-integration.js b/static/js/jsfiddle-integration.js index d4a066698..fcf09e43f 100644 --- a/static/js/jsfiddle-integration.js +++ b/static/js/jsfiddle-integration.js @@ -1,3 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + */ + // Do not delete or move this file. // Many fiddles reference it so we have to keep it here. (function() { diff --git a/vercel.json b/vercel.json new file mode 100644 index 000000000..064ec2639 --- /dev/null +++ b/vercel.json @@ -0,0 +1,278 @@ +{ + "github": { + "silent": true + }, + "trailingSlash": false, + "redirects": [ + { + "source": "/tips/controlled-input-null-value.html", + "destination": "/docs/forms.html#controlled-input-null-value", + "permanent": false + }, + { + "source": "/link/switch-to-createroot", + "destination": "https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-client-rendering-apis", + "permanent": false + }, + { + "source": "/server-components", + "destination": "/blog/2020/12/21/data-fetching-with-react-server-components.html", + "permanent": false + }, + { + "source": "/concurrent", + "destination": "/docs/concurrent-mode-intro.html", + "permanent": false + }, + { + "source": "/hooks", + "destination": "/docs/hooks-intro.html", + "permanent": false + }, + { + "source": "/tutorial", + "destination": "/tutorial/tutorial.html", + "permanent": false + }, + { + "source": "/your-story", + "destination": "https://www.surveymonkey.co.uk/r/MVQV2R9", + "permanent": false + }, + { + "source": "/stories", + "destination": "https://medium.com/react-community-stories", + "permanent": false + }, + { + "source": "/html-jsx.html", + "destination": "", + "permanent": false + }, + { + "source": "/link/attribute-behavior", + "destination": "/blog/2017/09/08/dom-attributes-in-react-16.html#changes-in-detail", + "permanent": false + }, + { + "source": "/link/controlled-components", + "destination": "/docs/forms.html#controlled-components", + "permanent": false + }, + { + "source": "/link/crossorigin-error", + "destination": "/docs/cross-origin-errors.html", + "permanent": false + }, + { + "source": "/link/dangerously-set-inner-html", + "destination": "/docs/dom-elements.html#dangerouslysetinnerhtml", + "permanent": false + }, + { + "source": "/link/derived-state", + "destination": "/blog/2018/06/07/you-probably-dont-need-derived-state.html", + "permanent": false + }, + { + "source": "/link/error-boundaries", + "destination": "/docs/error-boundaries.html", + "permanent": false + }, + { + "source": "/link/event-pooling", + "destination": "/docs/legacy-event-pooling.html", + "permanent": false + }, + { + "source": "/link/hooks-data-fetching", + "destination": "/docs/hooks-faq.html#how-can-i-do-data-fetching-with-hooks", + "permanent": false + }, + { + "source": "/link/invalid-aria-props", + "destination": "/warnings/invalid-aria-prop.html", + "permanent": false + }, + { + "source": "/link/invalid-hook-call", + "destination": "/warnings/invalid-hook-call-warning.html", + "permanent": false + }, + { + "source": "/link/legacy-context", + "destination": "/docs/legacy-context.html", + "permanent": false + }, + { + "source": "/link/legacy-factories", + "destination": "/warnings/legacy-factories.html", + "permanent": false + }, + { + "source": "/link/mock-scheduler", + "destination": "", + "permanent": false + }, + { + "source": "/link/perf-use-production-build", + "destination": "/docs/optimizing-performance.html#use-the-production-build", + "permanent": false + }, + { + "source": "/link/react-devtools", + "destination": "/blog/2015/09/02/new-react-developer-tools.html#installation", + "permanent": false + }, + { + "source": "/link/react-polyfills", + "destination": "/docs/javascript-environment-requirements.html", + "permanent": false + }, + { + "source": "/link/refs-must-have-owner", + "destination": "/warnings/refs-must-have-owner.html", + "permanent": false + }, + { + "source": "/link/rules-of-hooks", + "destination": "/docs/hooks-rules.html", + "permanent": false + }, + { + "source": "/link/special-props", + "destination": "/warnings/special-props.html", + "permanent": false + }, + { + "source": "/link/strict-mode-find-node", + "destination": "/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage", + "permanent": false + }, + { + "source": "/link/strict-mode-string-ref", + "destination": "/docs/refs-and-the-dom.html#legacy-api-string-refs", + "permanent": false + }, + { + "source": "/link/unsafe-component-lifecycles", + "destination": "/blog/2018/03/27/update-on-async-rendering.html", + "permanent": false + }, + { + "source": "/link/warning-keys", + "destination": "/docs/lists-and-keys.html#keys", + "permanent": false + }, + { + "source": "/link/wrap-tests-with-act", + "destination": "/docs/test-utils.html#act", + "permanent": false + }, + { + "source": "/link/interaction-tracing", + "destination": "https://gist.github.com/bvaughn/8de925562903afd2e7a12554adcdda16", + "permanent": false + }, + { + "source": "/link/profiling", + "destination": "https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977", + "permanent": false + }, + { + "source": "/link/test-utils-mock-component", + "destination": "https://gist.github.com/bvaughn/fbf41b3f895bf2d297935faa5525eee9", + "permanent": false + }, + { + "source": "/link/uselayouteffect-ssr", + "destination": "https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85", + "permanent": false + }, + { + "source": "/link/react-devtools-faq", + "destination": "https://github.com/facebook/react/tree/main/packages/react-devtools#faq", + "permanent": false + }, + { + "source": "/link/setstate-in-render", + "destination": "https://github.com/facebook/react/issues/18178#issuecomment-595846312", + "permanent": false + }, + { + "source": "/version/15.6", + "destination": "https://react-legacy.netlify.app", + "permanent": false + }, + { + "source": "/version/16.8", + "destination": "https://5d4b5feba32acd0008d0df98--reactjs.netlify.com/", + "permanent": false + }, + { + "source": "/version/16.7", + "destination": "https://5c54aa429e16c80007af3cd2--reactjs.netlify.com/", + "permanent": false + }, + { + "source": "/version/16.6", + "destination": "https://5c11762d4be4d10008916ab1--reactjs.netlify.com/", + "permanent": false + }, + { + "source": "/version/16.5", + "destination": "https://5bcf5863c6aed64970d6de5b--reactjs.netlify.com/", + "permanent": false + }, + { + "source": "/version/16.4", + "destination": "https://5b90c17ac9659241e7f4c938--reactjs.netlify.com", + "permanent": false + }, + { + "source": "/version/16.3", + "destination": "https://5b05c94e0733d530fd1fafe0--reactjs.netlify.com", + "permanent": false + }, + { + "source": "/version/16.2", + "destination": "https://5abc31d8be40f1556f06c4be--reactjs.netlify.com", + "permanent": false + }, + { + "source": "/version/16.1", + "destination": "https://5a1dbcf14c4b93299e65b9a9--reactjs.netlify.com", + "permanent": false + }, + { + "source": "/version/16.0", + "destination": "https://5a046bf5a6188f4b8fa4938a--reactjs.netlify.com", + "permanent": false + }, + { + "source": "/docs/concurrent-mode-adoption.html", + "destination": "https://17.reactjs.org/docs/concurrent-mode-adoption.html", + "permanent": true + }, + { + "source": "/docs/concurrent-mode-intro.html", + "destination": "https://17.reactjs.org/docs/concurrent-mode-intro.html", + "permanent": true + }, + { + "source": "/docs/concurrent-mode-patterns.html", + "destination": "https://17.reactjs.org/docs/concurrent-mode-patterns.html", + "permanent": true + }, + { + "source": "/docs/concurrent-mode-reference.html", + "destination": "https://17.reactjs.org/docs/concurrent-mode-reference.html", + "permanent": true + }, + { + "source": "/docs/concurrent-mode-suspense.html", + "destination": "https://17.reactjs.org/docs/concurrent-mode-suspense.html", + "permanent": true + } + ] +} diff --git a/yarn.lock b/yarn.lock index bfea8a90a..c0b5ad996 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,43 +2,83 @@ # yarn lockfile v1 +"@ardatan/aggregate-error@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz#fe6924771ea40fc98dc7a7045c2e872dc8527609" + integrity sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ== + dependencies: + tslib "~2.0.1" + "@babel/code-frame@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" integrity sha512-cuAuTTIQ9RqcFRJ/Y8PvTh+paepNcaGxwQwjIDRWPXmzzyAeCO4KqS9ikMvq0MCbRk6GlYKwfzStrcP3/jSL8g== dependencies: "@babel/highlight" "7.0.0-beta.44" -"@babel/code-frame@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" - integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: - "@babel/highlight" "^7.0.0" + "@babel/highlight" "^7.10.4" -"@babel/core@^7.0.0": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.1.2.tgz#f8d2a9ceb6832887329a7b60f9d035791400ba4e" - integrity sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw== +"@babel/compat-data@^7.10.4", "@babel/compat-data@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c" + integrity sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.1.2" - "@babel/helpers" "^7.1.2" - "@babel/parser" "^7.1.2" - "@babel/template" "^7.1.2" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.1.2" - convert-source-map "^1.1.0" - debug "^3.1.0" - json5 "^0.5.0" - lodash "^4.17.10" + browserslist "^4.12.0" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330" + integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.5" + "@babel/types" "^7.10.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.11.6": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651" + integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.6" + "@babel/helper-module-transforms" "^7.11.0" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.11.5" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.11.5" + "@babel/types" "^7.11.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" "@babel/generator@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" integrity sha512-5xVb7hlhjGcdkKpMXgicAVgx8syK5VJz193k0i/0sLP6DzE6lRrU1K3B/rFefgdo9LPGMAOOOAWW4jycj07ShQ== dependencies: "@babel/types" "7.0.0-beta.44" @@ -47,694 +87,962 @@ source-map "^0.5.0" trim-right "^1.0.1" -"@babel/generator@^7.1.2", "@babel/generator@^7.1.3": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.3.tgz#2103ec9c42d9bdad9190a6ad5ff2d456fd7b8673" - integrity sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ== +"@babel/generator@^7.10.5", "@babel/generator@^7.11.5", "@babel/generator@^7.11.6": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" + integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== dependencies: - "@babel/types" "^7.1.3" + "@babel/types" "^7.11.5" jsesc "^2.5.1" - lodash "^4.17.10" source-map "^0.5.0" - trim-right "^1.0.1" -"@babel/helper-annotate-as-pure@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" - integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== +"@babel/helper-annotate-as-pure@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" + integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.10.4" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" - integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" + integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== dependencies: - "@babel/helper-explode-assignable-expression" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-explode-assignable-expression" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-builder-react-jsx@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.0.0.tgz#fa154cb53eb918cf2a9a7ce928e29eb649c5acdb" - integrity sha512-ebJ2JM6NAKW0fQEqN8hOLxK84RbRz9OkUhGS/Xd5u56ejMfVbayJ4+LykERZCOUM6faa6Fp3SZNX3fcT16MKHw== +"@babel/helper-builder-react-jsx-experimental@^7.10.4", "@babel/helper-builder-react-jsx-experimental@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.11.5.tgz#4ea43dd63857b0a35cd1f1b161dc29b43414e79f" + integrity sha512-Vc4aPJnRZKWfzeCBsqTBnzulVNjABVdahSPhtdMD3Vs80ykx4a87jTHtF/VR+alSrDmNvat7l13yrRHauGcHVw== dependencies: - "@babel/types" "^7.0.0" - esutils "^2.0.0" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/types" "^7.11.5" -"@babel/helper-call-delegate@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a" - integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ== +"@babel/helper-builder-react-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz#8095cddbff858e6fa9c326daee54a2f2732c1d5d" + integrity sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg== dependencies: - "@babel/helper-hoist-variables" "^7.0.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-define-map@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" - integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg== +"@babel/helper-compilation-targets@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" + integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/types" "^7.0.0" - lodash "^4.17.10" + "@babel/compat-data" "^7.10.4" + browserslist "^4.12.0" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" -"@babel/helper-explode-assignable-expression@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" - integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== +"@babel/helper-create-class-features-plugin@^7.10.4", "@babel/helper-create-class-features-plugin@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" + integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.10.5" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + +"@babel/helper-create-regexp-features-plugin@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" + integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g== dependencies: - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-regex" "^7.10.4" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" + integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + +"@babel/helper-explode-assignable-expression@^7.10.4": + version "7.11.4" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz#2d8e3470252cc17aba917ede7803d4a7a276a41b" + integrity sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ== + dependencies: + "@babel/types" "^7.10.4" "@babel/helper-function-name@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" integrity sha512-MHRG2qZMKMFaBavX0LWpfZ2e+hLloT++N7rfM3DYOMUOGCD8cVjqZpwiL8a0bOX3IYcQev1ruciT0gdFFRTxzg== dependencies: "@babel/helper-get-function-arity" "7.0.0-beta.44" "@babel/template" "7.0.0-beta.44" "@babel/types" "7.0.0-beta.44" -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" "@babel/helper-get-function-arity@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" integrity sha512-w0YjWVwrM2HwP6/H3sEgrSQdkCaxppqFeJtAnB23pRiJB5E/O9Yp7JAAeWBl+gGEgmBFinnTyOv2RN7rcSmMiw== dependencies: "@babel/types" "7.0.0-beta.44" -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.10.4" -"@babel/helper-hoist-variables@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88" - integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w== +"@babel/helper-hoist-variables@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" + integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.10.4" -"@babel/helper-member-expression-to-functions@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f" - integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg== +"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" + integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.11.0" -"@babel/helper-module-imports@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" - integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.10.4" -"@babel/helper-module-transforms@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz#470d4f9676d9fad50b324cdcce5fbabbc3da5787" - integrity sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw== +"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" + integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" - lodash "^4.17.10" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/template" "^7.10.4" + "@babel/types" "^7.11.0" + lodash "^4.17.19" -"@babel/helper-optimise-call-expression@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" - integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.10.4" -"@babel/helper-plugin-utils@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" - integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== +"@babel/helper-plugin-utils@7.10.4", "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== -"@babel/helper-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27" - integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg== +"@babel/helper-regex@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" + integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== dependencies: - lodash "^4.17.10" + lodash "^4.17.19" -"@babel/helper-remap-async-to-generator@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" - integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== +"@babel/helper-remap-async-to-generator@^7.10.4": + version "7.11.4" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz#4474ea9f7438f18575e30b0cac784045b402a12d" + integrity sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-wrap-function" "^7.1.0" - "@babel/template" "^7.1.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-wrap-function" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-replace-supers@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz#5fc31de522ec0ef0899dc9b3e7cf6a5dd655f362" - integrity sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ== +"@babel/helper-replace-supers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" + integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== dependencies: - "@babel/helper-member-expression-to-functions" "^7.0.0" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-simple-access@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" - integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== +"@babel/helper-simple-access@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" + integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== + dependencies: + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-skip-transparent-expression-wrappers@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" + integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q== dependencies: - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/types" "^7.11.0" "@babel/helper-split-export-declaration@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" integrity sha512-aQ7QowtkgKKzPGf0j6u77kBMdUFVBKNHw2p/3HX/POt5/oz8ec5cs0GwlgM8Hz7ui5EwJnzyfRmkNF1Nx1N7aA== dependencies: "@babel/types" "7.0.0-beta.44" -"@babel/helper-split-export-declaration@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" - integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== +"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.11.0" -"@babel/helper-wrap-function@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz#8cf54e9190706067f016af8f75cb3df829cc8c66" - integrity sha512-R6HU3dete+rwsdAfrOzTlE9Mcpk4RjU3aX3gi9grtmugQY0u79X7eogUvfXA5sI81Mfq1cn6AgxihfN33STjJA== +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helper-wrap-function@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" + integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/template" "^7.1.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-function-name" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helpers@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.1.2.tgz#ab752e8c35ef7d39987df4e8586c63b8846234b5" - integrity sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA== +"@babel/helpers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" + integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== dependencies: - "@babel/template" "^7.1.2" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.1.2" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" "@babel/highlight@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" integrity sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ== dependencies: chalk "^2.0.0" esutils "^2.0.2" js-tokens "^3.0.0" -"@babel/highlight@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" - integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== dependencies: + "@babel/helper-validator-identifier" "^7.10.4" chalk "^2.0.0" - esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.2", "@babel/parser@^7.1.3": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.3.tgz#2c92469bac2b7fbff810b67fca07bd138b48af77" - integrity sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w== +"@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.11.5", "@babel/parser@^7.7.0": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" + integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== -"@babel/plugin-proposal-async-generator-functions@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz#41c1a702e10081456e23a7b74d891922dd1bb6ce" - integrity sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew== +"@babel/plugin-proposal-async-generator-functions@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" + integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" - "@babel/plugin-syntax-async-generators" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-class-properties@^7.0.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.1.0.tgz#9af01856b1241db60ec8838d84691aa0bd1e8df4" - integrity sha512-/PCJWN+CKt5v1xcGn4vnuu13QDoV+P7NcICP44BoonAJoPSGwVkgrXihFIQGiEjjPlUDBIw1cM7wYFLARS2/hw== +"@babel/plugin-proposal-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" + integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-dynamic-import@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" + integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-export-namespace-from@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz#570d883b91031637b3e2958eea3c438e62c05f54" + integrity sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-member-expression-to-functions" "^7.0.0" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.1.0" - "@babel/plugin-syntax-class-properties" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz#3b4d7b5cf51e1f2e70f52351d28d44fc2970d01e" - integrity sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q== +"@babel/plugin-proposal-json-strings@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" + integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-json-strings" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.0" -"@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz#9a17b547f64d0676b6c9cecd4edf74a82ab85e7e" - integrity sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw== +"@babel/plugin-proposal-logical-assignment-operators@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz#9f80e482c03083c87125dee10026b58527ea20c8" + integrity sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-optional-catch-binding@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz#b610d928fe551ff7117d42c8bb410eec312a6425" - integrity sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" + integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-unicode-property-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz#498b39cd72536cd7c4b26177d030226eba08cd33" - integrity sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ== +"@babel/plugin-proposal-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" + integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" - regexpu-core "^4.2.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-syntax-async-generators@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz#bf0891dcdbf59558359d0c626fdc9490e20bc13c" - integrity sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA== +"@babel/plugin-proposal-object-rest-spread@7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" + integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" -"@babel/plugin-syntax-class-properties@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0.tgz#e051af5d300cbfbcec4a7476e37a803489881634" - integrity sha512-cR12g0Qzn4sgkjrbrzWy2GE7m9vMl/sFkqZ3gIpAQdrvPDnLM8180i+ANDFIXfjHo9aqp0ccJlQ0QNZcFUbf9w== +"@babel/plugin-proposal-object-rest-spread@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz#bd81f95a1f746760ea43b6c2d3d62b11790ad0af" + integrity sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" -"@babel/plugin-syntax-dynamic-import@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0.tgz#6dfb7d8b6c3be14ce952962f658f3b7eb54c33ee" - integrity sha512-Gt9xNyRrCHCiyX/ZxDGOcBnlJl0I3IWicpZRC4CdC0P5a/I07Ya2OAMEBU+J7GmRFVmIetqEYRko6QYRuKOESw== +"@babel/plugin-proposal-optional-catch-binding@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" + integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-syntax-flow@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.0.0.tgz#70638aeaad9ee426bc532e51523cff8ff02f6f17" - integrity sha512-zGcuZWiWWDa5qTZ6iAnpG0fnX/GOu49pGR5PFvkQ9GmKNaSphXQnlNXh/LG20sqWtNrx/eB6krzfEzcwvUyeFA== +"@babel/plugin-proposal-optional-chaining@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" + integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-syntax-json-strings@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz#0d259a68090e15b383ce3710e01d5b23f3770cbd" - integrity sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA== +"@babel/plugin-proposal-private-methods@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" + integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-jsx@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0.tgz#034d5e2b4e14ccaea2e4c137af7e4afb39375ffd" - integrity sha512-PdmL2AoPsCLWxhIr3kG2+F9v4WH06Q3z+NoGVpQgnUNGcagXHq5sB3OXxkSahKq9TLdNMN/AJzFYSOo8UKDMHg== +"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" + integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz#37d8fbcaf216bd658ea1aebbeb8b75e88ebc549b" - integrity sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw== +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz#886f72008b3a8b185977f7cb70713b45e51ee475" - integrity sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw== +"@babel/plugin-syntax-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" + integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-arrow-functions@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz#a6c14875848c68a3b4b3163a486535ef25c7e749" - integrity sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w== +"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-async-to-generator@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz#109e036496c51dd65857e16acab3bafdf3c57811" - integrity sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g== +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-block-scoped-functions@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz#482b3f75103927e37288b3b67b65f848e2aa0d07" - integrity sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ== +"@babel/plugin-syntax-flow@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.10.4.tgz#53351dd7ae01995e567d04ce42af1a6e0ba846a6" + integrity sha512-yxQsX1dJixF4qEEdzVbst3SZQ58Nrooz8NV9Z9GL4byTE25BvJgl5lf0RECUf0fh28rZBb/RYTWn/eeKwCMrZQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-block-scoping@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz#1745075edffd7cdaf69fab2fb6f9694424b7e9bc" - integrity sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg== +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - lodash "^4.17.10" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-classes@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz#ab3f8a564361800cbc8ab1ca6f21108038432249" - integrity sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg== +"@babel/plugin-syntax-jsx@7.10.4", "@babel/plugin-syntax-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c" + integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-define-map" "^7.1.0" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - globals "^11.1.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-computed-properties@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz#2fbb8900cd3e8258f2a2ede909b90e7556185e31" - integrity sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA== +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-destructuring@^7.0.0": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.3.tgz#e69ff50ca01fac6cb72863c544e516c2b193012f" - integrity sha512-Mb9M4DGIOspH1ExHOUnn2UUXFOyVTiX84fXCd+6B5iWrQg/QMeeRmSwpZ9lnjYLSXtZwiw80ytVMr3zue0ucYw== +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-dotall-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz#73a24da69bc3c370251f43a3d048198546115e58" - integrity sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig== +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" - regexpu-core "^4.1.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-duplicate-keys@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz#a0601e580991e7cace080e4cf919cfd58da74e86" - integrity sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ== +"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-exponentiation-operator@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz#9c34c2ee7fd77e02779cfa37e403a2e1003ccc73" - integrity sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ== +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-flow-strip-types@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.0.0.tgz#c40ced34c2783985d90d9f9ac77a13e6fb396a01" - integrity sha512-WhXUNb4It5a19RsgKKbQPrjmy4yWOY1KynpEbNw7bnd1QTcrT/EIl3MJvnGgpgvrKyKbqX7nUNOJfkpLOnoDKA== +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-for-of@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz#f2ba4eadb83bd17dc3c7e9b30f4707365e1c3e39" - integrity sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA== +"@babel/plugin-syntax-top-level-await@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" + integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz#29c5550d5c46208e7f730516d41eeddd4affadbb" - integrity sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg== +"@babel/plugin-syntax-typescript@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.4.tgz#2f55e770d3501e83af217d782cb7517d7bb34d25" + integrity sha512-oSAEz1YkBCAKr5Yiq8/BNtvSAPwkp/IyUnwZogd8p+F0RuYQQrLeRUzIQhueQTTBy/F+a40uS7OFKxnkRvmvFQ== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-literals@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz#2aec1d29cdd24c407359c930cdd89e914ee8ff86" - integrity sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA== +"@babel/plugin-transform-arrow-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" + integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-modules-amd@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz#f9e0a7072c12e296079b5a59f408ff5b97bf86a8" - integrity sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A== +"@babel/plugin-transform-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" + integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" -"@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz#0a9d86451cbbfb29bd15186306897c67f6f9a05c" - integrity sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA== +"@babel/plugin-transform-block-scoped-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" + integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-modules-systemjs@^7.0.0": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.1.3.tgz#2119a3e3db612fd74a19d88652efbfe9613a5db0" - integrity sha512-PvTxgjxQAq4pvVUZF3mD5gEtVDuId8NtWkJsZLEJZMZAW3TvgQl1pmydLLN1bM8huHFVVU43lf0uvjQj9FRkKw== +"@babel/plugin-transform-block-scoping@^7.10.4": + version "7.11.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215" + integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-classes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" + integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-define-map" "^7.10.4" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" + integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== dependencies: - "@babel/helper-hoist-variables" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-modules-umd@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz#a29a7d85d6f28c3561c33964442257cc6a21f2a8" - integrity sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig== +"@babel/plugin-transform-destructuring@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" + integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-new-target@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a" - integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw== +"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" + integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-object-super@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz#b1ae194a054b826d8d4ba7ca91486d4ada0f91bb" - integrity sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw== +"@babel/plugin-transform-duplicate-keys@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" + integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-parameters@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz#44f492f9d618c9124026e62301c296bf606a7aed" - integrity sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw== +"@babel/plugin-transform-exponentiation-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" + integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== dependencies: - "@babel/helper-call-delegate" "^7.1.0" - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-display-name@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.0.0.tgz#93759e6c023782e52c2da3b75eca60d4f10533ee" - integrity sha512-BX8xKuQTO0HzINxT6j/GiCwoJB0AOMs0HmLbEnAvcte8U8rSkNa/eSCAY+l1OA4JnCVq2jw2p6U8QQryy2fTPg== +"@babel/plugin-transform-flow-strip-types@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.10.4.tgz#c497957f09e86e3df7296271e9eb642876bf7788" + integrity sha512-XTadyuqNst88UWBTdLjM+wEY7BFnY2sYtPyAidfC7M/QaZnSuIZpMvLxqGT7phAcnGyWh/XQFLKcGf04CnvxSQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-flow" "^7.10.4" -"@babel/plugin-transform-react-jsx-self@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.0.0.tgz#a84bb70fea302d915ea81d9809e628266bb0bc11" - integrity sha512-pymy+AK12WO4safW1HmBpwagUQRl9cevNX+82AIAtU1pIdugqcH+nuYP03Ja6B+N4gliAaKWAegIBL/ymALPHA== +"@babel/plugin-transform-for-of@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" + integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-jsx-source@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.0.0.tgz#28e00584f9598c0dd279f6280eee213fa0121c3c" - integrity sha512-OSeEpFJEH5dw/TtxTg4nijl4nHBbhqbKL94Xo/Y17WKIf2qJWeIk/QeXACF19lG1vMezkxqruwnTjVizaW7u7w== +"@babel/plugin-transform-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" + integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-jsx@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.0.0.tgz#524379e4eca5363cd10c4446ba163f093da75f3e" - integrity sha512-0TMP21hXsSUjIQJmu/r7RiVxeFrXRcMUigbKu0BLegJK9PkYodHstaszcig7zxXfaBji2LYUdtqIkHs+hgYkJQ== +"@babel/plugin-transform-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" + integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== dependencies: - "@babel/helper-builder-react-jsx" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-regenerator@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1" - integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw== +"@babel/plugin-transform-member-expression-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" + integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== dependencies: - regenerator-transform "^0.13.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-runtime@^7.0.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.1.0.tgz#9f76920d42551bb577e2dc594df229b5f7624b63" - integrity sha512-WFLMgzu5DLQEah0lKTJzYb14vd6UiES7PTnXcvrPZ1VrwFeJ+mTbvr65fFAsXYMt2bIoOoC0jk76zY1S7HZjUg== +"@babel/plugin-transform-modules-amd@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1" + integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - resolve "^1.8.1" - semver "^5.5.1" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-shorthand-properties@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz#85f8af592dcc07647541a0350e8c95c7bf419d15" - integrity sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw== +"@babel/plugin-transform-modules-commonjs@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" + integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-spread@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz#93583ce48dd8c85e53f3a46056c856e4af30b49b" - integrity sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ== +"@babel/plugin-transform-modules-systemjs@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" + integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-hoist-variables" "^7.10.4" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-sticky-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz#30a9d64ac2ab46eec087b8530535becd90e73366" - integrity sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw== +"@babel/plugin-transform-modules-umd@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" + integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-template-literals@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz#084f1952efe5b153ddae69eb8945f882c7a97c65" - integrity sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg== +"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" + integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" -"@babel/plugin-transform-typeof-symbol@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz#4dcf1e52e943e5267b7313bff347fdbe0f81cec9" - integrity sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg== +"@babel/plugin-transform-new-target@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" + integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-unicode-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz#c6780e5b1863a76fe792d90eded9fcd5b51d68fc" - integrity sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw== +"@babel/plugin-transform-object-super@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" + integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" - regexpu-core "^4.1.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" -"@babel/polyfill@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0.tgz#c8ff65c9ec3be6a1ba10113ebd40e8750fb90bff" - integrity sha512-dnrMRkyyr74CRelJwvgnnSUDh2ge2NCTyHVwpOdvRMHtJUyxLtMAfhBN3s64pY41zdw0kgiLPh6S20eb1NcX6Q== +"@babel/plugin-transform-parameters@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" + integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== dependencies: - core-js "^2.5.7" - regenerator-runtime "^0.11.1" + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/preset-env@^7.0.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11" - integrity sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg== +"@babel/plugin-transform-property-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" + integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-async-generator-functions" "^7.1.0" - "@babel/plugin-proposal-json-strings" "^7.0.0" - "@babel/plugin-proposal-object-rest-spread" "^7.0.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.0.0" - "@babel/plugin-syntax-async-generators" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" - "@babel/plugin-transform-arrow-functions" "^7.0.0" - "@babel/plugin-transform-async-to-generator" "^7.1.0" - "@babel/plugin-transform-block-scoped-functions" "^7.0.0" - "@babel/plugin-transform-block-scoping" "^7.0.0" - "@babel/plugin-transform-classes" "^7.1.0" - "@babel/plugin-transform-computed-properties" "^7.0.0" - "@babel/plugin-transform-destructuring" "^7.0.0" - "@babel/plugin-transform-dotall-regex" "^7.0.0" - "@babel/plugin-transform-duplicate-keys" "^7.0.0" - "@babel/plugin-transform-exponentiation-operator" "^7.1.0" - "@babel/plugin-transform-for-of" "^7.0.0" - "@babel/plugin-transform-function-name" "^7.1.0" - "@babel/plugin-transform-literals" "^7.0.0" - "@babel/plugin-transform-modules-amd" "^7.1.0" - "@babel/plugin-transform-modules-commonjs" "^7.1.0" - "@babel/plugin-transform-modules-systemjs" "^7.0.0" - "@babel/plugin-transform-modules-umd" "^7.1.0" - "@babel/plugin-transform-new-target" "^7.0.0" - "@babel/plugin-transform-object-super" "^7.1.0" - "@babel/plugin-transform-parameters" "^7.1.0" - "@babel/plugin-transform-regenerator" "^7.0.0" - "@babel/plugin-transform-shorthand-properties" "^7.0.0" - "@babel/plugin-transform-spread" "^7.0.0" - "@babel/plugin-transform-sticky-regex" "^7.0.0" - "@babel/plugin-transform-template-literals" "^7.0.0" - "@babel/plugin-transform-typeof-symbol" "^7.0.0" - "@babel/plugin-transform-unicode-regex" "^7.0.0" - browserslist "^4.1.0" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-display-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz#b5795f4e3e3140419c3611b7a2a3832b9aef328d" + integrity sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-jsx-development@^7.10.4": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.11.5.tgz#e1439e6a57ee3d43e9f54ace363fb29cefe5d7b6" + integrity sha512-cImAmIlKJ84sDmpQzm4/0q/2xrXlDezQoixy3qoz1NJeZL/8PRon6xZtluvr4H4FzwlDGI5tCcFupMnXGtr+qw== + dependencies: + "@babel/helper-builder-react-jsx-experimental" "^7.11.5" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" + +"@babel/plugin-transform-react-jsx-self@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz#cd301a5fed8988c182ed0b9d55e9bd6db0bd9369" + integrity sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" + +"@babel/plugin-transform-react-jsx-source@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz#34f1779117520a779c054f2cdd9680435b9222b4" + integrity sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" + +"@babel/plugin-transform-react-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz#673c9f913948764a4421683b2bef2936968fddf2" + integrity sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A== + dependencies: + "@babel/helper-builder-react-jsx" "^7.10.4" + "@babel/helper-builder-react-jsx-experimental" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" + +"@babel/plugin-transform-react-pure-annotations@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz#3eefbb73db94afbc075f097523e445354a1c6501" + integrity sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-regenerator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" + integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" + integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-runtime@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.5.tgz#f108bc8e0cf33c37da031c097d1df470b3a293fc" + integrity sha512-9aIoee+EhjySZ6vY5hnLjigHzunBlscx9ANKutkeWTJTx6m5Rbq6Ic01tLvO54lSusR+BxV7u4UDdCmXv5aagg== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + resolve "^1.8.1" + semver "^5.5.1" + +"@babel/plugin-transform-shorthand-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" + integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-spread@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz#fa84d300f5e4f57752fe41a6d1b3c554f13f17cc" + integrity sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" + +"@babel/plugin-transform-sticky-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" + integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-regex" "^7.10.4" + +"@babel/plugin-transform-template-literals@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" + integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-typeof-symbol@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" + integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-typescript@^7.10.4": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.11.0.tgz#2b4879676af37342ebb278216dd090ac67f13abb" + integrity sha512-edJsNzTtvb3MaXQwj8403B7mZoGu9ElDJQZOKjGUnvilquxBA3IQoEIOvkX/1O8xfAsnHS/oQhe2w/IXrr+w0w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-typescript" "^7.10.4" + +"@babel/plugin-transform-unicode-escapes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" + integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-unicode-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" + integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/preset-env@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.11.5.tgz#18cb4b9379e3e92ffea92c07471a99a2914e4272" + integrity sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA== + dependencies: + "@babel/compat-data" "^7.11.0" + "@babel/helper-compilation-targets" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-async-generator-functions" "^7.10.4" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-dynamic-import" "^7.10.4" + "@babel/plugin-proposal-export-namespace-from" "^7.10.4" + "@babel/plugin-proposal-json-strings" "^7.10.4" + "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.11.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.11.0" + "@babel/plugin-proposal-private-methods" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.10.4" + "@babel/plugin-transform-arrow-functions" "^7.10.4" + "@babel/plugin-transform-async-to-generator" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions" "^7.10.4" + "@babel/plugin-transform-block-scoping" "^7.10.4" + "@babel/plugin-transform-classes" "^7.10.4" + "@babel/plugin-transform-computed-properties" "^7.10.4" + "@babel/plugin-transform-destructuring" "^7.10.4" + "@babel/plugin-transform-dotall-regex" "^7.10.4" + "@babel/plugin-transform-duplicate-keys" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator" "^7.10.4" + "@babel/plugin-transform-for-of" "^7.10.4" + "@babel/plugin-transform-function-name" "^7.10.4" + "@babel/plugin-transform-literals" "^7.10.4" + "@babel/plugin-transform-member-expression-literals" "^7.10.4" + "@babel/plugin-transform-modules-amd" "^7.10.4" + "@babel/plugin-transform-modules-commonjs" "^7.10.4" + "@babel/plugin-transform-modules-systemjs" "^7.10.4" + "@babel/plugin-transform-modules-umd" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" + "@babel/plugin-transform-new-target" "^7.10.4" + "@babel/plugin-transform-object-super" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-property-literals" "^7.10.4" + "@babel/plugin-transform-regenerator" "^7.10.4" + "@babel/plugin-transform-reserved-words" "^7.10.4" + "@babel/plugin-transform-shorthand-properties" "^7.10.4" + "@babel/plugin-transform-spread" "^7.11.0" + "@babel/plugin-transform-sticky-regex" "^7.10.4" + "@babel/plugin-transform-template-literals" "^7.10.4" + "@babel/plugin-transform-typeof-symbol" "^7.10.4" + "@babel/plugin-transform-unicode-escapes" "^7.10.4" + "@babel/plugin-transform-unicode-regex" "^7.10.4" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.11.5" + browserslist "^4.12.0" + core-js-compat "^3.6.2" invariant "^2.2.2" - js-levenshtein "^1.1.3" - semver "^5.3.0" + levenary "^1.1.1" + semver "^5.5.0" "@babel/preset-flow@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2" - integrity sha512-bJOHrYOPqJZCkPVbG1Lot2r5OSsB+iUOaxiHdlOeB1yPWS6evswVHwvkDLZ54WTaTRIk89ds0iHmGZSnxlPejQ== + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.10.4.tgz#e0d9c72f8cb02d1633f6a5b7b16763aa2edf659f" + integrity sha512-XI6l1CptQCOBv+ZKYwynyswhtOKwpZZp5n0LG1QKCo8erRhqjoQV6nvx61Eg30JHpysWQSBwA2AWRU3pBbSY5g== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-flow-strip-types" "^7.10.4" -"@babel/preset-react@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0" - integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w== +"@babel/preset-modules@^0.1.3": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" + integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-transform-react-display-name" "^7.0.0" - "@babel/plugin-transform-react-jsx" "^7.0.0" - "@babel/plugin-transform-react-jsx-self" "^7.0.0" - "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" -"@babel/runtime@^7.0.0": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3" - integrity sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg== +"@babel/preset-react@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.10.4.tgz#92e8a66d816f9911d11d4cc935be67adfc82dbcf" + integrity sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw== dependencies: - regenerator-runtime "^0.12.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-react-display-name" "^7.10.4" + "@babel/plugin-transform-react-jsx" "^7.10.4" + "@babel/plugin-transform-react-jsx-development" "^7.10.4" + "@babel/plugin-transform-react-jsx-self" "^7.10.4" + "@babel/plugin-transform-react-jsx-source" "^7.10.4" + "@babel/plugin-transform-react-pure-annotations" "^7.10.4" + +"@babel/preset-typescript@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.10.4.tgz#7d5d052e52a682480d6e2cc5aa31be61c8c25e36" + integrity sha512-SdYnvGPv+bLlwkF2VkJnaX/ni1sMNetcGI1+nThF1gyv6Ph8Qucc4ZZAjM5yZcE/AKRXIOTZz7eSRDWOEjPyRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-typescript" "^7.10.4" + +"@babel/runtime-corejs3@^7.10.2": + version "7.11.2" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz#02c3029743150188edeb66541195f54600278419" + integrity sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": + version "7.11.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" + integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/standalone@^7.11.6": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.11.6.tgz#2ea3c9463c8b1d04ee2dacc5ac4b81674cec2967" + integrity sha512-Ye1pj3fN76OWlJyi+Ocy1kTr1BNs5vFWHsq2oKPp3lB4Q0r2WrHi+n/Y2w3sZK+1QSKAkDXTp12tCuBprBHZ1w== "@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" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" integrity sha512-w750Sloq0UNifLx1rUqwfbnC6uSUk0mfwwgGRfdLiaUzfAOiH0tHJE6ILQIUi3KYkjiCDTskoIsnfqZvWLBDng== dependencies: "@babel/code-frame" "7.0.0-beta.44" @@ -742,18 +1050,18 @@ babylon "7.0.0-beta.44" lodash "^4.2.0" -"@babel/template@^7.1.0", "@babel/template@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" - integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.1.2" - "@babel/types" "^7.1.2" + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" "@babel/traverse@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" integrity sha512-UHuDz8ukQkJCDASKHf+oDt3FVUzFd+QYfuBIsiNu/4+/ix6pP/C+uQZJ6K1oEfbCMv/IKWbgDEh7fcsnIE5AtA== dependencies: "@babel/code-frame" "7.0.0-beta.44" @@ -767,84 +1075,864 @@ invariant "^2.2.0" lodash "^4.2.0" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0": - version "7.1.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.4.tgz#f4f83b93d649b4b2c91121a9087fa2fa949ec2b4" - integrity sha512-my9mdrAIGdDiSVBuMjpn/oXYpva0/EZwWL3sm3Wcy/AVWO2eXnsoZruOT9jOGNRXU8KbCIu5zsKnXcAJ6PcV6Q== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.1.3" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.1.3" - "@babel/types" "^7.1.3" - debug "^3.1.0" +"@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5", "@babel/traverse@^7.11.5", "@babel/traverse@^7.7.0": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" + integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.5" + "@babel/types" "^7.11.5" + debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.10" + lodash "^4.17.19" "@babel/types@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" integrity sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ== dependencies: esutils "^2.0.2" lodash "^4.2.0" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.1.2", "@babel/types@^7.1.3": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.3.tgz#3a767004567060c2f40fca49a304712c525ee37d" - integrity sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA== +"@babel/types@^7.0.0-beta.49", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.4.4", "@babel/types@^7.7.0": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" + integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== dependencies: - esutils "^2.0.2" - lodash "^4.17.10" + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" to-fast-properties "^2.0.0" -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== +"@emotion/cache@^10.0.27": + version "10.0.29" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" + integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ== + dependencies: + "@emotion/sheet" "0.9.4" + "@emotion/stylis" "0.8.5" + "@emotion/utils" "0.11.3" + "@emotion/weak-memoize" "0.2.5" + +"@emotion/core@^10.0.14": + version "10.0.35" + resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.0.35.tgz#513fcf2e22cd4dfe9d3894ed138c9d7a859af9b3" + integrity sha512-sH++vJCdk025fBlRZSAhkRlSUoqSqgCzYf5fMOmqqi3bM6how+sQpg3hkgJonj8GxXM4WbD7dRO+4tegDB9fUw== + dependencies: + "@babel/runtime" "^7.5.5" + "@emotion/cache" "^10.0.27" + "@emotion/css" "^10.0.27" + "@emotion/serialize" "^0.11.15" + "@emotion/sheet" "0.9.4" + "@emotion/utils" "0.11.3" + +"@emotion/css@^10.0.27": + version "10.0.27" + resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" + integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw== + dependencies: + "@emotion/serialize" "^0.11.15" + "@emotion/utils" "0.11.3" + babel-plugin-emotion "^10.0.27" + +"@emotion/hash@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.1": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== + dependencies: + "@emotion/memoize" "0.7.4" + +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== + +"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": + version "0.11.16" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" + integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg== + dependencies: + "@emotion/hash" "0.8.0" + "@emotion/memoize" "0.7.4" + "@emotion/unitless" "0.7.5" + "@emotion/utils" "0.11.3" + csstype "^2.5.7" + +"@emotion/sheet@0.9.4": + version "0.9.4" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" + integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== + +"@emotion/styled-base@^10.0.27": + version "10.0.31" + resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" + integrity sha512-wTOE1NcXmqMWlyrtwdkqg87Mu6Rj1MaukEoEmEkHirO5IoHDJ8LgCQL4MjJODgxWxXibGR3opGp1p7YvkNEdXQ== + dependencies: + "@babel/runtime" "^7.5.5" + "@emotion/is-prop-valid" "0.8.8" + "@emotion/serialize" "^0.11.15" + "@emotion/utils" "0.11.3" + +"@emotion/styled@^10.0.14": + version "10.0.27" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-10.0.27.tgz#12cb67e91f7ad7431e1875b1d83a94b814133eaf" + integrity sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q== + dependencies: + "@emotion/styled-base" "^10.0.27" + babel-plugin-emotion "^10.0.27" + +"@emotion/stylis@0.8.5": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + +"@emotion/utils@0.11.3": + version "0.11.3" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" + integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== + +"@emotion/weak-memoize@0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" + integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== + +"@graphql-tools/schema@^6.0.14": + version "6.2.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-6.2.3.tgz#7ffc8e5f49d9a81f446fb8db87a6f5d07b1fba8e" + integrity sha512-CV5vDfQhXidssLK5hjT55FfwRAvBoGW53lVBl0rbXrbsSX7H9iVHdUf4UaDIlMc6WcnnzOrRiue/khHz3rzDEg== dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" + "@graphql-tools/utils" "6.2.3" + tslib "~2.0.1" -"@nodelib/fs.stat@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.2.tgz#54c5a964462be3d4d78af631363c18d6fa91ac26" - integrity sha512-yprFYuno9FtNsSHVlSWd+nRlmGoAbqbeCwOryP6sC/zoCjhpArcRMYp19EvpSUSizJAlsXEwJv+wcWS9XaXdMw== +"@graphql-tools/utils@6.2.3", "@graphql-tools/utils@^6.0.14": + version "6.2.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-6.2.3.tgz#235636b47a62f12f3dddbdd30b2986fc03f3a5fa" + integrity sha512-eOhZy4y23r6AddokBqvFpQybtHvhTyZCc3VFWn8eIqF92vre90UKHbCX6Cf6VBo6i7l0ZwChPPbUzEiHOk+HJQ== + dependencies: + "@ardatan/aggregate-error" "0.0.6" + camel-case "4.1.1" + tslib "~2.0.1" -"@reach/router@^1.1.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.2.1.tgz#34ae3541a5ac44fa7796e5506a5d7274a162be4e" - integrity sha512-kTaX08X4g27tzIFQGRukaHmNbtMYDS3LEWIS8+l6OayGIw6Oyo1HIF/JzeuR2FoF9z6oV+x/wJSVSq4v8tcUGQ== +"@hapi/address@2.x.x": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + +"@hapi/bourne@1.x.x": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" + integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== + +"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" + integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== + +"@hapi/joi@^15.1.1": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" + integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== + dependencies: + "@hapi/address" "2.x.x" + "@hapi/bourne" "1.x.x" + "@hapi/hoek" "8.x.x" + "@hapi/topo" "3.x.x" + +"@hapi/topo@3.x.x": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== + dependencies: + "@hapi/hoek" "^8.3.0" + +"@jest/types@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" + integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + +"@jimp/bmp@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.14.0.tgz#6df246026554f276f7b354047c6fff9f5b2b5182" + integrity sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + bmp-js "^0.1.0" + +"@jimp/core@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.14.0.tgz#870c9ca25b40be353ebda1d2abb48723d9010055" + integrity sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + any-base "^1.1.0" + buffer "^5.2.0" + exif-parser "^0.1.12" + file-type "^9.0.0" + load-bmfont "^1.3.1" + mkdirp "^0.5.1" + phin "^2.9.1" + pixelmatch "^4.0.2" + tinycolor2 "^1.4.1" + +"@jimp/custom@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.14.0.tgz#1dbbf0094df7403f4e03bc984ed92e7458842f74" + integrity sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/core" "^0.14.0" + +"@jimp/gif@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.14.0.tgz#db159f57c3cfd1566bbe8b124958791998614960" + integrity sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg== dependencies: - create-react-context "^0.2.1" + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + gifwrap "^0.9.2" + omggif "^1.0.9" + +"@jimp/jpeg@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.14.0.tgz#8a687a6a653bbbae38c522edef8f84bb418d9461" + integrity sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + jpeg-js "^0.4.0" + +"@jimp/plugin-blit@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz#5eb374be1201313b2113899fb842232d8fcfd345" + integrity sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-blur@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz#fe07e4932d5a2f5d8c9831e245561553224bfc60" + integrity sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-circle@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz#82c0e904a34e90fa672fb9c286bc892e92088ddf" + integrity sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-color@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.14.0.tgz#772bd2d80a88bc66ea1331d010207870f169a74b" + integrity sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + tinycolor2 "^1.4.1" + +"@jimp/plugin-contain@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz#c68115420d182e696f81bbe76fb5e704909b2b6a" + integrity sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-cover@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz#4755322589c5885e44e14e31b86b542e907297ce" + integrity sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-crop@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz#4cbd856ca84ffc37230fad2534906f2f75aa3057" + integrity sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-displace@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz#b0e6a57d00cb1f893f541413fe9d737d23c3b70c" + integrity sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-dither@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz#9185ec4c38e02edc9e5831f5d709f6ba891e1b93" + integrity sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-fisheye@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz#9f26346cf2fbc660cc2008cd7fd30a83b5029e78" + integrity sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-flip@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz#7966d6aa3b5fe1aa4d2d561ff12b8ef5ccb9b071" + integrity sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-gaussian@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz#452bc1971a4467ad9b984aa67f4c200bf941bb65" + integrity sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-invert@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz#cd31a555860e9f821394936d15af161c09c42921" + integrity sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-mask@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz#52619643ac6222f85e6b27dee33c771ca3a6a4c9" + integrity sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-normalize@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz#bf39e356b6d473f582ce95633ad49c9cdb82492b" + integrity sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-print@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.14.0.tgz#1c43c2a92a7adc05b464863882cb89ce486d63e6" + integrity sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + load-bmfont "^1.4.0" + +"@jimp/plugin-resize@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz#ef7fc6c2e45f8bcab62456baf8fd3bc415b02b64" + integrity sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-rotate@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz#3632bc159bf1c3b9ec9f459d9c05d02a11781ee7" + integrity sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-scale@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz#d30f0cd1365b8e68f43fa423300ae7f124e9bf10" + integrity sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-shadow@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz#471fdb9f109ff2d9e20d533d45e1e18e0b48c749" + integrity sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugin-threshold@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz#ebd72721c7d1d518c5bb6e494e55d97ac3351d3b" + integrity sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + +"@jimp/plugins@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.14.0.tgz#41dba85f15ab8dadb4162100eb54e5f27b93ee2c" + integrity sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/plugin-blit" "^0.14.0" + "@jimp/plugin-blur" "^0.14.0" + "@jimp/plugin-circle" "^0.14.0" + "@jimp/plugin-color" "^0.14.0" + "@jimp/plugin-contain" "^0.14.0" + "@jimp/plugin-cover" "^0.14.0" + "@jimp/plugin-crop" "^0.14.0" + "@jimp/plugin-displace" "^0.14.0" + "@jimp/plugin-dither" "^0.14.0" + "@jimp/plugin-fisheye" "^0.14.0" + "@jimp/plugin-flip" "^0.14.0" + "@jimp/plugin-gaussian" "^0.14.0" + "@jimp/plugin-invert" "^0.14.0" + "@jimp/plugin-mask" "^0.14.0" + "@jimp/plugin-normalize" "^0.14.0" + "@jimp/plugin-print" "^0.14.0" + "@jimp/plugin-resize" "^0.14.0" + "@jimp/plugin-rotate" "^0.14.0" + "@jimp/plugin-scale" "^0.14.0" + "@jimp/plugin-shadow" "^0.14.0" + "@jimp/plugin-threshold" "^0.14.0" + timm "^1.6.1" + +"@jimp/png@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.14.0.tgz#0f2dddb5125c0795ca7e67c771204c5437fcda4b" + integrity sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.14.0" + pngjs "^3.3.3" + +"@jimp/tiff@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.14.0.tgz#a5b25bbe7c43fc3b07bad4e2ab90e0e164c1967f" + integrity sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw== + dependencies: + "@babel/runtime" "^7.7.2" + utif "^2.0.1" + +"@jimp/types@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.14.0.tgz#ef681ff702883c5f105b5e4e30d49abf39ee9e34" + integrity sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/bmp" "^0.14.0" + "@jimp/gif" "^0.14.0" + "@jimp/jpeg" "^0.14.0" + "@jimp/png" "^0.14.0" + "@jimp/tiff" "^0.14.0" + timm "^1.6.1" + +"@jimp/utils@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.14.0.tgz#296254e63118554c62c31c19ac6b8c4bfe6490e5" + integrity sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A== + dependencies: + "@babel/runtime" "^7.7.2" + regenerator-runtime "^0.13.3" + +"@mdx-js/mdx@^2.0.0-next.4", "@mdx-js/mdx@^2.0.0-next.8": + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-2.0.0-next.8.tgz#7d29d7ee634ab0c37cf44bd8d9b1e93c5e09649f" + integrity sha512-OT3bkvsA+rmqv378+UWFgeQuchaafhVgOO46+hc5U7KrGK3iPI2yGTcFwD3/KzSu+JGPCEUBREE96ncpvYqKjA== + dependencies: + "@babel/core" "7.10.5" + "@babel/plugin-syntax-jsx" "7.10.4" + "@babel/plugin-syntax-object-rest-spread" "7.8.3" + "@mdx-js/util" "^2.0.0-next.8" + babel-plugin-apply-mdx-type-prop "^2.0.0-next.8" + babel-plugin-extract-export-names "^2.0.0-next.8" + babel-plugin-extract-import-names "^2.0.0-next.8" + camelcase-css "2.0.1" + detab "2.0.3" + hast-to-hyperscript "9.0.0" + hast-util-raw "6.0.0" + lodash.uniq "4.5.0" + mdast-util-to-hast "9.1.0" + remark-footnotes "1.0.0" + remark-mdx "^2.0.0-next.8" + remark-mdxjs "^2.0.0-next.8" + remark-parse "8.0.2" + remark-squeeze-paragraphs "4.0.0" + unified "9.0.0" + unist-builder "2.0.3" + unist-util-visit "2.0.3" + +"@mdx-js/react@^1.5.2": + version "1.6.18" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.18.tgz#f83cbb2355de9cf36a213140ce21647da1e34fa7" + integrity sha512-aFHsZVu7r9WamlP+WO/lyvHHZAubkQjkcRYlvS7fQElypfJvjKdHevjC3xiqlsQpasx/4KqRMoEIb++wNtd+6w== + +"@mdx-js/react@^2.0.0-next.4", "@mdx-js/react@^2.0.0-next.8": + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-2.0.0-next.8.tgz#fa774dc781600eb075513eebaae6bd705776ac34" + integrity sha512-I/ped8Wb1L4sUlumQmUlYQsH0tjd2Zj2eyCWbqgigpg+rtRlNFO9swkeyr0GY9hNZnwI8QOnJtNe+UdIZim8LQ== + +"@mdx-js/runtime@^2.0.0-next.4": + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/@mdx-js/runtime/-/runtime-2.0.0-next.8.tgz#08ab8f83d935876bba97c1cfef1033c429ec6167" + integrity sha512-W51pdm1NF5xjuHNYomKmK7ByiCvJ3rg6eGvvvGX8k3sUGZZbojBWxypamEiS25EX5Gt0FoDYxo6q0Yf9EmEs6Q== + dependencies: + "@mdx-js/mdx" "^2.0.0-next.8" + "@mdx-js/react" "^2.0.0-next.8" + buble-jsx-only "^0.19.8" + +"@mdx-js/util@^2.0.0-next.8": + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-2.0.0-next.8.tgz#66ecc27b78e07a3ea2eb1a8fc5a99dfa0ba96690" + integrity sha512-T0BcXmNzEunFkuxrO8BFw44htvTPuAoKbLvTG41otyZBDV1Rs+JMddcUuaP5vXpTWtgD3grhcrPEwyx88RUumQ== + +"@mikaelkristiansson/domready@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mikaelkristiansson/domready/-/domready-1.0.10.tgz#f6d69866c0857664e70690d7a0bfedb72143adb5" + integrity sha512-6cDuZeKSCSJ1KvfEQ25Y8OXUjqDJZ+HgUs6dhASWbAX8fxVraTfPsSeRe2bN+4QJDsgUaXaMWBYfRomCr04GGg== + +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + +"@pieh/friendly-errors-webpack-plugin@1.7.0-chalk-2": + version "1.7.0-chalk-2" + resolved "https://registry.yarnpkg.com/@pieh/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.0-chalk-2.tgz#2e9da9d3ade9d18d013333eb408c457d04eabac0" + integrity sha512-65+vYGuDkHBCWWjqzzR/Ck318+d6yTI00EqII9qe3aPD1J3Olhvw0X38uM5moQb1PK/ksDXwSoPGt/5QhCiotw== + dependencies: + chalk "^2.4.2" + error-stack-parser "^2.0.0" + string-width "^2.0.0" + strip-ansi "^3" + +"@pmmmwh/react-refresh-webpack-plugin@^0.4.1": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.2.tgz#1f9741e0bde9790a0e13272082ed7272a083620d" + integrity sha512-Loc4UDGutcZ+Bd56hBInkm6JyjyCwWy4t2wcDXzN8EDPANgVRj0VP8Nxn0Zq2pc+WKauZwEivQgbDGg4xZO20A== + dependencies: + ansi-html "^0.0.7" + error-stack-parser "^2.0.6" + html-entities "^1.2.1" + native-url "^0.2.6" + schema-utils "^2.6.5" + source-map "^0.7.3" + +"@reach/alert@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@reach/alert/-/alert-0.10.3.tgz#9e4278edf8e6cfbe94df9a105faaa1c049a84517" + integrity sha512-Nu0XRKsHdM4gblgIgfTyJSl2KV1vrRTVVCVpol/f/ZVckTXAM/qN0C+JCCZSMfdjtt3u29CX6pRNkVu3PLfYsQ== + dependencies: + "@reach/utils" "^0.10.3" + "@reach/visually-hidden" "^0.10.2" + prop-types "^15.7.2" + tslib "^1.11.2" + +"@reach/auto-id@^0.10.3": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.10.5.tgz#fa78c71ce2f565ebed1ad91a8d9a685176d23c48" + integrity sha512-we4/bwjFxJ3F+2eaddQ1HltbKvJ7AB8clkN719El7Zugpn/vOjfPMOVUiBqTmPGLUvkYrq4tpuFwLvk2HyOVHg== + dependencies: + "@reach/utils" "0.10.5" + tslib "^2.0.0" + +"@reach/combobox@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@reach/combobox/-/combobox-0.10.3.tgz#a5c2b76fdaf28a8872e5a49200570970da20e7e7" + integrity sha512-Z9Xl+j4Tm9JNC6ouHhzL0lv2Y+Of5/tD7CnpxaVudeIeXQKjeg5YSUCnIBU/OTUtRsIllkgACk70SGHqvntQAw== + dependencies: + "@reach/auto-id" "^0.10.3" + "@reach/descendants" "^0.10.3" + "@reach/popover" "^0.10.3" + "@reach/portal" "^0.10.3" + "@reach/utils" "^0.10.3" + highlight-words-core "1.2.2" + prop-types "^15.7.2" + tslib "^1.11.2" + +"@reach/descendants@^0.10.3": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@reach/descendants/-/descendants-0.10.5.tgz#2611174e9e9b326dba548356221e2f8c8f5c8612" + integrity sha512-8HhN4DwS/HsPQ+Ym/Ft/XJ1spXBYdE8hqpnbYR9UcU7Nx3oDbTIdhjA6JXXt23t5avYIx2jRa8YHCtVKSHuiwA== + dependencies: + "@reach/utils" "0.10.5" + tslib "^2.0.0" + +"@reach/dialog@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@reach/dialog/-/dialog-0.10.3.tgz#ba789809c3b194fff79d3bcb4a583c58e03edb83" + integrity sha512-RMpUHNjRQhkjGzKt9/oLmDhwUBikW3JbEzgzZngq5MGY5kWRPwYInLDkEA8We4E43AbBsl5J/PRzQha9V+EEXw== + dependencies: + "@reach/portal" "^0.10.3" + "@reach/utils" "^0.10.3" + prop-types "^15.7.2" + react-focus-lock "^2.3.1" + react-remove-scroll "^2.3.0" + tslib "^1.11.2" + +"@reach/menu-button@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.10.3.tgz#6e72cd122e16f28c4b15a140f329be256adc72c8" + integrity sha512-50C5nl7JJG9YcKqngmwTLVft+ZF2MMieto1GSCC7qEU8ykUNz0p69Ipup+Eqjk7KRHpSIYPlYIfAOS75dDuiZQ== + dependencies: + "@reach/auto-id" "^0.10.3" + "@reach/descendants" "^0.10.3" + "@reach/popover" "^0.10.3" + "@reach/utils" "^0.10.3" + prop-types "^15.7.2" + tslib "^1.11.2" + +"@reach/observe-rect@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2" + integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ== + +"@reach/popover@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@reach/popover/-/popover-0.10.3.tgz#82e29b91748869923756a165758a29c8269b93e3" + integrity sha512-41iNfdjd9/5HtYuhezTc9z9WGkloYFVB8wBmPX3QOTuBP4qYd0La5sXClrfyiVqPn/uj1gGzehrZKuh8oSkorw== + dependencies: + "@reach/portal" "^0.10.3" + "@reach/rect" "^0.10.3" + "@reach/utils" "^0.10.3" + tabbable "^4.0.0" + tslib "^1.11.2" + +"@reach/popover@^0.10.3": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@reach/popover/-/popover-0.10.5.tgz#ccf89f6540e6477d8d087e78a8294e89295a743d" + integrity sha512-S+qWIsjrN1yMpHjgELhjpdGc4Q3q1plJtXBGGQRxUAjmCUA/5OY7t5w5C8iqMNAEBwCvYXKvK/pLcXFxxLykSw== + dependencies: + "@reach/portal" "0.10.5" + "@reach/rect" "0.10.5" + "@reach/utils" "0.10.5" + tabbable "^4.0.0" + tslib "^2.0.0" + +"@reach/portal@0.10.5", "@reach/portal@^0.10.3": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.10.5.tgz#532ce8472fc99d6c556520f6c8d53333d89e49a4" + integrity sha512-K5K8gW99yqDPDCWQjEfSNZAbGOQWSx5AN2lpuR1gDVoz4xyWpTJ0k0LbetYJTDVvLP/InEcR7AU42JaDYDCXQw== + dependencies: + "@reach/utils" "0.10.5" + tslib "^2.0.0" + +"@reach/rect@0.10.5", "@reach/rect@^0.10.3": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.10.5.tgz#ba68722f155b146c6790e7d10a97dca1fd7ab14e" + integrity sha512-JBKs2HniYecq5zLO6UFReX28SUBPM3n0aizdNgHuvwZmDcTfNV4jsuJYQLqJ+FbCQsrSHkBxKZqWpfGXY9bUEg== + dependencies: + "@reach/observe-rect" "1.2.0" + "@reach/utils" "0.10.5" + prop-types "^15.7.2" + tslib "^2.0.0" + +"@reach/router@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c" + integrity sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA== + dependencies: + create-react-context "0.3.0" invariant "^2.2.3" prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" - warning "^3.0.0" + +"@reach/tabs@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@reach/tabs/-/tabs-0.10.3.tgz#392461762b33af2476d26b3018e1489260532b85" + integrity sha512-yKHyb4NRah9+V8kjkgzIXnj+FPG9aNfHX9uBs32A4MAG4RQLsZr9jBVSoWV1jxMUcYDe4CLtQj8qVphaW/GB2A== + dependencies: + "@reach/auto-id" "^0.10.3" + "@reach/descendants" "^0.10.3" + "@reach/utils" "^0.10.3" + prop-types "^15.7.2" + tslib "^1.11.2" + +"@reach/tooltip@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.10.3.tgz#0f34630aceaba7da7ad2dd23f92cc0d484e7113f" + integrity sha512-tbj569uSJ+O86fAvR62lK8Tb00aTQxah6dFKgf06lskCGUoYzeFxkZTds9b+TRjzz9G1v68McQHwuAZUH0XrGA== + dependencies: + "@reach/auto-id" "^0.10.3" + "@reach/portal" "^0.10.3" + "@reach/rect" "^0.10.3" + "@reach/utils" "^0.10.3" + "@reach/visually-hidden" "^0.10.2" + prop-types "^15.7.2" + tslib "^1.11.2" + +"@reach/utils@0.10.5", "@reach/utils@^0.10.3": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.10.5.tgz#fbac944d29565f6dd7abd0e1b13950e99b1e470b" + integrity sha512-5E/xxQnUbmpI/LrufBAOXjunl96DnqX6B4zC2MO2KH/dRzLug5gM5VuOwV26egsp0jvsSPxojwciOhS43px3qw== + dependencies: + "@types/warning" "^3.0.0" + tslib "^2.0.0" + warning "^4.0.3" + +"@reach/visually-hidden@^0.10.2": + version "0.10.4" + resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.10.4.tgz#ab390db0adf759393af4d856f84375468b1df676" + integrity sha512-GnuPuTRCf+Ih47BoKvGyB+jP8EVWLb04GfbGa5neOrjdp90qrb4zr7pMSL4ZvTsrxt9MRooJA2BhSxs5DbyqCQ== + dependencies: + tslib "^2.0.0" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@sindresorhus/is@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" + integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== + +"@sindresorhus/is@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" + integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== + +"@sindresorhus/slugify@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/slugify/-/slugify-1.1.0.tgz#2f195365d9b953384305b62664b44b4036c49430" + integrity sha512-ujZRbmmizX26yS/HnB3P9QNlNa4+UvHh+rIse3RbOXLp8yl6n1TxB4t7NHggtVgS8QmmOtzXo48kCxZGACpkPw== + dependencies: + "@sindresorhus/transliterate" "^0.1.1" + escape-string-regexp "^4.0.0" + +"@sindresorhus/transliterate@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/transliterate/-/transliterate-0.1.1.tgz#779b31244781d3c898f185b61d58c89e7c782674" + integrity sha512-QSdIQ5keUFAZ3KLbfbsntW39ox0Ym8183RqTwBq/ZEFoN3NQAtGV+qWaNdzKpIDHgj9J2CQ2iNDRVU11Zyr7MQ== + dependencies: + escape-string-regexp "^2.0.0" + lodash.deburr "^4.1.0" + +"@styled-system/css@^5.0.16": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@styled-system/css/-/css-5.1.5.tgz#0460d5f3ff962fa649ea128ef58d9584f403bbbc" + integrity sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@szmarczak/http-timer@^4.0.0": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + +"@turist/fetch@^7.1.7": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@turist/fetch/-/fetch-7.1.7.tgz#a2b1f7ec0265e6fe0946c51eef34bad9b9efc865" + integrity sha512-XP20kvfyMNlWdPVQXyuzA40LoCHbbJptikt7W+TlZ5sS+NNjk70xjXCtHBLEudp7li3JldXEFSIUzpW1a0WEhA== + dependencies: + "@types/node-fetch" "2" + +"@turist/time@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@turist/time/-/time-0.0.1.tgz#57637d2a7d1860adb9f9cecbdcc966ce4f551d63" + integrity sha512-M2BiThcbxMxSKX8W4z5u9jKZn6datnM3+FpEU+eYw0//l31E2xhqi7vTAuJ/Sf0P3yhp66SDJgPu3bRRpvrdQQ== + +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/common-tags@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@types/common-tags/-/common-tags-1.8.0.tgz#79d55e748d730b997be5b7fce4b74488d8b26a6b" + integrity sha512-htRqZr5qn8EzMelhX/Xmx142z218lLyGaeZ3YR8jlze4TATRU9huKKvuBmAJEW4LCC4pnY1N6JAm6p85fMHjhg== "@types/configstore@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/configstore/-/configstore-2.1.1.tgz#cd1e8553633ad3185c3f2f239ecff5d2643e92b6" integrity sha1-zR6FU2M60xhcPy8jns/10mQ+krY= -"@types/debug@^0.0.29": - version "0.0.29" - resolved "http://registry.npmjs.org/@types/debug/-/debug-0.0.29.tgz#a1e514adfbd92f03a224ba54d693111dbf1f3754" - integrity sha1-oeUUrfvZLwOiJLpU1pMRHb8fN1Q= +"@types/debug@^0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.30.tgz#dc1e40f7af3b9c815013a7860e6252f6352a84df" + integrity sha512-orGL5LXERPYsLov6CWs3Fh6203+dXzJkR7OnddIr2514Hsecwc8xRpzCapshBbKFImCsvS/mk6+FWiN5LyZJAQ== + +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== "@types/events@*": - version "1.2.0" - resolved "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" - integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/get-port@^0.0.4": - version "0.0.4" - resolved "http://registry.npmjs.org/@types/get-port/-/get-port-0.0.4.tgz#eb6bb7423d9f888b632660dc7d2fd3e69a35643e" - integrity sha1-62u3Qj2fiItjJmDcfS/T5po1ZD4= +"@types/get-port@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/get-port/-/get-port-3.2.0.tgz#f9e0a11443cc21336470185eae3dfba4495d29bc" + integrity sha512-TiNg8R1kjDde5Pub9F9vCwZA/BNW9HeXP5b9j7Qucqncy/McfPZ6xze/EyBdXS5FhMIGN6Fx3vg75l5KHy3V1Q== -"@types/glob@^5.0.30": +"@types/glob@*", "@types/glob@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/glob@^5.0.34": version "5.0.36" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.36.tgz#0c80a9c8664fc7d19781de229f287077fd622cb2" integrity sha512-KEzSKuP2+3oOjYYjujue6Z3Yqis5HKA1BsIC+jZ1v3lrRNdsqyNNtX0rQf6LSuI4DJJ2z5UV//zBZCcvM0xikg== @@ -853,237 +1941,445 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/hast@^2.0.0": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.1.tgz#b16872f2a6144c7025f296fb9636a667ebb79cd9" + integrity sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q== + dependencies: + "@types/unist" "*" + "@types/history@*": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.0.tgz#2fac51050c68f7d6f96c5aafc631132522f4aa3f" - integrity sha512-1A/RUAX4VtmGzNTGLSfmiPxQ3XwUSe/1YN4lW9GRa+j307oFK6MPjhlvw6jEHDodUBIvSvrA7/iHDchr5LS+0Q== + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + +"@types/http-proxy@^1.17.4": + version "1.17.4" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b" + integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" + integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" + integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/keyv@*", "@types/keyv@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + +"@types/lodash.sample@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/lodash.sample/-/lodash.sample-4.2.6.tgz#ec7f6a6dbd0401c4a1e5f5b3c85fa5a85a42a84a" + integrity sha512-hxBvsUjPcW1O8mC9TiBE4m8TwvLuUU+zW8J6GI1M6WmPg8J87mXGt7zavpJ/9Znb+0rVsSB3VNAjCFaJ9YUJKg== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*", "@types/lodash@^4.14.92": + version "4.14.161" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" + integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== + +"@types/mdast@^3.0.0", "@types/mdast@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" + integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== + dependencies: + "@types/unist" "*" "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mkdirp@^0.3.29": - version "0.3.29" - resolved "http://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.3.29.tgz#7f2ad7ec55f914482fc9b1ec4bb1ae6028d46066" - integrity sha1-fyrX7FX5FEgvybHsS7GuYCjUYGY= +"@types/mkdirp@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" + integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== + dependencies: + "@types/node" "*" + +"@types/node-fetch@2": + version "2.5.7" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" + integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== + dependencies: + "@types/node" "*" + form-data "^3.0.0" "@types/node@*": - version "10.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.7.tgz#0e75ca9357d646ca754016ca1d68a127ad7e7300" - integrity sha512-yOxFfkN9xUFLyvWaeYj90mlqTJ41CsQzWKS3gXdOMOyPVacUsymejKxJ4/pMW7exouubuEeZLJawGgcNGYlTeg== + version "14.11.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" + integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== -"@types/node@^7.0.11": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-7.10.0.tgz#9a42f16e2b264cb63903989c5d1d0a7c5eb901c2" - integrity sha512-yF75IZxur7xs90zpmoE+ktRJGJIauORo4qblVFvfKTYSSBFRRWlrl2dO/tE4vetSS4KAvFumS+1thTf3mMZhaA== +"@types/node@^8.5.7": + version "8.10.64" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.64.tgz#0dddc4c53ca4819a32b7478232d8b446ca90e1c6" + integrity sha512-/EwBIb+imu8Qi/A3NF9sJ9iuKo7yV+pryqjmeRqaU0C4wBAOhas5mdvoYeJ5PCKrh6thRSJHdoasFqh3BQGILA== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/parse5@^5.0.0": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" + integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== "@types/prop-types@*": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.6.tgz#9c03d3fed70a8d517c191b7734da2879b50ca26c" - integrity sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ== + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/reach__router@^1.0.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.2.0.tgz#189c211870a608bec3d4162ade78ec372ad982b5" - integrity sha512-+mzlpU4fbeIrFECUJhx+wCV5GRQeWlP62O9JL4OQajIBH4WpcTTfFvmX0/HPFJ44l5S5HSHxMJbd5Tz2ngcXAg== +"@types/q@^1.5.1": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" + integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== + +"@types/reach__router@^1.3.3": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.5.tgz#14e1e981cccd3a5e50dc9e969a72de0b9d472f6d" + integrity sha512-h0NbqXN/tJuBY/xggZSej1SKQEstbHO7J/omt1tYoFGmj3YXOodZKbbqD4mNDh7zvEGYd7YFrac1LTtAr3xsYQ== dependencies: "@types/history" "*" "@types/react" "*" "@types/react@*": - version "16.4.16" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.16.tgz#99f91b1200ae8c2062030402006d3b3c3a177043" - integrity sha512-lxyoipLWweAnLnSsV4Ho2NAZTKKmxeYgkTQ6PaDiPDU9JJBUY2zJVVGiK1smzYv8+ZgbqEmcm5xM74GCpunSEA== + version "16.9.49" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872" + integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g== dependencies: "@types/prop-types" "*" - csstype "^2.2.0" - -"@types/tmp@^0.0.32": - version "0.0.32" - resolved "http://registry.npmjs.org/@types/tmp/-/tmp-0.0.32.tgz#0d3cb31022f8427ea58c008af32b80da126ca4e3" - integrity sha1-DTyzECL4Qn6ljACK8yuA2hJspOM= - -"@webassemblyjs/ast@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.8.tgz#f31f480debeef957f01b623f27eabc695fa4fe8f" - integrity sha512-dOrtdtEyB8sInpl75yLPNksY4sRl0j/+t6aHyB/YA+ab9hV3Fo7FmG12FHzP+2MvWVAJtDb+6eXR5EZbZJ+uVg== - dependencies: - "@webassemblyjs/helper-module-context" "1.7.8" - "@webassemblyjs/helper-wasm-bytecode" "1.7.8" - "@webassemblyjs/wast-parser" "1.7.8" - -"@webassemblyjs/floating-point-hex-parser@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.8.tgz#1b3ed0e27e384032254e9322fc646dd3e70ef1b9" - integrity sha512-kn2zNKGsbql5i56VAgRYkpG+VazqHhQQZQycT2uXAazrAEDs23gy+Odkh5VblybjnwX2/BITkDtNmSO76hdIvQ== - -"@webassemblyjs/helper-api-error@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.8.tgz#a2b49c11f615e736f815ec927f035dcfa690d572" - integrity sha512-xUwxDXsd1dUKArJEP5wWM5zxgCSwZApSOJyP1XO7M8rNUChUDblcLQ4FpzTpWG2YeylMwMl1MlP5Ztryiz1x4g== - -"@webassemblyjs/helper-buffer@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.8.tgz#3fc66bfa09c1c60e824cf3d5887826fac062877d" - integrity sha512-WXiIMnuvuwlhWvVOm8xEXU9DnHaa3AgAU0ZPfvY8vO1cSsmYb2WbGbHnMLgs43vXnA7XAob9b56zuZaMkxpCBg== - -"@webassemblyjs/helper-code-frame@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.8.tgz#cc5a7e9522b70e7580df056dfd34020cf29645b0" - integrity sha512-TLQxyD9qGOIdX5LPQOPo0Ernd88U5rHkFb8WAjeMIeA0sPjCHeVPaGqUGGIXjUcblUkjuDAc07bruCcNHUrHDA== - dependencies: - "@webassemblyjs/wast-printer" "1.7.8" - -"@webassemblyjs/helper-fsm@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.8.tgz#fe4607430af466912797c21acafd3046080182ea" - integrity sha512-TjK0CnD8hAPkV5mbSp5aWl6SO1+H3WFcjWtixWoy8EMA99YnNzYhpc/WSYWhf7yrhpzkq5tZB0tvLK3Svr3IXA== - -"@webassemblyjs/helper-module-context@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.8.tgz#3c2e7ee93d14ff4768ba66fb1be42fdc9dc7160a" - integrity sha512-uCutAKR7Nm0VsFixcvnB4HhAyHouNbj0Dx1p7eRjFjXGGZ+N7ftTaG1ZbWCasAEbtwGj54LP8+lkBZdTCPmLGg== - -"@webassemblyjs/helper-wasm-bytecode@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.8.tgz#89bdb78cd6dd5209ae2ed2925de78d0f0e00b6f0" - integrity sha512-AdCCE3BMW6V34WYaKUmPgVHa88t2Z14P4/0LjLwuGkI0X6pf7nzp0CehzVVk51cKm2ymVXjl9dCG+gR1yhITIQ== - -"@webassemblyjs/helper-wasm-section@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.8.tgz#c68ef7d26a6fc12421b2e6e56f9bc810dfb33e87" - integrity sha512-BkBhYQuzyl4hgTGOKo87Vdw6f9nj8HhI7WYpI0MCC5qFa5ahrAPOGgyETVdnRbv+Rjukl9MxxfDmVcVC435lDg== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/helper-buffer" "1.7.8" - "@webassemblyjs/helper-wasm-bytecode" "1.7.8" - "@webassemblyjs/wasm-gen" "1.7.8" - -"@webassemblyjs/ieee754@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.8.tgz#1f37974b13cb486a9237e73ce04cac7a2f1265ed" - integrity sha512-tOarWChdG1a3y1yqCX0JMDKzrat5tQe4pV6K/TX19BcXsBLYxFQOL1DEDa5KG9syeyvCrvZ+i1+Mv1ExngvktQ== + csstype "^3.0.2" + +"@types/responselike@*": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + +"@types/rimraf@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.4.tgz#403887b0b53c6100a6c35d2ab24f6ccc042fec46" + integrity sha512-8gBudvllD2A/c0CcEX/BivIDorHFt5UI5m46TsNj8DjWCCTTZT74kEe4g+QsY7P/B9WdO98d82zZgXO/RQzu2Q== + dependencies: + "@types/glob" "*" + "@types/node" "*" + +"@types/tmp@^0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d" + integrity sha1-EHPEvIJHVK49EM+riKsCN7qWTk0= + +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" + integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== + +"@types/vfile-message@*": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-2.0.0.tgz#690e46af0fdfc1f9faae00cd049cc888957927d5" + integrity sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw== + dependencies: + vfile-message "*" + +"@types/vfile@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-3.0.2.tgz#19c18cd232df11ce6fa6ad80259bc86c366b09b9" + integrity sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw== + dependencies: + "@types/node" "*" + "@types/unist" "*" + "@types/vfile-message" "*" + +"@types/warning@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" + integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI= + +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^15.0.0": + version "15.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79" + integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w== + dependencies: + "@types/yargs-parser" "*" + +"@types/yoga-layout@1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.2.tgz#efaf9e991a7390dc081a0b679185979a83a9639a" + integrity sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw== + +"@typescript-eslint/eslint-plugin@^2.24.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" + integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== + dependencies: + "@typescript-eslint/experimental-utils" "2.34.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^2.24.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" + integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@urql/core@^1.12.3": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@urql/core/-/core-1.13.0.tgz#811154b5951b4282dd698a8c9dff4ff3825ca215" + integrity sha512-g7Kn2a0TDQotOqvGR7bRC+JqviA4TYtiDZH5M9Roszx7LiqZBkViSr5zmdwlNZjR4kSDn+kSXlCV+Ht0Uqls0Q== + dependencies: + wonka "^4.0.14" + +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.8.tgz#1bee83426819192db2ea1a234b84c7ebc6d34c1f" - integrity sha512-GCYeGPgUFWJiZuP4NICbcyUQNxNLJIf476Ei+K+jVuuebtLpfvwkvYT6iTUE7oZYehhkor4Zz2g7SJ/iZaPudQ== - dependencies: - "@xtuc/long" "4.2.1" - -"@webassemblyjs/utf8@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.8.tgz#2b489d5cf43e0aebb93d8e2d792aff9879c61f05" - integrity sha512-9X+f0VV+xNXW2ujfIRSXBJENGE6Qh7bNVKqu3yDjTFB3ar3nsThsGBBKdTG58aXOm2iUH6v28VIf88ymPXODHA== - -"@webassemblyjs/wasm-edit@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.8.tgz#f8bdbe7088718eca27b1c349bb7c06b8a457950c" - integrity sha512-6D3Hm2gFixrfyx9XjSON4ml1FZTugqpkIz5Awvrou8fnpyprVzcm4X8pyGRtA2Piixjl3DqmX/HB1xdWyE097A== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/helper-buffer" "1.7.8" - "@webassemblyjs/helper-wasm-bytecode" "1.7.8" - "@webassemblyjs/helper-wasm-section" "1.7.8" - "@webassemblyjs/wasm-gen" "1.7.8" - "@webassemblyjs/wasm-opt" "1.7.8" - "@webassemblyjs/wasm-parser" "1.7.8" - "@webassemblyjs/wast-printer" "1.7.8" - -"@webassemblyjs/wasm-gen@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.8.tgz#7e8abf1545eae74ac6781d545c034af3cfd0c7d5" - integrity sha512-a7O/wE6eBeVKKUYgpMK7NOHmMADD85rSXLe3CqrWRDwWff5y3cSVbzpN6Qv3z6C4hdkpq9qyij1Ga1kemOZGvQ== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/helper-wasm-bytecode" "1.7.8" - "@webassemblyjs/ieee754" "1.7.8" - "@webassemblyjs/leb128" "1.7.8" - "@webassemblyjs/utf8" "1.7.8" - -"@webassemblyjs/wasm-opt@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.8.tgz#7ada6e211914728fce02ff0ff9c344edc6d41f26" - integrity sha512-3lbQ0PT81NHCdi1sR/7+SNpZadM4qYcTSr62nFFAA7e5lFwJr14M1Gi+A/Y3PgcDWOHYjsaNGPpPU0H03N6Blg== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/helper-buffer" "1.7.8" - "@webassemblyjs/wasm-gen" "1.7.8" - "@webassemblyjs/wasm-parser" "1.7.8" - -"@webassemblyjs/wasm-parser@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.8.tgz#dac47c291fb6a3e63529aecd647592cd34afbf94" - integrity sha512-rZ/zlhp9DHR/05zh1MbAjT2t624sjrPP/OkJCjXqzm7ynH+nIdNcn9Ixc+qzPMFXhIrk0rBoQ3to6sEIvHh9jQ== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/helper-api-error" "1.7.8" - "@webassemblyjs/helper-wasm-bytecode" "1.7.8" - "@webassemblyjs/ieee754" "1.7.8" - "@webassemblyjs/leb128" "1.7.8" - "@webassemblyjs/utf8" "1.7.8" - -"@webassemblyjs/wast-parser@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.8.tgz#f8aab9a450c048c1f9537695c89faeb92fabfba5" - integrity sha512-Q/zrvtUvzWuSiJMcSp90fi6gp2nraiHXjTV2VgAluVdVapM4gy1MQn7akja2p6eSBDQpKJPJ6P4TxRkghRS5dg== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/floating-point-hex-parser" "1.7.8" - "@webassemblyjs/helper-api-error" "1.7.8" - "@webassemblyjs/helper-code-frame" "1.7.8" - "@webassemblyjs/helper-fsm" "1.7.8" - "@xtuc/long" "4.2.1" - -"@webassemblyjs/wast-printer@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.8.tgz#e7e965782c1912f6a965f14a53ff43d8ad0403a5" - integrity sha512-GllIthRtwTxRDAURRNXscu7Napzmdf1jt1gpiZiK/QN4fH0lSGs3OTmvdfsMNP7tqI4B3ZtfaaWRlNIQug6Xyg== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/wast-parser" "1.7.8" - "@xtuc/long" "4.2.1" +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== -"@xtuc/long@4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" - integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@^1.3.0, accepts@~1.3.4, accepts@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= +accepts@^1.3.7, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" + mime-types "~2.1.24" + negotiator "0.6.2" -acorn-dynamic-import@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278" - integrity sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg== - dependencies: - acorn "^5.0.0" +acorn-dynamic-import@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" + integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== acorn-jsx@^3.0.0, acorn-jsx@^3.0.1: version "3.0.1" - resolved "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= dependencies: acorn "^3.0.4" +acorn-jsx@^5.0.1, acorn-jsx@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + acorn-object-spread@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/acorn-object-spread/-/acorn-object-spread-1.0.0.tgz#48ead0f4a8eb16995a17a0db9ffc6acaada4ba68" @@ -1093,38 +2389,61 @@ acorn-object-spread@^1.0.0: acorn@^3.0.4, acorn@^3.1.0, acorn@^3.3.0: version "3.3.0" - resolved "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= -acorn@^5.0.0, acorn@^5.5.0, acorn@^5.6.2: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== +acorn@^5.5.0: + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== + +acorn@^6.1.1, acorn@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + +acorn@^7.1.1, acorn@^7.2.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== -address@1.0.3, address@^1.0.1: +address@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" integrity sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg== +address@1.1.2, address@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-errors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59" - integrity sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk= + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== ajv-keywords@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= -ajv-keywords@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" - integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv@^5.2.3, ajv@^5.3.0: version "5.5.2" @@ -1136,12 +2455,12 @@ ajv@^5.2.3, ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.1.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.4.tgz#247d5274110db653706b550fcc2b797ca28cfc59" - integrity sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg== +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4: + version "6.12.5" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" + integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== dependencies: - fast-deep-equal "^2.0.1" + fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" @@ -1151,31 +2470,31 @@ alphanum-sort@^1.0.0: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= +ansi-align@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" + integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== dependencies: - string-width "^2.0.0" + string-width "^3.0.0" ansi-colors@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.1.0.tgz#dcfaacc90ef9187de413ec3ef8d5eb981a98808f" - integrity sha512-hTv1qPdi+sVEk3jYsdjox5nQI0C9HTbjKShbCdYLKb1LOfNbb7wsF4d7OEKIZoxIHx02tSp3m94jcPW2EfMjmA== + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" - integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== +ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-gray@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" - integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE= +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== dependencies: - ansi-wrap "0.1.0" + type-fest "^0.11.0" -ansi-html@0.0.7: +ansi-html@0.0.7, ansi-html@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= @@ -1190,35 +2509,40 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= -ansi-styles@^3.2.1: +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-wrap@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= - -any-promise@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= - -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +any-base@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" + integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== anymatch@^2.0.0: version "2.0.0" @@ -1228,33 +2552,35 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -apollo-link@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d" - integrity sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw== +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: - apollo-utilities "^1.0.0" - zen-observable-ts "^0.8.10" + normalize-path "^3.0.0" + picomatch "^2.0.4" -apollo-utilities@^1.0.0, apollo-utilities@^1.0.1: - version "1.0.21" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.21.tgz#cb8b5779fe275850b16046ff8373f4af2de90765" - integrity sha512-ZcxELlEl+sDCYBgEMdNXJAsZtRVm8wk4HIA58bMsqYfd1DSAJQEtZ93F0GZgYNAGy3QyaoBeZtbb0/01++G8JQ== - dependencies: - fast-json-stable-stringify "^2.0.0" - fclone "^1.0.11" +application-config-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.0.tgz#193c5f0a86541a4c66fba1e2dc38583362ea5e8f" + integrity sha1-GTxfCoZUGkxm+6Hi3DhYM2LqXo8= aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -archive-type@^3.0.0, archive-type@^3.0.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/archive-type/-/archive-type-3.2.0.tgz#9cd9c006957ebe95fadad5bd6098942a813737f6" - integrity sha1-nNnABpV+vpX62tW9YJiUKoE3N/Y= +arch@^2.1.0, arch@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf" + integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ== + +archive-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/archive-type/-/archive-type-4.0.0.tgz#f92e72233056dfc6969472749c267bdb046b1d70" + integrity sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA= dependencies: - file-type "^3.1.0" + file-type "^4.2.0" are-we-there-yet@~1.1.2: version "1.1.5" @@ -1264,42 +2590,27 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -argparse@^1.0.7: +argparse@^1.0.10, argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" -argparse@~0.1.15: - version "0.1.16" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-0.1.16.tgz#cfd01e0fbba3d6caed049fbd758d40f65196f57c" - integrity sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw= - dependencies: - underscore "~1.7.0" - underscore.string "~2.4.0" - -aria-query@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" - integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= - dependencies: - ast-types-flow "0.0.7" - commander "^2.11.0" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== dependencies: - arr-flatten "^1.0.1" + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= -arr-flatten@^1.0.1, arr-flatten@^1.1.0: +arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== @@ -1309,11 +2620,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= - array-filter@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" @@ -1330,22 +2636,23 @@ array-flatten@1.1.1: integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= array-flatten@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" - integrity sha1-Qmu52oQJDBg42BLIFQryCoMx4pY= + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-includes@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" - integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== dependencies: - define-properties "^1.1.2" - es-abstract "^1.7.0" + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" array-iterate@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.2.tgz#f66a57e84426f8097f4197fbb6c051b8e5cdf7d8" - integrity sha512-1hWSHTIlG/8wtYD+PPX5AOBtKWngpDFjrsrHgZpe+JdgNGz0udYu6ZIkAa/xuenIUEqFv7DvE2Yr60jxweJSrQ== + version "1.1.4" + resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.4.tgz#add1522e9dd9749bb41152d08b845bd08d6af8b7" + integrity sha512-sNRaPGh9nnmdC8Zf+pT3UqP8rnWj5Hf9wiFGsX3wUQ2yVSIhO2ShFwCoceIPpB41QF6i2OEmrHmCo36xronCVA== array-map@~0.0.0: version "0.0.0" @@ -1364,44 +2671,62 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" -array-uniq@^1.0.0, array-uniq@^1.0.1, array-uniq@^1.0.2: +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +array.prototype.flatmap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443" + integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== -arrify@^1.0.0, arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" asn1@~0.2.3: version "0.2.4" @@ -1416,10 +2741,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== dependencies: + object-assign "^4.1.1" util "0.10.3" assign-symbols@^1.0.0: @@ -1427,64 +2753,99 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-types-flow@0.0.7, ast-types-flow@^0.0.7: +ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= -async-each-series@^1.1.0: +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-cache@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/async-each-series/-/async-each-series-1.1.0.tgz#f42fd8155d38f21a5b8ea07c28e063ed1700b138" - integrity sha1-9C/YFV048hpbjqB8KOBj7RcAsTg= + resolved "https://registry.yarnpkg.com/async-cache/-/async-cache-1.1.0.tgz#4a9a5a89d065ec5d8e5254bd9ee96ba76c532b5a" + integrity sha1-SppaidBl7F2OUlS9nulrp2xTK1o= + dependencies: + lru-cache "^4.0.0" -async-each@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - integrity sha1-GdOGodntxufByF04iu28xW0zYC0= +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" - integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +async-retry-ng@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/async-retry-ng/-/async-retry-ng-2.0.1.tgz#f5285ec1c52654a2ba6a505d0c18b1eadfaebd41" + integrity sha512-iitlc2murdQ3/A5Re3CcplQBEf7vOmFrFQ6RFn3+/+zZUyIHYkZnnEziMSa6YIb2Bs2EJEPZWReTxjHqvQbDbw== -async@1.5.2, async@^1.5.2: +async@1.5.2: version "1.5.2" - resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.1.2: - version "2.6.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" - integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== dependencies: - lodash "^4.17.10" + lodash "^4.17.14" + +async@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.1.1: +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autolinker@~0.15.0: - version "0.15.3" - resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832" - integrity sha1-NCQX2PLzRhsUzwkIjV7fh5HcmDI= +auto-bind@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" + integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== + +autolinker@~0.28.0: + version "0.28.1" + resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" + integrity sha1-BlK0kYgYefB3XazgzcoyM5QqTkc= + dependencies: + gulp-header "^1.7.1" -autoprefixer@^8.6.5: - version "8.6.5" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-8.6.5.tgz#343f3d193ed568b3208e00117a1b96eb691d4ee9" - integrity sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig== +autoprefixer@^9.8.4: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== dependencies: - browserslist "^3.2.8" - caniuse-lite "^1.0.30000864" + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^6.0.23" - postcss-value-parser "^3.2.3" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" aws-sign2@~0.7.0: version "0.7.0" @@ -1492,16 +2853,31 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + version "1.10.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" + integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axobject-query@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.1.tgz#05dfa705ada8ad9db993fa6896f22d395b0b0a07" - integrity sha1-Bd+nBa2orZ25k/polvItOVsLCgc= +axe-core@^3.5.4: + version "3.5.5" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227" + integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== + +axe-core@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.0.2.tgz#c7cf7378378a51fcd272d3c09668002a4990b1cb" + integrity sha512-arU1h31OGFu+LPrOLGZ7nB45v940NMDMEJeNmbutu57P+UFDVnkZg3e+J1I2HJRZ9hT7gO8J91dn/PMrAiKakA== + +axios@^0.19.0, axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== dependencies: - ast-types-flow "0.0.7" + follow-redirects "1.5.10" + +axobject-query@^2.1.2, axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== babel-code-frame@6.26.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" @@ -1517,7 +2893,19 @@ babel-core@7.0.0-bridge.0: resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== -babel-eslint@^8.0.1, babel-eslint@^8.2.2: +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-eslint@^8.0.1: version "8.2.6" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.6.tgz#6270d0c73205628067c0f7ae1693a9e797acefd9" integrity sha512-aCdHjhzcILdP8c9lej7hvXKvQieyRt20SF102SIGyY4cUIiw6UaAtK4j2o3dXX74jEmy0TJ0CEhv4fTIM3SzcA== @@ -1529,413 +2917,130 @@ babel-eslint@^8.0.1, babel-eslint@^8.2.2: eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== +babel-loader@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" + integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" + find-cache-dir "^2.1.0" + loader-utils "^1.4.0" + mkdirp "^0.5.3" + pify "^4.0.1" + schema-utils "^2.6.5" -babel-helper-builder-react-jsx@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" - integrity sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA= - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - esutils "^2.0.2" +babel-plugin-add-module-exports@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.3.3.tgz#b9f7c0a93b989170dce07c3e97071a905a13fc29" + integrity sha512-hC37mm7aAdEb1n8SgggG8a1QuhZapsY/XLCi4ETSH6AVjXBCWEa50CXlOsAMPPWLnSx5Ns6mzz39uvuseh0Xjg== + optionalDependencies: + chokidar "^2.0.4" -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= +babel-plugin-apply-mdx-type-prop@^2.0.0-next.8: + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-2.0.0-next.8.tgz#f598b236ac1e5fd250d93bfbb179c771c9a9caf5" + integrity sha512-Mcr9VAMxfS3ltNm3SXnSgP+7uqxx2zYS4xya2t8KvnLGejzSNsODSgjpNHUyfLihoDnfYaeCH7VFewZRKaRT8g== dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" + "@babel/helper-plugin-utils" "7.10.4" + "@mdx-js/util" "^2.0.0-next.8" -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-loader@8.0.0-beta.4: - version "8.0.0-beta.4" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.0-beta.4.tgz#c3fab00696c385c70c04dbe486391f0eb996f345" - integrity sha512-fQMCj8jRpF/2CPuVnpFrOb8+8pRuquKqoC+tspy5RWBmL37/2qc104sLLLqpwWltrFzpYb30utPpKc3H6P3ETQ== - dependencies: - find-cache-dir "^1.0.0" - loader-utils "^1.0.2" - mkdirp "^0.5.1" - util.promisify "^1.0.0" + object.assign "^4.1.0" -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= +babel-plugin-emotion@^10.0.27: + version "10.0.33" + resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz#ce1155dcd1783bbb9286051efee53f4e2be63e03" + integrity sha512-bxZbTTGz0AJQDHm8k6Rf3RQJ8tX2scsfsRyKVgAbiUPUNIRtlK+7JxP+TAd1kRLABFxe0CFm2VdK4ePkoA9FxQ== dependencies: - babel-runtime "^6.22.0" - -babel-plugin-add-module-exports@^0.2.1: - version "0.2.1" - resolved "http://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz#9ae9a1f4a8dc67f0cdec4f4aeda1e43a5ff65e25" - integrity sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU= + "@babel/helper-module-imports" "^7.0.0" + "@emotion/hash" "0.8.0" + "@emotion/memoize" "0.7.4" + "@emotion/serialize" "^0.11.16" + babel-plugin-macros "^2.0.0" + babel-plugin-syntax-jsx "^6.18.0" + convert-source-map "^1.5.0" + escape-string-regexp "^1.0.5" + find-root "^1.1.0" + source-map "^0.5.7" -babel-plugin-check-es2015-constants@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" - integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= +babel-plugin-extract-export-names@^2.0.0-next.8: + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/babel-plugin-extract-export-names/-/babel-plugin-extract-export-names-2.0.0-next.8.tgz#96a4d7fc7b4dbaa67e10a0f9d156848b43b1f20f" + integrity sha512-W0DbJHAIlxSlb110h7uVq0aHmxPS985YSiEloTM7irvt8YkOFhxn4WkSAoOfTAJY/+xecRgwhMd8YTAZfoLq5A== dependencies: - babel-runtime "^6.22.0" + "@babel/helper-plugin-utils" "7.10.4" -babel-plugin-dynamic-import-node@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-1.2.0.tgz#f91631e703e0595e47d4beafbb088576c87fbeee" - integrity sha512-yeDwKaLgGdTpXL7RgGt5r6T4LmnTza/hUn5Ul8uZSGGMtEjYo13Nxai7SQaGCTEzUtg9Zq9qJn0EjEr7SeSlTQ== +babel-plugin-extract-import-names@^2.0.0-next.8: + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-2.0.0-next.8.tgz#80f77e92853e3074c6a1907342ba720de5861366" + integrity sha512-jdk6h7FaArjwMKqlF0hdozMwum5JDzLse99D5wWVbZWe0P7w/ghXDpE0VbooqJ/jyYwei5a6tHeTTU59Ds4WXg== dependencies: - babel-plugin-syntax-dynamic-import "^6.18.0" + "@babel/helper-plugin-utils" "7.10.4" -babel-plugin-macros@^2.4.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.4.2.tgz#21b1a2e82e2130403c5ff785cba6548e9b644b28" - integrity sha512-NBVpEWN4OQ/bHnu1fyDaAaTPAjnhXCEPqr1RwqxrU7b6tZ2hypp+zX4hlNfmVGfClD5c3Sl6Hfj5TJNF5VG5aA== +babel-plugin-lodash@3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz#4f6844358a1340baed182adbeffa8df9967bc196" + integrity sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg== dependencies: - cosmiconfig "^5.0.5" - resolve "^1.8.1" - -babel-plugin-remove-graphql-queries@^2.0.2-rc.3: - version "2.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-2.5.0.tgz#975e8bc9452133984e44eee99743b6599dafce25" - integrity sha512-6EKBqM/sK+FOBhkYcYc055ecHlW3y8VrSiKj5clUOZ2DNwKsYNVl6IgI22ZHlyKosR1Gy8CU4Nqhkol5jMe3Ag== + "@babel/helper-module-imports" "^7.0.0-beta.49" + "@babel/types" "^7.0.0-beta.49" + glob "^7.1.1" + lodash "^4.17.10" + require-package-name "^2.0.1" -babel-plugin-syntax-class-properties@^6.8.0: - version "6.13.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" - integrity sha1-1+sjt5oxf4VDlixQW4J8fWysJ94= +babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" -babel-plugin-syntax-dynamic-import@^6.18.0: - version "6.18.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" - integrity sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo= +babel-plugin-remove-export-keywords@^1.6.5: + version "1.6.18" + resolved "https://registry.yarnpkg.com/babel-plugin-remove-export-keywords/-/babel-plugin-remove-export-keywords-1.6.18.tgz#cabdedb571535efcc86465cf023d7620b71b95af" + integrity sha512-uX5ni5zoCqBzOMNDlgCaf4apVyqBlzDbOexG7qOhuoXUKHU5v1G0gmGaV5Wvs4cAOtyL1294h3rBEWbj9sMeCg== -babel-plugin-syntax-flow@^6.18.0, babel-plugin-syntax-flow@^6.8.0: - version "6.18.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" - integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0= +babel-plugin-remove-graphql-queries@^2.9.19: + version "2.9.19" + resolved "https://registry.yarnpkg.com/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-2.9.19.tgz#e53f7062be69d96d60c3ed261ffc9260fa777b36" + integrity sha512-s8Ar5NtJD5JXsRntMFKBMjIauWaGCOTTyZO4XdaktRA7JW1gzzN0p1uyiW9QaNenVbzV+RR4ceObCwlfH8e/xA== -babel-plugin-syntax-jsx@^6.8.0: +babel-plugin-syntax-jsx@^6.18.0: version "6.18.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= -babel-plugin-syntax-object-rest-spread@^6.8.0: - version "6.13.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" - integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= - -babel-plugin-syntax-trailing-function-commas@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= - -babel-plugin-transform-class-properties@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" - integrity sha1-anl2PqYdM9NvN7YRqp3vgagbRqw= - dependencies: - babel-helper-function-name "^6.24.1" - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-arrow-functions@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.8.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.8.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-for-of@^6.8.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-commonjs@^6.8.0: - version "6.26.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" - integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-object-super@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-template-literals@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es3-member-expression-literals@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz#733d3444f3ecc41bef8ed1a6a4e09657b8969ebb" - integrity sha1-cz00RPPsxBvvjtGmpOCWV7iWnrs= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es3-property-literals@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz#b2078d5842e22abf40f73e8cde9cd3711abd5758" - integrity sha1-sgeNWELiKr9A9z6M3pzTcRq9V1g= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-flow-strip-types@^6.8.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" - integrity sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988= - dependencies: - babel-plugin-syntax-flow "^6.18.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-object-rest-spread@^6.8.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" - integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.26.0" - -babel-plugin-transform-react-display-name@^6.8.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" - integrity sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-react-jsx@^6.8.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" - integrity sha1-hAoCjn30YN/DotKfDA2R9jduZqM= - dependencies: - babel-helper-builder-react-jsx "^6.24.1" - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-polyfill@^6.20.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" - integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= - dependencies: - babel-runtime "^6.26.0" - core-js "^2.5.0" - regenerator-runtime "^0.10.5" - -babel-preset-fbjs@^2.1.4: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-2.3.0.tgz#92ff81307c18b926895114f9828ae1674c097f80" - integrity sha512-ZOpAI1/bN0Y3J1ZAK9gRsFkHy9gGgJoDRUjtUCla/129LC7uViq9nIK22YdHfey8szohYoZY3f9L2lGOv0Edqw== - dependencies: - babel-plugin-check-es2015-constants "^6.8.0" - babel-plugin-syntax-class-properties "^6.8.0" - babel-plugin-syntax-flow "^6.8.0" - babel-plugin-syntax-jsx "^6.8.0" - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-plugin-syntax-trailing-function-commas "^6.8.0" - babel-plugin-transform-class-properties "^6.8.0" - babel-plugin-transform-es2015-arrow-functions "^6.8.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.8.0" - babel-plugin-transform-es2015-block-scoping "^6.8.0" - babel-plugin-transform-es2015-classes "^6.8.0" - babel-plugin-transform-es2015-computed-properties "^6.8.0" - babel-plugin-transform-es2015-destructuring "^6.8.0" - babel-plugin-transform-es2015-for-of "^6.8.0" - babel-plugin-transform-es2015-function-name "^6.8.0" - babel-plugin-transform-es2015-literals "^6.8.0" - babel-plugin-transform-es2015-modules-commonjs "^6.8.0" - babel-plugin-transform-es2015-object-super "^6.8.0" - babel-plugin-transform-es2015-parameters "^6.8.0" - babel-plugin-transform-es2015-shorthand-properties "^6.8.0" - babel-plugin-transform-es2015-spread "^6.8.0" - babel-plugin-transform-es2015-template-literals "^6.8.0" - babel-plugin-transform-es3-member-expression-literals "^6.8.0" - babel-plugin-transform-es3-property-literals "^6.8.0" - babel-plugin-transform-flow-strip-types "^6.8.0" - babel-plugin-transform-object-rest-spread "^6.8.0" - babel-plugin-transform-react-display-name "^6.8.0" - babel-plugin-transform-react-jsx "^6.8.0" - -babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: +babel-plugin-transform-react-remove-prop-types@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" + integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== + +babel-preset-gatsby@^0.5.10: + version "0.5.10" + resolved "https://registry.yarnpkg.com/babel-preset-gatsby/-/babel-preset-gatsby-0.5.10.tgz#3f55f542ece75c286e70d3783e69d90af3e73bbb" + integrity sha512-vusxdVDj3kF4lNjF5Fkm/S800WOaMLZYnRiLEEHK5c+9kqXX4wKoqE629i7TGgx9j9u/4ZwmuLJ4cXa0iqDnQQ== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.11.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-runtime" "^7.11.5" + "@babel/plugin-transform-spread" "^7.11.0" + "@babel/preset-env" "^7.11.5" + "@babel/preset-react" "^7.10.4" + "@babel/runtime" "^7.11.2" + babel-plugin-dynamic-import-node "^2.3.3" + babel-plugin-macros "^2.8.0" + babel-plugin-transform-react-remove-prop-types "^0.4.24" + gatsby-core-utils "^1.3.20" + gatsby-legacy-polyfills "^0.0.4" + +babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -1943,91 +3048,45 @@ babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.24.1, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - babylon@7.0.0-beta.44: version "7.0.0-beta.44" - resolved "http://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" integrity sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g== -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -babylon@^7.0.0-beta: - version "7.0.0-beta.47" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.47.tgz#6d1fa44f0abec41ab7c780481e62fd9aafbdea80" - integrity sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ== - -backo2@1.0.2: +backo2@1.0.2, backo2@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= bail@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3" - integrity sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg== + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-arraybuffer@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" + integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= + base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= -base64-js@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" - integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg= - base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== -base64id@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" - integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY= +base64id@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== base@^0.11.1: version "0.11.2" @@ -2054,11 +3113,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= - better-assert@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" @@ -2066,12 +3120,19 @@ better-assert@~1.0.0: dependencies: callsite "1.0.0" +better-opn@1.0.0, better-opn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-1.0.0.tgz#0454e4bb9115c6a9e4e5744417dd9c97fb9fce41" + integrity sha512-q3eO2se4sFbTERB1dFBDdjTiIIpRohMErpwBX21lhPvmgmQNNrcQj0zbWRhMREDesJvyod9kxBS3kOtdAvkB/A== + dependencies: + open "^6.4.0" + better-queue-memory@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/better-queue-memory/-/better-queue-memory-1.0.3.tgz#4e71fbb5f5976188656e0c5610da7b411af41493" - integrity sha512-QLFkfV+k/7e4L4FR7kqkXKtRi22kl68c/3AaBs0ArDSz0iiuAl0DjVlb6gM220jW7izLE5TRy7oXOd4Cxa0wog== + version "1.0.4" + resolved "https://registry.yarnpkg.com/better-queue-memory/-/better-queue-memory-1.0.4.tgz#f390d6b30bb3b36aaf2ce52b37a483e8a7a81a22" + integrity sha512-SWg5wFIShYffEmJpI6LgbL8/3Dqhku7xI1oEiy6FroP9DbcZlG0ZDjxvPdP9t7hTGW40IpIcC6zVoGT1oxjOuA== -better-queue@^3.8.6, better-queue@^3.8.7: +better-queue@^3.8.10: version "3.8.10" resolved "https://registry.yarnpkg.com/better-queue/-/better-queue-3.8.10.tgz#1c93b9ec4cb3d1b72eb91d0efcb84fc80e8c6835" integrity sha512-e3gwNZgDCnNWl0An0Tz6sUjKDV9m6aB+K9Xg//vYeo8+KiH8pWhLFxkawcXhm6FpM//GfD9IQv/kmvWCAVVpKA== @@ -2080,28 +3141,10 @@ better-queue@^3.8.6, better-queue@^3.8.7: node-eta "^0.9.0" uuid "^3.0.0" -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== - -bignumber.js@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8" - integrity sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg= - -bin-build@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/bin-build/-/bin-build-2.2.0.tgz#11f8dd61f70ffcfa2bdcaa5b46f5e8fedd4221cc" - integrity sha1-EfjdYfcP/Por3KpbRvXo/t1CIcw= - dependencies: - archive-type "^3.0.1" - decompress "^3.0.0" - download "^4.1.2" - exec-series "^1.0.0" - rimraf "^2.2.6" - tempfile "^1.0.0" - url-regex "^3.0.0" +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== bin-build@^3.0.0: version "3.0.0" @@ -2114,90 +3157,117 @@ bin-build@^3.0.0: p-map-series "^1.0.0" tempfile "^2.0.0" -bin-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bin-check/-/bin-check-2.0.0.tgz#86f8e6f4253893df60dc316957f5af02acb05930" - integrity sha1-hvjm9CU4k99g3DFpV/WvAqywWTA= +bin-check@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bin-check/-/bin-check-4.1.0.tgz#fc495970bdc88bb1d5a35fc17e65c4a149fc4a49" + integrity sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA== dependencies: - executable "^1.0.0" + execa "^0.7.0" + executable "^4.1.0" -bin-version-check@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-2.1.0.tgz#e4e5df290b9069f7d111324031efc13fdd11a5b0" - integrity sha1-5OXfKQuQaffRETJAMe/BP90RpbA= +bin-version-check@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-4.0.0.tgz#7d819c62496991f80d893e6e02a3032361608f71" + integrity sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ== dependencies: - bin-version "^1.0.0" - minimist "^1.1.0" - semver "^4.0.3" - semver-truncate "^1.0.0" + bin-version "^3.0.0" + semver "^5.6.0" + semver-truncate "^1.1.2" -bin-version@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-1.0.4.tgz#9eb498ee6fd76f7ab9a7c160436f89579435d78e" - integrity sha1-nrSY7m/Xb3q5p8FgQ2+JV5Q1144= +bin-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-3.1.0.tgz#5b09eb280752b1bd28f0c9db3f96f2f43b6c0839" + integrity sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ== dependencies: - find-versions "^1.0.0" + execa "^1.0.0" + find-versions "^3.0.0" -bin-wrapper@^3.0.0, bin-wrapper@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/bin-wrapper/-/bin-wrapper-3.0.2.tgz#67d3306262e4b1a5f2f88ee23464f6a655677aeb" - integrity sha1-Z9MwYmLksaXy+I7iNGT2plVneus= +bin-wrapper@^4.0.0, bin-wrapper@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bin-wrapper/-/bin-wrapper-4.1.0.tgz#99348f2cf85031e3ef7efce7e5300aeaae960605" + integrity sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q== dependencies: - bin-check "^2.0.0" - bin-version-check "^2.1.0" - download "^4.0.0" - each-async "^1.1.1" - lazy-req "^1.0.0" - os-filter-obj "^1.0.0" + bin-check "^4.1.0" + bin-version-check "^4.0.0" + download "^7.1.0" + import-lazy "^3.1.0" + os-filter-obj "^2.0.0" + pify "^4.0.1" binary-extensions@^1.0.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" - integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +binary-extensions@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" + integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" bl@^1.0.0: - version "1.2.2" - resolved "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" - integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" + integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== dependencies: readable-stream "^2.3.5" safe-buffer "^5.1.1" -blob@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" - integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE= +bl@^4.0.0, bl@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" + integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +blob@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" + integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== + +bluebird@^3.5.0, bluebird@^3.5.5, bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bluebird@^3.5.0, bluebird@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a" - integrity sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg== +bmp-js@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" + integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM= -bmp-js@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.0.3.tgz#64113e9c7cf1202b376ed607bf30626ebe57b18a" - integrity sha1-ZBE+nHzxICs3btYHvzBibr5XsYo= +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: + version "4.11.9" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" + integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== +bn.js@^5.1.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" + integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== -body-parser@1.18.3: - version "1.18.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" - integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= +body-parser@1.19.0, body-parser@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== dependencies: - bytes "3.0.0" + bytes "3.1.0" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" - http-errors "~1.6.3" - iconv-lite "0.4.23" + http-errors "1.7.2" + iconv-lite "0.4.24" on-finished "~2.3.0" - qs "6.5.2" - raw-body "2.3.3" - type-is "~1.6.16" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" bonjour@^3.5.0: version "3.5.0" @@ -2221,19 +3291,34 @@ bowser@^1.7.3: resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== -boxen@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== - dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" +boxen@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-3.2.0.tgz#fbdff0de93636ab4450886b6ff45b92d098f45eb" + integrity sha512-cU4J/+NodM3IHdSL2yN8bqYqnmlBTidDR4RC7nJs61ZmtGz8VZzM3HLQX0zY5mrSmPtR3xWwsq2jOUQqFZN8+A== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^2.4.2" + cli-boxes "^2.2.0" + string-width "^3.0.0" term-size "^1.2.0" + type-fest "^0.3.0" widest-line "^2.0.0" +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" + brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2242,16 +3327,7 @@ brace-expansion@^1.0.0, brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.0, braces@^2.3.1: +braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== @@ -2267,6 +3343,13 @@ braces@^2.3.0, braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -2274,7 +3357,7 @@ brorand@^1.0.1: browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" - resolved "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== dependencies: buffer-xor "^1.0.3" @@ -2303,26 +3386,28 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-rsa@^4.0.0: +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: version "4.0.1" - resolved "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= dependencies: bn.js "^4.1.0" randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" browserify-zlib@^0.2.0: version "0.2.0" @@ -2331,29 +3416,29 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^3.2.8: - version "3.2.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" - integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== - dependencies: - caniuse-lite "^1.0.30000844" - electron-to-chromium "^1.3.47" - -browserslist@^4.0.0, browserslist@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.2.0.tgz#3e5e5edf7fa9758ded0885cf88c1e4be753a591c" - integrity sha512-Berls1CHL7qfQz8Lct6QxYA5d2Tvt4doDWHcjvAISybpd+EKZVppNtXgXhaN6SdrPKo7YLTSZuYBs5cYrSWN8w== - dependencies: - caniuse-lite "^1.0.30000889" - electron-to-chromium "^1.3.73" - node-releases "^1.0.0-alpha.12" - -bser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" - integrity sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk= - dependencies: - node-int64 "^0.4.0" +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.12.2, browserslist@^4.8.5: + version "4.16.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== + dependencies: + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" + escalade "^3.1.1" + node-releases "^1.1.71" + +buble-jsx-only@^0.19.8: + version "0.19.8" + resolved "https://registry.yarnpkg.com/buble-jsx-only/-/buble-jsx-only-0.19.8.tgz#6e3524aa0f1c523de32496ac9aceb9cc2b493867" + integrity sha512-7AW19pf7PrKFnGTEDzs6u9+JZqQwM1VnLS19OlqYDhXomtFFknnoQJAPHeg84RMFWAvOhYrG7harizJNwUKJsA== + dependencies: + acorn "^6.1.1" + acorn-dynamic-import "^4.0.0" + acorn-jsx "^5.0.1" + chalk "^2.4.2" + magic-string "^0.25.3" + minimist "^1.2.0" + regexpu-core "^4.5.4" buble@^0.15.2: version "0.15.2" @@ -2406,91 +3491,72 @@ buffer-indexof@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== -buffer-to-vinyl@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz#00f15faee3ab7a1dda2cde6d9121bffdd07b2262" - integrity sha1-APFfruOreh3aLN5tkSG//dB7ImI= - dependencies: - file-type "^3.1.0" - readable-stream "^2.0.2" - uuid "^2.0.1" - vinyl "^1.0.0" - buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= -buffer@^3.0.1: - version "3.6.0" - resolved "http://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb" - integrity sha1-pyyTb3e5a/UvX357RnGAYoVR3vs= +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: - base64-js "0.0.8" + base64-js "^1.0.2" ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^4.3.0: - version "4.9.1" - resolved "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= +buffer@^5.2.0, buffer@^5.2.1, buffer@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" - isarray "^1.0.0" -builtin-modules@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= +builtin-modules@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" + integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -cacache@^10.0.4: - version "10.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" - integrity sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA== - dependencies: - bluebird "^3.5.1" - chownr "^1.0.1" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.1" - mississippi "^2.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.2" - ssri "^5.2.4" - unique-filename "^1.1.0" - y18n "^4.0.0" +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cacache@^11.0.2: - version "11.2.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.2.0.tgz#617bdc0b02844af56310e411c0878941d5739965" - integrity sha512-IFWl6lfK6wSeYCHUXh+N1lY72UDrpyrYQJNIVQf48paDuWbv5RbAtJYf/4gUQFObTCHZwdZ5sI8Iw7nqwP6nlQ== +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== dependencies: - bluebird "^3.5.1" - chownr "^1.0.1" - figgy-pudding "^3.1.0" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.3" + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" mississippi "^3.0.0" mkdirp "^0.5.1" move-concurrently "^1.0.1" promise-inflight "^1.0.1" - rimraf "^2.6.2" - ssri "^6.0.0" - unique-filename "^1.1.0" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" y18n "^4.0.0" cache-base@^1.0.1: @@ -2508,26 +3574,83 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -cache-manager-fs-hash@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/cache-manager-fs-hash/-/cache-manager-fs-hash-0.0.6.tgz#fccc5a6b579080cbe2186697e51b5b8ff8ca9fd0" - integrity sha512-p1nmcCQH4/jyKqEqUqPSDDcCo0PjFdv56OvtSdUrSIB7s8rAfwETLZ0CHXWdAPyg0QaER/deTvl1dCXyjZ5xAA== +cache-manager-fs-hash@^0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/cache-manager-fs-hash/-/cache-manager-fs-hash-0.0.9.tgz#a65bb7ca2c9f9f9cf7035945bbfab536c5aab340" + integrity sha512-G0RUUSMZADiMx/0tHjPa+uzJyjtVB/Xt9yuFm6g/rBpm0p/IMr4atUWX2G2f1yGCPmDnyUcFz4RlSpgNRgvldg== dependencies: - es6-promisify "^6.0.0" lockfile "^1.0.4" -cache-manager@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-2.9.0.tgz#5e1f6317ca1a25e40ddf365a7162757af152353e" - integrity sha1-Xh9jF8oaJeQN3zZacWJ1evFSNT4= +cache-manager@^2.11.1: + version "2.11.1" + resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-2.11.1.tgz#212e8c3db15288af653b029a1d9fe12f1fd9df61" + integrity sha512-XhUuc9eYwkzpK89iNewFwtvcDYMUsvtwzHeyEOPJna/WsVsXcrzsA1ft2M0QqPNunEzLhNCYPo05tEfG+YuNow== dependencies: async "1.5.2" + lodash.clonedeep "4.5.0" lru-cache "4.0.0" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= +cacheable-lookup@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz#87be64a18b925234875e10a9bb1ebca4adce6b38" + integrity sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg== + dependencies: + "@types/keyv" "^3.1.1" + keyv "^4.0.0" + +cacheable-request@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" + integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= + dependencies: + clone-response "1.0.2" + get-stream "3.0.0" + http-cache-semantics "3.8.1" + keyv "3.0.0" + lowercase-keys "1.0.0" + normalize-url "2.0.1" + responselike "1.0.2" + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" caller-path@^0.1.0: version "0.1.0" @@ -2536,6 +3659,13 @@ caller-path@^0.1.0: dependencies: callsites "^0.2.0" +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + callsite@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" @@ -2546,6 +3676,29 @@ callsites@^0.2.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" + integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== + dependencies: + pascal-case "^3.1.1" + tslib "^1.10.0" + +camelcase-css@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -2559,15 +3712,10 @@ camelcase@^2.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - -camelcase@^4.0.0, camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== caniuse-api@^3.0.0: version "3.0.0" @@ -2579,32 +3727,22 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000864, caniuse-lite@^1.0.30000889: - version "1.0.30000890" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000890.tgz#86a18ffcc65d79ec6a437e985761b8bf1c4efeaf" - integrity sha512-4NI3s4Y6ROm+SgZN5sLUG4k7nVWQnedis3c/RWkynV5G6cHSY7+a8fwFyn2yoBDE3E6VswhTNNwR3PvzGqlTkg== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219: + version "1.0.30001230" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" + integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== -capture-stack-trace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" - integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== +case@^1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" + integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -caw@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/caw/-/caw-1.2.0.tgz#ffb226fe7efc547288dc62ee3e97073c212d1034" - integrity sha1-/7Im/n78VHKI3GLuPpcHPCEtEDQ= - dependencies: - get-proxy "^1.0.1" - is-obj "^1.0.0" - object-assign "^3.0.0" - tunnel-agent "^0.4.0" - -caw@^2.0.0: +caw@^2.0.0, caw@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" integrity sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA== @@ -2614,14 +3752,14 @@ caw@^2.0.0: tunnel-agent "^0.6.0" url-to-options "^1.0.1" -ccount@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" - integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw== +ccount@^1.0.0, ccount@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" + integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== -chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" - resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= dependencies: ansi-styles "^2.2.1" @@ -2630,109 +3768,123 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== +chalk@^2.0, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + character-entities-html4@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.2.tgz#c44fdde3ce66b52e8d321d6c1bf46101f0150610" - integrity sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw== + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" + integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== character-entities-legacy@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c" - integrity sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA== + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== character-entities@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363" - integrity sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== character-reference-invalid@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed" - integrity sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ== + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= -charenc@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -cheerio@^1.0.0-rc.2: - version "1.0.0-rc.2" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" - integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs= +cheerio@^1.0.0-rc.2, cheerio@^1.0.0-rc.3: + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" + integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== dependencies: css-select "~1.2.0" - dom-serializer "~0.1.0" + dom-serializer "~0.1.1" entities "~1.1.1" htmlparser2 "^3.9.1" lodash "^4.15.0" parse5 "^3.0.1" -chokidar@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg= - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -chokidar@^2.0.0, chokidar@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" - integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ== +chokidar@^2.0.4, chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== dependencies: anymatch "^2.0.0" - async-each "^1.0.0" - braces "^2.3.0" + async-each "^1.0.1" + braces "^2.3.2" glob-parent "^3.1.0" - inherits "^2.0.1" + inherits "^2.0.3" is-binary-path "^1.0.0" is-glob "^4.0.0" - lodash.debounce "^4.0.8" - normalize-path "^2.1.1" + normalize-path "^3.0.0" path-is-absolute "^1.0.0" - readdirp "^2.0.0" - upath "^1.0.5" + readdirp "^2.2.1" + upath "^1.1.1" optionalDependencies: - fsevents "^1.2.2" + fsevents "^1.2.7" + +chokidar@^3.4.1, chokidar@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" + integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.4.0" + optionalDependencies: + fsevents "~2.1.2" -chownr@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chrome-trace-event@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" - integrity sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A== +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== dependencies: tslib "^1.9.0" -ci-info@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" - integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== +ci-info@2.0.0, ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -2747,13 +3899,6 @@ circular-json@^0.3.1: resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== -clap@^1.0.9: - version "1.2.3" - resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" - integrity sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA== - dependencies: - chalk "^1.1.3" - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -2764,10 +3909,15 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-boxes@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== cli-cursor@^2.1.0: version "2.1.0" @@ -2776,75 +3926,92 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -clipboard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.1.tgz#a12481e1c13d8a50f5f036b0560fe5d16d74e46a" - integrity sha512-7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: - good-listener "^1.2.2" - select "^1.1.2" - tiny-emitter "^2.0.0" + restore-cursor "^3.1.0" -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" +cli-spinners@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" + integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== +cli-table3@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" + integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw== dependencies: + object-assign "^4.1.0" string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" + optionalDependencies: + colors "^1.1.2" -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - integrity sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8= +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -clone@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -co@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78" - integrity sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g= +clipboardy@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290" + integrity sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ== + dependencies: + arch "^2.1.1" + execa "^1.0.0" + is-wsl "^2.1.1" + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +clone-response@1.0.2, clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -coa@~1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" - integrity sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0= - dependencies: - q "^1.1.2" - -coa@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.1.tgz#f3f8b0b15073e35d70263fb1042cb2c023db38af" - integrity sha512-5wfTTO8E2/ja4jFSxePXlG5nRu5bBtL/r1HCIpJW/lzT6yDtKl0u0Z4o/Vpz32IpKmBn7HerheEZQgA9N2DarQ== +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" q "^1.1.2" code-point-at@^1.0.0: @@ -2853,9 +4020,9 @@ code-point-at@^1.0.0: integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= collapse-white-space@^1.0.0, collapse-white-space@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091" - integrity sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw== + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== collection-visit@^1.0.0: version "1.0.0" @@ -2872,12 +4039,19 @@ color-convert@^1.9.0, color-convert@^1.9.1: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -2890,73 +4064,63 @@ color-string@^1.5.2: color-name "^1.0.0" simple-swizzle "^0.2.2" -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +color-string@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa" + integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" color@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.0.tgz#d8e9fb096732875774c84bf922815df0308d0ffc" - integrity sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== dependencies: color-convert "^1.9.1" color-string "^1.5.2" -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= - -combined-stream@1.0.6: - version "1.0.6" - resolved "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" - integrity sha1-cj599ugBrFYTETp+RFqbactjKBg= +color@^4.2.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== dependencies: - delayed-stream "~1.0.0" + color-convert "^2.0.1" + color-string "^1.9.0" -combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== +colorette@^1.2.1, colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" comma-separated-tokens@^1.0.0, comma-separated-tokens@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db" - integrity sha512-Cg90/fcK93n0ecgYTAz1jaA3zvnQ0ExlmKY1rdbyHqAx6BHxwoJc+J7HDu0iuQ7ixEs1qaa+WyQ6oeuBpYP1iA== - dependencies: - trim "0.0.1" - -command-exists@^1.2.2: - version "1.2.7" - resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.7.tgz#16828f0c3ff2b0c58805861ef211b64fc15692a8" - integrity sha512-doWDvhXCcW5LK0cIUWrOQ8oMFXJv3lEQCkJpGVjM8v9SV0uhqYXB943538tEA2CiaWqSyuYUGAm5ezDwEx9xlw== - -commander@^2.11.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" - integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== -commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== +command-exists@^1.2.4: + version "1.2.9" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" + integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== -commander@~2.8.1: - version "2.8.1" - resolved "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" - integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ= - dependencies: - graceful-readlink ">= 1.0.0" +commander@^2.20.0, commander@^2.20.3, commander@^2.8.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -common-tags@^1.4.0: +common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== @@ -2971,11 +4135,16 @@ component-bind@1.0.0: resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= -component-emitter@1.2.1, component-emitter@^1.2.1: +component-emitter@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= +component-emitter@^1.2.1, component-emitter@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + component-inherit@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" @@ -2991,23 +4160,23 @@ component-xor@0.0.3: resolved "https://registry.yarnpkg.com/component-xor/-/component-xor-0.0.3.tgz#68ae7f1c40b78d843d69f2a829cfb31d6afbf051" integrity sha1-aK5/HEC3jYQ9afKoKc+zHWr78FE= -compressible@~2.0.14: - version "2.0.15" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.15.tgz#857a9ab0a7e5a07d8d837ed43fe2defff64fe212" - integrity sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw== +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: - mime-db ">= 1.36.0 < 2" + mime-db ">= 1.43.0 < 2" -compression@^1.5.2, compression@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db" - integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg== +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== dependencies: accepts "~1.3.5" bytes "3.0.0" - compressible "~2.0.14" + compressible "~2.0.16" debug "2.6.9" - on-headers "~1.0.1" + on-headers "~1.0.2" safe-buffer "5.1.2" vary "~1.1.2" @@ -3016,7 +4185,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.4.6, concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.6.0: +concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -3026,6 +4195,28 @@ concat-stream@^1.4.6, concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@ readable-stream "^2.2.2" typedarray "^0.0.6" +concat-with-sourcemaps@*: + version "1.1.0" + resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" + integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== + dependencies: + source-map "^0.6.1" + +concurrently@^5.0.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b" + integrity sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ== + dependencies: + chalk "^2.4.2" + date-fns "^2.0.1" + lodash "^4.17.15" + read-pkg "^4.0.1" + rxjs "^6.5.2" + spawn-command "^0.0.2-1" + supports-color "^6.1.0" + tree-kill "^1.2.2" + yargs "^13.3.0" + config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -3034,34 +4225,32 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" -configstore@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" - integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== dependencies: - dot-prop "^4.1.0" + dot-prop "^5.2.0" graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" -confusing-browser-globals@2.0.0-next.66cc7a90: - version "2.0.0-next.66cc7a90" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-2.0.0-next.66cc7a90.tgz#438e83bb16602abf1cd5c5aa9d6e4d61d924743e" - integrity sha512-pVhpqs/CvjFgJm6pIamnHI7xxutxywZr4WaG7/g3+1uTrJldBS+jKe/4NvGv0etgAAY6z2+iaogt4pkXM+6wag== +confusing-browser-globals@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" + integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== -connect-history-api-fallback@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" - integrity sha1-sGhzk0vF40T+9hGhlqb6rgruAVo= +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= - dependencies: - date-now "^0.1.4" + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" @@ -3083,25 +4272,45 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= -content-disposition@0.5.2, content-disposition@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= +content-disposition@0.5.3, content-disposition@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" content-type@^1.0.4, content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-hrtime@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-hrtime/-/convert-hrtime-2.0.0.tgz#19bfb2c9162f9e11c2f04c2c79de2b7e8095c627" - integrity sha1-Gb+yyRYvnhHC8Ewsed4rfoCVxic= +contentful-management@^5.26.3: + version "5.28.0" + resolved "https://registry.yarnpkg.com/contentful-management/-/contentful-management-5.28.0.tgz#f3b58d60400d66e42439bbd9085cecb0e486f0bb" + integrity sha512-o+qihN3zrD6+/BT/e8n26jl/zQvmV6+9S6NY5QDmzM+IaiSeCk6yvPMq74s+IZT9mOS54igl6qFTbeIpdJ9FDA== + dependencies: + axios "^0.19.0" + contentful-sdk-core "^6.4.0" + lodash "^4.17.11" + type-fest "0.15.1" -convert-source-map@^1.1.0, convert-source-map@^1.1.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" - integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== +contentful-sdk-core@^6.4.0: + version "6.4.5" + resolved "https://registry.yarnpkg.com/contentful-sdk-core/-/contentful-sdk-core-6.4.5.tgz#e73f4c5426f354608543fc73e46c17c6730180e9" + integrity sha512-rygNuiwbG6UKrJg6EDlaKewayTeLWrjA2wJwVmq7rV/DYo0cic6t28y0EMhRQ4pgJDV5HyUQFoFeBm2lwLfG2Q== + dependencies: + lodash "^4.17.10" + qs "^6.5.2" + +convert-hrtime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/convert-hrtime/-/convert-hrtime-3.0.0.tgz#62c7593f5809ca10be8da858a6d2f702bcda00aa" + integrity sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA== + +convert-source-map@^1.5.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" @@ -3110,10 +4319,15 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +cookie@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== copy-concurrently@^1.0.0: version "1.0.5" @@ -3132,70 +4346,91 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -copyfiles@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-1.2.0.tgz#a8da3ac41aa2220ae29bd3c58b6984294f2c593c" - integrity sha1-qNo6xBqiIgrim9PFi2mEKU8sWTw= +copyfiles@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.3.0.tgz#1c26ebbe3d46bba2d309a3fd8e3aaccf53af8c76" + integrity sha512-73v7KFuDFJ/ofkQjZBMjMBFWGgkS76DzXvBMUh7djsMOE5EELWtAO/hRB6Wr5Vj5Zg+YozvoHemv0vnXpqxmOQ== dependencies: glob "^7.0.5" - ltcdr "^2.2.1" minimatch "^3.0.3" - mkdirp "^0.5.1" + mkdirp "^1.0.4" noms "0.0.0" through2 "^2.0.1" + yargs "^15.3.1" + +core-js-compat@^3.6.2, core-js-compat@^3.6.5: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" + integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== + dependencies: + browserslist "^4.8.5" + semver "7.0.0" + +core-js-pure@^3.0.0: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" + integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.7: - version "2.5.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" - integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== +core-js@^2.4.0, core-js@^2.4.1: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-js@^3.6.5: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" + integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc" - integrity sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ== +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== dependencies: - is-directory "^0.3.1" - js-yaml "^3.9.0" - parse-json "^4.0.0" - require-from-string "^2.0.1" + object-assign "^4" + vary "^1" -cosmiconfig@^5.0.0, cosmiconfig@^5.0.5: - version "5.0.6" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.6.tgz#dca6cf680a0bd03589aff684700858c81abeeb39" - integrity sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ== +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== dependencies: + import-fresh "^2.0.0" is-directory "^0.3.1" - js-yaml "^3.9.0" + js-yaml "^3.13.1" parse-json "^4.0.0" -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" -create-error-class@^3.0.0, create-error-class@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== dependencies: - capture-stack-trace "^1.0.0" + bn.js "^4.1.0" + elliptic "^6.5.3" -create-hash@^1.1.0, create-hash@^1.1.2: +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" - resolved "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== dependencies: cipher-base "^1.0.1" @@ -3204,9 +4439,9 @@ create-hash@^1.1.0, create-hash@^1.1.2: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" - resolved "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== dependencies: cipher-base "^1.0.3" @@ -3216,13 +4451,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-context@^0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3" - integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag== +create-react-context@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" + integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== dependencies: - fbjs "^0.8.0" gud "^1.0.0" + warning "^4.0.3" cross-fetch@2.2.2: version "2.2.2" @@ -3232,6 +4467,13 @@ cross-fetch@2.2.2: node-fetch "2.1.2" whatwg-fetch "2.0.4" +cross-fetch@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" + integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== + dependencies: + node-fetch "2.6.1" + cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -3252,10 +4494,14 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -crypt@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" crypto-browserify@^3.11.0: version "3.12.0" @@ -3274,14 +4520,14 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-random-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" - integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" - resolved "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= css-declaration-sorter@^4.0.1: @@ -3300,16 +4546,16 @@ css-in-js-utils@^2.0.0: hyphenate-style-name "^1.0.2" isobject "^3.0.1" -css-loader@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.0.tgz#9f46aaa5ca41dbe31860e3b62b8e23c42916bf56" - integrity sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA== +css-loader@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.1.tgz#6885bb5233b35ec47b006057da01cc640b6b79fe" + integrity sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw== dependencies: babel-code-frame "^6.26.0" css-selector-tokenizer "^0.7.0" icss-utils "^2.1.0" loader-utils "^1.0.2" - lodash.camelcase "^4.3.0" + lodash "^4.17.11" postcss "^6.0.23" postcss-modules-extract-imports "^1.2.0" postcss-modules-local-by-default "^1.2.0" @@ -3318,10 +4564,10 @@ css-loader@^1.0.0: postcss-value-parser "^3.3.0" source-list-map "^2.0.0" -css-select-base-adapter@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.0.tgz#0102b3d14630df86c3eb9fa9f5456270106cf990" - integrity sha1-AQKz0UYw34bD65+p9UVicBBs+ZA= +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== css-select@^1.1.0, css-select@~1.2.0: version "1.2.0" @@ -3334,99 +4580,98 @@ css-select@^1.1.0, css-select@~1.2.0: nth-check "~1.0.1" css-select@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.0.0.tgz#7aa2921392114831f68db175c0b6a555df74bbd5" - integrity sha512-MGhoq1S9EyPgZIGnts8Yz5WwUOyHmPMdlqeifsYs/xFX7AAm3hY0RJe1dqVlXtYPI66Nsk39R/sa5/ree6L2qg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== dependencies: boolbase "^1.0.0" - css-what "2.1" + css-what "^3.2.1" domutils "^1.7.0" - nth-check "^1.0.1" + nth-check "^1.0.2" css-selector-parser@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.3.0.tgz#5f1ad43e2d8eefbfdc304fcd39a521664943e3eb" - integrity sha1-XxrUPi2O77/cME/NOaUhZklD4+s= + version "1.4.1" + resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.4.1.tgz#03f9cb8a81c3e5ab2c51684557d5aaf6d2569759" + integrity sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g== css-selector-tokenizer@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" - integrity sha1-5piEdK6MlTR3v15+/s/OzNnPTIY= + version "0.7.3" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" + integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== dependencies: - cssesc "^0.1.0" - fastparse "^1.1.1" - regexpu-core "^1.0.0" + cssesc "^3.0.0" + fastparse "^1.1.2" -css-tree@1.0.0-alpha.28: - version "1.0.0-alpha.28" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.28.tgz#8e8968190d886c9477bc8d61e96f61af3f7ffa7f" - integrity sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w== +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== dependencies: - mdn-data "~1.1.0" - source-map "^0.5.3" + mdn-data "2.0.4" + source-map "^0.6.1" -css-tree@1.0.0-alpha.29: - version "1.0.0-alpha.29" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39" - integrity sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg== +css-tree@1.0.0-alpha.39: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== dependencies: - mdn-data "~1.1.0" - source-map "^0.5.3" + mdn-data "2.0.6" + source-map "^0.6.1" -css-unit-converter@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" - integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css-url-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" - integrity sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w= +css-what@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" + integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg== -css-what@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" - integrity sha1-lGfQMsOM+u+58teVASUwYvh/ob0= +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssesc@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" - integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= +cssfilter@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" + integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4= -cssnano-preset-default@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.2.tgz#1de3f27e73b7f0fbf87c1d7fd7a63ae980ac3774" - integrity sha512-zO9PeP84l1E4kbrdyF7NSLtA/JrJY1paX5FHy5+w/ziIXO2kDqDMfJ/mosXkaHHSa3RPiIY3eB6aEgwx3IiGqA== +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== dependencies: css-declaration-sorter "^4.0.1" cssnano-util-raw-cache "^4.0.1" postcss "^7.0.0" - postcss-calc "^6.0.2" - postcss-colormin "^4.0.2" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.1" + postcss-discard-comments "^4.0.2" postcss-discard-duplicates "^4.0.2" postcss-discard-empty "^4.0.1" postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.6" - postcss-merge-rules "^4.0.2" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.1" - postcss-minify-params "^4.0.1" - postcss-minify-selectors "^4.0.1" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.1" - postcss-normalize-positions "^4.0.1" - postcss-normalize-repeat-style "^4.0.1" - postcss-normalize-string "^4.0.1" - postcss-normalize-timing-functions "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" postcss-normalize-unicode "^4.0.1" postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.1" - postcss-ordered-values "^4.1.1" - postcss-reduce-initial "^4.0.2" - postcss-reduce-transforms "^4.0.1" - postcss-svgo "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" postcss-unique-selectors "^4.0.1" cssnano-util-get-arguments@^4.0.0: @@ -3451,35 +4696,32 @@ cssnano-util-same-parent@^4.0.0: resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== -cssnano@^4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.4.tgz#55b71e3d8f5451dd3edc7955673415c98795788f" - integrity sha512-wP0wbOM9oqsek14CiNRYrK9N3w3jgadtGZKHXysgC/OMVpy0KZgWVPdNqODSZbz7txO9Gekr9taOfcCgL0pOOw== +cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== dependencies: cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.2" + cssnano-preset-default "^4.0.7" is-resolvable "^1.0.0" postcss "^7.0.0" -csso@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.1.tgz#7b9eb8be61628973c1b261e169d2f024008e758b" - integrity sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg== +csso@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" + integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== dependencies: - css-tree "1.0.0-alpha.29" + css-tree "1.0.0-alpha.39" -csso@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" - integrity sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U= - dependencies: - clap "^1.0.9" - source-map "^0.5.3" +csstype@^2.5.7: + version "2.6.13" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" + integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== -csstype@^2.2.0: - version "2.5.7" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff" - integrity sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw== +csstype@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" + integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== currently-unhandled@^0.4.1: version "0.4.1" @@ -3488,24 +4730,15 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" -cwebp-bin@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cwebp-bin/-/cwebp-bin-4.0.0.tgz#ee2b7f6333d3426fb52bb405fa6f2ec8b62894f4" - integrity sha1-7it/YzPTQm+1K7QF+m8uyLYolPQ= - dependencies: - bin-build "^2.2.0" - bin-wrapper "^3.0.1" - logalot "^2.0.0" - -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" - integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -damerau-levenshtein@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" - integrity sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ= +damerau-levenshtein@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" + integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== dashdash@^1.12.0: version "1.14.1" @@ -3514,22 +4747,12 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= - -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= - -death@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" - integrity sha1-AaqcQB7dknUFFEcLgmY5DGbGcxg= +date-fns@^2.0.1, date-fns@^2.14.0, date-fns@^2.8.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" + integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== -debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: +debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -3543,25 +4766,32 @@ debug@=3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@^3.1.0: +debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== dependencies: ms "^2.1.1" -decamelize@^1.1.1, decamelize@^1.1.2: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + dependencies: + ms "2.1.2" + +debug@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= -decamelize@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-2.0.0.tgz#656d7bbc8094c4c788ea53c5840908c9c7d063c7" - integrity sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg== - dependencies: - xregexp "4.0.0" - decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -3574,17 +4804,19 @@ decompress-response@^3.2.0, decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -decompress-tar@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-3.1.0.tgz#217c789f9b94450efaadc5c5e537978fc333c466" - integrity sha1-IXx4n5uURQ76rcXF5TeXj8MzxGY= +decompress-response@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f" + integrity sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw== + dependencies: + mimic-response "^2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== dependencies: - is-tar "^1.0.0" - object-assign "^2.0.0" - strip-dirs "^1.0.0" - tar-stream "^1.1.1" - through2 "^0.6.1" - vinyl "^0.4.3" + mimic-response "^3.1.0" decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: version "4.1.1" @@ -3595,19 +4827,6 @@ decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: is-stream "^1.1.0" tar-stream "^1.5.2" -decompress-tarbz2@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz#8b23935681355f9f189d87256a0f8bdd96d9666d" - integrity sha1-iyOTVoE1X58YnYclag+L3ZbZZm0= - dependencies: - is-bzip2 "^1.0.0" - object-assign "^2.0.0" - seek-bzip "^1.0.3" - strip-dirs "^1.0.0" - tar-stream "^1.1.1" - through2 "^0.6.1" - vinyl "^0.4.3" - decompress-tarbz2@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" @@ -3619,18 +4838,6 @@ decompress-tarbz2@^4.0.0: seek-bzip "^1.0.5" unbzip2-stream "^1.0.9" -decompress-targz@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-3.1.0.tgz#b2c13df98166268991b715d6447f642e9696f5a0" - integrity sha1-ssE9+YFmJomRtxXWRH9kLpaW9aA= - dependencies: - is-gzip "^1.0.0" - object-assign "^2.0.0" - strip-dirs "^1.0.0" - tar-stream "^1.1.1" - through2 "^0.6.1" - vinyl "^0.4.3" - decompress-targz@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" @@ -3640,19 +4847,6 @@ decompress-targz@^4.0.0: file-type "^5.2.0" is-stream "^1.1.0" -decompress-unzip@^3.0.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-3.4.0.tgz#61475b4152066bbe3fee12f9d629d15fe6478eeb" - integrity sha1-YUdbQVIGa74/7hL51inRX+ZHjus= - dependencies: - is-zip "^1.0.0" - read-all-stream "^3.0.0" - stat-mode "^0.2.0" - strip-dirs "^1.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" - yauzl "^2.2.1" - decompress-unzip@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" @@ -3663,25 +4857,10 @@ decompress-unzip@^4.0.1: pify "^2.3.0" yauzl "^2.4.2" -decompress@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-3.0.0.tgz#af1dd50d06e3bfc432461d37de11b38c0d991bed" - integrity sha1-rx3VDQbjv8QyRh033hGzjA2ZG+0= - dependencies: - buffer-to-vinyl "^1.0.0" - concat-stream "^1.4.6" - decompress-tar "^3.0.0" - decompress-tarbz2 "^3.0.0" - decompress-targz "^3.0.0" - decompress-unzip "^3.0.0" - stream-combiner2 "^1.1.1" - vinyl-assign "^1.0.1" - vinyl-fs "^2.2.0" - -decompress@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" - integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50= +decompress@^4.0.0, decompress@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118" + integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== dependencies: decompress-tar "^4.0.0" decompress-tarbz2 "^4.0.0" @@ -3692,10 +4871,17 @@ decompress@^4.0.0: pify "^2.3.0" strip-dirs "^2.0.0" -deep-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= +deep-equal@^1.0.1, deep-equal@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" deep-extend@^0.6.0: version "0.6.0" @@ -3707,20 +4893,35 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@^2.0.1: +deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== -default-gateway@^2.6.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f" - integrity sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ== +deepmerge@^4.0.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +default-gateway@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== dependencies: - execa "^0.10.0" + execa "^1.0.0" ip-regex "^2.1.0" -define-properties@^1.1.1, define-properties@^1.1.2: +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +defer-to-connect@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" + integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== + +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -3749,41 +4950,38 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -del@^2.0.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" - integrity sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag= - dependencies: - globby "^5.0.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - rimraf "^2.2.8" - -del@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" - integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU= +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== dependencies: + "@types/glob" "^7.1.1" globby "^6.1.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - p-map "^1.1.1" - pify "^3.0.0" - rimraf "^2.2.8" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + +del@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" + integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== + dependencies: + globby "^10.0.1" + graceful-fs "^4.2.2" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.1" + p-map "^3.0.0" + rimraf "^3.0.0" + slash "^3.0.0" delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegate@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" - integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== - delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -3794,15 +4992,10 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -deprecated-decorator@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" - integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc= - des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" @@ -3812,31 +5005,37 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detab@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.1.tgz#531f5e326620e2fd4f03264a905fb3bcc8af4df4" - integrity sha512-/hhdqdQc5thGrqzjyO/pz76lDZ5GSuAs6goxOaKTsvPk7HNnzAyFN5lyHgqpX4/s1i66K8qMGj+VhA9504x7DQ== +detab@2.0.3, detab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.3.tgz#33e5dd74d230501bd69985a0d2b9a3382699a130" + integrity sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A== dependencies: repeat-string "^1.5.4" -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= - dependencies: - repeating "^2.0.0" +detect-indent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" + integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== -detect-indent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= +detect-libc@^2.0.0, detect-libc@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== -detect-libc@^1.0.2, detect-libc@^1.0.3: +detect-newline@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-1.0.3.tgz#e97b1003877d70c09af1af35bfadff168de4920d" + integrity sha1-6XsQA4d9cMCa8a81v63/Fo3kkg0= + dependencies: + get-stdin "^4.0.1" + minimist "^1.1.0" + +detect-node-es@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.0.0.tgz#c0318b9e539a5256ca780dd9575c9345af05b8ed" + integrity sha512-S4AHriUkTX9FoFvL4G8hXDcx6t3gp2HpfCza3Q0v6S78gul2hKWifLQbeW+ZF89+hSm2ZIc/uF3J97ZgytgTRg== -detect-node@^2.0.3: +detect-node@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== @@ -3849,52 +5048,62 @@ detect-port-alt@1.1.3: address "^1.0.1" debug "^2.6.0" -detect-port@^1.2.1: - version "1.2.3" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.2.3.tgz#15bf49820d02deb84bfee0a74876b32d791bf610" - integrity sha512-IDbrX6PxqnYy8jV4wSHBaJlErYKTJvW8OQb9F7xivl1iQLqiUYHGa+nZ61Do6+N5uuOn/pReXKNqI9rUn04vug== +detect-port@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.3.0.tgz#d9c40e9accadd4df5cac6a782aefd014d573d1f1" + integrity sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ== dependencies: address "^1.0.1" debug "^2.6.0" -devcert-san@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/devcert-san/-/devcert-san-0.3.3.tgz#aa77244741b2d831771c011f22ee25e396ad4ba9" - integrity sha1-qnckR0Gy2DF3HAEfIu4l45atS6k= +devcert@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/devcert/-/devcert-1.1.3.tgz#ff8119efae52ebf2449531b7482ae0f7211542e9" + integrity sha512-7/nIzKdQ8y2K0imjIP7dyg2GJ2h38Ps6VOMXWZHIarNDV3p6mTXyEugKFnkmsZ2DD58JEG34ILyVb3qdOMmP9w== dependencies: "@types/configstore" "^2.1.1" - "@types/debug" "^0.0.29" - "@types/get-port" "^0.0.4" - "@types/glob" "^5.0.30" - "@types/mkdirp" "^0.3.29" - "@types/node" "^7.0.11" - "@types/tmp" "^0.0.32" - command-exists "^1.2.2" - configstore "^3.0.0" - debug "^2.6.3" - eol "^0.8.1" - get-port "^3.0.0" - glob "^7.1.1" + "@types/debug" "^0.0.30" + "@types/get-port" "^3.2.0" + "@types/glob" "^5.0.34" + "@types/lodash" "^4.14.92" + "@types/mkdirp" "^0.5.2" + "@types/node" "^8.5.7" + "@types/rimraf" "^2.0.2" + "@types/tmp" "^0.0.33" + application-config-path "^0.1.0" + command-exists "^1.2.4" + debug "^3.1.0" + eol "^0.9.1" + get-port "^3.2.0" + glob "^7.1.2" + lodash "^4.17.4" mkdirp "^0.5.1" - tmp "^0.0.31" - tslib "^1.6.0" + password-prompt "^1.0.4" + rimraf "^2.6.2" + sudo-prompt "^8.2.0" + tmp "^0.0.33" + tslib "^1.10.0" + +diff-sequences@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" + integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== diffie-hellman@^5.0.0: version "5.0.3" - resolved "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" - integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag== +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: - arrify "^1.0.1" - path-type "^3.0.0" + path-type "^4.0.0" dns-equal@^1.0.0: version "1.0.0" @@ -3902,9 +5111,9 @@ dns-equal@^1.0.0: integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== dependencies: ip "^1.1.0" safe-buffer "^5.0.1" @@ -3931,18 +5140,20 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -dom-converter@~0.2: +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== dependencies: utila "~0.4" -dom-helpers@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" - integrity sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg== - dom-iterator@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/dom-iterator/-/dom-iterator-0.3.0.tgz#5a745bf47bc692ac0fa3c0c385396ff2cbd55882" @@ -3951,40 +5162,50 @@ dom-iterator@^0.3.0: component-props "1.1.1" component-xor "0.0.3" -dom-serializer@0, dom-serializer@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" - integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII= +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" + domelementtype "^2.0.1" + entities "^2.0.0" -dom-walk@^0.1.0: +dom-serializer@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.1.0.tgz#5f7c828f1bfc44887dc2a315ab5c45691d544b58" + integrity sha512-ox7bvGXt2n+uLWtCRLybYx60IrOlWL/aCebWJk1T0d4m3y2tzf4U3ij9wBMUb6YJZpz06HCCYuyCDveE2xXmzQ== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.0.0" + entities "^2.0.0" + +dom-serializer@~0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" - integrity sha1-sXrtguirWeUt2cGbF1bg/BhyBMI= - -domelementtype@~1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs= +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domhandler@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" - integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ= - dependencies: - domelementtype "1" +domelementtype@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" + integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== domhandler@^2.3.0: version "2.4.2" @@ -3993,17 +5214,12 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -domready@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/domready/-/domready-1.0.8.tgz#91f252e597b65af77e745ae24dd0185d5e26d58c" - integrity sha1-kfJS5Ze2Wvd+dFriTdAYXV4m1Yw= - -domutils@1.1: - version "1.1.6" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" - integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU= +domhandler@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.0.0.tgz#51cd13efca31da95bbb0c5bee3a48300e333b3e9" + integrity sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw== dependencies: - domelementtype "1" + domelementtype "^2.0.1" domutils@1.5.1: version "1.5.1" @@ -4021,38 +5237,26 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dot-prop@^4.1.0, dot-prop@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" - integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== +domutils@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.3.0.tgz#6469c63a3da2de0c3016f3a59e6a969e10705bce" + integrity sha512-xWC75PM3QF6MjE5e58OzwTX0B/rPQnlqH0YyXB/c056RtVJA+eu60da2I/bdnEHzEYC00g8QaZUlAbqOZVbOsw== dependencies: - is-obj "^1.0.0" + dom-serializer "^1.0.1" + domelementtype "^2.0.1" + domhandler "^3.0.0" -dotenv@^4.0.0: - version "4.0.0" - resolved "http://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" - integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= - -download@^4.0.0, download@^4.1.2: - version "4.4.3" - resolved "https://registry.yarnpkg.com/download/-/download-4.4.3.tgz#aa55fdad392d95d4b68e8c2be03e0c2aa21ba9ac" - integrity sha1-qlX9rTktldS2jowr4D4MKqIbqaw= - dependencies: - caw "^1.0.1" - concat-stream "^1.4.7" - each-async "^1.0.0" - filenamify "^1.0.1" - got "^5.0.0" - gulp-decompress "^1.2.0" - gulp-rename "^1.2.0" - is-url "^1.2.0" - object-assign "^4.0.1" - read-all-stream "^3.0.0" - readable-stream "^2.0.2" - stream-combiner2 "^1.1.1" - vinyl "^1.0.0" - vinyl-fs "^2.2.0" - ware "^1.2.0" +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== download@^6.2.2: version "6.2.5" @@ -4071,19 +5275,23 @@ download@^6.2.2: p-event "^1.0.0" pify "^3.0.0" -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= - dependencies: - readable-stream "~1.1.9" - -duplexer2@^0.1.4, duplexer2@~0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= +download@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/download/-/download-7.1.0.tgz#9059aa9d70b503ee76a132897be6dec8e5587233" + integrity sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ== dependencies: - readable-stream "^2.0.2" + archive-type "^4.0.0" + caw "^2.0.1" + content-disposition "^0.5.2" + decompress "^4.2.0" + ext-name "^5.0.0" + file-type "^8.1.0" + filenamify "^2.0.0" + get-stream "^3.0.0" + got "^8.3.1" + make-dir "^1.2.0" + p-event "^2.1.0" + pify "^3.0.0" duplexer3@^0.1.4: version "0.1.4" @@ -4091,28 +5299,20 @@ duplexer3@^0.1.4: integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= duplexer@^0.1.1: - version "0.1.1" - resolved "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -duplexify@^3.2.0, duplexify@^3.4.2, duplexify@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" - integrity sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ== +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== dependencies: end-of-stream "^1.0.0" inherits "^2.0.1" readable-stream "^2.0.0" stream-shift "^1.0.0" -each-async@^1.0.0, each-async@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/each-async/-/each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473" - integrity sha1-3uUim98KtrogEqOV4bhpq/iBNHM= - dependencies: - onetime "^1.0.0" - set-immediate-shim "^1.0.0" - ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -4126,15 +5326,15 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.73: - version "1.3.77" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.77.tgz#9207a874a21a8fcb665bb4ff1675a11ba65517f4" - integrity sha512-XIfQcdU9L4qUte31fFATwptHodMH0Otf53N8y1AKxd1+79vR+2UYpLq+Z1Zbtbuy+w0xd7KwIUrvlnje/htiOg== +electron-to-chromium@^1.3.723: + version "1.3.739" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz#f07756aa92cabd5a6eec6f491525a64fe62f98b9" + integrity sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A== -elliptic@^6.0.0: - version "6.4.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" - integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ== +elliptic@^6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" + integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -4149,15 +5349,25 @@ elliptic@^6.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= -emoji-regex@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" - integrity sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ== +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4" + integrity sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== encodeurl@~1.0.2: version "1.0.2" @@ -4165,82 +5375,104 @@ encodeurl@~1.0.2: integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: - iconv-lite "~0.4.13" + iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -engine.io-client@~3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" - integrity sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw== +engine.io-client@~3.4.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.3.tgz#192d09865403e3097e3575ebfeb3861c4d01a66c" + integrity sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw== dependencies: - component-emitter "1.2.1" + component-emitter "~1.3.0" component-inherit "0.0.3" - debug "~3.1.0" - engine.io-parser "~2.1.1" + debug "~4.1.0" + engine.io-parser "~2.2.0" has-cors "1.1.0" indexof "0.0.1" parseqs "0.0.5" parseuri "0.0.5" - ws "~3.3.1" + ws "~6.1.0" xmlhttprequest-ssl "~1.5.4" yeast "0.1.2" -engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.2.tgz#4c0f4cff79aaeecbbdcfdea66a823c6085409196" - integrity sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw== +engine.io-client@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.0.tgz#fc1b4d9616288ce4f2daf06dcf612413dec941c7" + integrity sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA== + dependencies: + component-emitter "~1.3.0" + component-inherit "0.0.3" + debug "~3.1.0" + engine.io-parser "~2.2.0" + has-cors "1.1.0" + indexof "0.0.1" + parseqs "0.0.6" + parseuri "0.0.6" + ws "~7.4.2" + xmlhttprequest-ssl "~1.5.4" + yeast "0.1.2" + +engine.io-parser@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7" + integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg== dependencies: after "0.8.2" arraybuffer.slice "~0.0.7" - base64-arraybuffer "0.1.5" - blob "0.0.4" + base64-arraybuffer "0.1.4" + blob "0.0.5" has-binary2 "~1.0.2" -engine.io@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.0.tgz#54332506f42f2edc71690d2f2a42349359f3bf7d" - integrity sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw== +engine.io@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b" + integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA== dependencies: accepts "~1.3.4" - base64id "1.0.0" - cookie "0.3.1" - debug "~3.1.0" - engine.io-parser "~2.1.0" - ws "~3.3.1" + base64id "2.0.0" + cookie "~0.4.1" + debug "~4.1.0" + engine.io-parser "~2.2.0" + ws "~7.4.2" -enhanced-resolve@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== +enhanced-resolve@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" + integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== dependencies: graceful-fs "^4.1.2" - memory-fs "^0.4.0" + memory-fs "^0.5.0" tapable "^1.0.0" entities@^1.1.1, entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" - integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== -envinfo@^5.8.1: - version "5.10.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.10.0.tgz#503a9774ae15b93ea68bdfae2ccd6306624ea6df" - integrity sha512-rXbzXWvnQxy+TcqZlARbWVQwgGVVouVJgFZhLVN5htjLxl1thstrP2ZGi0pXC309AbK7gVOPU+ulz/tmpCI7iw== +envinfo@^7.5.1, envinfo@^7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" + integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== -eol@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/eol/-/eol-0.8.1.tgz#defc3224990c7eca73bb34461a56cf9dc24761d0" - integrity sha1-3vwyJJkMfspzuzRGGlbPncJHYdA= +eol@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" + integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg== errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -4256,42 +5488,66 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -error-stack-parser@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d" - integrity sha512-E1fPutRDdIj/hohG0UpT5mayXNCxXP9d+snxFsPU9X0XgccOumKraa3juDMwTUyi7+Bu5+mCGagjg4IYeNbOdw== +error-stack-parser@^2.0.0, error-stack-parser@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== dependencies: - stackframe "^1.0.4" + stackframe "^1.1.1" -es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" - integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== dependencies: - es-to-primitive "^1.1.1" + es-to-primitive "^1.2.1" function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" - -es-to-primitive@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" - integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.0: + version "1.18.0-next.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc" + integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" is-date-object "^1.0.1" is-symbol "^1.0.2" -es6-promise@^3.0.2: - version "3.3.1" - resolved "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" - integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM= +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -es6-promisify@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.0.0.tgz#b526a75eaa5ca600e960bf3d5ad98c40d75c7203" - integrity sha512-8Tbqjrb8lC85dd81haajYwuRmiU2rkqNAFnlvQOJeeKqdUloIlI+JcUqeJruV4rCm5Y7oNU7jfs2FbmxhRR/2g== +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== escape-html@~1.0.3: version "1.0.3" @@ -4303,10 +5559,20 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + eslint-config-fbjs@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/eslint-config-fbjs/-/eslint-config-fbjs-2.0.1.tgz#395896fd740e0e28dc1c2072e3bc982e88247df5" - integrity sha512-nZ/JByixNK/8epeQqmrtNCYYMXCjHoPkJwHaHg4aZyZlS62YLttDSWYE6ISGl070V+o6dkFbDALceWaO3Po+Sw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-fbjs/-/eslint-config-fbjs-2.1.0.tgz#bfe4f8c2d2282bfe515359553905d830e3a5d12f" + integrity sha512-wh7Lveo51V3/SUydWtR2VEU8wNfSHt5V7YzIUKTRkHF3kvkCwFtM6Jgsn+xBNkjxZGpfWgNJN/drk1LLx64Dww== eslint-config-prettier@^2.6.0: version "2.10.0" @@ -4315,30 +5581,30 @@ eslint-config-prettier@^2.6.0: dependencies: get-stdin "^5.0.1" -eslint-config-react-app@3.0.0-next.66cc7a90: - version "3.0.0-next.66cc7a90" - resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-3.0.0-next.66cc7a90.tgz#f8c7bb3cca0f1e8f60bbf567ec71f6af1cce7edd" - integrity sha512-6J+fEOLy7uE+fxpGERi8Yts9vNEgul6AXbHhdvGRj+4Xpus7jR7Q4fu1oXmnuRwVPBxJ/MQkcpdFa2m8iBG20Q== +eslint-config-react-app@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df" + integrity sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ== dependencies: - confusing-browser-globals "2.0.0-next.66cc7a90" + confusing-browser-globals "^1.0.9" eslint-config-react@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/eslint-config-react/-/eslint-config-react-1.1.7.tgz#a0918d0fc47d0e9bd161a47308021da85d2585b3" integrity sha1-oJGND8R9DpvRYaRzCAIdqF0lhbM= -eslint-import-resolver-node@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" - integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== +eslint-import-resolver-node@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== dependencies: debug "^2.6.9" - resolve "^1.5.0" + resolve "^1.13.1" -eslint-loader@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.1.1.tgz#2a9251523652430bfdd643efdb0afc1a2a89546a" - integrity sha512-1GrJFfSevQdYpoDzx8mEE2TDWsb/zmFuY09l6hURg1AeFIKQOvZ+vH0UPjzmd1CZIbfTV5HUkMeBmFiDBkgIsQ== +eslint-loader@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.2.1.tgz#28b9c12da54057af0845e2a6112701a2f6bf8337" + integrity sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg== dependencies: loader-fs-cache "^1.0.0" loader-utils "^1.0.2" @@ -4346,13 +5612,13 @@ eslint-loader@^2.0.0: object-hash "^1.1.4" rimraf "^2.6.1" -eslint-module-utils@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" - integrity sha1-snA2LNiLGkitMIl2zn+lTphBF0Y= +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== dependencies: - debug "^2.6.8" - pkg-dir "^1.0.0" + debug "^2.6.9" + pkg-dir "^2.0.0" eslint-plugin-babel@^4.1.2: version "4.1.2" @@ -4360,89 +5626,126 @@ eslint-plugin-babel@^4.1.2: integrity sha1-eSAqDjV1fdkngJGbIzbx+i/lPB4= eslint-plugin-flowtype@^2.39.1: - version "2.50.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.0.tgz#953e262fa9b5d0fa76e178604892cf60dfb916da" - integrity sha512-10FnBXCp8odYcpUFXGAh+Zko7py0hUWutTd3BN/R9riukH360qNPLYPR3/xV9eu9K7OJDjJrsflBnL6RwxFnlw== - dependencies: - lodash "^4.17.10" - -eslint-plugin-flowtype@^2.46.1: version "2.50.3" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.3.tgz#61379d6dce1d010370acd6681740fd913d68175f" integrity sha512-X+AoKVOr7Re0ko/yEXyM5SSZ0tazc6ffdIOocp2fFUlWoDt7DV0Bz99mngOkAFLOAWjqRA5jPwqUCbrx13XoxQ== dependencies: lodash "^4.17.10" -eslint-plugin-graphql@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-graphql/-/eslint-plugin-graphql-2.1.1.tgz#dae5d597080075320ea8e98795056309ffe73a18" - integrity sha512-JT2paUyu3e9ZDnroSshwUMc6pKcnkfXTsZInX1+/rPotvqOLVLtdrx/cmfb7PTJwjiEAshwcpm3/XPdTpsKJPw== +eslint-plugin-flowtype@^3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-3.13.0.tgz#e241ebd39c0ce519345a3f074ec1ebde4cf80f2c" + integrity sha512-bhewp36P+t7cEV0b6OdmoRWJCBYRiHFlqPZAG1oS3SF+Y0LQkeDvFSM4oxoxvczD1OdONCXMlJfQFiWLcV9urw== + dependencies: + lodash "^4.17.15" + +eslint-plugin-graphql@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-graphql/-/eslint-plugin-graphql-3.1.1.tgz#640f7f73f12cee2f7145140bd2ff21694018bff5" + integrity sha512-VNu2AipS8P1BAnE/tcJ2EmBWjFlCnG+1jKdUlFNDQjocWZlFiPpMu9xYNXePoEXK+q+jG51M/6PdhOjEgJZEaQ== dependencies: graphql-config "^2.0.1" lodash "^4.11.1" -eslint-plugin-import@^2.9.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8" - integrity sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g== +eslint-plugin-import@^2.22.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" + integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" contains-path "^0.1.0" - debug "^2.6.8" + debug "^2.6.9" doctrine "1.5.0" - eslint-import-resolver-node "^0.3.1" - eslint-module-utils "^2.2.0" - has "^1.0.1" - lodash "^4.17.4" - minimatch "^3.0.3" + eslint-import-resolver-node "^0.3.3" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" read-pkg-up "^2.0.0" - resolve "^1.6.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" -eslint-plugin-jsx-a11y@^6.0.2: - version "6.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.1.tgz#7bf56dbe7d47d811d14dbb3ddff644aa656ce8e1" - integrity sha512-JsxNKqa3TwmPypeXNnI75FntkUktGzI1wSa1LgNZdSOMI+B4sxnr1lSF8m8lPiz4mKiC+14ysZQM4scewUrP7A== +eslint-plugin-jsx-a11y@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660" + integrity sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g== dependencies: - aria-query "^3.0.0" - array-includes "^3.0.3" + "@babel/runtime" "^7.10.2" + aria-query "^4.2.2" + array-includes "^3.1.1" ast-types-flow "^0.0.7" - axobject-query "^2.0.1" - damerau-levenshtein "^1.0.4" - emoji-regex "^6.5.1" + axe-core "^3.5.4" + axobject-query "^2.1.2" + damerau-levenshtein "^1.0.6" + emoji-regex "^9.0.0" has "^1.0.3" - jsx-ast-utils "^2.0.1" + jsx-ast-utils "^2.4.1" + language-tags "^1.0.5" -eslint-plugin-jsx-a11y@^6.0.3: - version "6.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.2.tgz#69bca4890b36dcf0fe16dd2129d2d88b98f33f88" - integrity sha512-7gSSmwb3A+fQwtw0arguwMdOdzmKUgnUcbSNlo+GjKLAQFuC2EZxWqG9XHRI8VscBJD5a8raz3RuxQNFW+XJbw== +eslint-plugin-jsx-a11y@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" + integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== dependencies: - aria-query "^3.0.0" - array-includes "^3.0.3" + "@babel/runtime" "^7.11.2" + aria-query "^4.2.2" + array-includes "^3.1.1" ast-types-flow "^0.0.7" - axobject-query "^2.0.1" - damerau-levenshtein "^1.0.4" - emoji-regex "^6.5.1" + axe-core "^4.0.2" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.6" + emoji-regex "^9.0.0" has "^1.0.3" - jsx-ast-utils "^2.0.1" + jsx-ast-utils "^3.1.0" + language-tags "^1.0.5" eslint-plugin-prettier@^2.3.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.2.tgz#71998c60aedfa2141f7bfcbf9d1c459bf98b4fad" - integrity sha512-tGek5clmW5swrAx1mdPYM8oThrBE83ePh7LeseZHBWfHVGrHPhKn7Y5zgRMbU/9D5Td9K4CEmUPjGxA7iw98Og== + version "2.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz#b4312dcf2c1d965379d7f9d5b5f8aaadc6a45904" + integrity sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA== dependencies: fast-diff "^1.1.1" jest-docblock "^21.0.0" -eslint-plugin-react@^7.4.0, eslint-plugin-react@^7.8.2: - version "7.11.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz#c01a7af6f17519457d6116aa94fc6d2ccad5443c" - integrity sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw== +eslint-plugin-react-hooks@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" + integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== + +eslint-plugin-react@^7.20.6: + version "7.20.6" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz#4d7845311a93c463493ccfa0a19c9c5d0fd69f60" + integrity sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg== dependencies: - array-includes "^3.0.3" + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" doctrine "^2.1.0" has "^1.0.3" - jsx-ast-utils "^2.0.1" - prop-types "^15.6.2" + jsx-ast-utils "^2.4.1" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.17.0" + string.prototype.matchall "^4.0.2" + +eslint-plugin-react@^7.21.5: + version "7.21.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" + integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.18.1" + string.prototype.matchall "^4.0.2" eslint-plugin-relay@^0.0.19: version "0.0.19" @@ -4467,22 +5770,44 @@ eslint-scope@^3.7.1: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" - integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-visitor-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" - integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@^4.19.1, eslint@^4.8.0: +eslint@^4.8.0: version "4.19.1" - resolved "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ== dependencies: ajv "^5.3.0" @@ -4524,6 +5849,49 @@ eslint@^4.19.1, eslint@^4.8.0: table "4.0.2" text-table "~0.2.0" +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + espree@^3.5.4: version "3.5.4" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" @@ -4532,54 +5900,73 @@ espree@^3.5.4: acorn "^5.5.0" acorn-jsx "^3.0.0" -esprima@^2.6.0: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" - integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== +esquery@^1.0.0, esquery@^1.0.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== dependencies: - estraverse "^4.0.0" + estraverse "^5.1.0" -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== +esrecurse@^4.1.0, esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: - estraverse "^4.1.0" + estraverse "^5.2.0" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" - integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -esutils@^2.0.0, esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eventemitter3@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" - integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== +event-source-polyfill@^1.0.15: + version "1.0.20" + resolved "https://registry.yarnpkg.com/event-source-polyfill/-/event-source-polyfill-1.0.20.tgz#cd40856d79bd402fe3ed6a6c07cb4bb50600d7b2" + integrity sha512-+uOWalBp4xnbtSwKsRfqkVMnx1jPHNjC0PISYBjGJqV8N3YVxnkdm5ZqzO0RCRQvrQy0TFC32+nFcEcA+dZ+gA== -events@^1.0.0: - version "1.1.1" - resolved "http://registry.npmjs.org/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= +eventemitter3@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== eventsource@0.1.6: version "0.1.6" @@ -4588,6 +5975,13 @@ eventsource@0.1.6: dependencies: original ">=0.0.5" +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -4596,38 +5990,6 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -exec-buffer@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/exec-buffer/-/exec-buffer-3.2.0.tgz#b1686dbd904c7cf982e652c1f5a79b1e5573082b" - integrity sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA== - dependencies: - execa "^0.7.0" - p-finally "^1.0.0" - pify "^3.0.0" - rimraf "^2.5.4" - tempfile "^2.0.0" - -exec-series@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/exec-series/-/exec-series-1.0.3.tgz#6d257a9beac482a872c7783bc8615839fc77143a" - integrity sha1-bSV6m+rEgqhyx3g7yGFYOfx3FDo= - dependencies: - async-each-series "^1.1.0" - object-assign "^4.1.0" - -execa@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" - integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== - dependencies: - cross-spawn "^6.0.0" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -4641,43 +6003,62 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" - integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo= +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" + cross-spawn "^6.0.0" + get-stream "^4.0.0" is-stream "^1.1.0" npm-run-path "^2.0.0" p-finally "^1.0.0" signal-exit "^3.0.0" strip-eof "^1.0.0" -executable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/executable/-/executable-1.1.0.tgz#877980e9112f3391066da37265de7ad8434ab4d9" - integrity sha1-h3mA6REvM5EGbaNyZd562ENKtNk= - dependencies: - meow "^3.1.0" +execa@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" + integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" -exenv@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" - integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= +execa@^4.0.0, execa@^4.0.2, execa@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2" + integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +executable@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" -exif-parser@^0.1.9: +exif-parser@^0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" integrity sha1-WKnS1ywCwfbwKg70qRZicrd2CSI= -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= - dependencies: - is-posix-bracket "^0.1.0" - expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -4691,17 +6072,10 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - -expand-template@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-1.1.1.tgz#981f188c0c3a87d2e28f559bc541426ff94f21dd" - integrity sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" @@ -4710,49 +6084,49 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -express-graphql@^0.6.12: - version "0.6.12" - resolved "http://registry.npmjs.org/express-graphql/-/express-graphql-0.6.12.tgz#dfcb2058ca72ed5190b140830ad8cdbf76a9128a" - integrity sha512-ouLWV0hRw4hnaLtXzzwhdC79ewxKbY2PRvm05mPc/zOH5W5WVCHDQ1SmNxEPBQdUeeSNh29aIqW9zEQkA3kMuA== +express-graphql@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.9.0.tgz#00fd8552f866bac5c9a4612b2c4c82076107b3c2" + integrity sha512-wccd9Lb6oeJ8yHpUs/8LcnGjFUUQYmOG9A5BNLybRdCzGw0PeUrtBxsIR8bfiur6uSW4OvPkVDoYH06z6/N9+w== dependencies: - accepts "^1.3.0" + accepts "^1.3.7" content-type "^1.0.4" - http-errors "^1.3.0" - raw-body "^2.3.2" + http-errors "^1.7.3" + raw-body "^2.4.1" -express@^4.16.2, express@^4.16.3: - version "4.16.4" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" - integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== dependencies: - accepts "~1.3.5" + accepts "~1.3.7" array-flatten "1.1.1" - body-parser "1.18.3" - content-disposition "0.5.2" + body-parser "1.19.0" + content-disposition "0.5.3" content-type "~1.0.4" - cookie "0.3.1" + cookie "0.4.0" cookie-signature "1.0.6" debug "2.6.9" depd "~1.1.2" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.1.1" + finalhandler "~1.1.2" fresh "0.5.2" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" - parseurl "~1.3.2" + parseurl "~1.3.3" path-to-regexp "0.1.7" - proxy-addr "~2.0.4" - qs "6.5.2" - range-parser "~1.2.0" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" safe-buffer "5.1.2" - send "0.16.2" - serve-static "1.13.2" - setprototypeof "1.1.0" - statuses "~1.4.0" - type-is "~1.6.16" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -4786,26 +6160,28 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== external-editor@^2.0.4: version "2.2.0" - resolved "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== dependencies: chardet "^0.4.0" iconv-lite "^0.4.17" tmp "^0.0.33" -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== dependencies: - is-extglob "^1.0.0" + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" extglob@^2.0.4: version "2.0.4" @@ -4831,56 +6207,54 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fancy-log@^1.1.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" - integrity sha1-9BEl49hPLn2JpD0G2VjI94vha+E= - dependencies: - ansi-gray "^0.1.1" - color-support "^1.1.3" - time-stamp "^1.0.0" - fast-deep-equal@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-diff@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" - integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig== + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^2.0.0, fast-glob@^2.0.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.3.tgz#d09d378e9ef6b0076a0fa1ba7519d9d4d9699c28" - integrity sha512-NiX+JXjnx43RzvVFwRWfPKo4U+1BrK5pJPsHQdKMlLoFHrrGktXglQhHliSihWAq+m1z6fHk3uwGHrtRbS9vLA== +fast-glob@^3.0.3, fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.0.1" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.1" - micromatch "^3.1.10" + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fastparse@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" - integrity sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg= +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + +fastq@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + dependencies: + reusify "^1.0.4" faye-websocket@^0.10.0: version "0.10.0" @@ -4889,21 +6263,14 @@ faye-websocket@^0.10.0: dependencies: websocket-driver ">=0.5.1" -faye-websocket@~0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" - integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg= +faye-websocket@~0.11.0, faye-websocket@~0.11.1: + version "0.11.3" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" + integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== dependencies: websocket-driver ">=0.5.1" -fb-watchman@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" - integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= - dependencies: - bser "^2.0.0" - -fbjs@^0.8.0, fbjs@^0.8.12, fbjs@^0.8.14: +fbjs@^0.8.12: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= @@ -4916,11 +6283,6 @@ fbjs@^0.8.0, fbjs@^0.8.12, fbjs@^0.8.14: setimmediate "^1.0.5" ua-parser-js "^0.7.18" -fclone@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640" - integrity sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA= - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -4928,10 +6290,15 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -figgy-pudding@^3.1.0, figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== +fd@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/fd/-/fd-0.0.3.tgz#b3240de86dbf5a345baae7382a07d4713566ff0c" + integrity sha512-iAHrIslQb3U68OcMSP0kkNWabp7sSN6d2TBSb2JO3gcLJVDd4owr/hKM4SFJovFOUeeXeItjYgouEDTMWiVAnA== + +figgy-pudding@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^1.3.5: version "1.7.0" @@ -4948,6 +6315,13 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" @@ -4956,9 +6330,16 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + file-loader@^1.1.11: version "1.1.11" - resolved "http://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8" integrity sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg== dependencies: loader-utils "^1.0.2" @@ -4969,12 +6350,17 @@ file-type@5.2.0, file-type@^5.2.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" integrity sha1-LdvqfHP/42No365J3DOMBYwritY= -file-type@^3.1.0, file-type@^3.8.0: +file-type@^12.0.0, file-type@^12.4.2: + version "12.4.2" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9" + integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg== + +file-type@^3.8.0: version "3.9.0" - resolved "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek= -file-type@^4.3.0: +file-type@^4.2.0: version "4.4.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-4.4.0.tgz#1b600e5fca1fbdc6e80c0a70c71c8dba5f7906c5" integrity sha1-G2AOX8ofvcboDApwxxyNul95BsU= @@ -4989,30 +6375,21 @@ file-type@^8.1.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-8.1.0.tgz#244f3b7ef641bbe0cca196c7276e4b332399f68c" integrity sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ== -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= +file-type@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18" + integrity sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw== -filename-reserved-regex@^1.0.0: +file-uri-to-path@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz#e61cf805f0de1c984567d0386dc5df50ee5af7e4" - integrity sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q= + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== filename-reserved-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= -filenamify@^1.0.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-1.2.1.tgz#a9f2ffd11c503bed300015029272378f1f1365a5" - integrity sha1-qfL/0RxQO+0wABUCknI3jx8TZaU= - dependencies: - filename-reserved-regex "^1.0.0" - strip-outer "^1.0.0" - trim-repeated "^1.0.0" - filenamify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9" @@ -5027,17 +6404,6 @@ filesize@3.5.11: resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" integrity sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g== -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -5048,17 +6414,24 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -finalhandler@1.1.1: - version "1.1.1" - resolved "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" - integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== dependencies: debug "2.6.9" encodeurl "~1.0.2" escape-html "~1.0.3" on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.4.0" + parseurl "~1.3.3" + statuses "~1.5.0" unpipe "~1.0.0" find-cache-dir@^0.1.1: @@ -5070,23 +6443,28 @@ find-cache-dir@^0.1.1: mkdirp "^0.5.1" pkg-dir "^1.0.0" -find-cache-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" - integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8= +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== dependencies: commondir "^1.0.1" - make-dir "^1.0.0" - pkg-dir "^2.0.0" + make-dir "^2.0.0" + pkg-dir "^3.0.0" -find-cache-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d" - integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA== +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== dependencies: commondir "^1.0.1" - make-dir "^1.0.0" - pkg-dir "^3.0.0" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== find-up@^1.0.0: version "1.1.2" @@ -5110,42 +6488,49 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-versions@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-1.2.1.tgz#cbde9f12e38575a0af1be1b9a2c5d5fd8f186b62" - integrity sha1-y96fEuOFdaCvG+G5osXV/Y8Ya2I= +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - array-uniq "^1.0.0" - get-stdin "^4.0.1" - meow "^3.5.0" - semver-regex "^1.0.0" + locate-path "^5.0.0" + path-exists "^4.0.0" -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04= +find-versions@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" + integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== + dependencies: + semver-regex "^2.0.0" flat-cache@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" - integrity sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE= + version "1.3.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" + integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== dependencies: circular-json "^0.3.1" - del "^2.0.2" graceful-fs "^4.1.2" + rimraf "~2.6.2" write "^0.2.1" -flat@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== dependencies: - is-buffer "~2.0.3" + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" -flatten@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" - integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flatted@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.0.5.tgz#2db8faa9fc50b48b7c4ba3d6399846538fdab9f6" + integrity sha512-bDEpEsHk2pHn4R+slxblk0N0gK5lQsK2aRwW6LJyIpX3o9qhoVkufDjDvU3fpSJbR7UgOl+icRoR9agYyjzMTw== flow-bin@^0.56.0: version "0.56.0" @@ -5153,53 +6538,77 @@ flow-bin@^0.56.0: integrity sha1-zkMJIgOjRLqb9jwMq+ldlRRfbK0= flush-write-stream@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" - integrity sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw== + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== dependencies: - inherits "^2.0.1" - readable-stream "^2.0.4" + inherits "^2.0.3" + readable-stream "^2.3.6" -follow-redirects@^1.0.0: - version "1.5.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.9.tgz#c9ed9d748b814a39535716e531b9196a845d89c6" - integrity sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w== +fn-name@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" + integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc= + +focus-lock@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a" + integrity sha512-LI7v2mH02R55SekHYdv9pRHR9RajVNyIJ2N5IEkWbg7FT5ZmJ9Hw4mWxHeEUcd+dJo0QmzztHvDvWcc7prVFsw== + +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== dependencies: debug "=3.1.0" -for-each@^0.3.2: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" +follow-redirects@^1.0.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" + integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= - dependencies: - for-in "^1.0.1" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" + integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" - integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk= + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== dependencies: asynckit "^0.4.0" - combined-stream "1.0.6" + combined-stream "^1.0.6" mime-types "^2.1.12" +formik@^2.0.8: + version "2.1.5" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8" + integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ== + dependencies: + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.14" + lodash-es "^4.17.14" + react-fast-compare "^2.0.1" + scheduler "^0.18.0" + tiny-warning "^1.0.2" + tslib "^1.10.0" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -5217,16 +6626,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -friendly-errors-webpack-plugin@^1.6.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.0.tgz#efc86cbb816224565861a1be7a9d84d0aafea136" - integrity sha512-K27M3VK30wVoOarP651zDmb93R9zF28usW4ocaK3mfQeIEI5BPht/EzZs5E8QLLwbLRJQMwscAjDxYPb1FuNiw== - dependencies: - chalk "^1.1.3" - error-stack-parser "^2.0.0" - string-width "^2.0.0" - -from2@^2.1.0: +from2@^2.1.0, from2@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -5237,42 +6637,31 @@ from2@^2.1.0: fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-copy-file-sync@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz#11bf32c096c10d126e5f6b36d06eece776062918" - integrity sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ== - -fs-exists-cached@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-exists-cached/-/fs-exists-cached-1.0.0.tgz#cf25554ca050dc49ae6656b41de42258989dcbce" - integrity sha1-zyVVTKBQ3EmuZla0HeQiWJidy84= - -fs-extra@^4.0.1, fs-extra@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" - integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== +fs-exists-cached@1.0.0, fs-exists-cached@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-exists-cached/-/fs-exists-cached-1.0.0.tgz#cf25554ca050dc49ae6656b41de42258989dcbce" + integrity sha1-zyVVTKBQ3EmuZla0HeQiWJidy84= + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== dependencies: - graceful-fs "^4.1.2" + graceful-fs "^4.2.0" jsonfile "^4.0.0" universalify "^0.1.0" -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== +fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== dependencies: - minipass "^2.2.1" + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" fs-write-stream-atomic@^1.0.8: version "1.0.10" @@ -5289,15 +6678,20 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^1.0.0, fsevents@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" - integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== +fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" + bindings "^1.5.0" + nan "^2.12.1" -function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + +function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== @@ -5307,192 +6701,384 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gatsby-cli@^2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-2.4.3.tgz#355b2278ec3936618516935426ae0d164a75b994" - integrity sha512-syIrRagg7a0i4XgWCnAzUmDV3RZXsIiuNwn7P9L1OH00bCJfp5Q3cnjQa5VbRF8i8fWQX/rh/RBmpdFUJZVdNg== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/runtime" "^7.0.0" - bluebird "^3.5.0" - common-tags "^1.4.0" - convert-hrtime "^2.0.0" - core-js "^2.5.0" - envinfo "^5.8.1" - execa "^0.8.0" +gatsby-cli@^2.12.99: + version "2.12.99" + resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-2.12.99.tgz#50078a0afd09854a92ca7243997b498de7d331b7" + integrity sha512-LHPX8bRHye69LPS9OiLw9in2ypyEnsxcU2p1MiBEs542D7bGmNXvJW61vN1kcXB9t5kFs3Ka2LDJjSn+5LbhfQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@hapi/joi" "^15.1.1" + "@types/common-tags" "^1.8.0" + better-opn "^1.0.0" + chalk "^2.4.2" + clipboardy "^2.3.0" + common-tags "^1.8.0" + configstore "^5.0.1" + convert-hrtime "^3.0.0" + envinfo "^7.5.1" + execa "^3.4.0" fs-exists-cached "^1.0.0" - fs-extra "^4.0.1" - hosted-git-info "^2.6.0" - lodash "^4.17.10" - opentracing "^0.14.3" + fs-extra "^8.1.0" + gatsby-core-utils "^1.3.20" + gatsby-recipes "^0.2.27" + gatsby-telemetry "^1.3.35" + hosted-git-info "^3.0.4" + ink "^2.7.1" + ink-spinner "^3.1.0" + is-valid-path "^0.1.1" + lodash "^4.17.20" + meant "^1.0.1" + node-fetch "^2.6.0" + opentracing "^0.14.4" pretty-error "^2.1.1" - resolve-cwd "^2.0.0" - source-map "^0.5.7" + progress "^2.0.3" + prompts "^2.3.2" + react "^16.8.0" + redux "^4.0.5" + resolve-cwd "^3.0.0" + semver "^7.3.2" + signal-exit "^3.0.3" + source-map "0.7.3" stack-trace "^0.0.10" - update-notifier "^2.3.0" - yargs "^11.1.0" - yurnalist "^0.2.1" + strip-ansi "^5.2.0" + update-notifier "^4.1.0" + uuid "3.4.0" + yargs "^15.3.1" + yurnalist "^1.1.2" + +gatsby-core-utils@^1.3.20: + version "1.3.20" + resolved "https://registry.yarnpkg.com/gatsby-core-utils/-/gatsby-core-utils-1.3.20.tgz#3fee11634ec457f01d1327da885a9f8fe50c6823" + integrity sha512-tTry2Iz7QKfMEkYiqXOEbMhR96hpttkKeUCQAj7syC9tQwFGd1nkGlpbD4n8lBa22cXKLlL9J2edhDo1xwnfGQ== + dependencies: + ci-info "2.0.0" + configstore "^5.0.1" + fs-extra "^8.1.0" + node-object-hash "^2.0.0" + proper-lockfile "^4.1.1" + tmp "^0.2.1" + xdg-basedir "^4.0.0" + +gatsby-design-tokens@^2.0.2: + version "2.0.11" + resolved "https://registry.yarnpkg.com/gatsby-design-tokens/-/gatsby-design-tokens-2.0.11.tgz#b0cf12abc283c73e13de3f610fbcec5fce3e7b48" + integrity sha512-Hp4mFCDydvYkAYp2icEdilYptyKBSaDlYFD7/GO1+QJHskc+Yy9mhFIZOnC9Fa8XOIRp59RBkh71Jv4Pln2vdw== + dependencies: + hex2rgba "^0.0.1" + +gatsby-graphiql-explorer@^0.4.14: + version "0.4.14" + resolved "https://registry.yarnpkg.com/gatsby-graphiql-explorer/-/gatsby-graphiql-explorer-0.4.14.tgz#346959390d1c28c8faba8ce57e4c34a3f0e4210e" + integrity sha512-B8ChC4THCF0Aa+0F1jErKzTlUAdMAUtoJ0Ayi3+zVzlTk3LsRO+/PWecHeZa/DnFzds3libYuqskclKnRyAZWg== + dependencies: + "@babel/runtime" "^7.11.2" + +gatsby-interface@^0.0.166: + version "0.0.166" + resolved "https://registry.yarnpkg.com/gatsby-interface/-/gatsby-interface-0.0.166.tgz#ce970498fa0b36767595d423a30ed16a4832ac1e" + integrity sha512-PN0lTVOKu50zfY7kfjgHvT5jsYZIOdSxuWrV/WVxDXo4O3oifLiWUyfFy8zg9T8S1G+TwRyfzhWT9Pfj1CZ2Dg== + dependencies: + "@mdx-js/react" "^1.5.2" + "@reach/alert" "0.10.3" + "@reach/combobox" "0.10.3" + "@reach/dialog" "0.10.3" + "@reach/menu-button" "0.10.3" + "@reach/popover" "0.10.3" + "@reach/tabs" "0.10.3" + "@reach/tooltip" "0.10.3" + "@types/lodash.sample" "^4.2.6" + case "^1.6.2" + date-fns "^2.8.1" + gatsby-design-tokens "^2.0.2" + lodash.sample "^4.2.1" + theme-ui "^0.2.49" + +gatsby-legacy-polyfills@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/gatsby-legacy-polyfills/-/gatsby-legacy-polyfills-0.0.4.tgz#2c11859f485b87ca6fd3331bda1951f64d114b7e" + integrity sha512-BDlY9jkhEhqpQN5yvfnJYt8wTRzBOEtIQZnWHzuE7b6tYHsngxbfIMLN3UBOs9t5ZUqcPKc1C0J0NKG6NhC4Qw== + dependencies: + core-js-compat "^3.6.5" -gatsby-link@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/gatsby-link/-/gatsby-link-2.0.4.tgz#2af30f21171261d7b5ab61954704a399f81ca28f" - integrity sha512-yz5tRpEPfabYrauOL/lg76z+TbV8Et3nmGX4vxfdiVI1pSsEsFyWIJwlK2z6cKGuBBvReoVmGQSYtvcRibgcpw== +gatsby-link@^2.4.14: + version "2.4.14" + resolved "https://registry.yarnpkg.com/gatsby-link/-/gatsby-link-2.4.14.tgz#655ba6a436b0eca7b7d8c63d9dcab17310059c35" + integrity sha512-JLF7pb5E8flmsH2oQvGmGV1mMEuaQCzLFOXFj6FZOcG/JuOEXSb5N/Z/cp2MAlAVCyLLeYI7Ru/+o0TOpQMwkQ== dependencies: - "@babel/runtime" "^7.0.0" - "@reach/router" "^1.1.1" - "@types/reach__router" "^1.0.0" - prop-types "^15.6.1" - ric "^1.3.0" + "@babel/runtime" "^7.11.2" + "@types/reach__router" "^1.3.3" + prop-types "^15.7.2" + +gatsby-page-utils@^0.2.25: + version "0.2.25" + resolved "https://registry.yarnpkg.com/gatsby-page-utils/-/gatsby-page-utils-0.2.25.tgz#bfaa132d80c5e6e8877261177368ba7390d31435" + integrity sha512-0npo/wjYO94nqcjl0aMkL65LvJuVnaieJzlxpA6Fdj2s90RjKI0mCj/3VPvRBz3p0aDp5+gas4kUa5KE4B3b0Q== + dependencies: + "@babel/runtime" "^7.11.2" + bluebird "^3.7.2" + chokidar "^3.4.2" + fs-exists-cached "^1.0.0" + gatsby-core-utils "^1.3.20" + glob "^7.1.6" + lodash "^4.17.20" + micromatch "^3.1.10" gatsby-plugin-catch-links@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/gatsby-plugin-catch-links/-/gatsby-plugin-catch-links-2.0.4.tgz#7b62099dc7a99f72d58e0f8631581892fcef59f8" - integrity sha512-jBPB6ND33CxG/C/U8iGcZy5yt9WCdBUBpr21/U1/ZQicSODD3U8yqp048IYC49wCfDoSpRhmMpG+va0/p/X9XQ== + version "2.3.12" + resolved "https://registry.yarnpkg.com/gatsby-plugin-catch-links/-/gatsby-plugin-catch-links-2.3.12.tgz#1342ccb87658acb9a5b5cfccda45494836d5341b" + integrity sha512-AGWN8U6Gz1qoUUAfrut8kPITauBWvonFqzKfJZSPCkAIrY8lzq1G4w5VhlqrUaK1jhSrfSfXN0U8A3F85oVNvQ== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.11.2" escape-string-regexp "^1.0.5" gatsby-plugin-feed@^2.0.0: - version "2.0.8" - resolved "https://registry.yarnpkg.com/gatsby-plugin-feed/-/gatsby-plugin-feed-2.0.8.tgz#4fdd31d852891df225538c4c690dbb9a7a6bc0e7" - integrity sha512-ComKXQ4g+PRWNUd/vVFUvRDfqvE64W7HkAe1f0Fk/eNzkVZtvOLIDUD6ENh/IPMP9AaulrDeoN0OP6ek/qp1Kg== - dependencies: - "@babel/runtime" "^7.0.0" - lodash.merge "^4.6.0" - mkdirp "^0.5.1" - pify "^3.0.0" + version "2.5.12" + resolved "https://registry.yarnpkg.com/gatsby-plugin-feed/-/gatsby-plugin-feed-2.5.12.tgz#190caa89c75504c2f893216de4001247b26d956c" + integrity sha512-Aw9rvlZeBSrJ5grzxBYhIXpoYtdnbDT0Qf+SpPfuD6jaKYQ/LwXS/hWIteOl8Czz0yysxGl36i9cNn3EXBsAcg== + dependencies: + "@babel/runtime" "^7.11.2" + "@hapi/joi" "^15.1.1" + fs-extra "^8.1.0" + lodash.merge "^4.6.2" rss "^1.2.2" gatsby-plugin-glamor@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/gatsby-plugin-glamor/-/gatsby-plugin-glamor-2.0.5.tgz#584ab258cb2c4f44822984d157a70764eba0d88f" - integrity sha512-NxDhdB9/a8YG6OD2sR+owwq2syxXGRGortMUMGshnO1qVyx5XOrWu3ztdv2Rvw/yHFM99iUxhqX7ABYUKd2fdg== + version "2.3.11" + resolved "https://registry.yarnpkg.com/gatsby-plugin-glamor/-/gatsby-plugin-glamor-2.3.11.tgz#460866ac3a1f9cd988dd5d21226e4c2f387d3d77" + integrity sha512-YUO6RQLZCmuLIluaOfDIyymsIl8R4kSdElapTYQ7k5p9wnX14GXsyHMz/7RqrcINSwP8C9gk63uDNZ3nyR8bLw== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.11.2" gatsby-plugin-google-analytics@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/gatsby-plugin-google-analytics/-/gatsby-plugin-google-analytics-2.0.6.tgz#b6bc37ce357125ae9969ee38edf8fd031c57723c" - integrity sha512-o39E8myn9r1fwJOJMO4HxyoreVhm2wQNwkqugDRsvzHb+FFOLldQP9ZSUT/88OEnAr1jlQ+RajGrmhDv4ePIEQ== + version "2.3.14" + resolved "https://registry.yarnpkg.com/gatsby-plugin-google-analytics/-/gatsby-plugin-google-analytics-2.3.14.tgz#61b3517a9bdc2548101440d1c2803f33bed97136" + integrity sha512-KgOIRbIULeMMyGBCY5WQVOM1AmB/1HgY1ypH1UZYxS15riTO6WSTfp+5hcxCS75Xc/6HgAOjZlNibCH02Tz1mQ== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.11.2" + minimatch "3.0.4" gatsby-plugin-manifest@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.0.5.tgz#511b604dd92d7917d06a338b338b63d621282c23" - integrity sha512-iemgD5bczubqhkH6J66Sr9rGrC0i0V0vIox9xHwBdEy/6NC5yPSBntxfIK/Zknd2XMOrQ3ndQaUtYKipOzPLJw== + version "2.4.30" + resolved "https://registry.yarnpkg.com/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.4.30.tgz#a5edca7bdf898a6f06e33969ab4e21f7438438aa" + integrity sha512-ZcQcQRH/VNomRKlOPH/HkwzzJM0XmXw5egYYsVjf8TMtZka3/FDZucIGTeTLv7FZwdMSF2sGJrHU+nx3PrhCUQ== dependencies: - "@babel/runtime" "^7.0.0" - bluebird "^3.5.0" - sharp "^0.20.2" + "@babel/runtime" "^7.11.2" + gatsby-core-utils "^1.3.20" + semver "^7.3.2" + sharp "^0.25.4" gatsby-plugin-netlify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/gatsby-plugin-netlify/-/gatsby-plugin-netlify-2.0.1.tgz#285fc47296d6eaa21c3329181bcf44f4173df3a5" - integrity sha512-rEqn8C6LTZffSEs0PmC3iiQqF93L/dR0JD/ecYTVQQy74Fh0dVJCT/s7qwUyR5ksbdHT2MOK8kOkXrm5eBDo4w== + version "2.3.15" + resolved "https://registry.yarnpkg.com/gatsby-plugin-netlify/-/gatsby-plugin-netlify-2.3.15.tgz#f0d9ee8f9dac862c6c0f87f9609bc0113345879d" + integrity sha512-ui21s/2Myl5BYlyyjLvTuDms7UzJgsL8WXT/3FeURgsl5E5ZbgjaNDBsF7uhzxEzyUORlaZMV9lvbFoc9YlIPg== dependencies: - "@babel/runtime" "^7.0.0" - fs-extra "^4.0.2" + "@babel/runtime" "^7.11.2" + fs-extra "^8.1.0" kebab-hash "^0.1.2" - lodash "^4.17.10" - webpack-assets-manifest "^3.0.2" + lodash "^4.17.20" + webpack-assets-manifest "^3.1.1" gatsby-plugin-nprogress@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/gatsby-plugin-nprogress/-/gatsby-plugin-nprogress-2.0.5.tgz#694489a017b5a9f08d96ea445fdd9b03c6660df0" - integrity sha512-1Q3hfgVQarl6SdcuEDX17Oa/R5zmd9FCZg6dP1OEx2EcgrUwQl5iH4orHObJhc2U3oJNPUq/8HhJDYf8isdpmQ== + version "2.3.11" + resolved "https://registry.yarnpkg.com/gatsby-plugin-nprogress/-/gatsby-plugin-nprogress-2.3.11.tgz#0a11c6f1dc5ba41412be37dc6a7c1143a64e475f" + integrity sha512-eUGuHB2MrSb4fV3W2yZoenPN5smOqWiaVUZe/GLUgOIl/WLhEp16qTfytTCyASNBspOXuCduSVcpto2fD/VVTg== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.11.2" nprogress "^0.2.0" -gatsby-plugin-page-creator@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/gatsby-plugin-page-creator/-/gatsby-plugin-page-creator-2.0.1.tgz#001b4ea35daccb1a10a96205cb742e05aaba9499" - integrity sha512-IFvVwKbM2ZFq4F4Qj+Jt9AE0r3Fxg2dJhgTy0mXkAlAMdUCeo3wImx1laFefbMlmPnfOHQE2/13l9TZ/myRgrw== +gatsby-plugin-page-creator@^2.3.28: + version "2.3.28" + resolved "https://registry.yarnpkg.com/gatsby-plugin-page-creator/-/gatsby-plugin-page-creator-2.3.28.tgz#871b08155616b3e3fb5c6dd29188f5d615154a3c" + integrity sha512-AS1e44tF6ahADXTVvgTRcSWAzowbO7aPxg6RbX5BuUBpAnbQgXVTISVztk5ZVPA6/tESbfrkeEEoMHHcZmbPmA== dependencies: - "@babel/runtime" "^7.0.0" - bluebird "^3.5.0" - chokidar "^1.7.0" + "@babel/traverse" "^7.11.5" + "@sindresorhus/slugify" "^1.1.0" + chokidar "^3.4.2" fs-exists-cached "^1.0.0" - glob "^7.1.1" - lodash "^4.17.10" - parse-filepath "^1.0.1" - slash "^1.0.0" + gatsby-page-utils "^0.2.25" + globby "^11.0.1" + graphql "^14.6.0" + lodash "^4.17.20" gatsby-plugin-react-helmet@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/gatsby-plugin-react-helmet/-/gatsby-plugin-react-helmet-3.0.0.tgz#d833a7f7f65b6d2c5adb2d99095a7bd3882d33cd" - integrity sha512-d8Rrgg1tg4VxhJq5axy4xWvuH2y5CB7OIkujsPA4dKnhzIGx5PJ39ZMWLQ9ekV01Qu+ApVNWhfaC9GhnXUgWvA== - dependencies: - "@babel/runtime" "^7.0.0" - -gatsby-plugin-sharp@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-2.0.6.tgz#6d0b7e7102fcedbe8d0018e53d366018ebc84fdf" - integrity sha512-ie3vPvOkxoX+DLG/xyxg5Ibha3RqtnWZFmchYWPOdJDofKMA2rIiCMkTspSOA17yvfJf0AiqLe68Uri6ssw0gw== - dependencies: - "@babel/runtime" "^7.0.0" - async "^2.1.2" - bluebird "^3.5.0" - fs-exists-cached "^1.0.0" - imagemin "^6.0.0" - imagemin-mozjpeg "^7.0.0" - imagemin-pngquant "^6.0.0" - imagemin-webp "^4.1.0" - lodash "^4.17.10" - mini-svg-data-uri "^1.0.0" - potrace "^2.1.1" - probe-image-size "^4.0.0" - progress "^1.1.8" - sharp "^0.20.2" - svgo "^0.7.2" + version "3.3.11" + resolved "https://registry.yarnpkg.com/gatsby-plugin-react-helmet/-/gatsby-plugin-react-helmet-3.3.11.tgz#f49d31abfc5e4b31e81226d82b7b5e9c1b71f797" + integrity sha512-O9CBmxSAE/ODCKj5fGITP5zAVguD83+fIWQPgEzur+lwnvRyXoJBfMjKQezMECvWVv5UOfTwTL1/dcU87+UNkA== + dependencies: + "@babel/runtime" "^7.11.2" + +gatsby-plugin-sharp@^2.4.12: + version "2.6.36" + resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-2.6.36.tgz#1b2489d76078dc371ec5808915c58dcd360d202b" + integrity sha512-iOm9nQiDyLSA2ol8Xde4XSj+lJJ5bdlilkNCr3rTbvSoPV8qJ8Zj4RxLZ48Rgy9cVNbgibSZQzitM9bIfVijkA== + dependencies: + "@babel/runtime" "^7.11.2" + async "^3.2.0" + bluebird "^3.7.2" + fs-extra "^9.0.1" + gatsby-core-utils "^1.3.20" + got "^10.7.0" + imagemin "^7.0.1" + imagemin-mozjpeg "^9.0.0" + imagemin-pngquant "^9.0.1" + lodash "^4.17.19" + mini-svg-data-uri "^1.2.3" + potrace "^2.1.8" + probe-image-size "^5.0.0" + progress "^2.0.3" + semver "^7.3.2" + sharp "^0.25.4" + svgo "1.3.2" + uuid "3.4.0" gatsby-plugin-twitter@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/gatsby-plugin-twitter/-/gatsby-plugin-twitter-2.0.6.tgz#a187f271aabb70628c0e89b2b9fa78d60e93934a" - integrity sha512-TJ5MoCCd/+v8aKpCGAG4j5Md5Ik+AbUEpwQfrqeg2A8ugzEBVYkMBce5d5/+k9gJrhDHOUCNl0jWUA82CorTeA== - dependencies: - "@babel/runtime" "^7.0.0" - -gatsby-react-router-scroll@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/gatsby-react-router-scroll/-/gatsby-react-router-scroll-2.0.0.tgz#fb96e9d0b4454ad819b430388539bc1762f04442" - integrity sha512-in58kEsdflO8BCtQNXMR9uPBh/N5yuN8XDDAcYsf6pkLfVPYq3B9U62tztLFtvcuLV41oJb34zuVUcIwbc03dg== - dependencies: - "@babel/runtime" "^7.0.0" - scroll-behavior "^0.9.9" - warning "^3.0.0" - -gatsby-remark-code-repls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/gatsby-remark-code-repls/-/gatsby-remark-code-repls-2.0.0.tgz#b3290987046f1d325e6e93dbe9295f3cbb3df34a" - integrity sha512-a+4inOXFj5PeyAN92x5NATA5LlGia2mXWXvDvCq2eqlAUjP8uP5dGf+E/HVtPXieGnElHd4cIqIZp/Y7UOtSIg== - dependencies: - "@babel/runtime" "^7.0.0" + version "2.3.11" + resolved "https://registry.yarnpkg.com/gatsby-plugin-twitter/-/gatsby-plugin-twitter-2.3.11.tgz#6d2fdf16bdbed62ee0a3238248ffaa04aff657a0" + integrity sha512-3PfiroUOA5nZOi0LrwjrULB0JLBW+rn6lnpqGqUC5Y4sUN3eRBOMER+s5r+S4OTvm/sxRl1+8aJmjiY8OO/ghA== + dependencies: + "@babel/runtime" "^7.11.2" + +gatsby-plugin-typescript@^2.4.20: + version "2.4.20" + resolved "https://registry.yarnpkg.com/gatsby-plugin-typescript/-/gatsby-plugin-typescript-2.4.20.tgz#460c819b9a1d3da3d0626ca2dc9f131ad9859e6c" + integrity sha512-GqjbK/tchTq4yT4yJWRCfbvBAaYn3fKd1w0z9YQywx26yELe8+aM1CrGSErnuTP4rQ7xmfbK+0ASh2PUb2cSwg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.11.0" + "@babel/preset-typescript" "^7.10.4" + "@babel/runtime" "^7.11.2" + babel-plugin-remove-graphql-queries "^2.9.19" + +gatsby-react-router-scroll@^3.0.13: + version "3.0.13" + resolved "https://registry.yarnpkg.com/gatsby-react-router-scroll/-/gatsby-react-router-scroll-3.0.13.tgz#d6ceb2d12c935c39886d32a59d38076966febfe0" + integrity sha512-x/qLvDmvSvKiyoqiTuPWQChtHt8vzEGb4Y9mE1qFoDzX392/KEkh5p8jT7z0XZfo/yB4cQqlQOFKxIMTUOhZvg== + dependencies: + "@babel/runtime" "^7.11.2" + +gatsby-recipes@^0.2.27: + version "0.2.27" + resolved "https://registry.yarnpkg.com/gatsby-recipes/-/gatsby-recipes-0.2.27.tgz#85b7d31b9f6707f3980d666766ca8a085b5868d2" + integrity sha512-UaLmM4/+yyzQ/LSBu5kp8SrGe5ebOoiG/GU4z7UmKyL/rFaMdHPbWgc779b/LvJZX0159WxTHugeyQqT6JIjlg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.11.6" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.11.0" + "@babel/plugin-transform-react-jsx" "^7.10.4" + "@babel/standalone" "^7.11.6" + "@babel/template" "^7.10.4" + "@babel/types" "^7.11.5" + "@emotion/core" "^10.0.14" + "@emotion/styled" "^10.0.14" + "@graphql-tools/schema" "^6.0.14" + "@graphql-tools/utils" "^6.0.14" + "@hapi/hoek" "8.x.x" + "@hapi/joi" "^15.1.1" + "@mdx-js/mdx" "^2.0.0-next.4" + "@mdx-js/react" "^2.0.0-next.4" + "@mdx-js/runtime" "^2.0.0-next.4" + acorn "^7.2.0" + acorn-jsx "^5.2.0" + ansi-html "^0.0.7" + babel-plugin-remove-export-keywords "^1.6.5" + better-queue "^3.8.10" + chokidar "^3.4.2" + concurrently "^5.0.0" + contentful-management "^5.26.3" + cors "^2.8.5" + cross-fetch "^3.0.6" + debug "^4.1.1" + detect-port "^1.3.0" + dotenv "^8.2.0" + execa "^4.0.2" + express "^4.17.1" + express-graphql "^0.9.0" + flatted "^3.0.0" + formik "^2.0.8" + fs-extra "^8.1.0" + gatsby-core-utils "^1.3.20" + gatsby-interface "^0.0.166" + gatsby-telemetry "^1.3.35" + glob "^7.1.6" + graphql "^14.6.0" + graphql-compose "^6.3.8" + graphql-subscriptions "^1.1.0" + graphql-type-json "^0.3.2" + hicat "^0.7.0" + html-tag-names "^1.1.5" + ink-box "^1.0.0" + is-binary-path "^2.1.0" + is-url "^1.2.4" + jest-diff "^25.5.0" + lock "^1.0.0" + lodash "^4.17.20" + mitt "^1.2.0" + mkdirp "^0.5.1" + node-fetch "^2.5.0" + normalize.css "^8.0.1" + pkg-dir "^4.2.0" + prettier "^2.0.5" + prop-types "^15.6.1" + property-information "5.5.0" + react-circular-progressbar "^2.0.0" + react-icons "^3.0.1" + react-reconciler "^0.25.1" + remark-mdx "^2.0.0-next.4" + remark-mdxjs "^2.0.0-next.4" + remark-parse "^6.0.3" + remark-stringify "^8.1.0" + resolve-cwd "^3.0.0" + resolve-from "^5.0.0" + semver "^7.3.2" + single-trailing-newline "^1.0.0" + strip-ansi "^6.0.0" + style-to-object "^0.3.0" + subscriptions-transport-ws "^0.9.16" + svg-tag-names "^2.0.1" + unified "^8.4.2" + unist-util-remove "^2.0.0" + unist-util-visit "^2.0.2" + urql "^1.9.7" + uuid "3.4.0" + ws "^7.3.0" + xstate "^4.9.1" + yoga-layout-prebuilt "^1.9.6" + yup "^0.27.0" + +gatsby-remark-code-repls@^3.0.0: + version "3.2.12" + resolved "https://registry.yarnpkg.com/gatsby-remark-code-repls/-/gatsby-remark-code-repls-3.2.12.tgz#bdf0f7eb141cffa90b35c1357643d25002cfead5" + integrity sha512-KhDieivPzPE8/bTNAEzCRxIRnwRi6uHpcak8ZQx21WOjG4cZSLdmqlI4Kf9ooU+DL3cnoaIW4rHnNhidAAKsFA== + dependencies: + "@babel/runtime" "^7.11.2" lz-string "^1.4.4" - normalize-path "^2.1.1" - recursive-readdir-synchronous "^0.0.3" - unist-util-map "^1.0.3" - urijs "^1.19.0" + normalize-path "^3.0.0" + npm-package-arg "^6.1.1" + recursive-readdir "^2.2.2" + unist-util-map "^1.0.5" + urijs "^1.19.2" gatsby-remark-copy-linked-files@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/gatsby-remark-copy-linked-files/-/gatsby-remark-copy-linked-files-2.0.5.tgz#885a9a6a403b82d5fcf707e57911bbe3bfe1a1e6" - integrity sha512-pya1Kh64FkYV/GtCxxBEUEX+lmpn8M1xs5Ro6KCBmlUu1FQ5F3LPnHHKeQ7wtrLPxVAGOlvgT/3KQ1ZbrHpNGg== - dependencies: - "@babel/runtime" "^7.0.0" - cheerio "^1.0.0-rc.2" - fs-extra "^4.0.1" - is-relative-url "^2.0.0" - lodash "^4.17.10" + version "2.3.15" + resolved "https://registry.yarnpkg.com/gatsby-remark-copy-linked-files/-/gatsby-remark-copy-linked-files-2.3.15.tgz#2f5e6f81bd489fe88dca3836476b6ae66c7067ee" + integrity sha512-IjgM7JK0ln23D3Svka+j80f1W6ew5boVzijS6Mf5ecbcOz0Wp+Xz5gudXKzjpH2t3g/MpSuYmjewngO+Gmz6SQ== + dependencies: + "@babel/runtime" "^7.11.2" + cheerio "^1.0.0-rc.3" + fs-extra "^8.1.0" + is-relative-url "^3.0.0" + lodash "^4.17.20" path-is-inside "^1.0.2" - probe-image-size "^4.0.0" - unist-util-visit "^1.3.0" + probe-image-size "^5.0.0" + unist-util-visit "^1.4.1" 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== + version "3.2.4" + resolved "https://registry.yarnpkg.com/gatsby-remark-embed-snippet/-/gatsby-remark-embed-snippet-3.2.4.tgz#06dc7c8e47ba754dcb084bebcd1d9f9f2f1868f0" + integrity sha512-zK10vqDQ7SD6TbGM6Xd6SXJn7Prjtjx/uN+JO/Uvl8xqR5Raw6rSW82u41q7aIGDT4F8cNf/okVJLW6tH3XZtQ== dependencies: "@babel/runtime" "^7.0.0" normalize-path "^2.1.1" @@ -5502,6 +7088,7 @@ gatsby-remark-embed-snippet@^3.0.0: gatsby-remark-external-links@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/gatsby-remark-external-links/-/gatsby-remark-external-links-0.0.4.tgz#85b98c1e9dacfaa58085319648c904ff3cab42f0" + integrity sha512-JIKZguAGoGlzsJusfCb4JKM5E6JUEDbtlBkbErt7CdMnfBP+AldZeMQEQWK5xsJ5uXCyc4qqcBWR8vp0afFpOw== dependencies: babel-runtime "^6.26.0" is-relative-url "^2.0.0" @@ -5509,9 +7096,9 @@ gatsby-remark-external-links@^0.0.4: unist-util-visit "^1.1.3" gatsby-remark-images@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/gatsby-remark-images/-/gatsby-remark-images-2.0.4.tgz#29894b85b4181527fa34514fb73218c8e7f89129" - integrity sha512-J9uvZac/a4eTRZlsDmDDiHUbaqWh4vIbDB1cTMWk4Pq/b8UPvjxqQrdFuUmDq93Cko+VHORNKAvF2VwhjzSQNg== + version "2.0.6" + resolved "https://registry.yarnpkg.com/gatsby-remark-images/-/gatsby-remark-images-2.0.6.tgz#7567b40be11fec8931084f27d8b3aa18ccc4d5fc" + integrity sha512-1gXW1Gm/qiUS6YBZU//lH8gwB6SHbu2jdZlCxIzbSl4GvQ/P1zvo1hkOmxEScmit2FC4g4UrnLco/BbWAgNj1A== dependencies: "@babel/runtime" "^7.0.0" cheerio "^1.0.0-rc.2" @@ -5522,211 +7109,268 @@ gatsby-remark-images@^2.0.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== + version "3.5.13" + resolved "https://registry.yarnpkg.com/gatsby-remark-prismjs/-/gatsby-remark-prismjs-3.5.13.tgz#861c3646f777cc33cc0f503d745253bcefbed08b" + integrity sha512-dfDacOdbKq+u9FanylDIt+BrKMyw7eocNpoKSWXMXPXMWeo6ouiWQMLp8udtcTPM6J7Sb0mJo8cn517E/oGa0A== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.11.2" 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" - resolved "https://registry.yarnpkg.com/gatsby-remark-responsive-iframe/-/gatsby-remark-responsive-iframe-2.0.5.tgz#a8ee276e38ce92f98746916c01d1c30e328048fd" - integrity sha512-RRMZglBz6brT4DGEIGkyrbPRrI6df8z92FmwIJcW/P8C6nIfKro7JHbYDf4FmTSgo6AucQmTWxFBbrTkEq6oIA== + version "2.4.14" + resolved "https://registry.yarnpkg.com/gatsby-remark-responsive-iframe/-/gatsby-remark-responsive-iframe-2.4.14.tgz#a498ec1a90fe7f13c8e41f029d2d9da83c7fbeb6" + integrity sha512-BsAyKplgZaLAXGd5d1DNBkuk3xS5rSiQn6J5Rx2ZQi4PznZiCuHAdOzFHRq/K/mQw8rEsesy1bsf6p/gQltwlw== dependencies: - "@babel/runtime" "^7.0.0" - bluebird "^3.5.0" - cheerio "^1.0.0-rc.2" - lodash "^4.17.10" - unist-util-visit "^1.3.0" + "@babel/runtime" "^7.11.2" + cheerio "^1.0.0-rc.3" + common-tags "^1.8.0" + lodash "^4.17.20" + unist-util-visit "^1.4.1" gatsby-remark-smartypants@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/gatsby-remark-smartypants/-/gatsby-remark-smartypants-2.0.5.tgz#806d177df72f17fd39a7e50f95b9a1f784358a82" - integrity sha512-8W77kZVggIQsJy/A5HWGUYIhdJTcJGzxXFPVV32BarxHOkblYXPMgmkf4EJW1H9eUoH03R158jfgI7gzcKRHJw== + version "2.3.11" + resolved "https://registry.yarnpkg.com/gatsby-remark-smartypants/-/gatsby-remark-smartypants-2.3.11.tgz#c4b4a93ec95560c9d0e309322196dbf8fefe010b" + integrity sha512-IY4cp1KzQE0yJCHvCQt7sK47oEtzdxNPF3SAjRDmXsEgsk7h+GnIljv+Y+n43cLMsr1UsY3n9so1mmSuTx94zg== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.11.2" retext "^5.0.0" - retext-smartypants "^3.0.0" - unist-util-visit "^1.3.0" + retext-smartypants "^3.0.3" + unist-util-visit "^1.4.1" gatsby-source-filesystem@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/gatsby-source-filesystem/-/gatsby-source-filesystem-2.0.3.tgz#2436e93cd473a4338719f27ab44242b68e3e5fbf" - integrity sha512-pOhZ7PKBBAdSGIKCW8bP4o8gVVt9J+srJAQCHKSOZRUhJpRX/eTg2N6bTG8SKDoLivqGyQWDWNPStEtHnxXb7w== - dependencies: - "@babel/runtime" "^7.0.0" - better-queue "^3.8.7" - bluebird "^3.5.0" - chokidar "^1.7.0" - fs-extra "^5.0.0" - got "^7.1.0" - md5-file "^3.1.1" - mime "^2.2.0" - pretty-bytes "^4.0.2" - slash "^1.0.0" + version "2.3.30" + resolved "https://registry.yarnpkg.com/gatsby-source-filesystem/-/gatsby-source-filesystem-2.3.30.tgz#4d5298ed1470c0aa9ae9d5e735191d4b5ad6ad23" + integrity sha512-jZ6fWTT0a/aUMmhEyGcxEc8IdTJvxA/806qkan/5Mhctt4tjqmjSq8TWC0yde+0uHBWBf9krbzKtuT/+hT9KuA== + dependencies: + "@babel/runtime" "^7.11.2" + better-queue "^3.8.10" + bluebird "^3.7.2" + chokidar "^3.4.2" + file-type "^12.4.2" + fs-extra "^8.1.0" + gatsby-core-utils "^1.3.20" + got "^9.6.0" + md5-file "^3.2.3" + mime "^2.4.6" + pretty-bytes "^5.3.0" + progress "^2.0.3" + read-chunk "^3.2.0" valid-url "^1.0.9" - xstate "^3.1.0" + xstate "^4.11.0" + +gatsby-telemetry@^1.3.35: + version "1.3.35" + resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-1.3.35.tgz#e188b7dac1c6edb0b908a7f67bb12082d9dc79c5" + integrity sha512-MFMQl5KCOO6Xzlp2JMO4bRbsh1rjQDsbkJRZgYZB9izmPSK8AgNrHCjruxZC448ndtUfIVwjHnV+/K18XuPCHw== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.11.2" + "@turist/fetch" "^7.1.7" + "@turist/time" "^0.0.1" + async-retry-ng "^2.0.1" + boxen "^4.2.0" + configstore "^5.0.1" + envinfo "^7.7.3" + fs-extra "^8.1.0" + gatsby-core-utils "^1.3.20" + git-up "^4.0.2" + is-docker "^2.1.1" + lodash "^4.17.20" + node-fetch "^2.6.0" + uuid "3.4.0" gatsby-transformer-remark@^2.0.0: - version "2.1.7" - resolved "https://registry.yarnpkg.com/gatsby-transformer-remark/-/gatsby-transformer-remark-2.1.7.tgz#09abb259666c819d01b9832a8e9e49546c89a895" - integrity sha512-s6/n70/VU7SVb9hTGruymU7r4LtEedIF+65g61+M8fusABWzPjm9E5w3OfpmXJNSSHPG3w4dLer0OEPJOY/KmA== - dependencies: - "@babel/runtime" "^7.0.0" - bluebird "^3.5.0" - gray-matter "^4.0.0" - hast-util-raw "^2.0.2" - hast-util-to-html "^3.0.0" - lodash "^4.17.10" - mdast-util-to-hast "^3.0.0" - mdast-util-toc "^2.0.1" - remark "^9.0.0" - remark-parse "^5.0.0" - remark-retext "^3.1.0" - remark-stringify "^5.0.0" - retext-english "^3.0.0" - sanitize-html "^1.18.2" - underscore.string "^3.3.4" - unified "^6.1.5" - unist-util-remove-position "^1.1.2" + version "2.8.35" + resolved "https://registry.yarnpkg.com/gatsby-transformer-remark/-/gatsby-transformer-remark-2.8.35.tgz#590f2818644c3e1ff0c67a9894b5bfa90132ac4a" + integrity sha512-dIzl5OKgH3eeH8mJZ45GxgUVbClGp9VulGVVhJAnG7H5v3AMMey8JGVoUyECbAd42HH+2YoebYrvsEKExYeHFA== + dependencies: + "@babel/runtime" "^7.11.2" + bluebird "^3.7.2" + gatsby-core-utils "^1.3.20" + gray-matter "^4.0.2" + hast-util-raw "^4.0.0" + hast-util-to-html "^4.0.1" + lodash "^4.17.20" + mdast-util-to-hast "^3.0.4" + mdast-util-to-string "^1.1.0" + mdast-util-toc "^5.0" + remark "^10.0.1" + remark-parse "^6.0.3" + remark-retext "^3.1.3" + remark-stringify "6.0.4" + retext-english "^3.0.4" + sanitize-html "^1.27.0" + underscore.string "^3.3.5" + unified "^6.2.0" + unist-util-remove-position "^1.1.4" unist-util-select "^1.5.0" - unist-util-visit "^1.3.0" - -gatsby-transformer-sharp@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/gatsby-transformer-sharp/-/gatsby-transformer-sharp-2.1.3.tgz#9929919884e9edaab0179545c23d1cfc497c5070" - integrity sha512-irEGZBcYp52oU/ACASRYuMYBvztOrf4RoKVbyZbKjJLI9rZPVom8wnLHK0cxGxHWgbvsyX63I1gpt7lENPFTSw== - dependencies: - "@babel/runtime" "^7.0.0" - bluebird "^3.5.0" - fs-extra "^4.0.2" - potrace "^2.1.1" - probe-image-size "^4.0.0" - sharp "^0.20.2" - -gatsby@^2.0.0: - version "2.0.21" - resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-2.0.21.tgz#c182ad534f5f6cc19cc1dbf0ac6c267b39e42652" - integrity sha512-Wh8AsbzqrmQhI+VwZxRESN9/qtrwtSF8OIQZ1l14ydcH2vGcjsWws11Nbe15BITYfoT9cqVMxMVR8iDeR+6yPA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/core" "^7.0.0" - "@babel/parser" "^7.0.0" - "@babel/plugin-proposal-class-properties" "^7.0.0" - "@babel/plugin-syntax-dynamic-import" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.0.0" - "@babel/polyfill" "^7.0.0" - "@babel/preset-env" "^7.0.0" - "@babel/preset-react" "^7.0.0" - "@babel/runtime" "^7.0.0" - "@babel/traverse" "^7.0.0" - "@reach/router" "^1.1.1" - autoprefixer "^8.6.5" + unist-util-visit "^1.4.1" + +gatsby-transformer-sharp@^2.3.18: + version "2.5.15" + resolved "https://registry.yarnpkg.com/gatsby-transformer-sharp/-/gatsby-transformer-sharp-2.5.15.tgz#e48e0eb9a146a74330a588addae8988fabf86ada" + integrity sha512-Ey5MxC8zjq7CidltROudmK99zHKd2HLaSpVXMx+/VmXhFvSJ3I+g+QJQUltF155a9CJ7Q7RGTgAMGcfrzBrF8Q== + dependencies: + "@babel/runtime" "^7.11.2" + bluebird "^3.7.2" + fs-extra "^9.0.1" + potrace "^2.1.8" + probe-image-size "^5.0.0" + semver "^7.3.2" + sharp "^0.25.4" + +gatsby@^2.24.63: + version "2.24.63" + resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-2.24.63.tgz#c4d34729831d125709b498b61e90eae5611f09ac" + integrity sha512-nUsva6x9Cih2PpWWoTqr4o+F1zcliioYEPphL5Q90p5eqW5balJa874fzDBKwKbpzHK3gw71myITwzZvVhgxIg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/core" "^7.11.6" + "@babel/parser" "^7.11.5" + "@babel/runtime" "^7.11.2" + "@babel/traverse" "^7.11.5" + "@babel/types" "^7.11.5" + "@hapi/joi" "^15.1.1" + "@mikaelkristiansson/domready" "^1.0.10" + "@pieh/friendly-errors-webpack-plugin" "1.7.0-chalk-2" + "@pmmmwh/react-refresh-webpack-plugin" "^0.4.1" + "@reach/router" "^1.3.4" + "@types/http-proxy" "^1.17.4" + "@typescript-eslint/eslint-plugin" "^2.24.0" + "@typescript-eslint/parser" "^2.24.0" + address "1.1.2" + autoprefixer "^9.8.4" + axios "^0.19.2" babel-core "7.0.0-bridge.0" - babel-eslint "^8.2.2" - babel-loader "8.0.0-beta.4" - babel-plugin-add-module-exports "^0.2.1" - babel-plugin-dynamic-import-node "^1.2.0" - babel-plugin-macros "^2.4.0" - babel-plugin-remove-graphql-queries "^2.0.2-rc.3" - better-queue "^3.8.6" - bluebird "^3.5.0" - cache-manager "^2.9.0" - cache-manager-fs-hash "^0.0.6" - chalk "^2.3.2" - chokidar "^2.0.2" - common-tags "^1.4.0" - compression "^1.7.3" - convert-hrtime "^2.0.0" - copyfiles "^1.2.0" - core-js "^2.5.0" - css-loader "^1.0.0" - debug "^3.1.0" - del "^3.0.0" - detect-port "^1.2.1" - devcert-san "^0.3.3" - domready "^1.0.8" - dotenv "^4.0.0" - eslint "^4.19.1" - eslint-config-react-app "3.0.0-next.66cc7a90" - eslint-loader "^2.0.0" - eslint-plugin-flowtype "^2.46.1" - eslint-plugin-graphql "^2.0.0" - eslint-plugin-import "^2.9.0" - eslint-plugin-jsx-a11y "^6.0.3" - eslint-plugin-react "^7.8.2" - express "^4.16.3" - express-graphql "^0.6.12" - fast-levenshtein "~2.0.4" + babel-eslint "^10.1.0" + babel-loader "^8.1.0" + babel-plugin-add-module-exports "^0.3.3" + babel-plugin-dynamic-import-node "^2.3.3" + babel-plugin-lodash "3.3.4" + babel-plugin-remove-graphql-queries "^2.9.19" + babel-preset-gatsby "^0.5.10" + better-opn "1.0.0" + better-queue "^3.8.10" + bluebird "^3.7.2" + body-parser "^1.19.0" + browserslist "^4.12.2" + cache-manager "^2.11.1" + cache-manager-fs-hash "^0.0.9" + chalk "^2.4.2" + chokidar "^3.4.2" + common-tags "^1.8.0" + compression "^1.7.4" + convert-hrtime "^3.0.0" + copyfiles "^2.3.0" + core-js "^3.6.5" + cors "^2.8.5" + css-loader "^1.0.1" + date-fns "^2.14.0" + debug "^3.2.6" + del "^5.1.0" + detect-port "^1.3.0" + devcert "^1.1.3" + dotenv "^8.2.0" + eslint "^6.8.0" + eslint-config-react-app "^5.2.1" + eslint-loader "^2.2.1" + eslint-plugin-flowtype "^3.13.0" + eslint-plugin-graphql "^3.1.1" + eslint-plugin-import "^2.22.0" + eslint-plugin-jsx-a11y "^6.3.1" + eslint-plugin-react "^7.20.6" + eslint-plugin-react-hooks "^1.7.0" + event-source-polyfill "^1.0.15" + execa "^4.0.3" + express "^4.17.1" + express-graphql "^0.9.0" + fast-levenshtein "^2.0.6" file-loader "^1.1.11" - flat "^4.0.0" - friendly-errors-webpack-plugin "^1.6.1" - fs-extra "^5.0.0" - gatsby-cli "^2.4.3" - gatsby-link "^2.0.4" - gatsby-plugin-page-creator "^2.0.1" - gatsby-react-router-scroll "^2.0.0" - glob "^7.1.1" - graphql "^0.13.2" - graphql-relay "^0.5.5" - graphql-skip-limit "^2.0.0" - graphql-tools "^3.0.4" - graphql-type-json "^0.2.1" - hash-mod "^0.0.5" + find-cache-dir "^3.3.1" + fs-exists-cached "1.0.0" + fs-extra "^8.1.0" + gatsby-cli "^2.12.99" + gatsby-core-utils "^1.3.20" + gatsby-graphiql-explorer "^0.4.14" + gatsby-legacy-polyfills "^0.0.4" + gatsby-link "^2.4.14" + gatsby-plugin-page-creator "^2.3.28" + gatsby-plugin-typescript "^2.4.20" + gatsby-react-router-scroll "^3.0.13" + gatsby-telemetry "^1.3.35" + glob "^7.1.6" + got "8.3.2" + graphql "^14.6.0" + graphql-compose "^6.3.8" + graphql-playground-middleware-express "^1.7.18" + hasha "^5.2.0" + http-proxy "^1.18.1" invariant "^2.2.4" is-relative "^1.0.0" - is-relative-url "^2.0.0" - jest-worker "^23.2.0" - joi "12.x.x" + is-relative-url "^3.0.0" + is-wsl "^2.2.0" + jest-worker "^24.9.0" json-loader "^0.5.7" json-stringify-safe "^5.0.1" - kebab-hash "^0.1.2" - lodash "^4.17.10" - md5 "^2.2.1" - md5-file "^3.1.1" - mime "^2.2.0" - mini-css-extract-plugin "^0.4.0" - mitt "^1.1.2" + latest-version "5.1.0" + lodash "^4.17.20" + md5-file "^3.2.3" + meant "^1.0.1" + micromatch "^3.1.10" + mime "^2.4.6" + mini-css-extract-plugin "^0.8.2" + mitt "^1.2.0" mkdirp "^0.5.1" - moment "^2.21.0" + moment "^2.27.0" name-all-modules-plugin "^1.0.1" - normalize-path "^2.1.1" - null-loader "^0.1.1" - opentracing "^0.14.3" - opn "^5.3.0" - optimize-css-assets-webpack-plugin "^5.0.1" - parse-filepath "^1.0.1" + normalize-path "^3.0.0" + null-loader "^3.0.0" + opentracing "^0.14.4" + optimize-css-assets-webpack-plugin "^5.0.3" + p-defer "^3.0.0" + parseurl "^1.3.3" physical-cpu-count "^2.0.0" - postcss-flexbugs-fixes "^3.0.0" - postcss-loader "^2.1.3" + pnp-webpack-plugin "^1.6.4" + postcss-flexbugs-fixes "^4.2.1" + postcss-loader "^3.0.0" + prompts "^2.3.2" + prop-types "^15.7.2" + query-string "^6.13.1" raw-loader "^0.5.1" - react-dev-utils "^4.2.1" + react-dev-utils "^4.2.3" react-error-overlay "^3.0.0" - react-hot-loader "^4.1.0" - redux "^3.6.0" - relay-compiler "1.5.0" - request "^2.85.0" + react-hot-loader "^4.12.21" + react-refresh "^0.7.0" + redux "^4.0.5" + redux-thunk "^2.3.0" + semver "^7.3.2" shallow-compare "^1.2.2" - sift "^5.1.0" - signal-exit "^3.0.2" - slash "^1.0.0" - socket.io "^2.0.3" - string-similarity "^1.2.0" - style-loader "^0.21.0" - terser-webpack-plugin "^1.0.2" + signal-exit "^3.0.3" + slugify "^1.4.4" + socket.io "^2.3.0" + socket.io-client "2.3.0" + st "^2.0.0" + stack-trace "^0.0.10" + string-similarity "^1.2.2" + style-loader "^0.23.1" + terser-webpack-plugin "^1.4.4" + tmp "^0.2.1" + "true-case-path" "^2.2.1" type-of "^2.0.1" - url-loader "^1.0.1" - uuid "^3.1.0" - v8-compile-cache "^1.1.0" - webpack "^4.12.0" - webpack-dev-middleware "^3.0.1" - webpack-dev-server "^3.1.1" - webpack-hot-middleware "^2.21.0" - webpack-merge "^4.1.0" - webpack-stats-plugin "^0.1.5" - yaml-loader "^0.5.0" + url-loader "^1.1.2" + util.promisify "^1.0.1" + uuid "3.4.0" + v8-compile-cache "^1.1.2" + webpack "^4.44.1" + webpack-dev-middleware "^3.7.2" + webpack-dev-server "^3.11.0" + webpack-hot-middleware "^2.25.0" + webpack-merge "^4.2.2" + webpack-stats-plugin "^0.3.1" + webpack-virtual-modules "^0.2.2" + xstate "^4.11.0" + yaml-loader "^0.6.0" gauge@~2.7.3: version "2.7.4" @@ -5742,23 +7386,35 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" -get-port@^3.0.0: +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + +get-port@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= -get-proxy@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-1.1.0.tgz#894854491bc591b0f147d7ae570f5c678b7256eb" - integrity sha1-iUhUSRvFkbDxR9euVw9cZ4tyVus= - dependencies: - rc "^1.1.2" - get-proxy@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" @@ -5776,18 +7432,32 @@ get-stdin@^5.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" integrity sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g= +get-stream@3.0.0, get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + get-stream@^2.2.0: version "2.3.1" - resolved "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4= dependencies: object-assign "^4.0.1" pinkie-promise "^2.0.0" -get-stream@^3.0.0: - version "3.0.0" - resolved "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= +get-stream@^4.0.0, get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" @@ -5801,22 +7471,31 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +gifwrap@^0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.2.tgz#348e286e67d7cf57942172e1e6f05a71cee78489" + integrity sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA== + dependencies: + image-q "^1.1.1" + omggif "^1.0.10" + +git-up@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.2.tgz#10c3d731051b366dc19d3df454bfca3f77913a7c" + integrity sha512-kbuvus1dWQB2sSW4cbfTeGpCMd8ge9jx9RKnhXhuJ7tnvT+NIrTVfYZxjtflZddQYcmdOTlkAcjmx7bor+15AQ== + dependencies: + is-ssh "^1.3.0" + parse-url "^5.0.0" + github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -github-slugger@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.0.tgz#8ada3286fd046d8951c3c952a8d7854cfd90fd9a" - integrity sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q== - dependencies: - emoji-regex ">=6.0.0 <=6.1.1" - github-slugger@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.1.tgz#47e904e70bf2dccd0014748142d31126cfd49508" - integrity sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ== + version "1.3.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" + integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== dependencies: emoji-regex ">=6.0.0 <=6.1.1" @@ -5831,22 +7510,7 @@ glamor@^2.20.40: prop-types "^15.5.10" through "^2.3.8" -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^3.0.0, glob-parent@^3.1.0: +glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= @@ -5854,40 +7518,17 @@ glob-parent@^3.0.0, glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-stream@^5.3.2: - version "5.3.5" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" - integrity sha1-pVZlqajM3EGRWofHAeMtTgFvrSI= - dependencies: - extend "^3.0.0" - glob "^5.0.3" - glob-parent "^3.0.0" - micromatch "^2.3.7" - ordered-read-streams "^0.3.0" - through2 "^0.6.0" - to-absolute-glob "^0.1.1" - unique-stream "^2.0.2" - -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= - -glob@^5.0.3: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" + is-glob "^4.0.1" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== +glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -5896,12 +7537,12 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= +global-dirs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" + integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== dependencies: - ini "^1.3.4" + ini "^1.3.5" global-modules@1.0.0, global-modules@^1.0.0: version "1.0.0" @@ -5923,7 +7564,15 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -global@^4.3.0, global@~4.3.0: +global@^4.3.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + +global@~4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8= @@ -5932,26 +7581,42 @@ global@^4.3.0, global@~4.3.0: process "~0.5.1" globals@^11.0.1, globals@^11.1.0: - version "11.8.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d" - integrity sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA== - -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - -globby@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" - integrity sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0= - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@^10.0.0, globby@^10.0.1: + version "10.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" + integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== + dependencies: + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" + +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" globby@^6.1.0: version "6.1.0" @@ -5964,72 +7629,51 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.1.tgz#b5ad48b8aa80b35b814fc1281ecc851f1d2b5b50" - integrity sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw== - dependencies: - array-union "^1.0.1" - dir-glob "^2.0.0" - fast-glob "^2.0.2" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - -glogg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" - integrity sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw== - dependencies: - sparkles "^1.0.0" - -good-listener@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" - integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= - dependencies: - delegate "^3.1.2" - -got@^5.0.0: - version "5.7.1" - resolved "http://registry.npmjs.org/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" - integrity sha1-X4FjWmHkplifGAVp6k44FoClHzU= - dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^3.0.0" - unzip-response "^1.0.2" - url-parse-lax "^1.0.0" - -got@^6.7.1: - version "6.7.1" - resolved "http://registry.npmjs.org/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= +got@8.3.2, got@^8.3.1: + version "8.3.2" + resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" + integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== dependencies: - create-error-class "^3.0.0" + "@sindresorhus/is" "^0.7.0" + cacheable-request "^2.1.1" + decompress-response "^3.3.0" duplexer3 "^0.1.4" get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" + into-stream "^3.1.0" + is-retry-allowed "^1.1.0" + isurl "^1.0.0-alpha5" lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" + mimic-response "^1.0.0" + p-cancelable "^0.4.0" + p-timeout "^2.0.1" + pify "^3.0.0" + safe-buffer "^5.1.1" + timed-out "^4.0.1" + url-parse-lax "^3.0.0" + url-to-options "^1.0.1" -got@^7.0.0, got@^7.1.0: +got@^10.7.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/got/-/got-10.7.0.tgz#62889dbcd6cca32cd6a154cc2d0c6895121d091f" + integrity sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg== + dependencies: + "@sindresorhus/is" "^2.0.0" + "@szmarczak/http-timer" "^4.0.0" + "@types/cacheable-request" "^6.0.1" + cacheable-lookup "^2.0.0" + cacheable-request "^7.0.1" + decompress-response "^5.0.0" + duplexer3 "^0.1.4" + get-stream "^5.0.0" + lowercase-keys "^2.0.0" + mimic-response "^2.1.0" + p-cancelable "^2.0.0" + p-event "^4.0.0" + responselike "^2.0.0" + to-readable-stream "^2.0.0" + type-fest "^0.10.0" + +got@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== @@ -6049,25 +7693,40 @@ got@^7.0.0, got@^7.1.0: url-parse-lax "^1.0.0" url-to-options "^1.0.1" -graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.6: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= - -graceful-fs@^4.1.2: - version "4.1.15" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" - integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +graphql-compose@^6.3.8: + version "6.3.8" + resolved "https://registry.yarnpkg.com/graphql-compose/-/graphql-compose-6.3.8.tgz#9f82a85d5001a83adf1f7c4d3b5e5f72c432a062" + integrity sha512-o0/jzQEMIpSjryLKwmD1vGrCubiPxD0LxlGTgWDSu38TBepu2GhugC9gYgTEbtiCZAHPtvkZ90SzzABOWZyQLA== + dependencies: + graphql-type-json "^0.2.4" + object-path "^0.11.4" graphql-config@^2.0.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-2.2.1.tgz#5fd0ec77ac7428ca5fb2026cf131be10151a0cb2" - integrity sha512-U8+1IAhw9m6WkZRRcyj8ZarK96R6lQBQ0an4lp76Ps9FyhOXENC5YQOxOFGm5CxPrX2rD0g3Je4zG5xdNJjwzQ== + version "2.2.2" + resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-2.2.2.tgz#a4b577826bba9b83e7b0f6cd617be43ca67da045" + integrity sha512-mtv1ejPyyR2mJUUZNhljggU+B/Xl8tJJWf+h145hB+1Y48acSghFalhNtXfPBcYl2tJzpb+lGxfj3O7OjaiMgw== dependencies: graphql-import "^0.7.1" graphql-request "^1.5.0" @@ -6083,10 +7742,19 @@ graphql-import@^0.7.1: lodash "^4.17.4" resolve-from "^4.0.0" -graphql-relay@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/graphql-relay/-/graphql-relay-0.5.5.tgz#d6815e6edd618e878d5d921c13fc66033ec867e2" - integrity sha1-1oFebt1hjoeNXZIcE/xmAz7IZ+I= +graphql-playground-html@^1.6.28: + version "1.6.28" + resolved "https://registry.yarnpkg.com/graphql-playground-html/-/graphql-playground-html-1.6.28.tgz#4a7c2e368c3f44deb7e86b70d3782b65edc64213" + integrity sha512-22uwTEGjZg0h9rYcM7WspcMPVsixAE8m56tNzwjGr2Y3pNY7OctbsMkJ3EPtPcL6ZdUpzsa4rMgYR54BGmTrpQ== + dependencies: + xss "^1.0.6" + +graphql-playground-middleware-express@^1.7.18: + version "1.7.21" + resolved "https://registry.yarnpkg.com/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.7.21.tgz#e3af11848232000a2d61ab3c216d467fb6cf8376" + integrity sha512-CjPHDZqJ8ifS6v+JCyEZOEGrR8eKHWaUIUawggfUlW1xFFHCNcBhG4/S7EnSUspaUldSnL/cFcBp4yLhYkG53A== + dependencies: + graphql-playground-html "^1.6.28" graphql-request@^1.5.0: version "1.8.2" @@ -6095,28 +7763,22 @@ graphql-request@^1.5.0: dependencies: cross-fetch "2.2.2" -graphql-skip-limit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/graphql-skip-limit/-/graphql-skip-limit-2.0.0.tgz#42c7831097adaa4bf9d94a46246740916d8f30af" - integrity sha512-+kPBlN6njgKljSSvQz8Y1D0BZNdCfTeRBzznEz6SVOQ0Ul24oRDNxtpWZmownKWsI0c1Y68RtlpvWb2YXSxiVA== - dependencies: - "@babel/runtime" "^7.0.0" - -graphql-tools@^3.0.4: - version "3.1.1" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-3.1.1.tgz#d593358f01e7c8b1671a17b70ddb034dea9dbc50" - integrity sha512-yHvPkweUB0+Q/GWH5wIG60bpt8CTwBklCSzQdEHmRUgAdEQKxw+9B7zB3dG7wB3Ym7M7lfrS4Ej+jtDZfA2UXg== +graphql-subscriptions@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11" + integrity sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA== dependencies: - apollo-link "^1.2.2" - apollo-utilities "^1.0.1" - deprecated-decorator "^0.1.6" - iterall "^1.1.3" - uuid "^3.1.0" + iterall "^1.2.1" -graphql-type-json@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/graphql-type-json/-/graphql-type-json-0.2.1.tgz#d2c177e2f1b17d87f81072cd05311c0754baa420" - integrity sha1-0sF34vGxfYf4EHLNBTEcB1S6pCA= +graphql-type-json@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/graphql-type-json/-/graphql-type-json-0.2.4.tgz#545af27903e40c061edd30840a272ea0a49992f9" + integrity sha512-/tq02ayMQjrG4oDFDRLLrPk0KvJXue0nVXoItBe7uAdbNXjQUu+HYCBdAmPLQoseVzUKKMzrhq2P/sfI76ON6w== + +graphql-type-json@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" + integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== graphql@^0.11.3: version "0.11.7" @@ -6125,17 +7787,17 @@ graphql@^0.11.3: dependencies: iterall "1.1.3" -graphql@^0.13.0, graphql@^0.13.2: - version "0.13.2" - resolved "http://registry.npmjs.org/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" - integrity sha512-QZ5BL8ZO/B20VA8APauGBg3GyEgZ19eduvpLWoq5x7gMmWnHoy8rlQWPLmWgFvo1yNgjSEFMesmS4R6pPr7xog== +graphql@^14.6.0: + version "14.7.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72" + integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA== dependencies: - iterall "^1.2.1" + iterall "^1.2.2" -gray-matter@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.1.tgz#375263c194f0d9755578c277e41b1c1dfdf22c7d" - integrity sha512-p0MADBEBl1CohV7nRZ8sVinBexEe3CKVhh0A0QIHKpcbRoxB0VgeMpRPjW/HBHIPLAKrpIIIm5mZ6hKu3E+iQg== +gray-matter@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.2.tgz#9aa379e3acaf421193fce7d2a28cebd4518ac454" + integrity sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw== dependencies: js-yaml "^3.11.0" kind-of "^6.0.2" @@ -6147,62 +7809,14 @@ gud@^1.0.0: resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== -gulp-decompress@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gulp-decompress/-/gulp-decompress-1.2.0.tgz#8eeb65a5e015f8ed8532cafe28454960626f0dc7" - integrity sha1-jutlpeAV+O2FMsr+KEVJYGJvDcc= - dependencies: - archive-type "^3.0.0" - decompress "^3.0.0" - gulp-util "^3.0.1" - readable-stream "^2.0.2" - -gulp-rename@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.4.0.tgz#de1c718e7c4095ae861f7296ef4f3248648240bd" - integrity sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg== - -gulp-sourcemaps@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c" - integrity sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw= - dependencies: - convert-source-map "^1.1.1" - graceful-fs "^4.1.2" - strip-bom "^2.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" - -gulp-util@^3.0.1: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08= +gulp-header@^1.7.1: + version "1.8.12" + resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" + integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" + concat-with-sourcemaps "*" + lodash.template "^4.4.0" through2 "^2.0.0" - vinyl "^0.5.0" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - integrity sha1-4oxNRdBey77YGDY86PnFkmIp/+U= - dependencies: - glogg "^1.0.0" gzip-size@3.0.0: version "3.0.0" @@ -6211,22 +7825,22 @@ gzip-size@3.0.0: dependencies: duplexer "^0.1.1" -handle-thing@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" - integrity sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ= +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" - integrity sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA== +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: - ajv "^5.3.0" + ajv "^6.12.3" har-schema "^2.0.0" has-ansi@^2.0.0: @@ -6253,22 +7867,20 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - integrity sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4= - dependencies: - sparkles "^1.0.0" +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== -has-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== has-to-string-tag-x@^1.2.0: version "1.4.1" @@ -6313,6 +7925,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.0, has@^1.0.1, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -6321,114 +7938,179 @@ has@^1.0.0, has@^1.0.1, has@^1.0.3: function-bind "^1.1.1" hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash-mod@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/hash-mod/-/hash-mod-0.0.5.tgz#daf1e4973a9116643467d54ee7690b43ef802ecc" - integrity sha1-2vHklzqRFmQ0Z9VO52kLQ++ALsw= + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812" - integrity sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA== + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hast-to-hyperscript@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-3.1.0.tgz#58ef4af5344f4da22f0622e072a8d5fa062693d3" - integrity sha512-/At2y6sQLTAcL6y+3hRQFcaBoRlKrmHSpvvdOZqRz6uI2YyjrU8rJ7e1LbmLtWUmzaIqKEdNSku+AJC0pt4+aw== +hasha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.0.tgz#33094d1f69c40a4a6ac7be53d5fe3ff95a269e0c" + integrity sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + +hast-to-hyperscript@9.0.0, hast-to-hyperscript@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.0.tgz#768fb557765fe28749169c885056417342d71e83" + integrity sha512-NJvMYU3GlMLs7hN3CRbsNlMzusVNkYBogVWDGybsuuVQ336gFLiD+q9qtFZT2meSHzln3pNISZWTASWothMSMg== dependencies: + "@types/unist" "^2.0.3" comma-separated-tokens "^1.0.0" - is-nan "^1.2.1" - kebab-case "^1.0.0" - property-information "^3.0.0" + property-information "^5.3.0" space-separated-tokens "^1.0.0" - trim "0.0.1" + style-to-object "^0.3.0" + unist-util-is "^4.0.0" + web-namespaces "^1.0.0" + +hast-to-hyperscript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-5.0.0.tgz#5106cbba78edb7c95e2e8a49079371eb196c1ced" + integrity sha512-DLl3eYTz8uwwzEubDUdCChsR5t5b2ne+yvHrA2h58Suq/JnN3+Gsb9Tc4iZoCCsykmFUc6UUpwxTmQXs0akSeg== + dependencies: + comma-separated-tokens "^1.0.0" + property-information "^4.0.0" + space-separated-tokens "^1.0.0" + style-to-object "^0.2.1" unist-util-is "^2.0.0" + web-namespaces "^1.1.2" -hast-util-from-parse5@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-2.1.0.tgz#f6123d83d3689630b097e13e430d16d9d1bd8884" - integrity sha1-9hI9g9NoljCwl+E+Qw0W2dG9iIQ= +hast-util-from-parse5@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-4.0.2.tgz#b7164a7ffc88da4f751dc7c2f801ff8d7c143bab" + integrity sha512-I6dtjsGtDqz4fmGSiFClFyiXdKhj5bPceS6intta7k/VDuiKz9P61C6hO6WMiNNmEm1b/EtBH8f+juvz4o0uwQ== dependencies: - camelcase "^3.0.0" - hastscript "^3.0.0" - property-information "^3.1.0" - vfile-location "^2.0.0" + ccount "^1.0.3" + hastscript "^4.0.0" + property-information "^4.0.0" + web-namespaces "^1.1.2" + xtend "^4.0.1" + +hast-util-from-parse5@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.0.tgz#b38793c81e1a99f5fd592a4a88fc2731dccd0f30" + integrity sha512-3ZYnfKenbbkhhNdmOQqgH10vnvPivTdsOJCri+APn0Kty+nRkDHArnaX9Hiaf8H+Ig+vkNptL+SRY/6RwWJk1Q== + dependencies: + "@types/parse5" "^5.0.0" + ccount "^1.0.0" + hastscript "^5.0.0" + property-information "^5.0.0" + vfile "^4.0.0" + web-namespaces "^1.0.0" hast-util-is-element@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.0.1.tgz#c76e8aafbdb6e5c83265bf50324e2f2e024eb12a" - integrity sha512-s/ggaNehYVqmLgTXEv12Lbb72bsOD2r5DhAqPgtDdaI/YFNXVzz0zHFVJnhjIjn7Nak8GbL4nzT2q0RA5div+A== + version "1.1.0" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425" + integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ== -hast-util-parse-selector@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.0.tgz#2175f18cdd697308fc3431d5c29a9e48dfa4817a" - integrity sha512-trw0pqZN7+sH9k7hPWCJNZUbWW2KroSIM/XpIy3G5ZMtx9LSabCyoSp4skJZ4q/eZ5UOBPtvWh4W9c+RE3HRoQ== +hast-util-parse-selector@^2.0.0, hast-util-parse-selector@^2.2.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.4.tgz#60c99d0b519e12ab4ed32e58f150ec3f61ed1974" + integrity sha512-gW3sxfynIvZApL4L07wryYF4+C9VvH3AUi7LAnVXV4MneGEgwOByXvFo18BgmTWnm7oHAe874jKbIB1YhHSIzA== -hast-util-raw@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-2.0.2.tgz#20674cfb45428213917a54ec929e6774df0642d8" - integrity sha512-ujytXSAZC85bvh38f8ALzfE2IZDdCwB9XeHUs9l20C1p4/1YeAoZqq9z9U17vWQ9hMmqbVaROuSK8feL3wTCJg== +hast-util-raw@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.0.tgz#49a38f5107d483f83a139709f2f705f22e7e7d32" + integrity sha512-IQo6tv3bMMKxk53DljswliucCJOQxaZFCuKEJ7X80249dmJ1nA9LtOnnylsLlqTG98NjQ+iGcoLAYo9q5FRhRg== + dependencies: + "@types/hast" "^2.0.0" + hast-util-from-parse5 "^6.0.0" + hast-util-to-parse5 "^6.0.0" + html-void-elements "^1.0.0" + parse5 "^6.0.0" + unist-util-position "^3.0.0" + vfile "^4.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hast-util-raw@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-4.0.0.tgz#2dc10c9facd9b810ea6ac51df251e6f87c2ed5b5" + integrity sha512-5xYHyEJMCf8lX/NT4iA5z6N43yoFsrJqXJ5GWwAbLn815URbIz+UNNFEgid33F9paZuDlqVKvB+K3Aqu5+DdSw== dependencies: - hast-util-from-parse5 "^2.0.0" - hast-util-to-parse5 "^2.0.0" + hast-util-from-parse5 "^4.0.2" + hast-util-to-parse5 "^4.0.1" html-void-elements "^1.0.1" - parse5 "^3.0.3" + parse5 "^5.0.0" unist-util-position "^3.0.0" web-namespaces "^1.0.0" + xtend "^4.0.1" zwitch "^1.0.0" -hast-util-to-html@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-3.1.0.tgz#882c99849e40130e991c042e456d453d95c36cff" - integrity sha1-iCyZhJ5AEw6ZHAQuRW1FPZXDbP8= +hast-util-to-html@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-4.0.1.tgz#3666b05afb62bd69f8f5e6c94db04dea19438e2a" + integrity sha512-2emzwyf0xEsc4TBIPmDJmBttIw8R4SXAJiJZoiRR/s47ODYWgOqNoDbf2SJAbMbfNdFWMiCSOrI3OVnX6Qq2Mg== dependencies: ccount "^1.0.0" comma-separated-tokens "^1.0.1" hast-util-is-element "^1.0.0" hast-util-whitespace "^1.0.0" html-void-elements "^1.0.0" - kebab-case "^1.0.0" - property-information "^3.1.0" + property-information "^4.0.0" space-separated-tokens "^1.0.0" stringify-entities "^1.0.1" unist-util-is "^2.0.0" xtend "^4.0.1" -hast-util-to-parse5@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-2.2.0.tgz#48c8f7f783020c04c3625db06109d02017033cbc" - integrity sha512-Eg1mrf0VTT/PipFN5z1+mVi+4GNhinKk/i/HKeX1h17IYiMdm3G8vgA0FU04XCuD1cWV58f5zziFKcBkr+WuKw== +hast-util-to-parse5@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-4.0.1.tgz#e52534b4bf40dc4e7d0428fcaf6d32bc75c62ee5" + integrity sha512-U/61W+fsNfBpCyJBB5Pt3l5ypIfgXqEyW9pyrtxF7XrqDJHzcFrYpnC94d0JDYjvobLpYCzcU9srhMRPEO1YXw== dependencies: - hast-to-hyperscript "^3.0.0" - mapz "^1.0.0" + hast-to-hyperscript "^5.0.0" + property-information "^4.0.0" web-namespaces "^1.0.0" xtend "^4.0.1" zwitch "^1.0.0" +hast-util-to-parse5@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" + integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== + dependencies: + hast-to-hyperscript "^9.0.0" + property-information "^5.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + hast-util-whitespace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.1.tgz#d67da2c87637b1ce1d85dd15b270ba057930149a" - integrity sha512-Mfx2ZnmVMTAopZ8as42nKrNt650tCZYhy/MPeO1Imdg/cmCWK6GUSnFrrE3ezGjVifn7x5zMfu8jrjwIGyImSw== + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" + integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== -hastscript@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-3.1.0.tgz#66628ba6d7f1ad07d9277dd09028aba7f4934599" - integrity sha512-8V34dMSDT1Ik+ZSgTzCLdyp89MrWxcxctXPxhmb72GQj1Xkw1aHPM9UaHCWewvH2Q+PVkYUm4ZJVw4T0dgEGNA== +hastscript@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-4.1.0.tgz#ea5593fa6f6709101fc790ced818393ddaa045ce" + integrity sha512-bOTn9hEfzewvHyXdbYGKqOr/LOz+2zYhKbC17U2YAjd16mnjqB1BQ0nooM/RdMy/htVyli0NAznXiBtwDi1cmQ== + dependencies: + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.2.0" + property-information "^4.0.0" + space-separated-tokens "^1.0.0" + +hastscript@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-5.1.2.tgz#bde2c2e56d04c62dd24e8c5df288d050a355fb8a" + integrity sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ== dependencies: - camelcase "^3.0.0" comma-separated-tokens "^1.0.0" hast-util-parse-selector "^2.0.0" - property-information "^3.0.0" + property-information "^5.0.0" space-separated-tokens "^1.0.0" hex-color-regex@^1.1.0: @@ -6441,6 +8123,24 @@ hex2rgba@^0.0.1: resolved "https://registry.yarnpkg.com/hex2rgba/-/hex2rgba-0.0.1.tgz#8701ba1c6ec02c204504158407c1c4b47a9336ed" integrity sha1-hwG6HG7ALCBFBBWEB8HEtHqTNu0= +hicat@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/hicat/-/hicat-0.7.0.tgz#a704cb3f57e49fbd7d38c2edd7aba38ff0b35263" + integrity sha1-pwTLP1fkn719OMLt16ujj/CzUmM= + dependencies: + highlight.js "^8.1.0" + minimist "^0.2.0" + +highlight-words-core@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa" + integrity sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg== + +highlight.js@^8.1.0: + version "8.9.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-8.9.1.tgz#b8a9c5493212a9392f0222b649c9611497ebfb88" + integrity sha1-uKnFSTISqTkvAiK2SclhFJfr+4g= + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -6450,27 +8150,31 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoek@4.x.x: - version "4.2.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" - integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== - -hoist-non-react-statics@^2.5.0: - version "2.5.5" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" - integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" homedir-polyfill@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" - integrity sha1-TCu8inWJmP7r9e1oWA921GdotLw= + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== dependencies: parse-passwd "^1.0.0" -hosted-git-info@^2.1.4, hosted-git-info@^2.6.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" - integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== +hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +hosted-git-info@^3.0.4: + version "3.0.5" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.5.tgz#bea87905ef7317442e8df3087faa3c842397df03" + integrity sha512-i4dpK6xj9BIpVOTboXIlKG9+8HMKggcrMX7WA24xZtKwX0TPelq/rbaS5rCKeNX8sJXZJGdSxpnEGtta+wismQ== + dependencies: + lru-cache "^6.0.0" hpack.js@^2.1.6: version "2.1.6" @@ -6497,85 +8201,122 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-entities@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" - integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= +html-entities@^1.2.0, html-entities@^1.2.1, html-entities@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" + integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== + +html-tag-names@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/html-tag-names/-/html-tag-names-1.1.5.tgz#f537420c16769511283f8ae1681785fbc89ee0a9" + integrity sha512-aI5tKwNTBzOZApHIynaAwecLBv8TlZTEy/P4Sj2SzzAhBrGuI8yGZ0UIXVPQzOHGS+to2mjb04iy6VWt/8+d8A== html-void-elements@^1.0.0, html-void-elements@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.3.tgz#956707dbecd10cf658c92c5d27fee763aa6aa982" - integrity sha512-SaGhCDPXJVNrQyKMtKy24q6IMdXg5FCPN3z+xizxw9l+oXQw5fOoaj/ERU5KqWhSYhXtW5bWthlDbTDLBhJQrA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" + integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== -htmlparser2@^3.9.0, htmlparser2@^3.9.1: - version "3.9.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" - integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg= +htmlparser2@^3.3.0, htmlparser2@^3.9.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== dependencies: - domelementtype "^1.3.0" + domelementtype "^1.3.1" domhandler "^2.3.0" domutils "^1.5.1" entities "^1.1.1" inherits "^2.0.1" - readable-stream "^2.0.2" + readable-stream "^3.1.1" -htmlparser2@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" - integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4= +htmlparser2@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" + integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q== dependencies: - domelementtype "1" - domhandler "2.1" - domutils "1.1" - readable-stream "1.0" + domelementtype "^2.0.1" + domhandler "^3.0.0" + domutils "^2.0.0" + entities "^2.0.0" + +http-cache-semantics@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" + integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= -http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: - version "1.6.3" - resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== dependencies: depd "~1.1.2" inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" -http-errors@^1.3.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.1.tgz#6a4ffe5d35188e1c39f872534690585852e1f027" - integrity sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw== +http-errors@1.7.3, http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== dependencies: depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@^1.7.3: + version "1.8.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" + integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -http-parser-js@>=0.4.0: - version "0.4.13" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" - integrity sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc= +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" + integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== -http-proxy-middleware@~0.18.0: - version "0.18.0" - resolved "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab" - integrity sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q== +http-proxy-middleware@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== dependencies: - http-proxy "^1.16.2" + http-proxy "^1.17.0" is-glob "^4.0.0" - lodash "^4.17.5" - micromatch "^3.1.9" + lodash "^4.17.11" + micromatch "^3.1.10" -http-proxy@^1.16.2: - version "1.17.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" - integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== +http-proxy@^1.17.0, http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== dependencies: - eventemitter3 "^3.0.0" + eventemitter3 "^4.0.0" follow-redirects "^1.0.0" requires-port "^1.0.0" @@ -6593,25 +8334,30 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -hyphenate-style-name@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b" - integrity sha1-MRYKNpMK2vH8BMYHT360FGXU7Es= +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -iconv-lite@0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" - integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== - dependencies: - safer-buffer ">= 2.1.2 < 3" +hyphenate-style-name@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== -iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -6625,71 +8371,67 @@ icss-utils@^2.1.0: postcss "^6.0.1" ieee754@^1.1.4: - version "1.1.12" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" - integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - -ignore@^3.3.3, ignore@^3.3.5: +ignore@^3.3.3: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== -imagemin-mozjpeg@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/imagemin-mozjpeg/-/imagemin-mozjpeg-7.0.0.tgz#d926477fc6ef5f3a768a4222f7b2d808d3eba568" - integrity sha1-2SZHf8bvXzp2ikIi97LYCNPrpWg= - dependencies: - execa "^0.8.0" - is-jpg "^1.0.0" - mozjpeg "^5.0.0" +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -imagemin-pngquant@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/imagemin-pngquant/-/imagemin-pngquant-6.0.0.tgz#7c0c956338fa9a3a535deb63973c1c894519cc78" - integrity sha512-lZ87Y7u0UaJuhtQZ2wkKyxsFeNTEv1C5xxoHN7jFD89rKpiC/Qu2cIYGAOypOsxqAxWlsHaoz0hJlFFdCnG6Zg== - dependencies: - execa "^0.10.0" - is-png "^1.0.0" - is-stream "^1.1.0" - pngquant-bin "^5.0.0" +ignore@^5.1.1, ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -imagemin-webp@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/imagemin-webp/-/imagemin-webp-4.1.0.tgz#effd00160d8456b95cbde5fd26c32d64b0318062" - integrity sha1-7/0AFg2EVrlcveX9JsMtZLAxgGI= - dependencies: - cwebp-bin "^4.0.0" - exec-buffer "^3.0.0" - is-cwebp-readable "^2.0.1" +image-q@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/image-q/-/image-q-1.1.1.tgz#fc84099664460b90ca862d9300b6bfbbbfbf8056" + integrity sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY= -imagemin@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/imagemin/-/imagemin-6.0.0.tgz#1ae68c6b867863651a454f882054d9abf7d13b78" - integrity sha512-m4Mxwt2QvCp1F85HXoTungXk0Y6XzuvQGqrK9qEddQfo/7x4aZjRENmyXXfc29ei4Mk55rW002bORG86YM3/aQ== +imagemin-mozjpeg@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/imagemin-mozjpeg/-/imagemin-mozjpeg-9.0.0.tgz#d1af26d0b43d75a41c211051c1910da59d9d2324" + integrity sha512-TwOjTzYqCFRgROTWpVSt5UTT0JeCuzF1jswPLKALDd89+PmrJ2PdMMYeDLYZ1fs9cTovI9GJd68mRSnuVt691w== dependencies: - file-type "^8.1.0" - globby "^8.0.1" - make-dir "^1.0.0" - p-pipe "^1.1.0" - pify "^3.0.0" - replace-ext "^1.0.0" + execa "^4.0.0" + is-jpg "^2.0.0" + mozjpeg "^7.0.0" -immutable@~3.7.6: - version "3.7.6" - resolved "http://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" - integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks= +imagemin-pngquant@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/imagemin-pngquant/-/imagemin-pngquant-9.0.1.tgz#ecf22f522bdb734a503ecc21bdd7bc3d0230edcc" + integrity sha512-PYyo9G/xwddf+Qqlqe3onz5ZH7p6vHYVVkiuuczUjxZmfekyY77RXaOA/AR6FnVoeQxGa/pDtEK5xUKOcVo+sA== + dependencies: + execa "^4.0.0" + is-png "^2.0.0" + is-stream "^2.0.0" + ow "^0.17.0" + pngquant-bin "^6.0.0" + +imagemin@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/imagemin/-/imagemin-7.0.1.tgz#f6441ca647197632e23db7d971fffbd530c87dbf" + integrity sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w== + dependencies: + file-type "^12.0.0" + globby "^10.0.0" + graceful-fs "^4.2.2" + junk "^3.1.0" + make-dir "^3.0.0" + p-pipe "^3.0.0" + replace-ext "^1.0.0" import-cwd@^2.0.0: version "2.1.0" @@ -6698,6 +8440,22 @@ import-cwd@^2.0.0: dependencies: import-from "^2.1.0" +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.0.0, import-fresh@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-from@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" @@ -6710,6 +8468,11 @@ import-lazy@^2.1.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= +import-lazy@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-3.1.0.tgz#891279202c8a2280fdbd6674dbd8da1a1dfc67cc" + integrity sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ== + import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -6730,6 +8493,11 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -6740,6 +8508,11 @@ indexof@0.0.1: resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= +infer-owner@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -6748,20 +8521,70 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= -ini@^1.3.4, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ink-box@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ink-box/-/ink-box-1.0.0.tgz#8cbcb5541d32787d08d43acf1a9907e86e3572f3" + integrity sha512-wD2ldWX9lcE/6+flKbAJ0TZF7gKbTH8CRdhEor6DD8d+V0hPITrrGeST2reDBpCia8wiqHrdxrqTyafwtmVanA== + dependencies: + boxen "^3.0.0" + prop-types "^15.7.2" + +ink-spinner@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ink-spinner/-/ink-spinner-3.1.0.tgz#a1090102663bf3cc90f1dbfb81f143378a892300" + integrity sha512-sPqmE4qeJ43vJFk9DGLd0wIqhMBAr3129ZqHPt7b847fVl+YTZ3g96khI82Db+FYE7v/Fc5B3lp4ZNtJfqpRUg== + dependencies: + cli-spinners "^1.0.0" + prop-types "^15.5.10" + +ink@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/ink/-/ink-2.7.1.tgz#ff1c75b4b022924e2993af62297fa0e48e85618b" + integrity sha512-s7lJuQDJEdjqtaIWhp3KYHl6WV3J04U9zoQ6wVc+Xoa06XM27SXUY57qC5DO46xkF0CfgXMKkKNcgvSu/SAEpA== + dependencies: + ansi-escapes "^4.2.1" + arrify "^2.0.1" + auto-bind "^4.0.0" + chalk "^3.0.0" + cli-cursor "^3.1.0" + cli-truncate "^2.1.0" + is-ci "^2.0.0" + lodash.throttle "^4.1.1" + log-update "^3.0.0" + prop-types "^15.6.2" + react-reconciler "^0.24.0" + scheduler "^0.18.0" + signal-exit "^3.0.2" + slice-ansi "^3.0.0" + string-length "^3.1.0" + widest-line "^3.1.0" + wrap-ansi "^6.2.0" + yoga-layout-prebuilt "^1.9.3" + +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== inline-style-prefixer@^3.0.6: version "3.0.8" @@ -6771,7 +8594,7 @@ inline-style-prefixer@^3.0.6: bowser "^1.7.3" css-in-js-utils "^2.0.0" -inquirer@3.3.0, inquirer@^3.0.1, inquirer@^3.0.6: +inquirer@3.3.0, inquirer@^3.0.6: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== @@ -6791,13 +8614,49 @@ inquirer@3.3.0, inquirer@^3.0.1, inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" -internal-ip@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-3.0.1.tgz#df5c99876e1d2eb2ea2d74f520e3f669a00ece27" - integrity sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q== +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +internal-ip@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== + dependencies: + default-gateway "^4.2.0" + ipaddr.js "^1.9.0" + +internal-slot@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3" + integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g== + dependencies: + es-abstract "^1.17.0-next.1" + has "^1.0.3" + side-channel "^1.0.2" + +into-stream@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" + integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= dependencies: - default-gateway "^2.6.0" - ipaddr.js "^1.5.2" + from2 "^2.1.1" + p-is-promise "^1.1.0" invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" @@ -6806,21 +8665,6 @@ invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - -ip-regex@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" - integrity sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0= - ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -6831,36 +8675,21 @@ ip@^1.1.0, ip@^1.1.5: resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= -ipaddr.js@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" - integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= - -ipaddr.js@^1.5.2: - version "1.8.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.1.tgz#fa4b79fa47fd3def5e3b159825161c0a519c9427" - integrity sha1-+kt5+kf9Pe9eOxWYJRYcClGclCc= +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-absolute@^0.1.5: - version "0.1.7" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.1.7.tgz#847491119fccb5fb436217cc737f7faad50f603f" - integrity sha1-hHSREZ/MtftDYhfMc39/qtUPYD8= - dependencies: - is-relative "^0.1.0" - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - +is-absolute-url@^3.0.0, is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -6876,9 +8705,9 @@ is-accessor-descriptor@^1.0.0: kind-of "^6.0.0" is-alphabetical@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41" - integrity sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== is-alphanumeric@^1.0.0: version "1.0.0" @@ -6886,13 +8715,18 @@ is-alphanumeric@^1.0.0: integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ= is-alphanumerical@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40" - integrity sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== dependencies: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -6910,39 +8744,41 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1: +is-binary-path@^2.1.0, is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^1.1.4, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" - integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== +is-buffer@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= +is-builtin-module@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.0.0.tgz#137d3d2425023a19a660fb9dd6ddfabe52c03466" + integrity sha512-/93sDihsAD652hrMEbJGbMAVBf1qc96kyThHQ0CAOONHaE3aROLpTjDe4WQ5aoC5ITHFxEq1z8XqSU7km+8amw== dependencies: - builtin-modules "^1.0.0" - -is-bzip2@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bzip2/-/is-bzip2-1.0.0.tgz#5ee58eaa5a2e9c80e21407bedf23ae5ac091b3fc" - integrity sha1-XuWOqlounIDiFAe+3yOuWsCRs/w= + builtin-modules "^3.0.0" -is-callable@^1.1.3, is-callable@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" - integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-ci@^1.0.10: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" - integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== dependencies: - ci-info "^1.5.0" + ci-info "^2.0.0" is-color-stop@^1.0.0: version "1.1.0" @@ -6956,12 +8792,12 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-cwebp-readable@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-cwebp-readable/-/is-cwebp-readable-2.0.1.tgz#afb93b0c0abd0a25101016ae33aea8aedf926d26" - integrity sha1-r7k7DAq9CiUQEBauM66ort+SbSY= +is-core-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" + integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== dependencies: - file-type "^4.3.0" + has "^1.0.3" is-data-descriptor@^0.1.4: version "0.1.4" @@ -6978,14 +8814,14 @@ is-data-descriptor@^1.0.0: kind-of "^6.0.0" is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= - -is-decimal@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff" - integrity sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg== + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-decimal@^1.0.0, is-decimal@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== is-descriptor@^0.1.0: version "0.1.6" @@ -7010,17 +8846,10 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= - dependencies: - is-primitive "^2.0.0" +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" @@ -7045,11 +8874,9 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= - dependencies: - number-is-nan "^1.0.0" + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== is-fullwidth-code-point@^1.0.0: version "1.0.0" @@ -7063,12 +8890,17 @@ is-fullwidth-code-point@^2.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-function@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" - integrity sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU= + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" + integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== -is-glob@^2.0.0, is-glob@^2.0.1: +is-glob@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= @@ -7082,64 +8914,52 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" -is-gzip@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" - integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM= - is-hexadecimal@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835" - integrity sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== -is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" - -is-jpg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-jpg/-/is-jpg-1.0.1.tgz#296d57fdd99ce010434a7283e346ab9a1035e975" - integrity sha1-KW1X/dmc4BBDSnKD40armhA16XU= + global-dirs "^2.0.1" + is-path-inside "^3.0.1" -is-nan@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" - integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= +is-invalid-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34" + integrity sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ= dependencies: - define-properties "^1.1.1" + is-glob "^2.0.0" -is-natural-number@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-2.1.1.tgz#7d4c5728377ef386c3e194a9911bf57c6dc335e7" - integrity sha1-fUxXKDd+84bD4ZSpkRv1fG3DNec= +is-jpg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-jpg/-/is-jpg-2.0.0.tgz#2e1997fa6e9166eaac0242daae443403e4ef1d97" + integrity sha1-LhmX+m6RZuqsAkLarkQ0A+TvHZc= is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= -is-npm@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== is-number@^3.0.0: version "3.0.0" @@ -7148,83 +8968,73 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0: - version "1.0.1" - resolved "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== is-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= +is-path-cwd@^2.0.0, is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-in-cwd@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" - integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ== +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== dependencies: - is-path-inside "^1.0.0" + is-path-inside "^2.1.0" -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== dependencies: - path-is-inside "^1.0.1" + path-is-inside "^1.0.2" + +is-path-inside@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" + integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" -is-png@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-png/-/is-png-1.1.0.tgz#d574b12bf275c0350455570b0e5b57ab062077ce" - integrity sha1-1XSxK/J1wDUEVVcLDltXqwYgd84= - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= - -is-primitive@^2.0.0: +is-png@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= - -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= + resolved "https://registry.yarnpkg.com/is-png/-/is-png-2.0.0.tgz#ee8cbc9e9b050425cedeeb4a6fb74a649b0a4a8d" + integrity sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g== -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= +is-regex@^1.0.4, is-regex@^1.1.0, is-regex@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== dependencies: - has "^1.0.1" + has-symbols "^1.0.1" is-relative-url@^2.0.0: version "2.0.0" @@ -7233,10 +9043,12 @@ is-relative-url@^2.0.0: dependencies: is-absolute-url "^2.0.0" -is-relative@^0.1.0: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.1.3.tgz#905fee8ae86f45b3ec614bc3c15c869df0876e82" - integrity sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI= +is-relative-url@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-relative-url/-/is-relative-url-3.0.0.tgz#f623c8e26baa5bd3742b3b7ec074f50f3b45b3f3" + integrity sha512-U1iSYRlY2GIMGuZx7gezlB5dp1Kheaym7zKzO1PV06mOihiWTXejLwm4poEJysPyXF+HtK/BEd0DVlcCh30pEA== + dependencies: + is-absolute-url "^3.0.0" is-relative@^1.0.0: version "1.0.0" @@ -7250,21 +9062,38 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-retry-allowed@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" - integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= +is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" + integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== is-root@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" integrity sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU= +is-ssh@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b" + integrity sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ== + dependencies: + protocols "^1.1.0" + is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + is-svg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" @@ -7273,18 +9102,13 @@ is-svg@^3.0.0: html-comment-regex "^1.1.0" is-symbol@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" - integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== dependencies: - has-symbols "^1.0.0" - -is-tar@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-tar/-/is-tar-1.0.0.tgz#2f6b2e1792c1f5bb36519acaa9d65c0d26fe853d" - integrity sha1-L2suF5LB9bs2UZrKqdZcDSb+hT0= + has-symbols "^1.0.1" -is-typedarray@~1.0.0: +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -7296,7 +9120,7 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" -is-url@^1.2.0: +is-url@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== @@ -7306,15 +9130,17 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-valid-glob@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" - integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4= +is-valid-path@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df" + integrity sha1-EQ+f90w39mPh7HkV60UfLbk6yd8= + dependencies: + is-invalid-path "^0.1.0" is-whitespace-character@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed" - integrity sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" + integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" @@ -7322,19 +9148,26 @@ is-windows@^1.0.1, is-windows@^1.0.2: integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== is-word-character@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.2.tgz#46a5dac3f2a1840898b91e576cd40d493f3ae553" - integrity sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" + integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -is-zip@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-zip/-/is-zip-1.0.0.tgz#47b0a8ff4d38a76431ccfd99a8e15a4c86ba2325" - integrity sha1-R7Co/004p2QxzP2ZqOFaTIa6IyU= +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== isarray@0.0.1: version "0.0.1" @@ -7351,13 +9184,6 @@ isarray@2.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= -isemail@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.3.tgz#64f37fc113579ea12523165c3ebe3a71a56ce571" - integrity sha512-5xbsG5wYADIcB+mfLsd+nst1V/D+I7EU7LEZPo2GOIMu4JzfcRs5yQoypP4avA7QtUqgxYLKBYNv4IdzBmbhdw== - dependencies: - punycode "2.x.x" - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -7401,63 +9227,54 @@ iterall@1.1.3: resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" integrity sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ== -iterall@^1.1.3, iterall@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" - integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== +iterall@^1.2.1, iterall@^1.2.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" + integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== + +jest-diff@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" + integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.5.0" jest-docblock@^21.0.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== -jest-worker@^23.2.0: - version "23.2.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9" - integrity sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk= - dependencies: - merge-stream "^1.0.1" - -jimp@^0.2.24: - version "0.2.28" - resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.2.28.tgz#dd529a937190f42957a7937d1acc3a7762996ea2" - integrity sha1-3VKak3GQ9ClXp5N9Gsw6d2KZbqI= - dependencies: - bignumber.js "^2.1.0" - bmp-js "0.0.3" - es6-promise "^3.0.2" - exif-parser "^0.1.9" - file-type "^3.1.0" - jpeg-js "^0.2.0" - load-bmfont "^1.2.3" - mime "^1.3.4" - mkdirp "0.5.1" - pixelmatch "^4.0.0" - pngjs "^3.0.0" - read-chunk "^1.0.1" - request "^2.65.0" - stream-to-buffer "^0.1.0" - tinycolor2 "^1.1.2" - url-regex "^3.0.0" - -joi@12.x.x: - version "12.0.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-12.0.0.tgz#46f55e68f4d9628f01bbb695902c8b307ad8d33a" - integrity sha512-z0FNlV4NGgjQN1fdtHYXf5kmgludM65fG/JlXzU6+rwkt9U5UWuXVYnXa2FpK0u6+qBuCmrm5byPNuiiddAHvQ== - dependencies: - hoek "4.x.x" - isemail "3.x.x" - topo "2.x.x" - -jpeg-js@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.2.0.tgz#53e448ec9d263e683266467e9442d2c5a2ef5482" - integrity sha1-U+RI7J0mPmgyZkZ+lELSxaLvVII= +jest-get-type@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" + integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== -js-levenshtein@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.4.tgz#3a56e3cbf589ca0081eb22cd9ba0b1290a16d26e" - integrity sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow== +jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + +jimp@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.14.0.tgz#fde55f69bdb918c1b01ac633d89a25853af85625" + integrity sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/custom" "^0.14.0" + "@jimp/plugins" "^0.14.0" + "@jimp/types" "^0.14.0" + regenerator-runtime "^0.13.3" + +jpeg-js@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" + integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" @@ -7469,42 +9286,39 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.10.0, js-yaml@^3.11.0, js-yaml@^3.12.0, js-yaml@^3.5.2, js-yaml@^3.9.0, js-yaml@^3.9.1: - version "3.12.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" - integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== +js-yaml@^3.10.0, js-yaml@^3.11.0, js-yaml@^3.13.1, js-yaml@^3.9.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== dependencies: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@~3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" - integrity sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A= - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= - jsesc@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" - integrity sha1-5CGiqOINawgZ3yiQj3glJrlt0f4= + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-loader@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" @@ -7515,6 +9329,11 @@ json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -7535,27 +9354,29 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stable-stringify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= json3@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= + version "3.3.3" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" + integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== -json5@^0.5.0: - version "0.5.1" - resolved "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" jsonfile@^4.0.0: version "4.0.0" @@ -7564,6 +9385,15 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" + integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + dependencies: + universalify "^1.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -7579,17 +9409,26 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jsx-ast-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" - integrity sha1-6AGxs5mF4g//yHtA43SAgOLcrH8= +jsx-ast-utils@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" + integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== dependencies: - array-includes "^3.0.3" + array-includes "^3.1.1" + object.assign "^4.1.0" -kebab-case@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/kebab-case/-/kebab-case-1.0.0.tgz#3f9e4990adcad0c686c0e701f7645868f75f91eb" - integrity sha1-P55JkK3K0MaGwOcB92RYaPdfkes= +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + +junk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" + integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== kebab-hash@^0.1.2: version "0.1.2" @@ -7598,7 +9437,28 @@ kebab-hash@^0.1.2: dependencies: lodash.kebabcase "^4.1.1" -killable@^1.0.0: +keyv@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" + integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== + dependencies: + json-buffer "3.0.0" + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +keyv@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== + dependencies: + json-buffer "3.0.1" + +killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== @@ -7623,9 +9483,26 @@ kind-of@^5.0.0: integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +language-subtag-registry@~0.3.2: + version "0.3.20" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz#a00a37121894f224f763268e431c55556b0c0755" + integrity sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" last-call-webpack-plugin@^3.0.0: version "3.0.0" @@ -7635,43 +9512,24 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" -latest-version@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= - dependencies: - package-json "^4.0.0" - -lazy-req@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" - integrity sha1-va6+rTD42CQDnODOFJ1Nqge6H6w= - -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= +latest-version@5.1.0, latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== dependencies: - readable-stream "^2.0.5" + package-json "^6.3.0" -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== dependencies: - invert-kv "^2.0.0" - -leven@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" - integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= + leven "^3.1.0" levn@^0.3.0, levn@~0.3.0: version "0.3.0" @@ -7681,10 +9539,15 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -load-bmfont@^1.2.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.0.tgz#75f17070b14a8c785fe7f5bee2e6fd4f98093b6b" - integrity sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g== +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +load-bmfont@^1.3.1, load-bmfont@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.1.tgz#c0f5f4711a1e2ccff725a7b6078087ccfcddd3e9" + integrity sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA== dependencies: buffer-equal "0.0.1" mime "^1.3.4" @@ -7697,7 +9560,7 @@ load-bmfont@^1.2.3: load-json-file@^1.0.0: version "1.1.0" - resolved "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= dependencies: graceful-fs "^4.1.2" @@ -7708,7 +9571,7 @@ load-json-file@^1.0.0: load-json-file@^2.0.0: version "2.0.0" - resolved "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= dependencies: graceful-fs "^4.1.2" @@ -7727,26 +9590,26 @@ load-json-file@^4.0.0: strip-bom "^3.0.0" loader-fs-cache@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc" - integrity sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw= + version "1.0.3" + resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz#f08657646d607078be2f0a032f8bd69dd6f277d9" + integrity sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA== dependencies: find-cache-dir "^0.1.1" - mkdirp "0.5.1" + mkdirp "^0.5.1" -loader-runner@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979" - integrity sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw== +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" - integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= +loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" locate-path@^2.0.0: version "2.0.0" @@ -7764,6 +9627,18 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lock@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/lock/-/lock-1.1.0.tgz#53157499d1653b136ca66451071fca615703fa55" + integrity sha1-UxV0mdFlOxNspmRRBx/KYVcD+lU= + lockfile@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" @@ -7771,82 +9646,25 @@ lockfile@^1.0.4: dependencies: signal-exit "^3.0.2" -lodash-es@^4.2.1: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" - integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q== - -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY= - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U= - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc= - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw= - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo= - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0= +lodash-es@^4.17.14: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" + integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - -lodash.clonedeep@^4.5.0: +lodash.clonedeep@4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= - -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg= - dependencies: - lodash._root "^3.0.0" - -lodash.escaperegexp@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" - integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= +lodash.deburr@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b" + integrity sha1-3bG7s+8HRYwBd7oH3hRCLLAz/5s= lodash.every@^4.6.0: version "4.6.0" @@ -7864,58 +9682,25 @@ lodash.foreach@^4.5.0: integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= lodash.get@^4.0: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= - -lodash.has@^4.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= - -lodash.isequal@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= +lodash.has@^4.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= lodash.iteratee@^4.5.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.iteratee/-/lodash.iteratee-4.7.0.tgz#be4177db289a8ccc3c0990f1db26b5b22fc1554c" + integrity sha1-vkF32yiajMw8CZDx2ya1si/BVUw= lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - lodash.map@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" @@ -7931,60 +9716,61 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.merge@^4.6.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54" - integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ== - -lodash.mergewith@^4.6.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" - integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== - -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8= - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.sample@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + integrity sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20= + +lodash.template@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" + lodash.templatesettings "^4.0.0" -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU= +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== dependencies: lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" + +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= -lodash.uniq@^4.5.0: +lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.11.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== +lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-update@^3.0.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-3.4.0.tgz#3b9a71e00ac5b1185cc193a36d654581c48f97b9" + integrity sha512-ILKe88NeMt4gmDvk/eb615U/IVn7K9KWGkoYbdatQ69Z65nj1ZzjM6fHXfcs0Uge+e+EGnMW7DY4T9yko8vWFg== + dependencies: + ansi-escapes "^3.2.0" + cli-cursor "^2.1.0" + wrap-ansi "^5.0.0" -logalot@^2.0.0: +logalot@^2.0.0, logalot@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/logalot/-/logalot-2.1.0.tgz#5f8e8c90d304edf12530951a5554abb8c5e3f552" integrity sha1-X46MkNME7fElMJUaVVSruMXj9VI= @@ -7992,33 +9778,34 @@ logalot@^2.0.0: figures "^1.3.5" squeak "^1.0.0" -loglevel@^1.4.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" - integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= +loglevel@^1.6.8: + version "1.7.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" + integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ== longest-streak@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-1.0.0.tgz#d06597c4d4c31b52ccb1f5d8f8fe7148eafd6965" + integrity sha1-0GWXxNTDG1LMsfXY+P5xSOr9aWU= longest-streak@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" - integrity sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA== + version "2.0.4" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" + integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== longest@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" -loud-rejection@^1.0.0, loud-rejection@^1.2.0: +loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= @@ -8026,11 +9813,36 @@ loud-rejection@^1.0.0, loud-rejection@^1.2.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lowercase-keys@^1.0.0: +loud-rejection@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-2.2.0.tgz#4255eb6e9c74045b0edc021fa7397ab655a8517c" + integrity sha512-S0FayMXku80toa5sZ6Ro4C+s+EtFDCsyJNG/AzFMfX3AxD5Si4dZsgzm/kKnbOxHl5Cv8jBlno8+3XYIh2pNjQ== + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.2" + +lower-case@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" + integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== + dependencies: + tslib "^1.10.0" + +lowercase-keys@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lpad-align@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/lpad-align/-/lpad-align-1.1.2.tgz#21f600ac1c3095c3c6e497ee67271ee08481fe9e" @@ -8041,11 +9853,6 @@ lpad-align@^1.0.1: longest "^1.0.0" meow "^3.3.0" -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= - lru-cache@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.0.tgz#b5cbf01556c16966febe54ceec0fb4dc90df6c28" @@ -8054,18 +9861,32 @@ lru-cache@4.0.0: pseudomap "^1.0.1" yallist "^2.0.0" -lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" - integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA== +lru-cache@^4.0.0, lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== dependencies: pseudomap "^1.0.2" yallist "^2.1.2" -ltcdr@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ltcdr/-/ltcdr-2.2.1.tgz#5ab87ad1d4c1dab8e8c08bbf037ee0c1902287cf" - integrity sha1-Wrh60dTB2rjowIu/A37gwZAih88= +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lru-cache@^7.4.0: + version "7.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.7.3.tgz#98cd19eef89ce6a4a3c4502c17c833888677c252" + integrity sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw== lz-string@^1.4.4: version "1.4.4" @@ -8079,21 +9900,36 @@ magic-string@^0.14.0: dependencies: vlq "^0.2.1" -make-dir@^1.0.0: +magic-string@^0.25.3: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + +make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== dependencies: pify "^3.0.0" -map-age-cleaner@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74" - integrity sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ== +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: - p-defer "^1.0.0" + semver "^6.0.0" -map-cache@^0.2.0, map-cache@^0.2.2: +map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= @@ -8110,33 +9946,29 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -mapz@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/mapz/-/mapz-1.0.2.tgz#f6af94c425db0874ac2c4e67a76f5df1724b1322" - integrity sha512-NuY43BoHy5K4jVg3/oD+g8ysNwdXY3HB5UankVWoikxT9YMqgCYC77pNRENTm/DfslLxPFEOyJUw9h9isRty6w== - dependencies: - x-is-array "^0.1.0" - markdown-escapes@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122" - integrity sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" + integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== markdown-table@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-0.4.0.tgz#890c2c1b3bfe83fb00e4129b8e4cfe645270f9d1" + integrity sha1-iQwsGzv+g/sA5BKbjkz+ZFJw+dE= markdown-table@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.2.tgz#c78db948fa879903a41bce522e3b96f801c63786" - integrity sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw== + version "1.1.3" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" + integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== -math-random@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" - integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= +markdown-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" + integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== + dependencies: + repeat-string "^1.0.0" -md5-file@^3.1.1: +md5-file@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-3.2.3.tgz#f9bceb941eca2214a4c0727f5e700314e770f06f" integrity sha512-3Tkp1piAHaworfcCgH0jKbTvj1jWWFgbvh2cXaNCgHwyTCBxxvD1Y04rmfpvdPm1P4oXMOpm6+2H7sr7v9v8Fw== @@ -8152,33 +9984,62 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -md5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" - integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= +mdast-squeeze-paragraphs@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" + integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ== dependencies: - charenc "~0.0.1" - crypt "~0.0.1" - is-buffer "~1.1.1" + unist-util-remove "^2.0.0" mdast-util-compact@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649" - integrity sha512-d2WS98JSDVbpSsBfVvD9TaDMlqPRz7ohM/11G0rp5jOBb5q96RJ6YLszQ/09AAixyzh23FeIpCGqfaamEADtWg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz#d531bb7667b5123abf20859be086c4d06c894593" + integrity sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg== dependencies: unist-util-visit "^1.1.0" +mdast-util-compact@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490" + integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA== + dependencies: + unist-util-visit "^2.0.0" + mdast-util-definitions@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-1.2.3.tgz#49f936b09207c45b438db19551652934312f04f0" - integrity sha512-P6wpRO8YVQ1iv30maMc93NLh7COvufglBE8/ldcOyYmk5EbfF0YeqlLgtqP/FOBU501Kqar1x5wYWwB3Nga74g== + version "1.2.5" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-1.2.5.tgz#3fe622a4171c774ebd06f11e9f8af7ec53ea5c74" + integrity sha512-CJXEdoLfiISCDc2JB6QLb79pYfI6+GcIH+W2ox9nMc7od0Pz+bovcHsiq29xAQY6ayqe/9CsK2VzkSJdg1pFYA== dependencies: unist-util-visit "^1.0.0" -mdast-util-to-hast@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-3.0.2.tgz#26b1971f49d6db1e3428463a12e66c89db5021cb" - integrity sha512-YI8Ea3TFWEZrS31+6Q/d8ZYTOSDKM06IPc3l2+OMFX1o3JTG2mrztlmzDsUMwIXLWofEdTVl/WXBgRG6ddlU/A== +mdast-util-definitions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-3.0.1.tgz#06af6c49865fc63d6d7d30125569e2f7ae3d0a86" + integrity sha512-BAv2iUm/e6IK/b2/t+Fx69EL/AGcq/IG2S+HxHjDJGfLJtd6i9SZUS76aC9cig+IEucsqxKTR0ot3m933R3iuA== + dependencies: + unist-util-visit "^2.0.0" + +mdast-util-to-hast@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-9.1.0.tgz#6ef121dd3cd3b006bf8650b1b9454da0faf79ffe" + integrity sha512-Akl2Vi9y9cSdr19/Dfu58PVwifPXuFt1IrHe7l+Crme1KvgUT+5z+cHLVcQVGCiNTZZcdqjnuv9vPkGsqWytWA== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.3" + collapse-white-space "^1.0.0" + detab "^2.0.0" + mdast-util-definitions "^3.0.0" + mdurl "^1.0.0" + trim-lines "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + +mdast-util-to-hast@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-3.0.4.tgz#132001b266031192348d3366a6b011f28e54dc40" + integrity sha512-/eIbly2YmyVgpJNo+bFLLMCI1XgolO/Ffowhf+pHDq3X4/V6FntC9sGQCDLM147eTS+uSXv5dRzJyFn+o0tazA== dependencies: collapse-white-space "^1.0.0" detab "^2.0.0" @@ -8193,61 +10054,59 @@ mdast-util-to-hast@^3.0.0: xtend "^4.0.1" mdast-util-to-nlcst@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-nlcst/-/mdast-util-to-nlcst-3.2.0.tgz#dad262857658d1eab4b5814a20e2f93d7ca1e3b6" - integrity sha1-2tJihXZY0eq0tYFKIOL5PXyh47Y= + version "3.2.3" + resolved "https://registry.yarnpkg.com/mdast-util-to-nlcst/-/mdast-util-to-nlcst-3.2.3.tgz#dcd0f51b59515b11a0700aeb40f168ed7ba9ed3d" + integrity sha512-hPIsgEg7zCvdU6/qvjcR6lCmJeRuIEpZGY5xBV+pqzuMOvQajyyF8b6f24f8k3Rw8u40GwkI3aAxUXr3bB2xag== dependencies: nlcst-to-string "^2.0.0" repeat-string "^1.5.2" unist-util-position "^3.0.0" vfile-location "^2.0.0" -mdast-util-to-string@^1.0.2, mdast-util-to-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.0.5.tgz#3552b05428af22ceda34f156afe62ec8e6d731ca" - integrity sha512-2qLt/DEOo5F6nc2VFScQiHPzQ0XXcabquRJxKMhKte8nt42o08HUxNDPk7tt0YPxnWjAT11I1SYi0X0iPnfI5A== +mdast-util-to-string@^1.0.5, mdast-util-to-string@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" + integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== -mdast-util-toc@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-2.1.0.tgz#82b6b218577bb0e67b23abf5c3f7ac73a4b5389f" - integrity sha512-ove/QQWSrYOrf9G3xn2MTAjy7PKCtCmm261wpQwecoPAsUtkihkMVczxFqil7VihxgSz4ID9c8bBTsyXR30gQg== - dependencies: - github-slugger "^1.1.1" - mdast-util-to-string "^1.0.2" - unist-util-visit "^1.1.0" +mdast-util-toc@^5.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-5.0.3.tgz#5fb1503e3655688929d596799a6910cc6548e420" + integrity sha512-A3xzcgC1XFHK0+abFmbINOxjwo7Bi0Nsfp3yTgTy5JHo2q2V6YZ5BVJreDWoK3szcLlSMvHqe8WPbjY50wAkow== + dependencies: + "@types/mdast" "^3.0.3" + "@types/unist" "^2.0.3" + extend "^3.0.2" + github-slugger "^1.2.1" + mdast-util-to-string "^1.0.5" + unist-util-is "^4.0.0" + unist-util-visit "^2.0.0" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -mdn-data@~1.1.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" - integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA== +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== -mdurl@^1.0.1: +mdurl@^1.0.0, mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +meant@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.2.tgz#5d0c78310a3d8ae1408a16be0fe0bd42a969f560" + integrity sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" - integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= - dependencies: - mimic-fn "^1.0.0" - -mem@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf" - integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^1.0.0" - p-is-promise "^1.1.0" - -memory-fs@^0.4.0, memory-fs@~0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -8255,12 +10114,20 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= -meow@^3.1.0, meow@^3.3.0, meow@^3.5.0: +meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -8281,43 +10148,22 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -merge-stream@^1.0.0, merge-stream@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" - integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= - dependencies: - readable-stream "^2.0.1" +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.2.tgz#03212e3da8d86c4d8523cebd6318193414f94e34" - integrity sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg== +merge2@^1.2.3, merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^2.1.5, micromatch@^2.3.7: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8, micromatch@^3.1.9: +micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -8336,6 +10182,14 @@ micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8, micromatch@^3.1.9: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -8344,10 +10198,15 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.36.0 < 2", mime-db@^1.28.0, mime-db@~1.36.0: - version "1.36.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" - integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw== +mime-db@1.44.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-db@1.45.0: + version "1.45.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" + integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== mime-db@~1.25.0: version "1.25.0" @@ -8361,38 +10220,55 @@ mime-types@2.1.13: dependencies: mime-db "~1.25.0" -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19: - version "2.1.20" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" - integrity sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A== +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "~1.36.0" + mime-db "1.44.0" -mime@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" - integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== +mime-types@~2.1.24: + version "2.1.28" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" + integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== + dependencies: + mime-db "1.45.0" -mime@^1.3.4: +mime@1.6.0, mime@^1.3.4: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.0.3, mime@^2.2.0, mime@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" - integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg== +mime@^2.0.3, mime@^2.4.4, mime@^2.4.6: + version "2.4.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" + integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-response@^1.0.0: +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^2.0.0, mimic-response@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -8400,19 +10276,25 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" -mini-css-extract-plugin@^0.4.0: - version "0.4.4" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.4.tgz#c10410a004951bd3cedac1da69053940fccb625d" - integrity sha512-o+Jm+ocb0asEngdM6FsZWtZsRzA8koFUudIDwYUfl94M3PejPHG7Vopw5hN9V8WsMkSFpm3tZP3Fesz89EyrfQ== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +mini-css-extract-plugin@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz#a875e169beb27c88af77dd962771c9eedc3da161" + integrity sha512-a3Y4of27Wz+mqK3qrcd3VhYz6cU0iW5x3Sgvqzbj+XmlrSizmvu8QQMl5oMYJjgHOC4iyt+w7l4umP+dQeW3bw== dependencies: loader-utils "^1.1.0" + normalize-url "1.9.1" schema-utils "^1.0.0" webpack-sources "^1.1.0" -mini-svg-data-uri@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.0.1.tgz#d81ffc14b85558860581cc58d9790daaecbe91bf" - integrity sha512-KJ3cjR4kJIP4RroDIXqVTOX0hDYaFMmeHPXqwakVuJmak31uB4+DEqK2L7cedtYHUOdQgh23YsXnAIOHLvjM0g== +mini-svg-data-uri@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.2.3.tgz#e16baa92ad55ddaa1c2c135759129f41910bc39f" + integrity sha512-zd6KCAyXgmq6FV1mR10oKXYtvmA9vRoB6xPSTUJTbFApCtkefDnYueVR1gkof3KcdLZo1Y8mjF2DFmQMIxsHNQ== minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" @@ -8424,21 +10306,6 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" - integrity sha1-J12O2qxPG7MyZHIInnlJyDlGmd0= - dependencies: - lru-cache "2" - sigmund "~1.0.0" - -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - minimatch@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" @@ -8446,46 +10313,22 @@ minimatch@3.0.3: dependencies: brace-expansion "^1.0.0" -minimist@0.0.8: - version "0.0.8" - resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" - integrity sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w== +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" + brace-expansion "^1.1.7" -minizlib@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" - integrity sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg== - dependencies: - minipass "^2.2.1" +minimist@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.1.tgz#827ba4e7593464e7c221e8c5bed930904ee2c455" + integrity sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg== -mississippi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f" - integrity sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^2.0.1" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" +minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== mississippi@^3.0.0: version "3.0.0" @@ -8503,30 +10346,40 @@ mississippi@^3.0.0: stream-each "^1.1.0" through2 "^2.0.0" -mitt@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.1.3.tgz#528c506238a05dce11cd914a741ea2cc332da9b8" - integrity sha512-mUDCnVNsAi+eD6qA0HkRkwYczbLHJ49z17BGe2PYRhZL4wpZUFZGJHU7/5tmvohoma+Hdn0Vh/oJTiPEmgSruA== +mitt@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz#cb24e6569c806e31bd4e3995787fe38a04fdf90d" + integrity sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw== mixin-deep@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" - integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== dependencies: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: - version "0.5.1" - resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^0.5, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: - minimist "0.0.8" + minimist "^1.2.5" -moment@^2.21.0: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +moment@^2.27.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.0.tgz#fcbef955844d91deb55438613ddcec56e86a3425" + integrity sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA== move-concurrently@^1.0.1: version "1.0.1" @@ -8540,25 +10393,35 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -mozjpeg@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/mozjpeg/-/mozjpeg-5.0.0.tgz#b8671c4924568a363de003ff2fd397ab83f752c5" - integrity sha1-uGccSSRWijY94AP/L9OXq4P3UsU= +mozjpeg@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/mozjpeg/-/mozjpeg-7.0.0.tgz#c20f67a538fcaaa388d325875c53c0e7bc432f7d" + integrity sha512-mH7atSbIusVTO3A4H43sEdmveN3aWn54k6V0edefzCEvOsTrbjg5murY2TsNznaztWnIgaRbWxeLVp4IgKdedQ== dependencies: - bin-build "^2.2.0" - bin-wrapper "^3.0.0" - logalot "^2.0.0" + bin-build "^3.0.0" + bin-wrapper "^4.0.0" + logalot "^2.1.0" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.1.1: +ms@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -8572,27 +10435,25 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s= - dependencies: - duplexer2 "0.0.2" - -mute-stream@0.0.7, mute-stream@~0.0.4: +mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +mute-stream@0.0.8, mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + name-all-modules-plugin@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/name-all-modules-plugin/-/name-all-modules-plugin-1.0.1.tgz#0abfb6ad835718b9fb4def0674e06657a954375c" integrity sha1-Cr+2rYNXGLn7Te8GdOBmV6lUN1w= -nan@^2.11.0, nan@^2.9.2: - version "2.11.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" - integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== +nan@^2.12.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nanomatch@^1.2.9: version "1.2.13" @@ -8611,34 +10472,37 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + +native-url@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" + integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA== + dependencies: + querystring "^0.2.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -needle@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" - integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= +negotiator@0.6.2, negotiator@~0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo-async@^2.5.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.2.tgz#489105ce7bc54e709d736b195f82135048c50fcc" - integrity sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw== +neo-async@^2.5.0, neo-async@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== next-tick@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== nice-try@^1.0.4: version "1.0.5" @@ -8646,21 +10510,34 @@ nice-try@^1.0.4: integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== nlcst-to-string@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.2.tgz#7125af4d4d369850c697192a658f01f36af9937b" - integrity sha512-DV7wVvMcAsmZ5qEwvX1JUNF4lKkAAKbChwNlIH7NLsPR7LWWoeIt53YlZ5CQH5KDXEXQ9Xa3mw0PbPewymrtew== + version "2.0.4" + resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz#9315dfab80882bbfd86ddf1b706f53622dc400cc" + integrity sha512-3x3jwTd6UPG7vi5k4GEzvxJ5rDA7hVUIRNHPblKuMVP9Z3xmlsd9cgLcpAMkc5uPOBna82EeshROFhsPkbnTZg== -node-abi@^2.2.0: - version "2.4.5" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.4.5.tgz#1fd1fb66641bf3c4dcf55a5490ba10c467ead80c" - integrity sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA== +no-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" + integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== dependencies: - semver "^5.4.1" + lower-case "^2.0.1" + tslib "^1.10.0" -node-emoji@^1.0.4: - version "1.8.1" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" - integrity sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg== +node-abi@^3.3.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.8.0.tgz#679957dc8e7aa47b0a02589dbfde4f77b29ccb32" + integrity sha512-tzua9qWWi7iW4I42vUPKM+SfaF0vQSLAm4yO5J83mSwB7GeoWrDKC/K+8YCnYNwqP5duwazbw2X9l4m8SC2cUw== + dependencies: + semver "^7.3.5" + +node-addon-api@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" + integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== + +node-emoji@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== dependencies: lodash.toarray "^4.4.0" @@ -8671,9 +10548,14 @@ node-eta@^0.9.0: node-fetch@2.1.2: version "2.1.2" - resolved "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= +node-fetch@2.6.1, node-fetch@^2.5.0, node-fetch@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -8682,20 +10564,15 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -node-forge@0.7.5: - version "0.7.5" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" - integrity sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-libs-browser@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" - integrity sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg== +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== dependencies: assert "^1.1.1" browserify-zlib "^0.2.0" @@ -8704,10 +10581,10 @@ node-libs-browser@^2.0.0: constants-browserify "^1.0.0" crypto-browserify "^3.11.0" domain-browser "^1.1.1" - events "^1.0.0" + events "^3.0.0" https-browserify "^1.0.0" os-browserify "^0.3.0" - path-browserify "0.0.0" + path-browserify "0.0.1" process "^0.11.10" punycode "^1.2.4" querystring-es3 "^0.2.0" @@ -8718,36 +10595,18 @@ node-libs-browser@^2.0.0: timers-browserify "^2.0.4" tty-browserify "0.0.0" url "^0.11.0" - util "^0.10.3" - vm-browserify "0.0.4" - -node-pre-gyp@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" + util "^0.11.0" + vm-browserify "^1.0.1" -node-releases@^1.0.0-alpha.12: - version "1.0.0-alpha.12" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.0-alpha.12.tgz#32e461b879ea76ac674e511d9832cf29da345268" - integrity sha512-VPB4rTPqpVyWKBHbSa4YPFme3+8WHsOSpvbp0Mfj0bWsC8TEjt4HQrLl1hsBDELlp1nB4lflSgSuGTYiuyaP7Q== - dependencies: - semver "^5.3.0" +node-object-hash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-2.0.0.tgz#9971fcdb7d254f05016bd9ccf508352bee11116b" + integrity sha512-VZR0zroAusy1ETZMZiGeLkdu50LGjG5U1KHZqTruqtTyQ2wfWhHG2Ow4nsUbfTFGlaREgNHcCWoM/OzEm6p+NQ== -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" - integrity sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8= +node-releases@^1.1.71: + version "1.1.72" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" + integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== noms@0.0.0: version "0.0.0" @@ -8757,55 +10616,66 @@ noms@0.0.0: inherits "^2.0.1" readable-stream "~1.0.31" -noop-logger@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" - integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: - version "2.4.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" - integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" + resolve "^1.10.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= -normalize-url@^3.0.0: +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + +normalize-url@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" + integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== + dependencies: + prepend-http "^2.0.0" + query-string "^5.0.1" + sort-keys "^2.0.0" + +normalize-url@^3.0.0, normalize-url@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== -normalize.css@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.0.tgz#14ac5e461612538a4ce9be90a7da23f86e718493" - integrity sha512-iXcbM3NWr0XkNyfiSBsoPezi+0V92P9nj84yVV1/UZxRUrGczgX/X91KMAGM0omWLY2+2Q1gKD/XRn4gQRDB2A== +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== -npm-bundled@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" - integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g== +normalize.css@^8.0.0, normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== npm-conf@^1.1.0: version "1.1.3" @@ -8815,13 +10685,15 @@ npm-conf@^1.1.0: config-chain "^1.1.11" pify "^3.0.0" -npm-packlist@^1.1.6: - version "1.1.12" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" - integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g== +npm-package-arg@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7" + integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg== dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" + hosted-git-info "^2.7.1" + osenv "^0.1.5" + semver "^5.6.0" + validate-npm-package-name "^3.0.0" npm-run-all@^4.1.5: version "4.1.5" @@ -8845,7 +10717,14 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -8860,17 +10739,20 @@ nprogress@^0.2.0: resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E= -nth-check@^1.0.1, nth-check@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" - integrity sha1-mSms32KPwsQQmN6rgqxYDPFJquQ= +nth-check@^1.0.1, nth-check@^1.0.2, nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== dependencies: boolbase "~1.0.0" -null-loader@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-0.1.1.tgz#17be9abfcd3ff0e1512f6fc4afcb1f5039378fae" - integrity sha1-F76av80/8OFRL2/Er8sfUDk3j64= +null-loader@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-3.0.0.tgz#3e2b6c663c5bda8c73a54357d8fa0708dc61b245" + integrity sha512-hf5sNLl8xdRho4UPBOOeoIwT3WhjYcMUQm0zj44EhD6UscMAz72o2udpoDFBgykucdEDGIcd6SXbc/G6zssbzw== + dependencies: + loader-utils "^1.2.3" + schema-utils "^1.0.0" num2fraction@^1.2.2: version "1.2.2" @@ -8887,17 +10769,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= - -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= - -object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -8917,19 +10789,32 @@ object-copy@^0.1.0: kind-of "^3.0.3" object-hash@^1.1.4: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.0.tgz#76d9ba6ff113cf8efc0d996102851fe6723963e2" - integrity sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ== + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== + +object-inspect@^1.7.0, object-inspect@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== -object-keys@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" - integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== +object-is@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" + integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-path@^0.11.2: - version "0.11.4" - resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" - integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk= +object-path@^0.11.2, object-path@^0.11.4: + version "0.11.8" + resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.8.tgz#ed002c02bbdd0070b78a27455e8ae01fc14d4742" + integrity sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA== object-visit@^1.0.0: version "1.0.1" @@ -8938,21 +10823,52 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.getownpropertydescriptors@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" - integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= +object.assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.1" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= +object.assign@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" object.pick@^1.3.0: version "1.3.0" @@ -8961,21 +10877,26 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" - integrity sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo= +object.values@^1.1.0, object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" -obuf@^1.0.0, obuf@^1.1.1: +obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +omggif@^1.0.10, omggif@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" + integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -8983,10 +10904,10 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" - integrity sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c= +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: version "1.4.0" @@ -8995,11 +10916,6 @@ once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -9007,10 +10923,24 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -opentracing@^0.14.3: - version "0.14.3" - resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.3.tgz#23e3ad029fa66a653926adbe57e834469f8550aa" - integrity sha1-I+OtAp+mamU5Jq2+V+g0Rp+FUKo= +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" + integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== + dependencies: + is-wsl "^1.1.0" + +opentracing@^0.14.4: + version "0.14.4" + resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.4.tgz#a113408ea740da3a90fde5b3b0011a375c2e4268" + integrity sha512-nNnZDkUNExBwEpb7LZaeMeQgvrlO8l4bgY/LvGNZCR0xG/dGWqHqjKrAmR5GUoYo0FIz38kxasvA1aevxWs2CA== opn@5.1.0: version "5.1.0" @@ -9019,42 +10949,34 @@ opn@5.1.0: dependencies: is-wsl "^1.1.0" -opn@^5.1.0, opn@^5.3.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035" - integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw== +opn@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== dependencies: is-wsl "^1.1.0" -optimize-css-assets-webpack-plugin@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.1.tgz#9eb500711d35165b45e7fd60ba2df40cb3eb9159" - integrity sha512-Rqm6sSjWtx9FchdP0uzTQDc7GXDKnwVEGoSxjezPkzMewx7gEWE9IMUYKmigTRC4U3RaNSwYVnUDLuIdtTpm0A== +optimize-css-assets-webpack-plugin@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#85883c6528aaa02e30bbad9908c92926bb52dc90" + integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A== dependencies: - cssnano "^4.1.0" + cssnano "^4.1.10" last-call-webpack-plugin "^3.0.0" -optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= +optionator@^0.8.2, optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== dependencies: deep-is "~0.1.3" - fast-levenshtein "~2.0.4" + fast-levenshtein "~2.0.6" levn "~0.3.0" prelude-ls "~1.1.2" type-check "~0.3.2" - wordwrap "~1.0.0" - -ordered-read-streams@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" - integrity sha1-cTfmmzKYuzQiR6G77jiByA4v14s= - dependencies: - is-stream "^1.0.1" - readable-stream "^2.0.1" + word-wrap "~1.2.3" -original@>=0.0.5: +original@>=0.0.5, original@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== @@ -9066,40 +10988,24 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-filter-obj@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/os-filter-obj/-/os-filter-obj-1.0.3.tgz#5915330d90eced557d2d938a31c6dd214d9c63ad" - integrity sha1-WRUzDZDs7VV9LZOKMcbdIU2cY60= +os-filter-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/os-filter-obj/-/os-filter-obj-2.0.0.tgz#1c0b62d5f3a2442749a2d139e6dddee6e81d8d16" + integrity sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg== + dependencies: + arch "^2.1.0" os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" - integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== - dependencies: - execa "^0.7.0" - lcid "^1.0.0" - mem "^1.1.0" - -os-locale@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.0.1.tgz#3b014fbf01d87f60a1e5348d80fe870dc82c4620" - integrity sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw== - dependencies: - execa "^0.10.0" - lcid "^2.0.0" - mem "^4.0.0" - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4: +osenv@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -9107,15 +11013,37 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +ow@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/ow/-/ow-0.17.0.tgz#4f938999fed6264c9048cd6254356e0f1e7f688c" + integrity sha512-i3keDzDQP5lWIe4oODyDFey1qVrq2hXKTuTH2VpqwpYtzPiKZt2ziRI4NBQmgW40AnV5Euz17OyWweCb+bNEQA== + dependencies: + type-fest "^0.11.0" + p-cancelable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= +p-cancelable@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" + integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== + +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-cancelable@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" + integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== + +p-defer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83" + integrity sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw== p-event@^1.0.0: version "1.3.0" @@ -9124,14 +11052,33 @@ p-event@^1.0.0: dependencies: p-timeout "^1.1.1" +p-event@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-2.3.1.tgz#596279ef169ab2c3e0cae88c1cfbb08079993ef6" + integrity sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA== + dependencies: + p-timeout "^2.0.1" + +p-event@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" + integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== + dependencies: + p-timeout "^3.1.0" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-finally@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" + integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== + p-is-promise@^1.1.0: version "1.1.0" - resolved "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= p-limit@^1.1.0: @@ -9141,10 +11088,10 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" - integrity sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A== +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" @@ -9162,6 +11109,13 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-map-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" @@ -9169,21 +11123,35 @@ p-map-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-map@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" - integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -p-pipe@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" - integrity sha1-SxoROZoRUgpneQ7loMHViB1r7+k= +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-pipe@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" + integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= +p-retry@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== + dependencies: + retry "^0.12.0" + p-timeout@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" @@ -9191,50 +11159,71 @@ p-timeout@^1.1.1: dependencies: p-finally "^1.0.0" +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== + dependencies: + p-finally "^1.0.0" + +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= -p-try@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" - integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== +p-try@^2.0.0, p-try@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" -pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - integrity sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg== +pako@^1.0.5, pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== dependencies: - cyclist "~0.2.2" + cyclist "^1.0.1" inherits "^2.0.3" readable-stream "^2.1.5" -parse-asn1@^5.0.0: - version "5.1.1" - resolved "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" - integrity sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== dependencies: - asn1.js "^4.0.0" + asn1.js "^5.2.0" browserify-aes "^1.0.0" - create-hash "^1.1.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" parse-bmfont-ascii@^1.0.3: version "1.0.6" @@ -9255,9 +11244,9 @@ parse-bmfont-xml@^1.1.4: xml2js "^0.4.5" parse-english@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/parse-english/-/parse-english-4.1.1.tgz#2f75872e617769d857d9b6992dcde2a891f1b2d3" - integrity sha512-g7hegR9AFIlGXl5645mG8nQeeWW7SrK7lgmgIWR0KKWvGyZO5mxa4GGoNxRLm6VW2LGpLnn6g4O9yyLJQ4IzQw== + version "4.1.3" + resolved "https://registry.yarnpkg.com/parse-english/-/parse-english-4.1.3.tgz#692ec002e515b4b9b3e9e64ee1224b082667a20b" + integrity sha512-IQl1v/ik9gw437T8083coohMihae0rozpc7JYC/9h6hi9xKBSxFwh5HWRpzVC2ZhEs2nUlze2aAktpNBJXdJKA== dependencies: nlcst-to-string "^2.0.0" parse-latin "^4.0.0" @@ -9265,9 +11254,9 @@ parse-english@^4.0.0: unist-util-visit-children "^1.0.0" parse-entities@^1.0.2, parse-entities@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4" - integrity sha512-XXtDdOPLSB0sHecbEapQi6/58U/ODj/KWfIXmmMCJF/eRn8laX6LZbOyioMoETOOJoWRW8/qTSl5VQkUIfKM5g== + version "1.2.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50" + integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg== dependencies: character-entities "^1.0.0" character-entities-legacy "^1.0.0" @@ -9276,34 +11265,24 @@ parse-entities@^1.0.2, parse-entities@^1.1.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" parse-headers@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.1.tgz#6ae83a7aa25a9d9b700acc28698cd1f1ed7e9536" - integrity sha1-aug6eqJanZtwCswoaYzR8e1+lTY= - dependencies: - for-each "^0.3.2" - trim "0.0.1" + version "2.0.3" + resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.3.tgz#5e8e7512383d140ba02f0c7aa9f49b4399c92515" + integrity sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA== -parse-json@^2.1.0, parse-json@^2.2.0: +parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= @@ -9318,10 +11297,20 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-json@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" + integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + parse-latin@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-4.1.1.tgz#3a3edef405b2d5dce417b7157d3d8a5c7cdfab1d" - integrity sha512-9fPVvDdw6G8LxL3o/PL6IzSGNGpF+3HEjCzFe0dN83sZPstftyr+McP9dNi3+EnR7ICYOHbHKCZ0l7JD90K5xQ== + version "4.2.1" + resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-4.2.1.tgz#b78c57c026cdf8e4e9924b296a2d0aa69877fab8" + integrity sha512-7T9g6mIsFFpLlo0Zzb2jLWdCt+H9Qtf/hRmMYFi/Mq6Ovi+YKo+AyDFX3OhFfu0vXX5Nid9FKJGKSSzNcTkWiA== dependencies: nlcst-to-string "^2.0.0" unist-util-modify-children "^1.0.0" @@ -9337,13 +11326,46 @@ parse-passwd@^1.0.0: resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= -parse5@^3.0.1, parse5@^3.0.3: +parse-path@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.2.tgz#ef14f0d3d77bae8dd4bc66563a4c151aac9e65aa" + integrity sha512-HSqVz6iuXSiL8C1ku5Gl1Z5cwDd9Wo0q8CoffdAghP6bz8pJa1tcMC+m4N+z6VAS8QdksnIGq1TB6EgR4vPR6w== + dependencies: + is-ssh "^1.3.0" + protocols "^1.4.0" + +parse-srcset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" + integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE= + +parse-url@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.2.tgz#856a3be1fcdf78dc93fc8b3791f169072d898b59" + integrity sha512-Czj+GIit4cdWtxo3ISZCvLiUjErSo0iI3wJ+q9Oi3QuMYTI6OZu+7cewMWZ+C1YAnKhYTk6/TLuhIgCypLthPA== + dependencies: + is-ssh "^1.3.0" + normalize-url "^3.3.0" + parse-path "^4.0.0" + protocols "^1.4.0" + +parse5@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== dependencies: "@types/node" "*" +parse5@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -9351,6 +11373,11 @@ parseqs@0.0.5: dependencies: better-assert "~1.0.0" +parseqs@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" + integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== + parseuri@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" @@ -9358,20 +11385,41 @@ parseuri@0.0.5: dependencies: better-assert "~1.0.0" -parseurl@~1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" - integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= +parseuri@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" + integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow== + +parseurl@^1.3.3, parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" + integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" - integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo= +password-prompt@^1.0.4: + version "1.1.2" + resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" + integrity sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA== + dependencies: + ansi-escapes "^3.1.0" + cross-spawn "^6.0.5" + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== path-dirname@^1.0.0: version "1.0.2" @@ -9390,12 +11438,17 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -9405,22 +11458,15 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-parse@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= - dependencies: - path-root-regex "^0.1.0" +path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" @@ -9450,10 +11496,15 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pbkdf2@^3.0.3: - version "3.0.17" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" - integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" + integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -9472,21 +11523,26 @@ performance-now@^2.1.0: integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= phin@^2.9.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.2.tgz#0a82d5b6dd75552b665f371f8060689c1af7336e" - integrity sha512-j+UOz1qs+k8NlBRws2IF+Qd+YsVKcqIjvYPBEP9IpmhyvLvyN6GTuqsGbsqH3fIgHufqVqLQSttidIgshkgT7w== + version "2.9.3" + resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c" + integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA== physical-cpu-count@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz#18de2f97e4bf7a9551ad7511942b5496f7aba660" integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA= +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + pidtree@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" - integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg== + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== -pify@^2.0.0, pify@^2.3.0: +pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= @@ -9496,6 +11552,11 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -9508,7 +11569,7 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= -pixelmatch@^4.0.0: +pixelmatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" integrity sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ= @@ -9536,54 +11597,67 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== -pngjs@^3.0.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b" - integrity sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q== +pngjs@^3.0.0, pngjs@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== -pngquant-bin@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pngquant-bin/-/pngquant-bin-5.0.0.tgz#91df99c15b0492cb30b001746a10a67d3202509e" - integrity sha512-oJ9Kcmm5oSFkgvYB32bopBN0F6lw0OBnVY36IpkIteBLKt9s8EswiOzAsbSVZ79I8zrvoP/i8IcQPZxsORCOfg== +pngquant-bin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/pngquant-bin/-/pngquant-bin-6.0.0.tgz#aff0d7e61095feb96ced379ad8c7294ad3dd1712" + integrity sha512-oXWAS9MQ9iiDAJRdAZ9KO1mC5UwhzKkJsmetiu0iqIjJuW7JsuLhmc4JdRm7uJkIWRzIAou/Vq2VcjfJwz30Ow== dependencies: bin-build "^3.0.0" - bin-wrapper "^3.0.0" - execa "^0.10.0" + bin-wrapper "^4.0.1" + execa "^4.0.0" logalot "^2.0.0" -portfinder@^1.0.9: - version "1.0.17" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.17.tgz#a8a1691143e46c4735edefcf4fbcccedad26456a" - integrity sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ== +pnp-webpack-plugin@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== dependencies: - async "^1.5.2" - debug "^2.2.0" - mkdirp "0.5.x" + ts-pnp "^1.1.6" + +portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -postcss-calc@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-6.0.2.tgz#4d9a43e27dbbf27d095fecb021ac6896e2318337" - integrity sha512-fiznXjEN5T42Qm7qqMCVJXS3roaj9r4xsSi+meaBVe7CJBl8t/QLOXu02Z2E6oWAMWIvCuF6JrvzFekmVEbOKA== +postcss-calc@^7.0.1: + version "7.0.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.4.tgz#5e177ddb417341e6d4a193c5d9fd8ada79094f8b" + integrity sha512-0I79VRAd1UTkaHzY9w83P39YGO/M3bG7/tNLrHGEunBolfoGM0hSjrGvjoeaj0JE/zIw5GsI2KZ0UwDJqv5hjw== dependencies: - css-unit-converter "^1.1.1" - postcss "^7.0.2" - postcss-selector-parser "^2.2.2" - reduce-css-calc "^2.0.0" + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" -postcss-colormin@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.2.tgz#93cd1fa11280008696887db1a528048b18e7ed99" - integrity sha512-1QJc2coIehnVFsz0otges8kQLsryi4lo19WD+U5xCWvXd0uw/Z+KKYnbiNDCnO9GP+PvErPHCG0jNvWTngk9Rw== +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== dependencies: browserslist "^4.0.0" color "^3.0.0" @@ -9599,10 +11673,10 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-discard-comments@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.1.tgz#30697735b0c476852a7a11050eb84387a67ef55d" - integrity sha512-Ay+rZu1Sz6g8IdzRjUgG2NafSNpp2MSMOQUb+9kkzzzP+kh07fP0yNbhtFejURnyVXSX3FYy2nVNW1QTnNjgBQ== +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== dependencies: postcss "^7.0.0" @@ -9627,45 +11701,45 @@ postcss-discard-overridden@^4.0.1: dependencies: postcss "^7.0.0" -postcss-flexbugs-fixes@^3.0.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-3.3.1.tgz#0783cc7212850ef707f97f8bc8b6fb624e00c75d" - integrity sha512-9y9kDDf2F9EjKX6x9ueNa5GARvsUbXw4ezH8vXItXHwKzljbu8awP7t5dCaabKYm18Vs1lo5bKQcnc0HkISt+w== +postcss-flexbugs-fixes@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" + integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== dependencies: - postcss "^6.0.1" + postcss "^7.0.26" postcss-load-config@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.0.0.tgz#f1312ddbf5912cd747177083c5ef7a19d62ee484" - integrity sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.1.tgz#0a684bb8beb05e55baf922f7ab44c3edb17cf78e" + integrity sha512-D2ENobdoZsW0+BHy4x1CAkXtbXtYWYRIxL/JbtRBqrRGOPtJ2zoga/bEZWhV/ShWB5saVxJMzbMdSyA/vv4tXw== dependencies: - cosmiconfig "^4.0.0" + cosmiconfig "^5.0.0" import-cwd "^2.0.0" -postcss-loader@^2.1.3: - version "2.1.6" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.6.tgz#1d7dd7b17c6ba234b9bed5af13e0bea40a42d740" - integrity sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg== +postcss-loader@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" + integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== dependencies: loader-utils "^1.1.0" - postcss "^6.0.0" + postcss "^7.0.0" postcss-load-config "^2.0.0" - schema-utils "^0.4.0" + schema-utils "^1.0.0" -postcss-merge-longhand@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.6.tgz#2b938fa3529c3d1657e53dc7ff0fd604dbc85ff1" - integrity sha512-JavnI+V4IHWsaUAfOoKeMEiJQGXTraEy1nHM0ILlE6NIQPEZrJDAnPh3lNGZ5HAk2mSSrwp66JoGhvjp6SqShA== +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== dependencies: css-color-names "0.0.4" postcss "^7.0.0" postcss-value-parser "^3.0.0" stylehacks "^4.0.0" -postcss-merge-rules@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.2.tgz#2be44401bf19856f27f32b8b12c0df5af1b88e74" - integrity sha512-UiuXwCCJtQy9tAIxsnurfF0mrNHKc4NnNx6NxqmzNNjXpQwLSukUxELHTRF0Rg1pAmcoKLih8PwvZbiordchag== +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== dependencies: browserslist "^4.0.0" caniuse-api "^3.0.0" @@ -9682,20 +11756,20 @@ postcss-minify-font-values@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-minify-gradients@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.1.tgz#6da95c6e92a809f956bb76bf0c04494953e1a7dd" - integrity sha512-pySEW3E6Ly5mHm18rekbWiAjVi/Wj8KKt2vwSfVFAWdW6wOIekgqxKxLU7vJfb107o3FDNPkaYFCxGAJBFyogA== +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== dependencies: cssnano-util-get-arguments "^4.0.0" is-color-stop "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-minify-params@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.1.tgz#5b2e2d0264dd645ef5d68f8fec0d4c38c1cf93d2" - integrity sha512-h4W0FEMEzBLxpxIVelRtMheskOKKp52ND6rJv+nBS33G1twu2tCyurYj/YtgU76+UDCvWeNs0hs8HFAWE2OUFg== +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== dependencies: alphanum-sort "^1.0.0" browserslist "^4.0.0" @@ -9704,10 +11778,10 @@ postcss-minify-params@^4.0.1: postcss-value-parser "^3.0.0" uniqs "^2.0.0" -postcss-minify-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.1.tgz#a891c197977cc37abf60b3ea06b84248b1c1e9cd" - integrity sha512-8+plQkomve3G+CodLCgbhAKrb5lekAnLYuL1d7Nz+/7RANpBEVdgBkPNwljfSKvZ9xkkZTZITd04KP+zeJTJqg== +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== dependencies: alphanum-sort "^1.0.0" has "^1.0.0" @@ -9715,9 +11789,9 @@ postcss-minify-selectors@^4.0.1: postcss-selector-parser "^3.0.0" postcss-modules-extract-imports@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" - integrity sha1-ZhQOzs447wa/DT41XWm/WdFB6oU= + version "1.2.1" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a" + integrity sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw== dependencies: postcss "^6.0.1" @@ -9752,48 +11826,48 @@ postcss-normalize-charset@^4.0.1: dependencies: postcss "^7.0.0" -postcss-normalize-display-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz#d9a83d47c716e8a980f22f632c8b0458cfb48a4c" - integrity sha512-R5mC4vaDdvsrku96yXP7zak+O3Mm9Y8IslUobk7IMP+u/g+lXvcN4jngmHY5zeJnrQvE13dfAg5ViU05ZFDwdg== +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== dependencies: cssnano-util-get-match "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-normalize-positions@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.1.tgz#ee2d4b67818c961964c6be09d179894b94fd6ba1" - integrity sha512-GNoOaLRBM0gvH+ZRb2vKCIujzz4aclli64MBwDuYGU2EY53LwiP7MxOZGE46UGtotrSnmarPPZ69l2S/uxdaWA== +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== dependencies: cssnano-util-get-arguments "^4.0.0" has "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-normalize-repeat-style@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.1.tgz#5293f234b94d7669a9f805495d35b82a581c50e5" - integrity sha512-fFHPGIjBUyUiswY2rd9rsFcC0t3oRta4wxE1h3lpwfQZwFeFjXFSiDtdJ7APCmHQOnUZnqYBADNRPKPwFAONgA== +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== dependencies: cssnano-util-get-arguments "^4.0.0" cssnano-util-get-match "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-normalize-string@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.1.tgz#23c5030c2cc24175f66c914fa5199e2e3c10fef3" - integrity sha512-IJoexFTkAvAq5UZVxWXAGE0yLoNN/012v7TQh5nDo6imZJl2Fwgbhy3J2qnIoaDBrtUP0H7JrXlX1jjn2YcvCQ== +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== dependencies: has "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-normalize-timing-functions@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.1.tgz#8be83e0b9cb3ff2d1abddee032a49108f05f95d7" - integrity sha512-1nOtk7ze36+63ONWD8RCaRDYsnzorrj+Q6fxkQV+mlY5+471Qx9kspqv0O/qQNMeApg8KNrRf496zHwJ3tBZ7w== +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== dependencies: cssnano-util-get-match "^4.0.0" postcss "^7.0.0" @@ -9818,65 +11892,66 @@ postcss-normalize-url@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-normalize-whitespace@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.1.tgz#d14cb639b61238418ac8bc8d3b7bdd65fc86575e" - integrity sha512-U8MBODMB2L+nStzOk6VvWWjZgi5kQNShCyjRhMT3s+W9Jw93yIjOnrEkKYD3Ul7ChWbEcjDWmXq0qOL9MIAnAw== +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== dependencies: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-ordered-values@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.1.tgz#2e3b432ef3e489b18333aeca1f1295eb89be9fc2" - integrity sha512-PeJiLgJWPzkVF8JuKSBcylaU+hDJ/TX3zqAMIjlghgn1JBi6QwQaDZoDIlqWRcCAI8SxKrt3FCPSRmOgKRB97Q== +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== dependencies: cssnano-util-get-arguments "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-reduce-initial@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.2.tgz#bac8e325d67510ee01fa460676dc8ea9e3b40f15" - integrity sha512-epUiC39NonKUKG+P3eAOKKZtm5OtAtQJL7Ye0CBN1f+UQTHzqotudp+hki7zxXm7tT0ZAKDMBj1uihpPjP25ug== +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== dependencies: browserslist "^4.0.0" caniuse-api "^3.0.0" has "^1.0.0" postcss "^7.0.0" -postcss-reduce-transforms@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.1.tgz#8600d5553bdd3ad640f43bff81eb52f8760d4561" - integrity sha512-sZVr3QlGs0pjh6JAIe6DzWvBaqYw05V1t3d9Tp+VnFRT5j+rsqoWsysh/iSD7YNsULjq9IAylCznIwVd5oU/zA== +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== dependencies: cssnano-util-get-match "^4.0.0" has "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-selector-parser@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" - integrity sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A= +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== dependencies: - flatten "^1.0.2" + dot-prop "^5.2.0" indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" - integrity sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU= +postcss-selector-parser@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.3.tgz#766d77728728817cc140fa1ac6da5e77f9fada98" + integrity sha512-0ClFaY4X1ra21LRqbW6y3rUbWcxnSVkDFG57R7Nxus9J9myPFlv+jYDMohzpkBx0RrjjiqjtycpchQ+PLGmZ9w== dependencies: - dot-prop "^4.1.1" + cssesc "^3.0.0" indexes-of "^1.0.1" uniq "^1.0.1" + util-deprecate "^1.0.2" -postcss-svgo@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.1.tgz#5628cdb38f015de6b588ce6d0bf0724b492b581d" - integrity sha512-YD5uIk5NDRySy0hcI+ZJHwqemv2WiqqzDgtvgMzO8EGSkK5aONyX8HMVFRFJSdO8wUWTuisUFn/d7yRRbBr5Qw== +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== dependencies: is-svg "^3.0.0" postcss "^7.0.0" @@ -9892,12 +11967,17 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" - integrity sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU= +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23: +postcss@^6.0.1, postcss@^6.0.23: version "6.0.23" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== @@ -9906,67 +11986,70 @@ postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2: - version "7.0.5" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.5.tgz#70e6443e36a6d520b0fd4e7593fcca3635ee9f55" - integrity sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ== +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32: + version "7.0.34" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20" + integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw== dependencies: - chalk "^2.4.1" + chalk "^2.4.2" source-map "^0.6.1" - supports-color "^5.5.0" + supports-color "^6.1.0" -potrace@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/potrace/-/potrace-2.1.1.tgz#79111a858197f366418845f667fe8f7fac0a79db" - integrity sha1-eREahYGX82ZBiEX2Z/6Pf6wKeds= +potrace@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/potrace/-/potrace-2.1.8.tgz#50f6fba92e1e39ddef6f979b0a0f841809e0acf2" + integrity sha512-V9hI7UMJyEhNZjM8CbZaP/804ZRLgzWkCS9OOYnEZkszzj3zKR/erRdj0uFMcN3pp6x4B+AIZebmkQgGRinG/g== dependencies: - jimp "^0.2.24" + jimp "^0.14.0" -prebuild-install@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-4.0.0.tgz#206ce8106ce5efa4b6cf062fc8a0a7d93c17f3a8" - integrity sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA== +prebuild-install@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870" + integrity sha512-QBSab31WqkyxpnMWQxubYAHR5S9B2+r81ucocew34Fkl98FhvKIF50jIJnNOBmAZfyNV7vE5T6gd3hTVWgY6tg== dependencies: - detect-libc "^1.0.3" - expand-template "^1.0.2" + detect-libc "^2.0.0" + expand-template "^2.0.3" github-from-package "0.0.0" - minimist "^1.2.0" - mkdirp "^0.5.1" - node-abi "^2.2.0" - noop-logger "^0.1.1" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" npmlog "^4.0.1" - os-homedir "^1.0.1" - pump "^2.0.1" - rc "^1.1.6" - simple-get "^2.7.0" - tar-fs "^1.13.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" tunnel-agent "^0.6.0" - which-pm-runs "^1.0.0" prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.1: +prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= prettier@^1.7.4: - version "1.14.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.2.tgz#0ac1c6e1a90baa22a62925f41963c841983282f9" - integrity sha512-McHPg0n1pIke+A/4VcaS2en+pTNjy4xF+Uuq86u/5dyDO59/TtFZtQ708QIRkEZ3qwKz3GVkVa6mpxK/CpB8Rg== + version "1.19.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -pretty-bytes@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" - integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk= +prettier@^2.0.5: + version "2.1.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" + integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== + +pretty-bytes@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" + integrity sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA== pretty-error@^2.1.1: version "2.1.1" @@ -9976,34 +12059,36 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -prismjs@^1.15.0, prismjs@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9" - integrity sha512-Lf2JrFYx8FanHrjoV5oL8YHCclLQgbJcVZR+gikGGMqz6ub5QVWDTM6YIwm3BuPxM/LOV+rKns3LssXNLIf+DA== - optionalDependencies: - clipboard "^2.0.0" +pretty-format@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" + integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== + dependencies: + "@jest/types" "^25.5.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" -private@^0.1.6: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== +prismjs@^1.27.0, prismjs@^1.6.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== -probe-image-size@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-4.0.0.tgz#d35b71759e834bcf580ea9f18ec8b9265c0977eb" - integrity sha512-nm7RvWUxps+2+jZKNLkd04mNapXNariS6G5WIEVzvAqjx7EUuKcY1Dp3e6oUK7GLwzJ+3gbSbPLFAASHFQrPcQ== +probe-image-size@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-5.0.0.tgz#1b87d20340ab8fcdb4324ec77fbc8a5f53419878" + integrity sha512-V6uBYw5eBc5UVIE7MUZD6Nxg0RYuGDWLDenEn0B1WC6PcTvn1xdQ6HLDDuznefsiExC6rNrCz7mFRBo0f3Xekg== dependencies: - any-promise "^1.3.0" - deepmerge "^2.0.1" + deepmerge "^4.0.0" inherits "^2.0.3" next-tick "^1.0.0" request "^2.83.0" stream-parser "~0.3.1" process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process@^0.11.10: version "0.11.10" @@ -10015,15 +12100,10 @@ process@~0.5.1: resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= -progress@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" - integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= - -progress@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" - integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8= +progress@^2.0.0, progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== promise-inflight@^1.0.1: version "1.0.1" @@ -10037,31 +12117,68 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2: - version "15.6.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" - integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== +prompts@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.4" + +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== dependencies: - loose-envify "^1.3.1" + loose-envify "^1.4.0" object-assign "^4.1.1" + react-is "^16.8.1" -property-information@^3.0.0, property-information@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-3.2.0.tgz#fd1483c8fbac61808f5fe359e7693a1f48a58331" - integrity sha1-/RSDyPusYYCPX+NZ52k6H0ilgzE= +proper-lockfile@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.1.tgz#284cf9db9e30a90e647afad69deb7cb06881262c" + integrity sha512-1w6rxXodisVpn7QYvLk706mzprPTAPCYAqxMvctmPN3ekuRk/kuGkGc82pangZiAt4R3lwSuUzheTTn0/Yb7Zg== + dependencies: + graceful-fs "^4.1.11" + retry "^0.12.0" + signal-exit "^3.0.2" + +property-expr@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" + integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== + +property-information@5.5.0, property-information@^5.0.0, property-information@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943" + integrity sha512-RgEbCx2HLa1chNgvChcx+rrCWD0ctBmGSE0M7lVm1yyv4UbvbrWoXp/BkVLZefzjrRBGW8/Js6uh/BnlHXFyjA== + dependencies: + xtend "^4.0.0" + +property-information@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-4.2.0.tgz#f0e66e07cbd6fed31d96844d958d153ad3eb486e" + integrity sha512-TlgDPagHh+eBKOnH2VYvk8qbwsCG/TAJdmTL7f1PROUcSO8qt/KSmShEQ/OKvock8X9tFjtqjCScyOkkkvIKVQ== + dependencies: + xtend "^4.0.1" proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proxy-addr@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" - integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== +protocols@^1.1.0, protocols@^1.4.0: + version "1.4.8" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" + integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== dependencies: forwarded "~0.1.2" - ipaddr.js "1.8.0" + ipaddr.js "1.9.1" prr@~1.0.1: version "1.0.1" @@ -10073,10 +12190,10 @@ pseudomap@^1.0.1, pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" - integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== public-encrypt@^4.0.0: version "4.0.3" @@ -10090,15 +12207,7 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pump@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" - integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^2.0.0, pump@^2.0.1: +pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== @@ -10128,26 +12237,69 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@2.x.x, punycode@^2.1.0: +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -punycode@^1.2.4, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +pupa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" + integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== + dependencies: + escape-goat "^2.0.0" q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.5.2, qs@~6.5.2: +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@^6.5.2: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + +qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +query-string@^6.13.1: + version "6.13.2" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.2.tgz#3585aa9412c957cbd358fd5eaca7466f05586dda" + integrity sha512-BMmDaUiLDFU1hlM38jTFcRt7HYiGP/zt1sRzrIWm5zpeEuO1rkbPS0ELI3uehoLuuhHDCS8u8lhFN3fEN4JzPQ== + dependencies: + decode-uri-component "^0.2.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -10158,24 +12310,15 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" - integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg== - -randomatic@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116" - integrity sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ== - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" - integrity sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A== +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" @@ -10187,19 +12330,29 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@^1.0.3, range-parser@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.3.3, raw-body@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" - integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== dependencies: - bytes "3.0.0" - http-errors "1.6.3" - iconv-lite "0.4.23" + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +raw-body@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" + integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== + dependencies: + bytes "3.1.0" + http-errors "1.7.3" + iconv-lite "0.4.24" unpipe "1.0.0" raw-loader@^0.5.1: @@ -10207,7 +12360,7 @@ raw-loader@^0.5.1: resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" integrity sha1-DD0L6u2KAclm2Xh793goElKpeao= -rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.2.7: +rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -10217,10 +12370,22 @@ rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dev-utils@^4.2.1: - version "4.2.2" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-4.2.2.tgz#e8f2ffdbf27bfb13ee88ac18e20c83163aac0659" - integrity sha512-HwN0EE+9DS7wB0kKy6Bc5kUTUGUAOyZorJeb+ZGeTrxd1ZNwEJn1TfCRuNpRRa+Iu3VeYBcQ2pjuordJ4eqmfA== +react-circular-progressbar@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/react-circular-progressbar/-/react-circular-progressbar-2.0.3.tgz#fa8eb59f8db168d2904bae4590641792c80f5991" + integrity sha512-YKN+xAShXA3gYihevbQZbavfiJxo83Dt1cUxqg/cltj4VVsRQpDr7Fg1mvjDG3x1KHGtd9NmYKvJ2mMrPwbKyw== + +react-clientside-effect@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.2.tgz#6212fb0e07b204e714581dd51992603d1accc837" + integrity sha512-nRmoyxeok5PBO6ytPvSjKp9xwXg9xagoTK1mMjwnQxqM9Hd7MNPl+LS1bOSOe+CV2+4fnEquc7H/S8QD3q697A== + dependencies: + "@babel/runtime" "^7.0.0" + +react-dev-utils@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-4.2.3.tgz#5b42d9ea58d5e9e017a2f57a40a8af408a3a46fb" + integrity sha512-uvmkwl5uMexCmC0GUv1XGQP0YjfYePJufGg4YYiukhqk2vN1tQxwWJIBERqhOmSi80cppZg8mZnPP/kOMf1sUQ== dependencies: address "1.0.3" babel-code-frame "6.26.0" @@ -10241,42 +12406,72 @@ react-dev-utils@^4.2.1: strip-ansi "3.0.1" text-table "0.2.0" -react-dom@^16.13.0: - version "16.13.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.0.tgz#cdde54b48eb9e8a0ca1b3dc9943d9bb409b81866" - integrity sha512-y09d2c4cG220DzdlFkPTnVvGTszVvNpC73v+AaLGLHbkpy3SSgvYq8x0rNwPJ/Rk/CicTNgk0hbHNw1gMEZAXg== +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.0" + scheduler "^0.20.2" react-error-overlay@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655" integrity sha512-XzgvowFrwDo6TWcpJ/WTiarb9UI6lhA4PMzS7n1joK3sHfBBBOQHUc0U4u57D6DWO9vHv6lVSWx2Q/Ymfyv4hw== +react-fast-compare@^2.0.1, react-fast-compare@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-focus-lock@^2.3.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.4.1.tgz#e842cc93da736b5c5d331799012544295cbcee4f" + integrity sha512-c5ZP56KSpj9EAxzScTqQO7bQQNPltf/W1ZEBDqNDOV1XOIwvAyHX0O7db9ekiAtxyKgnqZjQlLppVg94fUeL9w== + dependencies: + "@babel/runtime" "^7.0.0" + focus-lock "^0.7.0" + prop-types "^15.6.2" + react-clientside-effect "^1.2.2" + use-callback-ref "^1.2.1" + use-sidecar "^1.0.1" + react-helmet@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7" - integrity sha1-qBgR3yExOm1VxfBYxK66XW89l6c= + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.1.tgz#16a7192fdd09951f8e0fe22ffccbf9bb3e591ffa" + integrity sha512-CnwD822LU8NDBnjCpZ4ySh8L6HYyngViTZLfBBb3NjtrpN8m49clH8hidHouq20I51Y6TpCTISCBbqiY5GamwA== dependencies: - deep-equal "^1.0.1" object-assign "^4.1.1" prop-types "^15.5.4" + react-fast-compare "^2.0.2" react-side-effect "^1.1.0" -react-hot-loader@^4.1.0: - version "4.3.11" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.3.11.tgz#fe5cf7be7700c249b58293f977c1e6e0900f0d87" - integrity sha512-T0G5jURyTsFLoiW6MTr5Q35UHC/B2pmYJ7+VBjk8yMDCEABRmCGy4g6QwxoB4pWg4/xYvVTa/Pbqnsgx/+NLuA== +react-hot-loader@^4.12.21: + version "4.13.0" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202" + integrity sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA== dependencies: fast-levenshtein "^2.0.6" global "^4.3.0" - hoist-non-react-statics "^2.5.0" + hoist-non-react-statics "^3.3.0" + loader-utils "^1.1.0" prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" - shallowequal "^1.0.2" + shallowequal "^1.1.0" + source-map "^0.7.3" + +react-icons@^3.0.1: + version "3.11.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.11.0.tgz#2ca2903dfab8268ca18ebd8cc2e879921ec3b254" + integrity sha512-JRgiI/vdF6uyBgyZhVyYJUZAop95Sy4XDe/jmT3R/bKliFWpO/uZBwvSjWEdxwzec7SYbEPNPck0Kff2tUGM2Q== + dependencies: + camelcase "^5.0.0" + +react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-lifecycles-compat@^3.0.4: version "3.0.4" @@ -10295,35 +12490,90 @@ react-live@1.8.0-0: prop-types "^15.5.8" unescape "^0.2.0" +react-reconciler@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.24.0.tgz#5a396b2c2f5efe8554134a5935f49f546723f2dd" + integrity sha512-gAGnwWkf+NOTig9oOowqid9O0HjTDC+XVGBCAmJYYJ2A2cN/O4gDdIuuUQjv8A4v6GDwVfJkagpBBLW5OW9HSw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.18.0" + +react-reconciler@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.25.1.tgz#f9814d59d115e1210762287ce987801529363aaa" + integrity sha512-R5UwsIvRcSs3w8n9k3tBoTtUHdVhu9u84EG7E5M0Jk9F5i6DA1pQzPfUZd6opYWGy56MJOtV3VADzy6DRwYDjw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-refresh@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.7.2.tgz#f30978d21eb8cac6e2f2fde056a7d04f6844dd50" + integrity sha512-u5l7fhAJXecWUJzVxzMRU2Zvw8m4QmDNHlTrT5uo3KBlYBhmChd7syAakBoay1yIiVhx/8Fi7a6v6kQZfsw81Q== + +react-remove-scroll-bar@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.1.0.tgz#edafe9b42a42c0dad9bdd10712772a1f9a39d7b9" + integrity sha512-5X5Y5YIPjIPrAoMJxf6Pfa7RLNGCgwZ95TdnVPgPuMftRfO8DaC7F4KP1b5eiO8hHbe7u+wZNDbYN5WUTpv7+g== + dependencies: + react-style-singleton "^2.1.0" + tslib "^1.0.0" + +react-remove-scroll@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.0.tgz#190c16eb508c5927595935499e8f5dd9ab0e75cf" + integrity sha512-BZIO3GaEs0Or1OhA5C//n1ibUP1HdjJmqUVUsOCMxwoIpaCocbB9TFKwHOkBa/nyYy3slirqXeiPYGwdSDiseA== + dependencies: + react-remove-scroll-bar "^2.1.0" + react-style-singleton "^2.1.0" + tslib "^1.0.0" + use-callback-ref "^1.2.3" + use-sidecar "^1.0.1" + react-side-effect@^1.1.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d" - integrity sha512-Z2ZJE4p/jIfvUpiUMRydEVpQRf2f8GMHczT6qLcARmX7QRb28JDBTpnM2g/i5y/p7ZDEXYGHWg0RbhikE+hJRw== + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.2.0.tgz#0e940c78faba0c73b9b0eba9cd3dda8dfb7e7dae" + integrity sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w== dependencies: - exenv "^1.2.1" shallowequal "^1.0.1" -react@^16.13.0: - version "16.13.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7" - integrity sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ== +react-style-singleton@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.0.tgz#7396885332e9729957f9df51f08cadbfc164e1c4" + integrity sha512-DH4ED+YABC1dhvSDYGGreAHmfuTXj6+ezT3CmHoqIEfxNgEYfIMoOtmbRp42JsUst3IPqBTDL+8r4TF7EWhIHw== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^1.0.0" + +react@^16.8.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - integrity sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po= +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" + loose-envify "^1.1.0" + object-assign "^4.1.1" -read-chunk@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-1.0.1.tgz#5f68cab307e663f19993527d9b589cace4661194" - integrity sha1-X2jKswfmY/GZk1J9m1icrORmEZQ= +read-chunk@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-3.2.0.tgz#2984afe78ca9bfbbdb74b19387bf9e86289c16ca" + integrity sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ== + dependencies: + pify "^4.0.1" + with-open-file "^0.1.6" read-pkg-up@^1.0.1: version "1.0.1" @@ -10368,6 +12618,15 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" +read-pkg@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" + integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc= + dependencies: + normalize-package-data "^2.3.2" + parse-json "^4.0.0" + pify "^3.0.0" + read@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -10375,10 +12634,10 @@ read@^1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: - version "2.3.6" - resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -10388,27 +12647,26 @@ read@^1.0.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.31: - version "1.0.34" - resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" -readable-stream@~1.1.9: - version "1.1.14" - resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= +readable-stream@~1.0.31: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= dependencies: core-util-is "~1.0.0" inherits "~2.0.1" isarray "0.0.1" string_decoder "~0.10.x" -readdirp@^2.0.0: +readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== @@ -10417,12 +12675,12 @@ readdirp@^2.0.0: micromatch "^3.1.10" readable-stream "^2.0.2" -recursive-readdir-synchronous@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/recursive-readdir-synchronous/-/recursive-readdir-synchronous-0.0.3.tgz#d5e5a096ad56cf9666241c22a30b4f338bb7ed88" - integrity sha1-1eWglq1Wz5ZmJBwiowtPM4u37Yg= +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== dependencies: - minimatch "0.3.0" + picomatch "^2.2.1" recursive-readdir@2.2.1: version "2.2.1" @@ -10431,7 +12689,7 @@ recursive-readdir@2.2.1: dependencies: minimatch "3.0.3" -recursive-readdir@^2.2.1: +recursive-readdir@^2.2.1, recursive-readdir@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== @@ -10446,64 +12704,47 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -reduce-css-calc@^2.0.0: - version "2.1.5" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.5.tgz#f283712f0c9708ef952d328f4b16112d57b03714" - integrity sha512-AybiBU03FKbjYzyvJvwkJZY6NLN+80Ufc2EqEs+41yQH+8wqBEslD6eGiS0oIeq5TNLA5PrhBeYHXWdn8gtW7A== - dependencies: - css-unit-converter "^1.1.1" - postcss-value-parser "^3.3.0" +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== -redux@^3.6.0: - version "3.7.2" - resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" - integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A== +redux@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== dependencies: - lodash "^4.2.1" - lodash-es "^4.2.1" - loose-envify "^1.1.0" - symbol-observable "^1.0.3" + loose-envify "^1.4.0" + symbol-observable "^1.2.0" -regenerate-unicode-properties@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" - integrity sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw== +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== dependencies: regenerate "^1.4.0" -regenerate@^1.2.1, regenerate@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" - integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== - -regenerator-runtime@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" - integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= +regenerate@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" + integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== -regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: +regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.12.0: - version "0.12.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" - integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== - -regenerator-transform@^0.13.3: - version "0.13.3" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" - integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA== - dependencies: - private "^0.1.6" +regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== +regenerator-transform@^0.14.2: + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== dependencies: - is-equal-shallow "^0.1.3" + "@babel/runtime" "^7.8.4" regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -10513,104 +12754,120 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" + integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + regexpp@^1.0.1: version "1.1.0" - resolved "http://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" integrity sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw== -regexpu-core@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" - integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== -regexpu-core@^4.1.3, regexpu-core@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.2.0.tgz#a3744fa03806cffe146dea4421a3e73bdcc47b1d" - integrity sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw== +regexpp@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +regexpu-core@^4.5.4, regexpu-core@^4.7.0: + version "4.7.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" + integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== dependencies: regenerate "^1.4.0" - regenerate-unicode-properties "^7.0.0" - regjsgen "^0.4.0" - regjsparser "^0.3.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.0.2" + unicode-match-property-value-ecmascript "^1.2.0" -registry-auth-token@^3.0.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" - integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== +registry-auth-token@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da" + integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w== dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" + rc "^1.2.8" -registry-url@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== dependencies: - rc "^1.0.1" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= + rc "^1.2.8" -regjsgen@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.4.0.tgz#c1eb4c89a209263f8717c782591523913ede2561" - integrity sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA== - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= - dependencies: - jsesc "~0.5.0" +regjsgen@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== -regjsparser@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.3.0.tgz#3c326da7fcfd69fa0d332575a41c8c0cdf588c96" - integrity sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA== +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== dependencies: jsesc "~0.5.0" -relay-compiler@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-1.5.0.tgz#564f1582c549fa6b4af9d9f09dadb5e239c11055" - integrity sha512-nB3HbGXy4UtdQRGVeBlzNbUSN0maETdB/dAggdxN2+mdg4tGqj04zdrcxrnXUpnobab8tXKZlyaRnKKEHvcTTA== - dependencies: - babel-generator "^6.26.0" - babel-polyfill "^6.20.0" - babel-preset-fbjs "^2.1.4" - babel-runtime "^6.23.0" - babel-traverse "^6.26.0" - babel-types "^6.24.1" - babylon "^7.0.0-beta" - chalk "^1.1.1" - fast-glob "^2.0.0" - fb-watchman "^2.0.0" - fbjs "^0.8.14" - graphql "^0.13.0" - immutable "~3.7.6" - relay-runtime "1.5.0" - signedsource "^1.0.0" - yargs "^9.0.0" - -relay-runtime@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-1.5.0.tgz#95e7c26f95f216370f7d699290238a4d966a915c" - integrity sha512-XWV9xsjIKPPSPAfpVSaiXXZkefIMpBlj2x1MAsZgQ9v2aLVIewB4f8gTHMl1tBfrC9zSREaMhbemz9Inlwnkyg== +remark-footnotes@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-1.0.0.tgz#9c7a97f9a89397858a50033373020b1ea2aad011" + integrity sha512-X9Ncj4cj3/CIvLI2Z9IobHtVi8FVdUrdJkCNaL9kdX8ohfsi18DXHsCVd/A7ssARBdccdDb5ODnt62WuEWaM/g== + +remark-mdx@^2.0.0-next.4, remark-mdx@^2.0.0-next.8: + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-2.0.0-next.8.tgz#db1c3cbc606ea0d01526242199bb134d99020363" + integrity sha512-mjP0yo6BgjYrx5a+gKWYRFWbGnRiWi4Fdf17xGCr9VkSMnG4Dyo06spqbaLfHwl0KkQ/RQZlR2sn1mKnYduJdw== + dependencies: + parse-entities "^2.0.0" + remark-stringify "^8.1.0" + stringify-entities "^3.0.1" + strip-indent "^3.0.0" + unist-util-stringify-position "^2.0.3" + +remark-mdxjs@^2.0.0-next.4, remark-mdxjs@^2.0.0-next.8: + version "2.0.0-next.8" + resolved "https://registry.yarnpkg.com/remark-mdxjs/-/remark-mdxjs-2.0.0-next.8.tgz#ff603ebfcb17f19503ee3fab78447445eaa08783" + integrity sha512-Z/+0eWc7pBEABwg3a5ptL+vCTWHYMFnYzpLoJxTm2muBSk8XyB/CL+tEJ6SV3Q/fScHX2dtG4JRcGSpbZFLazQ== + dependencies: + "@babel/core" "7.10.5" + "@babel/helper-plugin-utils" "7.10.4" + "@babel/plugin-proposal-object-rest-spread" "7.10.4" + "@babel/plugin-syntax-jsx" "7.10.4" + "@mdx-js/util" "^2.0.0-next.8" + +remark-parse@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.2.tgz#5999bc0b9c2e3edc038800a64ff103d0890b318b" + integrity sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ== dependencies: - babel-runtime "^6.23.0" - fbjs "^0.8.14" + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" remark-parse@^1.1.0: version "1.1.0" - resolved "http://registry.npmjs.org/remark-parse/-/remark-parse-1.1.0.tgz#c3ca10f9a8da04615c28f09aa4e304510526ec21" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-1.1.0.tgz#c3ca10f9a8da04615c28f09aa4e304510526ec21" + integrity sha1-w8oQ+ajaBGFcKPCapOMEUQUm7CE= dependencies: collapse-white-space "^1.0.0" extend "^3.0.0" @@ -10622,37 +12879,65 @@ remark-parse@^1.1.0: unist-util-remove-position "^1.0.0" vfile-location "^2.0.0" -remark-parse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" - integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA== +remark-parse@^6.0.0, remark-parse@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a" + integrity sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg== dependencies: collapse-white-space "^1.0.2" is-alphabetical "^1.0.0" is-decimal "^1.0.0" is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^1.1.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^1.0.0" + vfile-location "^2.0.0" + xtend "^4.0.1" + +remark-retext@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/remark-retext/-/remark-retext-3.1.3.tgz#77173b1d9d13dab15ce5b38d996195fea522ee7f" + integrity sha512-UujXAm28u4lnUvtOZQFYfRIhxX+auKI9PuA2QpQVTT7gYk1OgX6o0OUrSo1KOa6GNrFX+OODOtS5PWIHPxM7qw== + dependencies: + mdast-util-to-nlcst "^3.2.0" + +remark-squeeze-paragraphs@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" + integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw== + dependencies: + mdast-squeeze-paragraphs "^4.0.0" + +remark-stringify@6.0.4, remark-stringify@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088" + integrity sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg== + dependencies: + ccount "^1.0.0" + is-alphanumeric "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + longest-streak "^2.0.1" markdown-escapes "^1.0.0" - parse-entities "^1.1.0" + markdown-table "^1.1.0" + mdast-util-compact "^1.0.0" + parse-entities "^1.0.2" repeat-string "^1.5.4" state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" + stringify-entities "^1.0.1" unherit "^1.0.4" - unist-util-remove-position "^1.0.0" - vfile-location "^2.0.0" xtend "^4.0.1" -remark-retext@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/remark-retext/-/remark-retext-3.1.1.tgz#38eb1ba8ac7c03846eecd534b414355676815927" - integrity sha512-6njJXkOTfQhyDYABvi4iEB81x8E6EL5cnLPtfpYrunSLQM2s1j51hma29dVkMzk9FuHqy65Zb1Tgb34UAzw+TQ== - dependencies: - mdast-util-to-nlcst "^3.2.0" - remark-stringify@^1.1.0: version "1.1.0" - resolved "http://registry.npmjs.org/remark-stringify/-/remark-stringify-1.1.0.tgz#a7105e25b9ee2bf9a49b75d2c423f11b06ae2092" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-1.1.0.tgz#a7105e25b9ee2bf9a49b75d2c423f11b06ae2092" + integrity sha1-pxBeJbnuK/mkm3XSxCPxGwauIJI= dependencies: ccount "^1.0.0" extend "^3.0.0" @@ -10663,10 +12948,10 @@ remark-stringify@^1.1.0: stringify-entities "^1.0.1" unherit "^1.0.4" -remark-stringify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba" - integrity sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w== +remark-stringify@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5" + integrity sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A== dependencies: ccount "^1.0.0" is-alphanumeric "^1.0.0" @@ -10674,39 +12959,40 @@ remark-stringify@^5.0.0: is-whitespace-character "^1.0.0" longest-streak "^2.0.1" markdown-escapes "^1.0.0" - markdown-table "^1.1.0" - mdast-util-compact "^1.0.0" - parse-entities "^1.0.2" + markdown-table "^2.0.0" + mdast-util-compact "^2.0.0" + parse-entities "^2.0.0" repeat-string "^1.5.4" state-toggle "^1.0.0" - stringify-entities "^1.0.1" + stringify-entities "^3.0.0" unherit "^1.0.4" xtend "^4.0.1" +remark@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df" + integrity sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ== + dependencies: + remark-parse "^6.0.0" + remark-stringify "^6.0.0" + unified "^7.0.0" + remark@^5.0.1: version "5.1.0" - resolved "http://registry.npmjs.org/remark/-/remark-5.1.0.tgz#cb463bd3dbcb4b99794935eee1cf71d7a8e3068c" + resolved "https://registry.yarnpkg.com/remark/-/remark-5.1.0.tgz#cb463bd3dbcb4b99794935eee1cf71d7a8e3068c" + integrity sha1-y0Y709vLS5l5STXu4c9x16jjBow= dependencies: remark-parse "^1.1.0" remark-stringify "^1.1.0" unified "^4.1.1" -remark@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/remark/-/remark-9.0.0.tgz#c5cfa8ec535c73a67c4b0f12bfdbd3a67d8b2f60" - integrity sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A== - dependencies: - remark-parse "^5.0.0" - remark-stringify "^5.0.0" - unified "^6.0.0" - remarkable@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.1.tgz#aaca4972100b66a642a63a1021ca4bac1be3bff6" - integrity sha1-qspJchALZqZCpjoQIcpLrBvjv/Y= + version "1.7.4" + resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" + integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== dependencies: - argparse "~0.1.15" - autolinker "~0.15.0" + argparse "^1.0.10" + autolinker "~0.28.0" remove-trailing-separator@^1.0.1: version "1.1.0" @@ -10714,13 +13000,13 @@ remove-trailing-separator@^1.0.1: integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= renderkid@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa" - integrity sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg== + version "2.0.3" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" + integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA== dependencies: css-select "^1.1.0" - dom-converter "~0.2" - htmlparser2 "~3.3.0" + dom-converter "^0.2" + htmlparser2 "^3.3.0" strip-ansi "^3.0.0" utila "^0.4.0" @@ -10729,7 +13015,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.0.0, repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -10741,37 +13027,37 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= - -replace-ext@1.0.0, replace-ext@^1.0.0: +replace-ext@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= -request-promise-core@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" - integrity sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY= +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== dependencies: - lodash "^4.13.1" + lodash "^4.17.19" request-promise@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.2.tgz#d1ea46d654a6ee4f8ee6a4fea1018c22911904b4" - integrity sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ= + version "4.2.6" + resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.6.tgz#7e7e5b9578630e6f598e3813c0f8eb342a27f0a2" + integrity sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ== dependencies: bluebird "^3.5.0" - request-promise-core "1.1.1" - stealthy-require "^1.1.0" - tough-cookie ">=2.3.3" + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" -request@^2.65.0, request@^2.83.0, request@^2.85.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== +request@^2.83.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -10780,7 +13066,7 @@ request@^2.65.0, request@^2.83.0, request@^2.85.0: extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.0" + har-validator "~5.1.3" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" @@ -10790,7 +13076,7 @@ request@^2.65.0, request@^2.83.0, request@^2.85.0: performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" - tough-cookie "~2.4.3" + tough-cookie "~2.5.0" tunnel-agent "^0.6.0" uuid "^3.3.2" @@ -10799,15 +13085,15 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= +require-package-name@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/require-package-name/-/require-package-name-2.0.1.tgz#c11e97276b65b8e2923f75dabf5fb2ef0c3841b9" + integrity sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk= require-uncached@^1.0.3: version "1.0.3" @@ -10829,6 +13115,13 @@ resolve-cwd@^2.0.0: dependencies: resolve-from "^3.0.0" +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-dir@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -10852,17 +13145,44 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.3.2, resolve@^1.5.0, resolve@^1.6.0, resolve@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +resolve@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + +responselike@1.0.2, responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== dependencies: - path-parse "^1.0.5" + lowercase-keys "^2.0.0" restore-cursor@^2.0.0: version "2.0.0" @@ -10872,39 +13192,47 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retext-english@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/retext-english/-/retext-english-3.0.0.tgz#c17cb56bd5f1ba3dee3355ddbab79f1c4894a809" - integrity sha1-wXy1a9Xxuj3uM1XdurefHEiUqAk= +retext-english@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/retext-english/-/retext-english-3.0.4.tgz#f978828d51fbcee842bc3807a45b7f709822ea8d" + integrity sha512-yr1PgaBDde+25aJXrnt3p1jvT8FVLVat2Bx8XeAWX13KXo8OT+3nWGU3HWxM4YFJvmfqvJYJZG2d7xxaO774gw== dependencies: parse-english "^4.0.0" unherit "^1.0.4" retext-latin@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-2.0.0.tgz#b11bd6cae9113fa6293022a4527cd707221ac4b6" - integrity sha1-sRvWyukRP6YpMCKkUnzXByIaxLY= + version "2.0.4" + resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-2.0.4.tgz#ef5d34ae7641ae56b0675ea391095e8ee762b251" + integrity sha512-fOoSSoQgDZ+l/uS81oxI3alBghDUPja0JEl0TpQxI6MN+dhM6fLFumPJwMZ4PJTyL5FFAgjlsdv8IX+6IRuwMw== dependencies: parse-latin "^4.0.0" unherit "^1.0.4" -retext-smartypants@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-3.0.1.tgz#53d718fde7178a9bc09f120159b0d76cc537fb6c" - integrity sha512-cwE0L/C13dw/DVi4Iao3FIdZEDm0reOKmXQUqNreAq5DPcqmO8SiaAvHaO7d6WzNLhRMhFu/R89IDQzJePn0ng== +retext-smartypants@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-3.0.3.tgz#9c982dcc210ae3bb36f4bc46aae802798fce43f9" + integrity sha512-/0fIipYayOvvucn3yjxvWwyT9P6p8gbpqdqUQNs1+L7av2hxatmiA9sk+fygJSDn5OXRyhBzcezvTbEmEabfIQ== dependencies: nlcst-to-string "^2.0.0" unist-util-visit "^1.0.0" retext-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-2.0.0.tgz#00238facc5491f5bcdc589703a4658db2e54415b" - integrity sha1-ACOPrMVJH1vNxYlwOkZY2y5UQVs= + version "2.0.4" + resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-2.0.4.tgz#496d6c532f7dc6d15e4b262de0266e828f72efa9" + integrity sha512-xOtx5mFJBoT3j7PBtiY2I+mEGERNniofWktI1cKXvjMEJPOuqve0dghLHO1+gz/gScLn4zqspDGv4kk2wS5kSA== dependencies: nlcst-to-string "^2.0.0" @@ -10917,6 +13245,16 @@ retext@^5.0.0: retext-stringify "^2.0.0" unified "^6.0.0" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -10927,17 +13265,26 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -ric@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ric/-/ric-1.3.0.tgz#8e95042609ce8213548a83164d08e94fae94909f" - integrity sha1-jpUEJgnOghNUioMWTQjpT66UkJ8= +rimraf@2.6.3, rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" -rimraf@^2.2.6, rimraf@^2.2.8, rimraf@^2.5.0, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: - glob "^7.0.5" + glob "^7.1.3" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" @@ -10955,12 +13302,15 @@ rss@^1.2.2: mime-types "2.1.13" xml "1.0.1" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= - dependencies: - is-promise "^2.1.0" +run-async@^2.2.0, run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" @@ -10981,11 +13331,23 @@ rx-lite@*, rx-lite@^4.0.8: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= -safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +rxjs@^6.5.2, rxjs@^6.6.0: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -10993,41 +13355,51 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@^1.18.2: - version "1.19.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.19.1.tgz#e8b33c69578054d6ee4f57ea152d6497f3f6fb7d" - integrity sha512-zNYr6FvBn4bZukr9x2uny6od/9YdjCLwF+FqxivqI0YOt/m9GIxfX+tWhm52tBAPUXiTTb4bJTGVagRz5b06bw== - dependencies: - chalk "^2.3.0" - htmlparser2 "^3.9.0" - lodash.clonedeep "^4.5.0" - lodash.escaperegexp "^4.1.2" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.mergewith "^4.6.0" - postcss "^6.0.14" - srcset "^1.0.0" - xtend "^4.0.0" +sanitize-html@^1.27.0: + version "1.27.4" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.27.4.tgz#3864e7562fc708cefabcb0d51bbacde3411504cb" + integrity sha512-VvY1hxVvMXzSos/LzqeBl9/KYu3mkEOtl5NMwz6jER318dSHDCig0AOjZOtnoCwAC3HMs9LhfWkPCmQGttb4ng== + dependencies: + htmlparser2 "^4.1.0" + lodash "^4.17.15" + parse-srcset "^1.0.2" + postcss "^7.0.27" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1, sax@~1.2.4: +sax@>=0.6.0, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.0.tgz#a715d56302de403df742f4a9be11975b32f5698d" - integrity sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA== +scheduler@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" + integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@^0.4.0, schema-utils@^0.4.4, schema-utils@^0.4.5: +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@^0.4.5: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== @@ -11044,13 +13416,14 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -scroll-behavior@^0.9.9: - version "0.9.9" - resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.9.tgz#ebfe0658455b82ad885b66195215416674dacce2" - integrity sha1-6/4GWEVbgq2IW2YZUhVBZnTazOI= +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== dependencies: - dom-helpers "^3.2.1" - invariant "^2.2.2" + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" section-matter@^1.0.0: version "1.0.0" @@ -11060,63 +13433,75 @@ section-matter@^1.0.0: extend-shallow "^2.0.1" kind-of "^6.0.0" -seek-bzip@^1.0.3, seek-bzip@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc" - integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w= +seek-bzip@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" + integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== dependencies: - commander "~2.8.1" + commander "^2.8.1" select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -select@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" - integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= - -selfsigned@^1.9.1: - version "1.10.4" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd" - integrity sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw== +selfsigned@^1.10.7: + version "1.10.8" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" + integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== dependencies: - node-forge "0.7.5" + node-forge "^0.10.0" -semver-diff@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== dependencies: - semver "^5.0.3" + semver "^6.3.0" -semver-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" - integrity sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk= +semver-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" + integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== -semver-truncate@^1.0.0: +semver-truncate@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/semver-truncate/-/semver-truncate-1.1.2.tgz#57f41de69707a62709a7e0104ba2117109ea47e8" integrity sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g= dependencies: semver "^5.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^4.0.3: - version "4.3.6" - resolved "http://registry.npmjs.org/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +semver@^7.3.5: + version "7.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" + integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== + dependencies: + lru-cache "^7.4.0" -send@0.16.2: - version "0.16.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" - integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== dependencies: debug "2.6.9" depd "~1.1.2" @@ -11125,19 +13510,21 @@ send@0.16.2: escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.4.0" + range-parser "~1.2.1" + statuses "~1.5.0" -serialize-javascript@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" - integrity sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ== +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" -serve-index@^1.7.2: +serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= @@ -11150,40 +13537,25 @@ serve-index@^1.7.2: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" - integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.2" + parseurl "~1.3.3" + send "0.17.1" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-immediate-shim@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= - -set-value@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" - integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.1" - to-object-path "^0.3.0" - -set-value@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" - integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== dependencies: extend-shallow "^2.0.1" is-extendable "^0.1.1" @@ -11200,9 +13572,19 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" - resolved "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== dependencies: inherits "^2.0.1" @@ -11213,25 +13595,23 @@ shallow-compare@^1.2.2: resolved "https://registry.yarnpkg.com/shallow-compare/-/shallow-compare-1.2.2.tgz#fa4794627bf455a47c4f56881d8a6132d581ffdb" integrity sha512-LUMFi+RppPlrHzbqmFnINTrazo0lPNwhcgzuAXVVcfy/mqPDrQmHAyz5bvV0gDAuRFrk804V0HpQ6u9sZ0tBeg== -shallowequal@^1.0.1, shallowequal@^1.0.2: +shallowequal@^1.0.1, shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== -sharp@^0.20.2: - version "0.20.8" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.20.8.tgz#e853f10b53b730824f0c3c5e453c79fa0812a48b" - integrity sha512-A8NaPGWRDKpmHTi8sl2xzozYXhTQWBb/GaJ8ZPU7L/vKW8wVvd4Yq+isJ0c7p9sX5gnjPQcM3eOfHuvvnZ2fOQ== - dependencies: - color "^3.0.0" - detect-libc "^1.0.3" - fs-copy-file-sync "^1.1.1" - nan "^2.11.0" - npmlog "^4.1.2" - prebuild-install "^4.0.0" - semver "^5.5.1" - simple-get "^2.8.1" - tar "^4.4.6" +sharp@^0.25.4, sharp@^0.30.3: + version "0.30.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.3.tgz#315a1817423a4d1cde5119a21c99c234a7a6fb37" + integrity sha512-rjpfJFK58ZOFSG8sxYSo3/JQb4ej095HjXp9X7gVu7gEn1aqSG8TCW29h/Rr31+PXrFADo1H/vKfw0uhMQWFtg== + dependencies: + color "^4.2.1" + detect-libc "^2.0.1" + node-addon-api "^4.3.0" + prebuild-install "^7.0.1" + semver "^7.3.5" + simple-get "^4.0.1" + tar-fs "^2.1.1" tunnel-agent "^0.6.0" shebang-command@^1.2.0: @@ -11241,12 +13621,24 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shell-quote@1.6.1, shell-quote@^1.6.1: +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c= @@ -11256,37 +13648,44 @@ shell-quote@1.6.1, shell-quote@^1.6.1: array-reduce "~0.0.0" jsonify "~0.0.0" -sift@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/sift/-/sift-5.1.0.tgz#1bbf2dfb0eb71e56c4cc7fb567fbd1351b65015e" - integrity sha1-G78t+w63HlbEzH+1Z/vRNRtlAV4= - -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= +shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +side-channel@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" + integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== + dependencies: + es-abstract "^1.18.0-next.0" + object-inspect "^1.8.0" -signedsource@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" - integrity sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo= +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== simple-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" - integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^2.7.0, simple-get@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== dependencies: - decompress-response "^3.3.0" + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" once "^1.3.1" simple-concat "^1.0.0" @@ -11297,11 +13696,28 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +single-trailing-newline@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/single-trailing-newline/-/single-trailing-newline-1.0.0.tgz#81f0ad2ad645181945c80952a5c1414992ee9664" + integrity sha1-gfCtKtZFGBlFyAlSpcFBSZLulmQ= + dependencies: + detect-newline "^1.0.3" + +sisteransi@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" @@ -11309,10 +13725,28 @@ slice-ansi@1.0.0: dependencies: is-fullwidth-code-point "^2.0.0" -slugify@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2" - integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw== +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slugify@^1.2.1, slugify@^1.4.4: + version "1.4.5" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.4.5.tgz#a7517acf5f4c02a4df41e735354b660a4ed1efcf" + integrity sha512-WpECLAgYaxHoEAJ8Q1Lo8HOs1ngn7LN7QjXgOLbmmfkcWvosyk4ZTXkTzKyhngK640USTZUlgoQJfED1kz5fnQ== snapdragon-node@^2.0.1: version "2.1.1" @@ -11345,50 +13779,76 @@ snapdragon@^0.8.1: use "^3.1.0" socket.io-adapter@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" - integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs= + version "1.1.2" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9" + integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g== -socket.io-client@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f" - integrity sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ== +socket.io-client@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4" + integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA== dependencies: backo2 "1.0.2" base64-arraybuffer "0.1.5" component-bind "1.0.0" component-emitter "1.2.1" - debug "~3.1.0" - engine.io-client "~3.2.0" + debug "~4.1.0" + engine.io-client "~3.4.0" has-binary2 "~1.0.2" has-cors "1.1.0" indexof "0.0.1" object-component "0.0.3" parseqs "0.0.5" parseuri "0.0.5" - socket.io-parser "~3.2.0" + socket.io-parser "~3.3.0" to-array "0.1.4" -socket.io-parser@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077" - integrity sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA== +socket.io-client@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35" + integrity sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ== dependencies: - component-emitter "1.2.1" + backo2 "1.0.2" + component-bind "1.0.0" + component-emitter "~1.3.0" debug "~3.1.0" - isarray "2.0.1" + engine.io-client "~3.5.0" + has-binary2 "~1.0.2" + indexof "0.0.1" + parseqs "0.0.6" + parseuri "0.0.6" + socket.io-parser "~3.3.0" + to-array "0.1.4" -socket.io@^2.0.3: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980" - integrity sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA== +socket.io-parser@~3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: + component-emitter "~1.3.0" debug "~3.1.0" - engine.io "~3.2.0" + isarray "2.0.1" + +socket.io-parser@~3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.4.1.tgz#b06af838302975837eab2dc980037da24054d64a" + integrity sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A== + dependencies: + component-emitter "1.2.1" + debug "~4.1.0" + isarray "2.0.1" + +socket.io@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2" + integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w== + dependencies: + debug "~4.1.0" + engine.io "~3.5.0" has-binary2 "~1.0.2" socket.io-adapter "~1.1.0" - socket.io-client "2.1.1" - socket.io-parser "~3.2.0" + socket.io-client "2.4.0" + socket.io-parser "~3.4.0" sockjs-client@1.1.4: version "1.1.4" @@ -11402,25 +13862,26 @@ sockjs-client@1.1.4: json3 "^3.3.2" url-parse "^1.1.8" -sockjs-client@1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.5.tgz#1bb7c0f7222c40f42adf14f4442cbd1269771a83" - integrity sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM= +sockjs-client@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" + integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== dependencies: - debug "^2.6.6" - eventsource "0.1.6" - faye-websocket "~0.11.0" - inherits "^2.0.1" + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" json3 "^3.3.2" - url-parse "^1.1.8" + url-parse "^1.4.3" -sockjs@0.3.19: - version "0.3.19" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== +sockjs@0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" + integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== dependencies: faye-websocket "^0.10.0" - uuid "^3.0.1" + uuid "^3.4.0" + websocket-driver "0.6.5" sort-keys-length@^1.0.0: version "1.0.1" @@ -11436,26 +13897,33 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= + dependencies: + is-plain-obj "^1.0.0" + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== dependencies: - atob "^2.1.1" + atob "^2.1.2" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@~0.5.6: - version "0.5.9" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" - integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== +source-map-support@~0.5.12: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -11465,7 +13933,12 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: +source-map@0.7.3, source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -11475,68 +13948,74 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + space-separated-tokens@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412" - integrity sha512-G3jprCEw+xFEs0ORweLmblJ3XLymGGr6hxZYTYZjIlvDti9vOBUjRQa1Rzjt012aRrocKstHwdNi+F7HguPsEA== - dependencies: - trim "0.0.1" + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== -sparkles@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" - integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= spdx-correct@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" - integrity sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ== + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2" - integrity sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg== + version "3.0.6" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" + integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== -spdy-transport@^2.0.18: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.0.tgz#4bbb15aaffed0beefdd56ad61dbdc8ba3e2cb7a1" - integrity sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g== +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== dependencies: - debug "^2.6.8" - detect-node "^2.0.3" + debug "^4.1.0" + detect-node "^2.0.4" hpack.js "^2.1.6" - obuf "^1.1.1" - readable-stream "^2.2.9" - safe-buffer "^5.0.1" - wbuf "^1.7.2" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" -spdy@^3.4.1: - version "3.4.7" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" - integrity sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw= +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== dependencies: - debug "^2.6.8" - handle-thing "^1.2.5" + debug "^4.1.0" + handle-thing "^2.0.0" http-deceiver "^1.2.7" - safe-buffer "^5.0.1" select-hose "^2.0.0" - spdy-transport "^2.0.18" + spdy-transport "^3.0.0" + +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -11546,9 +14025,9 @@ split-string@^3.0.1, split-string@^3.0.2: extend-shallow "^3.0.0" sprintf-js@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" - integrity sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw= + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== sprintf-js@~1.0.2: version "1.0.3" @@ -11564,45 +14043,42 @@ squeak@^1.0.0: console-stream "^0.1.1" lpad-align "^1.0.1" -srcset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef" - integrity sha1-pWad4StC87HV6D7QPHEEb8SPQe8= - dependencies: - array-uniq "^1.0.2" - number-is-nan "^1.0.0" - sshpk@^1.7.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" - integrity sha1-xvxhZIo9nE52T9P8306hBeSSupg= + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - safer-buffer "^2.0.2" - optionalDependencies: bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" ecc-jsbn "~0.1.1" - jsbn "~0.1.0" - tweetnacl "~0.14.0" - -ssri@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" - integrity sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ== - dependencies: - safe-buffer "^5.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" -ssri@^6.0.0: +ssri@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== dependencies: figgy-pudding "^3.5.1" -stable@~0.1.6: +st@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/st/-/st-2.0.0.tgz#eabd11e7722863b8ee8cfbdd027cb25e76ff35e9" + integrity sha512-drN+aGYnrZPNYIymmNwIY7LXYJ8MqsqXj4fMRue3FOgGMdGjSX10fhJ3qx0sVQPhcWxhEaN4U/eWM4O4dbYNAw== + dependencies: + async-cache "^1.1.0" + bl "^4.0.0" + fd "~0.0.2" + mime "^2.4.4" + negotiator "~0.6.2" + optionalDependencies: + graceful-fs "^4.2.3" + +stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== @@ -11612,20 +14088,15 @@ stack-trace@^0.0.10: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -stackframe@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b" - integrity sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw== - -stat-mode@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" - integrity sha1-5sgLYjEj19gM8TLOU480YokHJQI= +stackframe@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" + integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== state-toggle@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a" - integrity sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og== + version "1.0.3" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" + integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== static-extend@^0.1.1: version "0.1.2" @@ -11635,37 +14106,24 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2": +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" - integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== - -stealthy-require@^1.1.0: +stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= stream-browserify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - integrity sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds= + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== dependencies: inherits "~2.0.1" readable-stream "^2.0.2" -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -11693,23 +14151,29 @@ stream-parser@~0.3.1: debug "2" stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -stream-to-buffer@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz#26799d903ab2025c9bd550ac47171b00f8dd80a9" - integrity sha1-JnmdkDqyAlyb1VCsRxcbAPjdgKk= - dependencies: - stream-to "~0.2.0" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= -stream-to@~0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stream-to/-/stream-to-0.2.2.tgz#84306098d85fdb990b9fa300b1b3ccf55e8ef01d" - integrity sha1-hDBgmNhf25kLn6MAsbPM9V6O8B0= +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= + +string-length@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" + integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== + dependencies: + astral-regex "^1.0.0" + strip-ansi "^5.2.0" -string-similarity@^1.2.0: +string-similarity@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-1.2.2.tgz#99b2c20a3c9bbb3903964eae1d89856db3d8db9b" integrity sha512-IoHUjcw3Srl8nsPlW04U3qwWPk3oG2ffLM0tN853d/E/JlIvcmZmDY2Kz5HzKp4lEi2T7QD7Zuvjq/1rDw+XcQ== @@ -11737,27 +14201,79 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.matchall@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e" + integrity sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + has-symbols "^1.0.1" + internal-slot "^1.0.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.2" + string.prototype.padend@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0" - integrity sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA= + version "3.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" + integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== dependencies: - define-properties "^1.1.2" - es-abstract "^1.4.3" - function-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" -string_decoder@^1.0.0, string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== dependencies: - safe-buffer "~5.1.0" + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + stringify-entities@^1.0.1: version "1.3.2" resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7" @@ -11768,9 +14284,20 @@ stringify-entities@^1.0.1: is-alphanumerical "^1.0.0" is-hexadecimal "^1.0.0" -strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1: +stringify-entities@^3.0.0, stringify-entities@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.1.tgz#32154b91286ab0869ab2c07696223bd23b6dbfc0" + integrity sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.2" + is-hexadecimal "^1.0.0" + +strip-ansi@3.0.1, strip-ansi@^3, strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" - resolved "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" @@ -11782,13 +14309,19 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-bom-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" - integrity sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4= +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: - first-chunk-stream "^1.0.0" - strip-bom "^2.0.0" + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" strip-bom-string@^1.0.0: version "1.0.0" @@ -11807,17 +14340,10 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-dirs@^1.0.0: - version "1.1.1" - resolved "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz#960bbd1287844f3975a4558aa103a8255e2456a0" - integrity sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA= - dependencies: - chalk "^1.0.0" - get-stdin "^4.0.1" - is-absolute "^0.1.5" - is-natural-number "^2.0.0" - minimist "^1.1.0" - sum-up "^1.0.1" +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-dirs@^2.0.0: version "2.1.0" @@ -11831,6 +14357,11 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -11838,6 +14369,18 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -11850,80 +14393,118 @@ strip-outer@^1.0.0: dependencies: escape-string-regexp "^1.0.2" -style-loader@^0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.21.0.tgz#68c52e5eb2afc9ca92b6274be277ee59aea3a852" - integrity sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg== +style-loader@^0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" + integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg== dependencies: loader-utils "^1.1.0" - schema-utils "^0.4.5" + schema-utils "^1.0.0" + +style-to-object@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.2.3.tgz#afcf42bc03846b1e311880c55632a26ad2780bcb" + integrity sha512-1d/k4EY2N7jVLOqf2j04dTc37TPOv/hHxZmvpg8Pdh8UYydxeu/C1W1U4vD8alzf5V2Gt7rLsmkr4dxAlDm9ng== + dependencies: + inline-style-parser "0.1.1" + +style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" stylehacks@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.1.tgz#3186595d047ab0df813d213e51c8b94e0b9010f2" - integrity sha512-TK5zEPeD9NyC1uPIdjikzsgWxdQQN/ry1X3d1iOz1UkYDCmcr928gWD1KHgyC27F50UnE0xCTrBOO1l6KR8M4w== + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== dependencies: browserslist "^4.0.0" postcss "^7.0.0" postcss-selector-parser "^3.0.0" -sum-up@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sum-up/-/sum-up-1.0.3.tgz#1c661f667057f63bcb7875aa1438bc162525156e" - integrity sha1-HGYfZnBX9jvLeHWqFDi8FiUlFW4= +subscriptions-transport-ws@^0.9.16: + version "0.9.18" + resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz#bcf02320c911fbadb054f7f928e51c6041a37b97" + integrity sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA== dependencies: - chalk "^1.0.0" + backo2 "^1.0.2" + eventemitter3 "^3.1.0" + iterall "^1.2.1" + symbol-observable "^1.0.4" + ws "^5.2.0" + +sudo-prompt@^8.2.0: + version "8.2.5" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.5.tgz#cc5ef3769a134bb94b24a631cc09628d4d53603e" + integrity sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw== supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@^5.3.0, supports-color@^5.4.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" -svgo@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" - integrity sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U= +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== dependencies: - coa "~1.0.1" - colors "~1.1.2" - csso "~2.3.1" - js-yaml "~3.7.0" - mkdirp "~0.5.1" - sax "~1.2.1" - whet.extend "~0.9.9" + has-flag "^3.0.0" -svgo@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985" - integrity sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g== +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: - coa "~2.0.1" - colors "~1.1.2" + has-flag "^4.0.0" + +svg-tag-names@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/svg-tag-names/-/svg-tag-names-2.0.1.tgz#acf5655faaa2e4b173007599226b906be1b38a29" + integrity sha512-BEZ508oR+X/b5sh7bT0RqDJ7GhTpezjj3P1D4kugrOaPs6HijviWksoQ63PS81vZn0QCjZmVKjHDBniTo+Domg== + +svgo@1.3.2, svgo@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" css-select "^2.0.0" - css-select-base-adapter "~0.1.0" - css-tree "1.0.0-alpha.28" - css-url-regex "^1.1.0" - csso "^3.5.0" - js-yaml "^3.12.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" mkdirp "~0.5.1" - object.values "^1.0.4" + object.values "^1.1.0" sax "~1.2.4" - stable "~0.1.6" + stable "^0.1.8" unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.0.3: +symbol-observable@^1.0.4, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== +synchronous-promise@^2.0.6: + version "2.0.13" + resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702" + integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA== + +tabbable@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261" + integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ== + table@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" @@ -11936,22 +14517,42 @@ table@4.0.2: slice-ansi "1.0.0" string-width "^2.1.1" -tapable@^1.0.0, tapable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.0.tgz#0d076a172e3d9ba088fd2272b2668fb8d194b78c" - integrity sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA== +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar-fs@^1.13.0: - version "1.16.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" - integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== +tar-fs@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5" + integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg== dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" -tar-stream@^1.1.1, tar-stream@^1.1.2, tar-stream@^1.5.2: +tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== @@ -11964,32 +14565,33 @@ tar-stream@^1.1.1, tar-stream@^1.1.2, tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" -tar@^4, tar@^4.4.6: - version "4.4.6" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" - integrity sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg== +tar-stream@^2.0.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa" + integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw== dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= -tempfile@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-1.1.1.tgz#5bcc4eaecc4ab2c707d8bc11d99ccc9a2cb287f2" - integrity sha1-W8xOrsxKsscH2LwR2ZzMmiyyh/I= - dependencies: - os-tmpdir "^1.0.0" - uuid "^2.0.1" - tempfile@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" @@ -12005,112 +14607,99 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -terser-webpack-plugin@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.1.0.tgz#cf7c25a1eee25bf121f4a587bb9e004e3f80e528" - integrity sha512-61lV0DSxMAZ8AyZG7/A4a3UPlrbOBo8NIQ4tJzLPAdGOQ+yoNC7l5ijEow27lBAL2humer01KLS6bGIMYQxKoA== +term-size@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" + integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== + +terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^1.4.4: + version "1.4.5" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" + integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== dependencies: - cacache "^11.0.2" - find-cache-dir "^2.0.0" + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^1.4.0" + serialize-javascript "^4.0.0" source-map "^0.6.1" - terser "^3.8.1" - webpack-sources "^1.1.0" - worker-farm "^1.5.2" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" -terser@^3.8.1: - version "3.10.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-3.10.0.tgz#6ae15dafecbd02c9788d5f36d27fca32196b533a" - integrity sha512-hNh2WR3YxtKoY7BNSb3+CJ9Xv9bbUuOU9uriIf2F1tiAYNA4rNy2wWuSDV8iKcag27q65KPJ/sPpMWEh6qttgw== +terser@^4.1.2: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== dependencies: - commander "~2.17.1" + commander "^2.20.0" source-map "~0.6.1" - source-map-support "~0.5.6" + source-map-support "~0.5.12" -text-table@0.2.0, text-table@~0.2.0: +text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -through2-filter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" - integrity sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw= - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" - -through2@^0.6.0, through2@^0.6.1: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= +theme-ui@^0.2.49: + version "0.2.52" + resolved "https://registry.yarnpkg.com/theme-ui/-/theme-ui-0.2.52.tgz#c041a32b0b257fc8ecbae273e14e92abd48c0f2d" + integrity sha512-JFujorP5aFxIm1UyVCtefN5baXjwh5TXHKFYNWgAP+3rqVvggIr46uSMrRNvDjyhFOQiMK8YI8ctPQrrhcETpw== dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" + "@emotion/is-prop-valid" "^0.8.1" + "@styled-system/css" "^5.0.16" + deepmerge "^4.0.0" -through2@^2.0.0, through2@^2.0.1, through2@~2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= +through2@^2.0.0, through2@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== dependencies: - readable-stream "^2.1.5" + readable-stream "~2.3.6" xtend "~4.0.1" through@^2.3.6, through@^2.3.8: version "2.3.8" - resolved "http://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= thunky@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371" - integrity sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E= - -time-stamp@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -timed-out@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" - integrity sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc= - -timed-out@^4.0.0: +timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= timers-browserify@^2.0.4: - version "2.0.10" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" - integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== dependencies: setimmediate "^1.0.4" +timm@^1.6.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f" + integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw== + timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-emitter@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" - integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinycolor2@^1.1.2: +tinycolor2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g= -tmp@^0.0.31: - version "0.0.31" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" - integrity sha1-jzirlDjhcxXl29izZX6L+yd65Kc= - dependencies: - os-tmpdir "~1.0.1" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -12118,12 +14707,12 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -to-absolute-glob@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" - integrity sha1-HN+kcqnvUMI57maZm2YsoOs5k38= +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== dependencies: - extend-shallow "^2.0.1" + rimraf "^3.0.0" to-array@0.1.4: version "0.1.4" @@ -12140,11 +14729,6 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -12157,6 +14741,16 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-readable-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8" + integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -12165,6 +14759,13 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -12180,25 +14781,28 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -topo@2.x.x: +toposort@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" - integrity sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI= - dependencies: - hoek "4.x.x" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= -tough-cookie@>=2.3.3, tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== +tough-cookie@^2.3.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: - psl "^1.1.24" - punycode "^1.4.1" + psl "^1.1.28" + punycode "^2.1.1" + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== trim-lines@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.1.tgz#da738ff58fa74817588455e30b11b85289f2a396" - integrity sha512-X+eloHbgJGxczUk1WSjIvn7aC9oN3jVE3rQfRVKcgpavi3jxtCn0VVKtjOBj64Yop96UYn/ujJRpTbCdAF1vyg== + version "1.1.3" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.3.tgz#839514be82428fd9e7ec89e35081afe8f6f93115" + integrity sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA== trim-newlines@^1.0.0: version "1.0.0" @@ -12218,9 +14822,9 @@ trim-right@^1.0.1: integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= trim-trailing-lines@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz#e0ec0810fd3c3f1730516b45f49083caaf2774d9" - integrity sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg== + version "1.1.3" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz#7f0739881ff76657b7776e10874128004b625a94" + integrity sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA== trim@0.0.1: version "0.0.1" @@ -12228,25 +14832,52 @@ trim@0.0.1: integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= trough@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24" - integrity sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw== + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -tslib@^1.6.0, tslib@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +"true-case-path@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" + integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== + +ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.0.0, tslib@^1.10.0, tslib@^1.11.2, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + +tslib@^2.0.0, tslib@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" + integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= -tunnel-agent@^0.4.0: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - integrity sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us= - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -12266,70 +14897,75 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== +type-fest@0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.15.1.tgz#d2c4e73d3e4a53cf1a906396dd460a1c5178ca00" + integrity sha512-n+UXrN8i5ioo7kqT/nF8xsEzLaqFra7k32SEsSPwvXVGyAcRgV/FUQN/sgfptJTR1oRmmq7z4IXMFSM7im7C9A== + +type-fest@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.10.0.tgz#7f06b2b9fbfc581068d1341ffabd0349ceafc642" + integrity sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" + integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== + +type-fest@^0.8.0, type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== dependencies: media-typer "0.3.0" - mime-types "~2.1.18" + mime-types "~2.1.24" type-of@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/type-of/-/type-of-2.0.1.tgz#e72a1741896568e9f628378d816d6912f7f23972" integrity sha1-5yoXQYllaOn2KDeNgW1pEvfyOXI= +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= ua-parser-js@^0.7.18: - version "0.7.18" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" - integrity sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA== - -uglify-es@^3.3.4: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" - integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== - dependencies: - commander "~2.13.0" - source-map "~0.6.1" - -uglifyjs-webpack-plugin@^1.2.4: - version "1.3.0" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de" - integrity sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw== - dependencies: - cacache "^10.0.4" - find-cache-dir "^1.0.0" - schema-utils "^0.4.5" - serialize-javascript "^1.4.0" - source-map "^0.6.1" - uglify-es "^3.3.4" - webpack-sources "^1.1.0" - worker-farm "^1.5.2" - -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== + version "0.7.22" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3" + integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q== unbzip2-stream@^1.0.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1" - integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw== + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== dependencies: - buffer "^3.0.1" - through "^2.3.6" + buffer "^5.2.1" + through "^2.3.8" unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -underscore.string@^3.3.4: +underscore.string@^3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" integrity sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg== @@ -12337,28 +14973,18 @@ underscore.string@^3.3.4: sprintf-js "^1.0.3" util-deprecate "^1.0.2" -underscore.string@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b" - integrity sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs= - -underscore@~1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" - integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk= - unescape@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/unescape/-/unescape-0.2.0.tgz#b78b9b60c86f1629df181bf53eee3bc8d6367ddf" integrity sha1-t4ubYMhvFinfGBv1Pu47yNY2fd8= unherit@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c" - integrity sha512-+XZuV691Cn4zHsK0vkKYwBEwB74T3IZIcxrgn2E4rKwTfFyI1zCh7X7grwh9Re08fdPlarIdyWgI8aVB3F5A5g== + version "1.1.3" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" + integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== dependencies: - inherits "^2.0.1" - xtend "^4.0.1" + inherits "^2.0.0" + xtend "^4.0.0" unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" @@ -12373,19 +14999,32 @@ unicode-match-property-ecmascript@^1.0.4: unicode-canonical-property-names-ecmascript "^1.0.4" unicode-property-aliases-ecmascript "^1.0.4" -unicode-match-property-value-ecmascript@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4" - integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ== +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== unicode-property-aliases-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" - integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg== + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +unified@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.0.0.tgz#12b099f97ee8b36792dbad13d278ee2f696eed1d" + integrity sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" unified@^4.1.1: version "4.2.1" - resolved "http://registry.npmjs.org/unified/-/unified-4.2.1.tgz#76ff43aa8da430f6e7e4a55c84ebac2ad2cfcd2e" + resolved "https://registry.yarnpkg.com/unified/-/unified-4.2.1.tgz#76ff43aa8da430f6e7e4a55c84ebac2ad2cfcd2e" + integrity sha1-dv9Dqo2kMPbn5KVchOusKtLPzS4= dependencies: bail "^1.0.0" extend "^3.0.0" @@ -12394,7 +15033,7 @@ unified@^4.1.1: trough "^1.0.0" vfile "^1.0.0" -unified@^6.0.0, unified@^6.1.5: +unified@^6.0.0, unified@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA== @@ -12406,15 +15045,40 @@ unified@^6.0.0, unified@^6.1.5: vfile "^2.0.0" x-is-string "^0.1.0" +unified@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-7.1.0.tgz#5032f1c1ee3364bd09da12e27fdd4a7553c7be13" + integrity sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw== + dependencies: + "@types/unist" "^2.0.0" + "@types/vfile" "^3.0.0" + bail "^1.0.0" + extend "^3.0.0" + is-plain-obj "^1.1.0" + trough "^1.0.0" + vfile "^3.0.0" + x-is-string "^0.1.0" + +unified@^8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-8.4.2.tgz#13ad58b4a437faa2751a4a4c6a16f680c500fff1" + integrity sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + union-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" - integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== dependencies: arr-union "^3.1.0" get-value "^2.0.6" is-extendable "^0.1.1" - set-value "^0.4.3" + set-value "^2.0.1" uniq@^1.0.1: version "1.0.1" @@ -12426,7 +15090,7 @@ uniqs@^2.0.0: resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= -unique-filename@^1.1.0: +unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== @@ -12434,78 +15098,100 @@ unique-filename@^1.1.0: unique-slug "^2.0.0" unique-slug@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6" - integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== dependencies: imurmurhash "^0.1.4" -unique-stream@^2.0.2: - version "2.2.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" - integrity sha1-WqADz76Uxf+GbE59ZouxxNuts2k= +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== dependencies: - json-stable-stringify "^1.0.0" - through2-filter "^2.0.0" + crypto-random-string "^2.0.0" -unique-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" - integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= - dependencies: - crypto-random-string "^1.0.0" +unist-builder@2.0.3, unist-builder@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" + integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== unist-builder@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-1.0.3.tgz#ab0f9d0f10936b74f3e913521955b0478e0ff036" - integrity sha512-/KB8GEaoeHRyIqClL+Kam+Y5NWJ6yEiPsAfv1M+O1p+aKGgjR89WwoEHKTyOj17L6kAlqtKpAgv2nWvdbQDEig== + version "1.0.4" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-1.0.4.tgz#e1808aed30bd72adc3607f25afecebef4dd59e17" + integrity sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg== dependencies: object-assign "^4.1.0" unist-util-find@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unist-util-find/-/unist-util-find-1.0.1.tgz#1062bbb6928c7a97c6adc89b53745d4c46c222a2" + integrity sha1-EGK7tpKMepfGrcibU3RdTEbCIqI= dependencies: lodash.iteratee "^4.5.0" remark "^5.0.1" unist-util-visit "^1.1.0" -unist-util-generated@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.2.tgz#8b993f9239d8e560be6ee6e91c3f7b7208e5ce25" - integrity sha512-1HcwiEO62dr0XWGT+abVK4f0aAm8Ik8N08c5nAYVmuSxfvpA9rCcNyX/le8xXj1pJK5nBrGlZefeWB6bN8Pstw== +unist-util-generated@^1.0.0, unist-util-generated@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.5.tgz#1e903e68467931ebfaea386dae9ea253628acd42" + integrity sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw== -unist-util-is@^2.0.0, unist-util-is@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db" - integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw== +unist-util-is@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.3.tgz#459182db31f4742fceaea88d429693cbf0043d20" + integrity sha512-4WbQX2iwfr/+PfM4U3zd2VNXY+dWtZsN1fLnWEi2QQXA4qyDYAZcDMfXUX0Cu6XZUHHAO9q4nyxxLT4Awk1qUA== -unist-util-map@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unist-util-map/-/unist-util-map-1.0.4.tgz#f27bb03e14e8072171379d132c96c1dfcea44574" - integrity sha512-Qv68pQz05hQbjPI+TubZQI5XII5DScRVWaKNc6+qfmHaFGxaGUbkV8i++mM2nk7XgwXE+vei99d/Q2d1tMA3EQ== +unist-util-is@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" + integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== + +unist-util-is@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de" + integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ== + +unist-util-map@^1.0.3, 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" - integrity sha512-GRi04yhng1WqBf5RBzPkOtWAadcZS2gvuOgNn/cyJBYNxtTuyYqTKN0eg4rC1YJwGnzrqfRB3dSKm8cNCjNirg== + version "1.1.6" + resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-1.1.6.tgz#1587130ca0ab5c56155fa60837ff524c3fbfbfaa" + integrity sha512-TOA6W9QLil+BrHqIZNR4o6IA5QwGOveMbnQxnWYq+7EFORx9vz/CHrtzF36zWrW61E2UKw7sM1KPtIgeceVwXw== dependencies: array-iterate "^1.0.0" unist-util-position@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.0.1.tgz#8e220c24658239bf7ddafada5725ed0ea1ebbc26" - integrity sha512-05QfJDPI7PE1BIUtAxeSV+cDx21xP7+tUZgSval5CA7tr0pHBwybF7OnEa1dOFqg6BfYH/qiMUnWwWj+Frhlww== + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" + integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== -unist-util-remove-position@^1.0.0, unist-util-remove-position@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb" - integrity sha512-XxoNOBvq1WXRKXxgnSYbtCF76TJrRoe5++pD4cCBsssSiWSnPEktyFrFLE8LTk3JW5mt9hB0Sk5zn4x/JeWY7Q== +unist-util-remove-position@^1.0.0, unist-util-remove-position@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz#ec037348b6102c897703eee6d0294ca4755a2020" + integrity sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A== dependencies: unist-util-visit "^1.1.0" +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + +unist-util-remove@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.0.0.tgz#32c2ad5578802f2ca62ab808173d505b2c898488" + integrity sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g== + dependencies: + unist-util-is "^4.0.0" + unist-util-select@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/unist-util-select/-/unist-util-select-1.5.0.tgz#a93c2be8c0f653827803b81331adec2aa24cd933" @@ -12520,22 +15206,46 @@ unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ== +unist-util-stringify-position@^2.0.0, unist-util-stringify-position@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + unist-util-visit-children@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-1.1.2.tgz#bd78b53db9644b9c339ac502854f15471f964f5b" - integrity sha512-q4t6aprUcSQ2/+xlswuh2wUKwUUuMmDjSkfwkMjeVwCXc8NqX8g0FSmNf68CznCmbkrsOPDUR0wj14bCFXXqbA== + version "1.1.4" + resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-1.1.4.tgz#e8a087e58a33a2815f76ea1901c15dec2cb4b432" + integrity sha512-sA/nXwYRCQVRwZU2/tQWUqJ9JSFM1X3x7JIOsIgSzrFHcfVt6NkzDtKzyxg2cZWkCwGF9CO8x4QNZRJRMK8FeQ== unist-util-visit-parents@^2.0.0, unist-util-visit-parents@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217" - integrity sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA== + version "2.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" + integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g== dependencies: - unist-util-is "^2.1.2" + unist-util-is "^3.0.0" -unist-util-visit@^1.0.0, unist-util-visit@^1.1.0, unist-util-visit@^1.1.3, unist-util-visit@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1" - integrity sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw== +unist-util-visit-parents@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz#4dd262fb9dcfe44f297d53e882fc6ff3421173d5" + integrity sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +unist-util-visit@^1.0.0, unist-util-visit@^1.1.0, unist-util-visit@^1.1.3, 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" @@ -12544,6 +15254,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -12562,55 +15277,48 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -unzip-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" - integrity sha1-uYTwh3/AqJwsdzzB73tbIytbBv4= - -unzip-response@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= - -upath@^1.0.5: - version "1.1.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" - integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@^2.3.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" - integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== +update-notifier@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.1.tgz#895fc8562bbe666179500f9f2cebac4f26323746" + integrity sha512-9y+Kds0+LoLG6yN802wVXoIfxYEwh3FlZwzMwpCZp62S2i1/Jzeqb9Eeeju3NSHccGGasfGlK5/vEHbAifYRDg== dependencies: - boxen "^1.2.1" - chalk "^2.0.1" - configstore "^3.0.0" + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== dependencies: punycode "^2.1.0" -urijs@^1.19.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.1.tgz#5b0ff530c0cbde8386f6342235ba5ca6e995d25a" - integrity sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg== +urijs@^1.19.2: + version "1.19.10" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.10.tgz#8e2fe70a8192845c180f75074884278f1eea26cb" + integrity sha512-EzauQlgKuJgsXOqoMrCiePBf4At5jVqRhXykF3Wfb8ZsOBMxPcfiVBcsHXug4Aepb/ICm2PIgqAUGMelgdrWEg== urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-loader@^1.0.1: +url-loader@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.2.tgz#b971d191b83af693c5e3fea4064be9e1f2d7f8d8" integrity sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg== @@ -12626,20 +15334,20 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" -url-parse@^1.1.8, url-parse@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.3.tgz#bfaee455c889023219d757e045fa6a684ec36c15" - integrity sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw== +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= dependencies: - querystringify "^2.0.0" - requires-port "^1.0.0" + prepend-http "^2.0.0" -url-regex@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/url-regex/-/url-regex-3.2.0.tgz#dbad1e0c9e29e105dd0b1f09f6862f7fdb482724" - integrity sha1-260eDJ4p4QXdCx8J9oYvf9tIJyQ= +url-parse@^1.1.8, url-parse@^1.4.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: - ip-regex "^1.0.1" + querystringify "^2.1.1" + requires-port "^1.0.0" url-to-options@^1.0.1: version "1.0.1" @@ -12654,23 +15362,53 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +urql@^1.9.7: + version "1.10.1" + resolved "https://registry.yarnpkg.com/urql/-/urql-1.10.1.tgz#48353c0d02175481377fa95ff150b7449bd2f7c5" + integrity sha512-DMafjxLZfWUPSZRs39+wxmrHTqHm4LLfHvKQfSqkmkwneO/Ws5SLJsT/enZcQfLlH0ZWGvBOVHtVt3j0y8HbcQ== + dependencies: + "@urql/core" "^1.12.3" + wonka "^4.0.14" + +use-callback-ref@^1.2.1, use-callback-ref@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c" + integrity sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ== + +use-sidecar@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.3.tgz#17a4e567d4830c0c0ee100040e85a7fe68611e0f" + integrity sha512-ygJwGUBeQfWgDls7uTrlEDzJUUR67L8Rm14v/KfFtYCdHhtjHZx1Krb3DIQl3/Q5dJGfXLEQ02RY8BdNBv87SQ== + dependencies: + detect-node-es "^1.0.0" + tslib "^1.9.3" + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@^1.0.2, util-deprecate@~1.0.1: +utif@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759" + integrity sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg== + dependencies: + pako "^1.0.5" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@^1.0.0, util.promisify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== +util.promisify@^1.0.1, util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" util@0.10.3: version "0.10.3" @@ -12679,10 +15417,10 @@ util@0.10.3: dependencies: inherits "2.0.1" -util@^0.10.3: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== dependencies: inherits "2.0.3" @@ -12696,25 +15434,20 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^2.0.1: - version "2.0.3" - resolved "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" - integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= - -uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@3.4.0, uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@^1.1.0: +v8-compile-cache@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4" integrity sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA== -vali-date@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6" - integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY= +v8-compile-cache@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== valid-url@^1.0.9: version "1.0.9" @@ -12729,15 +15462,22 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -vary@~1.1.2: +validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= + dependencies: + builtins "^1.0.3" + +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= vendors@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" - integrity sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== verror@1.10.0: version "1.10.0" @@ -12749,20 +15489,34 @@ verror@1.10.0: extsprintf "^1.2.0" vfile-location@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.3.tgz#083ba80e50968e8d420be49dd1ea9a992131df77" - integrity sha512-zM5/l4lfw1CBoPx3Jimxoc5RNDAHHpk6AM6LM0pTIkm5SUSsx8ZekZ0PVdf0WEZ7kjlhSt7ZlqbRL6Cd6dBs6A== + version "2.0.6" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e" + integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== + +vfile-location@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.1.0.tgz#81cd8a04b0ac935185f4fce16f270503fc2f692f" + integrity sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g== + +vfile-message@*, vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" vfile-message@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.0.1.tgz#51a2ccd8a6b97a7980bb34efb9ebde9632e93677" - integrity sha512-vSGCkhNvJzO6VcWC6AlJW4NtYOVtS+RgCaqFIYUjoGIlHnFL+i0LbtYvonDWOMcB97uTPT4PRsyYY7REWC9vug== + version "1.1.1" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1" + integrity sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA== dependencies: unist-util-stringify-position "^1.1.1" vfile@^1.0.0: version "1.4.0" - resolved "http://registry.npmjs.org/vfile/-/vfile-1.4.0.tgz#c0fd6fa484f8debdb771f68c31ed75d88da97fe7" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-1.4.0.tgz#c0fd6fa484f8debdb771f68c31ed75d88da97fe7" + integrity sha1-wP1vpIT43r23cfaMMe112I2pf+c= vfile@^2.0.0: version "2.3.0" @@ -12774,114 +15528,78 @@ vfile@^2.0.0: unist-util-stringify-position "^1.0.0" vfile-message "^1.0.0" -vinyl-assign@^1.0.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/vinyl-assign/-/vinyl-assign-1.2.1.tgz#4d198891b5515911d771a8cd9c5480a46a074a45" - integrity sha1-TRmIkbVRWRHXcajNnFSApGoHSkU= - dependencies: - object-assign "^4.0.1" - readable-stream "^2.0.0" - -vinyl-fs@^2.2.0: - version "2.4.4" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" - integrity sha1-vm/zJwy1Xf19MGNkDegfJddTIjk= - dependencies: - duplexify "^3.2.0" - glob-stream "^5.3.2" - graceful-fs "^4.0.0" - gulp-sourcemaps "1.6.0" - is-valid-glob "^0.3.0" - lazystream "^1.0.0" - lodash.isequal "^4.0.0" - merge-stream "^1.0.0" - mkdirp "^0.5.0" - object-assign "^4.0.0" - readable-stream "^2.0.4" - strip-bom "^2.0.0" - strip-bom-stream "^1.0.0" - through2 "^2.0.0" - through2-filter "^2.0.0" - vali-date "^1.0.0" - vinyl "^1.0.0" - -vinyl@^0.4.3: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - integrity sha1-LzVsh6VQolVGHza76ypbqL94SEc= - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= +vfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803" + integrity sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ== dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" + is-buffer "^2.0.0" + replace-ext "1.0.0" + unist-util-stringify-position "^1.0.0" + vfile-message "^1.0.0" -vinyl@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" - integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= +vfile@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" + integrity sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw== dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + replace-ext "1.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" vlq@^0.2.1: version "0.2.3" resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== -vm-browserify@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" - integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM= - dependencies: - indexof "0.0.1" +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -ware@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ware/-/ware-1.3.0.tgz#d1b14f39d2e2cb4ab8c4098f756fe4b164e473d4" - integrity sha1-0bFPOdLiy0q4xAmPdW/ksWTkc9Q= +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== dependencies: - wrap-fn "^0.1.0" + loose-envify "^1.0.0" -warning@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" - integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= +watchpack-chokidar2@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" + integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== dependencies: - loose-envify "^1.0.0" + chokidar "^2.1.8" -watchpack@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== +watchpack@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" + integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== dependencies: - chokidar "^2.0.2" graceful-fs "^4.1.2" neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.0" -wbuf@^1.1.0, wbuf@^1.7.2: +wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== dependencies: minimalistic-assert "^1.0.0" -web-namespaces@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.2.tgz#c8dc267ab639505276bae19e129dbd6ae72b22b4" - integrity sha512-II+n2ms4mPxK+RnIxRPOw3zwF2jRscdJIUE9BfkKHm4FYEg9+biIoTMnaZF5MpemE3T+VhMLrhbyD4ilkPCSbg== +web-namespaces@^1.0.0, web-namespaces@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" + integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== -webpack-assets-manifest@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.0.tgz#5c540606e061a39e314042059c854a38bc696911" - integrity sha512-YHrK/iSWrUvLFEYfwozuIvL2kAa+SAXjBdS7fkx7BuOBBR/zas9e83+AIsTSwrCIEpF7Zpx/cOTqKypkObLP8g== +webpack-assets-manifest@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz#39bbc3bf2ee57fcd8ba07cda51c9ba4a3c6ae1de" + integrity sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ== dependencies: chalk "^2.0" lodash.get "^4.0" @@ -12891,54 +15609,60 @@ webpack-assets-manifest@^3.0.2: tapable "^1.0.0" webpack-sources "^1.0.0" -webpack-dev-middleware@3.4.0, webpack-dev-middleware@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890" - integrity sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA== +webpack-dev-middleware@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" + integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== dependencies: - memory-fs "~0.4.1" - mime "^2.3.1" - range-parser "^1.0.3" + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.1.1: - version "3.1.9" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.9.tgz#8b32167624d2faff40dcedc2cbce17ed1f34d3e0" - integrity sha512-fqPkuNalLuc/hRC2QMkVYJkgNmRvxZQo7ykA2e1XRg/tMJm3qY7ZaD6d89/Fqjxtj9bOrn5wZzLD2n84lJdvWg== +webpack-dev-server@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" + integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" - chokidar "^2.0.0" - compression "^1.5.2" - connect-history-api-fallback "^1.3.0" - debug "^3.1.0" - del "^3.0.0" - express "^4.16.2" - html-entities "^1.2.0" - http-proxy-middleware "~0.18.0" + chokidar "^2.1.8" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + debug "^4.1.1" + del "^4.1.1" + express "^4.17.1" + html-entities "^1.3.1" + http-proxy-middleware "0.19.1" import-local "^2.0.0" - internal-ip "^3.0.1" + internal-ip "^4.3.0" ip "^1.1.5" - killable "^1.0.0" - loglevel "^1.4.1" - opn "^5.1.0" - portfinder "^1.0.9" + is-absolute-url "^3.0.3" + killable "^1.0.1" + loglevel "^1.6.8" + opn "^5.5.0" + p-retry "^3.0.1" + portfinder "^1.0.26" schema-utils "^1.0.0" - selfsigned "^1.9.1" - serve-index "^1.7.2" - sockjs "0.3.19" - sockjs-client "1.1.5" - spdy "^3.4.1" - strip-ansi "^3.0.0" - supports-color "^5.1.0" - webpack-dev-middleware "3.4.0" + selfsigned "^1.10.7" + semver "^6.3.0" + serve-index "^1.9.1" + sockjs "0.3.20" + sockjs-client "1.4.0" + spdy "^4.0.2" + strip-ansi "^3.0.1" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.7.2" webpack-log "^2.0.0" - yargs "12.0.2" + ws "^6.2.1" + yargs "^13.3.2" -webpack-hot-middleware@^2.21.0: - version "2.24.3" - resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.24.3.tgz#5bb76259a8fc0d97463ab517640ba91d3382d4a6" - integrity sha512-pPlmcdoR2Fn6UhYjAhp1g/IJy1Yc9hD+T6O9mjRcWV2pFbBjIFoJXhP0CoD0xPOhWJuWXuZXGBga9ybbOdzXpg== +webpack-hot-middleware@^2.25.0: + version "2.25.0" + resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.0.tgz#4528a0a63ec37f8f8ef565cf9e534d57d09fe706" + integrity sha512-xs5dPOrGPCzuRXNi8F6rwhawWvQQkeli5Ro48PRuQh8pYPCPmNnltP9itiUPT4xI8oW+y0m59lyyeQk54s5VgA== dependencies: ansi-html "0.0.7" html-entities "^1.2.0" @@ -12953,94 +15677,98 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@^4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.4.tgz#0fde38eabf2d5fd85251c24a5a8c48f8a3f4eb7b" - integrity sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ== +webpack-merge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" + integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== dependencies: - lodash "^4.17.5" + lodash "^4.17.15" -webpack-sources@^1.0.0, webpack-sources@^1.1.0, webpack-sources@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" - integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== +webpack-sources@^1.0.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-stats-plugin@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9" - integrity sha1-KeXxLr/VMVjTHWVqETrB97hhedk= - -webpack@^4.12.0: - version "4.20.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.20.2.tgz#89f6486b6bb276a91b0823453d377501fc625b5a" - integrity sha512-75WFUMblcWYcocjSLlXCb71QuGyH7egdBZu50FtBGl2Nso8CK3Ej+J7bTZz2FPFq5l6fzCisD9modB7t30ikuA== - dependencies: - "@webassemblyjs/ast" "1.7.8" - "@webassemblyjs/helper-module-context" "1.7.8" - "@webassemblyjs/wasm-edit" "1.7.8" - "@webassemblyjs/wasm-parser" "1.7.8" - acorn "^5.6.2" - acorn-dynamic-import "^3.0.0" - ajv "^6.1.0" - ajv-keywords "^3.1.0" - chrome-trace-event "^1.0.0" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.0" +webpack-stats-plugin@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.3.2.tgz#c06b185aa5dcc93b3f0c3a7891d24a111f849740" + integrity sha512-kxEtPQ6lBBik2qtJlsZkiaDMI6rGXe9w1kLH9ZCdt0wgCGVnbwwPlP60cMqG6tILNFYqXDxNt4+c4OIIuE+Fnw== + +webpack-virtual-modules@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.2.2.tgz#20863dc3cb6bb2104729fff951fbe14b18bd0299" + integrity sha512-kDUmfm3BZrei0y+1NTHJInejzxfhtU8eDj2M7OKb2IWrPFAeO1SOH2KuQ68MSZu9IGEHcxbkKKR1v18FrUSOmA== + dependencies: + debug "^3.0.0" + +webpack@^4.44.1: + version "4.44.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" + integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.4.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.3.0" + eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - micromatch "^3.1.8" - mkdirp "~0.5.0" - neo-async "^2.5.0" - node-libs-browser "^2.0.0" - schema-utils "^0.4.4" - tapable "^1.1.0" - uglifyjs-webpack-plugin "^1.2.4" - watchpack "^1.5.0" - webpack-sources "^1.3.0" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.3" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.7.4" + webpack-sources "^1.4.1" + +websocket-driver@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" + integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= + dependencies: + websocket-extensions ">=0.1.1" websocket-driver@>=0.5.1: - version "0.7.0" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" - integrity sha1-DK+dLXVdk67gSdS90NP+LMoqJOs= + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== dependencies: - http-parser-js ">=0.4.0" + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" websocket-extensions ">=0.1.1" websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== whatwg-fetch@2.0.4: version "2.0.4" - resolved "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== whatwg-fetch@>=0.10.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== - -whet.extend@~0.9.9: - version "0.9.9" - resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" - integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE= + version "3.4.1" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3" + integrity sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ== which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= - which@^1.2.14, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -13048,6 +15776,13 @@ which@^1.2.14, which@^1.2.9: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -13056,52 +15791,84 @@ wide-align@^1.1.0: string-width "^1.0.2 || 2" widest-line@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.0.tgz#0142a4e8a243f8882c0233aa0e0281aa76152273" - integrity sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM= + version "2.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" + integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== dependencies: string-width "^2.1.1" -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" -worker-farm@^1.5.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" - integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ== +with-open-file@^0.1.6: + version "0.1.7" + resolved "https://registry.yarnpkg.com/with-open-file/-/with-open-file-0.1.7.tgz#e2de8d974e8a8ae6e58886be4fe8e7465b58a729" + integrity sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA== + dependencies: + p-finally "^1.0.0" + p-try "^2.1.0" + pify "^4.0.1" + +wonka@^4.0.14: + version "4.0.14" + resolved "https://registry.yarnpkg.com/wonka/-/wonka-4.0.14.tgz#77d680a84e575ed15a9f975eb87d6c530488f3a4" + integrity sha512-v9vmsTxpZjrA8CYfztbuoTQSHEsG3ZH+NCYfasHm0V3GqBupXrjuuz0RJyUaw2cRO7ouW2js0P6i853/qxlDcA== + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== dependencies: errno "~0.1.7" -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= +wrap-ansi@^5.0.0, wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" -wrap-fn@^0.1.0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/wrap-fn/-/wrap-fn-0.1.5.tgz#f21b6e41016ff4a7e31720dbc63a09016bdf9845" - integrity sha1-8htuQQFv9KfjFyDbxjoJAWvfmEU= +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: - co "3.1.0" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" - integrity sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== dependencies: - graceful-fs "^4.1.11" imurmurhash "^0.1.4" + is-typedarray "^1.0.0" signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" write@^0.2.1: version "0.2.1" @@ -13110,29 +15877,41 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" -ws@~3.3.1: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== +ws@^5.2.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d" + integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== dependencies: async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" -x-is-array@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/x-is-array/-/x-is-array-0.1.0.tgz#de520171d47b3f416f5587d629b89d26b12dc29d" - integrity sha1-3lIBcdR7P0FvVYfWKbidJrEtwp0= +ws@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +ws@^7.3.0, ws@~7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd" + integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA== + +ws@~6.1.0: + version "6.1.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" + integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== + dependencies: + async-limiter "~1.0.0" x-is-string@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== xhr@^2.0.1: version "2.5.0" @@ -13150,49 +15929,47 @@ xml-parse-from-string@^1.0.0: integrity sha1-qQKekp09vN7RafPG4oI42VpdWig= xml2js@^0.4.5: - version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" - integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== dependencies: sax ">=0.6.0" - xmlbuilder "~9.0.1" + xmlbuilder "~11.0.0" xml@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= -xmlbuilder@~9.0.1: - version "9.0.7" - resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== xmlhttprequest-ssl@~1.5.4: version "1.5.5" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= -xregexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" - integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg== - -xstate@^3.1.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/xstate/-/xstate-3.3.3.tgz#64177cd4473d4c2424b3df7d2434d835404b09a9" - integrity sha512-p0ZYDPWxZZZRAJyD3jaGO9/MYioHuxZp6sjcLhPfBZHAprl4EDrZRGDqRVH9VvK8oa6Nrbpf+U5eNmn8KFwO3g== +xss@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.8.tgz#32feb87feb74b3dcd3d404b7a68ababf10700535" + integrity sha512-3MgPdaXV8rfQ/pNn16Eio6VXYPTkqwa0vc7GkiymmY/DqR1SE/7VPAAVZz1GJsJFrllMYO3RHfEaiUGjab6TNw== + dependencies: + commander "^2.20.3" + cssfilter "0.0.10" -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= +xstate@^4.11.0, xstate@^4.9.1: + version "4.13.0" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.13.0.tgz#0be22ceb8bae2bc6a025fab330fe44204d76771c" + integrity sha512-UnUJJzP2KTPqnmxIoD/ymXtpy/hehZnUlO6EXqWC/72XkPb15p9Oz/X4WhS3QE+by7NP+6b5bCi/GTGFzm5D+A== -y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== @@ -13202,95 +15979,79 @@ yallist@^2.0.0, yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" - integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k= +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yaml-loader@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/yaml-loader/-/yaml-loader-0.5.0.tgz#86b1982d84a8e429e6647d93de9a0169e1c15827" - integrity sha512-p9QIzcFSNm4mCw/m5NdyMfN4RE4aFZJWRRb01ERVNGCym8VNbKtw3OYZXnvUIkim6U/EjqE/2yIh9F/msShH9A== - dependencies: - js-yaml "^3.5.2" +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" - integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== +yaml-loader@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/yaml-loader/-/yaml-loader-0.6.0.tgz#fe1c48b9f4803dace55a59a1474e790ba6ab1b48" + integrity sha512-1bNiLelumURyj+zvVHOv8Y3dpCri0F2S+DCcmps0pA1zWRLjS+FhZQg4o3aUUDYESh73+pKZNI18bj7stpReow== dependencies: - camelcase "^4.1.0" + loader-utils "^1.4.0" + yaml "^1.8.3" -yargs-parser@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" - integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k= +yaml@^1.7.2, yaml@^1.8.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" + integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: - camelcase "^4.1.0" + camelcase "^5.0.0" + decamelize "^1.2.0" -yargs-parser@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" - integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc= +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: - camelcase "^4.1.0" + camelcase "^5.0.0" + decamelize "^1.2.0" -yargs@12.0.2: - version "12.0.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc" - integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ== +yargs@^13.3.0, yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: - cliui "^4.0.0" - decamelize "^2.0.0" + cliui "^5.0.0" find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" + get-caller-file "^2.0.1" require-directory "^2.1.1" - require-main-filename "^1.0.1" + require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^2.0.0" + string-width "^3.0.0" which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^10.1.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" -yargs@^11.1.0: - version "11.1.0" - resolved "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" - integrity sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: - cliui "^4.0.0" - decamelize "^1.1.1" - find-up "^2.1.0" - get-caller-file "^1.0.1" - os-locale "^2.0.0" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" require-directory "^2.1.1" - require-main-filename "^1.0.1" + require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^9.0.2" - -yargs@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" - integrity sha1-UqzCP+7Kw0BCB47njAwAf1CF20w= - dependencies: - camelcase "^4.1.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - read-pkg-up "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" + string-width "^4.2.0" which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^7.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" -yauzl@^2.2.1, yauzl@^2.4.2: +yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= @@ -13303,41 +16064,51 @@ yeast@0.1.2: resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= -yurnalist@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/yurnalist/-/yurnalist-0.2.1.tgz#2d32b9618ab6491891c131bd90a5295e19fd4bad" - integrity sha1-LTK5YYq2SRiRwTG9kKUpXhn9S60= +yoga-layout-prebuilt@^1.9.3, yoga-layout-prebuilt@^1.9.6: + version "1.9.6" + resolved "https://registry.yarnpkg.com/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.9.6.tgz#98dde95bbf8e6e12835876e9305f1e995c4bb801" + integrity sha512-Wursw6uqLXLMjBAO4SEShuzj8+EJXhCF71/rJ7YndHTkRAYSU0GY3OghRqfAk9HPUAAFMuqp3U1Wl+01vmGRQQ== dependencies: - chalk "^1.1.1" - death "^1.0.0" - debug "^2.2.0" - detect-indent "^5.0.0" - inquirer "^3.0.1" - invariant "^2.2.0" - is-builtin-module "^1.0.0" - is-ci "^1.0.10" - leven "^2.0.0" - loud-rejection "^1.2.0" - node-emoji "^1.0.4" - object-path "^0.11.2" - read "^1.0.7" - rimraf "^2.5.0" - semver "^5.1.0" - strip-bom "^3.0.0" + "@types/yoga-layout" "1.9.2" -zen-observable-ts@^0.8.10: - version "0.8.10" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829" - integrity sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ== +yup@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7" + integrity sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ== dependencies: - zen-observable "^0.8.0" + "@babel/runtime" "^7.0.0" + fn-name "~2.0.1" + lodash "^4.17.11" + property-expr "^1.5.0" + synchronous-promise "^2.0.6" + toposort "^2.0.2" -zen-observable@^0.8.0: - version "0.8.9" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.9.tgz#0475c760ff0eda046bbdfa4dc3f95d392807ac53" - integrity sha512-Y9kPzjGvIZ5jchSlqlCpBW3I82zBBL4z+ulXDRVA1NwsKzjt5kwAi+gOYIy0htNkfuehGZZtP5mRXHRV6TjDWw== +yurnalist@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/yurnalist/-/yurnalist-1.1.2.tgz#0fce283f1c53ea25ec278e2d1ab58537323b63e0" + integrity sha512-y7bsTXqL+YMJQ2De2CBtSftJNLQnB7gWIzzKm10GDyC8Fg4Dsmd2LG5YhT8pudvUiuotic80WVXt/g1femRVQg== + dependencies: + babel-runtime "^6.26.0" + chalk "^2.4.2" + cli-table3 "^0.5.1" + debug "^4.1.1" + deep-equal "^1.1.0" + detect-indent "^6.0.0" + inquirer "^7.0.0" + invariant "^2.2.0" + is-builtin-module "^3.0.0" + is-ci "^2.0.0" + leven "^3.1.0" + loud-rejection "^2.2.0" + node-emoji "^1.10.0" + object-path "^0.11.2" + read "^1.0.7" + rimraf "^3.0.0" + semver "^6.3.0" + strip-ansi "^5.2.0" + strip-bom "^4.0.0" zwitch@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.3.tgz#159fae4b3f737db1e42bf321d3423e4c96688a18" - integrity sha512-aynRpmJDw7JIq6X4NDWJoiK1yVSiG57ArWSg4HLC1SFupX5/bo0Cf4jpX0ifwuzBfxpYBuNSyvMlWNNRuy3cVA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==