pass object to setState fails - reactjs

I created my own web-app of posts where you able to add,delete and update posts. Right now when I try to add a post and pass the inputs value throw a function and then execute 'setState' I get an error, something is wrong in the way I do it. could you help out?
I could just remove the root object from the state ('post') and just use the title,body as for themselfs. But I want to structure it this way: Post:{title,body}
import React, { Component } from 'react';
export class addPost extends Component {
state = {
post: {
title: '',
body: ''
}
};
handleChange = e => {
this.setState({ post[e.currentTarget.id]: e.currentTarget.value });
console.log(this.state);
};
handleSubmit = () => {};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
Enter title:
<input
type="text"
value={this.state.title}
onChange={this.handleChange}
id="title"
/>
Enter body:
<input
type="text"
value={this.state.body}
onChange={this.handleChange}
id="body"
/>
</form>
</div>
);
}
}
export default addPost;
I expect to pass this object post:{title:'sometext',body:'sometext'}

You need to fix on two places
Where you are setting state
Where you are accessing state in input
handleChange = e => {
this.setState(prevState => ({
post: { ...prevState.post, [e.target.id]: e.target.value }
}));
};
<input value={this.state.post.title />
<input value={this.state.post.body} />

Made a sandbox for you: https://codesandbox.io/s/p2w7765j0
Most conventions are to use the name property for your inputs and map them using event.target.name
import React, { Component } from "react";
class AddPost extends Component {
state = {
post: {
title: "",
body: ""
}
};
handleChange = event => {
this.setState(
{
post: {
...this.state.post,
[event.target.name]: event.target.value
}
},
() => console.log(this.state)
);
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
Enter title:
<input
type="text"
value={this.state.post.title}
onChange={this.handleChange}
name="title"
/>
Enter body:
<input
type="text"
value={this.state.post.body}
onChange={this.handleChange}
name="body"
/>
</form>
</div>
);
}
}
export default AddPost;

Related

Basic Form react

Hello I just want to make a form and my textboxes does not take my inputs and my submit works but sends no values. What am I doing wrong? I know it's a basic question but I don't know what the problem is in my code.
Key problems:
it doesn't update the state nor take my inputs.
fields editable but cant write into them
Code
import React, { Component } from "react";
class Postform extends Component {
constructor(props) {
super(props);
this.state = {
name: "",
category: "",
price: "",
};
}
changeHandler = (e) => {
this.setState = { [e.target.name]: e.target.value };
};
submitHandler = (e) => {
e.preventDefault();
console.log(this.state);
};
render() {
const { name, category, price } = this.state;
return (
<div>
<form onSubmit={this.submitHandler}>
<div>
<input
type="text"
name="name"
placeholder="Name"
value={name}
onChange={this.changeHandler}
/>
<input
type="text"
name="category"
placeholder="Category"
value={category}
onChange={this.changeHandler}
/>
<input
type="text"
name="price"
placeholder="Price"
value={price}
onChange={this.changeHandler}
/>
</div>
<button type="submit">Add product</button>
</form>
</div>
);
}
}
export default Postform;
Change this line to
changeHandler = e => {
this.setState({[e.target.name]: e.target.value });
};
Since setState is a function it is not a property !

How to push to an object to an array which is a state object, react way with Typescript

