diff --git a/TRANSLATION.md b/TRANSLATION.md index 31731ee2e..29d6fc6f9 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -74,12 +74,23 @@ | Орудний | _пропом_ | _пропсами_ | | Місцевий | _пропі_ | _пропсах_ | +Бандл + +| Відмінок | Однина | Множина | +| -------- | ------ | ------- | +| Називний | _бандл_ | _бандли_ | +| Родовий | _бандла_ | _бандлів_ | +| Давальний | _бандлу_ | _бандлам_ | +| Знахідний | _бандл_ | _бандли_ | +| Орудний | _бандлом_ | _бандлами_ | +| Місцевий | _бандлі_ | _бандлах_ | + Бандлер | Відмінок | Однина | Множина | | -------- | ------ | ------- | | Називний | _бандлер_ | _бандлери_ | -| Родовий | _бандлеру_ | _бандлерів_ | +| Родовий | _бандлера_ | _бандлерів_ | | Давальний | _бандлеру_ | _бандлерам_ | | Знахідний | _бандлер_ | _бандлери_ | | Орудний | _бандлером_ | _бандлерами_ | @@ -108,7 +119,11 @@ | batch | група оновлень | | batching | групування | | browser | браузер | +| build | збірка | +| build tool | інструмент збирання | +| build tooling | інструменти збирання | | bug | помилка, дефект | +| bundle | бандл; запаковувати | | bundler | бандлер | | callback | функція зворотного виклику | | camelCase | *camelCase* | @@ -125,7 +140,7 @@ | development | розробка | | development mode | режим розробки | | developer tools | інструменти розробника | -| React developer tools | інструменти React розробника | +| React developer tools | інструменти розробника React | | DOM container | DOM-контейнер | | effect | ефект | | encapsulation | інкапсуляція | @@ -161,11 +176,13 @@ | online | онлайн | | online playground | онлайн пісочниця | | package manager | менеджер пакетів | +| package registry | реєстр пакетів | | paint | фарбування, перефарбування | +| performance | продуктивність (TODO: розглянути "швидкодію") | | prop | проп | | props | пропси | -| production | продакшн | -| production mode | продакшн-режим | +| production | публічне середовище; впровадження | +| production mode | (у режимі) публічного середовища; режим публічного впровадження | | reducer | редюсер | | reuse | повторне використання, перевикористання | | React | React | diff --git a/next-env.d.ts b/next-env.d.ts index 52e831b43..3cd7048ed 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src/components/MDX/CodeBlock/CodeBlock.tsx b/src/components/MDX/CodeBlock/CodeBlock.tsx index 1fd9a8a90..42165c57d 100644 --- a/src/components/MDX/CodeBlock/CodeBlock.tsx +++ b/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -336,6 +336,7 @@ function getInlineDecorators( line.step === 3, 'bg-green-40 border-green-40 text-green-60 dark:text-green-30': line.step === 4, + // TODO: Some codeblocks use up to 6 steps. } ), }) diff --git a/src/content/blog/2025/02/14/sunsetting-create-react-app.md b/src/content/blog/2025/02/14/sunsetting-create-react-app.md new file mode 100644 index 000000000..531d9c9f6 --- /dev/null +++ b/src/content/blog/2025/02/14/sunsetting-create-react-app.md @@ -0,0 +1,309 @@ +--- +title: "Sunsetting Create React App" +author: Matt Carroll and Ricky Hanlon +date: 2025/02/14 +description: Today, we’re deprecating Create React App for new apps, and encouraging existing apps to migrate to a framework. We’re also providing docs for when a framework isn’t a good fit for your project, or you prefer to start by building a framework. +--- + +February 14, 2025 by [Matt Carroll](https://twitter.com/mattcarrollcode) and [Ricky Hanlon](https://bsky.app/profile/ricky.fm) + +--- + + + +Today, we’re deprecating [Create React App](https://create-react-app.dev/) for new apps, and encouraging existing apps to migrate to a [framework](/learn/creating-a-react-app). We’re also providing docs for when a framework isn’t a good fit for your project, or you prefer to start by [building a framework](/learn/building-a-react-framework). + + + +----- + +When we released Create React App in 2016, there was no clear way to build a new React app. + +To create a React app, you had to install a bunch of tools and wire them up together yourself to support basic features like JSX, linting, and hot reloading. This was very tricky to do correctly, so the [community](https://github.com/react-boilerplate/react-boilerplate) [created](https://github.com/kriasoft/react-starter-kit) [boilerplates](https://github.com/petehunt/react-boilerplate) for [common](https://github.com/gaearon/react-hot-boilerplate) [setups](https://github.com/erikras/react-redux-universal-hot-example). However, boilerplates were difficult to update and fragmentation made it difficult for React to release new features. + +Create React App solved these problems by combining several tools into a single recommended configuration. This allowed apps a simple way to upgrade to new tooling features, and allowed the React team to deploy non-trivial tooling changes (Fast Refresh support, React Hooks lint rules) to the broadest possible audience. + +This model became so popular that there's an entire category of tools working this way today. + +## Deprecating Create React App {/*deprecating-create-react-app*/} + +Although Create React App makes it easy to get started, [there are several limitations](#limitations-of-create-react-app) that make it difficult to build high performant production apps. In principle, we could solve these problems by essentially evolving it into a [framework](#why-we-recommend-frameworks). + +However, since Create React App currently has no active maintainers, and there are many existing frameworks that solve these problems already, we’ve decided to deprecate Create React App. + +Starting today, if you install a new app, you will see a deprecation warning: + + + + +create-react-app is deprecated. +{'\n\n'} +You can find a list of up-to-date React frameworks on react.dev +For more info see: react.dev/link/cra +{'\n\n'} +This error message will only be shown once per install. + + + + +We recommend [creating new React apps](/learn/creating-a-react-app) with a framework. All the frameworks we recommend support client-only SPAs, and can be deployed to a CDN or static hosting service without a server. + +For existing apps, these guides will help you migrate to a client-only SPA: + +* [Next.js’ Create React App migration guide](https://nextjs.org/docs/app/building-your-application/upgrading/from-create-react-app) +* [React Router’s framework adoption guide](https://reactrouter.com/upgrading/component-routes). +* [Expo Webpack to Expo Router migration guide](https://docs.expo.dev/router/migrate/from-expo-webpack/) + +Create React App will continue working in maintenance mode, and we've published a new version of Create React App to work with React 19. + +If your app has unusual constraints, or you prefer to solve these problems by building your own framework, or you just want to learn how react works from scratch, you can roll your own custom setup with React using Vite, Parcel or Rsbuild. + +To help users get started with Vite, Parcel or Rsbuild, we've published new docs for [Building a Framework](/learn/building-a-react-framework). Continue reading to learn more about the [limitations of Create React App](#limitations-of-create-react-app) and [why we recommend frameworks](#why-we-recommend-frameworks). + + + +#### Do you recommend Vite? {/*do-you-recommend-vite*/} + +We provide several Vite-based recommendations. + +React Router v7 is a Vite based framework which allows you to use Vite's fast development server and build tooling with a framework that provides routing and data fetching. Just like the other frameworks we recommend, you can build a SPA with React Router v7. + +We also recommend using Vite when [adding React to an existing project](/learn/add-react-to-an-existing-project), or [building a framework](/learn/building-a-react-framework). + +Just like Svelte has Sveltekit, Vue has Nuxt, and Solid has SolidStart, React recommends using a framework that integrates with build tools like Vite for new projects. + + + +## Limitations of Create React App {/*limitations-of-create-react-app*/} + +Create React App and build tools like it make it easy to get started building a React app. After running `npx create-react-app my-app`, you get a fully configured React app with a development server, linting, and a production build. + +For example, if you're building an internal admin tool, you can start with a landing page: + +```js +export default function App() { + return ( +
+

Welcome to the Admin Tool!

+
+ ) +} +``` + +This allows you to immediately start coding in React with features like JSX, default linting rules, and a bundler to run in both development and production. However, this setup is missing the tools you need to build a real production app. + +Most production apps need solutions to problems like routing, data fetching, and code splitting. + +### Routing {/*routing*/} + +Create React App does not include a specific routing solution. If you're just getting started, one option is to use `useState` to switch between routes. But doing this means that you can't share links to your app - every link would go to the same page - and structuring your app becomes difficult over time: + +```js +import {useState} from 'react'; + +import Home from './Home'; +import Dashboard from './Dashboard'; + +export default function App() { + // ❌ Routing in state does not create URLs + const [route, setRoute] = useState('home'); + return ( +
+ {route === 'home' && } + {route === 'dashboard' && } +
+ ) +} +``` + +This is why most apps that use Create React App solve add routing with a routing library like [React Router](https://reactrouter.com/) or [Tanstack Router](https://tanstack.com/router/latest). With a routing library, you can add additional routes to the app, which provides opinions on the structure of your app, and allows you to start sharing links to routes. For example, with React Router you can define routes: + +```js +import {RouterProvider, createBrowserRouter} from 'react-router'; + +import Home from './Home'; +import Dashboard from './Dashboard'; + +// ✅ Each route has it's own URL +const router = createBrowserRouter([ + {path: '/', element: }, + {path: '/dashboard', element: } +]); + +export default function App() { + return ( + + ) +} +``` + +With this change, you can share a link to `/dashboard` and the app will navigate to the dashboard page . Once you have a routing library, you can add additional features like nested routes, route guards, and route transitions, which are difficult to implement without a routing library. + +There's a tradeoff being made here: the routing library adds complexity to the app, but it also adds features that are difficult to implement without it. + +### Data Fetching {/*data-fetching*/} + +Another common problem in Create React App is data fetching. Create React App does not include a specific data fetching solution. If you're just getting started, a common option is to use `fetch` in an effect to load data. + +But doing this means that the data is fetched after the component renders, which can cause network waterfalls. Network waterfalls are caused by fetching data when your app renders instead of in parallel while the code is downloading: + +```js +export default function Dashboard() { + const [data, setData] = useState(null); + + // ❌ Fetching data in a component causes network waterfalls + useEffect(() => { + fetch('/api/data') + .then(response => response.json()) + .then(data => setData(data)); + }, []); + + return ( +
+ {data.map(item =>
{item.name}
)} +
+ ) +} +``` + +Fetching in an effect means the user has to wait longer to see the content, even though the data could have been fetched earlier. To solve this, you can use a data fetching library like [React Query](https://react-query.tanstack.com/), [SWR](https://swr.vercel.app/), [Apollo](https://www.apollographql.com/docs/react), or [Relay](https://relay.dev/) which provide options to prefetch data so the request is started before the component renders. + +These libraries work best when integrated with your routing "loader" pattern to specify data dependencies at the route level, which allows the router to optimize your data fetches: + +```js +export async function loader() { + const response = await fetch(`/api/data`); + const data = await response.json(); + return data; +} + +// ✅ Fetching data in parallel while the code is downloading +export default function Dashboard({loaderData}) { + return ( +
+ {loaderData.map(item =>
{item.name}
)} +
+ ) +} +``` + +On initial load, the router can fetch the data immediately before the route is rendered. As the user navigates around the app, the router is able to fetch both the data and the route at the same time, parallelizing the fetches. This reduces the time it takes to see the content on the screen, and can improve the user experience. + +However, this requires correctly configuring the loaders in your app and trades off complexity for performance. + +### Code Splitting {/*code-splitting*/} + +Another common problem in Create React App is [code splitting](https://www.patterns.dev/vanilla/bundle-splitting). Create React App does not include a specific code splitting solution. If you're just getting started, you might not consider code splitting at all. + +This means your app is shipped as a single bundle: + +```txt +- bundle.js 75kb +``` + +But for ideal performance, you should "split" your code into separate bundles so the user only needs to download what they need. This decreases the time the user needs to wait to load your app, by only downloading the code they need to see the page they are on. + +```txt +- core.js 25kb +- home.js 25kb +- dashboard.js 25kb +``` + +One way to do code-splitting is with `React.lazy`. However, this means that the code is not fetched until the component renders, which can cause network waterfalls. A more optimal solution is to use a router feature that fetches the code in parallel while the code is downloading. For example, React Router provides a `lazy` option to specify that a route should be code split and optimize when it is loaded: + +```js +import Home from './Home'; +import Dashboard from './Dashboard'; + +// ✅ Routes are downloaded before rendering +const router = createBrowserRouter([ + {path: '/', lazy: () => import('./Home')}, + {path: '/dashboard', lazy: () => import('Dashboard')} +]); +``` + +Optimized code-splitting is tricky to get right, and it's easy to make mistakes that can cause the user to download more code than they need. It works best when integrated with your router and data loading solutions to maximize caching, parallelize fetches, and support ["import on interaction"](https://www.patterns.dev/vanilla/import-on-interaction) patterns. + +### And more... {/*and-more*/} + +These are just a few examples of the limitations of Create React App. + +Once you've integrated routing, data-fetching, and code splitting, you now also need to consider pending states, navigation interruptions, error messages to the user, and revalidation of the data. There are entire categories of problems that users need to solve like: + +
+
    +
  • Accessibility
  • +
  • Asset loading
  • +
  • Authentication
  • +
  • Caching
  • +
+
    +
  • Error handling
  • +
  • Mutating data
  • +
  • Navigations
  • +
  • Optimistic updates
  • +
+
    +
  • Progressive enhancement
  • +
  • Server-side rendering
  • +
  • Static site generation
  • +
  • Streaming
  • +
