React, setting initial value to input element - reactjs

I have a list of students on my page. When I click on some student, a form should appear with input element for all of student's properties. When form is submitted, student's properties should change. The problem is when I want to set initial value of input elements as a properties of a current selected student. The value of input elements is filled with properties of a current student, but when I try to type something in that input, it's value does not change.
Code of Parent component
class App extends Component {
constructor(props){
super(props);
this.state = {
students :listStudents(),
visible: false,
index: 0,
student: listStudents()[0]
};
this.onClick = this.onClick.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onNewStudentSubmit = this.onNewStudentSubmit.bind(this);
}
onClick(index){
if(this.state.visible){
this.setState({
visible: false,
index: 0
});
}
else {
let student1 = this.state.students
.filter(s => s.index === index);
this.setState({
visible: true,
index: index,
student: student1[0]
});
}
}
onSubmit(student){
let list = this.state.students
.map(s => {
if(s.index === this.state.index)
return student;
return s;
});
this.setState({
students : list,
visible: false
});
}
render() {
return (
<div className="App">
<StudentsList students={this.state.students} onClick={this.onClick}/>
<EditStudentDetails student={this.state.student} visible={this.state.visible} onSubmit={this.onSubmit}/>
<CreateStudent onSubmit={this.onNewStudentSubmit}/>
</div>
);
}
}
export default App;
onClick function changes selected student and stores it in state.student.
onSubmit function is triggered when the student's editing form is submitted.
Render returns the list of all students and a editing component which can be visible or not. Below is code of my editing component.
import React from 'react';
class EditStudentDetails extends React.Component{
constructor(props){
super(props);
this.state = {
name: "",
surname: "",
index : "",
class : "",
visible: ""
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
this.handleSurnameChange = this.handleSurnameChange.bind(this);
this.handleClassChange = this.handleClassChange.bind(this);
this.handleIndexChange = this.handleIndexChange.bind(this);
}
handleSubmit(e){
const student = {
name: this.state.name,
surname: this.state.surname,
index : this.state.index,
class : this.state.class
};
this.props.onSubmit(student);
e.preventDefault();
}
handleNameChange(e){
const newName = e.target.value;
this.setState({
name: newName
});
}
handleSurnameChange(e){
const newName = e.target.value;
this.setState({
surname: newName
});
}
handleIndexChange(e){
const newIndex = e.target.value;
this.setState({
index: newIndex
});
}
handleClassChange(e){
const newClass = e.target.value;
this.setState({
class: newClass
});
}
render(){
const type = this.props.visible ? {display: 'block'} : {display: 'none'} ;
return(
<div style={type}>
<form className="form-group" onSubmit={this.handleSubmit}>
Име
<input className="Name" type="text" onChange={this.handleNameChange} value={this.props.student.name}/>
Презиме
<input className="Surname" type="text" onChange={this.handleSurnameChange} value={this.props.student.surname}/>
Индекс
<input className="Index" type="text" onChange={this.handleIndexChange} value={this.props.student.index}/>
Насока
<input className="Class" type="text" onChange={this.handleClassChange} value={this.props.student.class}/>
<input className="submit btn" type="submit" value="Промени"/>
</form>
</div>
);
}
}
export default EditStudentDetails;
I try to set initial value by setting each input's value to the appropriate prop. Then when something is changed in the input component state should update. But as I said, the value of each input does not change.

You are telling React to use the passed prop as the input value, and since the props are not changed, the value isn't either. Set the passed props to the component state and update the state when the input is changed.
If you want to shave of some bytes, you could also use a more general onChange method as in the example below, given you are able to set a name property on the input fields.
class EditStudentDetails extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
surname: "",
index: "",
class: "",
visible: "",
...this.props.student
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(e) {
const student = {
name: this.state.name,
surname: this.state.surname,
index: this.state.index,
class: this.state.class
};
this.props.onSubmit(student);
e.preventDefault();
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
const type = this.props.visible ? { display: 'block' } : { display: 'none' };
return (
<div style={type}>
<form className="form-group" onSubmit={this.handleSubmit}>
Име
<input className="Name" name="name" type="text" onChange={this.handleChange} value={this.state.name} />
Презиме
<input className="Surname" name="surname" type="text" onChange={this.handleChange} value={this.state.surname} />
Индекс
<input className="Index" name="index" type="text" onChange={this.handleChange} value={this.state.index} />
Насока
<input className="Class" name="class" type="text" onChange={this.handleChange} value={this.state.class} />
<input className="submit btn" type="submit" value="Промени" />
</form>
</div>
);
}
}

