React setState to toggle input's disabled and focus - reactjs

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()?

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().

Related

How to add ref to a conditional rendering component and make it work in React

I'm trying to build a table component and make one of its cells editable.
I need this cell to be clickable, and if clicked, an input component would replace the button, and it would get focused automatically so that users can decide the text of this cell.
Now in the first rendering, button would be rendered, which leads to the binding ref of Input failing.
Here is my simplified code:
import { Input, InputRef, Button } from 'antd'
import { useRef, useState, useEffect } from 'react'
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef<InputRef>(null)
useEffect(() => {
console.log(inputRef.current)
}, [inputRef, showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => {
setIsShowInput(true)
if (showInput) inputRef.current?.focus()
}}>Edit me</Button>}
</div>
);
}
How can I make the binding of ref takes effect in the first rendering, so when I click the button, Input would get focused.
Or is there any other way to achieve this?
The easiest way to achieve this is to watch the showInput value. If the value is true then call the focus method, otherwise do nothing as the Input component will be unmounted from the App.
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef(null)
useEffect(() => {
if (!showInput) return;
inputRef.current.focus()
}, [showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => setIsShowInput(true)}>Edit me</Button>}
</div>
);
}

Ref current is always null when rendering my element for the first time?

So my problem is simple I guess, I want that when I click an element, my input got the focus in, so this is my methods and constructor on my component :
constructor(props) {
super(props);
this.textInput = React.createRef();
this.state = {
searchValue: ""
};
}
activateSearchZone = action => {
this.props.activateSearchZone(action);
console.log(this.textInput);
this.textInput.current.focus();
};
handleSearchZone = event => {
let searchValue = event.target.value;
this.props.searchForUsers(searchValue, { isSearching: true });
setTimeout(() => {
this.props.searchForUsers(searchValue, {
isSearching: false,
searchDone: true
});
}, 1000);
this.setState({
searchValue
});
};
And this is my component :
{this.props.searchList.activated && (
<div className="search-bar__zone">
<FontAwesomeIcon icon={faSearch} size="xs"></FontAwesomeIcon>
<input
placeholder="Search"
onChange={event => this.handleSearchZone(event)}
value={this.state.searchValue}
type="text"
ref={this.textInput}
></input>
<FontAwesomeIcon
icon={faTimesCircle}
onClick={() => this.activateSearchZone(false)}
></FontAwesomeIcon>
</div>
)}
The console log shows that the current value is null, I understand now why, it is because my element is just rendered I think, but I want the focus in my input when clicking.
How can I do that ?
An help would be much appreciated.
You can focus an input element with autofocus attribute. In react, it will be like <input type="text" autoFocus />, this will do the job.
For detailed explanation, please refer the link https://davidwalsh.name/react-autofocus
That's because react doesn't knows about the ref on initial render. You need to use forwardRef. It is HOC that wraps your component and tells react that there is some ref. And it will not render that until it is available. Here is an example:
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));

Where does onChange pass arguments to its handelers

With the below code
<div className="input-first-line-right">
<Input
type="textarea"
label="Project Description"
onChange={this.handleDescChange}
value={this.state.projectDescription}
/>
</div>
handleDescChange = text => {
if (!text) return;
this.setState({ projectDescription: text });
};
If handleDescChange is expecting an argument 'text' how come it is never passed.
Meaning why isn't the code
onChange={this.handleDescChange("some new text")}
inorder for the function to work. How does the code inherintly know what the parameter is if nothing is ever passed to it.
For onChange attribute, this.handleDescChange isn't called here.
Here, this.handleDescChange is given as callback. Input component calls this.handleDescChange when the change event is triggered.
If you want to pass a variable you can use fat arrow function. Solution is given below.
<div className="input-first-line-right">
<Input
type="textarea"
label="Project Description"
onChange={event => this.handleDescChange(event.target.value)}
value={this.state.projectDescription}
/>
</div>
handleDescChange = text => {
if (!text) return;
this.setState({ projectDescription: text });
};
This warning is triggered when we try to access to a React synthetic event in an asynchronous way. Because of the reuse, after the event callback has been invoked the synthetic event object will no longer exist so we cannot access its properties. source
The source link above have the correct answer but here is the summary:
Use event.persist()
Note: If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code. React documentation
class App extends Component {
constructor(props) {
super(props);
this.state = {
projectDescription: ""
};
}
handleDescChange = event => {
// Add here
event.persist();
// Don't check for '!text' Check if there is a value
if (event.target.value === 0) return;
this.setState({ projectDescription: event.target.value });
};
render() {
return (
<div className="input-first-line-right">
<input
type="textarea"
label="Project Description"
onChange={this.handleDescChange}
value={this.state.projectDescription}
/>
</div>
);
}
}
Though, I would recommend to start learning/use React hooks as it is more clean, elegant way to do this:
import React, { useState } from "react";
const App = () => {
// useState hook
const [projectDescription, setProjectDescription] = useState("");
const handleDescChange = event => {
if (event.target.value === 0) return;
setProjectDescription(event.target.value);
};
return (
<div className="input-first-line-right">
<input
type="textarea"
label="Project Description"
onChange={handleDescChange}
value={projectDescription}
/>
</div>
);
};

Reactjs parent state is not refreshed when the children input is changed

