React updating form fields from backend - reactjs

I am working on a project using React and Redux where I need to update the data from the back end in the form. Since the data that needs to be updated will be fetched from the backend, I am initializing component state from props and the form values will be assigned from component state. The issue that I am having is that some of the values I am fetching from the back end are empty(undefined) at first and need to be added from the form and that is throwing error when the component is rendered. Please refer my React class below, any suggestion on how to avoid the component from throwing an error if the props value is undefined is appreciated.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import Radium from 'radium';
import ProfileBio from '../common/commonProfileBio';
const form = reduxForm({
form: 'profileEdit',
fields: [ 'firstName', 'lastName', 'email', 'educations'],
// validate
});
class ProfileBioList extends Component {
constructor(props) {
super(props);
this.state = {
editing: false,
//this is throwing error since the values are undefined initially
firstName: this.props.data.bio.firstName,
lastName: this.props.data.bio.lastName,
email: this.props.data.email
};
this.toggleEditing = this.toggleEditing.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.onChange = this.onChange.bind(this);
}
getStyles() {
return {
ulStyles: {
listStyle: "none"
},
listItem: {
borderRadius: 0,
boxShadow: "1px 1px 1px lightgrey"
}
}
}
handleUpdate(e) {
e.preventDefault();
this.props.updateProfile({bio:this.state});
}
renderItemOrEditField() {
const styles = this.getStyles();
if(this.state.editing !== true){
return (
<ProfileBio
toggleEditing={this.toggleEditing}
data={this.props.data}
styles={styles}/>
);
} else {
return (
<div className="row ">
<div className="col-xs-10 col-xs-offset-1">
<form>
<ul className="list-group-item" style={styles.ulStyles}>
<li >
<label>First Name:</label>
<input
name="firstName"
type="text"
className="form-control"
value={this.state.firstName}
onChange={this.onChange}/>
</li>
<li className="">
<label className="">Last Name:</label>
<input
name="lastName"
type="text"
className="form-control"
value={this.state.lastName }
onChange={this.onChange}/>
</li>
<li className="">
<label>Email:</label>
<input
name="email"
type="text"
className="form-control"
value={this.state.email}
onChange={this.onChange}/>
</li><br />
<li className="btn-group">
<button
onClick={this.handleUpdate}
action="submit"
className="btn btn-primary">Update</button>
<button
onClick={this.toggleEditing}
className="btn btn-default">Cancel</button>
</li>
</ul>
</form>
</div>
</div>
);
}
}
toggleEditing(e){
e.preventDefault();
this.setState({editing: !this.state.editing});
}
render(){
return (
<div>
{this.renderItemOrEditField()}
</div>
);
}
}
export default connect(null)(form(ProfileBioList));

The easiest way would be to use ES6 destructuring assignment with default value. So your constructor would look something like this...
class ProfileBioList extends Component {
constructor(props) {
super(props);
const {
bio: {
firstName = '',
lastName = '',
} = {},
email = '',
} = props.data;
this.state = {
editing: false,
firstName: firstName,
lastName: lastName,
email: email
};
this.toggleEditing = this.toggleEditing.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.onChange = this.onChange.bind(this);
}
...
}

Related

How do i display booking details on confirmation page using mern stack

