mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-22 03:42:14 +00:00
* Ignore braces when building Sandpack file map
Previously, `createFileMap` split the MDX meta string on spaces and assumed the first token was the filename. Once we prefixed code fences with `{expectedErrors: ...}`, it would incorrectly parse the meta and crash.
This PR updates createFileMap to skip tokens in the meta containing a start and end brace pair (using a stack to ensure we close on the correct brace) while tokenizing the meta string as expected.
Test plan: pages reported in #7994 no longer crash on the next PR
Closes #7994
* Restore lint corrections from #7989
Reverts the revert now that we've fixed the bug. These pages should no longer crash:
https://react-dev-git-pr7997-fbopensource.vercel.app/learn/referencing-values-with-refs
https://react-dev-git-pr7997-fbopensource.vercel.app/learn/synchronizing-with-effects
https://react-dev-git-pr7997-fbopensource.vercel.app/learn/separating-events-from-effects
https://react-dev-git-pr7997-fbopensource.vercel.app/learn/removing-effect-dependencies
https://react-dev-git-pr7997-fbopensource.vercel.app/learn/you-might-not-need-an-effect
This commit is contained in:
@@ -474,7 +474,7 @@ By strictly only writing your components as pure functions, you can avoid an ent
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [5]}}
|
||||
let guest = 0;
|
||||
|
||||
function Cup() {
|
||||
|
||||
@@ -201,7 +201,7 @@ There are two common cases in which you don't need Effects:
|
||||
|
||||
For example, you don't need an Effect to adjust some state based on other state:
|
||||
|
||||
```js {5-9}
|
||||
```js {expectedErrors: {'react-compiler': [8]}} {5-9}
|
||||
function Form() {
|
||||
const [firstName, setFirstName] = useState('Taylor');
|
||||
const [lastName, setLastName] = useState('Swift');
|
||||
|
||||
@@ -93,7 +93,7 @@ Here is a component that breaks this rule:
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [5]}}
|
||||
let guest = 0;
|
||||
|
||||
function Cup() {
|
||||
@@ -380,7 +380,7 @@ The buggy code is in `Profile.js`. Make sure you read it all from top to bottom!
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js src/Profile.js
|
||||
```js {expectedErrors: {'react-compiler': [7]}} src/Profile.js
|
||||
import Panel from './Panel.js';
|
||||
import { getImageUrl } from './utils.js';
|
||||
|
||||
@@ -602,7 +602,7 @@ export default function StoryTray({ stories }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/App.js hidden
|
||||
```js {expectedErrors: {'react-compiler': [16]}} src/App.js hidden
|
||||
import { useState, useEffect } from 'react';
|
||||
import StoryTray from './StoryTray.js';
|
||||
|
||||
@@ -698,7 +698,7 @@ export default function StoryTray({ stories }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/App.js hidden
|
||||
```js {expectedErrors: {'react-compiler': [16]}} src/App.js hidden
|
||||
import { useState, useEffect } from 'react';
|
||||
import StoryTray from './StoryTray.js';
|
||||
|
||||
@@ -790,7 +790,7 @@ export default function StoryTray({ stories }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/App.js hidden
|
||||
```js {expectedErrors: {'react-compiler': [16]}} src/App.js hidden
|
||||
import { useState, useEffect } from 'react';
|
||||
import StoryTray from './StoryTray.js';
|
||||
|
||||
|
||||
@@ -1131,7 +1131,7 @@ If you see a linter rule being suppressed, remove the suppression! That's where
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [16]}}
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function App() {
|
||||
@@ -1374,7 +1374,7 @@ export default function App() {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/ChatRoom.js active
|
||||
```js {expectedErrors: {'react-compiler': [8]}} src/ChatRoom.js active
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function ChatRoom({ roomId, createConnection }) {
|
||||
|
||||
@@ -704,7 +704,7 @@ Here, the `MyTextField` component function is defined *inside* `MyComponent`:
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [7]}}
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function MyComponent() {
|
||||
|
||||
@@ -28,7 +28,7 @@ React Compiler automatically optimizes your React application at build time. Rea
|
||||
|
||||
Without the compiler, you need to manually memoize components and values to optimize re-renders:
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [4]}}
|
||||
import { useMemo, useCallback, memo } from 'react';
|
||||
|
||||
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
|
||||
|
||||
@@ -211,7 +211,7 @@ If you tried to implement this with a ref, React would never re-render the compo
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [13]}}
|
||||
import { useRef } from 'react';
|
||||
|
||||
export default function Counter() {
|
||||
@@ -313,7 +313,7 @@ Regular variables like `let timeoutID` don't "survive" between re-renders becaus
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [10]}}
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Chat() {
|
||||
@@ -418,7 +418,7 @@ This button is supposed to toggle between showing "On" and "Off". However, it al
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [10]}}
|
||||
import { useRef } from 'react';
|
||||
|
||||
export default function Toggle() {
|
||||
|
||||
@@ -303,7 +303,7 @@ Suppressing the linter leads to very unintuitive bugs that are hard to find and
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [14]}}
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function Timer() {
|
||||
@@ -794,7 +794,7 @@ It is important to declare it as a dependency! This ensures, for example, that i
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [10]}}
|
||||
import { useState, useEffect } from 'react';
|
||||
import { createConnection } from './chat.js';
|
||||
|
||||
|
||||
@@ -546,7 +546,7 @@ Clicking this button is supposed to switch the page background between white and
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [5, 7]}}
|
||||
export default function LightSwitch() {
|
||||
function handleClick() {
|
||||
let bodyStyle = document.body.style;
|
||||
|
||||
@@ -711,7 +711,7 @@ Here, `url` inside `onVisit` corresponds to the *latest* `url` (which could have
|
||||
|
||||
In the existing codebases, you may sometimes see the lint rule suppressed like this:
|
||||
|
||||
```js {7-9}
|
||||
```js {expectedErrors: {'react-compiler': [8]}} {7-9}
|
||||
function Page({ url }) {
|
||||
const { items } = useContext(ShoppingCartContext);
|
||||
const numberOfItems = items.length;
|
||||
@@ -735,7 +735,7 @@ Can you see why?
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [16]}}
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function App() {
|
||||
@@ -990,7 +990,7 @@ To fix this code, it's enough to follow the rules.
|
||||
```
|
||||
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [14]}}
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function Timer() {
|
||||
|
||||
@@ -23,7 +23,7 @@ Here's a component that renders a sculpture image. Clicking the "Next" button sh
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [7]}}
|
||||
import { sculptureList } from './data.js';
|
||||
|
||||
export default function Gallery() {
|
||||
@@ -1229,7 +1229,7 @@ When you type into the input fields, nothing appears. It's like the input values
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [6]}}
|
||||
export default function Form() {
|
||||
let firstName = '';
|
||||
let lastName = '';
|
||||
@@ -1337,7 +1337,7 @@ Are there any limitations on _where_ Hooks may be called? Does this component br
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [9]}}
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function FeedbackForm() {
|
||||
|
||||
@@ -95,7 +95,7 @@ You might be tempted to try to call `play()` or `pause()` during rendering, but
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [7, 9]}}
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
|
||||
function VideoPlayer({ src, isPlaying }) {
|
||||
@@ -617,7 +617,7 @@ A common pitfall for preventing Effects firing twice in development is to use a
|
||||
|
||||
This makes it so you only see `"✅ Connecting..."` once in development, but it doesn't fix the bug.
|
||||
|
||||
When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix".
|
||||
When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix".
|
||||
|
||||
To fix the bug, it is not enough to just make the Effect run once. The effect needs to work after re-mounting, which means the connection needs to be cleaned up like in the solution above.
|
||||
|
||||
@@ -1005,7 +1005,7 @@ export default function MyInput({ value, onChange }) {
|
||||
const ref = useRef(null);
|
||||
|
||||
// TODO: This doesn't quite work. Fix it.
|
||||
// ref.current.focus()
|
||||
// ref.current.focus()
|
||||
|
||||
return (
|
||||
<input
|
||||
@@ -1468,7 +1468,8 @@ This component shows the biography for the selected person. It loads the biograp
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js src/App.js
|
||||
{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}
|
||||
```js {expectedErrors: {'react-compiler': [9]}} src/App.js
|
||||
import { useState, useEffect } from 'react';
|
||||
import { fetchBio } from './api.js';
|
||||
|
||||
@@ -1541,7 +1542,8 @@ To fix this race condition, add a cleanup function:
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js src/App.js
|
||||
{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}
|
||||
```js {expectedErrors: {'react-compiler': [9]}} src/App.js
|
||||
import { useState, useEffect } from 'react';
|
||||
import { fetchBio } from './api.js';
|
||||
|
||||
@@ -1605,4 +1607,3 @@ In addition to ignoring the result of an outdated API call, you can also use [`A
|
||||
</Solution>
|
||||
|
||||
</Challenges>
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ This example holds an object in state to represent the current pointer position.
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [11]}}
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function MovingDot() {
|
||||
@@ -209,7 +209,7 @@ These input fields don't work because the `onChange` handlers mutate the state:
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [11, 15, 19]}}
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Form() {
|
||||
@@ -832,7 +832,7 @@ Your task is to fix all of these bugs. As you fix them, explain why each of them
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [11]}}
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Scoreboard() {
|
||||
@@ -988,7 +988,7 @@ If something unexpected changes, there is a mutation. Find the mutation in `App.
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js src/App.js
|
||||
```js {expectedErrors: {'react-compiler': [17]}} src/App.js
|
||||
import { useState } from 'react';
|
||||
import Background from './Background.js';
|
||||
import Box from './Box.js';
|
||||
@@ -1293,7 +1293,7 @@ This is the same buggy example as in the previous challenge. This time, fix the
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js src/App.js
|
||||
```js {expectedErrors: {'react-compiler': [18]}} src/App.js
|
||||
import { useState } from 'react';
|
||||
import { useImmer } from 'use-immer';
|
||||
import Background from './Background.js';
|
||||
|
||||
@@ -34,7 +34,7 @@ To help you gain the right intuition, let's look at some common concrete example
|
||||
|
||||
Suppose you have a component with two state variables: `firstName` and `lastName`. You want to calculate a `fullName` from them by concatenating them. Moreover, you'd like `fullName` to update whenever `firstName` or `lastName` change. Your first instinct might be to add a `fullName` state variable and update it in an Effect:
|
||||
|
||||
```js {5-9}
|
||||
```js {expectedErrors: {'react-compiler': [8]}} {5-9}
|
||||
function Form() {
|
||||
const [firstName, setFirstName] = useState('Taylor');
|
||||
const [lastName, setLastName] = useState('Swift');
|
||||
@@ -66,7 +66,7 @@ function Form() {
|
||||
|
||||
This component computes `visibleTodos` by taking the `todos` it receives by props and filtering them according to the `filter` prop. You might feel tempted to store the result in state and update it from an Effect:
|
||||
|
||||
```js {4-8}
|
||||
```js {expectedErrors: {'react-compiler': [7]}} {4-8}
|
||||
function TodoList({ todos, filter }) {
|
||||
const [newTodo, setNewTodo] = useState('');
|
||||
|
||||
@@ -165,7 +165,7 @@ Also note that measuring performance in development will not give you the most a
|
||||
|
||||
This `ProfilePage` component receives a `userId` prop. The page contains a comment input, and you use a `comment` state variable to hold its value. One day, you notice a problem: when you navigate from one profile to another, the `comment` state does not get reset. As a result, it's easy to accidentally post a comment on a wrong user's profile. To fix the issue, you want to clear out the `comment` state variable whenever the `userId` changes:
|
||||
|
||||
```js {4-7}
|
||||
```js {expectedErrors: {'react-compiler': [6]}} {4-7}
|
||||
export default function ProfilePage({ userId }) {
|
||||
const [comment, setComment] = useState('');
|
||||
|
||||
@@ -208,7 +208,7 @@ Sometimes, you might want to reset or adjust a part of the state on a prop chang
|
||||
|
||||
This `List` component receives a list of `items` as a prop, and maintains the selected item in the `selection` state variable. You want to reset the `selection` to `null` whenever the `items` prop receives a different array:
|
||||
|
||||
```js {5-8}
|
||||
```js {expectedErrors: {'react-compiler': [7]}} {5-8}
|
||||
function List({ items }) {
|
||||
const [isReverse, setIsReverse] = useState(false);
|
||||
const [selection, setSelection] = useState(null);
|
||||
@@ -819,7 +819,7 @@ Simplify this component by removing all the unnecessary state and Effects.
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [12, 16, 20]}}
|
||||
import { useState, useEffect } from 'react';
|
||||
import { initialTodos, createTodo } from './todos.js';
|
||||
|
||||
@@ -1022,7 +1022,7 @@ One solution is to add a `useMemo` call to cache the visible todos. There is als
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [11]}}
|
||||
import { useState, useEffect } from 'react';
|
||||
import { initialTodos, createTodo, getVisibleTodos } from './todos.js';
|
||||
|
||||
@@ -1106,7 +1106,7 @@ Remove the state variable and the Effect, and instead add a `useMemo` call to ca
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [8]}}
|
||||
import { useState, useMemo } from 'react';
|
||||
import { initialTodos, createTodo, getVisibleTodos } from './todos.js';
|
||||
|
||||
@@ -1363,7 +1363,7 @@ export default function ContactList({
|
||||
}
|
||||
```
|
||||
|
||||
```js src/EditContact.js active
|
||||
```js {expectedErrors: {'react-compiler': [8, 9]}} src/EditContact.js active
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function EditContact({ savedContact, onSave }) {
|
||||
|
||||
@@ -296,7 +296,8 @@ import App from './App.js';
|
||||
hydrateRoot(document.getElementById('root'), <App />);
|
||||
```
|
||||
|
||||
```js src/App.js active
|
||||
{/* kind of an edge case, seems fine to use this hack here */}
|
||||
```js {expectedErrors: {'react-compiler': [7]}} src/App.js active
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function App() {
|
||||
|
||||
@@ -398,7 +398,8 @@ Here is a complete example you can play with:
|
||||
}
|
||||
```
|
||||
|
||||
```js src/App.js
|
||||
{/* TODO(@poteto) - fixed by https://github.com/facebook/react/pull/34462. need a new release */}
|
||||
```js {expectedErrors: {'react-compiler': [15]}} src/App.js
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { createMapWidget, addPopupToMapWidget } from './map-widget.js';
|
||||
|
||||
@@ -65,7 +65,7 @@ The optimization of caching return values based on inputs is known as [_memoizat
|
||||
|
||||
[//]: # 'TODO: add links to Server/Client Component reference once https://github.com/reactjs/react.dev/pull/6177 is merged'
|
||||
|
||||
- React will invalidate the cache for all memoized functions for each server request.
|
||||
- React will invalidate the cache for all memoized functions for each server request.
|
||||
- Each call to `cache` creates a new function. This means that calling `cache` with the same function multiple times will return different memoized functions that do not share the same cache.
|
||||
- `cachedFn` will also cache errors. If `fn` throws an error for certain arguments, it will be cached, and the same error is re-thrown when `cachedFn` is called with those same arguments.
|
||||
- `cache` is for use in [Server Components](/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components) only.
|
||||
@@ -98,9 +98,9 @@ function TeamReport({users}) {
|
||||
}
|
||||
```
|
||||
|
||||
If the same `user` object is rendered in both `Profile` and `TeamReport`, the two components can share work and only call `calculateUserMetrics` once for that `user`.
|
||||
If the same `user` object is rendered in both `Profile` and `TeamReport`, the two components can share work and only call `calculateUserMetrics` once for that `user`.
|
||||
|
||||
Assume `Profile` is rendered first. It will call <CodeStep step={1}>`getUserMetrics`</CodeStep>, and check if there is a cached result. Since it is the first time `getUserMetrics` is called with that `user`, there will be a cache miss. `getUserMetrics` will then call `calculateUserMetrics` with that `user` and write the result to cache.
|
||||
Assume `Profile` is rendered first. It will call <CodeStep step={1}>`getUserMetrics`</CodeStep>, and check if there is a cached result. Since it is the first time `getUserMetrics` is called with that `user`, there will be a cache miss. `getUserMetrics` will then call `calculateUserMetrics` with that `user` and write the result to cache.
|
||||
|
||||
When `TeamReport` renders its list of `users` and reaches the same `user` object, it will call <CodeStep step={2}>`getUserMetrics`</CodeStep> and read the result from cache.
|
||||
|
||||
@@ -170,12 +170,12 @@ export default function Precipitation({cityData}) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Here, both components call the <CodeStep step={3}>same memoized function</CodeStep> exported from `./getWeekReport.js` to read and write to the same cache.
|
||||
Here, both components call the <CodeStep step={3}>same memoized function</CodeStep> exported from `./getWeekReport.js` to read and write to the same cache.
|
||||
</Pitfall>
|
||||
|
||||
### Share a snapshot of data {/*take-and-share-snapshot-of-data*/}
|
||||
|
||||
To share a snapshot of data between components, call `cache` with a data-fetching function like `fetch`. When multiple components make the same data fetch, only one request is made and the data returned is cached and shared across components. All components refer to the same snapshot of data across the server render.
|
||||
To share a snapshot of data between components, call `cache` with a data-fetching function like `fetch`. When multiple components make the same data fetch, only one request is made and the data returned is cached and shared across components. All components refer to the same snapshot of data across the server render.
|
||||
|
||||
```js [[1, 4, "city"], [1, 5, "fetchTemperature(city)"], [2, 4, "getTemperature"], [2, 9, "getTemperature"], [1, 9, "city"], [2, 14, "getTemperature"], [1, 14, "city"]]
|
||||
import {cache} from 'react';
|
||||
@@ -196,7 +196,7 @@ async function MinimalWeatherCard({city}) {
|
||||
}
|
||||
```
|
||||
|
||||
If `AnimatedWeatherCard` and `MinimalWeatherCard` both render for the same <CodeStep step={1}>city</CodeStep>, they will receive the same snapshot of data from the <CodeStep step={2}>memoized function</CodeStep>.
|
||||
If `AnimatedWeatherCard` and `MinimalWeatherCard` both render for the same <CodeStep step={1}>city</CodeStep>, they will receive the same snapshot of data from the <CodeStep step={2}>memoized function</CodeStep>.
|
||||
|
||||
If `AnimatedWeatherCard` and `MinimalWeatherCard` supply different <CodeStep step={1}>city</CodeStep> arguments to <CodeStep step={2}>`getTemperature`</CodeStep>, then `fetchTemperature` will be called twice and each call site will receive different data.
|
||||
|
||||
@@ -260,7 +260,7 @@ When rendering `Profile`, we call <CodeStep step={2}>`getUser`</CodeStep> again.
|
||||
|
||||
When evaluating an [asynchronous function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function), you will receive a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for that work. The promise holds the state of that work (_pending_, _fulfilled_, _failed_) and its eventual settled result.
|
||||
|
||||
In this example, the asynchronous function <CodeStep step={1}>`fetchData`</CodeStep> returns a promise that is awaiting the `fetch`.
|
||||
In this example, the asynchronous function <CodeStep step={1}>`fetchData`</CodeStep> returns a promise that is awaiting the `fetch`.
|
||||
|
||||
```js [[1, 1, "fetchData()"], [2, 8, "getData()"], [3, 10, "getData()"]]
|
||||
async function fetchData() {
|
||||
@@ -271,7 +271,7 @@ const getData = cache(fetchData);
|
||||
|
||||
async function MyComponent() {
|
||||
getData();
|
||||
// ... some computational work
|
||||
// ... some computational work
|
||||
await getData();
|
||||
// ...
|
||||
}
|
||||
@@ -281,7 +281,7 @@ In calling <CodeStep step={2}>`getData`</CodeStep> the first time, the promise r
|
||||
|
||||
Notice that the first <CodeStep step={2}>`getData`</CodeStep> call does not `await` whereas the <CodeStep step={3}>second</CodeStep> does. [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) is a JavaScript operator that will wait and return the settled result of the promise. The first <CodeStep step={2}>`getData`</CodeStep> call simply initiates the `fetch` to cache the promise for the second <CodeStep step={3}>`getData`</CodeStep> to look-up.
|
||||
|
||||
If by the <CodeStep step={3}>second call</CodeStep> the promise is still _pending_, then `await` will pause for the result. The optimization is that while we wait on the `fetch`, React can continue with computational work, thus reducing the wait time for the <CodeStep step={3}>second call</CodeStep>.
|
||||
If by the <CodeStep step={3}>second call</CodeStep> the promise is still _pending_, then `await` will pause for the result. The optimization is that while we wait on the `fetch`, React can continue with computational work, thus reducing the wait time for the <CodeStep step={3}>second call</CodeStep>.
|
||||
|
||||
If the promise is already settled, either to an error or the _fulfilled_ result, `await` will return that value immediately. In both outcomes, there is a performance benefit.
|
||||
</DeepDive>
|
||||
@@ -309,7 +309,7 @@ async function DemoProfile() {
|
||||
|
||||
React only provides cache access to the memoized function in a component. When calling <CodeStep step={1}>`getUser`</CodeStep> outside of a component, it will still evaluate the function but not read or update the cache.
|
||||
|
||||
This is because cache access is provided through a [context](/learn/passing-data-deeply-with-context) which is only accessible from a component.
|
||||
This is because cache access is provided through a [context](/learn/passing-data-deeply-with-context) which is only accessible from a component.
|
||||
|
||||
</Pitfall>
|
||||
|
||||
@@ -323,7 +323,7 @@ All mentioned APIs offer memoization but the difference is what they're intended
|
||||
|
||||
In general, you should use [`useMemo`](/reference/react/useMemo) for caching an expensive computation in a Client Component across renders. As an example, to memoize a transformation of data within a component.
|
||||
|
||||
```jsx {4}
|
||||
```jsx {expectedErrors: {'react-compiler': [4]}} {4}
|
||||
'use client';
|
||||
|
||||
function WeatherReport({record}) {
|
||||
@@ -343,7 +343,7 @@ function App() {
|
||||
```
|
||||
In this example, `App` renders two `WeatherReport`s with the same record. Even though both components do the same work, they cannot share work. `useMemo`'s cache is only local to the component.
|
||||
|
||||
However, `useMemo` does ensure that if `App` re-renders and the `record` object doesn't change, each component instance would skip work and use the memoized value of `avgTemp`. `useMemo` will only cache the last computation of `avgTemp` with the given dependencies.
|
||||
However, `useMemo` does ensure that if `App` re-renders and the `record` object doesn't change, each component instance would skip work and use the memoized value of `avgTemp`. `useMemo` will only cache the last computation of `avgTemp` with the given dependencies.
|
||||
|
||||
#### `cache` {/*deep-dive-cache*/}
|
||||
|
||||
@@ -379,7 +379,7 @@ You should use [`memo`](reference/react/memo) to prevent a component re-renderin
|
||||
'use client';
|
||||
|
||||
function WeatherReport({record}) {
|
||||
const avgTemp = calculateAvg(record);
|
||||
const avgTemp = calculateAvg(record);
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -396,7 +396,7 @@ function App() {
|
||||
}
|
||||
```
|
||||
|
||||
In this example, both `MemoWeatherReport` components will call `calculateAvg` when first rendered. However, if `App` re-renders, with no changes to `record`, none of the props have changed and `MemoWeatherReport` will not re-render.
|
||||
In this example, both `MemoWeatherReport` components will call `calculateAvg` when first rendered. However, if `App` re-renders, with no changes to `record`, none of the props have changed and `MemoWeatherReport` will not re-render.
|
||||
|
||||
Compared to `useMemo`, `memo` memoizes the component render based on props vs. specific computations. Similar to `useMemo`, the memoized component only caches the last render with the last prop values. Once the props change, the cache invalidates and the component re-renders.
|
||||
|
||||
@@ -495,4 +495,3 @@ function App() {
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ function ProductPage({ productId, referrer, theme }) {
|
||||
orderDetails,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={theme}>
|
||||
{/* ... so ShippingForm's props will never be the same, and it will re-render every time */}
|
||||
@@ -207,7 +207,7 @@ The difference is in *what* they're letting you cache:
|
||||
|
||||
If you're already familiar with [`useMemo`,](/reference/react/useMemo) you might find it helpful to think of `useCallback` as this:
|
||||
|
||||
```js
|
||||
```js {expectedErrors: {'react-compiler': [3]}}
|
||||
// Simplified implementation (inside React)
|
||||
function useCallback(fn, dependencies) {
|
||||
return useMemo(() => fn, dependencies);
|
||||
@@ -222,7 +222,7 @@ function useCallback(fn, dependencies) {
|
||||
|
||||
#### Should you add useCallback everywhere? {/*should-you-add-usecallback-everywhere*/}
|
||||
|
||||
If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.
|
||||
If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.
|
||||
|
||||
Caching a function with `useCallback` is only valuable in a few cases:
|
||||
|
||||
@@ -310,7 +310,7 @@ function post(url, data) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/ShippingForm.js
|
||||
```js {expectedErrors: {'react-compiler': [7, 8]}} src/ShippingForm.js
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
|
||||
@@ -449,7 +449,7 @@ function post(url, data) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/ShippingForm.js
|
||||
```js {expectedErrors: {'react-compiler': [7, 8]}} src/ShippingForm.js
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
|
||||
@@ -868,7 +868,7 @@ When you find which dependency is breaking memoization, either find a way to rem
|
||||
|
||||
Suppose the `Chart` component is wrapped in [`memo`](/reference/react/memo). You want to skip re-rendering every `Chart` in the list when the `ReportList` component re-renders. However, you can't call `useCallback` in a loop:
|
||||
|
||||
```js {5-14}
|
||||
```js {expectedErrors: {'react-compiler': [6]}} {5-14}
|
||||
function ReportList({ items }) {
|
||||
return (
|
||||
<article>
|
||||
|
||||
@@ -244,7 +244,7 @@ input { margin: 10px; }
|
||||
|
||||
</Sandpack>
|
||||
|
||||
A common alternative UI pattern is to *defer* updating the list of results and to keep showing the previous results until the new results are ready. Call `useDeferredValue` to pass a deferred version of the query down:
|
||||
A common alternative UI pattern is to *defer* updating the list of results and to keep showing the previous results until the new results are ready. Call `useDeferredValue` to pass a deferred version of the query down:
|
||||
|
||||
```js {3,11}
|
||||
export default function App() {
|
||||
@@ -669,7 +669,7 @@ export default function App() {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/SlowList.js
|
||||
```js {expectedErrors: {'react-compiler': [19, 20]}} src/SlowList.js
|
||||
import { memo } from 'react';
|
||||
|
||||
const SlowList = memo(function SlowList({ text }) {
|
||||
@@ -746,7 +746,7 @@ export default function App() {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/SlowList.js
|
||||
```js {expectedErrors: {'react-compiler': [19, 20]}} src/SlowList.js
|
||||
import { memo } from 'react';
|
||||
|
||||
const SlowList = memo(function SlowList({ text }) {
|
||||
|
||||
@@ -928,7 +928,8 @@ Note the `ignore` variable which is initialized to `false`, and is set to `true`
|
||||
|
||||
<Sandpack>
|
||||
|
||||
```js src/App.js
|
||||
{/* TODO(@poteto) - investigate potential false positives in react compiler validation */}
|
||||
```js {expectedErrors: {'react-compiler': [9]}} src/App.js
|
||||
import { useState, useEffect } from 'react';
|
||||
import { fetchBio } from './api.js';
|
||||
|
||||
@@ -1737,7 +1738,9 @@ If your app uses server rendering (either [directly](/reference/react-dom/server
|
||||
|
||||
In rare cases, you might need to display different content on the client. For example, if your app reads some data from [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), it can't possibly do that on the server. Here is how you could implement this:
|
||||
|
||||
```js
|
||||
|
||||
{/* TODO(@poteto) - investigate potential false positives in react compiler validation */}
|
||||
```js {expectedErrors: {'react-compiler': [5]}}
|
||||
function MyComponent() {
|
||||
const [didMount, setDidMount] = useState(false);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ function Tooltip() {
|
||||
#### Parameters {/*parameters*/}
|
||||
|
||||
* `setup`: The function with your Effect's logic. Your setup function may also optionally return a *cleanup* function. Before your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. Before your component is removed from the DOM, React will run your cleanup function.
|
||||
|
||||
|
||||
* **optional** `dependencies`: The list of all reactive values referenced inside of the `setup` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. If you omit this argument, your Effect will re-run after every re-render of the component.
|
||||
|
||||
#### Returns {/*returns*/}
|
||||
@@ -87,7 +87,8 @@ To do this, you need to render in two passes:
|
||||
|
||||
**All of this needs to happen before the browser repaints the screen.** You don't want the user to see the tooltip moving. Call `useLayoutEffect` to perform the layout measurements before the browser repaints the screen:
|
||||
|
||||
```js {5-8}
|
||||
{/* TODO(@poteto) - fixed by https://github.com/facebook/react/pull/34462. need a new release */}
|
||||
```js {expectedErrors: {'react-compiler': [7]}} {5-8}
|
||||
function Tooltip() {
|
||||
const ref = useRef(null);
|
||||
const [tooltipHeight, setTooltipHeight] = useState(0); // You don't know real height yet
|
||||
@@ -187,7 +188,8 @@ export default function ButtonWithTooltip({ tooltipContent, ...rest }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/Tooltip.js active
|
||||
{/* TODO(@poteto) - fixed by https://github.com/facebook/react/pull/34462. need a new release */}
|
||||
```js {expectedErrors: {'react-compiler': [11]}} src/Tooltip.js active
|
||||
import { useRef, useLayoutEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import TooltipContainer from './TooltipContainer.js';
|
||||
@@ -337,7 +339,8 @@ export default function ButtonWithTooltip({ tooltipContent, ...rest }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/Tooltip.js active
|
||||
{/* TODO(@poteto) - fixed by https://github.com/facebook/react/pull/34462. need a new release */}
|
||||
```js {expectedErrors: {'react-compiler': [11]}} src/Tooltip.js active
|
||||
import { useRef, useLayoutEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import TooltipContainer from './TooltipContainer.js';
|
||||
@@ -484,7 +487,8 @@ export default function ButtonWithTooltip({ tooltipContent, ...rest }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/Tooltip.js active
|
||||
{/* TODO(@poteto) - fixed by https://github.com/facebook/react/pull/34462. need a new release */}
|
||||
```js {expectedErrors: {'react-compiler': [11]}} src/Tooltip.js active
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import TooltipContainer from './TooltipContainer.js';
|
||||
@@ -627,7 +631,7 @@ export default function ButtonWithTooltip({ tooltipContent, ...rest }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/Tooltip.js active
|
||||
```js {expectedErrors: {'react-compiler': [10, 11]}} src/Tooltip.js active
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import TooltipContainer from './TooltipContainer.js';
|
||||
|
||||
@@ -149,7 +149,7 @@ Also note that measuring performance in development will not give you the most a
|
||||
|
||||
#### Should you add useMemo everywhere? {/*should-you-add-usememo-everywhere*/}
|
||||
|
||||
If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.
|
||||
If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.
|
||||
|
||||
Optimizing with `useMemo` is only valuable in a few cases:
|
||||
|
||||
@@ -225,7 +225,8 @@ export default function App() {
|
||||
|
||||
```
|
||||
|
||||
```js src/TodoList.js active
|
||||
{/* TODO(@poteto) - investigate potential false positives in react compiler validation */}
|
||||
```js {expectedErrors: {'react-compiler': [5]}} src/TodoList.js active
|
||||
import { useMemo } from 'react';
|
||||
import { filterTodos } from './utils.js'
|
||||
|
||||
@@ -717,7 +718,7 @@ export default function TodoList({ todos, theme, tab }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/List.js
|
||||
```js {expectedErrors: {'react-compiler': [5, 6]}} src/List.js
|
||||
import { memo } from 'react';
|
||||
|
||||
const List = memo(function List({ items }) {
|
||||
@@ -855,7 +856,7 @@ export default function TodoList({ todos, theme, tab }) {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/List.js
|
||||
```js {expectedErrors: {'react-compiler': [5, 6]}} src/List.js
|
||||
import { memo } from 'react';
|
||||
|
||||
const List = memo(function List({ items }) {
|
||||
@@ -1127,7 +1128,7 @@ function ChatRoom({ roomId }) {
|
||||
serverUrl: 'https://localhost:1234',
|
||||
roomId: roomId
|
||||
}
|
||||
|
||||
|
||||
const connection = createConnection(options);
|
||||
connection.connect();
|
||||
return () => connection.disconnect();
|
||||
@@ -1371,7 +1372,7 @@ When you find which dependency breaks memoization, either find a way to remove i
|
||||
|
||||
Suppose the `Chart` component is wrapped in [`memo`](/reference/react/memo). You want to skip re-rendering every `Chart` in the list when the `ReportList` component re-renders. However, you can't call `useMemo` in a loop:
|
||||
|
||||
```js {5-11}
|
||||
```js {expectedErrors: {'react-compiler': [6]}} {5-11}
|
||||
function ReportList({ items }) {
|
||||
return (
|
||||
<article>
|
||||
|
||||
@@ -197,7 +197,7 @@ React expects that the body of your component [behaves like a pure function](/le
|
||||
|
||||
Reading or writing a ref **during rendering** breaks these expectations.
|
||||
|
||||
```js {3-4,6-7}
|
||||
```js {expectedErrors: {'react-compiler': [4]}} {3-4,6-7}
|
||||
function MyComponent() {
|
||||
// ...
|
||||
// 🚩 Don't write a ref during rendering
|
||||
|
||||
@@ -161,7 +161,7 @@ function CheckoutForm() {
|
||||
}
|
||||
```
|
||||
|
||||
The function passed to `startTransition` is called the "Action". You can update state and (optionally) perform side effects within an Action, and the work will be done in the background without blocking user interactions on the page. A Transition can include multiple Actions, and while a Transition is in progress, your UI stays responsive. For example, if the user clicks a tab but then changes their mind and clicks another tab, the second click will be immediately handled without waiting for the first update to finish.
|
||||
The function passed to `startTransition` is called the "Action". You can update state and (optionally) perform side effects within an Action, and the work will be done in the background without blocking user interactions on the page. A Transition can include multiple Actions, and while a Transition is in progress, your UI stays responsive. For example, if the user clicks a tab but then changes their mind and clicks another tab, the second click will be immediately handled without waiting for the first update to finish.
|
||||
|
||||
To give the user feedback about in-progress Transitions, the `isPending` state switches to `true` at the first call to `startTransition`, and stays `true` until all Actions complete and the final state is shown to the user. Transitions ensure side effects in Actions to complete in order to [prevent unwanted loading indicators](#preventing-unwanted-loading-indicators), and you can provide immediate feedback while the Transition is in progress with `useOptimistic`.
|
||||
|
||||
@@ -597,7 +597,7 @@ export default function TabButton({ action, children, isActive }) {
|
||||
<button onClick={() => {
|
||||
startTransition(async () => {
|
||||
// await the action that's passed in.
|
||||
// This allows it to be either sync or async.
|
||||
// This allows it to be either sync or async.
|
||||
await action();
|
||||
});
|
||||
}}>
|
||||
@@ -664,7 +664,7 @@ export default function TabButton({ action, children, isActive }) {
|
||||
<button onClick={async () => {
|
||||
startTransition(async () => {
|
||||
// await the action that's passed in.
|
||||
// This allows it to be either sync or async.
|
||||
// This allows it to be either sync or async.
|
||||
await action();
|
||||
});
|
||||
}}>
|
||||
@@ -682,7 +682,7 @@ export default function AboutTab() {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/PostsTab.js
|
||||
```js {expectedErrors: {'react-compiler': [19, 20]}} src/PostsTab.js
|
||||
import { memo } from 'react';
|
||||
|
||||
const PostsTab = memo(function PostsTab() {
|
||||
@@ -742,7 +742,7 @@ b { display: inline-block; margin-right: 10px; }
|
||||
|
||||
<Note>
|
||||
|
||||
When exposing an `action` prop from a component, you should `await` it inside the transition.
|
||||
When exposing an `action` prop from a component, you should `await` it inside the transition.
|
||||
|
||||
This allows the `action` callback to be either synchronous or asynchronous without requiring an additional `startTransition` to wrap the `await` in the action.
|
||||
|
||||
@@ -837,7 +837,7 @@ export default function AboutTab() {
|
||||
}
|
||||
```
|
||||
|
||||
```js src/PostsTab.js
|
||||
```js {expectedErrors: {'react-compiler': [19, 20]}} src/PostsTab.js
|
||||
import { memo } from 'react';
|
||||
|
||||
const PostsTab = memo(function PostsTab() {
|
||||
@@ -1805,7 +1805,7 @@ export default function App({}) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
// Store the actual quantity in separate state to show the mismatch.
|
||||
const [clientQuantity, setClientQuantity] = useState(1);
|
||||
|
||||
|
||||
const updateQuantityAction = newQuantity => {
|
||||
setClientQuantity(newQuantity);
|
||||
|
||||
@@ -1840,7 +1840,7 @@ export default function Item({action}) {
|
||||
startTransition(async () => {
|
||||
await action(e.target.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="item">
|
||||
<span>Eras Tour Tickets</span>
|
||||
@@ -2006,7 +2006,7 @@ export default function Item({action}) {
|
||||
startTransition(() => {
|
||||
action(e.target.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="item">
|
||||
<span>Eras Tour Tickets</span>
|
||||
|
||||
@@ -209,7 +209,7 @@ You can think of the props and state values as snapshots that are updated after
|
||||
### Don't mutate Props {/*props*/}
|
||||
Props are immutable because if you mutate them, the application will produce inconsistent output, which can be hard to debug as it may or may not work depending on the circumstances.
|
||||
|
||||
```js {2}
|
||||
```js {expectedErrors: {'react-compiler': [2]}} {2}
|
||||
function Post({ item }) {
|
||||
item.url = new Url(item.url, base); // 🔴 Bad: never mutate props directly
|
||||
return <Link url={item.url}>{item.title}</Link>;
|
||||
@@ -232,7 +232,7 @@ const [stateVariable, setter] = useState(0);
|
||||
|
||||
Rather than updating the state variable in-place, we need to update it using the setter function that is returned by `useState`. Changing values on the state variable doesn't cause the component to update, leaving your users with an outdated UI. Using the setter function informs React that the state has changed, and that we need to queue a re-render to update the UI.
|
||||
|
||||
```js {5}
|
||||
```js {expectedErrors: {'react-compiler': [2, 5]}} {5}
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
@@ -270,7 +270,7 @@ function Counter() {
|
||||
|
||||
Once values are passed to a hook, you should not modify them. Like props in JSX, values become immutable when passed to a hook.
|
||||
|
||||
```js {4}
|
||||
```js {expectedErrors: {'react-compiler': [4]}} {4}
|
||||
function useIconStyle(icon) {
|
||||
const theme = useContext(ThemeContext);
|
||||
if (icon.enabled) {
|
||||
@@ -331,7 +331,7 @@ Don't mutate values after they've been used in JSX. Move the mutation to before
|
||||
|
||||
When you use JSX in an expression, React may eagerly evaluate the JSX before the component finishes rendering. This means that mutating values after they've been passed to JSX can lead to outdated UIs, as React won't know to update the component's output.
|
||||
|
||||
```js {4}
|
||||
```js {expectedErrors: {'react-compiler': [4]}} {4}
|
||||
function Page({ colour }) {
|
||||
const styles = { colour, size: "large" };
|
||||
const header = <Header styles={styles} />;
|
||||
|
||||
@@ -21,7 +21,7 @@ function BlogPost() {
|
||||
}
|
||||
```
|
||||
|
||||
```js {2}
|
||||
```js {expectedErrors: {'react-compiler': [2]}} {2}
|
||||
function BlogPost() {
|
||||
return <Layout>{Article()}</Layout>; // 🔴 Bad: Never call them directly
|
||||
}
|
||||
@@ -51,7 +51,7 @@ Breaking this rule will cause React to not automatically optimize your component
|
||||
|
||||
Hooks should be as "static" as possible. This means you shouldn't dynamically mutate them. For example, this means you shouldn't write higher order Hooks:
|
||||
|
||||
```js {2}
|
||||
```js {expectedErrors: {'react-compiler': [2, 3]}} {2}
|
||||
function ChatInput() {
|
||||
const useDataWithLogging = withLogging(useData); // 🔴 Bad: don't write higher order Hooks
|
||||
const data = useDataWithLogging();
|
||||
@@ -74,7 +74,7 @@ function useDataWithLogging() {
|
||||
|
||||
Hooks should also not be dynamically used: for example, instead of doing dependency injection in a component by passing a Hook as a value:
|
||||
|
||||
```js {2}
|
||||
```js {expectedErrors: {'react-compiler': [2]}} {2}
|
||||
function ChatInput() {
|
||||
return <Button useData={useDataWithLogging} /> // 🔴 Bad: don't pass Hooks as props
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user