+
+ +All of these work together to create the most optimal [loading sequence](https://www.patterns.dev/vanilla/loading-sequence). + +Solving each of these problems individually in Create React App can be difficult as each problem is interconnected with the others and can require deep expertise in problem areas users may not be familiar with. In order to solve these problems, users end up building their own bespoke solutions on top of Create React App, which was the problem Create React App originally tried to solve. + +## Why we Recommend Frameworks {/*why-we-recommend-frameworks*/} + +Although you could solve all these pieces yourself in a build tool like Create React App, Vite, or Parcel, it is hard to do well. Just like when Create React App itself integrated several build tools together, you need a tool to integrate all of these features together to provide the best experience to users. + +This category of tools that integrates build tools, rendering, routing, data fetching, and code splitting are known as "frameworks" -- or if you prefer to call React itself a framework, you might call them "metaframeworks". + +Frameworks impose some opinions about structuring your app in order to provide a much better user experience, in the same way build tools impose some opinions to make tooling easier. This is why we started recommending frameworks like [Next.js](https://nextjs.org/), [React Router](https://reactrouter.com/), and [Expo](https://expo.dev/) for new projects. + +Frameworks provide the same getting started experience as Create React App, but also provide solutions to problems users need to solve anyway in real production apps. + + + +#### Server rendering is optional {/*server-rendering-is-optional*/} + +The frameworks we recommend all provide the option to create a [client-side rendered (CSR)](https://developer.mozilla.org/en-US/docs/Glossary/CSR) app. + +In some cases, CSR is the right choice for a page, but many times it's not. Even if most of your app is client-side, there are often individual pages that could benefit from server rendering features like [static-site generation (SSG)](https://developer.mozilla.org/en-US/docs/Glossary/SSG) or [server-side rendering (SSR)](https://developer.mozilla.org/en-US/docs/Glossary/SSR), for example a Terms of Service page, or documentation. + +Server rendering generally sends less JavaScript to the client, and a full HTML document which produces a faster [First Contentful Paint (FCP)](https://web.dev/articles/fcp) by reducing [Total Blocking Time (TBD)](https://web.dev/articles/tbt), which can also lower [Interaction to Next Paint (INP)](https://web.dev/articles/inp). This is why the [Chrome team has encouraged](https://web.dev/articles/rendering-on-the-web) developers to consider static or server-side render over a full client-side approach to achieve the best possible performance. + +There are tradeoffs to using a server, and it is not always the best option for every page. Generating pages on the server incurs additional cost and takes time to generate which can increase [Time to First Byte (TTFB)](https://web.dev/articles/ttfb). The best performing apps are able to pick the right rendering strategy on a per-page basis, based on the tradeoffs of each strategy. + +Frameworks provide the option to use a server on any page if you want to, but do not force you to use a server. This allows you to pick the right rendering strategy for each page in your app. + +#### What About Server Components {/*server-components*/} + +The frameworks we recommend also include support for React Server Components. + +Server Components help solve these problems by moving routing and data fetching to the server, and allowing code splitting to be done for client components based on the data you render, instead of just the route rendered, and reducing the amount of JavaScript shipped for the best possible [loading sequence](https://www.patterns.dev/vanilla/loading-sequence). + +Server Components do not require a server. They can be run at build time on your CI server to create a static-site generated app (SSG) app, at runtime on a web server for a server-side rendered (SSR) app. + +See [Introducing zero-bundle size React Server Components](/blog/2020/12/21/data-fetching-with-react-server-components) and [the docs](/reference/rsc/server-components) for more info. + + + + + +#### Server Rendering is not just for SEO {/*server-rendering-is-not-just-for-seo*/} + +A common misunderstanding is that server rendering is only for [SEO](https://developer.mozilla.org/en-US/docs/Glossary/SEO). + +While server rendering can improve SEO, it also improves performance by reducing the amount of JavaScript the user needs to download and parse before they can see the content on the screen. + +This is why the Chrome team [has encouraged](https://web.dev/articles/rendering-on-the-web) developers to consider static or server-side render over a full client-side approach to achieve the best possible performance. + + + +--- + +_Thank you to [Dan Abramov](https://bsky.app/profile/danabra.mov) for creating Create React App, and [Joe Haddad](https://github.com/Timer), [Ian Schmitz](https://github.com/ianschmitz), [Brody McKee](https://github.com/mrmckeb), and [many others](https://github.com/facebook/create-react-app/graphs/contributors) for maintaining Create React App over the years. Thank you to [Brooks Lybrand](https://bsky.app/profile/brookslybrand.bsky.social), [Dan Abramov](https://bsky.app/profile/danabra.mov), [Devon Govett](https://bsky.app/profile/devongovett.bsky.social), [Eli White](https://x.com/Eli_White), [Jack Herrington](https://bsky.app/profile/jherr.dev), [Joe Savona](https://x.com/en_JS), [Lauren Tan](https://bsky.app/profile/no.lol), [Lee Robinson](https://x.com/leeerob), [Mark Erikson](https://bsky.app/profile/acemarke.dev), [Ryan Florence](https://x.com/ryanflorence), [Sophie Alpert](https://bsky.app/profile/sophiebits.com), [Tanner Linsley](https://bsky.app/profile/tannerlinsley.com), and [Theo Browne](https://x.com/theo) for reviewing and providing feedback on this post._ + diff --git a/src/content/blog/index.md b/src/content/blog/index.md index cc50b83c0..5cd4f35bd 100644 --- a/src/content/blog/index.md +++ b/src/content/blog/index.md @@ -4,12 +4,20 @@ title: React Blog -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. +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 [@react.dev](https://bsky.app/profiles/react.js) account on Bluesky, or [@reactjs](https://twitter.com/reactjs) account on Twitter, but you won’t miss anything essential if you only read this blog.
+ + +Today, we’re deprecating Create React App for new apps, and encouraging existing apps to migrate to a framework. We’re also providing docs for when a framework isn’t a good fit for your project, or you prefer to start by building a framework. + + + In the React 19 Upgrade Guide, we shared step-by-step instructions for upgrading your app to React 19. In this post, we'll give an overview of the new features in React 19, and how you can adopt them ... diff --git a/src/content/community/conferences.md b/src/content/community/conferences.md index dd9dd2510..46636d4c9 100644 --- a/src/content/community/conferences.md +++ b/src/content/community/conferences.md @@ -40,6 +40,11 @@ June 13 - 17, 2025. In-person in Amsterdam, Netherlands + remote (hybrid event) [Website](https://reactsummit.com/) - [Twitter](https://x.com/reactsummit) +### React Nexus 2025 {/*react-nexus-2025*/} +July 03 - 05, 2025. In-person in Bangalore, India + +[Website](https://reactnexus.com/) - [Twitter](https://x.com/ReactNexus) - [Bluesky](https://bsky.app/profile/reactnexus.com) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) + ### React Universe Conf 2025 {/*react-universe-conf-2025*/} September 2-4, 2025. Wrocław, Poland. diff --git a/src/content/community/meetups.md b/src/content/community/meetups.md index 14097aa4d..906c170de 100644 --- a/src/content/community/meetups.md +++ b/src/content/community/meetups.md @@ -57,6 +57,9 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [React Advanced London](https://guild.host/react-advanced-london) * [React Native London](https://guild.host/RNLDN) +## Finland {/*finland*/} +* [Helsinki](https://www.meetabit.com/communities/react-helsinki) + ## France {/*france*/} * [Lille](https://www.meetup.com/ReactBeerLille/) * [Paris](https://www.meetup.com/ReactJS-Paris/) @@ -136,6 +139,9 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet ## Spain {/*spain*/} * [Barcelona](https://www.meetup.com/ReactJS-Barcelona/) +## Sri Lanka {/*sri-lanka*/} +* [Colombo](https://www.javascriptcolombo.com/) + ## Sweden {/*sweden*/} * [Goteborg](https://www.meetup.com/ReactJS-Goteborg/) * [Stockholm](https://www.meetup.com/Stockholm-ReactJS-Meetup/) diff --git a/src/content/learn/building-a-react-framework.md b/src/content/learn/building-a-react-framework.md new file mode 100644 index 000000000..8796aca94 --- /dev/null +++ b/src/content/learn/building-a-react-framework.md @@ -0,0 +1,137 @@ +--- +title: Побудова React-фреймворку +--- + + + +Якщо ваш застосунок містить обмеження, які не спрацюють із наявними фреймворками, або ви хочете розв'язувати усі проблеми самотужки, то можете збудувати власний фреймворк. + + + + + +### Зважте використання наявного фреймворку {/*you-should-probably-use-a-framework*/} + +Побудова фреймворку є комплексною і вимагає неабиякого досвіду в різних сферах. Ця складність не тільки в React — це поширена проблема, з якою стикаються всі UI-бібліотеки. Використання наявного фреймворку може значно заощадити час і зусилля, даючи змогу зосередитися саме на побудові застосунку. Наявні фреймворки мають перевірені й надійні функції та підтримку спільноти. + +Щоб отримати список рекомендованих фреймворків, перегляньте розділ ["Створення React-застосунку"](/learn/creating-a-react-app). + + + +Побудова фреймворку — це чимале зобов'язання, яке часто потребує досвіду в багатьох різних сферах. Розуміння ваших цілей і вимог перед початком створення власного фреймворку може допомогти вам скерувати процес розробки та заощадити велику кількість часу. + +Наприклад, якщо вам треба побудувати фреймворк, який інтегрується з певною системою чи інфраструктурою, важливо розуміти функції та межі цих систем. Усвідомлення їхніх обмежень допоможе керувати процесом розробки фреймворку. + +Якщо ви створюєте власний фреймворк для навчання, то використання популярних інструментів, як-от Vite і React Router, стане гарним початком і дасть вам змогу зосередитися на тому, як комбінувати різні інструменти для побудови фреймворку. + +## Крок 1: Установіть інструмент збирання {/*step-1-install-a-build-tool*/} + +Першим кроком є ​​встановлення інструменту збирання, наприклад, `vite`, `parcel` або `rsbuild`. Вони надають функції для пакування та виконання вихідного коду, сервер для локальної розробки та команду збирання, щоб розгорнути застосунок на робочому сервері. + +### Vite {/*vite*/} + +[Vite](https://vite.dev/) — це інструмент збирання, який має на меті забезпечити швидшу та ефективнішу розробку сучасних вебпроєктів. + + +npm create vite@latest my-app -- --template react + + +Vite є досить розсудливим і постачається відразу з виваженими початковими налаштуваннями. Він має багату екосистему плагінів для підтримування швидкого оновлення, JSX, Babel/SWC та інших поширених функцій. Найперше перегляньте [плагін React](https://vite.dev/plugins/#vitejs-plugin-react) або [плагін React SWC](https://vite.dev/plugins/#vitejs-plugin-react-swc) і [приклад проєкту з React SSR](https://vite.dev/guide/ssr.html#example-projects). + +Vite вже використовується як інструмент збирання в одному з наших [рекомендованих фреймворків](/learn/creating-a-react-app) — [React Router](https://reactrouter.com/start/framework/installation). + +### Parcel {/*parcel*/} + +[Parcel](https://parceljs.org/) поєднує в собі чудовий досвід розробки без додаткових налаштувань із масштабованою архітектурою, що може привести ваш проєкт на самому початку до великого та готового до впровадження застосунку. + + +npm install --save-dev parcel + + +Parcel відразу підтримує швидке оновлення, JSX, TypeScript, Flow і стилізацію. Найперше перегляньте [рецепт React від Parcel](https://parceljs.org/recipes/react/#getting-started). + +### Rsbuild {/*rsbuild*/} + +[Rsbuild](https://rsbuild.dev/) — це інструмент збирання на основі Rspack, який забезпечує безшовну розробку React-застосунків. Він постачається з ретельно налаштованими початковими параметрами й оптимізаціями продуктивності, готовими до використання. + + +npx create-rsbuild --template react + + +Rsbuild відразу підтримує такі функції React, як швидке оновлення, JSX, TypeScript і стилізація. Найперше перегляньте [посібник React від Rsbuild](https://rsbuild.dev/guide/framework/react). + + + +#### Metro для React Native {/*react-native*/} + +Якщо ви хочете, щоб ваш фреймворк підтримував React Native, вам потрібно буде використовувати [Metro](https://metrobundler.dev/) — бандлер JavaScript для React Native. Metro створює бандли для iOS та Android, але йому бракує багатьох функцій, як порівняти з Vite або Parcel. Ми рекомендуємо почати з Vite або Parcel, якщо ваш проєкт не вимагає підтримування React Native. + + + +## Крок 2: Збудуйте власний фреймворк {/*step-2-build-your-framework*/} + +Інструмент збирання, який ви вибрали, починається з клієнтського односторінкового застосунку (SPA). Хоч SPA можуть бути гарними для початку, багато з них зіткнуться з проблемами, коли розростуться. Фреймворки можуть надати риштування для вирішення цих проблем. Більшість із них реалізують маршрутизацію (routing), розділення коду (code-splitting), різні стратегії рендерингу та отримання даних (data-fetching). Ці функції взаємопов'язані. Наприклад, якщо ви використовуєте маршрутизатор, який працює лише на клієнті, це може перешкоджати реалізації рендерингу з боку сервера. Найкращі фреймворки забезпечують узгоджену, послідовну роботу з цими функціями для розробників і користувачів. + +### Маршрутизація {/*routing*/} + +Маршрутизація визначає, що відображати, коли користувач відвідує певну URL-адресу. Вам треба налаштувати маршрутизатор, щоб зіставляти URL-адреси з різними частинами застосунку. Вам також потрібно буде обробляти вкладені маршрути, параметри маршруту та параметри запиту (query parameters). Більшість сучасних маршрутизаторів використовує маршрутизацію на основі файлів. Маршрутизація може бути інтегрована з іншими функціями, як-от: + +* **Стратегії рендерингу**, щоб увімкнути різні стратегії рендерингу на різних маршрутах, щоб ви могли додавати нові стратегії без переписування всього застосунку. Це може зменшити час до завантаження першого байта вмісту ([час до першого байта, TTFB](https://web.dev/articles/ttfb)), рендерингу першого фрагмента вмісту ([малювання першого вмісту, FCP](https://web.dev/articles/fcp)) і рендерингу найбільшого видимого вмісту застосунку ([малювання найбільшого вмісту, LCP](https://web.dev/articles/lcp)). +* **Отримання даних**, щоб увімкнути отримання даних перед завантаженням сторінки за певним маршрутом. Це може запобігти зсувам макета ([кумулятивний зсув макета, CLS](https://web.dev/articles/cls)) та зменшити час до рендерингу найбільшого видимого вмісту застосунку ([малювання найбільшого вмісту, LCP](https://web.dev/articles/lcp)) +* **Розділення коду**, щоб зменшити розмір JavaScript-бандла, який надсилається клієнту, і підвищити продуктивність на малопотужних пристроях. Це може зменшити час, потрібний браузеру для відповіді на взаємодію користувача ([затримка після першого введення, FID](https://web.dev/articles/fid)) і рендерингу найбільшого видимого вмісту застосунку ([малювання найбільшого вмісту, LCP](https://web.dev/articles/lcp)). + +Якщо ви не впевнені, як працювати з маршрутизацією, рекомендуємо використати [React Router](https://reactrouter.com/start/framework/custom) або [Tanstack Router](https://tanstack.com/router/latest). + +### Отримання даних {/*data-fetching*/} + +Отримання даних — це процес отримання даних із сервера або іншого джерела даних. Вам треба налаштувати або створити відповідну бібліотеку, щоб обробляти отримання даних із вашого сервера та управляти станом цих даних. Вам також потрібно опрацьовувати стани завантаження, стани помилок і кешування даних. Отримання даних може бути інтегроване з іншими функціями, як-от: + +* **Маршрутизація**, щоб увімкнути отримання даних перед завантаженням сторінки. Це може покращити швидкість завантаження сторінки та її видимість для користувачів ([малювання найбільшого вмісту, LCP](https://web.dev/lcp)) і зменшити час, потрібний вашому застосунку, щоб стати інтерактивним ([час до інтерактивності, TTI](https://web.dev/tti)). +* **Стратегії рендерингу**, щоб попередньо рендерити запитувані дані перед тим, як надіслати їх клієнту. Це може зменшити час до рендерингу найбільшого видимого вмісту застосунку ([малювання найбільшого вмісту, LCP](https://web.dev/lcp)). + +Інтеграція маршрутизації та отримання даних насамперед важлива для запобігання мережевого "водоспаду". Якщо ви запитуєте дані під час початкового рендеру компонента, то перше отримання даних у SPA відкладається, доки не буде завантажено весь код і компоненти не завершать рендеринг. Це зазвичай називають водоспадом: замість того, щоб отримувати дані під час завантаження коду, вам потрібно спочатку зачекати, поки він завантажиться. І щоб усунути ці каскади, ваш застосунок повинен отримувати дані для кожного маршруту паралельно з надсиланням коду в браузер. + +Популярні бібліотеки отримання даних, які ви можете використовувати як частину свого фреймворку: [React Query](https://react-query.tanstack.com/), [SWR](https://swr.vercel.app/), [Apollo](https://www.apollographql.com/docs/react) і [Relay](https://relay.dev/). + +### Стратегії рендерингу {/*rendering-strategies*/} + +Оскільки вибраний вами інструмент збирання підтримує лише односторінкові застосунки (SPA), вам треба самостійно реалізувати інші [патерни рендерингу](https://www.patterns.dev/vanilla/rendering-patterns), як-от рендеринг із боку сервера (SSR), генерація статичного сайту (SSG) та/або серверні компоненти React (RSC). Хоч спочатку вони рідко потрібні, згодом SSR, SSG або RSC можуть оптимізувати деякі маршрути вашого застосунку. + +* **Односторінкові застосунки (SPA)** завантажують одну HTML-сторінку та динамічно оновлюють її, коли користувач взаємодіє із застосунком. SPA є швидкими та чуйними, але часто повільніші під час початкового завантаження. SPA є стандартною архітектурою для більшості інструментів збирання. + +* **Потоковий рендеринг з боку сервера (SSR)** рендерить сторінку на сервері та надсилає повністю відрендерену сторінку клієнту. SSR може покращити продуктивність, але його налаштування та обслуговування радше складніші, ніж для односторінкового застосунку. З додаванням потоковості SSR це може бути ще складнішим. Перегляньте [посібник Vite із SSR]( https://vite.dev/guide/ssr). + +* **Генерація статичного сайту (SSG)** генерує статичні HTML-файли для вашого застосунку під час збирання. SSG може покращити продуктивність, але його налаштування та обслуговування можуть бути складнішими, ніж для рендерингу з боку сервера. + +* **Серверні компоненти React (RSC)** дають змогу змішувати компільовані, суто серверні та інтерактивні компоненти в одному React-дереві. RSC може покращити продуктивність, але наразі необхідні глибокі знання для його налаштування та обслуговування. Перегляньте [приклади RSC від Parcel](https://github.com/parcel-bundler/rsc-examples). + +Ваші стратегії рендерингу мають бути інтегровані з вашим маршрутизатором, щоб застосунки, створені за допомогою вашого фреймворку, могли вибирати стратегію рендерингу для кожного маршруту. Це дає змогу використовувати різні стратегії рендерингу без переписування всього застосунку. Наприклад, головна сторінка вашого застосунку може мати користь від статичної генерації (SSG), а сторінка з інформаційною стрічкою — від рендерингу з боку сервера. Використання слушної стратегії рендерингу для відповідних маршрутів може зменшити час до завантаження першого байта вмісту ([час до першого байта](https://web.dev/articles/ttfb)), рендерингу першого фрагмента вмісту ([малювання першого вмісту, FCP](https://web.dev/articles/fcp)) і рендерингу найбільшого видимого вмісту застосунку ([малювання найбільшого вмісту, LCP](https://web.dev/articles/lcp)). + +### Розділення коду {/*code-splitting*/} + +Розділення коду — це процес розбиття вашого застосунку на менші бандли, які можна завантажувати на вимогу. Розмір коду застосунку збільшується з кожною новою функцією чи доданою залежністю. Застосунки можуть повільно завантажуватися, оскільки перед використанням потрібно надіслати весь код для всього застосунку. Кешування, зменшення функцій/залежностей і перенесення деякого коду для виконання на сервері радше пришвидшать завантаження, але є неповними рішеннями, які можуть пожертвувати функціональністю у разі надмірного використання. + +Також якщо ви покладаєтеся на те, щоб застосунки на базі вашого фреймворку розділяли код, ви можете зіткнутися із ситуаціями, коли завантаження стає повільнішим, ніж якби розділення коду не відбувалося взагалі. Наприклад, [ліниво завантажена](/reference/react/lazy) діаграма затримує надсилання коду, необхідного для її рендерингу, відділяючи свій код від решти застосунку. [Parcel підтримує розділення коду за допомогою React.lazy](https://parceljs.org/recipes/react/#code-splitting). Однак, якщо діаграма завантажує свої дані *після* свого початкового рендерингу, ви тепер чекаєте двічі. Це водоспад: замість одночасного отримання даних для діаграми та надсилання коду для її рендерингу, ви повинні чекати, поки кожен крок завершиться один за одним. + +Розділення коду за маршрутом, інтегроване з бандлером й отриманням даних, може зменшити час початкового завантаження вашого застосунку та рендерингу найбільшого видимого вмісту застосунку ([малювання найбільшого вмісту, LCP](https://web.dev/articles/lcp)). + +### І більше... {/*and-more*/} + +Це були лише кілька прикладів функцій, які необхідно враховувати фреймворку. + +Є багато інших проблем, які користувачі повинні вирішити, наприклад: + +- Доступність +- Завантаження ресурсів +- Автентифікація +- Обробка помилок +- Мутація даних +- Навігації +- Вкладені маршрути +- Оптимістичні оновлення +- Кешування +- Поступове покращення (Progressive Enhancement) +- Генерація статичного сайту (SSG) +- Рендеринг з боку сервера (SSR) + +Багато з цих проблем окремо можуть стати складними, оскільки кожна з них взаємопов'язана з іншими та може вимагати глибоких знань у сфері, з якою ви часом не будете знайомі. Якщо ви не хочете вирішувати ці проблеми самотужки, [розпочніть роботу з фреймворком](/learn/creating-a-react-app), який вже надає ці функції. diff --git a/src/content/learn/creating-a-react-app.md b/src/content/learn/creating-a-react-app.md new file mode 100644 index 000000000..330c25436 --- /dev/null +++ b/src/content/learn/creating-a-react-app.md @@ -0,0 +1,116 @@ +--- +title: Створення React-застосунку +--- + + + +Якщо ви вирішили розробити новий застосунок або вебсайт за допомогою React, ми радимо вибрати один із фреймворків. + + + +## Рекомендовані React-фреймворки {/*bleeding-edge-react-frameworks*/} + +Ці фреймворки підтримують усі функції, що знадобляться для розгортання та масштабування застосунку в публічному середовищі (in production). Вони інтегрували останні React-функції й послуговуються перевагами архітектури React. + + + +#### React-фреймворки не потребують сервера. {/*react-frameworks-do-not-require-a-server*/} + +Усі фреймворки на цій сторінці можуть створювати односторінкові застосунки (SPA). Їх можна розгортати через [CDN](https://developer.mozilla.org/en-US/docs/Glossary/CDN) або сервіс статичного хостингу та вони не потребують сервера. Якщо ви хочете використати функції, для яких потрібен сервер (наприклад, SSR — рендеринг із боку сервера), то можна ввімкнути окремі маршрути без переписування застосунку. + + + +### Next.js (App Router) {/*nextjs-app-router*/} + +**[App Router від Next.js's](https://nextjs.org/docs) — це React-фреймворк, який повністю використовує переваги архітектури React, щоб уможливлювати React-застосунки повного стеку.** + + +npx create-next-app@latest + + +Next.js підтримується командою [Vercel](https://vercel.com/). [Розгорнути застосунок Next.js](https://nextjs.org/docs/app/building-your-application/deploying) можна на будь-якому Node.js- або безсерверному хостингу, а також на власному сервері. Також Next.js підтримує [статичне експортування](https://nextjs.org/docs/app/building-your-application/deploying/static-exports), якому не потрібен сервер. На додачу Vercel пропонує необов'язкові платні хмарні послуги. + +### React Router (v7) {/*react-router-v7*/} + +**[React Router](https://reactrouter.com/start/framework/installation) — це найпопулярніша бібліотека маршрутизації для React, яку можна поєднати з Vite для створення повноцінного React-фреймворку**. Він зосереджується на стандартних вебінтерфейсах і має кілька [готових до розгортання шаблонів](https://github.com/remix-run/react-router-templates) для різних середовищ і платформ JavaScript. + +Щоб створити новий проєкт з допомогою фреймворку React Router, виконайте: + + +npx create-react-router@latest + + +React Router підтримується [компанією Shopify](https://www.shopify.com). + +### Expo (для нативних застосунків) {/*expo*/} + +**[Expo](https://expo.dev/) — це React-фреймворк, що дає змогу створювати універсальні застосунки для Android, iOS і вебу з по-справжньому нативним UI.** Він пропонує набір інструментів для [React Native](https://reactnative.dev/), завдяки якому цими нативними частинами легше користуватися. Щоб створити новий проєкт Expo, виконайте: + + +npx create-expo-app@latest + + +Якщо Expo — це для вас щось нове, перегляньте [підручник Expo](https://docs.expo.dev/tutorial/introduction/). + +Expo підтримується [компанією Expo](https://expo.dev/about). Розробляти застосунки за допомогою Expo можна безкоштовно, і їх можна подавати до каталогів застосунків Google і Apple без жодних обмежень. На додачу Expo пропонує необов'язкові платні хмарні послуги. + + +## Інші варіанти {/*other-options*/} + +Є й інші перспективні фреймворки, які працюють над нашим баченням повного стеку з React: + +- [TanStack Start (бета-версія)](https://tanstack.com/): TanStack Start — це React-фреймворк повного стеку на основі TanStack Router. Він надає повнодокументний SSR, потокове передавання, серверні функції, збирання тощо за допомогою інструментів як Nitro та Vite. +- [RedwoodJS](https://redwoodjs.com/): Redwood — це React-фреймворк повного стеку із великою кількістю попередньо встановлених пакетів і налаштувань, що дає змогу легко створювати вебзастосунки повного стеку. + + + +#### З яких функцій складається бачення архітектури повного стеку від команди React? {/*which-features-make-up-the-react-teams-full-stack-architecture-vision*/} + +Бандлер App Router від Next.js повністю реалізує офіційну [специфікацію серверних компонентів React](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md). Це дає змогу поєднувати компоненти, що виконуються під час збирання, ті, що працюють суто на сервері, та інтерактивні компоненти в єдине дерево React. + +Наприклад, можна написати суто серверний компонент React як `async` функцію, що зчитує дані з бази даних або з файлу. Потім можна передати дані до інтерактивних компонентів: + +```js +// Цей компонент виконується *лише* на сервері (або під час збирання). +async function Talks({ confId }) { + // 1. Ви на сервері, тож можете спілкуватися зі своїм шаром даних. Наявність API — необов'язкова. + const talks = await db.Talks.findAll({ confId }); + + // 2. Додавайте будь-яку кількість логіки рендерингу. Це не збільшить ваш бандл із JavaScript. + const videos = talks.map(talk => talk.video); + + // 3. Передайте дані до компонентів, що виконаються в браузері. + return ; +} +``` + +App Router від Next.js також інтегрує [отримання даних із Suspense](/blog/2022/03/29/react-v18#suspense-in-data-frameworks). Це дає змогу задати стан завантаження (наприклад, каркасний елемент для заповнення (skeleton placeholder)) для різних частин інтерфейсу безпосередньо в дереві React: + +```js +}> + + +``` + +Серверні компоненти та Suspense — це радше функції React, аніж Next.js. Проте їхнє залучення на рівні фреймворку вимагає нетривіальних зусиль для реалізації та підтримування. Наразі App Router від Next.js — це найповніша імплементація. Команда React працює разом із розробниками бандлерів, щоб було легше реалізувати ці функції в наступному поколінні фреймворків. + + + + + + +#### Чи рекомендуєте Vite? {/*do-you-recommend-vite*/} + +Ми пропонуємо кілька рекомендацій для Vite. + +React Router v7 — це фреймворк на основі Vite, який дає вам змогу використовувати швидкий сервер розробки Vite та налаштувати все за допомогою фреймворку, який забезпечує маршрутизацію та отримання даних. Як і в інших фреймворках, які ми рекомендуємо, ви можете створити SPA за допомогою React Router v7. + +Ми також рекомендуємо використовувати Vite під час [додавання React до наявного проєкту](/learn/add-react-to-an-existing-project) або [побудови фреймворку](/learn/building-a-react-framework). + +Подібно до того, як у Svelte є Sveltekit, у Vue — Nuxt, а у Solid — SolidStart, React рекомендує використовувати для нових проєктів фреймворк, який легко інтегрується з інструментами збирання, як-от Vite. + + + +----- + +_Якщо ви автор фреймворку та бажаєте додати його на цю сторінку, [будь ласка, напишіть нам](https://github.com/reactjs/react.dev/issues/new?assignees=&labels=type%3A+framework&projects=&template=3-framework.yml&title=%5BFramework%5D%3A+)._ \ No newline at end of file diff --git a/src/content/learn/describing-the-ui.md b/src/content/learn/describing-the-ui.md index a7a156d15..3209b0dc7 100644 --- a/src/content/learn/describing-the-ui.md +++ b/src/content/learn/describing-the-ui.md @@ -546,7 +546,7 @@ React використовує дерева для моделювання зв' -Дерево залежностей часто використовується бандлерами, щоб запакувати весь актуальний код JavaScript для завантаження та рендерингу клієнтом. Великий розмір бандлу погіршує досвід користування React-застосунками. Розуміння дерева залежностей модулів корисно для усунення таких проблем. +Дерево залежностей часто використовується бандлерами, щоб запакувати весь актуальний код JavaScript для завантаження та рендерингу клієнтом. Великий розмір бандла погіршує досвід користування React-застосунками. Розуміння дерева залежностей модулів корисно для усунення таких проблем. diff --git a/src/content/learn/index.md b/src/content/learn/index.md index 0a882b05e..58242c5e5 100644 --- a/src/content/learn/index.md +++ b/src/content/learn/index.md @@ -111,7 +111,7 @@ function AboutPage() { ``` -React не вказує, як додавати файли CSS. У найпростішому випадку ви додасте тег [``](https://webdoky.org/uk/docs/Web/HTML/Element/link) до свого HTML. Якщо ви використовуєте інструмент збірки або фреймворк, зверніться до його документації, щоб дізнатися, як додати файл CSS до вашого проєкту. +React не вказує, як додавати файли CSS. У найпростішому випадку ви додасте тег [``](https://webdoky.org/uk/docs/Web/HTML/Element/link) до свого HTML. Якщо ви використовуєте інструмент збирання або фреймворк, зверніться до його документації, щоб дізнатися, як додати файл CSS до вашого проєкту. ## Відображення даних {/*displaying-data*/} diff --git a/src/content/learn/installation.md b/src/content/learn/installation.md index 3be39c4c9..3a76ae758 100644 --- a/src/content/learn/installation.md +++ b/src/content/learn/installation.md @@ -8,15 +8,6 @@ title: Встановлення - - -* [Як почати новий React-проєкт](/learn/start-a-new-react-project) -* [Як інтегрувати React у наявний проєкт](/learn/add-react-to-an-existing-project) -* [Як налаштувати редактор коду](/learn/editor-setup) -* [Як встановити інструменти React розробника](/learn/react-developer-tools) - - - ## Спробувати React {/*try-react*/} Не потрібно нічого встановлювати, щоб погратися з React. Спробуйте відредагувати код у цій пісочниці! @@ -39,18 +30,28 @@ export default function App() { Більшість сторінок у документації React містять схожі пісочниці. Водночас існує багато інших онлайн-пісочниць з підтримкою React, наприклад: [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react) або [CodePen.](https://codepen.io/pen?template=QWYVwWN) -### Спробувати React локально {/*try-react-locally*/} - Щоб спробувати React локально на вашому комп'ютері, [завантажте цю HTML-сторінку.](https://gist.githubusercontent.com/gaearon/0275b1e1518599bbeafcde4722e79ed1/raw/db72dcbf3384ee1708c4a07d3be79860db04bff0/example.html) Відкрийте її у редакторі та браузері! -## Почати новий React-проєкт {/*start-a-new-react-project*/} +## Створення React-застосунку {/*creating-a-react-app*/} + +Якщо ви хочете почати проєкт за допомогою React, [створіть React-застосунок](/learn/creating-a-react-app), використовуючи один із рекомендованих фреймворків. -Якщо ви хочете створити застосунок або вебсайт за допомогою React, [почніть новий React-проєкт.](/learn/start-a-new-react-project) +## Побудова React-фреймворку {/*build-a-react-framework*/} + +Якщо фреймворк не годиться для вашого проєкту або ви бажаєте почати із самостійного створення його структури, [побудуйте власний React-фреймворк](/learn/building-a-react-framework). ## Інтегрувати React у наявний проєкт {/*add-react-to-an-existing-project*/} Якщо ви хочете використати React у власному застосунку або вебсайті, [інтегруйте React у наявний проєкт.](/learn/add-react-to-an-existing-project) +## Застарілі варіанти {/*deprecated-options*/} + +### Create React App (Deprecated) {/*create-react-app-deprecated*/} + +Create React App — це застарілий інструмент, який раніше рекомендувався для створення нових React-застосунків. Якщо ви хочете почати новий проєкт за допомогою React, ви можете [створити React-застосунок](/learn/creating-a-react-app), використовуючи один із рекомендованих фреймворків. + +Дізнайтеся більше у статті ["Sunsetting Create React App"](/blog/2025/02/14/sunsetting-create-react-app). + ## Подальші кроки {/*next-steps*/} Відвідайте розділ ["Швидкий старт"](/learn), щоб ознайомитися з найважливішими повсякденними концепціями React. diff --git a/src/content/learn/react-developer-tools.md b/src/content/learn/react-developer-tools.md index 76b8bbc5f..719521fb1 100644 --- a/src/content/learn/react-developer-tools.md +++ b/src/content/learn/react-developer-tools.md @@ -1,22 +1,22 @@ --- -title: Інструменти React розробника +title: Інструменти розробника React --- -Використовуйте інструменти React розробника (_React Developer Tools_) для інспектування React [компонентів](/learn/your-first-component), редагування їх [пропсів](/learn/passing-props-to-a-component) і [стану](/learn/state-a-components-memory), а також для виявлення проблем з продуктивністю. +Використовуйте інструменти розробника React (_React Developer Tools_) для інспектування React [компонентів](/learn/your-first-component), редагування їх [пропсів](/learn/passing-props-to-a-component) і [стану](/learn/state-a-components-memory), а також для виявлення проблем з продуктивністю. -* Як встановити інструменти React розробника +* Як встановити інструменти розробника React ## Розширення браузера {/*browser-extension*/} -Найпростіший спосіб налагодження вебсайтів, створених за допомогою React — встановити розширення браузера "Інструменти React розробника" (_React Developer Tools_). Воно доступне для декількох популярних браузерів: +Найпростіший спосіб налагодження вебсайтів, створених за допомогою React — встановити розширення браузера "Інструменти розробника React" (_React Developer Tools_). Воно доступне для декількох популярних браузерів: * [Встановити для **Chrome**](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=uk) * [Встановити для **Firefox**](https://addons.mozilla.org/uk/firefox/addon/react-devtools/) @@ -24,7 +24,7 @@ title: Інструменти React розробника Тепер, якщо ви відвідаєте вебсайт, **який створений за допомогою React**, ви побачите вкладки _Components_ і _Profiler_. -![Розширення 'Інструменти React розробника'](/images/docs/react-devtools-extension.png) +![Розширення 'Інструменти розробника React'](/images/docs/react-devtools-extension.png) ### Safari та інші браузери {/*safari-and-other-browsers*/} Для інших браузерів (наприклад, Safari) встановіть npm-пакет [`react-devtools`](https://www.npmjs.com/package/react-devtools): @@ -50,7 +50,7 @@ react-devtools Перезавантажте вебсайт у браузері, щоб переглянути його в інструментах розробника. -![Автономні 'Інструменти React розробника'](/images/docs/react-devtools-standalone.png) +![Автономні 'Інструменти розробника React'](/images/docs/react-devtools-standalone.png) ## Мобільний застосунок (React Native) {/*mobile-react-native*/} diff --git a/src/content/learn/setup.md b/src/content/learn/setup.md new file mode 100644 index 000000000..e904d2377 --- /dev/null +++ b/src/content/learn/setup.md @@ -0,0 +1,32 @@ +--- +title: Налаштування +--- + + +React інтегрується з різними інструментами: редакторами, TypeScript, розширеннями браузера та компіляторами. Цей розділ допоможе вам налаштувати середовище. + + + +## Налаштування редактора {/*editor-setup*/} + +Перегляньте [рекомендовані редактори](/learn/editor-setup) та дізнайтеся, як налаштувати їх для роботи з React. + +## Використання TypeScript {/*using-typescript*/} + +TypeScript — це популярний спосіб додавати визначення типів до JavaScript-коду. [Дізнайтеся, як інтегрувати TypeScript у ваші React-проєкти](/learn/typescript). + +## Інструменти розробника React {/*react-developer-tools*/} + +Інструменти розробника React (React Developer Tools) — це розширення для браузера, яке може інспектувати компоненти React, редагувати пропси та стан, а також виявляти проблеми продуктивності. Дізнайтеся, як його встановити, [тут](learn/react-developer-tools). + +## React Compiler {/*react-compiler*/} + +React Compiler — це інструмент, який автоматично оптимізує ваш React-застосунок. [Дізнатися більше](/learn/react-compiler). + +## Start a React Project from scratch {/*start-a-react-project-from-scratch*/} + +If you want to build your own framework, you can [start a React project from scratch](/learn/start-a-react-project-from-scratch). + +## Подальші кроки {/*next-steps*/} + +Відвідайте посібник ["Швидкий старт"](/learn), щоб ознайомитися з найважливішими концепціями React, з якими ви стикаєтеся щодня. diff --git a/src/content/learn/start-a-new-react-project.md b/src/content/learn/start-a-new-react-project.md deleted file mode 100644 index be3510ceb..000000000 --- a/src/content/learn/start-a-new-react-project.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: Початок нового React-проєкту ---- - - - -Якщо ви вирішили розробити новий застосунок або новий вебсайт цілком за допомогою React, ми радимо вибрати один із популярних у нашій спільноті React-фреймворків. - - - - -Ви можете використовувати React без фреймворку, проте ми з'ясували, що більшість застосунків і сайтів, зрештою, створюють рішення для поширених проблем, наприклад: розбиття коду, маршрутизації, отримання даних і генерування HTML. Ці проблеми — спільні для всіх бібліотек UI, а не лише для React. - -Вибравши фреймворк, можна швидко почати роботу з React і уникнути, по суті, розробки власного фреймворку пізніше. - - - -#### Чи можна використовувати React без фреймворку? {/*can-i-use-react-without-a-framework*/} - -Звісно можна використовувати React без фреймворку — так само, як можна [використовувати React для частини сторінки.](/learn/add-react-to-an-existing-project#using-react-for-a-part-of-your-existing-page) **Однак якщо ви розробляєте новий застосунок або сайт повністю за допомогою React, ми радимо скористатися фреймворком.** - -І ось чому. - -Навіть якщо вам спершу не потрібні маршрутизація чи отримання даних, то існує ймовірність того, що ви захочете додати для цих функцій кілька бібліотек. Поки ваш бандл JavaScript розростатиметься з кожною новою функцією, ви будете розбиратися, як розбити код для кожного окремого маршруту. Поки ваші потреби з отримання даних ускладнюватимуться, ймовірно, ви зустрінете серверно-клієнтські мережеві водоспади, що зроблять ваш застосунок дуже повільним. Коли серед ваших користувачів з'явиться більше осіб із поганим з'єднанням і бюджетними пристроями, вам може знадобитися генерація HTML із компонентів, щоб показувати вміст раніше — або з боку сервера, або під час збирання сайту. Зміна налаштувань для виконання частини коду на сервері чи під час збирання може стати завданням із зірочкою. - -**Ці проблеми не стосуються лише React. Це саме те, через що у Svelte є SvelteKit, у Vue є Nuxt і так далі.** Щоб вирішити ці проблеми самотужки, доведеться інтегрувати свій бандлер зі своїм маршрутизатором і зі своєю бібліотекою отримання даних. Неважко змусити працювати вкупі якийсь базовий варіант, проте є чимало тонкощів у створенні застосунку, який, зростаючи з часом, буде все ще швидко завантажуватися. Захочеться надсилати до браузера якнайменшу кількість коду застосунку, але робити це за один захід клієнт-сервер і паралельно до завантаження всіх необхідних на сторінці даних. Ймовірно, захочеться, щоб сторінка була інтерактивною навіть до виконання коду JavaScript, щоб підтримувати поступове покращення (progressive enhancement). Може захотітися генерувати теку повністю статичних файлів HTML для рекламних сторінок, які можуть викладатися будь-де і працювати навіть із вимкненим JavaScript. Розробка такого функціоналу вимагає суттєвих зусиль. - -**Наведені на цій сторінці фреймворки React вже вирішують такі проблеми, не потребуючи додаткових зусиль з вашого боку.** Вони дають змогу почати з малого, аби потім масштабувати застосунок відповідно до потреб. Кожний React-фреймворк має свою спільноту, тож шукати відповіді на питання та оновлювати інструменти розробки буде легше. Також фреймворки задають структуру коду, допомагаючи вам та іншим зберігати той самий контекст і користуватися тими ж навичками в різних проєктах. І навпаки: із саморобними налаштуваннями легше застрягнути у версіях залежностей, що більше не підтримуються, і, по суті, доведеться створювати власний фреймворк — але без власної спільноти та інструкцій з оновлення (і якщо він буде схожим на те, що колись писали ми, то цей ваш фреймворк буде написаний абияк). - -Якщо ваш застосунок містить незвичні обмеження, які не спрацюють із переліченими нижче фреймворками, або ви хочете розв'язувати ці проблеми самотужки, то можете розгорнути власну конфігурацію з React. Візьміть `react` і `react-dom` із npm, налаштуйте власний процес збирання з використанням бандлеру, як-от [Vite](https://vite.dev/) або [Parcel](https://parceljs.org/), і додавайте інші інструменти за необхідності для маршрутизації, статичної генерації, серверного рендерингу чи чогось іще. - - - -## Готові до впровадження фреймворки React {/*production-grade-react-frameworks*/} - -Ці фреймворки підтримують усі функції, що знадобляться для розгортання та масштабування застосунку в публічному середовищі (in production), та прямують до підтримки нашого [бачення архітектури повного стеку](#which-features-make-up-the-react-teams-full-stack-architecture-vision). Усі фреймворки, які ми радимо, є відкритим програмним забезпеченням з активною спільнотою для підтримки та можуть бути розгорнуті на власних серверах чи сторонніх хостингах. Якщо ви автор фреймворку та зацікавлені у потраплянні до цього списку, [будь ласка, повідомте нас](https://github.com/reactjs/react.dev/issues/new?assignees=&labels=type%3A+framework&projects=&template=3-framework.yml&title=%5BFramework%5D%3A+). - -### Next.js {/*nextjs-pages-router*/} - -**[Page Router від Next.js](https://nextjs.org/) — це фреймворк React повного стеку.** Він багатоцільовий та дає змогу розробляти застосунки React будь-якого розміру — від здебільшого статичних блогів до складних динамічних програм. Щоб створити новий проєкт Next.js, запустіть у терміналі: - - -npx create-next-app@latest - - -Якщо Next.js — це для вас дещо нове, то погляньте на [курс вивчення Next.js.](https://nextjs.org/learn) - -Next.js підтримується командою [Vercel](https://vercel.com/). [Розгорнути застосунок Next.js](https://nextjs.org/docs/app/building-your-application/deploying) можна на будь-якому Node.js- або безсерверному хостингу, а також на власному сервері. Також Next.js підтримує [статичне експортування](https://nextjs.org/docs/pages/building-your-application/deploying/static-exports), якому не потрібен сервер. - -### Remix {/*remix*/} - -**[Remix](https://remix.run/) — це фреймворк React повного стеку з вкладеною маршрутизацією.** Він дає змогу розбивати застосунок на вкладені одна в одну частини, які можуть паралельно завантажувати дані та оновлювати їх, реагуючи на дії користувача. Щоб створити новий проєкт Remix, запустіть: - - -npx create-remix - - -Якщо Remix — це для вас щось нове, перегляньте його [посібник зі створення блогу](https://remix.run/docs/en/main/tutorials/blog) (коротко) та [посібник зі створення застосунку](https://remix.run/docs/en/main/tutorials/jokes) (довго). - -Remix підтримується командою [Shopify](https://www.shopify.com/). Для створення проєкту Remix необхідно [вибрати свій варіант шаблону для розгортання](https://remix.run/docs/en/main/guides/deployment). Застосунок Remix можна розгорнути на будь-якому хостингу, що підтримує Node.js або є безсерверним, за допомогою готового чи власного [адаптера](https://remix.run/docs/en/main/other-api/adapter). - -### Gatsby {/*gatsby*/} - -**[Gatsby](https://www.gatsbyjs.com/) — це фреймворк React для швидких вебсайтів із системою керування вмістом.** Його багата екосистема плагінів і шар даних на основі GraphQL спрощують інтеграцію вмісту, API і сторонніх служб в один вебсайт. Щоб створити новий проєкт Gatsby, запустіть: - - -npx create-gatsby - - -Якщо Gatsby — це для вас щось нове, перегляньте [підручник Gatsby.](https://www.gatsbyjs.com/docs/tutorial/) - -Gatsby підтримується командою [Netlify](https://www.netlify.com/). [Повністю статичний сайт Gatsby можна розгорнути](https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting) на будь-якому статичному хостингу. За бажання скористатися суто серверними функціями фреймворку слід перевірити, що хостинг підтримує ці можливості для Gatsby. - -### Expo (для нативних застосунків) {/*expo*/} - -**[Expo](https://expo.dev/) — це фреймворк React, що дає змогу створювати універсальні застосунки для Android, iOS і вебу з по-справжньому нативним UI.** Він пропонує набір інструментів для [React Native](https://reactnative.dev/), завдяки якому цими нативними частинами легше користуватися. Щоб створити новий проєкт Expo, запустіть: - - -npx create-expo-app - - -Якщо Expo — це для вас щось нове, перегляньте [підручник Expo](https://docs.expo.dev/tutorial/introduction/). - -Expo підтримується [компанією Expo](https://expo.dev/about). Розробляти застосунки за допомогою Expo можна безкоштовно, і їх можна подавати до каталогів застосунків Google і Apple без жодних обмежень. На додачу Expo пропонує необов'язкові платні хмарні послуги. - -## Передові фреймворки React {/*bleeding-edge-react-frameworks*/} - -Під час досліджень заради подальшого покращення React ми усвідомили, що тісніша інтеграція React із фреймворками (а саме з маршрутизацією, згортанням і серверними технологіями) — це наша найбільша нагода допомогти користувачам React створювати кращі застосунки. Команда Next.js погодилася співпрацювати з нами щодо досліджень, розробки, інтегрування та тестування незалежних від фреймворків функцій React штибу [серверних компонентів React.](/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components) - -З кожним днем ці функції наближаються до стану готовності до впровадження (production-ready), і ми обговорюємо їх інтеграцію з іншими розробниками бандлерів і фреймворків. Сподіваємося, що за рік чи два усі фреймворки, перелічені на цій сторінці, матимуть підтримку цих функцій. (Якщо ви автор фреймворку та зацікавлені у співпраці щодо експериментів із цими функціями, будь ласка, повідомте нас!) - -### Next.js (App Router) {/*nextjs-app-router*/} - -**[App Router від Next.js](https://nextjs.org/docs) — це переробка API Next.js, що прагне втілити бачення повностекової архітектури команди React.** Вона дає змогу отримувати дані в асинхронних компонентах, що запускаються на сервері чи навіть під час збирання. - -Next.js підтримується командою [Vercel](https://vercel.com/). [Застосунок Next.js можна розгорнути](https://nextjs.org/docs/app/building-your-application/deploying) на будь-якому Node.js- або безсерверному хостингу, а також на власному сервері. Також Next.js підтримує [статичне експортування](https://nextjs.org/docs/app/building-your-application/deploying/static-exports), якому не потрібен сервер. - - - -#### З яких функцій складається бачення архітектури повного стеку від команди React? {/*which-features-make-up-the-react-teams-full-stack-architecture-vision*/} - -Бандлер App Router від Next.js повністю реалізує офіційну [специфікацію серверних компонентів React](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md). Це дає змогу поєднувати компоненти, що запускаються під час збирання, ті, що працюють суто на сервері, та інтерактивні компоненти в єдине дерево React. - -Наприклад, можна написати суто серверний компонент React як `async` функцію, що зчитує дані з бази даних або з файлу. Потім можна передати дані до інтерактивних компонентів: - -```js -// Цей компонент запускається *лише* на сервері (або під час збирання). -async function Talks({ confId }) { - // 1. Ви на сервері, тож можете спілкуватися зі своїм шаром даних. Наявність API — необов'язкова. - const talks = await db.Talks.findAll({ confId }); - - // 2. Додавайте будь-яку кількість логіки рендерингу. Це не збільшить ваш бандл JavaScript. - const videos = talks.map(talk => talk.video); - - // 3. Передайте дані до компонентів, що запустяться в браузері. - return ; -} -``` - -App Router від Next.js також інтегрує [отримання даних із Suspense](/blog/2022/03/29/react-v18#suspense-in-data-frameworks). Це дає змогу задати стан завантаження (наприклад, каркасний елемент для заповнення (skeleton placeholder)) для різних частин інтерфейсу безпосередньо в дереві React: - -```js -}> - - -``` - -Серверні компоненти та Suspense — це радше функції React, аніж Next.js. Проте їхнє залучення на рівні фреймворку вимагає нетривіальних зусиль для реалізації. Наразі App Router Next.js — це найповніша імплементація. Команда React працює разом із розробниками бандлерів, щоб було легше реалізувати ці функції в наступному поколінні фреймворків. - - diff --git a/src/content/learn/understanding-your-ui-as-a-tree.md b/src/content/learn/understanding-your-ui-as-a-tree.md index 9900c3ec8..dc7c9001c 100644 --- a/src/content/learn/understanding-your-ui-as-a-tree.md +++ b/src/content/learn/understanding-your-ui-as-a-tree.md @@ -281,7 +281,7 @@ export default [ Дерева залежностей корисні для визначення, які модулі необхідні для запуску вашого React-застосунку. Під час побудови готового до впровадження React-застосунку зазвичай є етап, який запакує (bundle) весь необхідний JavaScript-код для доставлення клієнту. Інструмент, що за це відповідає, називається [бандлер](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview#the_modern_tooling_ecosystem), і бандлери використовують дерево залежностей для визначення, які модулі повинно бути включено. -Відповідно до того, як зростає ваш застосунок, збільшується і розмір запакованого застосунку — бандлу. Великі розміри бандлів є "дорогими" для завантаження та виконання клієнтом. Великі розміри бандлів можуть затримувати час відображення вашого UI. Розуміння дерева залежностей вашого застосунку може допомогти з налагодженням цих проблем. +Відповідно до того, як зростає ваш застосунок, збільшується і розмір запакованого застосунку — бандла. Великі розміри бандлів є "дорогими" для завантаження та виконання клієнтом. Великі розміри бандлів можуть затримувати час відображення вашого UI. Розуміння дерева залежностей вашого застосунку може допомогти з налагодженням цих проблем. [comment]: <> (perhaps we should also deep dive on conditional imports) @@ -293,7 +293,7 @@ export default [ * Дерева рендерингу допомагають ідентифікувати, які компоненти є внутрішніми, а які — зовнішніми (листи). Внутрішні компоненти впливають на продуктивність рендерингу всіх компонентів-нащадків, а компоненти-листи часто піддаються повторному рендерингу. Їх ідентифікація корисна для розуміння та налагодження продуктивності рендерингу. * Дерева залежностей відображають залежності модулів у React-застосунку. * Дерева залежностей використовуються бандлерами, щоб запакувати необхідний код для доставлення застосунку. -* Дерева залежностей корисні для налагодження великих розмірів бандлів, які збільшують час до появи першого вмісту та показують можливі оптимізації відносно того, який код додається до бандлу. +* Дерева залежностей корисні для налагодження великих розмірів бандлів, які збільшують час до появи першого вмісту та показують можливі оптимізації відносно того, який код додається до бандла. diff --git a/src/content/reference/react-dom/client/createRoot.md b/src/content/reference/react-dom/client/createRoot.md index 54e7a7f1d..0a3933949 100644 --- a/src/content/reference/react-dom/client/createRoot.md +++ b/src/content/reference/react-dom/client/createRoot.md @@ -344,775 +344,127 @@ export default function App({counter}) { It is uncommon to call `render` multiple times. Usually, your components will [update state](/reference/react/useState) instead. -### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} +### Error logging in production {/*error-logging-in-production*/} -By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: +By default, React will log all errors to the console. To implement your own error reporting, you can provide the optional error handler root options `onUncaughtError`, `onCaughtError` and `onRecoverableError`: -```js [[1, 6, "onUncaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack"]] -import { createRoot } from 'react-dom/client'; - -const root = createRoot( - document.getElementById('root'), - { - onUncaughtError: (error, errorInfo) => { - console.error( - 'Uncaught error', - error, - errorInfo.componentStack - ); - } - } -); -root.render(); -``` - -The onUncaughtError option is a function called with two arguments: - -1. The error that was thrown. -2. An errorInfo object that contains the componentStack of the error. - -You can use the `onUncaughtError` root option to display error dialogs: - - - -```html public/index.html hidden - - - - My app - - - - - -
- - -``` - -```css src/styles.css active -label, button { display: block; margin-bottom: 20px; } -html, body { min-height: 300px; } - -#error-dialog { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: white; - padding: 15px; - opacity: 0.9; - text-wrap: wrap; - overflow: scroll; -} - -.text-red { - color: red; -} - -.-mb-20 { - margin-bottom: -20px; -} - -.mb-0 { - margin-bottom: 0; -} - -.mb-10 { - margin-bottom: 10px; -} - -pre { - text-wrap: wrap; -} - -pre.nowrap { - text-wrap: nowrap; -} - -.hidden { - display: none; -} -``` - -```js src/reportError.js hidden -function reportError({ title, error, componentStack, dismissable }) { - const errorDialog = document.getElementById("error-dialog"); - const errorTitle = document.getElementById("error-title"); - const errorMessage = document.getElementById("error-message"); - const errorBody = document.getElementById("error-body"); - const errorComponentStack = document.getElementById("error-component-stack"); - const errorStack = document.getElementById("error-stack"); - const errorClose = document.getElementById("error-close"); - const errorCause = document.getElementById("error-cause"); - const errorCauseMessage = document.getElementById("error-cause-message"); - const errorCauseStack = document.getElementById("error-cause-stack"); - const errorNotDismissible = document.getElementById("error-not-dismissible"); - - // Set the title - errorTitle.innerText = title; - - // Display error message and body - const [heading, body] = error.message.split(/\n(.*)/s); - errorMessage.innerText = heading; - if (body) { - errorBody.innerText = body; - } else { - errorBody.innerText = ''; - } - - // Display component stack - errorComponentStack.innerText = componentStack; - - // Display the call stack - // Since we already displayed the message, strip it, and the first Error: line. - errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; - - // Display the cause, if available - if (error.cause) { - errorCauseMessage.innerText = error.cause.message; - errorCauseStack.innerText = error.cause.stack; - errorCause.classList.remove('hidden'); - } else { - errorCause.classList.add('hidden'); - } - // Display the close button, if dismissible - if (dismissable) { - errorNotDismissible.classList.add('hidden'); - errorClose.classList.remove("hidden"); - } else { - errorNotDismissible.classList.remove('hidden'); - errorClose.classList.add("hidden"); - } - - // Show the dialog - errorDialog.classList.remove("hidden"); -} - -export function reportCaughtError({error, cause, componentStack}) { - reportError({ title: "Caught Error", error, componentStack, dismissable: true}); -} - -export function reportUncaughtError({error, cause, componentStack}) { - reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); -} - -export function reportRecoverableError({error, cause, componentStack}) { - reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); -} -``` - -```js src/index.js active +```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack", 15]] import { createRoot } from "react-dom/client"; -import App from "./App.js"; -import {reportUncaughtError} from "./reportError"; -import "./styles.css"; +import { reportCaughtError } from "./reportError"; const container = document.getElementById("root"); const root = createRoot(container, { - onUncaughtError: (error, errorInfo) => { - if (error.message !== 'Known error') { - reportUncaughtError({ + onCaughtError: (error, errorInfo) => { + if (error.message !== "Known error") { + reportCaughtError({ error, - componentStack: errorInfo.componentStack + componentStack: errorInfo.componentStack, }); } - } + }, }); -root.render(); -``` - -```js src/App.js -import { useState } from 'react'; - -export default function App() { - const [throwError, setThrowError] = useState(false); - - if (throwError) { - foo.bar = 'baz'; - } - - return ( -
- This error shows the error dialog: - -
- ); -} -``` - -
- - -### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/} - -By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option to handle errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary): - -```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack"]] -import { createRoot } from 'react-dom/client'; - -const root = createRoot( - document.getElementById('root'), - { - onCaughtError: (error, errorInfo) => { - console.error( - 'Caught error', - error, - errorInfo.componentStack - ); - } - } -); -root.render(); ``` The onCaughtError option is a function called with two arguments: -1. The error that was caught by the boundary. +1. The error that was thrown. 2. An errorInfo object that contains the componentStack of the error. -You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging: +Together with `onUncaughtError` and `onRecoverableError`, you can can implement your own error reporting system: -```html public/index.html hidden - - - - My app - - - - - -
- - -``` - -```css src/styles.css active -label, button { display: block; margin-bottom: 20px; } -html, body { min-height: 300px; } - -#error-dialog { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: white; - padding: 15px; - opacity: 0.9; - text-wrap: wrap; - overflow: scroll; -} - -.text-red { - color: red; -} - -.-mb-20 { - margin-bottom: -20px; +```js src/reportError.js +function reportError({ type, error, errorInfo }) { + // The specific implementation is up to you. + // `console.error()` is only used for demonstration purposes. + console.error(type, error, "Component Stack: "); + console.error("Component Stack: ", errorInfo.componentStack); } -.mb-0 { - margin-bottom: 0; -} - -.mb-10 { - margin-bottom: 10px; -} - -pre { - text-wrap: wrap; -} - -pre.nowrap { - text-wrap: nowrap; -} - -.hidden { - display: none; -} -``` - -```js src/reportError.js hidden -function reportError({ title, error, componentStack, dismissable }) { - const errorDialog = document.getElementById("error-dialog"); - const errorTitle = document.getElementById("error-title"); - const errorMessage = document.getElementById("error-message"); - const errorBody = document.getElementById("error-body"); - const errorComponentStack = document.getElementById("error-component-stack"); - const errorStack = document.getElementById("error-stack"); - const errorClose = document.getElementById("error-close"); - const errorCause = document.getElementById("error-cause"); - const errorCauseMessage = document.getElementById("error-cause-message"); - const errorCauseStack = document.getElementById("error-cause-stack"); - const errorNotDismissible = document.getElementById("error-not-dismissible"); - - // Set the title - errorTitle.innerText = title; - - // Display error message and body - const [heading, body] = error.message.split(/\n(.*)/s); - errorMessage.innerText = heading; - if (body) { - errorBody.innerText = body; - } else { - errorBody.innerText = ''; - } - - // Display component stack - errorComponentStack.innerText = componentStack; - - // Display the call stack - // Since we already displayed the message, strip it, and the first Error: line. - errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; - - // Display the cause, if available - if (error.cause) { - errorCauseMessage.innerText = error.cause.message; - errorCauseStack.innerText = error.cause.stack; - errorCause.classList.remove('hidden'); - } else { - errorCause.classList.add('hidden'); - } - // Display the close button, if dismissible - if (dismissable) { - errorNotDismissible.classList.add('hidden'); - errorClose.classList.remove("hidden"); - } else { - errorNotDismissible.classList.remove('hidden'); - errorClose.classList.add("hidden"); +export function onCaughtErrorProd(error, errorInfo) { + if (error.message !== "Known error") { + reportError({ type: "Caught", error, errorInfo }); } - - // Show the dialog - errorDialog.classList.remove("hidden"); } -export function reportCaughtError({error, cause, componentStack}) { - reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +export function onUncaughtErrorProd(error, errorInfo) { + reportError({ type: "Uncaught", error, errorInfo }); } -export function reportUncaughtError({error, cause, componentStack}) { - reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); -} - -export function reportRecoverableError({error, cause, componentStack}) { - reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +export function onRecoverableErrorProd(error, errorInfo) { + reportError({ type: "Recoverable", error, errorInfo }); } ``` ```js src/index.js active import { createRoot } from "react-dom/client"; import App from "./App.js"; -import {reportCaughtError} from "./reportError"; -import "./styles.css"; +import { + onCaughtErrorProd, + onRecoverableErrorProd, + onUncaughtErrorProd, +} from "./reportError"; const container = document.getElementById("root"); const root = createRoot(container, { - onCaughtError: (error, errorInfo) => { - if (error.message !== 'Known error') { - reportCaughtError({ - error, - componentStack: errorInfo.componentStack, - }); - } - } + // Keep in mind to remove these options in development to leverage + // React's default handlers or implement your own overlay for development. + // The handlers are only specfied unconditionally here for demonstration purposes. + onCaughtError: onCaughtErrorProd, + onRecoverableError: onRecoverableErrorProd, + onUncaughtError: onUncaughtErrorProd, }); root.render(); ``` ```js src/App.js -import { useState } from 'react'; -import { ErrorBoundary } from "react-error-boundary"; - -export default function App() { - const [error, setError] = useState(null); - - function handleUnknown() { - setError("unknown"); - } +import { Component, useState } from "react"; - function handleKnown() { - setError("known"); - } - - return ( - <> - { - setError(null); - }} - > - {error != null && } - This error will not show the error dialog: - - This error will show the error dialog: - - - - - ); +function Boom() { + foo.bar = "baz"; } -function fallbackRender({ resetErrorBoundary }) { - return ( -
-

