How to get state / value from form component? - javascript

Consider having form component like:
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.props.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
I would then use this <Form onSubmit={this.someFunction} /> component elsewhere within my app, lets assume inside HomePage component. Inside that home page I would have this.someFunction that executes when form is summited, how can I pass form value / state to it?

Create a callback in your component that will call the function sent to Form with the state as parameter.
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
onSubmit() {
this.props.onSubmit(this.state);
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}

What you're (essentially) looking to do is pass some data up the component chain (to a parent component). You could implement this with vanilla React, but I'm not going to advise you to do this.
If you try implementing some kind of state management yourself, unless your app is incredibly simple or you are an incredibly disciplined one-man-team, it's likely to get messy and unpredictable fast.
I advocate one way data flow. Data should flow one way through your app - down. I recommend you look at implementing a solution with Flux or Redux (Redux is my preference). These are both state containers that will propagate state throughout your app and enforce a set of conventions which you help you maintain structure to your data flow as your app grows.
I admit, you're adding to the learning curve by implementing a solution with these containers, but remember that React is only the view layer and it can't help you much with problems surrounding state management.

You could do this:
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
// Wrap around this.props.onSubmit and add data to it.
onSubmit() {
this.props.onSubmit(this.state);
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
Very similar to how you bound and use your onChange.

Related

React.js this.props.user always is null in componentDidMount

I have a user delete form, which leads users to create some tickets which will be discussed inside the back-end, but as I can see that after creating a ticket I would like to change some return pattern, instead of the form I would like to return a simple text, but I cannot get my this.props.user inside componentDidMount, I grab the state from redux. This is my code:
import React from 'react';
import { connect } from 'react-redux';
import { MdDeleteSweep } from 'react-icons/md';
import {
Container,
Title,
Span,
DangerSpan,
Desc,
Form,
FieldLabel,
TextField,
Field,
Btn,
} from './elements/Delete';
import { delete_request } from '../../actions/auth';
class AccountDelete extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
desc: '',
ticketCreated: false,
};
this.onChange = this.onChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
handleClick(e) {
e.preventDefault();
this.props.delete_request(this.state.email);
this.setState({ ticketCreated: true });
}
componentDidMount() {
console.log(this.props);
if (!this.props.user) return null;
let delres = this.props.user.del_request[0];
if (delres) {
this.setState({ ticketCreated: true });
}
}
render() {
const { user } = this.props;
if (!user) return null;
return (
<Container>
<div style={{ marginBottom: '1rem' }}>
<Title>
<MdDeleteSweep /> <DangerSpan>Delete</DangerSpan> Your Account
</Title>
<Desc>
You can submit your Winteka Account for deletion at any time. If you
change your mind, you might not be able to recover it.
</Desc>
</div>
{this.state.ticketCreated ? (
<Desc>
<DangerSpan>*</DangerSpan> You have already <b>opened a request</b>,
please check your <b>e-mail address</b> for more information. For
any questions please <b>Contact Us</b>.
</Desc>
) : (
<Form>
<FieldLabel>
<DangerSpan>*</DangerSpan> Your <Span>active email address</Span>
</FieldLabel>
<Field
type="email"
name="email"
placeholder={user.email}
value={this.state.email}
onChange={this.onChange}
/>
<FieldLabel>
<DangerSpan>*</DangerSpan> Your <Span>current password</Span>
</FieldLabel>
<Field
type="password"
name="password"
value={this.state.password}
onChange={this.onChange}
/>
<FieldLabel>
Describe <Span>your problem</Span>
</FieldLabel>
<TextField
rows="3"
name="desc"
value={this.state.desc}
onChange={this.onChange}
/>
<Btn onClick={this.handleClick}>Submit Application</Btn>
</Form>
)}
</Container>
);
}
}
const mapStateToProps = (state) => ({
user: state.auth.user,
});
export default connect(mapStateToProps, { delete_request })(AccountDelete);
this is what this.props is in componentDidMount
1. Why do you need the this.props.user inside componentDidMount ?
It seems like you are unnecessary duplicating the this.setState({ ticketCreated: true }) call inside both componentDidMount and
handleClick but it should be enough to just put it inside
handleClick so you can just remove the componentDidMount block.
If you want to introduce a side-effect or execute a callback
when ticketCreated becomes true, you should either,
a) use the componentDidUpdate lifecycle hook or,
b) this.setState(newState, callbackFn) where callbackFn gets executed on the state update.
2. The error you are getting,
Warning: Failed propType: Invalid prop component supplied to Route
indicates you are on an old version (<4.4.0) of react-router-dom. Link to relevant github issue.
You should probably update the package with the following command,
$ npm install --save react-router-dom

