Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show the exercise/solution beside the content for each lesson #7

Merged
merged 5 commits into from
Apr 22, 2024
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Frontend Infra Workshops

This repo contains source code used in various Frontend Infra workshops.

## QuickStart

- `yarn install`
- `yarn dev`
11 changes: 11 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<style>
head, body {
height: 100%;
margin: 0;
}

#root {
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="root"></div>
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@mdx-js/rollup": "^3.0.1",
"eventemitter3": "^5.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-fast-compare": "^3.2.2",
"react-router": "5",
"react-router-dom": "5"
"react-router-dom": "5",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^4.0.0"
},
"devDependencies": {
"@khanacademy/eslint-config": "^4.0.0",
Expand All @@ -26,6 +29,7 @@
"@types/react-dom": "^18.2.19",
"@types/react-router": "5",
"@types/react-router-dom": "5",
"@types/react-syntax-highlighter": "^15.5.11",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@vitejs/plugin-react-swc": "^3.5.0",
Expand All @@ -39,6 +43,7 @@
"eslint-plugin-react-refresh": "^0.4.5",
"prettier": "^3.2.5",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.1.0",
"vite": "^5.1.4"
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Link} from "react-router-dom";

# React Render Perf

The purpose of this workshop is to learn about common issues that can result
Expand All @@ -12,7 +14,7 @@ tab see this [blog post](https://legacy.reactjs.org/blog/2018/09/10/introducing-

## Lessons

1. [Memoizing expensive to render components](./lesson-01/README.md)
2. [Prevent React.Context from re-render the whole tree](./lesson-02/README.md)
3. [Avoid using React.Context at all](./lesson-03/README.md)
4. [Minimizing re-renders by splitting up large components](./lesson-04/README.md)
1. <Link to="/react-render-perf/lesson-01">Memoizing expensive to render components</Link>
2. <Link to="/react-render-perf/lesson-02">Prevent React.Context from re-rendering the whole tree</Link>
3. <Link to="/react-render-perf/lesson-03">Avoid using React.Context at all</Link>
4. <Link to="/react-render-perf/lesson-04">Minimizing re-renders by splitting up large components</Link>
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[Home](../README.md)

# 01 - Memoizing Expensive Components

Memoization can be used to avoid unnecessary renders. This is most useful when
the component itself is expensive to render (e.g. the `MathJax` component in
webapp) or it renders a lot of descedent components.
Expand Down
1 change: 0 additions & 1 deletion src/react-render-perf/lesson-01/exercise/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default function Exercise1() {

return (
<div>
<h1>Exercise 1: Memoizing Expensive Components</h1>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} circle={circle} />
</div>
Expand Down
26 changes: 26 additions & 0 deletions src/react-render-perf/lesson-01/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Link} from "react-router-dom";

import {code} from "../shared/code";
import Tabs from "../shared/tabs";
import styles from "../shared/index.module.css";

import Content from "./content.mdx";
import Exercise from "./exercise/index";
import Solution from "./solution/index";

export default function Page() {
return (
<div className={styles.container}>
<div className={styles.header}>
<Link to="/react-render-perf">Home</Link>
<h1>01 - Memoizing Expensive Components</h1>
</div>
<div className={styles.column}>
<Content components={{code}} />
</div>
<div className={styles.column}>
<Tabs tabs={{exercise: Exercise, solution: Solution}} />
</div>
</div>
);
}
1 change: 0 additions & 1 deletion src/react-render-perf/lesson-01/solution/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default function Solution1() {

return (
<div>
<h1>Solution 1: Memoizing Expensive Components</h1>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} circle={circle} />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[Home](../README.md)

# 02 - Prevent Context From Re-rendering

React Context is useful for sharing data with its descendent components. When
changes are made to the context, this causes the context and all of its
descendents to be re-rendered. If the number of descendents is large, this can
Expand All @@ -15,7 +11,7 @@ In the following example, everytime we update the `value` in the context, we
re-render both child components of the context even though only one of them
needs to be update.

```tsx
```tsx filename="index.tsx"
import {createContext, useContext, useState} from "react";

type FooBar = {
Expand Down
6 changes: 4 additions & 2 deletions src/react-render-perf/lesson-02/exercise/gradient.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ReactElement} from "react";

import {Swatch} from "./swatch";
import {Color, lerpColor} from "../../shared/color";
import {Color, lerpColor, toCssColor} from "../../shared/color";

type Props = {
start: Color;
Expand All @@ -13,7 +13,9 @@ export function Gradient({start, stop, steps}: Props) {
const swatches: Array<ReactElement<typeof Swatch>> = [];
for (let i = 0; i < steps; i++) {
const color = lerpColor(start, stop, i / steps);
swatches.push(<Swatch color={color} size={512 / steps} />);
swatches.push(
<Swatch key={toCssColor(color)} color={color} size={512 / steps} />,
);
}

return (
Expand Down
7 changes: 5 additions & 2 deletions src/react-render-perf/lesson-02/exercise/grid.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ReactElement} from "react";

import {Gradient} from "./gradient";
import {Color, lerpColor} from "../../shared/color";
import {Color, lerpColor, toCssColor} from "../../shared/color";

type Props = {
topLeft: Color;
Expand All @@ -22,7 +22,10 @@ export function Grid({
for (let i = 0; i < steps; i++) {
const start = lerpColor(topLeft, bottomLeft, i / steps);
const stop = lerpColor(topRight, bottomRight, i / steps);
gradients.push(<Gradient start={start} stop={stop} steps={steps} />);
const key = `${toCssColor(start)}-${toCssColor(stop)}`;
gradients.push(
<Gradient key={key} start={start} stop={stop} steps={steps} />,
);
}

return (
Expand Down
26 changes: 26 additions & 0 deletions src/react-render-perf/lesson-02/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Link} from "react-router-dom";

import {code} from "../shared/code";
import Tabs from "../shared/tabs";
import styles from "../shared/index.module.css";

import Content from "./content.mdx";
import Exercise from "./exercise/index";
import Solution from "./solution/index";

export default function Page() {
return (
<div className={styles.container}>
<div className={styles.header}>
<Link to="/react-render-perf">Home</Link>
<h1>02 - Prevent Context From Re-rendering</h1>
</div>
<div className={styles.column}>
<Content components={{code}} />
</div>
<div className={styles.column}>
<Tabs tabs={{exercise: Exercise, solution: Solution}} />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[Home](../README.md)

# 03 - Avoid Using Context

If your context contains an event emitter (or other class instance) and it's only
used once in your application, you can simplify things even further by using a
singleton instance of it.
Expand All @@ -21,7 +17,7 @@ benefits over React Context:

## Example

Using our example from [Lesson 2](../lesson-02/README.md) we can replace the context
Using our example from [Lesson 2](/react-render-perf/lesson-02) we can replace the context
by creating a `EventEmitter` singleton and using it directly in our components.

```tsx
Expand Down
26 changes: 26 additions & 0 deletions src/react-render-perf/lesson-03/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Link} from "react-router-dom";

import {code} from "../shared/code";
import Tabs from "../shared/tabs";
import styles from "../shared/index.module.css";

import Content from "./content.mdx";
import Exercise from "./exercise/index";
import Solution from "./solution/index";

export default function Page() {
return (
<div className={styles.container}>
<div className={styles.header}>
<Link to="/react-render-perf">Home</Link>
<h1>03 - Avoid Using Context</h1>
</div>
<div className={styles.column}>
<Content components={{code}} />
</div>
<div className={styles.column}>
<Tabs tabs={{exercise: Exercise, solution: Solution}} />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[Home](../README.md)

# 04 - Splitting Up Large Components

Large components often end up re-rendering more stuff than is necessary when something
changes in the component. If a component uses a lot of:
- props
Expand Down
26 changes: 26 additions & 0 deletions src/react-render-perf/lesson-04/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Link} from "react-router-dom";

import {code} from "../shared/code";
import Tabs from "../shared/tabs";
import styles from "../shared/index.module.css";

import Content from "./content.mdx";
import Exercise from "./exercise/index";
import Solution from "./solution/index";

export default function Page() {
return (
<div className={styles.container}>
<div className={styles.header}>
<Link to="/react-render-perf">Home</Link>
<h1>04 - Splitting Up Large Components</h1>
</div>
<div className={styles.column}>
<Content components={{code}} />
</div>
<div className={styles.column}>
<Tabs tabs={{exercise: Exercise, solution: Solution}} />
</div>
</div>
);
}
38 changes: 17 additions & 21 deletions src/react-render-perf/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
import {BrowserRouter, Route} from "react-router-dom";
import {JSX} from "react";

import TableOfContents from "./table-of-contents";

const lessons = import.meta.glob("./lesson-*/**/index.tsx", {eager: true});
import TableOfContents from "./index.mdx";
import Lesson01 from "./lesson-01/index";
import Lesson02 from "./lesson-02/index";
import Lesson03 from "./lesson-03/index";
import Lesson04 from "./lesson-04/index";

export default function Routes() {
return (
<BrowserRouter>
<Route path="/react-render-perf" exact={true}>
<TableOfContents />
</Route>
{Object.entries(lessons).map(
([path, {default: Component}]: [string, any]): JSX.Element => {
const base = "/react-render-perf/";
return (
<Route
path={
base +
path.replace("./", "").replace("/index.tsx", "")
}
key={path}
exact={true}
>
<Component />
</Route>
);
},
)}
<Route path="/react-render-perf/lesson-01" exact={true}>
<Lesson01 />
</Route>
<Route path="/react-render-perf/lesson-02" exact={true}>
<Lesson02 />
</Route>
<Route path="/react-render-perf/lesson-03" exact={true}>
<Lesson03 />
</Route>
<Route path="/react-render-perf/lesson-04" exact={true}>
<Lesson04 />
</Route>
</BrowserRouter>
);
}
15 changes: 15 additions & 0 deletions src/react-render-perf/shared/code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Prism as SyntaxHighlighter} from "react-syntax-highlighter";
import * as React from "react";

export const code: React.FC<
React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
> = ({children, className, ...properties}) => {
return className ? (
// @ts-expect-error: the types for `children` are incompatible
<SyntaxHighlighter language="tsx" {...properties}>
{children}
</SyntaxHighlighter>
) : (
<span style={{fontFamily: "monospace"}}>children</span>
);
};
Loading
Loading