diff --git a/src/react-render-perf/content.mdx b/src/react-render-perf/content.mdx
index 213a7eb..31ae0c6 100644
--- a/src/react-render-perf/content.mdx
+++ b/src/react-render-perf/content.mdx
@@ -6,8 +6,8 @@ import highlightRerenders from "./images/highlight-rerenders.png"
# React Render Perf
-The purpose of this workshop is to learn about common issues that can result
-react renders taking longer than expected.
+The purpose of this workshop is to learn about common performance issues that can
+result from react renders taking longer than expected.
## Preliminaries
diff --git a/src/react-render-perf/lesson-01/index.tsx b/src/react-render-perf/lesson-01/index.tsx
index 519f44a..02374d5 100644
--- a/src/react-render-perf/lesson-01/index.tsx
+++ b/src/react-render-perf/lesson-01/index.tsx
@@ -19,6 +19,9 @@ export default function Page() {
+
+ ⚠️ HMR may not work, please reload after making changes ⚠️
+
diff --git a/src/react-render-perf/lesson-02/content.mdx b/src/react-render-perf/lesson-02/content.mdx
index 6c17337..6cbf66f 100644
--- a/src/react-render-perf/lesson-02/content.mdx
+++ b/src/react-render-perf/lesson-02/content.mdx
@@ -1,7 +1,8 @@
-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
-result in poor rendering performance.
+React Context is useful for sharing data with its descendent components without
+having to perform prop-drilling (passing props down through several layers of
+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 result in poor rendering performance.
In order to avoid re-rendering all of the descendents we'd like only those
components using the context to re-render. Ideally, they should only re-render
@@ -12,27 +13,27 @@ re-render both child components of the context even though only one of them
needs to be updated.
```tsx filename="index.tsx"
-import {createContext, useContext, useState} from "react";
+import * as React from "react";
type FooBar = {
foo: number;
bar: number;
};
-const FooBarContext = createContext({foo: 0, bar: 0});
+const FooBarContext = React.createContext({foo: 0, bar: 0});
const Foo = () => {
- const {foo} = useContext(FooBarContext);
+ const {foo} = React.useContext(FooBarContext);
return foo = {foo}
;
};
const Bar = () => {
- const {bar} = useContext(FooBarContext);
+ const {bar} = React.useContext(FooBarContext);
return bar = {bar}
;
};
const Parent = () => {
- const [value, setValue] = useState({foo: 0, bar: 0});
+ const [value, setValue] = React.useState({foo: 0, bar: 0});
const incrementFoo = () => setValue(fb => {...fb, foo: fb.foo + 1});
const incrementBar = () => setValue(fb => {...fb, bar: fb.bar + 1});
<>
@@ -56,31 +57,37 @@ data they care about. The parent component will emit the appropriate event when
increment the values for foo
and bar
.
```tsx
-import {createContext, useContext, useState, useMemo} from "react";
+import * as React from "react";
import EventEmitter from "eventemitter3";
-const FooBarContext = createContext(null);
+const FooBarContext = React.createContext(null);
const Foo = () => {
- const emitter = useContext(FooBarContext);
- const [foo, setFoo] = useState(0);
- emitter?.on("foo", setFoo);
+ const emitter = React.useContext(FooBarContext);
+ const [foo, setFoo] = React.useState(0);
+ React.useEffect(() => {
+ emitter?.on("foo", setFoo);
+ return () => emitter?.off("foo", setFoo);
+ }, []);
return foo = {foo}
;
};
const Bar = () => {
- const emitter = useContext(FooBarContext);
- const [bar, setBar] = useState(0);
- emitter?.on("bar", setBar);
+ const emitter = React.useContext(FooBarContext);
+ const [bar, setBar] = React.useState(0);
+ React.useEffect(() => {
+ emitter?.on("bar", setBar);
+ return () => emitter?.off("bar", setBar);
+ });
return bar = {bar}
;
};
const Parent = () => {
- const [foo, setFoo] = useState(0);
- const [bar, setBar] = useState(0);
- const emitter = useMemo(() => new EventEmitter(), []);
+ const [foo, setFoo] = React.useState(0);
+ const [bar, setBar] = React.useState(0);
+ const emitter = React.useMemo(() => new EventEmitter(), []);
const incrementFoo = () => {
emitter.emit("foo", foo + 1);
@@ -109,26 +116,44 @@ need to memoize Foo
and Bar
themselves. Thankfully the
props so this is trivial to do.
```tsx
-import {createContext, useContext, useState, memo} from "react";
+import * as React from "react";
import arePropsEqual from "react-fast-compare";
-const Foo = memo(() => {
- const emitter = useContext(FooBarContext);
- const [foo, setFoo] = useState(0);
+const Foo = React.memo(() => {
+ const emitter = React.useContext(FooBarContext);
+ const [foo, setFoo] = React.useState(0);
emitter?.on("foo", setFoo);
return foo = {foo}
;
}, arePropsEqual);
-const Bar = memo(() => {
- const emitter = useContext(FooBarContext);
- const [bar, setBar] = useState(0);
+const Bar = React.memo(() => {
+ const emitter = React.useContext(FooBarContext);
+ const [bar, setBar] = React.useState(0);
emitter?.on("bar", setBar);
return bar = {bar}
;
}, arePropsEqual);
```
+## Notes
+
+- The example above shows the Parent
component creating the event emitter. This is
+ only necessary if Parent
was being used in multiple places and we didn't want its
+ descendents sharing the same event emitter. If there's only a single instance of
+ Parent
the event emitter could be created outside of the component and could be
+ used to initialize the context. This would result in the context value always being
+ defined and we wouldn't need to use optional chaining when accessing properties on
+ the emitter
we get from the context.
+- Why not just use redux
for prop-drilling? Unfortunately, it has the same issues
+ as the naive use of context shown in the original code above. That being said, the context +
+ event emitter pattern isn't great either because it's a pattern as opposed to a library. In
+ the long term I think we should evaluate possible replaces for redux
that don't
+ have this preformance issue like jotai
, zustand
, or recoil.js
.
+- React.useMemo(callback)
and React.memo(Component, arePropsEqual?)
are
+ both used to memoize things. The former is used for memoizing computations _within_ a functional
+ component while the latter is used for memoizing functional components themselves.
+
## Exercise
1. Use the profiler in React dev tools to measure the render performance of the
diff --git a/src/react-render-perf/lesson-02/exercise/index.tsx b/src/react-render-perf/lesson-02/exercise/index.tsx
index 1b75136..241dac7 100644
--- a/src/react-render-perf/lesson-02/exercise/index.tsx
+++ b/src/react-render-perf/lesson-02/exercise/index.tsx
@@ -4,6 +4,17 @@ export default function Exercise2() {
return (
Exercise 2: Prevent Context From Rendering
+
+ Below is a colour picker. Clicking on it will select the colour
+ under the mouse cursor. Profile to see where the performance
+ issues are and then resolve them using the technique described
+ in this lesson.
+
+
+ NOTE: There are multiple components in this example that could
+ be memoized. Part of this exercise is figuring out which ones
+ make sense to memoize.
+
);
diff --git a/src/react-render-perf/lesson-02/index.tsx b/src/react-render-perf/lesson-02/index.tsx
index 7a03394..960d559 100644
--- a/src/react-render-perf/lesson-02/index.tsx
+++ b/src/react-render-perf/lesson-02/index.tsx
@@ -19,6 +19,9 @@ export default function Page() {
+
+ ⚠️ HMR may not work, please reload after making changes ⚠️
+
diff --git a/src/react-render-perf/lesson-03/index.tsx b/src/react-render-perf/lesson-03/index.tsx
index 073c7b6..6ac17f2 100644
--- a/src/react-render-perf/lesson-03/index.tsx
+++ b/src/react-render-perf/lesson-03/index.tsx
@@ -19,6 +19,9 @@ export default function Page() {
+
+ ⚠️ HMR may not work, please reload after making changes ⚠️
+
diff --git a/src/react-render-perf/lesson-04/index.tsx b/src/react-render-perf/lesson-04/index.tsx
index c43c8a0..b1c9bab 100644
--- a/src/react-render-perf/lesson-04/index.tsx
+++ b/src/react-render-perf/lesson-04/index.tsx
@@ -19,6 +19,9 @@ export default function Page() {
+
+ ⚠️ HMR may not work, please reload after making changes ⚠️
+