How to disable a button when state changes in React [duplicate]

This question already has answers here:
How to disable button in React.js
(8 answers)
Closed 3 years ago.
I am using trying to disable a button in react based on couple states. Down below is a breakdown of my code
constructor(props) {
super(props);
this.state = {
email: '',
pass: '',
disabled: true
}
this.handleChange = this.handleChange.bind(this);
this.handlePass = this.handlePass.bind(this);
}
pretty self explanatory constructor. The disabled will be changed as state changes. My render method looks something like this
render() {
if(this.state.email && this.state.pass) {
this.setState({ disabled: false })
}
return (
<div className='container'>
<div className='top'></div>
<div className='card'>
<MuiThemeProvider>
<Card >
<div className='wrapper'>
<TextField
hintText="Email"
value={this.state.email} onChange={this.handleChange}
/><br/>
<TextField
hintText="Password"
type="password"
/><br/>
<div className='login-btn'>
<RaisedButton label="Login" primary={true}
disabled={this.state.disabled} />
</div>
</div>
</Card>
</MuiThemeProvider>
</div>
</div>
)
}
As you can see I have 2 text fields and I am handeling the data changes with the following method
handleChange(e) {
this.setState({email: e.target.value});
}
handlePass(e) {
this.setState({pass: e.target.value});
}
Now my button is initially disabled and everytime a state is changed and component re-renders I want to check for state changes and enable button accordingly. So I was thinking of using the life cycle method like so
componentWillMount() {
if(this.state.pass && this.state.disabled) {
this.setState({disabled: false})
}
}
However, this doesn't work. When both email and password field is not empty the button stays disabled. I am not sure what am I doing wrong.
Please, do not set states inside render() function. That might cause infinite loops to occur.
Refer: https://github.com/facebook/react/issues/5591
Instead of setting states inside render() function, you can set the disabled state inside the handleChange() and handlePass() function.
If more detail required, please do mention.
You should be setting the disabled state inside your handleChange and handlePass functions.
componentWillMount() only runs right before the component is rendered, but never again.
Just made a demo , is that you need, check the code in the demo below
demo
Change below code :
class App extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
pass: '',
invalidData: true
}
this.onEmailChange = this.onEmailChange.bind(this);
this.onPasswordChange = this.onPasswordChange.bind(this);
}
// componentWillUpdate is to be deprecated
//componentWillUpdate(nextProps, nextState) {
// nextState.invalidData = !(nextState.email && nextState.pass);
//}
onEmailChange(event) {
this.setState({ email: event.target.value });
}
onPasswordChange(event) {
this.setState({ pass: event.target.value });
}
render() {
return (
<form>
<input value={this.state.email} onChange={this.onEmailChange} placeholder="Email" />
<input value={this.state.password} onChange={this.onPasswordChange} placeholder="Password" />
// from this <button disabled={this.state.invalidData}>Submit</button>
//to
<button disabled={!(this.state.email && this.state.password)}>Submit</button>
</form>
);
}
}
**updated **
disable submit button in <button disabled={!(this.state.email && this.state.password)}>Submit</button> itself.

Creating a form in React that saves data

