ReactJS simple form wizard - javascript

For learning purpose I am writing a small app with a wizard in ReactJS.
I have created some components for the wizard as follow;
ProgressBar
Wizard
WizardStepOne
WizardStepOneForm
WizardStepTwo
WizardStepTwoForm
In the wizard component I have included my PorgressBar compnent for showing progression and created a switch statement to determine the oneClick value of the button included in each 'StepForm' to get a value and showing the next 'WizardStepTwoForm' component.
This all works well and does exactly what I expect but I am facing one problem. I don't want the user is able to get the next 'WizardStepTwoForm' form before I have validated the 'WizardStepOneForm'. So some how I should return a status to my parent component to determine if the user can click to the next state. Or, I disable the button by a state till a validation has been done but in this case the user won't be able to click on the button for an validation of the form.
During the submission of the form I want to send the data to the API, the dispatch is working but I am just wondering how to work it out right so my switch statement in the parent(Wizard) will only be fired if the form is valid.
Wizard
//.. imports
class Wizard extends React.Component {
constructor(props) {
super(props);
this.state = {
step : 'stepOne',
progression : '0%'
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
componentDidMount() {
const wizard = JSON.parse(localStorage.getItem('wizard'));
if (wizard !== null) {
this.setState({
step: wizard.step,
progression: wizard.progression
});
}
}
componentDidUpdate() {
localStorage.setItem('wizard', JSON.stringify({
step : this.state.step,
progression : this.state.progression
}));
}
handleFormSubmit(e) {
switch (e.target.value) {
case 'stepOne' :
this.setState({
step : 'stepOne',
progression : '0%',
});
break;
case 'stepTwo' :
this.setState({
step : 'stepTwo',
progression : '50%'
});
break;
}
}
/**
*
* Render
* #return {JSX}
*/
render() {
const { step, progression } = this.state;
switch (step) {
case 'stepOne' :
return (
<div>
<Header />
<WizardProgressBar progression={progression} stepOne="active" stepTwo="" />
<WizardStepOne handleFormSubmit={this.handleFormSubmit} />
<Footer/>
</div>
);
break;
case 'stepTwo' :
return (
<div>
<Header />
<WizardProgressBar progression={progression} stepOne="done" stepTwo="active" />
<WizardStepTwo handleFormSubmit={this.handleFormSubmit} />
<Footer/>
</div>
);
break;
}
}
}
export default Wizard;
WizardStepOne
export default class WizardStepOne extends React.Component {
constructor(props) {
super(props);
}
/**
*
* Render
* #return {XML}
*/
render() {
return(
<div className="step-one">
<h1>Step 1</h1>
<WizardStepOneForm handleFormSubmit={this.props.handleFormSubmit} />
</div>
);
}
}
WizardStepForm
//... imports
#connect((store) => {
return {
};
})
export default class WizardStepOneForm extends React.Component {
constructor(props) {
super(props);
this.state = {
formData : {
firstName : '',
lastName : '',
},
formErrors : {
firstName : true,
lastName : true,
},
formErrorMessages : {
firstName : 'some validation message',
lastName : 'some validation message',
},
formButtonEnabled : true,
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleFirstNameChange = this.handleFirstNameChange.bind(this);
this.handleLastNameChange = this.handleLastNameChange.bind(this);
}
componentDidMount() {
const stepOne = JSON.parse(localStorage.getItem('stepOne'));
if (stepOne !== null) {
this.setState({
formData : stepOne.formData,
formErrors : stepOne.formErrors,
formErrorMessages : stepOne.formErrorMessages,
});
}
}
handleFirstNameChange(e) {
let formData = this.state.formData;
let formErrors = this.state.formErrors;
let formErrorMessages = this.state.formErrorMessages;
formData.firstName = e.target.value;
if (!e.target.value) {
formErrors.firstName = true;
} else {
formErrors.firstName = false;
}
this.setState({ formData : formData, formErrors : formErrors, formErrorMessages : formErrorMessages });
localStorage.setItem('stepOne', JSON.stringify({ formData : formData, formErrors : formErrors, formErrorMessages : formErrorMessages }));
}
handleLastNameChange(e) {
let formData = this.state.formData;
let formErrors = this.state.formErrors;
let formErrorMessages = this.state.formErrorMessages;
formData.lastName = e.target.value;
if (!e.target.value) {
formErrors.lastName = true;
} else {
formErrors.lastName = false;
}
this.setState({ formData : formData, formErrors : formErrors, formErrorMessages : formErrorMessages });
localStorage.setItem('stepOne', JSON.stringify({ formData : formData, formErrors : formErrors, formErrorMessages : formErrorMessages }));
}
handleSubmitButton() {
}
handleSubmit(e) {
e.preventDefault();
this.props.dispatch(addUser(this.state.formData));
}
/**
*
* Render
* #return {XML}
*/
render() {
const firstNameError = this.state.formErrors.firstName ? 'error' : '';
const lastNameError = this.state.formErrors.lastName ? 'error' : '';
return(
<form className="step-one-form">
<div className="form-group right">
<div className="form-group__form-row">
<p className={classnames('col-2', firstNameError)}>
<label htmlFor="first_name">First name:</label>
<input type="text" id="firstName" name="fist_name" autoComplete="off" onChange={this.handleFirstNameChange} value={this.state.formData.firstName} />
{ firstNameError ? <FormElementErrorMessage message={this.state.formErrorMessages.firstName} /> : '' }
</p>
<p className={classnames('col-2', lastNameError)}>
<label htmlFor="last_name">Last name:</label>
<input type="text" id="lastName" name="last_name" autoComplete="off" onChange={this.handleLastNameChange} value={this.state.formData.lastName} />
{ lastNameError ? <FormElementErrorMessage message={this.state.formErrorMessages.lastName} /> : '' }
</p>
</div>
</div>
<button disabled={this.state.formButtonEnabled} onClick={this.props.handleFormSubmit} value="stepTwo">Next step</button>
</form>
);
}
}

So, I found out it's probably more easy as I thought.
The form submit handler comes in the child component where the form is created. So we don't need to push it downwards from the parent as a prop.
As we will fire an action via the dispatcher when the form 'stepOne' is valid, we change the given state by the reducer. The store receives this state change, which has been appended via connect and the provider at the root of our app, the parent component will receive this state change and we can fire up the next wizard screen.

Related

React Input validation if empty or number

I've created this method that gets the state of the calculator input and checks if its empty or not. I need help with two things:
What's the cleanest way to add here a validation to check if each input is also a number and outputs and error "Input must be a number"
Currently I have one error message that fires whether all the inputs are present or not, where what I want is for it to validate each input separately and fire an error under each one. How do I do it but still keep this function concise?
constructor(props) {
super(props);
this.state = {
price: 0,
downP: 0,
term: 0,
interest: 0,
error: ''
};
}
handleValidation = () => {
const {
price,
downP,
loan,
interest,
} = this.state;
let error = '';
let formIsValid = true;
if(!price ||
!downP ||
!loan ||
!interest){
formIsValid = false;
error = "Input fields cannot be empty";
}
this.setState({error: error});
return formIsValid;
}
And then this is the error message
<span style={{color: "red"}}>{this.state.error}</span>
If you want to keep your error messages separate I would recommend to reorganize your state.
So scalable solution (you may add more controls by just adding them to state) may look like:
class NumberControlsWithErrorMessage extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: [
{ name: 'price', value: 0, error: ''},
{ name: 'downP', value: 0, error: '' },
{ name: 'term', value: 0, error: '' },
{ name: 'interest', value: 0, error: '' }
]
};
}
handleInputChange = (idx, event) => {
const target = event.target;
const name = target.name;
let error = '';
if (isNaN(target.value)) {
error = `${name} field can only be number`
}
if (!target.value) {
error = `${name} field cannot be empty`
}
this.state.inputs[idx] = {
...this.state.inputs[idx],
value: target.value,
error
}
this.setState({
inputs: [...this.state.inputs]
});
}
render() {
return (
<form>
{this.state.inputs.map((input, idx) => (
<div>
<label htmlFor="">{input.name}</label>
<input type="text" value={input.value} onChange={(e) => this.handleInputChange(idx, e)}/>
{input.error && <span>{input.error}</span> }
</div>
))}
</form>
);
}
}
Working example
Also if you are building a complex form, you may want to try some React solution for forms, where all the mechanism for listening to events, state updates, validatoin are already handled for you. Like reactive-mobx-form
A straightforward way of handling multiple objects needing validation is to store an errors object in your state that has a property for each input field. Then you conditionally render the error underneath each input field depending on whether or not it has an error. Here is a very basic example:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {
price: 0, downP: 0, term: 0, interest: 0,
errors: { price: '', downP: '', term: '', interest: '' }
};
}
handleValidation = () => {
const { price, downP, loan, interest } = this.state;
let errors = { price: '', downP: '', term: '', interest: '' };
if (!price) {
errors.price = 'Price is required';
} else if (isNaN(price)) {
errors.price = 'Price must be a number';
}
if (!downP) {
errors.downP = 'Down Payment is required';
}
// Rest of validation conditions go here...
this.setState({ errors });
}
render() {
const { errors } = this.state;
return (
<form>
<input name="price" value={this.state.price} onChange={this.handleChange} />
{errors.price != '' && <span style={{color: "red"}}>{this.state.errors.price}</span>}
<input name="downP" value={this.state.downP} onChange={this.handleChange} />
{errors.downP != '' && <span style={{color: "red"}}>{this.state.errors.downP}</span>}
{/** Rest of components go here */}
</form>
);
}
}
You can choose whether or not to run validation once the form submits or on every keypress and that will affect how when those messages appear and disappear, but this should give you an idea on how you would manage error messages specific to each input field.
You can do this:
handleValidation() {
const { price, downP,loan, interest} = this.state;
// only each block with generate error
if (!price || isNaN(price)) {
this.setState({ error: 'price is not valid' });
} else if (!downP || isNaN(downP)) {
this.setState({ error: 'downP is not valid' });
} else {
this.setState({error: ""})
// submit code here
}
}
Note: you dont need to return anything. It will update the error state and only submit the form if it goes into no error (else{}) part
and for render() part add this:
{(this.state.error !== '')
? <span style={{color: "red"}}>{this.state.error}</span>
: ''
}
If you want validation msg on each add errPrice, errDownP and so on to the state, and check for them in render like (this.state.errPrice!== '') {} and so on.
One solution assuming you want a one size fits all error message only checking if it was a number or not would be to put them into an array and set error if the input is not a number.
const inputs = [ price, downP, loan, interest ]
inputs.map(input => {
if (!input || isNaN(input)){
error = "Input must be a number"
formIsValid = false
}
}
this.setState({error})
Something like that maybe.

The most "react" way to pass state to other components

I am fairly new to React. Currently I have two React components - Article.js and ControlForm.js
My render return in Article.js is this:
return (
<div className="article">
{article_wrapper.map( article =>
<div key={article.node.nid} className="article-main-display">
<h1 className="title" dangerouslySetInnerHTML={createMarkup(article.node.title)}/>
<div className="img-div"><img src={article.node.field_image.src} /></div>
<ControlForm />
<div dangerouslySetInnerHTML={createMarkup(article.node.field_user_hsk_level)} />;
<div className="field-name-field-chinese">
<div dangerouslySetInnerHTML={createMarkup(article.node.chinese)} />;
</div>
</div>
)}
</div>
);
The ControlForm.js has several form elements (all of which I'd like to be able to pass along if need be), but this is the main one:
<div className="form-item form-type-select form-group">
<label className="control-label">Font Size</label>
<select
value={this.state.value}
onChange={this.handleSizeSelect}
id="font-size"
className="form-control form-select"
>
<option value="0">Small</option>
<option value="1">Normal</option>
<option value="2">Large</option>
<option value="3">XL</option>
</select>
</div>
I'd like to be able to set a class on one of the divs in the Article.js based on changing a value in the ControlForm.js
What is the most "React" way to do this? Would creating a common parent to both be the best method (right now, their only parent in common is the main App.js)
Sorry if I don't totally understand how this is supposed to work - this is my first React app.
The class associated with the components are ControlForm and withFetching respectively.
EDIT - the demo example below works, but I have some additional issues with how to integrate it properly into my existing code. Here's the existing functions of ControlForm:
class ControlForm extends Component {
constructor() {
super();
this.state = { toggleActive: false, sizeSelect: "0", speed: 1.3, volume: .6};
this.onToggle = this.onToggle.bind(this);
this.handleSpeedChange = this.handleSpeedChange.bind(this);
this.handleVolumeChange = this.handleVolumeChange.bind(this);
this.handleSizeSelect = this.handleSizeSelect.bind(this);
}
onToggle() {
this.setState({ toggleActive: !this.state.toggleActive });
}
handleSizeSelect(event) {
this.setState({ sizeSelect: event.target.value });
this.setState({font: 'large-font'});
parentMethod(event.target.value);
}
handlePlayClick(e) {
e.preventDefault();
voice.playButtonClick();
}
handlePauseClick(e) {
e.preventDefault();
voice.pauseButtonClick();
}
handleStopClick(e) {
e.preventDefault();
voice.stopButtonClick();
}
handleVolumeChange(event) {
console.log(event.target.value);
this.setState({ volume: event.target.value });
}
handleSpeedChange(event) {
console.log(event.target.value);
this.setState({ speed: event.target.value });
}
Articles looks like this:
const withFetching = (url) => (Comp) =>
class WithFetching extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
error: null,
dynamicClassName: "parentClass"
};
this.changeClassName = this.changeClassName.bind(this);
}
changeClassName(childData) {
this.setState({
dynamicClassName: childData
});
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...');
}
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
//return "test";
return <Comp { ...this.props } { ...this.state } />
}
}
function createMarkup(html) {
return {__html: html};
}
function changeClassName(childData) {
console.log("GETS HERE!")
this.setState({
dynamicClassName: childData
});
}
const Articles = ({ data, isLoading, error }) => {
console.log(data);
console.log(isLoading);
const article_wrapper = data.nodes || [];
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return <p>Loading ...</p>;
}
return (
<div className="article">
{article_wrapper.map( article =>
<div key={article.node.nid} className="article-main-display">
<h1 className="title" dangerouslySetInnerHTML={createMarkup(article.node.title)}/>
<div className="img-div"><img src={article.node.field_image.src} /></div>
<ControlForm parentMethod={changeClassName} />
<div dangerouslySetInnerHTML={createMarkup(article.node.field_user_hsk_level)} />;
<div className="field-name-field-chinese">
<div dangerouslySetInnerHTML={createMarkup(article.node.chinese)} />;
</div>
</div>
)}
</div>
);
}
export default withFetching(API)(Articles);
Sorry about all of these questions, I know a lot of this is due to unfamiliarity with React - this is the first thing I've tried to build in React
You want to change parents from his childs.
First, you have to create a handler function at Article.js and pass it to ControlForm.js as a property.
<ControlForm changeDiv={this.changeDiv} />
Then you focus on ControlForm.js, whenever you want to happen, you just execute the function you passed as a the prop changeDiv, like this.props.changeDiv()
See also possible duplicate: How to update parent's state in React?
you can conditionally render a class based on state and your handler was missing the values from the event on the onChange
here's a demo of dynamically changing style base on the state
demo
Article.js ,
class Article extends React.Component {
constructor(props) {
super(props);
this.state = {
dynamicClassName: "parentClass"
}
this.changeClassName = this.changeClassName.bind(this);
}
changeClassName(childData) {
this.setState({
dynamicClassName: childData
});
}
// user dynamicClassName wherever u want .
return ( <
div className = "article" > {
article_wrapper.map(article =>
<
div key = {
article.node.nid
}
className = "article-main-display" >
<
h1 className = "title"
dangerouslySetInnerHTML = {
createMarkup(article.node.title)
}
/> <
div className = "img-div" > < img src = {
article.node.field_image.src
}
/></div >
<
ControlForm parentMethod={this.changeClassName} / >
<
div dangerouslySetInnerHTML = {
createMarkup(article.node.field_user_hsk_level)
}
/>; <
div className = "field-name-field-chinese" >
<
div dangerouslySetInnerHTML = {
createMarkup(article.node.chinese)
}
/>; < /
div > <
/div>
)
} <
/div>
);
}
In ControlForm js ,
class ControlForm extends React.Component {
constructor(props) {
super(props);
this.state = {
}
this.handleSizeSelect= this.handleSizeSelect.bind(this);
}
handleSizeSelect() {
this.props.parentMethod(this.state.value);
}
render() {
return (
<div className="form-item form-type-select form-group">
<label className="control-label">Font Size</label>
<select
value={this.state.value}
onChange={this.handleSizeSelect}
id="font-size"
className="form-control form-select"
>
<option value="0">Small</option>
<option value="1">Normal</option>
<option value="2">Large</option>
<option value="3">XL</option>
</select>
</div>
);
}
}

