React.js, improve generating object from form inputs - reactjs

In vanilla JS I could just use formData for this, and make an Object.fromEntries() from it.
In React I wasn't sure how to do this right, so here's what I came up with after some googling around:
class App extends Component{
constructor(props){
super(props);
this.autoData = {
title:'',
description:'',
year:'',
color:'',
status:'',
price:''
}
}
handleAutoData = e => {
if (e.target.id in this.autoData) {
this.autoData[e.target.id] = e.target.value
}
}
handleAutoForm = e => {
e.preventDefault()
if (Object.keys(this.autoData).every(k => this.autoData[k])) {
this.props.addAuto(this.autoData)
}
}
render(){
...
return (
<div className="App">
<form className="form" onSubmit={this.handleAutoForm}>
<input type="text" className="form_text" placeholder="Name" id="title" onChange={this.handleAutoData} />
<input type="text" className="form_text" placeholder="Year" id="year" onChange={this.handleAutoData} />
<input type="text" className="form_text" placeholder="Price" id="price" onChange={this.handleAutoData} />
<input type="text" className="form_text" placeholder="Details" id="description" onChange={this.handleAutoData} />
<button type="submit"> Send > </button>
</form>
</div>
)
}
}
This does the job and handleAutoForm pushes the object into Redux store. However:
Is there a better alternative for onChange event? I know it's the go-to way of handling form inputs, but right now it's spamming/overwriting my values on every keystroke. I'd only like to push a value once I stop typing/field loses focus. Out of alternatives, I saw articles of onFocusOut, but it's not supported or has issues.
Right now I'm mutating the component's state directly. Not sure if it's critical, since I'll be pushing the state to Redux anyway. I wanted a local object inside handleAutoData, just so I could write the values into it, but every time an onChange is called, a new object is made and it overwrites the previous values. Problem is, I can't use setState because of e.target's nature - it keeps complaining about missing brackets on render, because of all the dots (when I do something like this.setState({autoData[e.target.id]:e.target.value}) ). And if I assign it to a temporary variable (like let autoKey = e.target.id), setState pushes the autoKey as key, instead of e.target.id. What could be done with this?