The problem is here.
<input className="Name" type="text" onChange={this.handleNameChange} value={this.props.student.name}/>
The value of the input is this.props.student.name,but in the this.handleNameChange, you change this.state.name,so when you type something,you can't see the change in the input value.
I suppose you need something like this:
this.state = {
class: props.student.name
}
...
<input className="Name" type="text" onChange={this.handleNameChange} value={this.state.name}/>

Related

want to set value to an array key in react state

In the below code I have a state inside in emplist array with some keys and by onchange() event i want to set the value to emplist array keys. How can i set that values please help me :).
import React from 'react';
export default class Registrations extends React.Component{
constructor(props){
super(props);
this.state = {
EmployeeList:[
{ EmployeeId : '' },
{ EmployeeName :'' },
{ EmployeeSalary :'' },
{ EmployeeAddress : '' },
{ EmployeeDetails :false },
],
};
}
handleChange = (e) =>{
// some code actions//
}
render(){
var {EmployeeId,EmployeeName,EmployeeSalary,EmployeeAddress} = this.state;
return(
<div>
<form>
<input type="text" placeholder="Enter EmployeeId" onChange={this.handleChange}
value={EmployeeId} name='EmployeeId'/><br />
<input type="text" placeholder="Enter EmployeeName" onChange={this.handleChange}
value={EmployeeName} name='EmployeeName'/><br />
<input type="text" placeholder="Enter EmployeeSalary" onChange=
{this.handleChange} value={EmployeeSalary} name='EmployeeSalary'/><br />
<input type="text" placeholder="Enter EmployeeAddress" onChange=
{this.handleChange} value={EmployeeAddress} name='EmployeeAddress'/><br />
<button onClick={this.handleSubmit}>Submit</button>
</form>
</div>
);
}
}
Can you explain more about what you want to do.
In your code above, you type in the data for each piece of data inside a single employee object, so you state should has this shape:
this.state = {
Employee: {
EmployeeId: '',
EmployeeName: '',
EmployeeSalary: '',
EmployeeAddress: '',
EmployeeDetails: false
}
};
handleChange = (e) => {
this.setState({
Employee: {...this.state.Employee, [e.target.name]: e.target.value }
})
};
render() {
var {
EmployeeId,
EmployeeName,
EmployeeSalary,
EmployeeAddress,
} = this.state.Employee; // change the destructuring as well
}

React couldn't read the state

I used the same function I got on a react native in a react app and it didn't work, looks like I couldn't access the sate although I defined it in the constructor, the goal is to push data to firebase, I tried with random strings and it definitely works, it's just when using the form that it crashes.
As you can see I'm using text components to take a look a the state on the HTML page :
import React, { Component } from 'react';
import fire from './config/Fire';
class Home extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
isOpen: false,
title: '',
description: '',
loading: true
};
}
handleChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
saveData(e) {
e.preventDefault();
let title = this.state.title;
let description = this.state.description;
const { currentUser } = fire.auth();
fire
.database()
.ref(`/master/setup/`)
.push({ title, description })
.then(() => {
this.setState({ loading: false }).catch(error => {
console.log(error);
});
});
}
render() {
return (
<div>
<Container>
<Row>
<Col sm="2" lg="3" />
<Col sm="8" lg="6">
<h1>General Setup</h1>
<form>
<div class="form-group">
<label for="exampleInputEmail1">Title</label>
<input
value={this.state.title}
onChange={this.handleChange}
name="title"
class="form-control"
id="title"
placeholder="Enter event title"
/>
</div>
<div class="form-group">
<label for="exampleInputEmail1">Description</label>
<input
value={this.state.description}
onChange={this.handleChange}
name="description"
class="form-control"
id="description"
placeholder="Enter event description"
/>
</div>
<button onClick onClick={this.saveData} class="btn btn-primary">
Submit
</button>
</form>
<p>{this.state.title}</p>
<p>{this.state.description}</p>
<p>{this.state.loading.toString()}</p>
</Col>
<Col sm="2" lg="3" />
</Row>
</Container>
</div>
);
}
}
export default Home;
TypeError: Cannot read property 'state' of undefined
Please, someone, let me know what's going on with this code?
You can change saveData to an arrow function hence binding isn't required. This is an ES6 version, do something like below
saveData = e => {
e.preventDefault();
let title = this.state.title;
let description = this.state.description;
const { currentUser } = fire.auth();
fire.database().ref(`/master/setup/`)
.push({ title, description })
.then(() => {
this.setState({ loading: false})
.catch((error) => {
console.log(error);
})
});
}
You need to bind saveData in constructor.
this.saveData = this.saveData.bind(this);
You forgot to bind scope to saveData method.
Do it in constructor same as you bind it to handleChange method.
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.saveData = this.saveData.bind(this);
this.state = {
isOpen: false,
title: '',
description: '',
loading: true,
};
or
change saveData definition to one that uses arrow function syntax from ES6
saveData = (e) => {...function body as you already have it}
and parent scope will be bind for you by default

