I'm using useState
in the following way to toggle editing an input:
const [editing, setEditing] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
// returning:
<input
ref={inputRef}
disabled={!editing}
type="text"
name="name"
id="name"
defaultValue={organization?.name}
/>
<button
onClick={() => {
setEditing(!editing);
// inputRef.current?.focus();
}}
type="button"
>
<span>{editing ? "save" : "edit"}</span>
</button>
When the user clicks on "edit", editing
becomes true which toggles the <input>
to be no longer disabled and the input should be focused but it is not.
In order to get this working, I ended up using useEffect
the following way:
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing, inputRef]);
This works, but it just seems a bit much. Is there a way to "await" setState or something to ensure the new state took effect, so that I can then do this focus()
?
CodePudding user response:
The correct way of doing this for functional components is as you've done it, with useEffect.
React does not support state updates through the useState Hook, as Component#setState does.
If you wish to use a class component, however, then you can add a callback to the second argument of setState:
import React, { Component } from "react";
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
};
render() {
return (
<>
<input
ref={this.inputRef}
disabled={!this.state?.editing}
type="text"
name="name"
id="name"
/>
<button
onClick={() => {
this.setState({ editing: !this.state?.editing }, () => this.inputRef.current?.focus());
// inputRef.current?.focus();
}}
type="button"
>
<span>{this.state?.editing ? "save" : "edit"}</span>
</button>
</>
);
};
};
Which executes after the state has been set.
By attempting to use a callback argument in your set (state) function, React would throw the following error:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().