Saving state of inputs added by user in React - javascript

I working on a small personal project using React on Rails. I am very new to both of these things.
I have a react component that is a form. I also have another component that has some inputs that the user can add as many as needed. Using the Add Properties button. I am trying to save the state of each input that is added. I could have the component itself save the state but how then would I send it with my fetch post request that happens onClick?
I have looked at react's context API but cant figure out if this would help me. Also I have never used redux so it is possible I should look into that as well.
https://reactjs.org/docs/context.html
https://redux.js.org/basics/usagewithreact
I understand that I don't want to reach into state. So I was trying to figure out how to create an array of objects that will hold the input values of each input pair. But I cannot seem to wrap my mind around how to implement.
class ProductsCreate extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
upc: '',
availableOn: '',
inputs: []
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
if (e.target.name === 'name') {
this.setState({ name: e.target.value });
}
if (e.target.name === 'upc') {
this.setState({ upc: e.target.value });
}
if (e.target.name === 'date') {
this.setState({ availableOn: e.target.value });
}
}
submitData = () => {
fetch(`/send_data`, {
method: 'POST',
body: JSON.stringify({
name: this.state.name,
upc: this.state.upc,
availableOn: this.state.availableOn
}),
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin'
})
.then(response => {
return response.json;
})
.then(data => {
console.log(data);
});
};
clickHandler = e => {
e.preventDefault();
this.submitData();
};
appendInput = e => {
e.preventDefault();
const newInput = `input-${this.state.inputs.length}`;
this.setState({ inputs: this.state.inputs.concat([newInput]) });
};
render() {
return (
<div className="form_container">
<h1>Products</h1>
<form>
<label>Name</label>
<input type="text" name="name" onChange={this.handleChange} />
<label>UPC</label>
<input type="text" name="upc" onChange={this.handleChange} />
<label>Availiable On</label>
<input
type="text"
name="date"
placeholder="mm/dd/yyyy"
onChange={this.handleChange}
/>
<h1>Properties</h1>
{this.state.inputs.map(input => (
<Properties key={input} />
))}
<button onClick={this.appendInput}>Add Properties</button>
<button onClick={this.clickHandler}>Save</button>
</form>
</div>
);
}
}
export default ProductsCreate;
This is the component that will be added on click
import React from 'react';
class Properties extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="property_container">
<label>Property Name</label>
<input type="text" name="propertyName" />
<label>Property Value</label>
<input type="text" name="propertyValue" />
</div>
);
}
}
export default Properties;

Pass handle change as prop to Properties component and then use that prop in input onChange within your Properties component. Make the following edits. I also suggest if you don't want to continously update the state on every character typed use debounce.
ProductsCreate
{this.state.inputs.map(input => (
<Properties key={input} onChange={this.handleChange} name={input}/>
))}
Properties
<input type="text" name={this.props.name} onChange={this.props.onChange}/>

Related

trouble getting data from react input form

I am just making a simple app to learn react with redux.
I just want to get data input in the react input form on the server-side.
The problem is that the params on the server-side is like this.
{"item"=>{"name"=>"undefined","price"=>"undefined"...}...}
Here is part of my code:
import React from "react";
class ItemForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.update = this.update.bind(this);
}
handleSubmit(e) {
e.preventDefault();
const ItemData = new FormData();
ItemData.append("item[name]", this.props.item.name);
ItemData.append("item[price]", this.props.item.price);
};
update(field) {
return (e) => {
this.setState({ [field]: e.target.value });
};
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<label>
<div>Item</div>
<input
type="text"
value={this.props.item.name}
onChange={this.update("name")}
/>
</label>
<label>
<div>Price</div>
<input
type="number"
value={this.props.item.price}
onChange={this.update("price")}
/>
</label>
<div>
<input type="submit" value="Submit" />
</div>
</div>
</form>
</div>
);
}
}
Shoule I use store function in redux or is there more easy way?
Thanks.
Assumption
I'm assuming that the problem is that the data you can access in your handleSubmit function is not being updated, therefore you always receive the values that you initialized the component with.
Solution
Initialize the state based on the name and price props passed in
Set the value of your input tags to the state values
Access the state in your handleSubmit function
import React from "react";
import { withRouter } from "react-router-dom";
class ItemForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.update = this.update.bind(this);
this.state = {
name: this.props.item.name,
price: this.props.item.price
}
}
handleSubmit(e) {
e.preventDefault();
const ItemData = new FormData();
ItemData.append("item[name]", this.state.name);
ItemData.append("item[price]", this.state.price);
};
update(field) {
return (e) => {
this.setState({ [field]: e.target.value });
};
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<label>
<div>Item</div>
<input
type="text"
value={this.state.name}
onChange={this.update("name")}
/>
</label>
<label>
<div>Price</div>
<input
type="number"
value={this.state.price}
onChange={this.update("price")}
/>
</label>
<div>
<input type="submit" value="Submit" />
</div>
</div>
</form>
</div>
);
}
}
export default withRouter(ItemForm);
Other suggestions
Your redux wrapper component doesn't have any big effects on this, so you can remove it from this question for clarity 😀
This is actually one of the benefits of redux, the connected component (ItemForm) is a regular React component and does not have any knowledge that it will be acted on by redux
Hope that helps! 👍