How to dynamically generate pair of input fields using reactjs

I have tried the following code to create a react form to dynamically generate input fields to enter the series of person's name one by one. But user needs to enter the first name and last name instead of just name. So that, the form needs to generate pair of dynamic input fields. I am new to react. Can anyone please give an hint on how to accomplish this.
Note : The following code has been taken from the stackoverflow answer of #Mayank Shukla at How to implement a dynamic form with controlled components in React.JS?.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { values: [] };
this.handleSubmit = this.handleSubmit.bind(this);
}
createUI(){
return this.state.values.map((el, i) =>
<div key={i}>
<input type="text" value={el||''} onChange={this.handleChange.bind(this, i)} />
<input type='button' value='remove' onClick={this.removeClick.bind(this, i)}/>
</div>
)
}
handleChange(i, event) {
let values = [...this.state.values];
values[i] = event.target.value;
this.setState({ values });
}
addClick(){
this.setState(prevState => ({ values: [...prevState.values, '']}))
}
removeClick(i){
let values = [...this.state.values];
values.splice(i,1);
this.setState({ values });
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.values.join(', '));
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
{this.createUI()}
<input type='button' value='add more' onClick={this.addClick.bind(this)}/>
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(<App />, document.getElementById('container'));
Idea is, maintain an array of object in state variable. Each object will have two keys firstName and secondName (you can add more fields). Treat each object as a single unit and for all the keys render input element, and whenever user will click on add more, add one more object/entry the the array with two keys.
Working Fiddle.
Working Snippet:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [{firstName: "", lastName: ""}]
};
this.handleSubmit = this.handleSubmit.bind(this);
}
addClick(){
this.setState(prevState => ({
users: [...prevState.users, { firstName: "", lastName: "" }]
}))
}
createUI(){
return this.state.users.map((el, i) => (
<div key={i}>
<input placeholder="First Name" name="firstName" value={el.firstName ||''} onChange={this.handleChange.bind(this, i)} />
<input placeholder="Last Name" name="lastName" value={el.lastName ||''} onChange={this.handleChange.bind(this, i)} />
<input type='button' value='remove' onClick={this.removeClick.bind(this, i)}/>
</div>
))
}
handleChange(i, e) {
const { name, value } = e.target;
let users = [...this.state.users];
users[i] = {...users[i], [name]: value};
this.setState({ users });
}
removeClick(i){
let users = [...this.state.users];
users.splice(i, 1);
this.setState({ users });
}
handleSubmit(event) {
alert('A name was submitted: ' + JSON.stringify(this.state.users));
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
{this.createUI()}
<input type='button' value='add more' onClick={this.addClick.bind(this)}/>
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(<App />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container" />
Try this
state = {
number: [""],
dataArr: []
}
render() {
return (
<div>
<div onClick={()=>this.setState(prevState => ({number: [...prevState.number, ""]}))}>Add More element</div>
{this.state.number.map((e, i)=> {
return (
<input value={this.state.number[i]} onChange={(data) => this.setState({dataArr: update(this.state.dataArr, {i: {$set: data}})})} />
)
})}
</div>
)
}
You will have to handle the data inside this.state.dataArr. For instance, this.state.dataAtt[0] will contain the starting value in input field before user presses "Add More Element" button and then when user presses the same button again data will be added in this.state.dataArr[1] and so on.
You will need react-addons-update lib.

More compact way to write onChange handler for form with many inputs that affect nested state properties?

What is a more compact and more easy way to write a handleChange function for multiple input than the following:
import React from "react";
function initialState() {
return ({
customer: {
name: '',
primaryContactPerson: {
name: '',
email: ''
}
}
})
}
export default class AddCustomerForm extends React.Component {
constructor(props) {
super(props);
this.state = initialState();
this.handleChange = this.handleChange.bind(this);
}
handleChange(event, field) {
console.log(event.target.name);
switch(event.target.name) {
case 'customer.name':
console.log(1);
this.setState({
this: {
customer: {
name: event.target.value
}
}
})
break;
case 'customer.primaryContactPerson.name':
console.log(2);
this.setState({
this: {
customer: {
primaryContactPerson: {
name: event.target.value
}
}
}
})
break;
case 'customer.primaryContactPerson.email':
this.setState({
this: {
customer: {
primaryContactPerson: {
email: event.target.value
}
}
}
})
break;
default:
break;
}
this.setState({[event.target.name]: event.target.value});
}
render() {
return (
<div className='section section--full-width'>
<h1>Add Customer</h1>
<form onSubmit={this.handleSubmit}>
<div className='form__row'>
<label>
Customer name
</label>
<input type="text" name="customer.name" value={this.state.customer.Name} onChange={this.handleChange} />
</div>
<div className='form__row'>
<label>
Contact person
</label>
<input type="text" name="customer.primaryContactPerson.name" value={this.state.customer.primaryContactPerson.name} onChange={this.handleChange} />
</div>
<div className='form__row'>
<label>
Contact details
</label>
<input type="text" name="customer.primaryContactPerson.email" value={this.state.customer.primaryContactPerson.email} onChange={this.handleChange} />
</div>
<div>
<input type="submit" value="Add" />
</div>
</form>
</div>
);
}
}
I have tried the following method that does not work for nested objects:
handleChange(event, field) {
this.SetState({event.targetname: event.target.value})
}
One dirty hack is using lodash _.set Link. This is not the recommended method only a workaround.
you can use
const tmp = this.state;
_.set(tmp, event.target.name, event.target.value);
this.setState({ customer: tmp.customer });
if you want to avoid direct state mutation you can also use
const tmp = _.cloneDeep(this.state)
this process will be slower.

Handling multiple onChange callbacks in a React component

This is how I'm currently handling the scenario with two input boxes. As a separate update method for each one. Can/should this be done with a single handleChange method instead?
https://codepen.io/r11na/pen/bZKOpj?editors=0011
class App extends React.Component {
constructor(props) {
super(props);
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
this.state = {
name1: '',
name2: ''
};
};
handleChange1(e) {
this.setState({
name1: e.target.value
});
};
handleChange2(e) {
this.setState({
name2: e.target.value
});
};
render() {
return (
<div class="row column">
<Label name={this.state.name1}/>
<Input onChange={this.handleChange1} />
<Label name={this.state.name2}/>
<Input onChange={this.handleChange2} />
</div>
);
};
}
const Label = props => (
<p {...props}>Hello: <span className="label-name">{props.name}</span></p>
);
const Input = props => (
<input placeholder="Enter your name" {...props} type="text" />
);
ReactDOM.render(<App />, document.getElementById('app'))
Can/should this be done with a single handleChange method instead?
You can simplify your code like so.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name1: '',
name2: ''
};
};
handleChange(e, name) {
this.setState({ [name]: e.target.value });
};
render() {
return (
<div class="row column">
<Label name={this.state.name1}/>
<Input onChange={ (e) => this.handleChange(e, 'name1') } />
<Label name={this.state.name2}/>
<Input onChange={ (e) => this.handleChange(e, 'name2') } />
</div>
);
};
}
Example
Thanks #Alik that mentioned about eslint rule jsx-no-bind,
A bind call or arrow function in a JSX prop will create a brand new
function on every single render. This is bad for performance, as it
will result in the garbage collector being invoked way more than is
necessary.
Following this rule you can change your code like
class App extends React.Component {
constructor(props) {
super(props);
this.onChange = {
name1: this.handleChange.bind(this, 'name1'),
name2: this.handleChange.bind(this, 'name2'),
}
this.state = {
name1: '',
name2: ''
};
};
handleChange(name, event) {
this.setState({ [name]: event.target.value });
};
render() {
return (
<div class="row column">
<Label name={this.state.name1}/>
<Input onChange={ this.onChange.name1 } />
<Label name={this.state.name2}/>
<Input onChange={ this.onChange.name2 } />
</div>
);
};
}
Example

Resources