I created a component and wanted to wrap text input and add some functionality to it.
<Input value={this.state.name} />
In child component there is remove button beside text input in order to clear text when it is clicked.
const Input = (props) => {
let textInput = null;
const removeText = (e) =>{
e.preventDefault();
textInput.value = '';
textInput.focus();
}
return(<div>
<input ref={(input) => { textInput = input; }} {...props} />
<button
onClick = {removeText}
></button>
</div>)
}
As the Input component is function, I used ref in order to access input and manipulate it. The problem is the parent state is not updated when it is changed by functions in child component. Consider that I don't want to use props and pass the function to update parent state. By the way, I don't know this approach whether is correct or not.
Define removeText function in a component where you are calling Input component. Also avoid using refs for input field you no need ref for sure instead you can have event handler function which will set the value in state
removeText = (e) =>{
e.preventDefault();
this.setState({name: ''});
}
handleInput = e => {
this.setState({name: e.target.value});
}
And pass down functions to Input component as a prop like
<Input value={this.state.name} removeText={this.removeText} handleInput={this.handleInput} />
Now, change the Input component to something like below
const Input = (props) => {
return(<div>
<input value={props.value} onChange={e => props.handleInput(e)}/>
<button
onClick = {() => this.removeText()}
></button>
</div>)
}
This way it updates the parent state value. This is so called callbacks in react

[admin-on-rest]-"Warning" when create <SimpleForm/> with ImageInput [duplicate]

I am looking to create a stateless component who's input element can be validated by the parent component.
In my example below, I am running into a problem where the input ref is never being assigned to the parent's private _emailAddress property.
When handleSubmit is called, this._emailAddress is undefined. Is there something I'm missing, or is there a better way to do this?
interface FormTestState {
errors: string;
}
class FormTest extends React.Component<void, FormTestState> {
componentWillMount() {
this.setState({ errors: '' });
}
render(): JSX.Element {
return (
<main role='main' className='about_us'>
<form onSubmit={this._handleSubmit.bind(this)}>
<TextInput
label='email'
inputName='txtInput'
ariaLabel='email'
validation={this.state.errors}
ref={r => this._emailAddress = r}
/>
<button type='submit'>submit</button>
</form>
</main>
);
}
private _emailAddress: HTMLInputElement;
private _handleSubmit(event: Event): void {
event.preventDefault();
// this._emailAddress is undefined
if (!Validators.isEmail(this._emailAddress.value)) {
this.setState({ errors: 'Please enter an email address.' });
} else {
this.setState({ errors: 'All Good.' });
}
}
}
const TextInput = ({ label, inputName, ariaLabel, validation, ref }: { label: string; inputName: string; ariaLabel: string; validation?: string; ref: (ref: HTMLInputElement) => void }) => (
<div>
<label htmlFor='txt_register_first_name'>
{ label }
</label>
<input type='text' id={inputName} name={inputName} className='input ' aria-label={ariaLabel} ref={ref} />
<div className='input_validation'>
<span>{validation}</span>
</div>
</div>
);
You can useuseRef hook which is available since v16.7.0-alpha.
EDIT: You're encouraged to use Hooks in production as of 16.8.0 release!
Hooks enable you to maintain state and handle side effects in functional components.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Read more in Hooks API documentation
EDIT: You now can with React Hooks. See the answer by Ante Gulin.
You can't access React like methods (like componentDidMount, componentWillReceiveProps, etc) on stateless components, including refs. Checkout this discussion on GH for the full convo.
The idea of stateless is that there isn't an instance created for it (state). As such, you can't attach a ref, since there's no state to attach the ref to.
Your best bet would be to pass in a callback for when the component changes and then assign that text to the parent's state.
Or, you can forego the stateless component altogether and use an normal class component.
From the docs...
You may not use the ref attribute on functional components because they don't have instances. You can, however, use the ref attribute inside the render function of a functional component.
function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
This is late but I found this solution much better.
Pay attention to how it uses useRef & how properties are available under current property.
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
For more reference check react docs
The value of your TextInput is nothing more than a state of your component. So instead of fetching the current value with a reference (bad idea in general, as far as I know) you could fetch the current state.
In a reduced version (without typing):
class Form extends React.Component {
constructor() {
this.state = { _emailAddress: '' };
this.updateEmailAddress = this.updateEmailAddress.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
updateEmailAddress(e) {
this.setState({ _emailAddress: e.target.value });
}
handleSubmit() {
console.log(this.state._emailAddress);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
value={this.state._emailAddress}
onChange={this.updateEmailAddress}
/>
</form>
);
}
}
You can also get refs into functional components with a little plumbing
import React, { useEffect, useRef } from 'react';
// Main functional, complex component
const Canvas = (props) => {
const canvasRef = useRef(null);
// Canvas State
const [canvasState, setCanvasState] = useState({
stage: null,
layer: null,
context: null,
canvas: null,
image: null
});
useEffect(() => {
canvasRef.current = canvasState;
props.getRef(canvasRef);
}, [canvasState]);
// Initialize canvas
useEffect(() => {
setupCanvas();
}, []);
// ... I'm using this for a Konva canvas with external controls ...
return (<div>...</div>);
}
// Toolbar which can do things to the canvas
const Toolbar = (props) => {
console.log("Toolbar", props.canvasRef)
// ...
}
// Parent which collects the ref from Canvas and passes to Toolbar
const CanvasView = (props) => {
const canvasRef = useRef(null);
return (
<Toolbar canvasRef={canvasRef} />
<Canvas getRef={ ref => canvasRef.current = ref.current } />
}

Resources