I am a beginner in programming. I am building an appointment booking system using mern stack. I have created the booking form which sends data to the database, now i need to display the booking details ona confirmation page,which i am finding difficult. please this is where i need some help
import React, { Component } from 'react';
import axios from 'axios';
import DatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css'
import { Link } from 'react-router-dom';
class Form extends Component {
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
this.handleDateChange = this.handleDateChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this);
}
state = {
name: "",
service: "finance",
date: new Date(),
cost: "3"
};
styles = {
fontSize: 50,
fontWeight: "bold",
color: "blue"
}
//event to handle all inputs except datepicker
handleChange(e) {
const name = e.target.name;
const value = e.target.value;
//to update the input state
this.setState({ [name]: value });
}
//event to handle datepicker input
handleDateChange(date) {
//update the date state
this.setState({
date:date
})
}
handleSubmit(e) {
e.preventDefault()
//console.log(this.state);
if(this.state.value !== ""){
alert('booking success')
}
axios.post('http://localhost:5000/api', this.state)
.then(res => {
console.log(res)
})
.catch((error) => {
console.log(error)
})
this.setState({ name: '', service: '', date: '', cost: '' })
}
render() {
return (
<form className='form' onSubmit={this.handleSubmit}>
<h2 style={this.styles}>Create appointment</h2>
<div className="mb-3">
<label className="form-label">Name</label>
<input name='name' type="text" className="form-control" id="exampleFormControlInput1" value={this.state.name} onChange={this.handleChange} />
<label className="form-label">Service</label>
<input name='service' type="text" className="form-control " id="exampleFormControlInput1" value={this.state.service} onChange={this.handleChange} />
<label className="form-label"> Date</label>
<div>
<DatePicker
selected={this.state.date}
onChange={this.handleDateChange}
name='date'
/>
</div>
{/* <label className="form-label"> Date</label>
<input name='date'type="datetime-local" className="form-control" id="exampleFormControlInput1" value={this.state.date} onChange={this.handleChange} />
*/}
<label className="form-label">Cost</label>
<input name='cost' type="text" className="form-control" id="exampleFormControlInput1" value={this.state.cost} onChange={this.handleChange} />
</div>
<Link to={'/ConfirmBooking'} style={{ textDecoration: 'none' }}>
<input type="submit" value="Submit" className="btn btn-outline-success" />
</Link>
</form>
)
}
}
export default Form;
import React, { Component } from 'react';
import axios from 'axios';
import Form from './Form';
class ConfirmBooking extends Component {
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
state = {
};
handleSubmit(e){
e.preventDefault();
axios.get('http://localhost:5000/api', this.state)
.then(res => {
console.log(res)
})
.catch((error) => {
console.log(error)
})
this.setState({ name: '', service: '', date: '', cost: '' })
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit} >
<h3>Booking review</h3>
</form >
</div>
);
}
}
export default ConfirmBooking
;
the first code is for the oking form which works fine. The one underneath is for displaying booking details but doesnt work. please kindly help me

React: Updating state, mapping over state to display a list but the list disappears from the view after a second?