I need to maintain array of objects which is stored in a state object. Basically I need to push each object to this array whenever I click on Add button .This should basically store this object in array.
Also I am unable to fetch proper values when I am trying to submit?
Where am I going wrong?
Basically the structure I want is:
users= [
{"name":"xxx","email":"yyy","phone":"656"},
{"name":"yyy","email":"xxx","phone":"55"}
];
import * as React from 'react';
interface IState{
users : Account[];
}
interface Account{
name: string;
email: string;
phone: string
}
export default class App extends React.Component<{},IState> {
constructor(props:any){
super(props);
this.state= {
users: []
}
}
handleChange = ( event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
users:{
...this.state.users,
[event.target.name]:event.target.value
}
})
}
onAdd = () => {
this.setState((prevState) => ({
users: [...prevState.users],
}));
console.log(this.state.users); // Unable to get the proper info
}
render(){
<React.Fragment>
<form onSubmit={this.onAdd}>
<input type="text" onChange={(e:any) => this.handleChange(e)} name={"name"} />
<input type="text" onChange={(e:any) => this.handleChange(e)} name={"email"} />
<input type="text" onChange={(e:any) => this.handleChange(e)} name={"phone"} />
<button type="submit">Add</button>
</form>
</React.Fragment>
}
}
Update onAdd like below, because setState method updates state asynchronously, therefore you can't get the state right after calling it, but you can do so using a callback as second argument in setState method which is invoked after the method has updated the state
onAdd = () => {
this.setState((prevState) => {
const newUser = {}
return {
users: [...prevState.users, newUser],
}
}, () => {
console.log(this.state.users)
});
}
Things to correct:-
1) You are using only one state variable users for one user as well as all users. So create two state variables, one for temporary storing of data for a user and users variable for storing all users data.
2) You are trying to access console.log(this.state.users); after setState but it is not in the callback, setState is asynchronous it should be in callback of setState.
3) When user submits the form, the page refreshes which is default behaviour of application, we need e.preventDefault(); to override this behaviour.
4) Use state for individual input textboxes so that you could may be apply validation etc on fields.
import * as React from "react";
import { render } from "react-dom";
interface IState {
users : Account[],
user: Account
}
interface Account{
name: string;
email: string;
phone: string
}
class App extends React.Component<{}, IState> {
constructor(props: any) {
super(props);
this.state = {
users: [],
user: {name: '', email:'', phone: ''}
}
}
handleChange = (event: React.FormEvent<HTMLInputElement>) => {
this.setState({
user: {
...this.state.user,
[event.currentTarget.name]: event.currentTarget.value
}
});
};
onAdd = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
this.setState(
{
users: [...this.state.users, this.state.user],
user: {name:'', email: '', phone: ''}
},
() => {
console.log("updated state", this.state.users);
}
);
};
public render() {
const { name, email, phone } = this.state.user;
return (
<form onSubmit={this.onAdd}>
<input type="text" onChange={this.handleChange} value={name} name="name" />
<input type="text" onChange={this.handleChange} value={email} name="email" />
<input type="text" onChange={this.handleChange} value={phone} name="phone" />
<button type="submit">Add</button>
</form>
);
}
}
render(<App />, document.getElementById("root"));
Improvement Area - You could declare array of fields like fields = ['name', 'phone', 'email'] and map over in render function, this way you would need to write form once and any no of fields could be added.
Hope that helps!!!
you don't need handleChange, in onAdd get all inputs value from the event, put them in object( like {name: event.target.form.elements.name.value ...} and set it in users(which will be an array)
export default class App extends React.Component{
constructor(props:any){
super(props);
this.state = {
users: []
}
}
onAdd = (event) => {
const user = {
name: event.target.form.elements.name.value,
email: event.target.form.elements.email.value,
phone: event.target.form.elements.phone.value
}
this.setState({
users: [...this.state.users, user]
});
}
render(){
<React.Fragment>
<form onSubmit={this.onAdd}>
<input type="text" name="name" />
<input type="text" name="email" />
<input type="text" name="phone" />
<button type="submit">Add</button>
</form>
</React.Fragment>
}
}
then if you log this.state.users you will get the stractue you need
users= [
{"name":"xxx","email":"yyy","phone":"656"},
{"name":"yyy","email":"xxx","phone":"55"}
];
export default class App extends React.Component<{},IState> {
state = {
users: [],
text: '',
}
handleChange = ( event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
[e.target.name]: e.target.value
})
}
onAdd = () => this.setState({
users:[
...this.state.users,
{
e.target.name: e.target.value,
e.target.email: e.target.value,
e.target.phone: e.target.value
}
]
})
render(){
<React.Fragment>
<form onSubmit={this.onAdd}>
<input type="text" onChange={(e:any) => this.handleChange(e)} name={"name"} />
<input type="text" onChange={(e:any) => this.handleChange(e)} name={"email"} />
<input type="text" onChange={(e:any) => this.handleChange(e)} name={"phone"} />
<button type="submit">Add</button>
</form>
</React.Fragment>
}
}
You are setting your users as an array and then you are converting it into an object with the handleChange. Try to see if this works

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

