Skip to content

Commit ff10168

Browse files
committed
add flushSync
1 parent 1fe4fd9 commit ff10168

File tree

7 files changed

+240
-0
lines changed

7 files changed

+240
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# flushSync
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
main {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 1rem;
5+
padding: 3rem;
6+
}
7+
.editable-text {
8+
button {
9+
/* remove button styles. Make it look like text */
10+
background: none;
11+
border: none;
12+
padding: 4px 8px;
13+
font-size: 1.5rem;
14+
font-weight: bold;
15+
}
16+
17+
input {
18+
/* make it the same size as the button */
19+
font-size: 1.5rem;
20+
font-weight: bold;
21+
padding: 4px 8px;
22+
border: none;
23+
}
24+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { useRef, useState } from 'react'
2+
import * as ReactDOM from 'react-dom/client'
3+
4+
function EditableText({
5+
id,
6+
initialValue = '',
7+
fieldName,
8+
inputLabel,
9+
buttonLabel,
10+
}: {
11+
id?: string
12+
initialValue?: string
13+
fieldName: string
14+
inputLabel: string
15+
buttonLabel: string
16+
}) {
17+
const [edit, setEdit] = useState(false)
18+
const [value, setValue] = useState(initialValue)
19+
const inputRef = useRef<HTMLInputElement>(null)
20+
// 🐨 add a button ref here
21+
22+
return edit ? (
23+
<form
24+
method="post"
25+
onSubmit={event => {
26+
event.preventDefault()
27+
// here's where you'd send the updated value to the server
28+
// 🐨 wrap these calls in a flushSync
29+
setValue(inputRef.current?.value ?? '')
30+
setEdit(false)
31+
// 🐨 after flushSync, focus the button with the button ref
32+
}}
33+
>
34+
<input
35+
required
36+
ref={inputRef}
37+
type="text"
38+
id={id}
39+
aria-label={inputLabel}
40+
name={fieldName}
41+
defaultValue={value}
42+
onKeyDown={event => {
43+
if (event.key === 'Escape') {
44+
// 🐨 wrap this in a flushSync
45+
setEdit(false)
46+
// 🐨 after the flushSync, focus the button
47+
}
48+
}}
49+
onBlur={event => {
50+
// 🐨 wrap these in a flushSync
51+
setValue(event.currentTarget.value)
52+
setEdit(false)
53+
// 🐨 after the flushSync, focus the button
54+
}}
55+
/>
56+
</form>
57+
) : (
58+
<button
59+
aria-label={buttonLabel}
60+
// 🐨 add a ref prop for the button
61+
type="button"
62+
onClick={() => {
63+
// 🐨 wrap this in a flushSync
64+
setEdit(true)
65+
// 🐨 after the flushSync, select all the text of the input
66+
}}
67+
>
68+
{value || 'Edit'}
69+
</button>
70+
)
71+
}
72+
73+
function App() {
74+
return (
75+
<main>
76+
<button>Focus before</button>
77+
<div className="editable-text">
78+
<EditableText
79+
initialValue="Unnamed"
80+
fieldName="name"
81+
inputLabel="Edit project name"
82+
buttonLabel="Edit project name"
83+
/>
84+
</div>
85+
<button>Focus after</button>
86+
</main>
87+
)
88+
}
89+
90+
const rootEl = document.createElement('div')
91+
document.body.append(rootEl)
92+
ReactDOM.createRoot(rootEl).render(<App />)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# flushSync
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
main {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 1rem;
5+
padding: 3rem;
6+
}
7+
.editable-text {
8+
button {
9+
/* remove button styles. Make it look like text */
10+
background: none;
11+
border: none;
12+
padding: 4px 8px;
13+
font-size: 1.5rem;
14+
font-weight: bold;
15+
}
16+
17+
input {
18+
/* make it the same size as the button */
19+
font-size: 1.5rem;
20+
font-weight: bold;
21+
padding: 4px 8px;
22+
border: none;
23+
}
24+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useRef, useState } from 'react'
2+
import { flushSync } from 'react-dom'
3+
import * as ReactDOM from 'react-dom/client'
4+
5+
function EditableText({
6+
id,
7+
initialValue = '',
8+
fieldName,
9+
inputLabel,
10+
buttonLabel,
11+
}: {
12+
id?: string
13+
initialValue?: string
14+
fieldName: string
15+
inputLabel: string
16+
buttonLabel: string
17+
}) {
18+
const [edit, setEdit] = useState(false)
19+
const [value, setValue] = useState(initialValue)
20+
const inputRef = useRef<HTMLInputElement>(null)
21+
const buttonRef = useRef<HTMLButtonElement>(null)
22+
23+
return edit ? (
24+
<form
25+
method="post"
26+
onSubmit={event => {
27+
event.preventDefault()
28+
// here's where you'd send the updated value to the server
29+
flushSync(() => {
30+
setValue(inputRef.current?.value ?? '')
31+
setEdit(false)
32+
})
33+
buttonRef.current?.focus()
34+
}}
35+
>
36+
<input
37+
required
38+
ref={inputRef}
39+
type="text"
40+
id={id}
41+
aria-label={inputLabel}
42+
name={fieldName}
43+
defaultValue={value}
44+
onKeyDown={event => {
45+
if (event.key === 'Escape') {
46+
flushSync(() => {
47+
setEdit(false)
48+
})
49+
buttonRef.current?.focus()
50+
}
51+
}}
52+
onBlur={event => {
53+
setValue(event.currentTarget.value)
54+
setEdit(false)
55+
}}
56+
/>
57+
</form>
58+
) : (
59+
<button
60+
aria-label={buttonLabel}
61+
type="button"
62+
ref={buttonRef}
63+
onClick={() => {
64+
flushSync(() => {
65+
setEdit(true)
66+
})
67+
inputRef.current?.select()
68+
}}
69+
>
70+
{value || 'Edit'}
71+
</button>
72+
)
73+
}
74+
75+
function App() {
76+
return (
77+
<main>
78+
<button>Focus before</button>
79+
<div className="editable-text">
80+
<EditableText
81+
initialValue="Unnamed"
82+
fieldName="name"
83+
inputLabel="Edit project name"
84+
buttonLabel="Edit project name"
85+
/>
86+
</div>
87+
<button>Focus after</button>
88+
</main>
89+
)
90+
}
91+
92+
const rootEl = document.createElement('div')
93+
document.body.append(rootEl)
94+
ReactDOM.createRoot(rootEl).render(<App />)

exercises/08.focus/README.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Focus Management
2+
3+
This example was uses code from
4+
[trellix](https://github.com/remix-run/example-trellix/blob/3379b3d5e9c0173381031e4f062877e8a3696b2e/app/routes/board.%24id/components.tsx).

0 commit comments

Comments
 (0)