event.preventDefault in React gives TypeError: event is undefined

I have a React component called App that should display something based on a HTML form. Each time the user submits the form, the state of App should adapt. At first I implemented my class like this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {technologies: [], subject: ''};
this.updateSubject = this.updateSubject.bind(this);
this.updateTimeline = this.updateTimeline.bind(this);
}
componentDidMount() {
this.updateTimeline();
}
updateSubject(event) {
this.setState({subject: event.target.value});
}
updateTimeline() {
fetch('/timeline?subject=' + this.state.subject)
.then(res => res.json())
.then(technologies => this.setState({technologies: technologies}));
}
render() {
return <div>
<form id="subject" onSubmit={this.updateTimeline}>
<label>
Subject:
<input type="text" value={this.state.subject} onChange={this.updateSubject} />
</label>
<input type="submit" value="Submit" />
</form>
<Timeline techs={this.state.technologies} />
</div>;
}
}
However, I figured out that this way each form submission reloads the page (or at least calls the App constructor)... which is unfortunate, because the state of App gets reset to the empty string (see second line of the constructor). So I tried to add an event argument to the updateTimeline method, and call event.preventDefault(); before calling fetch:
updateTimeline(event) {
event.preventDefault();
fetch('/timeline?subject=' + this.state.subject)
.then(res => res.json())
.then(technologies => this.setState({technologies: technologies}));
}
This gives me TypeError: event is undefined in the console. Why is this the case?
onSubmit is very angular, instead of doing it that way change the code like so:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {technologies: [], subject: ''};
this.updateSubject = this.updateSubject.bind(this);
this.updateTimeline = this.updateTimeline.bind(this);
}
componentDidMount() {
this.updateTimeline();
}
updateSubject(event) {
this.setState({subject: event.target.value});
}
updateTimeline() {
return fetch('/timeline?subject=' + this.state.subject)
.then(res => res.json())
.then(technologies => this.setState({technologies: technologies}));
}
render() {
return <div>
<form id="subject">
<label>
Subject:
<input type="text" value={this.state.subject} onChange={this.updateSubject} />
</label>
<button type="button" onClick={this.updateTimeline}>Submit</button>
</form>
<Timeline techs={this.state.technologies} />
</div>;
}
}
In addition to the answer by #Ernesto that I accepted, I found a more general (and more semantic?) way to obtain the behavior I wanted. This just involves changing the updateTimeline method from the code in my original question:
updateTimeline(event) {
if (arguments.length > 0) event.preventDefault();
fetch('/timeline?subject=' + this.state.subject)
.then(res => res.json())
.then(technologies => this.setState({technologies: technologies}));
}
... this allows to keep the <input type="submit" value="Submit" /> tag, which sounds more semantic to me than a button tag. At the same time, this also nicely handles the event of a user hitting return instead of clicking "Submit".

React: update state with form data onChange using the spread operator

