I'm making my first steps with React and got stuck on POST-ing data from a simple user registration form to the backend API. For some reason, when I press the "Register" button in the form and want to call handleSubmit, it doesn't get called and instead throws some error.
import React, { Component } from 'react';
import axios from 'axios';
import { Container, Row, Col } from 'reactstrap';
import NavBar from '../NavBar';
class RegistrationForm extends Component {
constructor(props)
{
super(props)
this.state = {
name: '',
display: '',
email: '',
pswd: ''
}
console.log("RegistrationForm constructor");
};
changeNameValue = (e) => { this.setState({ name: e }) }
changeDisplayValue = (e) => { this.setState({ display: e }) }
changeEmailValue = (e) => { this.setState({ email: e }) }
changePswdValue = (e) => { this.setState({ pswd: e }) }
handleSubmit = () => {
console.log("handleSubmit()");
const data = {
username: this.state.name,
displayname: this.state.display,
email: this.state.email,
password: this.state.pswd
}
axios.post(
'http://localhost:9000/api/registeruser',
{ body: JSON.stringify(data) }
).then(res => console.log("Posted"));
}
render() {
var logoImage = require('../../images/logo_v3_150.png');
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<Container>
<Row>
<img src={logoImage} width='150' height='184' /><br />
</Row>
<Row>
<form>
<input value={this.state.name} onChange={(e) => this.changeNameValue(e.target.value)} placeholder='Username' /><br />
<input value={this.state.display} onChange={(e) => this.changeDisplayValue(e.target.value)} placeholder='Display Name' /><br />
<input value={this.state.email} onChange={(e) => this.changeEmailValue(e.target.value)} placeholder='Email' /><br />
<input value={this.state.pswd} onChange={(e) => this.changePswdValue(e.target.value)} placeholder='Password' /><br />
<button onClick={this.handleSubmit}>Register</button>
</form>
</Row>
<Row>
<NavBar />
</Row>
</Container>
</div>
);
}
}
export default RegistrationForm;
The error (only this message in the Firefox's Dev Tools):
This is probably something silly that I missed but I can't figure it out by myself.
Related
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
I have a react project that is pulling data from a firebase back end. When I load the page rendering my GigRegister component, I get the following error:
Unhandled Rejection (TypeError): Cannot read property 'uid' of null
....but when I refresh the page, it loads as it should. Any ideas as to why? Here's the code for my GigRegister component:
import React from "react";
import Header from "./Header";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
import axios from "axios";
import * as firebase from 'firebase'
import { auth } from 'firebase/app'
import {Link} from 'react-router-dom'
import UniqueVenueListing from './UniqueVenueListing'
class GigRegister extends React.Component {
constructor() {
super();
this.state = {
name: "",
venue: "",
time: "",
date: "",
genre: "",
tickets: "",
price: "",
userDetails:{},
filterGigs:[]
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this)
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value,
});
}
handleClick(){
console.log('handle click reached')
auth().signOut().then(() => {
console.log('Successfully signed out')
})
.catch(err => {
console.log(err)
})
}
authListener(){
auth().onAuthStateChanged((user)=>{
if(user){
this.setState({
userDetails: user
})
axios.get("https://us-central1-gig-fort.cloudfunctions.net/api/getGigListings")
.then(res=> {
let filteredGigs = res.data
.filter(gig => {
return gig.user === this.state.userDetails.uid
})
this.setState({
filterGigs: filteredGigs
})
})
} else {
this.setState({
userDetails: null
})
console.log('no user signed in')
}
})
}
componentDidMount(){
this.authListener()
}
handleSubmit(e) {
let user = auth().currentUser.uid
const gigData = {
name: this.state.name,
venue: this.state.venue,
time: this.state.time,
date: this.state.date,
genre: this.state.genre,
tickets: this.state.tickets,
price: this.state.price,
user:user
};
auth().currentUser.getIdToken().then(function(token) {
axios("http://localhost:5000/gig-fort/us-central1/api/createGigListing", {
method: "POST",
headers: {
"content-type": "application/json",
"Authorization": "Bearer "+token,
},
data: gigData,
})
})
.then((res) => {
console.log(res);
this.props.history.push('/Homepage')
})
.catch((err) => {
console.error(err);
});
}
render() {
return (
<div className="gig-register">
<Header />
<div className = 'heading-container'>
<h1>Venue Dashboard</h1> <br></br>
{
this.state.userDetails ?
<h3>You are signed in as {this.state.userDetails.email}</h3>
:
null
}
<div className = 'gig-reg-buttons'>
{
this.state.userDetails ?
<Button onClick = {this.handleClick}>Sign out </Button>
:
<Link to = '/' style={{ textDecoration: "none" }}>
<Button>Sign In</Button>
</Link>
}
<Link to="/Homepage" style={{ textDecoration: "none" }}>
<Button>Go to gig listings</Button>
</Link>
</div>
</div>
<div className = 'handle-gigs'>
<div className = 'reg-gig-input'>
<form onSubmit={this.handleSubmit}>
<h3>Register a gig</h3>
<br></br>
<TextField
placeholder="Event name"
defaultValue="Event name"
id="name"
name="name"
onChange={this.handleChange}
/>
<TextField
placeholder="Time"
defaultValue="Time"
type="time"
label="Enter start time"
id="time"
name="time"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={this.handleChange}
/>
<TextField
id="date"
label="Select date"
type="date"
defaultValue="2017-05-24"
InputLabelProps={{
shrink: true,
}}
onChange={(e) => {
this.setState({ date: e.target.value });
}}
/>
<TextField
placeholder="Genre"
defaultValue="Genre"
id="genre"
name="genre"
onChange={this.handleChange}
/>
<TextField
placeholder="Tickets"
defaultValue="Tickets"
id="tickets"
name="tickets"
onChange={this.handleChange}
/>
<TextField
placeholder="Price"
defaultValue="Price"
id="price"
name="price"
onChange={this.handleChange}
/>
<Button type="submit">Submit</Button>
</form>
</div>
<div className = 'manage-gigs'>
<h3 className = 'manage-gig'>Manage your gigs</h3>
<br></br>
{ this.state.userDetails ?
<UniqueVenueListing gigList = {this.state.filterGigs}/>
:
<h2>no gigs to show</h2>
}
</div>
</div>
</div>
);
}
}
export default GigRegister
Because you're not logged in yet on the first page load. When you first load this component on your app your browser still hasn't checked with Firebase if you're logged in or not, so on that very first render currentUser will be undefined.
It's an easy fix though. As an example, just have a state variable, say userIsLoggedIn tracking the login status, which will start out as undefined. On the callback in onAuthStateChanged you can set it to either true or false, depending on whether authentication was successful or not.
Then, if userIsLoggedIn is undefined, you can show a message like "Authenticating...", if it's false you show a login form, and if its true you get on with your app knowing your used is logged in properly and currentUser.uid has a value.
Here's the offical documentation on this, by the way.
I'm fetching an array of errors, one of them says "Username is taken" and the other one says "Password must be at least 5 chars".
It's displayed like this in React.
{this.props.auth.errors ? (
this.props.auth.errors.map( (err, i) => (
<div key={i} style={{color: 'red'}}>
{err}
</div>
))
):(
null
)}
this.props.auth.errors is an array containing the error messages, after registerUser gets called.
Actions.js
export const registerUser = (userData) => dispatch => {
Axios
.post('/users/register', userData)
.then( res => {
const token = res.data.token;
// console.log(token);
// pass the token in session
sessionStorage.setItem("jwtToken", token);
// set the auth token
setAuthToken(token);
// decode the auth token
const decoded = jwt_decode(token);
// pass the decoded token
dispatch(setCurrentUser(decoded))
// this.props.history.push("/dashboard")
}).catch( err => {
// console.log(err.response.data.error[0].msg)
Object.keys(err.response.data.error).forEach( (key) => {
dispatch({
type: GET_ERRORS,
payload: err.response.data.error[key].msg
})
})
})
};
How would i be able to remove the error, if for example the user does make a password that is minimum of 5 chars, or uses a username that does exist ? You know what i mean ? Also could this be done asynchronously ? As if you were to sign in or register on a top end social media website where it shows the error on async and gos away once you complied to the error message.
Full react code
SignUp.js
import React, { Component } from "react";
import {connect} from 'react-redux';
import {registerUser} from '../actions/authActions';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid';
import PropTypes from "prop-types";
import Typography from '#material-ui/core/Typography';
class SignUp extends Component{
constructor() {
super();
this.state = {
formData:{
email:'',
username:'',
password:'',
passwordConf: "",
isAuthenticated: false,
},
errors:{},
passErr:null
}
}
componentDidMount() {
// console.log(this.props.auth);
if (this.props.auth.isAuthenticated) {
this.props.history.push("/dashboard");
}
}
// this line is magic, redirects to the dashboard after user signs up
componentWillReceiveProps(nextProps) {
if (nextProps.auth.isAuthenticated) {
this.props.history.push("/dashboard");
}
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
handleChange = (e) => {
e.preventDefault();
const {formData} = this.state;
this.setState({
formData: {
...formData,
[e.target.name]: e.target.value
}
});
}
handleSubmit = (e) => {
e.preventDefault();
const {formData} = this.state;
const {username, email, password, passwordConf} = formData;
this.setState({
username: this.state.username,
password: this.state.password,
passwordConf: this.state.passwordConf,
email: this.state.email
});
const creds = {
username,
email,
password
}
console.log(creds);
if (password === passwordConf) {
this.props.registerUser(creds, this.props.history);
} else {
this.setState({passErr: "Passwords Don't Match"})
}
}
render(){
return(
<div>
<Grid container justify="center" spacing={0}>
<Grid item sm={10} md={6} lg={4} style={{ margin:'20px 0px'}}>
<Typography variant="h4" style={{ letterSpacing: '2px'}} >
Sign Up
</Typography>
{this.props.auth.errors ? (
this.props.auth.errors.map( (err, i) => (
<div key={i} style={{color: 'red'}}>
{err}
</div>
))
):(
null
)}
{this.state.passErr && (
<div style={{color: 'red'}}>
{this.state.passErr}
</div>
)}
<form onSubmit={this.handleSubmit}>
<TextField
label="Username"
style={{width: '100%' }}
name="username"
value={this.state.username}
onChange={this.handleChange}
margin="normal"
/>
<br></br>
<TextField
label="Email"
className=""
style={{width: '100%' }}
name="email"
value={this.state.email}
onChange={this.handleChange}
margin="normal"
/>
<br></br>
<TextField
label="Password"
name="password"
type="password"
style={{width: '100%' }}
className=""
value={this.state.password}
onChange={this.handleChange}
margin="normal"
/>
<br></br>
<TextField
label="Confirm Password"
name="passwordConf"
type="password"
style={{width: '100%' }}
className=""
value={this.state.passwordConf}
onChange={this.handleChange}
margin="normal"
/>
<br></br>
<br></br>
<Button variant="outlined" color="primary" type="submit">
Sign Up
</Button>
</form>
</Grid>
</Grid>
</div>
)
}
}
SignUp.propTypes = {
registerUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth
})
const mapDispatchToProps = (dispatch) => ({
registerUser: (userData) => dispatch(registerUser(userData))
})
export default connect(mapStateToProps, mapDispatchToProps)(SignUp)
AuthReducer
import {SET_CURRENT_USER, GET_ERRORS} from '../actions/types';
import isEmpty from '../actions/utils/isEmpty';
const initialState = {
isAuthenticated: false,
errors: []
}
export default (state = initialState, action) => {
switch (action.type) {
case SET_CURRENT_USER:
return{
...state,
isAuthenticated: !isEmpty(action.payload),
user:action.payload
}
case GET_ERRORS:
console.log(action.payload)
// allows for us to loop through an array of errors.
return Object.assign({}, state, {
errors: [...state.errors, action.payload]
})
default:
return state;
}
}
a UI example of how this plays out
Could you clear the errors from your state inside the GET_ERRORS case in your reducer? Basically change your case GET_ERRORS to this:
case GET_ERRORS:
console.log(action.payload)
// allows for us to loop through an array of errors.
return Object.assign({}, state, {
errors: [action.payload]
})
Then in your action creator, do this in your catch instead:
const errors = [];
Object.keys(err.response.data.error).forEach( (key) => {
errors.push(err.response.data.error[key].msg)
})
dispatch({
type: GET_ERRORS,
payload: errors,
})
To get the errors all on individual lines do something like this in your render function:
{this.props.auth.errors ? (
this.props.auth.errors.map( (err, i) => (
<div key={i} style={{color: 'red'}}>
{err}
</div>
<br />
))
):(
null
)}
I am completely new to both react and testing and am a bit stumped.
I was just wondering if someone could tell me why my test fails. I assume I making a basic mistake in how this should work.
I am trying to test a log in page. At the moment I am just trying to get my test to fire an onclick from a button and check that that a function has been called.
The log in component code can be seen below.
import React, { Component, Fragment } from "react";
import { Redirect } from "react-router-dom";
// Resources
//import logo from "assets/img/white-logo.png";
//import "./Login.css";
// Material UI
import {
withStyles,
MuiThemeProvider,
createMuiTheme
} from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Person from "#material-ui/icons/Person";
import InputAdornment from "#material-ui/core/InputAdornment";
// Custom Components
import Loading from "components/Loading/Loading.jsx";
// bootstrat 1.0
import { Alert, Row } from "react-bootstrap";
// MUI Icons
import LockOpen from "#material-ui/icons/LockOpen";
// remove
import axios from "axios";
// API
import api2 from "../../helpers/api2";
const styles = theme => ({
icon: {
color: "#fff"
},
cssUnderline: {
color: "#fff",
borderBottom: "#fff",
borderBottomColor: "#fff",
"&:after": {
borderBottomColor: "#fff",
borderBottom: "#fff"
},
"&:before": {
borderBottomColor: "#fff",
borderBottom: "#fff"
}
}
});
const theme = createMuiTheme({
palette: {
primary: { main: "#fff" }
}
});
class Login extends Component {
constructor(props, context) {
super(props, context);
this.state = {
username: "",
password: "",
isAuthenticated: false,
error: false,
toggle: true,
// loading
loading: false
};
}
openLoading = () => {
this.setState({ loading: true });
};
stopLoading = () => {
this.setState({ loading: false });
};
toggleMode = () => {
this.setState({ toggle: !this.state.toggle });
};
handleReset = e => {
const { username } = this.state;
this.openLoading();
api2
.post("auth/admin/forgotPassword", { email: username })
.then(resp => {
this.stopLoading();
console.log(resp);
})
.catch(error => {
this.stopLoading();
console.error(error);
});
};
handleSubmit = event => {
event.preventDefault();
localStorage.clear();
const cred = {
username: this.state.username,
password: this.state.password
};
api2
.post("auth/admin", cred)
.then(resp => {
console.log(resp);
localStorage.setItem("api_key", resp.data.api_key);
localStorage.setItem("username", cred.username);
return this.setState({ isAuthenticated: true });
})
.catch(error => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log("Error", error.message);
}
console.log(error.config);
return this.setState({ error: true });
});
};
handleInputChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
};
forgotPassword = () => {
console.log("object");
};
render() {
const { error } = this.state;
const { isAuthenticated } = this.state;
const { classes } = this.props;
if (isAuthenticated) {
return <Redirect to="/home/dashboard" />;
}
return (
<div className="login-page">
<video autoPlay muted loop id="myVideo">
<source
src=""
type="video/mp4"
/>
</video>
<div className="videoOver" />
<div className="midl">
<Row className="d-flex justify-content-center">
<img src={''} className="Login-logo" alt="logo" />
</Row>
<br />
<Row className="d-flex justify-content-center">
{error && (
<Alert style={{ color: "#fff" }}>
The username/password entered is incorrect. Try again!
</Alert>
)}
</Row>
<MuiThemeProvider theme={theme}>
<Row className="d-flex justify-content-center">
<TextField
id="input-username"
name="username"
type="text"
label="username"
value={this.state.username}
onChange={this.handleInputChange}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<Person className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
{this.state.toggle ? (
<Fragment>
<br />
<Row className="d-flex justify-content-center">
<TextField
id="input-password"
name="password"
type="password"
label="pasword"
value={this.state.password}
onChange={this.handleInputChange}
className={classes.cssUnderline}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<LockOpen className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
</Fragment>
) : (
""
)}
</MuiThemeProvider>
<br />
<Row className="d-flex justify-content-center">
{this.state.toggle ? (
<Button
className="button login-button"
data-testid='submit'
type="submit"
variant="contained"
color="primary"
onClick={this.handleSubmit}
name = "logIn"
>
Login
</Button>
) : (
<Button
className="button login-button"
type="submit"
variant="contained"
color="primary"
onClick={this.handleReset}
>
Reset
</Button>
)}
</Row>
<Row className="d-flex justify-content-center">
<p onClick={this.toggleMode} className="text-link">
{this.state.toggle ? "Forgot password?" : "Login"}
</p>
</Row>
</div>
<Loading open={this.state.loading} onClose={this.handleClose} />
</div>
);
}
}
export default withStyles(styles)(Login);
My current test which fails.
import React from 'react'
import {render, fireEvent, getByTestId} from 'react-testing-library'
import Login from './login'
describe('<MyComponent />', () => {
it('Function should be called once', () => {
const functionCalled = jest.fn()
const {getByTestId} = render(<Login handleSubmit={functionCalled} />)
const button = getByTestId('submit');
fireEvent.click(button);
expect(functionCalled).toHaveBeenCalledTimes(1)
});
});
I'm also fairly new to React Testing Library, but it looks like your button is using this.handleSubmit, so passing your mock function as a prop won't do anything. If you were to pass handleSubmit in from some container, then I believe your tests, as you currently have them, would pass.
I am using npm Validator and trying to validate the input info and then change the state of the submit address button depending on the validation. The way I have it set up now I can type and the text shows in the console but not in the UI. I'm not sure what I have wrong. The button will not change to 'enabled' either.
Here is the component:
import React, {Component} from 'react';
import { CardElement, injectStripe } from 'react-stripe-elements';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { clearCart } from '../actions/clearCartAction';
import getTotal from '../helpers/getTotalHelper';
import { Container, Col, Form, FormGroup, Input, Button } from 'reactstrap';
import './StripeCheckoutForm.css';
import validator from 'validator';
const cardElement = {
base: {
color: '#32325d',
width: '50%',
lineHeight: '30px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '18px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
const FIREBASE_FUNCTION = 'https://us-central1-velo-velo.cloudfunctions.net/charge/';
// Function used by all three methods to send the charge data to your Firebase function
async function charge(token, amount, currency) {
const res = await fetch(FIREBASE_FUNCTION, {
method: 'POST',
body: JSON.stringify({
token,
charge: {
amount,
currency,
},
}),
});
const data = await res.json();
data.body = JSON.parse(data.body);
return data;
}
class CheckoutForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
state = {
paymentComplete: false,
firstName: '',
lastName: '',
address: '',
city: '',
prefecture: '',
zipCode: '',
email: '',
submitDisabled: true
}
inputChangeHandler = (event, name) => {
console.log('inputChange', name, event.target.value)
event.preventDefault()
this.setState({
name: event.target.value
}, console.log(this.state.submitDisabled), function(){ this.canSubmit() })
}
canSubmit = () => {
console.log("canSubmit")
const { firstName, lastName, address, city, prefecture,zipCode, email} = this.state
if (validator.isAlpha(firstName)
&& validator.isAlpha(lastName)
&& address > 0
&& validator.isAlpha(city)
&& validator.isAlpha(prefecture)
&& zipCode > 0
&& validator.isEmail(email)) {
this.setState({submitDisabled: false})
} else {
this.setState({submitDisabled: true})
}
}
clearCartHandler = () => {
console.log('clearCartHandler');
this.props.onClearCart()
}
// User clicked submit
async submit(ev) {
console.log("clicked!")
const {token} = await this.props.stripe.createToken({name: "Name"});
const total = getTotal(this.props.cartItems);
const amount = total; // TODO: replace with form data
const currency = 'USD';
const response = await charge(token, amount, currency);
if (response.statusCode === 200) {
this.setState({paymentComplete: true});
console.log('200!!',response);
this.clearCartHandler();
} else {
alert("wrong credit information")
console.error("error: ", response);
}
}
render() {
if (this.state.complete) {
return (
<div>
<h1 className="purchase-complete">Purchase Complete</h1>
<Link to='/'>
<button>Continue Shopping</button>
</Link>
</div>
);
}
return (
<div className="checkout-wrapper">
<Container className="App">
<h2 className='text-center'>Let's Checkout</h2>
<Form className="form">
<Col>
<FormGroup>
<Input
onChange= {this.inputChangeHandler}
type="first name"
name={"first name"}
value={this.state.firstName}
id="exampleEmail"
placeholder="first name"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
onChange= {this.inputChangeHandler}
type="last name"
name="last name"
value={this.state.lastName}
id="exampleEmail"
placeholder="last name"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
onChange= {this.inputChangeHandler}
type="address"
name="address"
value={this.state.adress}
id="exampleEmail"
placeholder="address"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
onChange= {this.inputChangeHandler}
type="city"
name="city"
value={this.state.city}
id="exampleEmail"
placeholder="city"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
onChange= {this.inputChangeHandler}
type="prefecture"
name="prefecture"
value={this.state.prefecture}
id="exampleEmail"
placeholder="prefecture"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
onChange= {this.inputChangeHandler}
type="zipcode"
name="zipcode"
value={this.state.zipCode}
id="exampleEmail"
placeholder="zipcode"
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Input
onChange= {this.inputChangeHandler}
type="email"
name="email"
value={this.state.email}
id="exampleEmail"
placeholder="myemail#email.com"
/>
</FormGroup>
</Col>
<Button className="save-address-button" disabled={this.state.submitDisabled}>Submit Address</Button>
<div className="card-element">
<CardElement style={cardElement}/>
</div>
</Form>
<button className="checkout-button" disabled={false} onClick={this.submit}>Submit</button>
</Container>
</div>
);
}
}
const mapStateToProps = state => {
return {
cartItems: state.shoppingCart.cartItems
}
}
const mapDispatchToProps = (dispatch) => {
return {
onClearCart: () => dispatch(clearCart())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectStripe(CheckoutForm));
I think the bug is related to the misuse of the callback function of this.setState.
In your inputChangeHandler method, you passed three arguments to this.setState(), however, this.setState() expects you to pass only two arguments at most, with the second argument being the callback function.
Since you pass your callback function as the third argument, this.setState() will silently ignores it, so your validation method is not being called.
Another issue is related to the name parameter for the inputChangeHandler, the inputChangeHandler will only receive the event object, not the name. In order to access the name attribute of the input element, you will need to access it through event.target.name
To solve the issues, you can change the inputChangeHandler to
inputChangeHandler = event => {
const { name, value } = event.target;
console.log('inputChange', event.target.name, event.target.value)
// notice the square bracket, this means we use the computed variable as
// the object key, instead of using the string 'name' as object key
// also the name attribute in your Input element must match your state's key
// use name='lastName' instead of name='last name' as your state's key is lastName
this.setState({
[name]: value
}, this.canSubmit) // changing this line
}
If you want to keep your console.log(this.state.submitDisabled), you can add it inside your validation method.
canSubmit = () => {
console.log("canSubmit", this.state.submitDisabled) // add it here
const { firstName, lastName, address, city, prefecture, zipCode, email} = this.state
if (validator.isAlpha(firstName)
&& validator.isAlpha(lastName)
&& address > 0
&& validator.isAlpha(city)
&& validator.isAlpha(prefecture)
&& zipCode > 0
&& validator.isEmail(email)) {
this.setState({submitDisabled: false})
} else {
this.setState({submitDisabled: true})
}
}
In the render method, I think you mean
if (this.state.paymentComplete)
instead of
if (this.state.complete)
Also, the button at the bottom, you hard-coded the disabled attribute value
<button
className="checkout-button"
disabled={false}
onClick={this.submit}
>
Submit
</button>
maybe you mean
<button
className="checkout-button"
disabled={this.state.submitDisabled}
onClick={this.submit}
>
Submit
</button>
As a side note, the type attribute for the input element should be 'text', 'number', 'checkbox', etc... Instead of some random text like 'last name' or 'prefecture'