ReactJS application passing state to multiple pages

I am developing a financial application in ReactJS. The application will be making use of a custom web API to retrieve data from a a MS SQL database. The initial landing page of the application requires the user to log in using his/her user ID and password. Once the user has logged in successfully, the menu options are provided to the user (ie: review balance, transfer between accounts, pay bills, etc).
In my design, each menu function will be a separate component in its own src/screens member. My directory structure looks like this:
The app launches and executes the src/screens/login.js file and allows the user to log in. Once the user logs in, the state contains the user ID and password, a user ID and a 4 digit reference code.
If I were to create a src/screens/account_balance.js page, how would I get the state from src/screens/index.js into the state of src/screens/account_balance.js? At the same time, when I create my src/screens/transfer_balance.js page, how would I get the state from src/screens/index.js into the state of src/screens/transfer_balance.js?
This is what my current login.js code looks like:
import React from 'react';
import '../styles/app.css';
//think of react components as functions
class login extends React.Component {
constructor(props) {
super(props);
this.state = {
passData: {
passFamilyID: '',
passPlanID: '',
passMemberID: '',
passPIN: ''
},
login: {
errorMessage: '',
errorCSS: 'visibility:hidden',
buttonText: 'Log In'
},
userID: {
valid: true,
value: '',
label: 'User ID',
length: 0,
css: 'input-group-text text-black input-size-200'
},
password: {
valid: true,
value: '',
label: 'Password',
length: 0,
css: 'input-group-text text-black input-size-200'
},
}
this.verifyLogin = this.verifyLogin.bind(this);
}
verifyLogin () {
let currentComponent = this;
var validData = true;
var mqResponse = '';
var localLogin = {
errorMessage: '',
errorCSS: 'visibility:hidden',
buttonText: 'Log In'
}
var localUserID = {
valid: true,
value: '',
label: 'User ID',
length: 0,
css: 'input-group-text text-black input-size-200'
}
var localPassword = {
valid: true,
value: '',
label: 'Password',
length: 0,
css: 'input-group-text text-black input-size-200'
}
localUserID.value = document.getElementById("txtUserID").value;
localPassword.value = document.getElementById("txtPassword").value;
if (localUserID.value.length < 3) {
validData = false;
localUserID.valid = false;
localUserID.css = "input-group-text text-danger input-size-200";
localLogin.errorMessage = "Invaid User ID length";
localLogin.errorCSS = "text-danger";
}
else {
localUserID.css = 'input-group-text text-black input-size-200';
}
if (localPassword.value.length >= 5) {
localPassword.css = 'input-group-text text-black input-size-200';
} else {
validData = false;
localPassword.valid = false;
localPassword.css = "input-group-text text-danger input-size-200";
localLogin.errorCSS = "text-danger";
if (localLogin.errorMessage == '') {
localLogin.errorMessage = "Invalid Password length";
} else {
localLogin.errorMessage = "Invalid User ID and Password length";
}
}
if (validData == false) {
currentComponent.setState({ userID:localUserID, password:localPassword, login: localLogin });
} else {
localLogin.buttonText = "Please wait.....";
currentComponent.setState({ userID:localUserID, password:localPassword, login: localLogin });
var templocalUserID = document.getElementById("txtUserID").value + " ";
var templocalPassword = document.getElementById("txtPassword").value + " ";
var mqUserID= templocalUserID.substring(0,40);
var mqPassword = templocalPassword.substring(0,10);
var MQMessage = "6007sP0001????DEMI0000000000 INTERNET/093000050" + mqUserID + mqPassword;
var mqResponse = "";
var mqErrorCode = 0;
MQMessage = encodeURI(MQMessage);
let url = "http://localhost:12976/api/Homepage?mqRequest=" + MQMessage;
const options = { method: 'GET' };
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
mqResponse = myJson[0].return_response;
mqErrorCode = myJson[0].return_code;
if (mqErrorCode > 0) {
localLogin.errorMessage = "We are having a problem connecting to our service. Please try again at a later time.";
localLogin.css = "input-group-text text-danger input-size-200";
localLogin.errorCSS = "text-danger";
currentComponent.setState({ userID:localUserID, password:localPassword, login:localLogin })
} else {
var mqResponseCode = mqResponse.substr(0,3);
if (mqResponseCode > 0) {
localLogin.errorMessage = "Incorrect UserID/Password.";
localLogin.css = "input-group-text text-danger input-size-200";
localLogin.errorCSS = "text-danger";
currentComponent.setState({ userID:localUserID, password:localPassword, login:localLogin })
} else {
var localData = {
familyID: ' ',
planID: mqResponse.substr(27,4),
memberID: mqResponse.substr(13,10),
PIN: mqResponse.substr(23,4)
}
currentComponent.setState({ passData:localData })
location.href = "/account_balance";
}
}
}
});
}
}
render() {
return (
<div className="App">
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<a className="navbar-brand" href="#">Access your 401(k)</a>
</nav>
<div className="d-flex flex-wrap justify-content-center align-items-center align-content-center">
<div className="container">
<div className="row">
<div>
<form>
<p className="h4 text-center py-4">Please Log In</p>
<div className="input-group mb-3">
<div className="input-group-prepend">
<span className={this.state.userID.css}>{this.state.userID.label}</span>
</div>
<input id="txtUserID" type="text" className="form-control" />
</div>
<div className="input-group mb-3">
<div className="input-group-prepend">
<span className={this.state.password.css}>{this.state.password.label}</span>
</div>
<input id="txtPassword" type="current-password" className="form-control" />
</div>
<div className="text-center py-4 mt-3">
<button type="button" className="btn btn-primary" onClick={() => {
this.verifyLogin();
}}>{this.state.login.buttonText}</button>
</div>
<div className={this.state.login.errorCSS}>
<p className="h4 text-center">{this.state.login.errorMessage}</p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
<main details={this.state.passData} />
}
}
export default login
When I execute the application, I enter a user ID and Password. The data is retrieved and I pick up 4 pieces of data from the returned string. I then push those 4 pieces of data into the state. Then, I execute location.href='/account_balance'.
This redirects to the account_balance.js page. This is the code in my account_balance.js:
import React from 'react';
import '../styles/app.css';
//think of react components as functions
class account_balance extends React.Component {
constructor(props) {
super(props);
state = {
passData: {
passFamilyID: this.props.details.passFamilyID,
passPlanID: this.props.details.passPlanID,
passMemberID: this.props.details.passMemberID,
passPIN: this.props.details.passPIN
}
}
}
componentWillUpdate() {
console.log("passedData: ", state.passedData);
MQMessage = "6000" + "sP0000" + "????" + state.passData.passPlanID + state.passData.passMemberID + state.passData.FamilyID + "INTERNET/";
mqResponse = "";
mqErrorCode = 0;
MQMessage = encodeURI(MQMessage);
url = "http://localhost:12976/api/Homepage?mqRequest=" + MQMessage;
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
mqResponse = myJson[0].return_response;
mqErrorCode = myJson[0].return_code;
console.log("mqErrorCode: ", mqErrorCode);
console.log("mqResponse: ", mqResponse);
}
});
}
render() {
return (
<div className="App">
<div>
<label>output from account_balance.js</label>
</div>
<div>
<label>{this.state.passData.passFamilyID}</label>
</div>
<div>
<label>{this.state.passData.passPlanID}</label>
</div>
<div>
<label>{this.state.passData.passMemberID}</label>
</div>
<div>
<label>{this.state.passData.passPIN}</label>
</div>
</div>
)
}
}
export default account_balance
When the account_balance.js page loads, I see this in the console.log():
Uncaught TypeError: Cannot read property 'passFamilyID' of undefined
at new main (main.js:12)
When i click on the link to the line generating the error, this is what I see:
you can use props to share the state data from one component to another, or you can use redux as global state.
For example your index.js state looks like:
state = {
name: 'sameer',
}
render() {
return (
<Accountbal details={this.state.name} />
)
}
Now your Account bal component code looks like account_bal.js
constructor(props) {
super(props);
state = {
name: this.props.details, // here you can access the name from index.js
}
}
render() {
return (
)
}
I followed the suggestion by Mohamed Sameer and incorporated the code. After trial and error, and additional research on the web, I found out a command. I removed my location.href which I was using to redirect to the account_balance.js and added a state variable Boolean to let me know if I want to redirect on the render.
In the render, I placed this code:
if (this.state.passData.redirect === true) {
return (
<Redirect to={{
pathname: "/account_balance",
state: {
familyID: this.state.passData.passFamilyID,
planID: this.state.passData.passPlanID,
memberID: this.state.passData.memberID,
PIN: this.state.passData.PIN
}
}}/>
)
}
This passes the state variables from the current page to the account_balance page.
In the account_balance.js, I put in this line of code:
console.log("props: ", this.props.location.state);
Just to test the functionality. When the account_balance,js loads, I looked at the console.log() and I saw the values I passed.

