React. Can I get rid of ref in favor of onChange? - reactjs

According to React spec:
https://reactjs.org/docs/refs-and-the-dom.html
"There are a few good use cases for refs:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
Avoid using refs for anything that can be done declaratively."
That's why I'm not so sure for now, whether I used ref properly or not in this case:
export let FormInput = createReactClass({
handleInput(e){
e.preventDefault();
const product = this.name.value;
const red = this.red.checked;
this.props.addProduct(product,red);
this.inputForm.reset();
},
render(){
return(
<form className="prod_input" ref={x => this.inputForm = x} onSubmit={this.handleInput}>
<input type="text" ref={x => this.name = x} placeholder="Product name"/>
<input type="checkbox" ref={x => this.red = x} value="true"/>
<input type="submit" hidden/>
</form>
)
}
})
If not, how could it be rearranged in order to replace ref to onChange={}?

You can avoid using refs by making your input fields controlled. Here is an example:
export let FormInput = createReactClass({
constructor(props){
super(props);
this.state = {
name: '',
};
}
handleNameChange(updatedName){
this.setState({ name: updatedName });
}
handleInput(e){
e.preventDefault();
const product = this.state.name;
// ...
this.props.addProduct(product);
this.inputForm.reset();
},
render(){
return (
<form className="prod_input" ref={x => this.inputForm = x} onSubmit={this.handleInput}>
<input type="text" value={this.state.name} placeholder="Product name" onChange={e => this.handleNameChange(e.target.value)} />
// ...
</form>
);
}
})
The idea is to keep the value of your fields in your local state. Then you can set the value of each of your fields to the value currently stored in your state, and attach an onChange handler that updates this value every the user types something. When you submit your form, all your values are available directly in your component's state.

Related

React good practice - how to structure Parent and Child components including forms and where to keep state?

I have a question about reacts good practices when it comes to forms as to where to keep state (only parent etc). 2 approaches below:
Child (the form and input fields) has own onChange event and has state, only passes onsubmit the values back to parent and parent only passes down function to pass back those values
this approach Ive seen more. Child has no state (seems better as then the child is really just a presentational component and parent stores all the data and has state). However, then child passes 'back' the values on each onChange event (with each letter typed), ONLY to receive BACK the value it 'gave' to parent (since inputs need value most cases), which seems like back and forth data passing =waste?
I created 2 small versions to demonstrate. I would really appreciate opinions as to what way is better, since Im always wondering.
Thank you!!
class App extends React.Component {
state = {
name: "",
country: "",
result: ""
}
onChange = (key, val) => {
this.setState({
[key]: val
})
}
onSubmit = () => {
this.state({
result: this.state.name + " is from " + this.state.country
})
}
render() {
return (
<div>
<Child onChange={this.onChange} name={this.state.name} country={this.state.country}/>
<h1>{this.state.result}</h1>
</div>
)
}
}
const Child = ({onChange, onSubmit, name, country}) => {
return (
<form onSubmit={onSubmit}>
<input name="name" placeholder="name" value={name}
onChange={({target: {name, value}}) => onChange(name, value)}/>
<input name="country" placeholder="country" value={country}
onChange={({target: {name, value}}) => onChange(name, value)}/>
<input type="submit"/>
</form>
)
};
2nd version, where I keep state inside the child
class App extends React.Component {
state = {
result: ""
};
onSubmit = (event, name, country) => {
event.preventDefault();
this.setState({
result: name + " is from " + country
})
};
render() {
return (
<div>
<Child onSubmit={this.onSubmit}/>
<h1>{this.state.result}</h1>
</div>
)
}
}
class Child extends React.Component {
state = {};
onChange = ({target: {name, value}}) => {
this.setState({
[name]: value
})
};
render() {
return (
<form onSubmit={() => this.props.onSubmit(event, this.state.name, this.state.country)}>
<input name="name" placeholder="name" onChange={this.onChange} value={this.state.name}/>
<input name="country" placeholder="country" onChange={this.onChange} value={this.state.country}/>
<input type="submit"/>
</form>
)
}
}

emptying the input after submiting a form

