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.
Related
I am trying to make a drop down "select" field disabled under certain conditions in my app.
I have successfully done this with buttons already using a prop (disableBtn) that i pass up from the root of my app to the button component I made.
I try to do the EXACT same thing on a select component and it refuses to pass the prop (disableOption) back into the child component, even though its passing back many other binded props that work fine and build the options out in the drop down correctly.
I am logging the values on screen right now and can see they are updating in the main app component, but its not passing that back to the child for some reason.
Where am I off here? My understanding is you store the values you want to change in data() in the app.vue, then create a prop for them in the child component and bind them in the HTML. This has been working fine in all my other use cases.
app.vue
<template>
<div class="container">
<img alt="logo" src="./assets/logo.png">
<Header title="Provider Manager" :custTZ="custTZ" :shippingState="shippingState" :patientID="patientID" :custName="custFullName" :custEmail="custEmail" :custPhone="custPhone" />
<div v-if="providerVisibility" class="providerInfo">
<Button #btn-click="providerPicked" id="first-avail" text="First Available" />
<br />
<Select #dd-select="providerPickedSelect" :disabled="disableOption" :selectName="selectName" :id="id" :eligibleProviders="eligibleProviders" :labelFor="labelFor" :labelText="labelText" />
{{ disableOption }}
</div>
<div v-if="providerSelected" >
<hr>
<br />
<h2>Provider: {{ chosenProvider }} </h2>
<br />
</div>
<div v-if="providerSelected" >
<BookingSlots #selectSlot="removeUnselected" :slots="slots" />
<br />
<Button #btn-click="bookMeeting" text="Confirm Request" />
</div>
</div>
</template>
<script>
import { ZOHO } from "./assets/ZohoEmbededAppSDK.min.js";
import Header from './components/Header'
import Button from './components/Button'
import BookingSlots from './components/BookingSlots'
import Select from './components/Select'
const axios = require('axios');
export default {
name: 'App',
components: {
Header,
Button,
BookingSlots,
Select
},
data() {
return{
slots: [],
providerVisibility: true,
providerSelected: false,
currentProvider: 'None Assigned',
chosenProvider: '',
custFullName: '',
custEmail: '',
custPhone: '',
shippingState: '',
patientID: '',
custTZ: '',
providerZID: '',
eligibleProviders: [],
disableBtn: false,
disableOption: true,
}
},
methods: {
removeUnselected(id) {
console.log('id', id)
this.slots = this.slots.filter((slot) => slot.id === id)
},
providerPicked(id) {
console.log("id" + id)
console.log("currentProvider",this.currentProvider)
//Toggle front end visibility
this.providerSelected = true;
this.providerVisibility = false;
if(id === "first-avail"){
// hit booking engine, get name of first available
console.log("FIRST AVAIL")
this.chosenProvider = "Need to Hit Booking App";
}
if(id === "current-provider"){
// hit zoho values and get contact assigned provider
console.log("CURRENT PROVIDER")
this.chosenProvider = this.currentProvider;
}
},
providerPickedSelect(id, selectValue) {
if(this.id === "provider-select"){
// Get values from our DB for the provider selected
console.log("Provider-Select")
this.providerSelected = true;
this.providerVisibility = false;
this.chosenProvider = selectValue;
}
},
bookMeeting() {
//Book the meeting
console.log("book meeting called")
}
},
created() {
//Hit zoho and get customer info back
ZOHO.embeddedApp.on("PageLoad",(data) =>
{
console.log(data);
//Custom Business logic goes here
let entity = data.Entity;
let recordID = data.EntityId[0];
ZOHO.CRM.API.getRecord({Entity:entity,RecordID:recordID})
.then((data) => {
console.log(data.data[0])
// Set values scraped from CRM Contact profile
if(data.data[0].provider !== null && data.data[0].provider !== "None Assigned" ){
this.currentProvider = data.data[0].provider.name;
this.providerZID = data.data[0].provider.id;
}else{
//need to disable button if no doc assigned
this.disableBtn = true;
}
this.custEmail = data.data[0].Email;
this.custFullName = data.data[0].Full_Name;
this.custPhone = data.data[0].Phone;
this.patientID = data.data[0].Patient_ID;
this.shippingState = data.data[0].Mailing_State;
this.custTZ = data.data[0].GTM;
// getEligibleProviders(this.shippingState);
var data = JSON.stringify({
"state":this.shippingState,
});
axios(config)
.then((res) => {
console.log(res.data)
//this.eligibleProviders = res.data;
if(this.eligibleProviders && !this.eligibleProviders.length){
console.log("empty array")
this.eligibleProviders = [{
first_name: "None Avilable in Svc. State",
last_name: ""
}
];
this.disableOption = true;
}else{
console.log("full array")
}
console.log(this.eligibleProviders)
})
.catch((e) => {
console.log(e);
});
});
});
ZOHO.embeddedApp.init();
this.slots = [
{
id: 1,
text: 'Time Slot 1',
providerFname: 'James',
providerLname: "Appleton"
},
{
id: 2,
text: 'Time Slot 2',
providerFname: 'James',
providerLname: "Johnson"
}
];
this.selectName = "provider-select";
this.id = "provider-select";
this.labelFor = "provider-select";
this.labelText = "Choose a Provider: ";
}
}
</script>
select.vue
<template>
<br />
<label :for="labelFor">{{ labelText }} {{ disableOption }}</label>
<select v-on:change="onSelect($event, id)" class="select" :name="selectName" :id="id" :disabled="disableOption" >
<option :value="'none'" selected disabled hidden >Select One</option>
<option :key="provider.id" v-for="provider in eligibleProviders" :value="provider.first_name + ' ' + provider.last_name" >{{ provider.first_name +" "+ provider.last_name }}</option>
</select>
<br /><br />
</template>
<script>
export default {
name: 'Select',
props: {
selectValue: String,
selectName: String,
id: String,
labelFor: String,
labelText: String,
eligibleProviders: Array,
disableOption: Boolean,
},
methods: {
onSelect($event, id) {
console.log($event.target.value)
this.$emit('dd-select', id, $event.target.value);
}
},
emits: ['dd-select']
}
</script>
button.vue
<template>
<button #click="onClick(id)" class="btn" :id="id" :disabled="disableBtn" >{{ text }}</button>
</template>
<script>
export default {
name: 'Button',
props: {
text: String,
id: String,
disableBtn: Boolean,
},
methods: {
onClick(id) {
this.$emit('btn-click', id);
}
}
}
</script>
in select.vue, the props says it wants "disableOption", but you're passing disabled="disableOption"
so you can try updating app.vue with:
<Select
#dd-select="providerPickedSelect"
:disable-option="disableOption"
:select-name="selectName"
:id="id"
:eligible-providers="eligibleProviders"
:label-for="labelFor"
:label-text="labelText"
/>
I am using MERN stack and Redux. I have created an array in the state 'comments' which is updated via the clickHandler function with elements from the global state (accessed via props). When i try to show the contents of the array in the render i just get the length of it. How would i show the properties of the elements for example title.
import React, { Component } from "react";
import PropTypes from "prop-types";
import GoogleSearch from "./GoogleSearch";
import { connect } from "react-redux";
import { fetchSubjects } from "../../actions/subject";
import { fetchComments } from "../../actions/comment";
import store from "../../store";
class Subject extends Component {
// on loading the subjects and comments
// are fetched from the database
componentDidMount() {
this.props.fetchSubjects();
this.props.fetchComments();
}
constructor(props) {
super(props);
this.state = {
// set inital state for subjects description
// and summary to invisible
viewDesription: -1,
viewSummary: -1,
comments: [],
};
}
componentWillReceiveProps(nextProps) {
// new subject and comments are added to the top
if (nextProps.newPost) {
this.props.subjects.unshift(nextProps.newPost);
}
if (nextProps.newPost) {
this.props.comments.unshift(nextProps.newPost);
}
}
clickHandler = (id) => {
// when a subject title is clicked pass in its id
// and make the desciption visible
const { viewDescription } = this.state;
this.setState({ viewDescription: viewDescription === id ? -1 : id });
// clear the existing comments in state
this.setState({
comments: [],
});
// loop through the comment items in the global state
// and add any with the same subjects id passed in to the array
var i;
for (i = 0; i < this.props.comments.length; i++) {
if (this.props.comments[i].subject == id) {
console.log(this.props.comments[i]);
this.setState({
comments: this.state.comments.unshift(this.props.comments[i]),
});
}
} // set local storage to the id for the subject that has been clicked
localStorage.setItem("passedSubject", id);
};
// hovering on and off subjects toggles the visibility of the summary
hoverHandler = (id) => {
this.setState({ viewSummary: id });
};
hoverOffHandler = () => {
this.setState({ viewSummary: -1 });
};
render() {
const subjectItems = this.props.subjects.map((subject) => {
// if the state equals the id set to visible if not set to invisible
var view = this.state.viewDescription === subject._id ? "" : "none";
var hover = this.state.viewSummary === subject._id ? "" : "none";
var comments = this.state.comments;
return (
<div key={subject._id}>
<div
className="subjectTitle"
onClick={() => this.clickHandler(subject._id)}
onMouseEnter={() => this.hoverHandler(subject._id)}
onMouseLeave={() => this.hoverOffHandler()}
>
<p className="title">{subject.title}</p>
<p className="rating">Rating: {subject.rating}</p>
<p className="summary" style={{ display: hover }}>
{subject.summary}
</p>
</div>
<div className="subjectBody " style={{ display: view }}>
<div className="subjectAuthor">
<p className="author">
Subject created by: {subject.author} on {subject.date}
</p>
<a href="">
<div className="buttonRateSubject">RATE SUBJECT</div>
</a>
</div>
<div className="subjectDescription">
<p className="description">{subject.description}</p>
</div>
<div className="subjectLinks">Links:</div>
<div className="subjectComments">
<p>Comments:</p>
{/* ************HERE*********** */}
<p>{comments}</p>
{/* ********************************* */}
<a href="/addcomment">
<div className="buttonAddComment">ADD COMMENT</div>
</a>
</div>
</div>
</div>
);
});
return (
<div id="Subject">
<GoogleSearch />
{subjectItems}
</div>
);
}
}
Subject.propTypes = {
fetchSubjects: PropTypes.func.isRequired,
fetchComments: PropTypes.func.isRequired,
subjects: PropTypes.array.isRequired,
comments: PropTypes.array.isRequired,
newPost: PropTypes.object,
};
const mapStateToProps = (state) => ({
subjects: state.subjects.items,
newSubject: state.subjects.item,
comments: state.comments.items,
newComment: state.comments.item,
});
// export default Subject;
export default connect(mapStateToProps, { fetchSubjects, fetchComments })(
Subject,
Comment
);
I think I know your problem. You want to render items of an array.
Let me just give you a short overview.
Javascript:
this.setState({
comments: data
});
render (){
return (
<div>
{ this.state.comments.map(c=> <div>{c.body}</div> ) }
</div>
)
}
Thanks guys, i changed the for loop in the clickHandler to this which now has data rendering, it didn't like objects in the array for some reason.
var temp = [];
for (i = 0; i < this.props.comments.length; i++) {
if (this.props.comments[i].subject == id) {
console.log(this.props.comments[i]);
temp.unshift(this.props.comments[i].comment);
temp.unshift(this.props.comments[i].title);
}
}
this.setState({
comments: temp,
});
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.
I have a form that should update my applications state as a user types in the input field and in the textarea. The input and textarea call their event handlers through onChange={event handler}. For some reason when I type in either field the event handlers don't get called at all. The input and textarea fields seem to work fine outside of a form though.
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import LogoutHeader from './LogoutHeader';
import { fetchTodo, updateTodo, deleteTodo } from '../actions/index';
import { Link } from 'react-router';
class ShowTodo extends Component {
static contextTypes = {
router: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
descriptionChanged: false,
newDescription: '',
newTitle: '',
done: false,
id: 0
};
this.handleDescriptionChange = this.handleDescriptionChange.bind(this);
this.handleDeleteClick = this.handleDeleteClick.bind(this);
this.changeButtons = this.changeButtons.bind(this);
this.handleSaveClick = this.handleSaveClick.bind(this);
this.handleUndoClick = this.handleUndoClick.bind(this);
this.handleTitleChange = this.handleTitleChange.bind(this);
this.handleDoneChange = this.handleDoneChange.bind(this);
}
componentWillMount() {
this.props.fetchTodo(this.props.params.id).then(() => {
this.setState({
newDescription: this.props.todo.description,
newTitle: this.props.todo.title,
done: this.props.todo.completed,
id: this.props.todo.id
});
});
}
render() {
const { todo } = this.props;
const { fields: {title, description}, handleSubmit } = this.props;
console.log("Fields: description: ", this.props.fields.description.value); //These values change as expected
console.log("Fields: title: ", this.props.fields.title.value);
if (!todo) {
return (
<h3>Loading...</h3>
);
}
return (
<div id="showTodo">
<Link id="btnBack" className="btn btn-custom" role="button" to="/todos_index"><span className="glyphicon glyphicon-arrow-left"></span></Link>
<LogoutHeader></LogoutHeader>
<div className="row">
<div className="col-md-6 col-md-offset-3">
<form onSubmit={handleSubmit(this.handleSaveClick)}>
<h3>Edit Todo</h3>
<div className={`form-group ${title.touched && title.invalid ? 'has-danger' : ''}`}>
<label>Title</label>
<input
type="text"
className="form-control"
value={this.state.newTitle}
onChange={this.handleTitleChange}
{...title} />
</div>
<div className="text-help">
{description.touched ? description.error : ''}
</div>
<div className={`form-group ${description.touched && description.invalid ? 'has-danger' : ''}`}>
<label>Description</label>
<textarea
className="form-control"
value={this.state.newDescription}
onChange={this.handleDescriptionChange}
{...description} >
</textarea>
</div>
<div className="text-help">
{description.touched ? description.error : ''}
</div>
<span className="input-group-btn">
{this.changeButtons()}
<button id="editTodoDelete" className="btn btn-custom" onClick={this.handleDeleteClick}><span className="glyphicon glyphicon-trash"></span></button>
</span>
</form>
</div>
</div>
</div>
);
}
changeButtons() { //This does not get called when there are changed in the input or textareas.
if (!this.state.descriptionChanged) {
return null;
} else {
return [
<button
type="submit"
id="editTodoSave"
className="btn btn-custom"
><span className="glyphicon glyphicon-floppy-save"></span></button>,
<button
id="editTodoRefresh"
className="btn btn-custom"
onClick={this.handleUndoClick}
><span className="glyphicon glyphicon-refresh"></span></button>
];
}
}
handleDescriptionChange(event) { //This does not get called when there is a change in the textarea
this.setState({
descriptionChanged: true,
newDescription: this.props.fields.description.value
});
}
handleTitleChange(event) { //This does not get called when there is a changed in the input field.
this.setState({
descriptionChanged: true,
newTitle: this.props.fields.title.value
});
}
handleDoneChange() {
this.setState({
done: !this.state.done
});
var props = {
completed: this.state.done
};
this.props.updateTodo(this.state.id, JSON.stringify(props));
}
handleDeleteClick() {
this.props.deleteTodo(this.state.id).then(() => {
this.context.router.push('/todos_index');
});
}
handleSaveClick(props) {
this.props.updateTodo(this.state.id, JSON.stringify(props)).then(() => {
alert("Todo updates should have been recieved in database");
this.context.router.push('/todos_index');
});
}
handleUndoClick() {
this.setState({
descriptionChanged: false,
newTitle: this.props.todo.title,
newDescription: this.props.todo.description,
errors: {
title: '',
description: ''
}
});
}
}
function validate(values) {
const errors = {};
if (!values.title) {
errors.title = 'Please enter a title';
}
if (values.title) {
if (values.title.length > 25){
errors.title = 'You exceeded 25 characters';
}
}
if (!values.description) {
errors.description = 'Please enter your description';
}
if (values.description) {
if (values.description.length > 500) {
errors.description = "You exceeded 500 characters";
}
}
return errors;
}
function mapStateToProps(state) {
return { todo: state.todos.todo };
}
export default reduxForm({
form: 'ShowTodoForm',
fields: ['title', 'description'],
validate //These configurations will be added to the application state, so reduxForm is very similar to the connect function.
//connect: first argument is mapStateToProps, second is mapDispatchToProps
//reduxForm: 1st is form configuration, 2nd is mapStateToProps, 3rd is mapDispatchToProps
}, mapStateToProps, { fetchTodo, updateTodo, deleteTodo })(ShowTodo);
Using the example of initializingFromState within Redux-Form, I am trying to set this up dynamically. This is to edit a particular book in a list of books, and is using a simple api set up in express.js.
The full container is below. I somehow need to pass in initialValues, within the mapStateToProps function. In the example, it is done via a static object, but I can't work out how to use the information I have pulled in via fetchBook, and pass it to initialValues.
Container:
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { fetchBook, editBook } from '../actions/index';
class BookEdit extends Component {
componentWillMount() {
this.props.fetchBook(this.props.params.id);
}
static contextTypes = {
router: PropTypes.object
}
onSubmit(props) {
this.props.editBook(this.props.book.id, props)
.then(() => {
this.context.router.push('/');
});
}
const data = {
title: {this.props.book.title},
description: {this.props.author}
}
render() {
const { fields: { title, author }, handleSubmit } = this.props;
const { book } = this.props;
if (!book) {
return (
<div>
<p>Loading...</p>
</div>
)
}
return (
<div>
<Link to="/">Back</Link>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<h2>Add a new book</h2>
<label>Title</label>
<input type="text" {...title} />
<div className="text-help">{title.touched ? title.error : ''}</div>
<label>Author</label>
<input type="text" {...author} />
<div className="text-help">{author.touched ? author.error : ''}</div>
<button type="submit">Add</button>
<Link to="/" className="button">Go back</Link>
</form>
</div>
);
}
}
function mapStateToProps(state) {
return {
book: state.books.book,
initialValues: // how do I pass in the books here?
};
}
export default reduxForm({
form: 'EditBookForm',
fields: ['title', 'author']
}, mapStateToProps, { fetchBook, editBook })(BookEdit);
Thank you.
Your form values aren't what's in state.books.book? I think this is all you're looking for:
function mapStateToProps(state) {
return {
book: state.books.book,
initialValues: state.books.book
};
}
Since you're only really looking at this.props.book to know if it's loaded or not, it might be more explicit to do something like:
function mapStateToProps(state) {
return {
loaded: !!state.books.book,
initialValues: state.books.book
};
}
Hope that helps.
In related to above question, Erik. I have following form and not sure why it is not Validating on submit. It loads the data into fields but when I hit submit the validation fails.
Form_Bayan.js
import React, {Component, PropTypes} from "react";
import {browserHistory} from "react-router";
import {reduxForm, Field} from "redux-form";
import {MyCustomInput, MySimpleInput, MyCustomSelect} from "./__form_field_components";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
import {
ADMIN_FETCH_AUTOSUGGESTS_Lbl,
adminFetchAutoSuggestCats_act,
ADMIN_GENERATESLUG_Lbl,
adminGenerateSlug_act,
ADMIN_GETCATID_BYNAME_Lbl,
adminGetCatIdByName_act,
ADMIN_ADDNEWBAYAAN_Lbl,
adminAddNewBayaan_act,
adminFetchArticlesByCat_act,
adminUpdateBayaan_act
} from "../../actions/adminActionCreators";
import _ from "lodash";
class NewBayanForm extends Component {
constructor(props) {
super(props); // this component inherits "toggleViewFunction" function through props for redirection
this.generateSlug = this.generateSlug.bind(this);
this.state = {
submitButtonMeta: {
btnTitle: "Save",
btnClass: "btn btn-default",
btnIcon: null,
disabled: false
},
globalMessage: { // set when an action is performed by ActionCreation+Reducer and a message is returned
message: "",
className: ""
},
tempData: {
//the_bayaansMainCat_id : 1, // '1' refers to the 'Bayaans' parent category in admin , this ID is used here for different sort of lookups i.e. fetch available subcats for autosuggest, fetch cat ID by name under parent catID
the_bayaansMainCat_id: this.props.associatedMainCatId, // being passed from parent component to avoide redundent declaration
the_autoSuggestCatList: [],
slug: "",
the_catId: null
}
};
}
resetMessageState() {
var noMsg = {message: "", className: ""};
this.setState({globalMessage: noMsg});
}
componentDidMount() {
console.log("<NewBayanForm> (componentDidMount)");
this.props.adminFetchAutoSuggestCats_act(this.state.tempData.the_bayaansMainCat_id);
}
doSubmit(props) {
//console.log("----- submitting form -----");
//console.log(props);
this.disableSubmitButton();
// prepare data for submit request
// item_title, item_slug, content, picture, attachment, media_path, reference, tag_keywords, author_name, cat_id, date_created
var newBayanObj = {
item_title: props.titleTxt,
item_slug: this.state.tempData.slug,
content: props.videoIdTxt,
picture: "",
attachment: "",
media_path: "https://www.youtube.com/watch?v=" + props.videoIdTxt,
reference: "",
tag_keywords: props.keywordsTxt,
author_name: props.authorTxt,
cat_id: this.state.tempData.the_catId
};
this.props.adminUpdateBayaan_act(newBayaanObj)
.then(() => {
console.log("%c <NewBayanForm> (doSubmit) Updated bayaan, refetching updated bayaans list...", "color:blue;font-weight:bold;");
this.props.adminFetchArticlesByCat_act(this.props.associatedMainCatId)
.then(() => {
console.log("%c <NewBayanForm> (doSubmit) Redirecting to Gallery after update...", "color:blue;font-weight:bold;");
this.props.toggleViewFunction(); // comming from Parent Class (bayaansPage)
});
});
}
disableSubmitButton() {
console.log("<NewBayanForm> (disableSubmitButton)");
// Ref: http://stackoverflow.com/questions/18933985/this-setstate-isnt-merging-states-as-i-would-expect
var newButtonState = {
btnTitle: "Please wait... ",
btnClass: "btn btn-disabled",
btnIcon: null,
disabled: true
};
this.setState({submitButtonMeta: newButtonState});
this.resetMessageState(); // Need to reset message state when retrying for form submit after 1st failure
}
enableSubmitButton() {
console.log("<NewBayanForm> (enableSubmitButton)");
// Ref: http://stackoverflow.com/questions/18933985/this-setstate-isnt-merging-states-as-i-would-expect
var newButtonState = {btnTitle: "Save", btnClass: "btn btn-default", btnIcon: null, disabled: false};
this.setState({submitButtonMeta: newButtonState});
}
fetchCategoryId(value) {
console.log('<NewBayanForm> (fetchCategoryId) input-Value:', value); // make API call to fetch / generate category ID for this post
this.props.adminGetCatIdByName_act(value, this.state.tempData.the_bayaansMainCat_id); // '1': refers to look up under 'Bayaans' parent category for the specified category name
}
// will always receive and triggers when there are 'new props' and not old/same props
componentWillReceiveProps(nextProps) { // required when props are passed/changed from parent source. And we want to do some operation as props are changed (Ref: http://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form)
console.log("<NewBayanForm> (componentWillReceiveProps) nextProps: ", nextProps); // OK
//console.log("this.props : ", this.props); // OK
//console.log("nextProps.siteEssentials.actionsResult : ", nextProps.siteEssentials.actionsResult); // OK
if (nextProps.hasOwnProperty("siteEssentials")) { // if action status appeared as Done!
if (nextProps.siteEssentials.hasOwnProperty("actionsResult")) { // if action status appeared as Done!
if (nextProps.siteEssentials.actionsResult[ADMIN_GETCATID_BYNAME_Lbl] !== "FAILED") {
var clonedState = this.state.tempData;
clonedState.the_catId = nextProps.siteEssentials.actionsResult[ADMIN_GETCATID_BYNAME_Lbl];
//var newTempState = {slug: this.state.tempData.slug, the_catId: nextProps.siteEssentials.actionsResult[ADMIN_GETCATID_BYNAME_Lbl] };
this.setState({tempData: clonedState});
}
if (nextProps.siteEssentials.actionsResult[ADMIN_FETCH_AUTOSUGGESTS_Lbl] !== "FAILED") {
var clonedState = this.state.tempData;
clonedState.the_autoSuggestCatList = nextProps.siteEssentials.actionsResult[ADMIN_FETCH_AUTOSUGGESTS_Lbl];
this.setState({tempData: clonedState});
}
console.log("<NewBayanForm> (componentWillReceiveProps) new-State:", this.state);
}
}
}
render() { // rendering Edit form
const {handleSubmit} = this.props;
console.log('<NewBayanForm> (render_editForm) this.props:', this.props);
return (
<div className="adminForm">
<form onSubmit={handleSubmit(this.doSubmit.bind(this))}>
<div className="col-sm-6">
<div className="row">
<div className="col-sm-5"><label>Title:</label></div>
<div className="col-sm-7"><Field name="titleTxt" component={MySimpleInput}
defaultValue={this.props.name} type="text"
placeholder="Enter Title"/></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Slug:</label></div>
<div className="col-sm-7">{this.state.tempData.slug || this.props.slug} <input
type="hidden" name="slugTxt" value={this.state.tempData.slug}/></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Select Category:</label></div>
<div className="col-sm-7"><Field name="catTxt" component={MyCustomSelect}
defaultValue={this.props.category_name} type="text"
placeholder="Select or Type a New"
selectableOptionsList={this.state.tempData.the_autoSuggestCatList}
onSelectionDone={ this.fetchCategoryId.bind(this) }/>
<input type="hidden" name="catIdTxt"
value={this.state.tempData.the_catId || this.props.category_id}/>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="row">
<div className="col-sm-5"><label>Youtube Video ID:</label></div>
<div className="col-sm-7"><Field name="videoIdTxt" component={MySimpleInput}
defaultValue={this.props.content} type="text"
placeholder="TsQs9aDKwrw"/></div>
<div className="col-sm-12 hint"><b>Hint: </b> https://www.youtube.com/watch?v=<span
className="highlight">TsQs9aDKwrw</span></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Author/Speaker:</label></div>
<div className="col-sm-7"><Field name="authorTxt" component={MySimpleInput}
defaultValue={this.props.author} type="text"/></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Tags/Keywords:</label></div>
<div className="col-sm-7"><Field name="keywordsTxt" component={MySimpleInput}
defaultValue={this.props.tag_keywords} type="text"/>
</div>
</div>
</div>
<div className="row">
<div className={this.state.globalMessage.className}>{this.state.globalMessage.message}</div>
</div>
<div className="buttonControls">
<a className="cancelBtn" onClick={this.props.toggleViewFunction}>Cancel</a>
<button className={this.state.submitButtonMeta.btnClass}
disabled={this.state.submitButtonMeta.disabled}>
{this.state.submitButtonMeta.btnTitle}</button>
</div>
</form>
</div>
);
}
}
function validate(values) { // Validate function being called on Blur
const errors = {};
if (!values.titleTxt)
errors.titleTxt = "Enter Title";
if (!values.catTxt)
errors.catTxt = "Select/Enter a Category";
if (!values.videoIdTxt)
errors.videoIdTxt = "Enter youtube video ID (follow the provided hint)";
if (!values.keywordsTxt)
errors.keywordsTxt = "Enter keywords (will help in search)";
return errors;
}
// ReduxForm decorator
const newBayanFormAdmin_reduxformObj = reduxForm({
form: "newBayanFormAdmin", // any unique name of our form
validate // totally equivelent to--> validate: validate
});
function mapStateToProps({siteEssentials}, ownProps) {
console.log("<NewBayanForm> (mapStateToProps) siteEssentials:", siteEssentials);
// 1st param is related to our Redux State, 2nd param relates to our own component props
var initialValues = {
titleTxt: ownProps.name,
slugTxt: ownProps.slug,
catTxt: ownProps.category_name,
catIdTxt: ownProps.category_id,
videoIdTxt: ownProps.content,
authorTxt: ownProps.author,
keywordsTxt: ownProps.tag_keywords
};
console.log("<NewBayanForm> (mapStateToProps) initialValues: ", initialValues);
return ({siteEssentials}, initialValues);
};
function mapDispatchToProps(dispatch) {
return bindActionCreators({
adminFetchAutoSuggestCats_act,
adminGenerateSlug_act,
adminGetCatIdByName_act,
adminAddNewBayaan_act,
adminFetchArticlesByCat_act
}, dispatch);
};
NewBayanForm = connect(mapStateToProps, mapDispatchToProps) (newBayanFormAdmin_reduxformObj(NewBayanForm));
export default NewBayanForm;