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)
}
Related
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".
The handleSubmit function seems to refresh the page without firing any of the internal logic. I've set up a few console.log's along the way to test out if the internally declared const that's set to the venue property in the state would log, but nothing appears.
I've commented on various parts of the function stepwise starting with setting the scheduling variable to my Firebase schedule table.
After that, I changed the handleSubmit function from an arrow function to just handleSubmit(e) (sorry, I'm new to this so I'm not familiar with the terminology)
import React, {Component} from 'react';
import FrontNav from './nav.js';
import firebase from '../Firebase';
class FrontSchedule extends Component {
constructor () {
super();
this.state = {
venue:'',
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = (e) => {
e.preventDefault();
this.setState ({
venue: e.target.value,
});
}
handleSubmit = (e) => {
e.preventDefault();
// let schedule = firebase.database().ref('schedule')
const item = {
venue: this.state.venue,
}
console.log(item);
// schedule.push(item);
// console.log(firebase.database().ref('schedule'));
console.log(this.state.venue);
// this.setState({
// venue:'',
// })
}
render(){
return(
<div>
<FrontNav/>
<h1>Schedule</h1>
<form>
<input type="text"
name="venue"
onChange={this.handleChange}
onSubmit={this.handleSubmit}
value={this.state.venue}/>
<button onSubmit={this.handleSubmit}> Enter Event </button>
</form>
</div>
);
}
}
export default FrontSchedule;
Herein lies the crux of the problem. The page refreshes and the input bar clears, but no error message is provided. At this point, I'm really confused about what is going on here. Any feedback is appreciated!
Let us consider the following example:-
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
Now, when we press on submit button the default behavior to browse to a new page. If you want this behavior it works out of the box in ReactJS. But in cases where you need more sophisticated behavior like form validations, custom logic after the form is submitted you can use controlled components.
We can do so by using following:-
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
Now coming to your solution it can be implemented as follows:-
class FrontSchedule extends React.Component {
constructor () {
super();
this.state = {
venue:'',
}
/* this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); */
}
handleChange = (e) => {
e.preventDefault();
this.setState ({
venue: e.target.value,
});
}
handleSubmit = (e) => {
event.preventDefault();
// let schedule = firebase.database().ref('schedule')
const item = {
venue: this.state.venue,
}
console.log(item);
// schedule.push(item);
// console.log(firebase.database().ref('schedule'));
console.log(this.state.venue);
// this.setState({
// venue:'',
// })
}
render(){
console.log(this.state);
return(
<div>
<h1>Schedule</h1>
<form onSubmit={this.handleSubmit}>
<input type="text"
name="venue"
onChange={this.handleChange}
onSubmit={this.handleSubmit}
value={this.state.venue}/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
ReactDOM.render(<FrontSchedule />, document.querySelector("#app"))
Hope it helps :)
You can read more at react documentationhere
How to redirect in react router in self page. e.g I have a component ProfileUpdate it updates name & stores to backend. After successful update I want to redirect to same page but it's not working. Here is a sample code.
class ProfileUpdate extends Component {
constructor() {
super();
this.state = {
name: ''
};
}
onSubmit(e) {
e.preventDefault();
this.props.history.replace('/');
}
onChange(e) {
const name = e.target.value;
this.setState(() => ({
name
}))
}
render() {
return (
<form onSubmit={this.onSubmit}>
<div>
<h4>Display Name:</h4>
<div>
<input name='displayName' placeholder='Display name' type='text' value={this.state.name} onChange={this.onChange} />
</div>
</div>
<div>
<button type='submit'>Update</button>
</div>
</form>
);
}
}
Expected behaviour if I update name then click submit it will replace the route/redirect & name input should be empty.
But when I choose a different path to replace it works. e.g this.props.history.replace('/users') it works. But here this.props.history.replace('/'); it doesn't work.
I have small class in react, i want to display the result on the screen after i click on the button, but before the display happens, the page reload.
how do i do it?
what am I missing?
import React, {Component} from 'react';
class InputFieldWithButton extends Component{
constructor(props){
super();
this.state = {
message: ''
};
}
handleChange(e){
this.setState({
message: e.target.value
});
}
doSomething(e){
return(
<h1>{this.state.message}</h1>
)
}
render(){
return (
<div>
<form >
<input type="text" placeholder="enter some text!" value=
{this.state.message}
onChange={this.handleChange.bind(this)}/>
<button onClick={this.doSomething.bind(this)}>Click me</button>
</form>
</div>
)
}
}
export default InputFieldWithButton;
Your button is inside a form and triggering a submit.
You can use the preventDefault() method to stop it from doing so:
doSomething(e) {
e.preventDefault();
return (
<h1>{this.state.message}</h1>
)
}
By the way, your return statement of this click handler makes no sense at the moment.
Edit
As a followup to your comment:
Can you explain me what is my mistake in the return?
Not really a mistake, but it is useless in this context as your are not doing anything with the returned object.
Where and how do you expect to use the <h1>{this.state.message}</h1> that you are returning?
If you intend to show / hide the input message in your screen you could do it with conditional rendering.
Just store a bool like showMessage in your state and render the message only if it's set to true.
Here is a small example:
class InputFieldWithButton extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '',
showMessage: false
};
}
handleChange = (e) => {
this.setState({
message: e.target.value
});
}
toggleMessage = (e) => {
e.preventDefault();
this.setState({ showMessage: !this.state.showMessage })
}
render() {
const { showMessage, message } = this.state;
return (
<div>
<form >
<input
type="text"
placeholder="enter some text!"
value={message}
onChange={this.handleChange}
/>
<button onClick={this.toggleMessage}>Toggle Show Message</button>
{showMessage && <div>{message}</div>}
</form>
</div>
)
}
}
ReactDOM.render(<InputFieldWithButton />, 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>
By the way, it is considered as bad practice to bind the functions inside the render method, because you are creating a new instance of a function on each render call. instead do it inside the constructor which will run only once:
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
Or you can use arrow functions which will reference this in a lexical context:
handleChange = (e) => {
this.setState({
message: e.target.value
});
}
This is what i've used in my example.
you're not specifying the buttons'type
<button type="button">
Set the type attribute on the button to be button. The default is submit since it is wrapped in a form. So your new button html should look like this:
<button type="button" onClick={this.doSomething.bind(this)}>Click me</button>
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.