Unable to use onClick event ReactJS

I am trying to send data in a form using onClick. Below is my Form
import React from 'react';
const form = props => (
<form>
<input type = "text" name = "name" placeholder="name" />
<input type = "text" name = "lastname" placeholder="last name" />
<input type = "text" name = "id" placeholder="email address" />
<button onClick = {props.clickHandler}>Send</button>
</form>
);
export default form;
Below is my App.js
import React, { Component } from 'react';
import './App.css';
import Form from './Components/Form';
class App extends Component {
clickHandler = async(e) =>{
e.preventDefault();
const name = e.target.value;
console.log(name + 'was typed');
}
render() {
return (
<div className="App">
<h1>I am a React App </h1>
<Form form = {this.Form}/>
</div>
);
}
}
export default App;
I am not sure why the value is not being visible on console. Any help is appreciated.
First of all name your components as starting with a capital letter.
For this kind of situations, you can keep the values in your main component's state, then update them via your callback functions which are being passed to the child component.
class App extends React.Component {
state = {
name: "",
lastName: "",
email: ""
};
clickHandler = e => {
e.preventDefault();
const { name, lastName, email } = this.state;
const data = `Data is: ${name}-${lastName}-${email}`;
alert(data);
};
changeHandler = e => {
e.preventDefault();
const { name, value } = e.target;
this.setState({
[name]: value
});
};
render() {
console.log(this.state);
const { clickHandler, changeHandler } = this;
return (
<div className="App">
<h1>I am a React App </h1>
<Form clickHandler={clickHandler} changeHandler={changeHandler} />
</div>
);
}
}
const Form = props => (
<form>
<input
onChange={props.changeHandler}
type="text"
name="name"
placeholder="name"
/>
<input
onChange={props.changeHandler}
type="text"
name="lastName"
placeholder="last name"
/>
<input
onChange={props.changeHandler}
type="text"
name="email"
placeholder="email address"
/>
<button onClick={props.clickHandler}>Send</button>
</form>
);
ReactDOM.render(<App />, document.getElementById("root"));
<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="root"></div>
For the inputs, you need an onChange handler, for your button you need an onClick handler as you thought. You can see the changing state in the console. When you hit the button it creates an object using your state values then fires the alert.
You need to pass down the clickHandler as a prop to your Form component
render() {
return (
<div className="App">
<h1>I am a React App </h1>
<Form onSubmit={this.clickHandler} />
</div>
)
}
Your are using React Uncontrolled form inputs so you need to handle the form onSubmit event
import React from "react"
const form = props => (
<form
onSubmit={e => {
e.preventDefault()
this.props.onSubmit(e)
}}
>
<input type="text" name="name" placeholder="name" />
<input type="text" name="lastname" placeholder="last name" />
<input type="text" name="id" placeholder="email address" />
<input type="submit" value="Send" />
</form>
)
export default form
And in the clickHandleryou can now retrieve all values from the submit event like so
clickHandler = async e => {
e.preventDefault()
const name = e.target.elements[0]
console.log(name + "was typed")
}
Edit: Added a way to see form values
import React, { Component } from 'react';
import './App.css';
import Form from './Components/Form';
class App extends Component {
clickHandler = (data) => {
console.log('data::', data );
}
render() {
return (
<div className="App">
<h1>I am a React App </h1>
<Form clickHandler = {this.clickHandler}/>
</div>
);
}
}
export default App;
import React, { PureComponent } from 'react';
export class Form extends PureComponent {
state = {
name: '',
lastname: '',
id: ''
}
onNameChange = (event) => {
this.setState({
name: event.target.value
})
}
onLastNameChange = (event) => {
this.setState({
lastname: event.target.value
})
}
onEmailChange = (event) => {
this.setState({
id: event.target.value
})
}
render() {
return (
<form>
<input type = "text" name = "name" placeholder="name" onChange={this.onNameChange} />
<input type = "text" name = "lastname" placeholder="last name" onChange={this.onLastNameChange} />
<input type = "text" name = "id" placeholder="email address" onChange={this.onEmailChange}/>
<button onClick = {props.clickHandler.bind(null, this.state)}>Send</button>
</form>
);
}
}