So i'm trying to learn about state in react and i'm doing a little project. I've got three inputs which each update a value in my state on submit. On submit it then creates an object from those updated state values which it adds to an array in the state.
I then map over the array to display a list of the objects. Problem is when i add a new object on submit the map displays that object for a brief second in the browser and then it returns the state to its default values and displays none of the object entries.
code below:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
names: [],
id: 1,
firstName: '',
secondName: '',
userName: '',
gamesPlayed: 0
}
this.handleSubmit = this.handleSubmit.bind(this)
}
onChange = (event) => {
this.setState({[event.target.name]: event.target.value})
// console.log(this.state.firstName)
}
handleSubmit() {
this.setState(prevState => {
const newNames = [...prevState.names, {id: prevState.id, firstName: prevState.firstName,
secondName: prevState.secondName, userName: prevState.userName, gamesPlayed: prevState.gamesPlayed}]
const newId = prevState.id += 1
return ({
names: newNames,
id: newId
})
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">ReactND - Coding Practice</h1>
</header>
<h2>Add New User</h2>
<div>
<form onSubmit={this.handleSubmit}>
<input
type='text'
placeholder='First Name'
name='firstName'
onChange={this.onChange}
/>
<input
type='text'
placeholder='Last Name'
name='secondName'
onChange={this.onChange}
/>
<input
type='text'
placeholder='Username'
name='userName'
onChange={this.onChange}
/>
<input
type='submit'
value='Submit'
/>
</form>
<h2>Users</h2>
<div>{this.state.names.map((name) => (
<li key={name.id}>
<ol>
{name.firstName} {name.secondName} {name.userName} has played {name.gamesPlayed} games!
</ol>
</li>
))}
</div>
</div>
</div>
);
}
}
export default App;
By default your form will perform HTTP request to the current URL (will trigger reload page). You can opt to prevent this default behaviour via preventDefault
handleSubmit(e) {
e.preventDefault();
...
}
<form onSubmit={e => this.handleSubmit(e)}>
Spec: https://www.w3.org/TR/html52/sec-forms.html#form-submission
Prevent default form submit replace code line with the below code:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
names: [],
id: 1,
firstName: '',
secondName: '',
userName: '',
gamesPlayed: 0
}
this.handleSubmit = this.handleSubmit.bind(this)
}
onChange = (event) => {
this.setState({[event.target.name]: event.target.value})
// console.log(this.state.firstName)
}
handleSubmit(event) {
event.preventDefault();
this.setState(prevState => {
const newNames = [...prevState.names, {id: prevState.id, firstName: prevState.firstName,
secondName: prevState.secondName, userName: prevState.userName, gamesPlayed: prevState.gamesPlayed}]
const newId = prevState.id += 1
return ({
names: newNames,
id: newId
})
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">ReactND - Coding Practice</h1>
</header>
<h2>Add New User</h2>
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<input
type='text'
placeholder='First Name'
name='firstName'
onChange={this.onChange}
/>
<input
type='text'
placeholder='Last Name'
name='secondName'
onChange={this.onChange}
/>
<input
type='text'
placeholder='Username'
name='userName'
onChange={this.onChange}
/>
<input
type='submit'
value='Submit'
/>
</form>
<h2>Users</h2>
<div>{this.state.names.map((name) => (
<li key={name.id}>
<ol>
{name.firstName} {name.secondName} {name.userName} has played {name.gamesPlayed} games!
</ol>
</li>
))}
</div>
</div>
</div>

How do I set the value of an input after doing an Axios get request?

I have a component that represents a form for entering book data, e.g. title/author/etc.
If an ID is present, the component will make an API call to the API server, and get the book data.
What I'm trying to accomplish basically, is getting the record from the API server, and then setting the form fields to those values, so that the form is populated with data for the user to edit.
I have a method called loadBook which makes an API call to get the book data. The method works, it gets the record, it sets the state, but the form inputs do not seem to pick up that the state has changed.
What do I need to do to get the form populated with the record that was just fetched?
import React from "react";
import Axios from "axios";
import {
Redirect
} from "react-router-dom";
import FormBase from "../FormBase";
export default class BookForm extends FormBase {
constructor(props) {
super(props);
this.state = {
formFields: {
title: '',
author_id: null,
cover_image: null
},
authors: [],
};
}
componentDidMount() {
this.loadAuthors();
if (this.props.id) {
this.loadBook()
}
}
loadBook = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/books/${this.props.id}`).then(response => {
this.setState(prevState => {
let formFields = Object.assign({}, prevState.formFields)
formFields['title'] = response.data['title']
formFields['author_id'] = response.data['author_id']
return {formFields}
})
})
}
loadAuthors = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/authors`).then(response => {
this.setState({authors: response.data})
})
}
render() {
let authors = this.state.authors.map(author => {
return <option key={author.id} value={author.id}>{author.last_name}, {author.first_name}</option>
})
return (
<form onSubmit={(e) => {e.preventDefault(); this.props.handleSubmit(e, this.state.formFields, true)}}>
{this.state.redirect ? <Redirect to="/admin/books" /> : null}
<div className="form-group">
<label htmlFor="title">Title</label>
<input name="title" value={this.state.title} onChange={this.handleFieldChange} type="text" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="author">Author</label>
<select name="author_id" onChange={this.handleFieldChange} className="custom-select" size="5">
{authors}
</select>
</div>
<div className="custom-file form-group">
<input name="cover_image" type="file" onChange={this.handleFieldChange} className="custom-file-input" id="customFile" />
<label className="custom-file-label" htmlFor="customFile">Cover Image</label>
</div>
<button style={{marginTop: '1rem'}} type="submit" className="btn btn-primary">Submit</button>
</form>
)
}
}
Try setting your state simply like so:
this.setState({formFields:
{
...this.state.formFields,
title: response.data['title'],
author_id: response.data['author_id']
}
})
I essentially followed this guide on uncontrolled components.
I added attributes for each form field using React.createRef(), and then on the form inputs you link the ref object like ref={this.author_id}. Then, you can do this.author_id.current.value = response.data.author_id and the input's value will then be set. This won't trigger onChange though, so you'll need to update the state too.
import React from "react";
import Axios from "axios";
import {
Redirect
} from "react-router-dom";
import FormBase from "../FormBase";
export default class BookForm extends FormBase {
constructor(props) {
super(props);
this.state = {
formFields: {
title: '',
author_id: null,
cover_image: null
},
authors: [],
};
this.title = React.createRef();
this.author_id = React.createRef();
}
componentDidMount() {
this.loadAuthors();
if (this.props.id) {
this.loadBook()
}
}
loadBook = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/books/${this.props.id}`).then(response => {
console.log(this.author_id)
this.author_id.current.value = response.data.author_id
this.title.current.value = response.data.title
this.setState(prevState => {
let formFields = Object.assign({}, prevState.formFields)
formFields['title'] = response.data['title']
formFields['author_id'] = response.data['author_id']
return {formFields}
})
})
}
loadAuthors = () => {
Axios.get(`${process.env.REACT_APP_API_URL}/authors`).then(response => {
this.setState({authors: response.data})
})
}
render() {
let authors = this.state.authors.map(author => {
return <option key={author.id} value={author.id}>{author.last_name}, {author.first_name}</option>
})
return (
<form onSubmit={(e) => {e.preventDefault(); this.props.handleSubmit(e, this.state.formFields, true)}}>
{this.state.redirect ? <Redirect to="/admin/books" /> : null}
<div className="form-group">
<label htmlFor="title">Title</label>
<input name="title" ref={this.title} value={this.state.title} onChange={this.handleFieldChange} type="text" className="form-control" />
</div>
<div className="form-group">
<label htmlFor="author">Author</label>
<select name="author_id" ref={this.author_id} onChange={this.handleFieldChange} className="custom-select" size="5">
{authors}
</select>
</div>
<div className="custom-file form-group">
<input name="cover_image" type="file" onChange={this.handleFieldChange} className="custom-file-input" id="customFile" />

ReactJs: Defaultvalue of textArea not updated

I'm working on a form in my ReactJs app. What I'd like to do:
1/ select a draft letter from an array of draft letters by its id // ok
2/ display the title, body and location of the selected draft letter in a form below // ok
3/ then the user should be able to edit all the fields // not ok!
This is what I did :
class NewLetter extends Component {
constructor(props) {
super(props)
this.state = {
city: "",
title: "",
letterBody: "",
}
this.handleChange_city = this.handleChange_city.bind(this);
this.handleChange_letterBody = this.handleChange_letterBody.bind(this);
this.handleChange_title = this.handleChange_title.bind(this);
}
handleChange_city(e) {
this.setState({
city: e.target.value
});
}
handleChange_letterBody(e) {
this.setState({
title: e.target.value
});
}
handleChange_title(e) {
this.setState({
letterBody: e.target.value
});
}
render() {
return (
<form className="containerLetter">
<div className="form-group">
<input type="text" className="form-control" id="letterLocation" placeholder="where are you?" value={this.props.city} onChange={this.handleChange_city}/>
</div>
<div className="form-group">
<input type="text" className="form-control" id="letterTitle" placeholder="give a title to your letter" value={this.props.title} onChange={this.handleChange_title} />
</div>
<div className="form-group">
<textarea type="text" className="form-control" id="letterBody" placeholder="Letter content" value={this.props.letterBody} onChange={this.handleChange_letterBody} />
</div>
<button className="actionButton">Send the letter</button> <br />
<button className="actionButton">Save as draft</button>
</form>
)
}
}
export default NewLetter
If I use value, the fields are not editable
If I use defaultValue the field are editable but when the defaultValue change, React doesn't re-render. The values are passed as props by the parent component:
class DraftLetters extends Component {
constructor(props) {
super(props)
this.state = {
selectedDraftIndex: 0,
};
this.handleChange = this.handleChange.bind(this);
}
getListOfDrafts = () => {
var list = []
for (let i = 0; i < this.props.drafts.length; i++) {
list.push(<option value={i}> {this.props.drafts[i].redactionDate} </option>);
}
return (list);
}
handleChange(e) {
this.setState({
selectedDraftIndex: e.target.value,
})
}
render() {
return (
<div >
{(this.props.drafts.length != 0) &&
<div className="form-group selectLetter">
<label for="sel1">Select the draft:</label>
<select onChange={this.handleChange} className="form-control" id="sel1">
{this.getListOfDrafts()}
</select>
</div>
}
{(this.props.drafts.length == 0) &&
<div className="form-group">
<p className="noLetter"> You haven't saved any draft.</p>
<img width="400px" className="img-fluid" src={painting} alt="tableau"></img>
</div>
}
<div>
<br />
{(this.props.drafts.length != 0) &&
<NewLetter city={this.props.drafts[this.state.selectedDraftIndex].city}
title={this.props.drafts[this.state.selectedDraftIndex].title}
letterBody={this.props.drafts[this.state.selectedDraftIndex].letterBody}></NewLetter>
}
{this.state.selectedDraftIndex}
</div>
</div>
)
}
}
export default DraftLetters
It seems to be a well known problem
This is what I found:
defaultValue change does not re-render input
React input defaultValue doesn't update with state
https://github.com/facebook/react/issues/4101
Is there nowdays a fix for this problem?

Why data is not rendered on refresh in react js with asynchronous call?

I am creating edit form.First i have to get data to edit form and i am calling it in componentDidMount().Please see code below.
import React from 'react';
import CompanyForm from './CompanyForm';
import { connect } from 'react-redux';
import { companyActions } from '../../../redux/actions/company-action';
class EditCompanyPage extends React.Component {
constructor(props){
super(props);
};
componentDidMount () {
const { id } = this.props.match.params
const { dispatch } = this.props;
dispatch(companyActions.getCompany(id));
}
render(){
const {editUser } = this.props;
return(
<div>
<h1>Edit Company</h1>
{
editUser && <CompanyForm handleActionParent={this.handleAction} companyDataFP={editUser} />
}
</div>
);
};
}
function mapStateToProps(state) {
const { editUser } = state.companyReducer;
return {
editUser
};
}
const EditCompany = connect(mapStateToProps)(EditCompanyPage);
export default EditCompany;
see code for CompanyForm component below:
import React from 'react';
class CompanyForm extends React.Component {
constructor(props){
super(props);
this.state = {
company :{
name : this.props.companyDataFP.name || '',
address1 : this.props.companyDataFP.address1 || '',
}
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(e) {
const { name, value } = e.target;
const newState = Object.assign({}, this.state);
newState.company[name] = value;
this.setState(newState);
}
handleSubmit(e) {
e.preventDefault();
return false;
}
render(){
return(
<div className="col-md-12">
<form onSubmit={this.handleSubmit}>
<div className="row">
<div className="col-md-6">
<div className='form-group'>
<label htmlFor="name">Name</label>
<input type="text" name="name" className="form-control" onChange={this.handleChange} value={this.state.company.name} />
</div>
</div>
<div className="col-md-6">
<div className='form-group'>
<label htmlFor="address1">Address 1</label>
<input type="text" name="address1" className="form-control" onChange={this.handleChange} value={this.state.company.address1} />
</div>
</div>
</div>
<div className="row">
<div className="col-md-12">
<div className='form-group'>
<input type="submit" className="btn btn-info" value="submit" />
</div>
</div>
</div>
</form>
</div>
);
};
}
export default CompanyForm;
It works fine when i access this form with
<Link to="/edit-form/:id" >Edit</Link>
but when i refresh the current page then values are not rendering into form to edit.
I am using redux approach for state management, please guide me i am new to react.
Probably ComponyForm initializes form on its componentDidMount lifecycle function, so when editUser arrives nothing will change.
A way to handle this is changing:
<CompanyForm handleActionParent={this.handleAction} companyDataFP={editUser} />
to:
{editUser.name && <CompanyForm handleActionParent={this.handleAction} companyDataFP={editUser} />}

Resources