I have an order form that currently takes a users input and posts it into a dynamo db table on submit.
Right now, that's all the "submit" button does.
I want the on click to instead calculate a price based off of the users input, hide the form, display the price with an "accept and schedule" button, that then posts to dynamo db.
-----------------I am very new to to react js so I apologize if my code is sloppy. -------------------
I figured that my function to handle the calculation would look something like the following, as the price is based off of the property square footage in increments of 500.
let base_price = 149.99;
if(sqft > 2000){
let overage = sqft - 2000;
let percentage = Math.ceil(overage % 500) * 10;
base_price += base_price * percentage;
}
Now here is my current order form code:
import React, { Component } from "react";
import { InputGroup, Row, Form, Col, FormGroup, FormControl, ControlLabel } from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import config from "../config";
import { API } from "aws-amplify";
export default class OrderForm extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: null,
streetAddress: "",
streetAddress2: "",
city: "",
state: "",
zipCode: "",
propertySqft: "",
packageSelected: this.props.location.state,
};
}
validateForm() {
return this.state.streetAddress.length > 0;
return this.state.streetAddress2.legnth >= 0;
return this.state.city.length > 0;
return this.state.state.length > 0;
return this.state.zipCode.length > 0;
return this.state.propertySqft.length > 0;
return this.state.packageSelected.length > 0;
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value,
});
}
handleSubmit = async event => {
event.preventDefault();
this.setState({ isLoading: true });
try {
await this.createJob({
streetAddress: this.state.streetAddress,
streetAddress2: this.state.streetAddress2,
city: this.state.city,
state: this.state.state,
zipCode: this.state.zipCode,
propertySqft: this.state.propertySqft,
packageSelected: this.state.packageSelected,
});
this.props.history.push("/Scheduled");
} catch (e) {
alert(e);
this.setState({ isLoading: false });
}
}
createJob(job) {
return API.post("dynamodbname", "/prapp", {
body: job
});
}
render() {
var centerText = {textAlign: "center"}
return (
<div className="NewJob">
<Form onSubmit={this.handleSubmit}>
<Form.Group controlId="packageSelected">
<Form.Label>
</Form.Label>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text id="inputGroupPrepend">Package Selected:</InputGroup.Text>
</InputGroup.Prepend>{" "}
<Form.Control style={centerText} onChange={this.handleChange} plaintext readOnly defaultValue={this.props.location.state} />
</InputGroup>
</Form.Group>
<Form.Group controlId="streetAddress">
<Form.Label>Address 1</Form.Label>
<Form.Control onChange={this.handleChange} value={this.state.streetAddress} placeholder="Property Address" />
</Form.Group>
<Form.Group controlId="streetAddress2">
<Form.Label>Address 2</Form.Label>
<Form.Control onChange={this.handleChange} value={this.state.streetAddress2} placeholder="Apartment, studio, or floor" />
</Form.Group>
<Form.Row>
<Form.Group as={Col} controlId="city">
<Form.Label>City</Form.Label>
<Form.Control onChange={this.handleChange} value={this.state.city} placeholder="City" />
</Form.Group>
<Form.Group as={Col} controlId="state">
<Form.Label>State</Form.Label>
<Form.Control onChange={this.handleChange} value={this.state.state} as="select">
<option value="AL">State</option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
</Form.Control>
</Form.Group>
<Form.Group as={Col} controlId="zipCode">
<Form.Label>Zip</Form.Label>
<Form.Control onChange={this.handleChange} value={this.state.zipCode} placeholder="Zip Code" />
</Form.Group>
</Form.Row>
<Form.Group controlId="propertySqft">
<Form.Label>Square Feet</Form.Label>
<Form.Control onChange={this.handleChange} value={this.state.propertySqft} placeholder="1234" />
</Form.Group>
<Form.Group id="formGridCheckbox">
<Form.Check type="checkbox" label="I agree to terms and services" />
</Form.Group>
<LoaderButton
block
bsStyle="primary"
bsSize="large"
disabled={!this.validateForm()}
type="submit"
isLoading={this.state.isLoading}
text="Calculate Price"
loadingText="Calculating your priceā¦."
/>
</Form>
</div>
);
}
}
So how would I go about instead the on submit triggers a function that calculates a price based off of the users input, hide the form, display the price with an "accept and schedule" button, and then posts to dynamo db?
If anyone could share either some resources or insight on how I should go about executing this, that would be extremely appreciated!
Images of calculation debugger:
property sqft is 5000
Overage is 3000
Percentage is Zero?
when set to 5001 sqft, percentage is only 10?
In state, have a value called totalPrice, initialized at null, and add a function that get's met before the official onSubmit. It could look something like this.
checkPrice = () =>{
//YOUR PRICE EVAL BASED ON USER INPUT
this.setState({totalPrice: YOUR_PRICE_EVAL})
}
In your render, have a condition that says if totalPrice then show price and two buttons (continue, which will execute form submission, or back, which will perform setState({totalPrice: null}) And reveal the form again.
{this.state.totalPrice ? (
<div>
Your price is {this.state.totalPrice}
<button onClick={this.handleSubmit}>Continue?</button>
<button onClick={()=>{this.setState({totalPrice: null})}}>Back to Eval</button>
</div>
) : (
<Form onSubmit={this.checkPrice}>
...
</Form>
Here is a quick code sandbox. The code probably doesn't match up 100% with yours but it should be enough to point you in the right direction.
Alternatively you can check out Ant Design's Popconfirm component. Basically you wrap it around whatever is going to be clicked (i.e. the submit button) And before it submit's anything the component will render a popup. You can populate the popup with "Your price is..." and calculate the final price. Then pass the props onConfirm={YOUR_SUBMIT_FUNCTION} onCancel={()=>{this.setState({totalPrice: null})}}
Related
I have built a travel journal however I ran into two big bugs, one of them where the id which is set to Math.random()*10000 and is expected to change on submit however it does not, another issue I have is where once I remove one journal entry, I am not able to add any more entries via submit.
I have tried adding the math.random in different places however it doesn't change, I have run out of ideas on how to tackle this issue, if you have any suggestions ,any help is appreciated.
import React, { useState } from "react";
import Card from "./Card";
import data from "./data";
function Entry(props) {
const [entry, setEntry] = useState([
{
title: "",
location: "",
googleMapsUrl: "",
startDate: "",
endDate: "",
description: "",
imageUrl: "",
id: Math.random() * 100000000,
},
]);
function handleChange(e) {
setEntry((prevState) => {
return {
...prevState,
[e.target.name]: e.target.value,
};
});
}
// const newData = [...data];
function handleSubmit(e) {
e.preventDefault();
setEntry((prevState) => {
return {
...prevState,
};
});
data.unshift(entry);
}
return (
<div>
<form className="entry-form">
<h1 className="entry-title">Add another Travel Memory</h1>
<div className="journal-entry">
<input
className="entry-input"
type="text"
value={entry.location}
name="location"
placeholder="LOCATION"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="text"
name="title"
value={entry.title}
placeholder="LANDMARK"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="text"
name="googleMapsUrl"
value={entry.googleMapsUrl}
placeholder="GOOGLE MAPS LINK"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="date"
value={entry.startDate}
name="startDate"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="date"
value={entry.endDate}
name="endDate"
onChange={handleChange}
required
/>
<textarea
className="entry-input"
placeholder="ADD YOUR STORY OR A FUN FACT FROM YOUR JOURNEY"
name="description"
value={entry.description}
onChange={handleChange}
required
/>
<input
className="entry-input"
type="text"
name="imageUrl"
value={entry.imageUrl}
placeholder="ADD A IMAGE LINK TO REMIND YOU OF YOUR TRAVEL"
onChange={handleChange}
/>
<button type="submit" onClick={handleSubmit} className="entry-btn">
add your travel memory
</button>
</div>
</form>
<Card data={data} />
</div>
);
}
export default Entry;
Math.random()*10000 and is expected to change on submit however it does not
Because no code was written to change it. Take a look at the state update in the submit handler:
setEntry((prevState) => {
return {
...prevState,
};
});
No values are changed. The new state is an exact copy of the previous state. Contrast this with the state update in the change handler for the input fields:
setEntry((prevState) => {
return {
...prevState,
[e.target.name]: e.target.value,
};
});
Notice how the new state is constructed from the previous state, and a given field is updated.
If you want to update the id field in the submit handler, update the id field in the submit handler:
setEntry((prevState) => {
return {
...prevState,
id: Math.random() * 100000000
};
});
Hello I am new in ReactJS and I have to implement react-intl-tel-input for taking phone number from all over the world but while integration I was facing some issues. When I write this code:
<IntlTelInput
containerClassName="intl-tel-input"
inputClassName="form-control"
name="mobile"
placeholder="Enter Your Number"
input
type="tel"
value={this.state.phoneNumber}
onChange={this.handleChange}
I was not able to access this.handleChange but When I write my normal code like this
<input
type="tel"
id="phone"
name="mobile"
placeholder="Enter Your Number"
required
onChange={this.handleChange}
/>
I was able to access this.handleChange and my code work perfectly but I was unable to take country code. If anyone know the solution please help. I was getting this error
TypeError: Cannot read properties of null (reading 'phoneNumber')
This is my complete code.
Login.js
import React from 'react'
import firebase from './firebase'
import 'firebase/auth';
import "./App.css";
import { getDatabase, ref, child, get } from "firebase/database";
import IntlTelInput from 'react-intl-tel-input';
import 'react-intl-tel-input/dist/main.css';
class Login extends React.Component {
handleChange = (e) => {
console.log (e)
const { name, value } = e.target
this.setState({
[name]: value
})
console.log (value)
this.setState({ phoneNumber: value }, () => {
console.log(this.state.phoneNumber);
});
}
configureCaptcha = () =>{
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
'size': 'invisible',
'callback': (response) => {
// reCAPTCHA solved, allow signInWithPhoneNumber.
this.onSignInSubmit();
console.log("Recaptca varified")
},
// defaultCountry: "IN"
}
);
}
onSignInSubmit = (e) => {
e.preventDefault()
this.configureCaptcha()
const phoneNumber = this.state.mobile
console.log(phoneNumber)
const appVerifier = window.recaptchaVerifier;
const dbRef = ref(getDatabase());
get(child(dbRef, `Users/${phoneNumber}`)).then((snapshot) => {
if (snapshot.exists()) {
firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
.then((confirmationResult) => {
window.confirmationResult = confirmationResult;
alert('An OTP has been sent to your registered mobile number')
localStorage.setItem("Phone_No", phoneNumber)
console.log(localStorage.getItem('Phone_No'));
}).catch((error) => {
console.error(error);
alert("Oops! Some error occured. Please try again.")
});
}
else {
alert('Sorry, this mobile number is not registered with us. Please use your registered mobile number.');
}
})
}
onSubmitOTP = (e) => {
e.preventDefault()
const code = this.state.otp
console.log(code)
window.confirmationResult.confirm(code).then((result) => {
// User signed in successfully.
const Users = result.user;
console.log(JSON.stringify(Users))
this.props.history.push("/home");
}).catch((error) => {
alert("You have entered wrong code")
});
}
render() {
return (
<div className="Main-header">
<img src="./55k-logo.png" alt="Company Logo" style={{ height: "80px", width: "200px" }} />
<br />
<div>
<h2>Login Form</h2>
<p>Limtless Water. From Unlimited Air.</p>
<form onSubmit={this.onSignInSubmit}>
<div id="sign-in-button"></div>
{/* <PhoneInput */}
<label>Mobile Number</label> <br />
{/* for="phoneNumber" */}
<IntlTelInput
containerClassName="intl-tel-input"
inputClassName="form-control"
name="mobile" placeholder="Enter Your Number"
input type="tel" value={this.state.phoneNumber}
onChange={this.handleChange}
/>
{/* <input type="tel" id="phone" name="mobile" placeholder="Enter Your Number" required onChange={this.handleChange} /> */}
<div className="buttons">
<button type="submit">Submit</button>
</div>
</form>
</div>
<div>
<form onSubmit={this.onSubmitOTP}>
<label >Code</label> <br />
{/* for="code" */}
<input type="number" name="otp" placeholder="Enter The 6 Digit OTP" required onChange={this.handleChange} />
<div className="buttons" >
<button type="submit">Submit</button>
</div>
</form>
</div>
</div>
)
}
}
export default Login;
Issues
There is no defined initial state so this is why accessing this.state.phoneNumber is throwing an error.
The IntlTelInput component takes an onPhoneNumberChange handler that takes a validation status, current value, and country details as arguments instead of an onChange handler taking an onChange event object.
Solution
Provide valid initial state for the component. In React class components state is simply a class property, it just needs to be defined.
state = {};
Create a new change handler specifically for the IntlTelInput component.
handlePhoneChange = (status, phoneNumber, country) => {
this.setState({ phoneNumber });
};
Switch from onChange to onPhoneNumberChange event handler.
<IntlTelInput
containerClassName="intl-tel-input"
inputClassName="form-control"
name="mobile"
placeholder="Enter Your Number"
input
type="tel"
value={this.state.phoneNumber}
onPhoneNumberChange={this.handlePhoneChange}
/>
I am using the Context API to add user details from a form to a global state. When I submit the form, the state is always "one step behind" - essentially, a double click is required to get the desired result.
Basic recreation of the code (have removed irrelevant bits and some imports):
import { UserProvider, UserContext } from "../../StateContext"
export default function SignUp() {
const user = useContext(UserContext)
const history = useHistory()
const handleSubmit = (e) => {
e.preventDefault()
user.setName(userDetails.name)
//logging out the user object here will result in the previous values shown
}
const [userDetails, setUserDetails] = useState({
name: null,
age: null,
})
return (
<>
<form onSubmit={handleSubmit}>
<div className="form-vertical-batch">
<FormControl>
<Input
type="text"
placeholder="Your Name"
required={true}
onChange={(e) =>
setUserDetails({ ...userDetails, name: e.target.value })
}
></Input>
<FormHelperText>
Put the name you're best known by online - either a nickname,
brand name, or your real name.
</FormHelperText>
</FormControl>
<FormControl>
<TextField
type="number"
inputProps={{ min: 18, max: 99 }}
onChange={(e) =>
setUserDetails({ ...userDetails, age: e.target.value })
}
/>
<FormHelperText>
You currently must be at least 18 years old to use the platform.
</FormHelperText>
</FormControl>
</div>
</div>
<input
id="formButton"
className="btn sign-up-button"
type="submit"
placeholder="Send message"
/>
</form>
</>
)
}
To clarify the issue here - if I submit with a name as "Reikon" and log our the user object, the first time it will return as null, and then the second time it will return "Reikon" as expected.
I am building page where user can switch between login and signup mode by clicking the switch button.
Login form has 2 input fields and signup form has 3 input fields. My thinking was to build 2 separate forms independent from each other and use 2 separate custom hook instances.
import React, { useState } from "react";
import { useForm } from "../../shared/hooks/form-hook";
import Card from "../../shared/components/UIElements/Card";
import Input from "../../shared/components/FormElements/Input";
import Button from "../../shared/components/FormElements/Button";
import {
VALIDATOR_MINLENGTH,
VALIDATOR_EMAIL
} from "../../shared/util/validators";
import "./Auth.css";
const Auth = props => {
const [showLogin, setShowLogin] = useState(true);
const [formStateLogin, inputHandlerLogin] = useForm(
{
email: {
value: "",
isValid: false
},
password: {
value: "",
isValid: false
}
},
false
);
const [formStateSignup, inputHandlerSignup] = useForm(
{
name: {
value: "",
isValid: false
},
email: {
value: "",
isValid: false
},
password: {
value: "",
isValid: false
}
},
false
);
const loginSubmitHandler = event => {
event.preventDefault();
console.log("login handler");
};
const signupSubmitHandler = event => {
event.preventDefault();
console.log(formStateSignup.inputs);
};
const switchButtonHandler = () => {
setShowLogin(!showLogin);
};
return (
<Card className="authentication">
{showLogin ? (
<form onSubmit={loginSubmitHandler} className="place-form">
<h2>Enter your login details</h2>
<Input
id="email"
element="input"
type="email"
placeholder="Email address"
label="Email"
validators={[VALIDATOR_EMAIL(), VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerLogin}
errorText="Please enter valid email address"
/>
<Input
id="password"
element="input"
type="password"
placeholder="Password"
label="Password"
validators={[VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerLogin}
errorText="Please enter valid password (at least 5 chars)"
/>
<Button type="submit" disabled={!formStateLogin.isValid}>
LOGIN
</Button>
</form>
) : (
<form onSubmit={signupSubmitHandler} className="place-form">
<h2>Enter your signup details</h2>
<Input
id="name_s"
element="input"
type="text"
placeholder="Enter your name"
label="Name"
validators={[VALIDATOR_MINLENGTH(2)]}
onInput={inputHandlerSignup}
errorText="Please enter valid name at least 2 chars"
/>
<Input
id="email_s"
element="input"
type="email"
placeholder="Email address"
label="Email"
validators={[VALIDATOR_EMAIL(), VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerSignup}
errorText="Please enter valid email address"
/>
<Input
id="password_s"
element="input"
type="password"
placeholder="Password"
label="Password"
validators={[VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerSignup}
errorText="Please enter valid password (at least 5 chars)"
/>
<Button type="submit" disabled={!formStateSignup.isValid}>
LOGIN
</Button>
</form>
)}
<Button inverse onClick={switchButtonHandler}>
{showLogin ? "SWITCH TO SIGNUP" : "SWITCH TO LOGIN"}
</Button>
</Card>
);
};
export default Auth;
Both forms seem to render fine but the trouble is when I enter text in one form and decide to switch to other form, values from departed form are not lost but rather translated to new form:
Is this limitation of ReactJS, is it HTML? :) Or is it just my buggy code?
It's not a good convention to have two forms in one component, it makes a mess...I would make rather two separated components of LoginForm and SignUpForm and switch between them through ternary operator based on the state whatever way you like. Your forms and their state will be separated and code is more readable
It happens same for checkbox and radio buttons if you select radio button on first page then renders second page with radio button it will be automatically selected as dom operations are costly,
In your case react is just adding new third field and removing it you need to set the value attribute of fields to respective state.
you can actually. use the nested route to switch between components
import { Switch, Route } from 'react-router-dom';
<Switch>
<Route path='/register' >
// component 1
<Register />
</Route>
<Route path='/login' >
// component 2
<Login />
</Route>
</Switch>
In below image screenshot I make fields mandatory so click on register button If any fields then that empty field I want to highlight with red border in React how it is possible ?
(https://blueprintjs.com/docs/#core/components/text-inputs)
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
email: '',
password: '',
};
this.handleChange = this.handleChange.bind(this);
this.registerForm = this.registerForm.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
registerForm(){
if(this.state.firstName.trim() && this.state.lastName.trim() &&
this.state.email && this.state.password){
console.log("registration successfully..!!");
}else{
console.log("all * marked fields mandatory");
}
}
render() {
return (
<div>
<h2>Fill Registration Details..!!</h2>
<InputGroup placeholder="Enter First Name...*"
name="firstName" value={this.state.firstName} onChange={this.handleChange}/>
<InputGroup placeholder="Enter Last Name...*" name="lastName"
value={this.state.lastName} onChange={this.handleChange}/>
<InputGroup placeholder="Enter your email...*" name="email"
value={this.state.email} onChange={this.handleChange}/>
<InputGroup placeholder="Enter your password...*"name="password"
value={this.state.password} onChange={this.handleChange}/>
<Button intent="Primary" onClick={this.registerForm}>Register</Button>
</div>
)
}
One solution, as #Saraband stated, is to modify your node's class name depending on whether or not your input field contains an error:
<InputGroup
placeholder="Enter your password...*"
name="password"
className={this.state.password.length ? '' : 'error'}
value={this.state.password}
onChange={this.handleChange}
/>
You can then use it with the following CSS that will show a red border (for example) :
.error input
{
border-bottom: 1px solid #eb516d;
}
Another way is to use the native required attribute of the input tag, but this method is hard to customize :
<input type='text' required/>
https://www.w3schools.com/tags/att_input_required.asp
For those who might be looking for a solution to this question, the solution below will only validate once the submit button is clicked. You can add a custom css class to style the input tag.
import React, { useState } from 'react';
const ValidateInput = () => {
// set isSubmitting to false by default
// this will make sure error class is not added by default
const [isSubmitting, setIsSubmitting] = useState(false);
const [inputValue, setInputValue] = useState('');
const submitHandler = (event) => {
event.preventDefault();
// this will trigger the error validation
setIsSubmitting(true);
// add the rest of the logic here
};
return (
<form onSubmit={submitHandler}>
<input
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
className={isSubmitting && !inputValue ? 'error' : undefined}
/>
<button type="submit">Submit</button>
</form>
);
};
export default ValidateInput;
You can create a CSS class - let's say .red-border and add it to your input whenever their value is empty (your component need to be able to use this className prop and pass it down to your <input /> native component)
<InputGroup
placeholder="Enter your password...*"
name="password"
className={!this.state.password.length ? '' : 'red-border'}
value={this.state.password}
onChange={this.handleChange}
/>
Although it can be best to keep this sort of thing inside your InputGroup component thus confining the logic of your component to a single file
class InputGroup extends React.Component {
// code
render () {
return(
// code
<input
value={this.props.value}
className={!this.state.password.length ? '' : 'red-border'}
/>
);
}
};