Redux-Form initial values from

I'm trying to fill the profile form with data from API. Unfortunately redux-form doesn't want to cooperate with me in this case. For some reason fields stays empty whatever I do.
Setting the fixed values instead of values passed from reducer work well for some reason.
Maybe this is because I'm using redux-promise for API calls inside the action creators? How can I live with it and get rid of this. Here is my form component.
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import { fetchRoleList, fetchUserData } from '../actions';
class UserEdit extends Component {
componentWillMount() {
this.props.fetchRoleList();
this.props.fetchUserData();
}
handleEditProfileFormSubmit(formProps) {
console.log(formProps);
}
getRoleOptions(selected_id) {
if (!this.props.profile) {
return <option>No data</option>;
}
return this.props.profile.roles.map(role => {
return <option key={role.role_id} value={role.role_id}>{role.name}</option>;
});
}
renderField(props) {
const { input, placeholder, label, value, type, meta: { touched, error } } = props;
return (
<fieldset className={`form-group ${ (touched && error) ? 'has-error' : '' }`}>
<label>{label}</label>
<input className="form-control" {...input} type={type} placeholder={placeholder} />
{touched && error && <div className="error">{error}</div>}
</fieldset>
);
}
renderSelect({ input, placeholder, options, label, type, meta: { touched, error } }) {
return (
<fieldset className={`form-group ${ (touched && error) ? 'has-error' : '' }`}>
<label>{label}</label>
<select className="form-control" {...input}>
{options}
</select>
{touched && error && <div className="error">{error}</div>}
</fieldset>
);
}
render() {
const { handleSubmit } = this.props;
const user = this.props.profile.user;
return (
<div> {user ? user.email : ''}
<form onSubmit={handleSubmit(this.handleEditProfileFormSubmit.bind(this))}>
<Field name="email" label="Email:" component={this.renderField} type="text" placeholder="email#gmail.com" className="form-control"/>
<Field name="name" label="Name:" component={this.renderField} type="text" placeholder="John Doe" className="form-control"/>
<Field name="role" label="Role:" component={this.renderSelect} type="select" className="form-control" options={this.getRoleOptions()}/>
<button action="submit" className="btn btn-primary">Edit user</button>
<Field name="password" label="Password:" component={this.renderField} type="password" className="form-control"/>
<Field name="passwordConfirm" label="Confirm Password:" component={this.renderField} type="password" className="form-control"/>
{ this.props.errorMessage
&& <div className="alert alert-danger">
<strong>Oops!</strong> {this.props.errorMessage}
</div> }
<button action="submit" className="btn btn-primary">Sign up!</button>
</form>
</div>
);
}
}
let InitializeFromStateForm = reduxForm({
form: 'initializeFromState'
})(UserEdit);
InitializeFromStateForm = connect(
state => ({
profile: state.profile,
initialValues: state.profile.user
}),
{ fetchRoleList, fetchUserData }
)(InitializeFromStateForm);
export default InitializeFromStateForm;
I do believe action creator will be useful as well:
export function fetchUserData(user_id) {
user_id = user_id ? user_id : '';
const authorization = localStorage.getItem('token');
const request = axios.get(`${ROOT_URL}/user/${user_id}`, {
headers: { authorization }
});
return {
type: FETCH_USER,
payload: request
};
}
You need to add enableReinitialize: true as below.
let InitializeFromStateForm = reduxForm({
form: 'initializeFromState',
enableReinitialize : true // this is needed!!
})(UserEdit)
If your initialValues prop gets updated, your form will update too.
To set the initialValues it is important to apply the reduxForm() decorator before the connect() decorator from redux. The fields will not populate from the store state if the order of the decorators is inverted.
const FormDecoratedComponent = reduxForm(...)(Component)
const ConnectedAndFormDecoratedComponent = connect(...)(FormDecoratedComponent)
If, in addition to setting the values for the first time, you need to re-populate the form every time the state changes then set enableReinitialize: true
Find a simple example in this answer.
Read the official documentation and full example.
Read about this issue here.
So, you're trying:
Load API data into the form
Update the form just on load (aka. initialValues)
Whilst #FurkanO might work, I think the best approach is to load the form when you got all async data, you can do that by creating a parent component / container:
UserEditLoader.jsx
componentDidMount() {
// I think this one fits best for your case, otherwise just switch it to
// componentDidUpdate
apiCalls();
}
/* api methods here */
render() {
const { profile } = this.props;
return (
{profile && <UserEdit profile={profile} />}
);
}
Basically what you should be doing in the UserEditLoader is to execute the API functions and update the state (or props if redux connected). Whenever the profile variable isn't empty (meaning you got the data you were expecting) then mount UserEdit with profile as prop.
initialize() is a prop provided by reduxForm, that can be used to fill up the form values.
change() is another prop provided by reduxFrom to change a field value.
import * as React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
const submit = values => {
// print the form values to the console
console.log(values)
}
interface Props {
history?: any;
location?: any;
session?: any;
handleSubmit?: Function;
initialize?: Function;
change?: Function;
}
class ContactForm extends React.Component<Props, any> {
constructor(props, state) {
super(props, state);
this.state = {
value: ''
};
}
componentDidMount() {
const { initialize, session, location } = this.props;
console.log(location.pathname);
if (session && session.user) {
const values = {
firstName: session.user.name,
lastName: session.user.lastName,
email: session.user.email
};
initialize(values);
}
}
componentWillReceiveProps(nextProps) {
const { initialize, session } = this.props;
if (nextProps.session !== session) {
if (nextProps.session && nextProps.session.user) {
const values = {
firstName: nextProps.session.user.name,
lastName: nextProps.session.user.lastName,
email: nextProps.session.user.email
};
initialize(values);
} else {
const values = {
firstName: null,
lastName: null,
email: null
};
initialize(values);
}
}
}
render() {
const { handleSubmit, change } = this.props;
return (
<React.Fragment>
<form onSubmit={handleSubmit(submit)}>
<div>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" component="input" type="text" />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" component="input" type="text" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field name="email" component="input" type="email" />
</div>
<button type="submit">Submit</button>
</form>
<input type="text" value={this.state.value}
onChange={(e) => {
this.setState({ value: e.target.value });
change('firstName', e.target.value);
}}
/>
</React.Fragment>
);
}
}
export default connect((state) => {
return {
session: state.session
}
},
{}
)(withRouter((reduxForm({
form: 'contact'
})(ContactForm))));
If the enableReinitialize : true trick does not work, you can update each field when the initialValues prop changes.
componentWillReceiveProps(nextProps) {
const { change, initialValues } = this.props
const values = nextProps.initialValues;
if(initialValues !== values){
for (var key in values) {
if (values.hasOwnProperty(key)) {
change(key,values[key]);
}
}
}
}
I have never worked with FieldsArray but I assume this would not work here.
For a stateless functional component, you can do it like this:
componentWillMount() {
this.props.initialize({ discountCodes: ["ABC200", "XYZ500"] });
}
For a class, you can do it like this:
const mapStateToProps = state => (
{
initialValues: {
discountCodes: ["ABC200", "XYZ500"]
}
);

Resources