I'm trying to empty the input tag once I'm updating my state:
state = {
formName: '',
inputs: [],
tempInput: {
inputLabel: '',
inputType: '',
inputValue: ''
}
};
this is my form:
<div className="formG">
<form className="form-maker" onSubmit={this.handleSubmit}>
Label:
<input name="inputLabel" type="text" onChange={this.handleChange} />
Type:
<input name="inputType" type="text" onChange={this.handleChange} />
Value
<input name="inputValue" type="text" onChange={this.handleChange} />
Form Name
<input name="formName" type="text" onChange={this.formName} />
<button>Submit</button>
</form>
that's how I handle the change
handleChange = e => {
const { name, value } = e.target;
this.setState(currentState => ({
tempInput: { ...currentState.tempInput, [name]: value }
}));
};
and I tried to just empty the tempInput but it doesn't work, anybody knows why?
handleSubmit = e => {
e.preventDefault();
const inputs = [...this.state.inputs, this.state.tempInput];
const { tempInput } = this.state;
tempInput.inputLabel = '';
tempInput.inputType = '';
tempInput.inputValue = '';
this.setState({ inputs, tempInput });
};
Your form is an uncontrolled component, so they are not controlled by the state fields. That's why your approach didn't work. Instead you can do e.target.reset() which will clear the entire form. But if you want to reset some input, you can access them and set the .value to "" as I had shown below.
An uncontrolled component works like form elements do outside of React. When a user inputs data into a form field (an input box, dropdown, etc) the updated information is reflected without React needing to do anything. However, this also means that you can’t force the field to have a certain value. From Doc
So your handleSubmit method will look like:
handleSubmit = e => {
e.preventDefault();
const inputs = [...this.state.inputs, this.state.tempInput];
// ....
// The below will reset entire form.
// e.target.reset();
// If you want some of them to empty.
const { elements } = e.target
elements['inputLabel'].value = "";
elements['inputType'].value = "";
elements['inputValue'].value = "";
};
Check the doc of HTMLFormElement.elements
Your input tags are not displaying the value of your state.
1) pull the individual values out of tempInput
2) use the value stored in your state that is then updated by your handleChange.
3) In your handleSubmit function reset your individual values to and empty string.
your handleChange should look like:
handleChange = e => {
const { name, value } = e.target;
this.setState([name]: value);
};
your jsx should look like :
<form className="form-maker" onSubmit={this.handleSubmit}>
Label:
<input name="inputLabel" value={this.state.inputLabel} type="text" onChange={this.handleChange} />
Type:
<input name="inputType" value={this.state.inputType} type="text" onChange={this.handleChange} />
Value
<input name="inputValue" value={this.state.inputType} type="text" onChange={this.handleChange} />
Form Name
<input name="formName" value={this.state.formName} type="text" onChange={this.formName} />
<button>Submit</button>
</form>
You're mutating the original state. You can copy and then only set the state. Just changing the following will work fine for you.
Replace this:
const { tempInput } = this.state;
With this:
const { tempInput } = {...this.state}; // copy the state
Also, be sure to bind the state value in your input elements like this to make them controlled component:
<input name="inputLabel" type="text"
onChange={this.handleChange}
value={this.state.tempInput.inputLabel || ''} />
And your handler should be:
handleChange = e => {
const { value } = e.target;
this.setState({value});
// now, value will correspond to the controlled component
};
Also take care react suggest to use controlled component as far as possible:
In most cases, we recommend using controlled components to implement forms.

Efficient way to init state storage of forms

I'm working on a quite comprehensive form and was wondering if there is a smart way to prevent me of doing the following state initialisation:
class Demo extends React.Component {
state = { firstName = "",
secondName = "" };
//and so on...
render() {
const { firstName, secondName } = this.state;
//and so on
return (
<div>
<Form>
<Form.Input
placeholder="Name"
name="name"
value={firstName}
/>
//and so on
</Form>
</div>
);
}
}
If I don't init the state with empty strings I get the following Warning:
Component is changing an uncontrolled input of type text to be
controlled. Input elements should not switch from uncontrolled to
controlled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.
What's the state of the art way to deal with this?
Thanks!
Stefan
If you are using value as your state value then it will be undefined in render method in case you don't initialize state. So it is recommended to initialize your state like you're doing because you have controlled inputs. I believe this would help your case
Something like this should work but problem is you need input handlers for each fields which is cumbersome if you have huge form.
class Demo extends React.Component {
constructor(props) {
super(props)
this.state = {
firstName: '',
lastName: '',
submitted: false
};
}
handleFirstName = (e) => {
this.setState({firstName: e.target.value});
}
handleLastName = (e) => {
this.setState({lastName: e.target.value});
}
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const payload = [{this.state.firstName, this.state.lastName}];
this.props.saveData(payload);
}
render() {
return(
<form>
<label>
Firstname:
<input type="text" value={this.state.firstName} onChange={this.handleFirstName} />
</label>
<label>
Lastname:
<input type="text" value={this.state.lastName} onChange={this.handleLastName} />
</label>
<input type="submit" value="Submit" onClick={this.handleSubmit} />
</form>
);
}
}
Better solution for handling form is using lib like react-final-form

reactjs container component form values

