Skip to content

Commit 392b508

Browse files
committed
finish exercise 8
1 parent 9933cb5 commit 392b508

File tree

5 files changed

+130
-2
lines changed

5 files changed

+130
-2
lines changed
Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
11
# flushSync
22

3-
This example was uses code from
4-
[trellix](https://github.com/remix-run/example-trellix/blob/3379b3d5e9c0173381031e4f062877e8a3696b2e/app/routes/board.%24id/components.tsx).
3+
🧝‍♂️ I've put together a new component we need. It's called `<EditableText />` and
4+
it allows users to edit a piece of text inline. We display it in a button and
5+
when the user clicks it, the button turns into a text input. When the user
6+
presses enter, blurs, or hits escape, the text input turns back into a button.
7+
8+
Right now, when the user clicks the button, the button goes away and is replaced
9+
by the text input, but because their focus was on the button which is now gone,
10+
their focus returns to the `<body>` and the text input is not focused. This is
11+
not a good user experience.
12+
13+
👨‍💼 Thanks Kellie. So now what we need is for you to properly manage focus for
14+
all of these cases.
15+
16+
- When the user submits the form (by hitting "enter")
17+
- When the user cancels the form (by hitting "escape")
18+
- When the user blurs the input (by tabbing or clicking away)
19+
20+
Additionally, when the user clicks the button, we want to select all the text so
21+
it's easy for them to edit.
22+
23+
🧝‍♂️ I've added some buttons before and after the input so you have something to
24+
test tab focus with. Good luck!
25+
26+
<callout-info class="aside">
27+
This example was uses code from
28+
[trellix](https://github.com/remix-run/example-trellix/blob/3379b3d5e9c0173381031e4f062877e8a3696b2e/app/routes/board.%24id/components.tsx).
29+
</callout-info>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
# flushSync
2+
3+
👨‍💼 Awesome job. That's a mighty fine UX!

exercises/08.focus/FINISHED.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Focus Management
2+
3+
👨‍💼 It's important to consider users of keyboards, both those with disabilities
4+
who rely on them as well as power users who prefer to use the keyboard over the
5+
mouse. Good work!

exercises/08.focus/README.mdx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,96 @@
11
# Focus Management
2+
3+
Helping the user's focus stay on the right place is a key part of the user
4+
experience. This is especially important for users who rely on screen readers or
5+
keyboard navigation. But even able users can benefit from a well-thought focus
6+
management experience.
7+
8+
Sometimes, the element you want to focus on only becomes available after a state
9+
update. For example:
10+
11+
```tsx
12+
function MyComponent() {
13+
const inputRef = useRef<HTMLInputElement>(null)
14+
const [show, setShow] = useState(false)
15+
16+
return (
17+
<div>
18+
<button onClick={() => setShow(true)}>Show</button>
19+
{show && <input ref={inputRef} />}
20+
</div>
21+
)
22+
}
23+
```
24+
25+
Presumably after the user clicks "show" they will want to type something in the
26+
input there. Good focus management would focus the input after it becomes
27+
visible.
28+
29+
It's important for you to know that in React state updates happen in batches.
30+
So state updates do not necessarily take place at the same time you
31+
call the state updater function.
32+
33+
As a result of React state update batching, if you try to focus an element right
34+
after a state update, it might not work as expected. This is because the element
35+
you want to focus on might not be available yet.
36+
37+
```tsx remove=10
38+
function MyComponent() {
39+
const inputRef = useRef<HTMLInputElement>(null)
40+
const [show, setShow] = useState(false)
41+
42+
return (
43+
<div>
44+
<button
45+
onClick={() => {
46+
setShow(true)
47+
inputRef.current?.focus() // This probably won't work
48+
}}
49+
>
50+
Show
51+
</button>
52+
{show && <input ref={inputRef} />}
53+
</div>
54+
)
55+
}
56+
```
57+
58+
The solution to this problem is to force React to run the state and DOM updates
59+
synchronously so that the element you want to focus on is available when you try
60+
to focus it.
61+
62+
You do this by using the `flushSync` function from the `react-dom` package.
63+
64+
```tsx
65+
import { flushSync } from 'react-dom'
66+
67+
function MyComponent() {
68+
const inputRef = useRef<HTMLInputElement>(null)
69+
const [show, setShow] = useState(false)
70+
71+
return (
72+
<div>
73+
<button
74+
onClick={() => {
75+
flushSync(() => {
76+
setShow(true)
77+
})
78+
inputRef.current?.focus()
79+
}}
80+
>
81+
Show
82+
</button>
83+
{show && <input ref={inputRef} />}
84+
</div>
85+
)
86+
}
87+
```
88+
89+
What `flushSync` does is that it forces React to run the state update and DOM
90+
update synchronously. This way, the input element will be available when you try
91+
to focus it on the line following the `flushSync` call.
92+
93+
In general you want to avoid this de-optimization, but in some cases (like focus
94+
management), it's the perfect solution.
95+
96+
Learn more in [the `flushSync` docs](https://react.dev/reference/react-dom/flushSync).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Sync External State

0 commit comments

Comments
 (0)