Error Boundary

-

Something went wrong.

- -
- ); -} +class ErrorBoundary extends Component { + state = { hasError: false }; -function Throw({error}) { - if (error === "known") { - throw new Error('Known error') - } else { - foo.bar = 'baz'; + static getDerivedStateFromError(error) { + return { hasError: true }; } -} -``` - -```json package.json hidden -{ - "dependencies": { - "react": "19.0.0-rc-3edc000d-20240926", - "react-dom": "19.0.0-rc-3edc000d-20240926", - "react-scripts": "^5.0.0", - "react-error-boundary": "4.0.3" - }, - "main": "/index.js" -} -``` - -
- -### Displaying a dialog for recoverable errors {/*displaying-a-dialog-for-recoverable-errors*/} - -React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional `onRecoverableError` root option: - -```js [[1, 6, "onRecoverableError"], [2, 6, "error", 1], [3, 10, "error.cause"], [4, 6, "errorInfo"], [5, 11, "componentStack"]] -import { createRoot } from 'react-dom/client'; -const root = createRoot( - document.getElementById('root'), - { - onRecoverableError: (error, errorInfo) => { - console.error( - 'Recoverable error', - error, - error.cause, - errorInfo.componentStack, - ); + render() { + if (this.state.hasError) { + return

Something went wrong.

; } + return this.props.children; } -); -root.render(); -``` - -The onRecoverableError option is a function called with two arguments: - -1. The error that React throws. Some errors may include the original cause as error.cause. -2. An errorInfo object that contains the componentStack of the error. - -You can use the `onRecoverableError` root option to display error dialogs: - - - -```html public/index.html hidden - - - - My app - - - - - -
- - -``` - -```css src/styles.css active -label, button { display: block; margin-bottom: 20px; } -html, body { min-height: 300px; } - -#error-dialog { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: white; - padding: 15px; - opacity: 0.9; - text-wrap: wrap; - overflow: scroll; -} - -.text-red { - color: red; -} - -.-mb-20 { - margin-bottom: -20px; -} - -.mb-0 { - margin-bottom: 0; -} - -.mb-10 { - margin-bottom: 10px; -} - -pre { - text-wrap: wrap; -} - -pre.nowrap { - text-wrap: nowrap; -} - -.hidden { - display: none; -} -``` - -```js src/reportError.js hidden -function reportError({ title, error, componentStack, dismissable }) { - const errorDialog = document.getElementById("error-dialog"); - const errorTitle = document.getElementById("error-title"); - const errorMessage = document.getElementById("error-message"); - const errorBody = document.getElementById("error-body"); - const errorComponentStack = document.getElementById("error-component-stack"); - const errorStack = document.getElementById("error-stack"); - const errorClose = document.getElementById("error-close"); - const errorCause = document.getElementById("error-cause"); - const errorCauseMessage = document.getElementById("error-cause-message"); - const errorCauseStack = document.getElementById("error-cause-stack"); - const errorNotDismissible = document.getElementById("error-not-dismissible"); - - // Set the title - errorTitle.innerText = title; - - // Display error message and body - const [heading, body] = error.message.split(/\n(.*)/s); - errorMessage.innerText = heading; - if (body) { - errorBody.innerText = body; - } else { - errorBody.innerText = ''; - } - - // Display component stack - errorComponentStack.innerText = componentStack; - - // Display the call stack - // Since we already displayed the message, strip it, and the first Error: line. - errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; - - // Display the cause, if available - if (error.cause) { - errorCauseMessage.innerText = error.cause.message; - errorCauseStack.innerText = error.cause.stack; - errorCause.classList.remove('hidden'); - } else { - errorCause.classList.add('hidden'); - } - // Display the close button, if dismissible - if (dismissable) { - errorNotDismissible.classList.add('hidden'); - errorClose.classList.remove("hidden"); - } else { - errorNotDismissible.classList.remove('hidden'); - errorClose.classList.add("hidden"); - } - - // Show the dialog - errorDialog.classList.remove("hidden"); -} - -export function reportCaughtError({error, cause, componentStack}) { - reportError({ title: "Caught Error", error, componentStack, dismissable: true}); -} - -export function reportUncaughtError({error, cause, componentStack}) { - reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); } -export function reportRecoverableError({error, cause, componentStack}) { - reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); -} -``` - -```js src/index.js active -import { createRoot } from "react-dom/client"; -import App from "./App.js"; -import {reportRecoverableError} from "./reportError"; -import "./styles.css"; - -const container = document.getElementById("root"); -const root = createRoot(container, { - onRecoverableError: (error, errorInfo) => { - reportRecoverableError({ - error, - cause: error.cause, - componentStack: errorInfo.componentStack, - }); - } -}); -root.render(); -``` - -```js src/App.js -import { useState } from 'react'; -import { ErrorBoundary } from "react-error-boundary"; - -// 🚩 Bug: Never do this. This will force an error. -let errorThrown = false; export default function App() { + const [triggerUncaughtError, settriggerUncaughtError] = useState(false); + const [triggerCaughtError, setTriggerCaughtError] = useState(false); + return ( <> - - {!errorThrown && } -

This component threw an error, but recovered during a second render.

-

Since it recovered, no Error Boundary was shown, but onRecoverableError was used to show an error dialog.

-
- + + {triggerUncaughtError && } + + {triggerCaughtError && ( + + + + )} ); } - -function fallbackRender() { - return ( -
-

Error Boundary

-

Something went wrong.

-
- ); -} - -function Throw({error}) { - // Simulate an external value changing during concurrent render. - errorThrown = true; - foo.bar = 'baz'; -} -``` - -```json package.json hidden -{ - "dependencies": { - "react": "19.0.0-rc-3edc000d-20240926", - "react-dom": "19.0.0-rc-3edc000d-20240926", - "react-scripts": "^5.0.0", - "react-error-boundary": "4.0.3" - }, - "main": "/index.js" -} ```
- ---- ## Troubleshooting {/*troubleshooting*/} ### I've created a root, but nothing is displayed {/*ive-created-a-root-but-nothing-is-displayed*/} diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md index b1eeca30c..887cab7d2 100644 --- a/src/content/reference/react-dom/client/hydrateRoot.md +++ b/src/content/reference/react-dom/client/hydrateRoot.md @@ -374,554 +374,124 @@ export default function App({counter}) { 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. -### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} +### Error logging in production {/*error-logging-in-production*/} -By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: +By default, React will log all errors to the console. To implement your own error reporting, you can provide the optional error handler root options `onUncaughtError`, `onCaughtError` and `onRecoverableError`: -```js [[1, 7, "onUncaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]] -import { hydrateRoot } from 'react-dom/client'; +```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack", 15]] +import { hydrateRoot } from "react-dom/client"; +import { reportCaughtError } from "./reportError"; -const root = hydrateRoot( - document.getElementById('root'), - , - { - onUncaughtError: (error, errorInfo) => { - console.error( - 'Uncaught error', +const container = document.getElementById("root"); +const root = hydrateRoot(container, { + onCaughtError: (error, errorInfo) => { + if (error.message !== "Known error") { + reportCaughtError({ error, - errorInfo.componentStack - ); + componentStack: errorInfo.componentStack, + }); } - } -); -root.render(); + }, +}); ``` -The onUncaughtError option is a function called with two arguments: +The onCaughtError option is a function called with two arguments: 1. The error that was thrown. 2. An errorInfo object that contains the componentStack of the error. -You can use the `onUncaughtError` root option to display error dialogs: +Together with `onUncaughtError` and `onRecoverableError`, you can can implement your own error reporting system: -```html public/index.html hidden - - - - My app - - - - - -
This error shows the error dialog:
- - -``` - -```css src/styles.css active -label, button { display: block; margin-bottom: 20px; } -html, body { min-height: 300px; } - -#error-dialog { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: white; - padding: 15px; - opacity: 0.9; - text-wrap: wrap; - overflow: scroll; +```js src/reportError.js +function reportError({ type, error, errorInfo }) { + // The specific implementation is up to you. + // `console.error()` is only used for demonstration purposes. + console.error(type, error, "Component Stack: "); + console.error("Component Stack: ", errorInfo.componentStack); } -.text-red { - color: red; -} - -.-mb-20 { - margin-bottom: -20px; -} - -.mb-0 { - margin-bottom: 0; -} - -.mb-10 { - margin-bottom: 10px; -} - -pre { - text-wrap: wrap; -} - -pre.nowrap { - text-wrap: nowrap; -} - -.hidden { - display: none; -} -``` - -```js src/reportError.js hidden -function reportError({ title, error, componentStack, dismissable }) { - const errorDialog = document.getElementById("error-dialog"); - const errorTitle = document.getElementById("error-title"); - const errorMessage = document.getElementById("error-message"); - const errorBody = document.getElementById("error-body"); - const errorComponentStack = document.getElementById("error-component-stack"); - const errorStack = document.getElementById("error-stack"); - const errorClose = document.getElementById("error-close"); - const errorCause = document.getElementById("error-cause"); - const errorCauseMessage = document.getElementById("error-cause-message"); - const errorCauseStack = document.getElementById("error-cause-stack"); - const errorNotDismissible = document.getElementById("error-not-dismissible"); - - // Set the title - errorTitle.innerText = title; - - // Display error message and body - const [heading, body] = error.message.split(/\n(.*)/s); - errorMessage.innerText = heading; - if (body) { - errorBody.innerText = body; - } else { - errorBody.innerText = ''; +export function onCaughtErrorProd(error, errorInfo) { + if (error.message !== "Known error") { + reportError({ type: "Caught", error, errorInfo }); } - - // Display component stack - errorComponentStack.innerText = componentStack; - - // Display the call stack - // Since we already displayed the message, strip it, and the first Error: line. - errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; - - // Display the cause, if available - if (error.cause) { - errorCauseMessage.innerText = error.cause.message; - errorCauseStack.innerText = error.cause.stack; - errorCause.classList.remove('hidden'); - } else { - errorCause.classList.add('hidden'); - } - // Display the close button, if dismissible - if (dismissable) { - errorNotDismissible.classList.add('hidden'); - errorClose.classList.remove("hidden"); - } else { - errorNotDismissible.classList.remove('hidden'); - errorClose.classList.add("hidden"); - } - - // Show the dialog - errorDialog.classList.remove("hidden"); -} - -export function reportCaughtError({error, cause, componentStack}) { - reportError({ title: "Caught Error", error, componentStack, dismissable: true}); } -export function reportUncaughtError({error, cause, componentStack}) { - reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +export function onUncaughtErrorProd(error, errorInfo) { + reportError({ type: "Uncaught", error, errorInfo }); } -export function reportRecoverableError({error, cause, componentStack}) { - reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +export function onRecoverableErrorProd(error, errorInfo) { + reportError({ type: "Recoverable", error, errorInfo }); } ``` ```js src/index.js active import { hydrateRoot } from "react-dom/client"; import App from "./App.js"; -import {reportUncaughtError} from "./reportError"; -import "./styles.css"; -import {renderToString} from 'react-dom/server'; +import { + onCaughtErrorProd, + onRecoverableErrorProd, + onUncaughtErrorProd, +} from "./reportError"; const container = document.getElementById("root"); -const root = hydrateRoot(container, , { - onUncaughtError: (error, errorInfo) => { - if (error.message !== 'Known error') { - reportUncaughtError({ - error, - componentStack: errorInfo.componentStack - }); - } - } +hydrateRoot(container, , { + // Keep in mind to remove these options in development to leverage + // React's default handlers or implement your own overlay for development. + // The handlers are only specfied unconditionally here for demonstration purposes. + onCaughtError: onCaughtErrorProd, + onRecoverableError: onRecoverableErrorProd, + onUncaughtError: onUncaughtErrorProd, }); ``` ```js src/App.js -import { useState } from 'react'; +import { Component, useState } from "react"; -export default function App() { - const [throwError, setThrowError] = useState(false); - - if (throwError) { - foo.bar = 'baz'; - } - - return ( -
- This error shows the error dialog: - -
- ); +function Boom() { + foo.bar = "baz"; } -``` -
- - -### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/} - -By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option for errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary): +class ErrorBoundary extends Component { + state = { hasError: false }; -```js [[1, 7, "onCaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]] -import { hydrateRoot } from 'react-dom/client'; - -const root = hydrateRoot( - document.getElementById('root'), - , - { - onCaughtError: (error, errorInfo) => { - console.error( - 'Caught error', - error, - errorInfo.componentStack - ); - } + static getDerivedStateFromError(error) { + return { hasError: true }; } -); -root.render(); -``` - -The onCaughtError option is a function called with two arguments: -1. The error that was caught by the boundary. -2. An errorInfo object that contains the componentStack of the error. - -You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging: - - - -```html public/index.html hidden - - - - My app - - - - - -
This error will not show the error dialog:This error will show the error dialog:
- - -``` - -```css src/styles.css active -label, button { display: block; margin-bottom: 20px; } -html, body { min-height: 300px; } - -#error-dialog { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: white; - padding: 15px; - opacity: 0.9; - text-wrap: wrap; - overflow: scroll; -} - -.text-red { - color: red; -} - -.-mb-20 { - margin-bottom: -20px; -} - -.mb-0 { - margin-bottom: 0; -} - -.mb-10 { - margin-bottom: 10px; -} - -pre { - text-wrap: wrap; -} - -pre.nowrap { - text-wrap: nowrap; -} - -.hidden { - display: none; -} -``` - -```js src/reportError.js hidden -function reportError({ title, error, componentStack, dismissable }) { - const errorDialog = document.getElementById("error-dialog"); - const errorTitle = document.getElementById("error-title"); - const errorMessage = document.getElementById("error-message"); - const errorBody = document.getElementById("error-body"); - const errorComponentStack = document.getElementById("error-component-stack"); - const errorStack = document.getElementById("error-stack"); - const errorClose = document.getElementById("error-close"); - const errorCause = document.getElementById("error-cause"); - const errorCauseMessage = document.getElementById("error-cause-message"); - const errorCauseStack = document.getElementById("error-cause-stack"); - const errorNotDismissible = document.getElementById("error-not-dismissible"); - - // Set the title - errorTitle.innerText = title; - - // Display error message and body - const [heading, body] = error.message.split(/\n(.*)/s); - errorMessage.innerText = heading; - if (body) { - errorBody.innerText = body; - } else { - errorBody.innerText = ''; - } - - // Display component stack - errorComponentStack.innerText = componentStack; - - // Display the call stack - // Since we already displayed the message, strip it, and the first Error: line. - errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; - - // Display the cause, if available - if (error.cause) { - errorCauseMessage.innerText = error.cause.message; - errorCauseStack.innerText = error.cause.stack; - errorCause.classList.remove('hidden'); - } else { - errorCause.classList.add('hidden'); - } - // Display the close button, if dismissible - if (dismissable) { - errorNotDismissible.classList.add('hidden'); - errorClose.classList.remove("hidden"); - } else { - errorNotDismissible.classList.remove('hidden'); - errorClose.classList.add("hidden"); - } - - // Show the dialog - errorDialog.classList.remove("hidden"); -} - -export function reportCaughtError({error, cause, componentStack}) { - reportError({ title: "Caught Error", error, componentStack, dismissable: true}); -} - -export function reportUncaughtError({error, cause, componentStack}) { - reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); -} - -export function reportRecoverableError({error, cause, componentStack}) { - reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); -} -``` - -```js src/index.js active -import { hydrateRoot } from "react-dom/client"; -import App from "./App.js"; -import {reportCaughtError} from "./reportError"; -import "./styles.css"; - -const container = document.getElementById("root"); -const root = hydrateRoot(container, , { - onCaughtError: (error, errorInfo) => { - if (error.message !== 'Known error') { - reportCaughtError({ - error, - componentStack: errorInfo.componentStack - }); + render() { + if (this.state.hasError) { + return

Something went wrong.

; } + return this.props.children; } -}); -``` - -```js src/App.js -import { useState } from 'react'; -import { ErrorBoundary } from "react-error-boundary"; +} export default function App() { - const [error, setError] = useState(null); - - function handleUnknown() { - setError("unknown"); - } + const [triggerUncaughtError, settriggerUncaughtError] = useState(false); + const [triggerCaughtError, setTriggerCaughtError] = useState(false); - function handleKnown() { - setError("known"); - } - return ( <> - { - setError(null); - }} - > - {error != null && } - This error will not show the error dialog: - - This error will show the error dialog: - - - + + {triggerUncaughtError && } + + {triggerCaughtError && ( + + + + )} ); } - -function fallbackRender({ resetErrorBoundary }) { - return ( -
-

Error Boundary

-

Something went wrong.

- -
- ); -} - -function Throw({error}) { - if (error === "known") { - throw new Error('Known error') - } else { - foo.bar = 'baz'; - } -} -``` - -```json package.json hidden -{ - "dependencies": { - "react": "19.0.0-rc-3edc000d-20240926", - "react-dom": "19.0.0-rc-3edc000d-20240926", - "react-scripts": "^5.0.0", - "react-error-boundary": "4.0.3" - }, - "main": "/index.js" -} -``` - -
- -### Show a dialog for recoverable hydration mismatch errors {/*show-a-dialog-for-recoverable-hydration-mismatch-errors*/} - -When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to `console.error`. To override this behavior, you can provide the optional `onRecoverableError` root option: - -```js [[1, 7, "onRecoverableError"], [2, 7, "error", 1], [3, 11, "error.cause", 1], [4, 7, "errorInfo"], [5, 12, "componentStack"]] -import { hydrateRoot } from 'react-dom/client'; - -const root = hydrateRoot( - document.getElementById('root'), - , - { - onRecoverableError: (error, errorInfo) => { - console.error( - 'Caught error', - error, - error.cause, - errorInfo.componentStack - ); - } - } -); ``` -The onRecoverableError option is a function called with two arguments: - -1. The error React throws. Some errors may include the original cause as error.cause. -2. An errorInfo object that contains the componentStack of the error. - -You can use the `onRecoverableError` root option to display error dialogs for hydration mismatches: - - - ```html public/index.html hidden @@ -930,226 +500,12 @@ You can use the `onRecoverableError` root option to display error dialogs for hy - - -
Server
+
Server content before hydration.
``` - -```css src/styles.css active -label, button { display: block; margin-bottom: 20px; } -html, body { min-height: 300px; } - -#error-dialog { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: white; - padding: 15px; - opacity: 0.9; - text-wrap: wrap; - overflow: scroll; -} - -.text-red { - color: red; -} - -.-mb-20 { - margin-bottom: -20px; -} - -.mb-0 { - margin-bottom: 0; -} - -.mb-10 { - margin-bottom: 10px; -} - -pre { - text-wrap: wrap; -} - -pre.nowrap { - text-wrap: nowrap; -} - -.hidden { - display: none; -} -``` - -```js src/reportError.js hidden -function reportError({ title, error, componentStack, dismissable }) { - const errorDialog = document.getElementById("error-dialog"); - const errorTitle = document.getElementById("error-title"); - const errorMessage = document.getElementById("error-message"); - const errorBody = document.getElementById("error-body"); - const errorComponentStack = document.getElementById("error-component-stack"); - const errorStack = document.getElementById("error-stack"); - const errorClose = document.getElementById("error-close"); - const errorCause = document.getElementById("error-cause"); - const errorCauseMessage = document.getElementById("error-cause-message"); - const errorCauseStack = document.getElementById("error-cause-stack"); - const errorNotDismissible = document.getElementById("error-not-dismissible"); - - // Set the title - errorTitle.innerText = title; - - // Display error message and body - const [heading, body] = error.message.split(/\n(.*)/s); - errorMessage.innerText = heading; - if (body) { - errorBody.innerText = body; - } else { - errorBody.innerText = ''; - } - - // Display component stack - errorComponentStack.innerText = componentStack; - - // Display the call stack - // Since we already displayed the message, strip it, and the first Error: line. - errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; - - // Display the cause, if available - if (error.cause) { - errorCauseMessage.innerText = error.cause.message; - errorCauseStack.innerText = error.cause.stack; - errorCause.classList.remove('hidden'); - } else { - errorCause.classList.add('hidden'); - } - // Display the close button, if dismissible - if (dismissable) { - errorNotDismissible.classList.add('hidden'); - errorClose.classList.remove("hidden"); - } else { - errorNotDismissible.classList.remove('hidden'); - errorClose.classList.add("hidden"); - } - - // Show the dialog - errorDialog.classList.remove("hidden"); -} - -export function reportCaughtError({error, cause, componentStack}) { - reportError({ title: "Caught Error", error, componentStack, dismissable: true}); -} - -export function reportUncaughtError({error, cause, componentStack}) { - reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); -} - -export function reportRecoverableError({error, cause, componentStack}) { - reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); -} -``` - -```js src/index.js active -import { hydrateRoot } from "react-dom/client"; -import App from "./App.js"; -import {reportRecoverableError} from "./reportError"; -import "./styles.css"; - -const container = document.getElementById("root"); -const root = hydrateRoot(container, , { - onRecoverableError: (error, errorInfo) => { - reportRecoverableError({ - error, - cause: error.cause, - componentStack: errorInfo.componentStack - }); - } -}); -``` - -```js src/App.js -import { useState } from 'react'; -import { ErrorBoundary } from "react-error-boundary"; - -export default function App() { - const [error, setError] = useState(null); - - function handleUnknown() { - setError("unknown"); - } - - function handleKnown() { - setError("known"); - } - - return ( - {typeof window !== 'undefined' ? 'Client' : 'Server'} - ); -} - -function fallbackRender({ resetErrorBoundary }) { - return ( -
-