SetState causing App crash in react.js

I have recently started working on react.js, while creating the login page I have used setstate method to set the value of userEmail to text box.
I have created a method which checks the validity of email address and I am calling it every time when user enters a new letter.
handleChangeInEmail(event) {
var value = event.target.value;
console.log("change in email value" + value);
if(validateEmailAddress(value) == true) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
This method and userEmailForLogin state is passed in render method as
<EmailLoginPage
userEmailForLogin = {this.state.userEmailForLogin}
onHandleChangeInEmail= {this.handleChangeInEmail}
/>
I am using the method to validate the email address and the method is
validateEmailAddress : function(emailForLogin) {
if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(emailForLogin)) {
return true;
}
return false;
},
I am using this method and state in render of EmailLoginPage as <input type="text" name="" placeholder="Enter Email" className="email-input-txt" onChange={props.onHandleChangeInEmail} value = {props.userEmailForLogin}/>
This is working fine in normal case , but when I try to input a large email addess say yjgykgkykhhkuhkjhgkghjkhgkjhghjkghjghghkghbghbg#gmail.com, it crashes
IMO the frequent change in state is causing this but I couldn't understand what should be done to get rid of this.
I think issue is with the regex only, i tried with other and it's working properly.
Instead of writing the if/else inside change function simply you are write it like this:
change(event) {
var value = event.target.value;
this.setState({
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
});
}
Copied the regex from this answer: How to validate email address in JavaScript?
Check the working solution:
class App extends React.Component {
constructor(){
super();
this.state = {
value: '',
showInvalidEmailError: false
}
this.change = this.change.bind(this);
}
change(event) {
var value = event.target.value;
this.setState(function() {
return {
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
}
});
}
validateEmailAddress(emailForLogin) {
var regex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(regex.test(emailForLogin)){
return true;
}
return false;
}
render() {
return(
<div>
<input value={this.state.value} onChange={this.change}/>
<br/>
valid email: {this.state.showInvalidEmailError + ''}
</div>
);
}
}
ReactDOM.render(
<App/>,
document.getElementById("app")
);
<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='app'/>
You could use Lodash's debounce function so that the check function is not called unless the user stops typing for x amount of time (300ms in my scenario below).
_this.debounceCheck = debounce((value) => {
if(validateEmailAddress(value)) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
}, 300)
handleChangeInEmail(event) {
_this.debounce(event.target.value)
}
A solution using debounce. This way multiple setState can be reduced.
DEMO: https://jsfiddle.net/vedp/kp04015o/6/
class Email extends React.Component {
constructor (props) {
super(props)
this.state = { email: "" }
}
handleChange = debounce((e) => {
this.setState({ email: e.target.value })
}, 1000)
render() {
return (
<div className="widget">
<p>{this.state.email}</p>
<input onChange={this.handleChange} />
</div>
)
}
}
React.render(<Email/>, document.getElementById('container'));
function debounce(callback, wait, context = this) {
let timeout = null
let callbackArgs = null
const later = () => callback.apply(context, callbackArgs)
return function() {
callbackArgs = arguments
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}

Go to previous state in reactjs

I'm building a component which proceeds according to the selections of the users. I have completed it successfully but facing some issues when trying to implement a back button to go back.
My code is like follows.
class ReportMainCat extends Component {
constructor(props) {
super(props);
this.state = {
postType: null,
}
this.changeType = this.changeType.bind(this);
this.report_next = this.report_next.bind(this);
};
report_next() {
if (this.state.postType == null) {
return <ReportFirst changeType={this.changeType}/>
}
else if (this.state.postType === 'sexual') {
return <ReportXContent changeType={this.changeType}/>
} else if (this.state.postType === 'selfharm') {
return <ReportThreatContent changeType={this.changeType}/>
}
}
changeType = (postType) => {
this.setState({postType})
this.setState({
showMainReportCats: false,
})
}
render() {
return (
<div className="top_of_overlay">
<div className="section_container text_align_center padding_10px">
<a className="">Report Category</a>
{this.report_next()}
</div>
</div>
)
}
}
I'm binding the postType value as follows.
class ReportXContent extends Component {
constructor(props) {
super(props);
this.state = {
postType: '',
}
};
textType(postType) {
this.props.changeType(postType);
}
render() {
return (
<div className="text_align_left">
<div>
<div className="width_100 margin_bottom10px">
<input type="checkbox" ref="nudity" onClick={this.textType.bind(this,'nudity')}/>
<a>Nudity or Pornography</a>
</div>
<div className="width_100 margin_bottom10px">
<input type="checkbox" ref="minor" onClick={this.textType.bind(this,'minor')}/>
<a>Includes Minors</a>
</div>
</div>
<ReportButtons/>
</div>
)
}
}
My back button
<div>
<button className="float_right margin_left5px" onClick={this.props.back_report}>Back</button>
</div>
So basically what i'm trying to do is this.
Ex: If the user selects postType as sexual it will return the ReportXContent component. How can i return to the first page when the user clicks the back button.
Thanks.
You could implement the back button click handler like this in the ReportMainCat component:
handleBackClick() {
this.setState({ postType: null });
}
, and that would show the ReportFirst view again.
If you don't want the first view, but the last view, simply change your changeType implementation to save lastPostType to state like this:
changeType = (postType) => {
this.setState({
lastPostType: this.state.postType,
postType,
showMainReportCats: false,
});
}
Edit
If you want full history of changes - let's say if you want to implement a full back button history - you can simply rename lastPostType to postTypeHistory and implement it like a stack (like the browser history is):
changeType = (postType) => {
this.setState({
postTypeHistory: [...this.state.postTypeHistory, this.state.postType],
postType,
showMainReportCats: false,
});
}
handleBackClick() {
const { postTypeHistory } = this.state;
const postType = postTypeHistory.pop();
this.setState({
postType,
postTypeHistory,
});
}

Categories