Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 81 additions & 4 deletions src/components/MDX/Sandpack/createFileMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,66 @@ export const AppJSPath = `/src/App.js`;
export const StylesCSSPath = `/src/styles.css`;
export const SUPPORTED_FILES = [AppJSPath, StylesCSSPath];

/**
* Tokenize meta attributes while ignoring brace-wrapped metadata (e.g. {expectedErrors: …}).
*/
function splitMeta(meta: string): string[] {
const tokens: string[] = [];
let current = '';
let depth = 0;
const trimmed = meta.trim();

for (let ii = 0; ii < trimmed.length; ii++) {
const char = trimmed[ii];

if (char === '{') {
if (depth === 0 && current) {
tokens.push(current);
current = '';
}
depth += 1;
continue;
}

if (char === '}') {
if (depth > 0) {
depth -= 1;
}
if (depth === 0) {
current = '';
}
if (depth < 0) {
throw new Error(`Unexpected closing brace in meta: ${meta}`);
}
continue;
}

if (depth > 0) {
continue;
}

if (/\s/.test(char)) {
if (current) {
tokens.push(current);
current = '';
}
continue;
}

current += char;
}

if (current) {
tokens.push(current);
}

if (depth !== 0) {
throw new Error(`Unclosed brace in meta: ${meta}`);
}

return tokens;
}

export const createFileMap = (codeSnippets: any) => {
return codeSnippets.reduce(
(result: Record<string, SandpackFile>, codeSnippet: React.ReactElement) => {
Expand All @@ -37,12 +97,17 @@ export const createFileMap = (codeSnippets: any) => {
let fileActive = false; // if the file tab is shown by default

if (props.meta) {
const [name, ...params] = props.meta.split(' ');
filePath = '/' + name;
if (params.includes('hidden')) {
const tokens = splitMeta(props.meta);
const name = tokens.find(
(token) => token.includes('/') || token.includes('.')
);
if (name) {
filePath = name.startsWith('/') ? name : `/${name}`;
}
if (tokens.includes('hidden')) {
fileHidden = true;
}
if (params.includes('active')) {
if (tokens.includes('active')) {
fileActive = true;
}
} else {
Expand All @@ -57,6 +122,18 @@ export const createFileMap = (codeSnippets: any) => {
}
}

if (!filePath) {
if (props.className === 'language-js') {
filePath = AppJSPath;
} else if (props.className === 'language-css') {
filePath = StylesCSSPath;
} else {
throw new Error(
`Code block is missing a filename: ${props.children}`
);
}
}

if (result[filePath]) {
throw new Error(
`File ${filePath} was defined multiple times. Each file snippet should have a unique path name`
Expand Down
2 changes: 1 addition & 1 deletion src/content/learn/describing-the-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion src/content/learn/escape-hatches.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
10 changes: 5 additions & 5 deletions src/content/learn/keeping-components-pure.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Here is a component that breaks this rule:

<Sandpack>

```js
```js {expectedErrors: {'react-compiler': [5]}}
let guest = 0;

function Cup() {
Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -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';

Expand Down
4 changes: 2 additions & 2 deletions src/content/learn/lifecycle-of-reactive-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 }) {
Expand Down
2 changes: 1 addition & 1 deletion src/content/learn/preserving-and-resetting-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion src/content/learn/react-compiler/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand Down
6 changes: 3 additions & 3 deletions src/content/learn/referencing-values-with-refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down
4 changes: 2 additions & 2 deletions src/content/learn/removing-effect-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion src/content/learn/responding-to-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/content/learn/separating-events-from-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -735,7 +735,7 @@ Can you see why?

<Sandpack>

```js
```js {expectedErrors: {'react-compiler': [16]}}
import { useState, useEffect } from 'react';

export default function App() {
Expand Down Expand Up @@ -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() {
Expand Down
6 changes: 3 additions & 3 deletions src/content/learn/state-a-components-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 = '';
Expand Down Expand Up @@ -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() {
Expand Down
13 changes: 7 additions & 6 deletions src/content/learn/synchronizing-with-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -1605,4 +1607,3 @@ In addition to ignoring the result of an outdated API call, you can also use [`A
</Solution>

</Challenges>

Loading