Usual way is to use name or id along with onChange event.
state = {
title:'',
description:'',
year:'',
color:'',
status:'',
price:''
}
handleChange = (e) => {
this.setState({
[e.target.name]:e.target.value
});
handleSubmit = (e)=>{
e.preventDefault();
//Push state to redux,make API
call etc
}

Related

How access specific DOM in React js

I'm trying to get input tags' HTML inner values when submitting a form.
private handleSubmit = (event: any) => {
event.preventDefault();
console.log(event.currentTarget);
};
When the submit the form, it calls the function handleSubmit and it console logs the following.
Under the form tag, the first div has username value and the second div has password value. I would like to acess the two values. I think I should use DOM to do that, but can't be sure if I'm going for the right direction cuz I found some postings saying using DOM is not recommended.
Can anyone explain how I can acheive this?
Ideally you should update your state as the user enters information, and then access the data from the state. This would also allow you to run any validation on the data prior to it going into the state if you'd like.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: null,
password: null
}
this.submitForm = this.submitForm.bind(this);
this.updateState = this.updateState.bind(this);
}
updateState (e) {
this.setState({[e.target.name]: e.target.value})
}
submitForm (e) {
e.preventDefault();
console.log(this.state);
}
render() {
return (
<div className="App">
<form onSubmit={this.submitForm}>
<input type="text" name="username" placeholder="username" onChange={this.updateState} /><br />
<input type="password" name="password" placeholder="password" onChange={this.updateState} /><br />
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default App;
The above code does the following:
Stores default values for username and password. While this isn't required, it makes the code more readable
binds this to functions that need to access state
Uses an updateState() function that is called onChange of the inputs
updateState uses the name attribute of the input as the key for the state
You could customize the updateState() function to do some validation, before saving to state if you'd like.
Excessive Rendering
ReactJS is pretty smart to no re-render the REAL DOM if your render() method doesn't actually rely on the state values that were updated; however, if you'd like to prevent ReactJS from even creating the Virtual DOM and comparing, you could utilize the shouldComponentUpdate() lifecycle hook.
In the particular example above, since render doesn't rely on ANYTHING in state, you could simply add the following method:
shouldComponentUpdate(prevState, nextState) {
return false;
}
That will prevent the render method from EVER re-rendering, which is most likely not going to work in a normal component, thus you could do something like this instead, only re-rendering on values you care about.
shouldComponentUpdate(prevState, nextState) {
if (nextState.email !== prevState.email) {
return true
}
return false;
}
Demo
https://repl.it/#AnonymousSB/SO53689072
If you want to use the DOM, once you have the form element (event.currentTarget in your case), you can use the .elements property to access a list of child inputs and buttons.
Alternatively, you can use React refs to keep track of the underlying HTML element when it's rendered.
render() {
return ... <input ref={(e) => this._name = e; } ....> ... ;
}
handleSubmit(e) {
var name = this._name ? this._name.value : '';
....
}
This can achieve what you want to
class Login extends Component{
state={
username:"",
password:""
}
onChange = (event)=>{
event.preventDefault()
this.setState({
[event.target.name]: event.target.value})
}
onSubmit = (event)=>{
event.preventDefault()
// submit whatever is in state from here
console.log(this.state)
}
render(){
return(<div>
<form onSubmit={handleSubmit}>
<input type="text" name="username" onChange={this.onChange} /><br />
<input type="password" name="password" onChange={this.onChange} /><br />
<button type="submit">Submit</button>
</form>
</div>)
}
}

Do forms in React have bad performance?

I'm learning how to use <form>'s in React and most examples I've seen use a combination of state and onChange to keep track of your form's inputs:
class Form extends React.Component {
handleChange(event) {
this.setState({
inputvalue: event.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label>Name</label>
<input type="text" value={this.state.inputvalue} onChange={this.handleChange.bind(this)}/>
<input type="submit" value="Submit"/>
</form>
);
}
}
However, say I have numerous <input>'s and even some <textarea>'s which might change quite often. In that case each one of them would call the onChange method each time they are updated and the component would re-render on every key press.
Seeing as how people can type pretty fast, could this be an area for concern?
In a small testing I discovered that React successfully performs a shallow compare in the state and changes in the DOM in just the components that need a re-render. In Chrome I enabled the highlights (Paint Flashing) of the areas that were repainted by React in the DOM.
See the Paint Flashing in action.
In my example note that onChange will run on every keystroke to update the React state, the displayed value will update as the user types (based on the React Docs https://reactjs.org/docs/forms.html#controlled-components).
Also you can see my code here: https://codepen.io/anon/pen/KxjJRp
class Application extends React.Component {
state = {
value1: "",
value2: "",
value3: "",
value4: ""
}
onChange = ({target: {value, name}}) => {
this.setState({
[name]: value
})
}
render() {
const { state: { value1, value2, value3, value4 } } = this
return (
<div>
<label>Value 1</label>
<input type="text" value={value1} name="value1" onChange={this.onChange}/>
<label>Value 2</label>
<input type="text" value={value2} name="value2" onChange={this.onChange}/>
<label>Value 3</label>
<input type="text" value={value3} name="value3" onChange={this.onChange}/>
<label>Value 4</label>
<input type="text" value={value4} name="value4" onChange={this.onChange}/>
</div>
)
}
}
I am not totally sure of the best way to handle this but I could see an implementation of setting an onblur method to handle updating state. onChange would be constantly updating but this might not be the worst thing as it likely will not re-render the page with every keystroke.

ReactJS unable to edit the textbox created through state variable

I have the following code where the issue is the amount1 which the textbox is created through state is unable to edit. When i edit this textbox the value 10 is not changing from the textbox. The other normal textbox works as usual. Anyone knows the issue?
constructor(props) {
super(props);
this.state = {
amount1:"10",
amount2:"20",
input:"",
}
}
componentDidMount(){
var options = <input type="text" name="amount1" value={this.state.amount1}>
this.setState({'input':options})
}
render() {
return(
<div>
{this.state.input}
<input type="text" name="amount2" value={this.state.amount2}>
</div>
)
}
You are missing the close tag of input and since you are passing value from the state the input becomes read-only. So try defaultValue instead of value to make it editable.
Both your inputs don't change their value since they are bound to the values in the state,
this is your code
What you need to do is add an event handler to your input tag that changes the value saved in the state.
handleAmountInput = (event) => { this.setState({ amount: event.target.value }) }
render() {
return( this.handleAmountInput(event)} value={this.state.amount}> )
}
And you should close your input tag <input />
I think if you changed your render to the following, along w/ the onChange binding event, you should get the appropriate values in state.
onChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
render() {
return (
<div>
<input
type="text"
name="amount1"
value={this.state.amount1}
onChange={this.onChange.bind(this)}
/>
<input
type="text"
name="amount2"
value={this.state.amount2}
onChange={this.onChange.bind(this)}
/>
</div>
);
}
As mentioned previously, you are creating readOnly, uncontrolled elements. This is a good article on controlled (react recommended) vs uncontrolled:
https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/

A single onChange listener on a <form> tag

I was just playing a bit with a more generic way of dealing with form data, and figured that setting a single onChange listener on the wrapping form tag should work for all changes to input fields.
And indeed, it does - no surprise there. Events get called, I know what changed by inspecting event.target.name and can update state accordingly. Fully as expected.
However, React doesn't seem to like it and spams the well known "You provided a value prop to a form field without an onChange handler" console warning.
Would there be any other reason for not doing this, apart from the console warning? It seems to eliminate a lot of duplication React otherwise gets criticised about.
class App extends Component {
state = {
name: 'Default Name',
number: 12,
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
})
}
render() {
const { crashLogo } = this.props;
return (
<div className="App">
<form onChange={this.handleChange}>
<input type="text" name="name" value={this.state.name} />
<input type="number" name="number" value={this.state.number} />
</form>
</div>
);
}
}
Just for clarity: I'm not asking for alternative solutions, I know I can set the same change listener directly on every input, use redux-form, own wrapper components with context magic and so on...

How to clear uncontrolled field in react

I used to use ref for forms but now I always state for forms, I'm facing an issue where I have to clear a field after user submitted something.
handleSumbit = (e) => {
e.preventDefault()
const todoText = this.state.todoText
if(todoText.length > 0){
this.refs.todoTextElem = "" // wont work
this.props.onAddTodo(todoText)
} else {
this.refs.todoTextElem.focus() //worked
}
}
render() {
return(
<div>
<form onSubmit={this.handleSumbit}>
<input ref="todoTextElem" type="text" onChange={e => this.setState({todoText: e.target.value})} name="todoText" placeholder="What do you need to do?" />
<button className="button expanded">Add Todo</button>
</form>
</div>
)
}
Clearing the ref simply don't work because it's a controlled input. I don't want to do something stupid like
passing a flag from parent component telling the form is submitted then use setState to clear the input. Or make onAddTodo to have a callback so that I can do
this.props.onAddTodo(todoText).then(()=>this.state({todoText:""}))
The way you are using the input element is uncontrolled, because you are not using the value property, means not controlling it's value. Simply storing the value in state variable.
You don't need to store the input field value in state variable if you are using ref, ref will have the reference of DOM element, so you need to use this.refName.value to access the value of that element.
Steps:
1- Write the input element like this:
<input
ref= {el => this.todoTextElem = el}
type="text"
placeholder="What do you need to do?" />
To get it's value: this.todoTextElem.value
2- To clear the uncontrolled input field, clear it's value using ref:
this.todoTextElem.value = '';
Write it like this:
handleSumbit = (e) => {
e.preventDefault()
const todoText = this.todoTextElem.value;
if(todoText.length > 0){
this.todoTextElem.value = ''; //here
this.props.onAddTodo(todoText)
} else {
this.todoTextElem.focus()
}
}
Another change is about the string refs, As per DOC:
If you worked with React before, you might be familiar with an older
API where the ref attribute is a string, like "textInput", and the DOM
node is accessed as this.refs.textInput. We advise against it because
string refs have some issues, are considered legacy, and are likely to
be removed in one of the future releases. If you're currently using
this.refs.textInput to access refs, we recommend the callback pattern
instead.
Try and use functional refs instead. Note that the ref is to a DOM element, meaning you still need to address its properties (.value) to modify them as opposed to trying to overwriting the element directly.
The following should work:
handleSumbit = (e) => {
e.preventDefault()
const todoText = this.state.todoText
if(todoText.length > 0){
this.todoTextElem.value = ""
this.props.onAddTodo(todoText)
} else {
this.todoTextElem.focus()
}
}
render() {
return(
<div>
<form onSubmit={this.handleSumbit}>
<input ref={input => this.todoTextElem = input} type="text" onChange={e => this.setState({todoText: e.target.value})} name="todoText" placeholder="What do you need to do?" />
<button className="button expanded">Add Todo</button>
</form>
</div>
)
}

Resources