I'm trying to update my state using the spread operator so I can pass form data in a http request, but it doesn't seem to be updating at all from what i can see in my console.logs.
I'm also getting the error 'TypeError: Invalid attempt to spread non-iterable instance'. I need the data I pass in to be an object though.
import React from 'react'
import SuperAgent from 'superagent'
import { string } from 'prop-types'
class ContactForm extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.onChange = this.onChange.bind(this)
this.submitForm = this.submitForm.bind(this)
this.state = {
data: {}
}
}
handleClick (e) {
e.preventDefault()
this.submitForm()
}
onChange (e) {
this.setState(...this.state.data, {[e.target.name]: e.target.value })
}
submitForm () {
const { data } = this.state
SuperAgent
.post('https://something.com')
.set('Content-Type', 'application/json')
.send({
data: {
'name': 'name',
'formName': 'form',
'emailName': 'email',
data
}})
.end((err, res) => {
if (err || !res || !res.body) return console.log(err)
window.location='/contact-form-successful.html'
})
}
render () {
return (
<section className='contact-form' id={this.props.id}>
<form id='contact-form' method='post' action='#' onSubmit={this.handleClick}>
<input
name="username"
type="text"
value=''
onChange={this.onChange}
/>
<input
name="email"
type="email"
value=''
onChange={this.onChange}
/>
<input
name="birthdate"
type="text"
value=''
onChange={this.onChange}
/>
<button>Send data!</button>
</form>
</section>
)
}
}
export default ContactForm
ContactForm.propTypes = {
id: string
}
ContactForm.defaultProps = {
id: 'contact-form'
}
You're using setState wrong
this.setState(...this.state.data, {[e.target.name]: e.target.value })
Should be
const { target : { value, name } } = e
this.setState(prevState => ({
data: {
...prevState.data,
[name]: value
}
}))
Also the input's value should reflect the state (controlled input)
<input
name="username"
type="text"
value={this.state.username}
onChange={this.onChange}
/>
Your onChange method should rather do this :
onChange (e) {
this.setState(state => ({
data: {
...state.data,
[e.target.name]: e.target.value
}
}))
}
The proper syntax usage of spread operator would be like below:
onChange (e) {
this.setState({...this.state.data, [e.target.name]: e.target.value })
}
However, you dont need spread in using setState you only update what you need to.
Thus you would only set state like below:
onChange (e) {
this.setState({[e.target.name]: e.target.value })
}
If you want to rely on prior state value then you would have to use functional setState
onChange (e) {
this.setState(prevState => ({...prevState, [e.target.name]: e.target.value }))
}
Other than that you were also missing input's value to be retrieved from state of react component rather you were hardcoding it as '' empty string thus value was never reflected in the input while your state kept getting updated with last key input.
<input
name="username"
type="text"
value={this.state.username}
onChange={this.onChange}
/>
<input
name="email"
type="email"
value={this.state.email}
onChange={this.onChange}
/>
<input
name="birthdate"
type="text"
value={this.state.birthdate}
onChange={this.onChange}
/>

How can I keep on adding username and status instead of updating the previous one in reactJS?