I am trying to create a customer details form in react (currently using react-json-form) where I can reuse the values in the inputs to create a saved file that the app can refer to. I have created the form and can output the results but I am unsure how to save the input values for future use or call them back once they are saved.
If anyone has any suggestions or examples of a form that does this then I would be greatly appreciative.
My code is as follows:
import React, { Component } from 'react';
import JSONTree from 'react-json-tree';
import { BasicForm as Form, Nest, createInput } from 'react-json-form';
const Input = createInput()(props => <input type="text" {...props} />);
const UserFields = () => (
<section>
<h3>User</h3>
<div>Name: <Input path="name" /></div>
<div>Email: <Input path="email" /></div>
</section>
);
export default class ExampleForm extends Component {
state = { data: {} };
updateData = data => this.setState({ data });
render() {
return (
<Form onSubmit={this.updateData}>
<Nest path="user">
<UserFields />
</Nest>
<button type="submit">Submit</button>
<JSONTree data={this.state.data} shouldExpandNode={() => true} />
</Form>
);
}
}
A more simple solution would be to use a form, like a semanti-ui-react form, store the information to the state onChange, then convert the info to JSON for storage.
import { Form, Button } from 'semantic-ui-react'
export default class App extends Component {
constructor() {
super()
this.state = {
name: "",
email: ""
}
}
handleChange = (e, {name, value}) => {
console.log(name, value)
this.setState({[name]: value})
}
render() {
return (
<div>
<Form onSubmit={this.sendDataSomewhere}>
<Form.Field>
<Form.Input name="name" value={this.state.name} onChange={this.handleChange}/>
</Form.Field>
<Form.Field>
<Form.Input name="email" value={this.state.email} onChange={this.handleChange}/>
</Form.Field>
<Button type="submit">Submit</Button>
</Form>
</div>
)
}
}
I use a dynamic method of receiving the input from different fields using the name and val attributes. The values captured in state are then accessible by this.state.whatever
Hope this helped

How to avoid duplicate event listener in react?

I have a form in react with many input components. I do not like that I have to write a new onChange handler method for every input component that I build. So I want to know how can I stop repeated code.
<Input
label={"Blog Name"}
hint={"e.g. 'The Blog'"}
type={"text"}
value={this.state.name}
onChange={this.handleInputChange.bind(this, "name")}
/>
<Input
label={"Blog Description"}
hint={"e.g. 'The Blog Description'"}
type={"text"}
value={this.state.desc}
onChange={this.handleInputChange.bind(this, "desc")}
/>
So instead of writing a new function I am reusing the same function and passing an extra value. Is this the right way to do it? How do other experienced people solve this problem.
If you want your parent component to maintain the state with the value of each input field present in 'Input' child components, then you can achieve this with a single change handler in the following way:
handleChange(id, value) {
this.setState({
[id]: value
});
}
where the id and value are obtained from the Input component.
Here is a demo: http://codepen.io/PiotrBerebecki/pen/rrJXjK and the full code:
class App extends React.Component {
constructor() {
super();
this.state = {
input1: null,
input2: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id, value) {
this.setState({
[id]: value
});
}
render() {
return (
<div>
<Input id="input1"
changeHandler={this.handleChange} />
<Input id="input2"
changeHandler={this.handleChange} />
<p>See input1 in parent: {this.state.input1}</p>
<p>See input2 in parent: {this.state.input2}</p>
</div>
);
}
}
class Input extends React.Component {
constructor() {
super();
this.state = {
userInput: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const enteredText = event.target.valuel
this.setState({
userInput: enteredText
}, this.props.changeHandler(this.props.id, enteredText));
}
render() {
return (
<input type="text"
placeholder="input1 here..."
value={this.state.userInput}
onChange={this.handleChange} />
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
You can try event delegation, just like the traditional ways.
That is, just bind a function to the parent form element, and listens to all the events bubbling up from the children input elments.

How to pass forms event to a parent component to preventDefault?

I have a quite standard form:
export default class Form extends React.Component {
constructor (props) {
super(props)
this.state = {
email: '',
refCode: ''
}
this.onSubmit = this.onSubmit.bind(this)
this.changeEmail = this.changeEmail.bind(this)
}
changeEmail (event) {
this.setState({ email: event.target.value })
}
onSubmit () {
this.props.onSubmit(this.state)
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.onSubmit}>
<input
className={cx('Form-email')}
onChange={this.changeEmail}
value={this.state.email}
type='email'
placeholder='email' />
<input className={cx('Form-refcode')} type='text' placeholder='enter referal code' />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
I then want to handle forms submission in a parent component, via this function
submitForm (value) {
event.preventDefault()
console.log(value.email)
}
/* ... */
<Form
title='What are you waiting for?! Sign up now'
onSubmit={this.submitForm} />
The issue I encountered is that event.preventDefault() is not working, nor does it seem that I am getting correct value logged via console.
I assume I am not passing or receiving values here correctly but I have no idea where i'm going wrong.
In child component pass event(you can name it as you want) argument to method from parent component
onSubmit (event) {
this.props.onSubmit(event, this.state)
}
In parent component do like this
submitForm (event, value) {
event.preventDefault()
console.log(value.email)
}

Categories