Error Boundary

-

Something went wrong.

- -
- ); -} - -function Throw({error}) { - if (error === "known") { - throw new Error('Known error') - } else { - foo.bar = 'baz'; - } -} -``` - -```json package.json hidden -{ - "dependencies": { - "react": "19.0.0-rc-3edc000d-20240926", - "react-dom": "19.0.0-rc-3edc000d-20240926", - "react-scripts": "^5.0.0", - "react-error-boundary": "4.0.3" - }, - "main": "/index.js" -} -``` -
## Troubleshooting {/*troubleshooting*/} diff --git a/src/content/reference/react-dom/createPortal.md b/src/content/reference/react-dom/createPortal.md index 58ff6651e..0196899ba 100644 --- a/src/content/reference/react-dom/createPortal.md +++ b/src/content/reference/react-dom/createPortal.md @@ -350,7 +350,7 @@ p { const [popupContainer, setPopupContainer] = useState(null); ``` -При створенні віджета з допомогою стороннього пакету, зберігайте повернений віджетом DOM-вузол, щоб мати змогу рендерити контент в середину нього: +Під час створення стороннього віджета зберігайте повернений віджетом DOM-вузол, щоб мати змогу рендерити контент в середину нього: ```js {5-6} useEffect(() => { diff --git a/src/content/reference/react/Component.md b/src/content/reference/react/Component.md index 99fa17986..0821d1593 100644 --- a/src/content/reference/react/Component.md +++ b/src/content/reference/react/Component.md @@ -1273,7 +1273,11 @@ By default, if your application throws an error during rendering, React will rem 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} + With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development. + +```js {9-12,14-27} +import * as React from 'react'; + class ErrorBoundary extends React.Component { constructor(props) { super(props); @@ -1286,12 +1290,18 @@ class ErrorBoundary extends React.Component { } 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); + logErrorToMyService( + error, + // Example "componentStack": + // in ComponentThatThrows (created by App) + // in ErrorBoundary (created by App) + // in div (created by App) + // in App + info.componentStack, + // Only available in react@canary. + // Warning: Owner Stack is not available in production. + React.captureOwnerStack(), + ); } render() { diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md new file mode 100644 index 000000000..f8ed21a8c --- /dev/null +++ b/src/content/reference/react/captureOwnerStack.md @@ -0,0 +1,452 @@ +--- +title: captureOwnerStack +--- + + + +The `captureOwnerStack` API is currently only available in React's Canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + + + + +`captureOwnerStack` reads the current Owner Stack in development and returns it as a string if available. + +```js +const stack = captureOwnerStack(); +``` + + + + + +--- + +## Reference {/*reference*/} + +### `captureOwnerStack()` {/*captureownerstack*/} + +Call `captureOwnerStack` to get the current Owner Stack. + +```js {5,5} +import * as React from 'react'; + +function Component() { + if (process.env.NODE_ENV !== 'production') { + const ownerStack = React.captureOwnerStack(); + console.log(ownerStack); + } +} +``` + +#### Parameters {/*parameters*/} + +`captureOwnerStack` does not take any parameters. + +#### Returns {/*returns*/} + +`captureOwnerStack` returns `string | null`. + +Owner Stacks are available in +- Component render +- Effects (e.g. `useEffect`) +- React's event handlers (e.g. ` +
+ +
+ + + +``` + +```js src/errorOverlay.js + +export function onConsoleError({ consoleMessage, ownerStack }) { + const errorDialog = document.getElementById("error-dialog"); + const errorBody = document.getElementById("error-body"); + const errorOwnerStack = document.getElementById("error-owner-stack"); + + // Display console.error() message + errorBody.innerText = consoleMessage; + + // Display owner stack + errorOwnerStack.innerText = ownerStack; + + // Show the dialog + errorDialog.classList.remove("hidden"); +} +``` + +```js src/index.js active +import { captureOwnerStack } from "react"; +import { createRoot } from "react-dom/client"; +import App from './App'; +import { onConsoleError } from "./errorOverlay"; +import './styles.css'; + +const originalConsoleError = console.error; +console.error = function patchedConsoleError(...args) { + originalConsoleError.apply(console, args); + const ownerStack = captureOwnerStack(); + onConsoleError({ + // Keep in mind that in a real application, console.error can be + // called with multiple arguments which you should account for. + consoleMessage: args[0], + ownerStack, + }); +}; + +const container = document.getElementById("root"); +createRoot(container).render(); +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +```js src/App.js +function Component() { + return ; +} + +export default function App() { + return ; +} +``` + + + +## Troubleshooting {/*troubleshooting*/} + +### The Owner Stack is `null` {/*the-owner-stack-is-null*/} + +The call of `captureOwnerStack` happened outside of a React controlled function e.g. in a `setTimeout` callback, after a `fetch` call or in a custom DOM event handler. During render, Effects, React event handlers, and React error handlers (e.g. `hydrateRoot#options.onCaughtError`) Owner Stacks should be available. + +In the example below, clicking the button will log an empty Owner Stack because `captureOwnerStack` was called during a custom DOM event handler. The Owner Stack must be captured earlier e.g. by moving the call of `captureOwnerStack` into the Effect body. + + +```js +import {captureOwnerStack, useEffect} from 'react'; + +export default function App() { + useEffect(() => { + // Should call `captureOwnerStack` here. + function handleEvent() { + // Calling it in a custom DOM event handler is too late. + // The Owner Stack will be `null` at this point. + console.log('Owner Stack: ', captureOwnerStack()); + } + + document.addEventListener('click', handleEvent); + + return () => { + document.removeEventListener('click', handleEvent); + } + }) + + return ; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "latest" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + + + +### `captureOwnerStack` is not available {/*captureownerstack-is-not-available*/} + +`captureOwnerStack` is only exported in development builds. It will be `undefined` in production builds. If `captureOwnerStack` is used in files that are bundled for production and development, you should conditionally access it from a namespace import. + +```js +// Don't use named imports of `captureOwnerStack` in files that are bundled for development and production. +import {captureOwnerStack} from 'react'; +// Use a namespace import instead and access `captureOwnerStack` conditionally. +import * as React from 'react'; + +if (process.env.NODE_ENV !== 'production') { + const ownerStack = React.captureOwnerStack(); + console.log('Owner Stack', ownerStack); +} +``` diff --git a/src/content/reference/react/useContext.md b/src/content/reference/react/useContext.md index c7c929465..3617992c8 100644 --- a/src/content/reference/react/useContext.md +++ b/src/content/reference/react/useContext.md @@ -44,7 +44,7 @@ function MyComponent() { * Eлементи-провайдери не впливають на виклик `useContext()` у компоненті, з якого й повертаються. Відповідний `` **повинен бути розташований *вище*** компонента, що викликає `useContext()`. * React **автоматично оновлює** всі дочірні компоненти, які використовують певний контекст, починаючи з провайдера, що отримує змінене значення `value`. Попереднє та наступне значення порівнюються за допомогою [`Object.is`](https://webdoky.org/uk/docs/Web/JavaScript/Reference/Global_Objects/Object/is/). Пропуск повторних рендерів за допомогою [`memo`](/reference/react/memo) не заважає дочірнім компонентам отримувати оновлене значення контексту. -* Якщо ваша система збірки створює дублікати модулів у вихідному коді (що може статися через символьні посилання), це може порушити контекст. Передавання через контекст працює тільки у разі, якщо `SomeContext` для надання контексту та `SomeContext` для його зчитування є ***точно* тим самим об'єктом**, що визначається порівнянням `===`. +* Якщо ваша система збирання створює дублікати модулів у вихідному коді (що може статися через символьні посилання), це може порушити контекст. Передавання через контекст працює тільки у разі, якщо `SomeContext` для надання контексту та `SomeContext` для його зчитування є ***точно* тим самим об'єктом**, що визначається порівнянням `===`. --- @@ -1351,7 +1351,7 @@ function MyApp() { 1. Ви розміщуєте `` у тому ж компоненті або нижче, ніж компонент, де викликано `useContext()`. Перемістіть `` *вище і зовні* компонента, який викликає `useContext()`. 2. Можливо, ви забули обгорнути свій компонент у `` або розмістили його в іншій, ніж задумали, частині дерева. Переконайтеся за допомогою [React DevTools](/learn/react-developer-tools), що ієрархія компонентів налаштована правильно. -3. Можливо, ви зіткнулися з проблемою збірки, через яку `SomeContext` із компонента-провайдера і `SomeContext` компонента-читача є різними об'єктами. Це може статися, наприклад, якщо ви використовуєте символьні посилання. Можете перевірити це, присвоївши їх глобальним змінним, як-от `window.SomeContext1` і `window.SomeContext2`, а потім порівняти `window.SomeContext1 === window.SomeContext2` в консолі. Якщо вони не тотожні, виправте цю проблему на рівні збірки. +3. Можливо, ви зіткнулися з проблемою збирання, через яку `SomeContext` із компонента-провайдера і `SomeContext` компонента-читача є різними об'єктами. Це може статися, наприклад, якщо ви використовуєте символьні посилання. Можете перевірити це, присвоївши їх глобальним змінним, як-от `window.SomeContext1` і `window.SomeContext2`, а потім порівняти `window.SomeContext1 === window.SomeContext2` в консолі. Якщо вони не тотожні, виправте цю проблему на рівні збирання. ### Я завжди отримую `undefined` із контексту, хоча початкове значення інше {/*i-am-always-getting-undefined-from-my-context-although-the-default-value-is-different*/} diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json index bc71edbba..5147dcd0f 100644 --- a/src/sidebarLearn.json +++ b/src/sidebarLearn.json @@ -25,13 +25,23 @@ "path": "/learn/installation", "routes": [ { - "title": "Початок нового React-проєкту", - "path": "/learn/start-a-new-react-project" + "title": "Створення React-застосунку", + "path": "/learn/creating-a-react-app" + }, + { + "title": "Побудова React-фреймворку", + "path": "/learn/building-a-react-framework" }, { "title": "Інтеграція React у наявний проєкт", "path": "/learn/add-react-to-an-existing-project" - }, + } + ] + }, + { + "title": "Налаштування", + "path": "/learn/setup", + "routes": [ { "title": "Налаштування редактора", "path": "/learn/editor-setup" @@ -41,7 +51,7 @@ "path": "/learn/typescript" }, { - "title": "Інструменти React розробника", + "title": "Інструменти розробника React", "path": "/learn/react-developer-tools" }, { diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 00917ad56..a044c9f5b 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -147,6 +147,11 @@ "title": "experimental_taintUniqueValue", "path": "/reference/react/experimental_taintUniqueValue", "version": "canary" + }, + { + "title": "captureOwnerStack", + "path": "/reference/react/captureOwnerStack", + "version": "canary" } ] }, @@ -304,7 +309,6 @@ "title": "prerenderToNodeStream", "path": "/reference/react-dom/static/prerenderToNodeStream" } - ] }, { @@ -398,4 +402,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 440b38510..9d72e01bc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,12 +22,18 @@ "isolatedModules": true, "jsx": "preserve", "baseUrl": "src", - "incremental": true + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] }, "include": [ "next-env.d.ts", "src/**/*.ts", - "src/**/*.tsx" + "src/**/*.tsx", + ".next/types/**/*.ts" ], "exclude": [ "node_modules" diff --git a/vercel.json b/vercel.json index e08fda745..fdd63f5bb 100644 --- a/vercel.json +++ b/vercel.json @@ -164,6 +164,11 @@ "destination": "https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html", "permanent": false }, + { + "source": "/link/cra", + "destination": "/blog/2025/02/14/sunsetting-create-react-app", + "permanent": false + }, { "source": "/warnings/version-mismatch", "destination": "/warnings/invalid-hook-call-warning#mismatching-versions-of-react-and-react-dom", @@ -224,6 +229,11 @@ "destination": "https://18.react.dev/reference/react-dom/server/renderToNodeStream", "permanent": true }, + { + "source": "/learn/start-a-new-react-project", + "destination": "/learn/creating-a-react-app", + "permanent": true + }, { "source": "/blog/2024/04/25/react-19", "destination": "/blog/2024/12/05/react-19",