I have two components. One named 'Adduser' containing form elements so that a user may add details of post. Other named 'PostAdded', in which i want to show all posts in a list item. On every click, I want 'Adduser' to grab data from input elements and pass it to 'PostAdded' in a way that 'PostAdded' show every individual post(title and post together) in a new div instead of updating previous one. What is the best approach to do it?
File 'Adduser.js'
class AddUser extends Component {
constructor(props) {
super();
this.state = {
title : "",
post : "",
}
this.handleclick = this.handleclick.bind(this);
}
handleclick() {
this.setState(prevState => ({
title : document.getElementById("title").value,
post : document.getElementById("post").value,
}));
}
render() {
return(
<div>
<input type="text" id="title" placeholder="Title here" />
<input type="text" id="post" placeholder="Post here" />
<input type="button" onClick={this.handleclick} value="Add Post" />
<PostAdded posts={this.state.post} />
</div>
)
}
}
export default AddUser;
File 'PostAdded.js'
import React, {Component} from 'react';
class PostAdded extends Component {
constructor(props) {
super();
}
render() {
return <ul>
{ this.props.posts.map(post =>
<li>{post}</li>
)}
</ul>
}
}
export default PostAdded;
In AddUser component change your state and handleclick method. I have not modified your code too much so you can understand it easily.
class AddUser extends Component {
constructor(props) {
super();
this.state = {
posts: [],
}
this.handleclick = this.handleclick.bind(this);
}
handleclick() {
// accessing values from the input
let title = document.getElementById("title").value
let post = document.getElementById("post").value
// creating a new object
let newPostObj = {title, post}
// concatenating new object to component posts state
let newPost = this.state.posts.concat(newPostObj)
// setting newPost as component new state
this.setState({
posts: newPost
})
// emptying the input fields
document.getElementById("title").value = ''
document.getElementById("post").value = ''
}
render() {
return(
<div>
<input type="text" id="title" placeholder="Title here" />
<input type="text" id="post" placeholder="Post here" />
<input type="button" onClick={this.handleclick} value="Add Post" />
<PostAdded posts={this.state.posts} />
</div>
)
}
}
In your PostAdded component update render() method
class PostAdded extends Component {
constructor(props) {
super();
}
render() {
return (
<ul>
{ this.props.posts.map((post, i) =>
<li key={`${i}-post`}><span>{post.title}</span><span>{post.post}</span></li>
)}
</ul>
)
}
}
UPDATE
Change your AddUser Component
class AddUser extends Component {
constructor(props) {
super();
this.state = {
posts: [],
title: '',
post: ''
}
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
// called when we type something in input fields
handleChange(e) {
// you can console log here to see e.target.name and e.target.value
this.setState({
[e.target.name]: e.target.value
})
}
handleClick() {
// using spread operator to copy previous state posts and adding new post object
let newPosts = [ ...this.state.posts, { title: this.state.title, post: this.state.post}]
this.setState({
posts: newPosts,
title: '',
post: ''
})
}
render() {
return(
<div>
// added name,value attributes and onChange listener
<input type="text" name="title" value={this.state.title} onChange={this.handleChange} placeholder="Title here" />
<input type="text" name="post" value={this.state.post} onChange={this.handleChange} placeholder="Post here" />
<input type="button" onClick={this.handleClick} value="Add Post" />
<PostAdded posts={this.state.posts} />
</div>
)
}
}

another alternative to following approach without updating internal state

I have a usecase that there is a form which should be controlled one and should have its field be pre-populated so that user can edit the form. For this what i have done is
const mapStateToProps = createStructuredSelector({
myInfo: makeSelectMyInfo(),
errorResponse: makeSelectMyInfoErrorResponse()
});
const mapDispatchToPropes = dispatch => ({
loadMyInfo: () => dispatch(getMyInfo()),
updateMyInfo: (myInfo, token) => dispatch(updateMyInfo(myInfo, token))
});
class ConfirmPropertyByUser extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
user_info: {
contact_fname: "",
contact_lname: "",
agree_terms_condition: false,
}
};
}
componentDidMount() {
this.props.loadMyInfo();
}
componentWillReceiveProps(nextProps) {
if (nextProps.myInfo !== this.props.myInfo) {
console.log("object", Object.values(nextProps.myInfo));
this.setState(state => ({
user_info: {
...state.user_info,
contact_fname: nextProps.myInfo.contact_fname,
contact_lname: nextProps.myInfo.contact_lname,
}
}));
}
}
handleChange = e => {
this.setState({
user_info: { ...this.state.user_info, [e.target.name]: e.target.value }
});
};
handleUserTerms = e =>
this.setState({
user_info: {
...this.state.user_info,
agree_terms_condition: e.target.checked
}
});
handleSubmit = e => {
e.preventDefault();
this.props.updateMyInfo(this.state.user_info, this.props.match.params.id);
};
render() {
const { errorResponse } = this.props;
const { user_info } = this.state;
let message;
if (errorResponse && typeof errorResponse === "string") {
message = <Notification message={errorResponse} timeout={5000} />;
}
return (
<div className="container">
{message && message}
<div className="card card-lg">
<h1>Register</h1>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>First Name</label>
<input
type="text"
name="contact_fname"
className="form-control"
value={user_info && user_info.contact_fname}
onChange={this.handleChange}
/>
</div>
<div className="form-group">
<label>Last Name</label>
<input
type="text"
name="contact_lname"
className="form-control"
value={user_info && user_info.contact_lname}
onChange={this.handleChange}
/>
</div>
<div className="form-group">
<input
className="custom-control-input"
type="checkbox"
onChange={this.handleUserTerms}
/>
</div>
<button
className="btn btn-default btn-block btn-lg"
disabled={
!user_info.password || !user_info.agree_terms_condition
}
>
Submit Details
</button>
</fieldset>
</form>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToPropes)(
ConfirmPropertyByUser
);
I am using redux and also updating the internal state. But I have heard somewhere that when using redux its unnecessary to update the internal state. How can i approach the following problem without updating the internal state? Can anyone help me on this, please?
What you're doing appears to be fine. Per the Redux FAQ, there's nothing wrong with using component state in a Redux app. For forms, it's very common to need to have both an "original" set of values and a "work-in-progress" copied set of values, and it's up to you whether the "WIP" values are stored in Redux or in a React component.
For what it's worth, I did show some examples of putting the "WIP" form state into Redux in my blog post Practical Redux, Part 8: Form Draft Data Management, which might be a useful reference. But, overall, your code here looks good - you're correctly copying props to state in the constructor and in componentWillReceiveProps, and the conceptual approach you're following is perfectly fine.
One small stylistic suggestion: I generally recommend that people use the object shorthand syntax for the mapDispatch argument. In your case, it would look like:
const actions = {loadMyInfo : getMyInfo, updateMyInfo : updateMyInfo};
// later
export default connect(mapState, actions)(ConfirmPropertyByUser);

Categories