I am new to react and am trying to get the value from the firstName input in handleSubmit. handleSubmit is working but for some reason the input value is undefined. I tried following some other examples but they were all forms in React components.
let SomeForm = (props) => {
let firstName = '';
const handleSubmit = (e) => {
e.preventDefault();
console.log(firstName);
}
return (
<div>
<form onSubmit={handleSubmit}>
<TextField
floatingLabelText="First Name"
floatingLabelFixed={true}
underlineStyle={styles.underlineStyle}
underlineFocusStyle={styles.underlineFocusStyle}
value={firstName}
/>
<input type="submit" value="Submit" />
</form>
</div>
)
}
SomeForm = connect()(SomeForm)
export default SomeForm
You need to add onChange to the TextField to handle the updates. And because SomeForm is a stateful component, you may need to use a class component instead of a stateless functional component.
class SomeForm extends React.Component {
state = {
firstName: ''
};
handleChange = (e, value) => {
this.setState({ firstName: value });
};
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state.firstName);
};
render () {
return (
<div>
<form onSubmit={this.handleSubmit}>
<TextField
id="text-field-controlled"
floatingLabelText="First Name"
floatingLabelFixed={true}
underlineStyle={styles.underlineStyle}
underlineFocusStyle={styles.underlineFocusStyle}
value={this.state.firstName}
onChange={this.handleChange}
/>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
See the API and examples of TextField here: http://www.material-ui.com/#/components/text-field
You need function that you will throw in your TextField component and with onChange you can get value of input.
handleChange(e){
Console.log(e.target.value)
}
And in TextField
<TextField onChange={this.handleCahnge}/}
Or use refs
<TextField ref="textfield" />
And get value with this.refs.textfield.getValue()

React Modifying Textarea Values

I am working on a project which is basically notepad. I am having problems though updating the <textarea>'s value when an ajax call is made. I tried setting the textarea's value property but then no changes to its value can be made. How can I make it so on a state change the textarea's value changes and can be edited.
The code I have is as follows.
In the parent class
<Editor name={this.state.fileData} />
In the Editor class
var Editor = React.createClass({
render: function() {
return (
<form id="noter-save-form" method="POST">
<textarea id="noter-text-area" name="textarea" value={this.props.name}></textarea>
<input type="submit" value="Save" />
</form>
);
}
});
I can't use defaultValue because the value of the textarea is not known on page load and when I try and put the data between the textareas nothing happens. I would like it to take the state value whenever the state changes but have it editable in between.
Thanks
Edit
I managed to get it working using jQuery but would like to do it in React instead, I called this before render:
$('#noter-text-area').val(this.props.name);
I think you want something along the line of:
Parent:
<Editor name={this.state.fileData} />
Editor:
var Editor = React.createClass({
displayName: 'Editor',
propTypes: {
name: React.PropTypes.string.isRequired
},
getInitialState: function() {
return {
value: this.props.name
};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
return (
<form id="noter-save-form" method="POST">
<textarea id="noter-text-area" name="textarea" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Save" />
</form>
);
}
});
This is basically a direct copy of the example provided on https://facebook.github.io/react/docs/forms.html
Update for React 16.8:
import React, { useState } from 'react';
const Editor = (props) => {
const [value, setValue] = useState(props.name);
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<form id="noter-save-form" method="POST">
<textarea id="noter-text-area" name="textarea" value={value} onChange={handleChange} />
<input type="submit" value="Save" />
</form>
);
}
Editor.propTypes = {
name: PropTypes.string.isRequired
};
As a newbie in React world, I came across a similar issues where I could not edit the textarea and struggled with binding. It's worth knowing about controlled and uncontrolled elements when it comes to react.
The value of the following uncontrolled textarea cannot be changed because of value
<textarea type="text" value="some value"
onChange={(event) => this.handleOnChange(event)}></textarea>
The value of the following uncontrolled textarea can be changed because of use of defaultValue or no value attribute
<textarea type="text" defaultValue="sample"
onChange={(event) => this.handleOnChange(event)}></textarea>
<textarea type="text"
onChange={(event) => this.handleOnChange(event)}></textarea>
The value of the following controlled textarea can be changed because of how
value is mapped to a state as well as the onChange event listener
<textarea value={this.state.textareaValue}
onChange={(event) => this.handleOnChange(event)}></textarea>
Here is my solution using different syntax. I prefer the auto-bind than manual binding however, if I were to not use {(event) => this.onXXXX(event)} then that would cause the content of textarea to be not editable OR the event.preventDefault() does not work as expected. Still a lot to learn I suppose.
class Editor extends React.Component {
constructor(props) {
super(props)
this.state = {
textareaValue: ''
}
}
handleOnChange(event) {
this.setState({
textareaValue: event.target.value
})
}
handleOnSubmit(event) {
event.preventDefault();
this.setState({
textareaValue: this.state.textareaValue + ' [Saved on ' + (new Date()).toLocaleString() + ']'
})
}
render() {
return <div>
<form onSubmit={(event) => this.handleOnSubmit(event)}>
<textarea rows={10} cols={30} value={this.state.textareaValue}
onChange={(event) => this.handleOnChange(event)}></textarea>
<br/>
<input type="submit" value="Save"/>
</form>
</div>
}
}
ReactDOM.render(<Editor />, document.getElementById("content"));
The versions of libraries are
"babel-cli": "6.24.1",
"babel-preset-react": "6.24.1"
"React & ReactDOM v15.5.4"

Resources