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 } />
}
Related
interface MyValue {
//interface declaration
}
export function MyComponent {
const [myvalue, setMyvalue] = useState<MyValue>()
useEffect(() => {
setMyvalue(passedData)
}, [passedData])
function getAutofocus() {
// return true/false based on myvalue value
}
render() {
return (
<div>
<input
autofocus={getAutofocus()}
ref={c => (this._input = c)}
/>
</div>
);
}
}
}
passedData is passed as a prop from parent and this is populated in the parent via a server GET call, which takes some time to resolve.
PROBLEM - getAutofocus() rendered before passedData is properly loaded.
my requirement here is to wait until passedData is properly resolved before invoking the
getAutofocus() method.
If we can stop the rendering of the UI/ or the input field until passedData is completely resolved, that will allow getAutofocus() to properly execute.
what's the best way to do this? can react suspense be used here?
Sounds like conditional rendering would be enough to get what you need:
render() {
// if myvalue is not populated yet, do not render anything
return !myvalue ? null : (
<div>
<input
autofocus={getAutofocus()}
ref={c => (this._input = c)}
/>
</div>
);
}
}
The proper way of doing this is with a ref
const MyCom = props => {
const inputRef = React.useRef();
React.useEffect(()=>{
if (inputRef.current) {
inputRef.current.focus();
}
},[inputRef]);
return (
<div>
<input
ref={inputRef}
/>
</div>
);
}
Remove render method only class components have render
I'm not yet a React master, hence my question. Why there is still invoking a parent function if in child component I'm writing new characters in input fields? I want to call parent method only when I clicked Search button in my child component.
Parent component:
class MainPage extends Component {
render() {
let searchOffersBar = (
<MuiThemeProvider>
<SearchOffer
offersFound={this.props.onOffersFound}
/>
</MuiThemeProvider>
);
let searchResults = (
<SearchResults
offers={this.props.offers}
/>
);
return (
<Aux>
<div className={classes.container}>
<Intro/>
<div className={classes.contentSection}>
{searchOffersBar}
{searchResults}
</div>
</div>
</Aux>
)
}
}
const mapStateToProps = state => {
return {
offers: state.offers.offers
}
}
const mapDispatchToProps = dispatch => {
return {
onOffersFound: (searchParams) => dispatch(actions.fetchOffersByCriteria(searchParams))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
<SearchOffer> is my child component with a search section (input fields and button "Search offers"). I want to fill some data in my inputs and then click the button. I though that clicking the button will invoke a method in child component: onOffersFound:
const searchOffer = props => {
let currentDate = new Date();
const [searchCriteria, setSearchCriteria] = useState({
brand: 'xxx',
capacity: 100
})
const [drawerIsOpen, setDrawerIsOpen] = useState(false);
const handleToggle = () => setDrawerIsOpen(!drawerIsOpen);
const handleBrand = (event) => {
let mergedState = updateObject(searchCriteria, {brand: event.target.value})
setSearchCriteria(mergedState);
}
const handleCapacity = (event) => {
let mergedState = updateObject(searchCriteria, {capacity: event.target.value});
setSearchCriteria(mergedState);
}
const handleBookingFrom = (bookingFromValue) => {
let mergedState = updateObject(searchCriteria, {bookingFrom: bookingFromValue});
setSearchCriteria(mergedState);
}
const handleBookingTo = (bookingToValue) => {
let mergedState = updateObject(searchCriteria, {bookingTo: bookingToValue});
setSearchCriteria(mergedState);
}
return (
<div className={classes.sideNav}>
<Button variant={"outlined"} onClick={handleToggle} className={classes.sideNavBtn}>Search</Button>
<Drawer
className={classes.drawer}
containerStyle={{top: 55}}
docked={false}
width={200}
open={drawerIsOpen}
onRequestChange={handleToggle}
>
<AppBar title="Search"/>
<form noValidate autoComplete="off" onSubmit={props.offersFound(searchCriteria)}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<Grid container justify="space-around">
<TextField
id="brand"
label="Brand"
margin="normal"
onChange={handleBrand}
/>
<TextField
id="capacity"
label="Capacity"
margin="normal"
onChange={handleCapacity}
/>
<Button variant="contained" color="primary">
Search
</Button>
</Grid>
</MuiPickersUtilsProvider>
</form>
</Drawer>
</div>
);
}
export default searchOffer;
onOffersFound in my action creator looks like:
export const fetchOffersByCriteria = (searchParams) => {
return dispatch => {
let queryParams = '?brand='+searchParams.brand + '&capacity='+searchParams.capacity;
axios.get('/getFilteredOffers' + queryParams)
.then(response => {
dispatch(saveFoundOffers(response.data)); --> saves the state
})
.catch(error => {
console.log(error);
})
}
}
My question is why the above method fetchOffersByCriteria is invoked every time I enter new character in my child component? I want to invoke this method only when I click the Search button in child component. Maybe my approach is bad?
Thanks for all tips!
The issue is that props.offersFound(searchCriteria) is being invoked every render. The onSubmit prop should be a function to be invoked when submitted. Currently, it's being invoked immediately.
This line:
onSubmit={props.offersFound(searchCriteria)}
Should be (or something similar):
onSubmit={() => props.offersFound(searchCriteria)}
Currently, when typing in the brand (or capacity) field, the handleBrand change callback is invoked. This invokes setSearchCriteria (a state update) which triggers a re-render of the component. While this component is re-rendering, it's immediately invoking props.offersFound(searchCriteria) and passing the return value to the onSubmit prop. You likely want the onSubmit prop to be a function to be invoked at the time of submitting.
See the documentation for controlled components for more de3tails.
<form
noValidate
autoComplete="off"
onSubmit={props.offersFound(searchCriteria)}>
You are immediately invoking prop and trying to use result returned as event listener. It should be
<form
noValidate
autoComplete="off"
onSubmit={() => props.offersFound(searchCriteria)}>
instead
Solved see comment below (Only had to move the Chooser and Section function outside of the class component to get it to work.
So I have a problem with the react onChange function. It does not seem to work when it is passed to a component as props. I tried to pass the component instead of the data but still it did not work. Please consider the following example:
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
type: '1',
number: ''
}
}
handleChange = e => {
const { name, value } = e.target
this.setState({ [name]: value })
}
render() {
return (
<div className="App">
<Form
type={this.state.type}
number={this.state.number}
handleChange={this.handleChange}
/>
</div>
}
}
//receiving the props
export default class Child extends Component {
constructor(props) {
super(props)
}
render() {
const Chooser = ({ type, section }) => {
switch (type) {
case '1':
return <Fragment>{section}</Fragment>
default:
return <Fragment>></Fragment>
}
}
const Section = ({ number, handleChange }) => (
<Fragment>
<div>
<label>Number</label>
<input
type='text'
name='number'
placeholder='123456789'
value={number}
onChange={handleChange}
/>
</div>
</Fragment>
)
return (
<Chooser
type={this.props.type}
section={
<Section
number={this.props.number}
handleChange={this.props.handleChange}
/>
}
/>
)
}
}
Interestingly if I put the onChange on the Section level it does work. But this is not what I want since a passed component could have multiple Input functions that I want to pass.
return (
<Chooser
type={this.props.type}
section={
<Section
number={this.pops.number}
onChange={this.pops.handleChange}
/>
}
/>
Any ideas how I can pass the onChange function down using props? On a similar example the Input change does work but it is loosing focus each time a value is pressed. Already tried assigning keys but that did not work either.
You currently have this as your handleChange method
handleChange = e => {
const { name, value } = e.target.value
this.setState({ [name]: value })
}
You should change it to this.
handleChange = e => {
const { name, value } = e.target;
this.setState({ [name]: value })
}
You seem to be accessing the wrong property in the target because name will always be undefined inside e.target.value and as such, calling setState won't do anything.
Also, you should probably be declaring your function components outside of the class component.
this is because you need to destructure it like this on the next like
const {handleChange} = this.props.handleChange
You can wrap handleChange() in an anonymous function so that it will actively wait for you to make changes to the input. Otherwise it will run on render.
const Section = ({ number, handleChange }) => (
<Fragment>
<div>
<label>Number</label>
<input
type='text'
name='number'
placeholder='123456789'
value={number}
onChange={(e) => handleChange(e)}
/>
</div>
</Fragment>
)
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>
);
};
Currently in react js, when I want to bind a text area or an input with a "state", I will need to set the onChange method and setState() everytime user type in a single letter
I heard if you setState react js refresh and re-render everything in this component
Is there any more efficient way to do so? using "shouldComponentUpdate" will be improper in this case since if I don't make "state" update, all user input will be stuck..
Well, that's how you implement controlled input elements in React.
However, if performance is a major concern of yours, you could either isolate your input element in a separate stateful component, hence only triggering a re-render on itself and not on your entire app.
So something like:
class App extends Component {
render() {
return (
<div>
...
<MyInput />
...
</div>
);
}
}
class MyInput extends Component {
constructor() {
super();
this.state = {value: ""};
}
update = (e) => {
this.setState({value: e.target.value});
}
render() {
return (
<input onChange={this.update} value={this.state.value} />
);
}
}
Alternatively, you could just use an uncontrolled input element. For example:
class App extends Component {
render() {
return (
<div>
...
<input defaultValue="" />
...
</div>
);
}
}
Though, note that controlled inputs are generally recommended.
As #Chris stated, you should create another component to optimize the rerendering to only the specified component.
However, there are usecases where you need to update the parent component or dispatch an action with the value entered in your input to one of your reducers.
For example I created a SearchInput component which updates itself for every character entered in the input but only call the onChange function only if there are 3 characters at least.
Note: The clearTimeout is useful in order to call the onChange function only when the user has stopped typing for at least 200ms.
import React from 'react';
class SearchInput extends React.Component {
constructor(props) {
super(props);
this.tabTimeoutId = [];
this.state = {
value: this.props.value,
};
this.onChangeSearch = this.onChangeSearch.bind(this);
}
componentWillUpdate() {
// If the timoutId exists, it means a timeout is being launch
if (this.tabTimeoutId.length > 1) {
clearTimeout(this.tabTimeoutId[this.tabTimeoutId.length - 2]);
}
}
onChangeSearch(event) {
const { value } = event.target;
this.setState({
value,
});
const timeoutId = setTimeout(() => {
value.length >= this.props.minSearchLength ? this.props.onChange(value) : this.props.resetSearch();
this.tabTimeoutId = [];
}, this.props.searchDelay);
this.tabTimeoutId.push(timeoutId);
}
render() {
const {
onChange,
minSearchLength,
searchDelay,
...otherProps,
} = this.props;
return <input
{...otherProps}
value={this.state.value}
onChange={event => this.onChangeSearch(event)}
/>
}
}
SearchInput.propTypes = {
minSearchLength: React.PropTypes.number,
searchDelay: React.PropTypes.number,
};
SearchInput.defaultProps = {
minSearchLength: 3,
searchDelay: 200,
};
export default SearchInput;
Hope it helps.
You need to bind the onChange() event function inside constructor like as code snippets :
class MyComponent extends Component {
constructor() {
super();
this.state = {value: ""};
this.onChange = this.onChange.bind(this)
}
onChange= (e)=>{
const formthis = this;
let {name, value} = e.target;
formthis.setState({
[name]: value
});
}
render() {
return (
<div>
<form>
<input type="text" name="name" onChange={this.onChange} />
<input type="text" name="email" onChange={this.onChange} />
<input type="text" name="phone" onChange={this.onChange} />
<input type="submit" name="submit" value="Submit" />
</form>
</div>
);
}
}
You don't need a complicated react solution to this problem, just a little common sense about when to update state. The best way to achieve this is to encapsulate your setState call within a timeout.
class Element extends React.Component {
onChange = (e) => {
clearTimeout(this.setStateTimeout)
this.setStateTimeout = setTimeout(()=> {
this.setState({inputValue: e.target.value})
}, 500)
}
}
This will only set state on your react element a 500ms after the last keystroke and will prevent hammering the element with rerenders as your user is typing.