I have a single input element from react-bootstrap that will allow the user to change the field value and 2 buttons will appear, one to accept the changes and the other will cancel the changes leaving the original value in.
I manage to control the focus and blur events by delegating the listeners to the wrapping component, my thinking is that since the focus is still within the wrapping component, I won’t lose its focus, but pressing the inner buttons seems to blur the focus, therefore the Accept and Cancel buttons don’t fire any events…
Here is my code example:
JavaScript
x
79
79
1
import { useState, useEffect, useRef } from "react";
2
import { InputGroup, Button, FormControl } from "react-bootstrap";
3
4
import "./styles.css";
5
6
const InputField = ({ title }) => {
7
const formRef = useRef(null);
8
const [value, setValue] = useState(title);
9
const [toggleButtons, setToggleButtons] = useState(false);
10
11
const onChange = (e) => {
12
setValue(e.target.value);
13
};
14
15
const onFocus = () => {
16
setToggleButtons(true);
17
};
18
19
const onBlur = () => {
20
setToggleButtons(false);
21
};
22
23
const acceptChange = () => {
24
console.log("Accept");
25
setToggleButtons(false);
26
};
27
28
const cancelChange = () => {
29
console.log("Cancel");
30
setToggleButtons(false);
31
};
32
33
useEffect(() => {
34
const form = formRef.current;
35
form.addEventListener("focus", onFocus);
36
form.addEventListener("blur", onBlur);
37
38
return () => {
39
form.removeEventListener("focus", onFocus);
40
form.removeEventListener("blur", onBlur);
41
};
42
}, []);
43
44
return (
45
<div className="App">
46
<InputGroup className="m-3" style={{ width: "400px" }}>
47
<FormControl
48
ref={formRef}
49
value={value}
50
onChange={onChange}
51
// onFocus={onFocus}
52
// onBlur={onBlur}
53
/>
54
{toggleButtons ? (
55
<InputGroup.Append>
56
<Button variant="outline-secondary" onClick={() => acceptChange()}>
57
Accept
58
</Button>
59
<Button variant="outline-secondary" onClick={() => cancelChange()}>
60
Cancel
61
</Button>
62
</InputGroup.Append>
63
) : null}
64
</InputGroup>
65
</div>
66
);
67
};
68
69
export default function App() {
70
return (
71
<>
72
<InputField title={"Input 1"} />
73
<InputField title={"Input 2"} />
74
<InputField title={"Input 3"} />
75
<InputField title={"Input 4"} />
76
</>
77
);
78
}
79
Advertisement
Answer
A couple of changes are needed to make this work:
- The toggle buttons need to always be in the DOM, so hide them rather than only rendering if the focus is there.
- To avoid hiding the buttons when the blur occurs from the input to one of the buttons you can check if the newly focused element is a sibling of the input by using the event’s relatedTarget and the
currentTarget.parentNode
.
For example:
JavaScript
1
74
74
1
import { useState } from "react";
2
import { InputGroup, Button, FormControl } from "react-bootstrap";
3
4
import "./styles.css";
5
6
const InputField = ({ title }) => {
7
const [value, setValue] = useState(title);
8
const [toggleButtons, setToggleButtons] = useState(false);
9
10
const onChange = (e) => {
11
setValue(e.target.value);
12
};
13
14
const onFocus = () => {
15
setToggleButtons(true);
16
};
17
18
const onBlur = (e) => {
19
if (!e.currentTarget.parentNode.contains(e.relatedTarget)) {
20
setToggleButtons(false);
21
}
22
};
23
24
const acceptChange = () => {
25
console.log("Accept");
26
setToggleButtons(false);
27
};
28
29
const cancelChange = () => {
30
console.log("Cancel");
31
setToggleButtons(false);
32
};
33
34
return (
35
<div className="App">
36
<InputGroup className="m-3" style={{ width: "400px" }}>
37
<FormControl
38
value={value}
39
onChange={onChange}
40
onFocus={onFocus}
41
onBlur={onBlur}
42
/>
43
<InputGroup.Append className={toggleButtons ? "d-flex" : "d-none"}>
44
<Button
45
onBlur={onBlur}
46
variant="outline-secondary"
47
onClick={() => acceptChange()}
48
>
49
Accept
50
</Button>
51
<Button
52
onBlur={onBlur}
53
variant="outline-secondary"
54
onClick={() => cancelChange()}
55
>
56
Cancel
57
</Button>
58
</InputGroup.Append>
59
</InputGroup>
60
</div>
61
);
62
};
63
64
export default function App() {
65
return (
66
<>
67
<InputField title={"Input 1"} />
68
<InputField title={"Input 2"} />
69
<InputField title={"Input 3"} />
70
<InputField title={"Input 4"} />
71
</>
72
);
73
}
74