diff --git a/.env.production b/.env.production index d25eb7dd4..e403f96b6 100644 --- a/.env.production +++ b/.env.production @@ -1 +1 @@ -NEXT_PUBLIC_GA_TRACKING_ID = 'UA-41298772-4' \ No newline at end of file +NEXT_PUBLIC_GA_TRACKING_ID = 'G-B1E83PJ3RT' \ No newline at end of file diff --git a/package.json b/package.json index 472ef79c9..b5e07d70a 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "classnames": "^2.2.6", "date-fns": "^2.16.1", "debounce": "^1.2.1", - "ga-lite": "^2.1.4", "github-slugger": "^1.3.0", "next": "^13.4.1", "next-remote-watch": "^1.0.0", diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx index babef0993..b118e9ed7 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -4,7 +4,6 @@ import {useState} from 'react'; import {useRouter} from 'next/router'; -import {ga} from '../../utils/analytics'; export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { const {asPath} = useRouter(); @@ -48,14 +47,12 @@ const thumbsDownIcon = ( function sendGAEvent(isPositive: boolean) { // Fragile. Don't change unless you've tested the network payload // and verified that the right events actually show up in GA. - ga( - 'send', - 'event', - 'button', - 'feedback', - window.location.pathname, - isPositive ? '1' : '0' - ); + // @ts-ignore + gtag('event', 'feedback', { + event_category: 'button', + event_label: window.location.pathname, + value: isPositive ? 1 : 0, + }); } function SendFeedback({onSubmit}: {onSubmit: () => void}) { diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index 5f2bc8f04..a823c189b 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -28,7 +28,12 @@ interface PageProps { children: React.ReactNode; toc: Array; routeTree: RouteItem; - meta: {title?: string; canary?: boolean; description?: string}; + meta: { + title?: string; + titleForTitleTag?: string; + canary?: boolean; + description?: string; + }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; } @@ -107,6 +112,7 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { <> { if (lintErrors.length === 0) { diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx index 79f19f87c..bb6c50ffd 100644 --- a/src/components/Seo.tsx +++ b/src/components/Seo.tsx @@ -9,6 +9,7 @@ import {siteConfig} from '../siteConfig'; export interface SeoProps { title: string; + titleForTitleTag: undefined | string; description?: string; image?: string; // jsonld?: JsonLDType | Array; @@ -36,7 +37,7 @@ function getDomain(languageCode: string): string { export const Seo = withRouter( ({ title, - description = 'The library for web and native user interfaces', + titleForTitleTag, image = '/images/og-default.png', router, children, @@ -47,14 +48,20 @@ export const Seo = withRouter( const canonicalUrl = `https://${siteDomain}${ router.asPath.split(/[\?\#]/)[0] }`; - const pageTitle = isHomePage ? title : title + ' – React'; + // Allow setting a different title for Google results + const pageTitle = + (titleForTitleTag ?? title) + (isHomePage ? '' : ' – React'); // Twitter's meta parser is not very good. const twitterTitle = pageTitle.replace(/[<>]/g, ''); + let description = isHomePage + ? 'React est une bibliothèque pour des interfaces utilisateurs web et natives. Construisez des interfaces utilisateurs à partir de briques individuelles appelées composants, écrites en JavaScript. React est conçu pour vous permettre de combiner sans effort des composants produits par des acteurs distincts, qu’il s’agisse de personnes, d’équipes ou d’organisations entières.' + : 'La bibliothèque pour des interfaces utilisateurs web et natives'; return ( {title != null && {pageTitle}} - {description != null && ( + {isHomePage && ( + // Let Google figure out a good description for each page. )} diff --git a/src/content/learn/describing-the-ui.md b/src/content/learn/describing-the-ui.md index fd7635efd..ed9d1a4c7 100644 --- a/src/content/learn/describing-the-ui.md +++ b/src/content/learn/describing-the-ui.md @@ -529,13 +529,21 @@ React utilise des arbres pour modéliser les relations entre les composants ou l Un arbre de rendu React représente les relations parent-enfants entre les composants. -Un exemple d’arbre de rendu React. + + +Un exemple d’arbre de rendu React. + + Les composants proches du haut de l'arbre, près du composant racine, sont considérés comme des composants de haut niveau. Les composants qui n'ont pas de composants enfants sont qualifiés de composants feuilles. La catégorisation des composants aide à comprendre le flux de données et les performances de rendu. Une autre manière utile de percevoir votre application consiste à modéliser les relations entre les modules JavaScript. Nous parlons alors d'arbre de dépendances de modules. -Un exemple d’arbre de dépendances de modules. + + +Un exemple d’arbre de dépendances de modules. + + On utilise souvent un arbre de dépendances dans les outils de *build* pour *bundler* tout le code JavaScript que le client devra télécharger pour assurer le rendu. Un *bundle* massif nuira à l'expérience utilisateur des applis React. Comprendre l'arborescence des dépendances de modules aide à déboguer ces problèmes. diff --git a/src/content/learn/reacting-to-input-with-state.md b/src/content/learn/reacting-to-input-with-state.md index b537d41b8..75f71c3a4 100644 --- a/src/content/learn/reacting-to-input-with-state.md +++ b/src/content/learn/reacting-to-input-with-state.md @@ -84,7 +84,7 @@ function submitForm(answer) { // Imaginez que ça fait une requête réseau return new Promise((resolve, reject) => { setTimeout(() => { - if (answer.toLowerCase() == 'istanbul') { + if (answer.toLowerCase() === 'istanbul') { resolve(); } else { reject(new Error('Bonne idée, mais mauvaise réponse. Réessayez !')); 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 31e11aefb..e3991dd62 100644 --- a/src/content/learn/understanding-your-ui-as-a-tree.md +++ b/src/content/learn/understanding-your-ui-as-a-tree.md @@ -252,7 +252,7 @@ Avec le rendu conditionnel, d'un rendu à l'autre, l'arbre de rendu peut différ Dans cet exemple, selon la valeur de `inspiration.type`, nous pouvons afficher soit `` soit ``. L'arbre de rendu peut être différent d'une passe de rendu à l'autre. -Même si les arbres de rendu peuvent varier d'un rendu à l'autre, ces arbres restent utiles pour identifier les composants racine et feuilles d'une appli React. Les composants de haut niveau sont ceux les plus proches du composant racine, et peuvent impacter la performance de tous les composants en-dessous d'eux. Ce sont souvent eux qui ont la plus forte complexité. Les composants feuilles sont vers le bas de l'arbre, n'ont pas de composants enfants et refont fréquemment leur rendu. +Même si les arbres de rendu peuvent varier d'un rendu à l'autre, ces arbres restent utiles pour identifier les composants *racines* et *feuilles* d'une appli React. Les composants de haut niveau sont ceux les plus proches du composant racine, et peuvent impacter la performance de tous les composants en-dessous d'eux. Ce sont souvent eux qui ont la plus forte complexité. Les composants feuilles sont vers le bas de l'arbre, n'ont pas de composants enfants et refont fréquemment leur rendu. Il est utile de bien identifier ces catégories de composants pour comprendre le flux de données et les performances de votre appli. diff --git a/src/content/reference/react-dom/components/form.md b/src/content/reference/react-dom/components/form.md new file mode 100644 index 000000000..7c6023220 --- /dev/null +++ b/src/content/reference/react-dom/components/form.md @@ -0,0 +1,435 @@ +--- +title: "
" +canary: true +--- + + + +React's extensions to `` are currently only available in React's canary and experimental channels. In stable releases of React `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + + + + + +The [built-in browser `` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) lets you create interactive controls for submitting information. + +```js + + + + +``` + +
+ + + +--- + +## Reference {/*reference*/} + +### `
` {/*form*/} + +To create interactive controls for submitting information, render the [built-in browser `` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form). + +```js + + + +
+``` + +[See more examples below.](#usage) + +#### Props {/*props*/} + +`
` supports all [common element props.](/reference/react-dom/components/common#props) + +[`action`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#action): a URL or function. When a URL is passed to `action` the form will behave like the HTML form component. When a function is passed to `action` the function will handle the form submission. The function passed to `action` may be async and will be called with a single argument containing the [form data](https://developer.mozilla.org/en-US/docs/Web/API/FormData) of the submitted form. The `action` prop can be overridden by a `formAction` attribute on a ` +
+ ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + + + +### Handle form submission with a Server Action {/*handle-form-submission-with-a-server-action*/} + +Render a `
` with an input and submit button. Pass a server action (a function marked with [`'use server'`](/reference/react/use-server)) to the `action` prop of form to run the function when the form is submitted. + +Passing a server action to `` allow users to submit forms without JavaScript enabled or before the code has loaded. This is beneficial to users who have a slow connection, device, or have JavaScript disabled and is similar to the way forms work when a URL is passed to the `action` prop. + +You can use hidden form fields to provide data to the ``'s action. The server action will be called with the hidden form field data as an instance of [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData). + +```jsx +import { updateCart } from './lib.js'; + +function AddToCart({productId}) { + async function addToCart(formData) { + 'use server' + const productId = formData.get('productId') + await updateCart(productId) + } + return ( + + + +
+ + ); +} +``` + +In lieu of using hidden form fields to provide data to the `
`'s action, you can call the `bind` method to supply it with extra arguments. This will bind a new argument (`productId`) to the function in addition to the `formData` that is passed as a argument to the function. + +```jsx [[1, 8, "bind"], [2,8, "productId"], [2,4, "productId"], [3,4, "formData"]] +import { updateCart } from './lib.js'; + +function AddToCart({productId}) { + async function addToCart(productId, formData) { + "use server"; + await updateCart(productId) + } + const addProductToCart = addToCart.bind(null, productId); + return ( + + +
+ ); +} +``` + +When `
` is rendered by a [Server Component](/reference/react/use-client), and a [Server Action](/reference/react/use-server) is passed to the ``'s `action` prop, the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). + +### Display a pending state during form submission {/*display-a-pending-state-during-form-submission*/} +To display a pending state when a form is being submitted, you can call the `useFormStatus` Hook in a component rendered in a `` and read the `pending` property returned. + +Here, we use the `pending` property to indicate the form is submitting. + + + +```js App.js +import { useFormStatus } from "react-dom"; +import { submitForm } from "./actions.js"; + +function Submit() { + const { pending } = useFormStatus(); + return ( + + ); +} + +function Form({ action }) { + return ( + + + + ); +} + +export default function App() { + return
; +} +``` + +```js actions.js hidden +export async function submitForm(query) { + await new Promise((res) => setTimeout(res, 1000)); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + + +To learn more about the `useFormStatus` Hook see the [reference documentation](/reference/react-dom/hooks/useFormStatus). + +### Optimistically updating form data {/*optimistically-updating-form-data*/} +The `useOptimistic` Hook provides a way to optimistically update the user interface before a background operation, like a network request, completes. In the context of forms, this technique helps to make apps feel more responsive. When a user submits a form, instead of waiting for the server's response to reflect the changes, the interface is immediately updated with the expected outcome. + +For example, when a user types a message into the form and hits the "Send" button, the `useOptimistic` Hook allows the message to immediately appear in the list with a "Sending..." label, even before the message is actually sent to a server. This "optimistic" approach gives the impression of speed and responsiveness. The form then attempts to truly send the message in the background. Once the server confirms the message has been received, the "Sending..." label is removed. + + + + +```js App.js +import { useOptimistic, useState, useRef } from "react"; +import { deliverMessage } from "./actions.js"; + +function Thread({ messages, sendMessage }) { + const formRef = useRef(); + async function formAction(formData) { + addOptimisticMessage(formData.get("message")); + formRef.current.reset(); + await sendMessage(formData); + } + const [optimisticMessages, addOptimisticMessage] = useOptimistic( + messages, + (state, newMessage) => [ + ...state, + { + text: newMessage, + sending: true + } + ] + ); + + return ( + <> + {optimisticMessages.map((message, index) => ( +
+ {message.text} + {!!message.sending && (Sending...)} +
+ ))} + + + + + + ); +} + +export default function App() { + const [messages, setMessages] = useState([ + { text: "Hello there!", sending: false, key: 1 } + ]); + async function sendMessage(formData) { + const sentMessage = await deliverMessage(formData.get("message")); + setMessages([...messages, { text: sentMessage }]); + } + return ; +} +``` + +```js actions.js +export async function deliverMessage(message) { + await new Promise((res) => setTimeout(res, 1000)); + return message; +} +``` + + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + +
+ +[//]: # 'Uncomment the next line, and delete this line after the `useOptimisitc` reference documentatino page is published' +[//]: # 'To learn more about the `useOptimistic` Hook see the [reference documentation](/reference/react/hooks/useOptimistic).' + +### Handling form submission errors {/*handling-form-submission-errors*/} + +In some cases the function called by a `
`'s `action` prop throw an error. You can handle these errors by wrapping `` in an Error Boundary. If the function called by a ``'s `action` prop throws an error, the fallback for the error boundary will be displayed. + + + +```js App.js +import { ErrorBoundary } from "react-error-boundary"; + +export default function Search() { + function search() { + throw new Error("search error"); + } + return ( + There was an error while submitting the form

} + > + + + + +
+ ); +} + +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + +
+ +### Display a form submission error without JavaScript {/*display-a-form-submission-error-without-javascript*/} + +Displaying a form submission error message before the JavaScript bundle loads for progressive enhancement requires that: + +1. `
` be rendered by a [Server Component](/reference/react/use-client) +1. the function passed to the ``'s `action` prop be a [Server Action](/reference/react/use-server) +1. the `useFormState` Hook be used to display the error message + +`useFormState` takes two parameters: a [Server Action](/reference/react/use-server) and an initial state. `useFormState` returns two values, a state variable and an action. The action returned by `useFormState` should be passed to the `action` prop of the form. The state variable returned by `useFormState` can be used to displayed an error message. The value returned by the [Server Action](/reference/react/use-server) passed to `useFormState` will be used to update the state variable. + + + +```js App.js +import { useFormState } from "react-dom"; +import { signUpNewUser } from "./api"; + +export default function Page() { + async function signup(prevState, formData) { + "use server"; + const email = formData.get("email"); + try { + await signUpNewUser(email); + alert(`Added "${email}"`); + } catch (err) { + return err.toString(); + } + } + const [message, formAction] = useFormState(signup, null); + return ( + <> +

Signup for my newsletter

+

Signup with the same email twice to see an error

+ + + + + {!!message &&

{message}

} + + + ); +} +``` + +```js api.js hidden +let emails = []; + +export async function signUpNewUser(newEmail) { + if (emails.includes(newEmail)) { + throw new Error("This email address has already been added"); + } + emails.push(newEmail); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-6db7f4209-20231021", + "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react-scripts": "^5.0.0" + }, + "main": "/index.js", + "devDependencies": {} +} +``` + +
+ +Learn more about updating state from a form action with the [`useFormState`](/reference/react-dom/hooks/useFormState) docs + +### Handling multiple submission types {/*handling-multiple-submission-types*/} + +Forms can be designed to handle multiple submission actions based on the button pressed by the user. Each button inside a form can be associated with a distinct action or behavior by setting the `formAction` prop. + +When a user taps a specific button, the form is submitted, and a corresponding action, defined by that button's attributes and action, is executed. For instance, a form might submit an article for review by default but have a separate button with `formAction` set to save the article as a draft. + + + +```js App.js +export default function Search() { + function publish(formData) { + const content = formData.get("content"); + const button = formData.get("button"); + alert(`'${content}' was published with the '${button}' button`); + } + + function save(formData) { + const content = formData.get("content"); + alert(`Your draft of '${content}' has been saved!`); + } + + return ( +
+