Created
December 4, 2018 11:30
-
-
Save rproenza86/e0efa6ce393c3dbcaa84f1c6ac995f4e to your computer and use it in GitHub Desktop.
How to use ReactJS Material UI lib DatePicker comp on ReduxForm comp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import { Component } from 'react'; | |
import { Field, reduxForm } from 'redux-form'; | |
import { connect } from 'react-redux'; | |
import { assign } from 'lodash'; | |
import { DatePicker } from 'redux-form-material-ui'; | |
import * as testDriveSelectors from '../redux/testDriveSelectors'; | |
import { selectors } from '@makemydeal/dr-offer-redux'; | |
import { IStateTree } from '../../../common/types'; | |
// tslint:disable-next-line:import-name | |
import UIToolkit from '@makemydeal/dr-ui-toolkit'; | |
import history from './../../../history'; | |
import { TEST_DRIVE_CONFIRMATION } from '../redux/testDriveRoutes'; | |
const LoadingButtonUI = UIToolkit.LoadingButtonUI; | |
class TestDriveReduxForm extends Component<any, any> { | |
constructor(props) { | |
super(props); | |
this.state = { valid: false }; | |
} | |
componentDidMount() { | |
const historyEntries = [...history.entries]; | |
const previousRoute = historyEntries[historyEntries.length - 2].pathname; | |
if (TEST_DRIVE_CONFIRMATION !== previousRoute) { | |
this.setState({ valid: this.validateForm() || false }); | |
} | |
} | |
/** | |
* Validation function to disable previous days in the selection | |
* @param startDate: Date object | |
*/ | |
disablePrevDates(startDay: any) { | |
const day: any = new Date(new Date().setDate(new Date().getDate() - 1)); | |
const startSeconds = Date.parse(day); | |
return (startDate:any) => { | |
return Date.parse(startDay) < startSeconds; | |
}; | |
} | |
labelIconClick(refsName: string): void { | |
if (refsName === 'spTdfdatepicker') | |
(this[refsName] as any).getRenderedComponent().getRenderedComponent().openDialog(); | |
else | |
(this[refsName] as any).getRenderedComponent().focus(); | |
} | |
trimSpaces(value: string): string { | |
return !!value ? value.trim() : ''; | |
} | |
normalizePhone(value: any) { | |
if (!value) { | |
return value; | |
} | |
const onlyNums = value.replace(/[^\d]/g, ''); | |
if (onlyNums.length <= 3) { | |
return onlyNums; | |
} | |
if (onlyNums.length <= 7) { | |
return `(${onlyNums.slice(0, 3)}) ${onlyNums.slice(3)}`; | |
} | |
return `(${onlyNums.slice(0, 3)}) ${onlyNums.slice(3, 6)}-${onlyNums.slice(6, 10)}`; | |
} | |
updateRequiredElm(ref:string, isValid:boolean, elm:HTMLElement) { | |
if (isValid) { | |
(this.refs[ref] as any).style.display = 'none'; | |
elm.classList.remove('error-ring'); | |
} else { | |
(this.refs[ref] as any).style.display = 'block'; | |
elm.classList.add('error-ring'); | |
} | |
} | |
updateFocus(ref:string, focus:boolean) { | |
const elm = (document.getElementsByClassName(ref).item(0) as HTMLElement); | |
if (focus) { | |
elm.classList.add('focused'); | |
} else { | |
elm.classList.remove('focused'); | |
} | |
} | |
/**this | |
* TODO: Update any for corresponding types, in the case of HTMLElement create an interface | |
* extending form IReduxForm and HTMLElement. | |
*/ | |
validate(event:any = null) { | |
let valid = false; | |
if (event) { | |
const elm = (typeof event.target === 'object') ? | |
event.target : | |
(this['spTdfdatepicker'] as any).getRenderedComponent().getRenderedComponent(); | |
const cmpName = (typeof elm.getAttribute === 'function') ? elm.getAttribute('name') : elm.props.name; | |
let value = elm ? this.trimSpaces(elm.value) : ''; | |
const defaultValue = elm ? this.trimSpaces(elm.defaultValue) : ''; | |
valid = (defaultValue === value || value === '') ? false : true; | |
switch (cmpName) { | |
case 'day': | |
valid = (this.props.initialValues.day !== elm.props.value && elm.props.value === '') ? false : true; | |
this.updateRequiredElm('sp-tdf-lb-date', valid, document.getElementById('spTdfdatepicker')); | |
document.getElementById('spTdfdatepicker').focus(); | |
break; | |
case 'time': | |
this.updateRequiredElm('sp-tdf-lb-time', valid, elm); | |
break; | |
case 'firstName': | |
this.updateRequiredElm('sp-tdf-lb-firstName', valid, elm); | |
break; | |
case 'lastName': | |
this.updateRequiredElm('sp-tdf-lb-lastName', valid, elm); | |
break; | |
case 'email': | |
this.updateRequiredElm('sp-tdf-lb-email', valid, elm); | |
break; | |
case 'phone': | |
value = this.normalizePhone(value); | |
valid = (value !== '' && value.length === 14); | |
this.updateRequiredElm('sp-tdf-lb-phone', valid, elm); | |
break; | |
} | |
} | |
valid = this.validateForm(); | |
this.setState({ valid }); | |
} | |
validateForm() { | |
// tslint:disable-next-line:max-line-length | |
const day = (this['spTdfdatepicker'] as any) ? (this['spTdfdatepicker'] as any).getRenderedComponent().getRenderedComponent().props.value : ''; | |
let time = (this['spTdfTime'] as any) ? (this['spTdfTime'] as any).getRenderedComponent().value : ''; | |
let firstName = (this['spTdfFirstname'] as any) ? (this['spTdfFirstname'] as any).getRenderedComponent().value : ''; | |
let lastName = (this['spTdfLastname'] as any) ? (this['spTdfLastname'] as any).getRenderedComponent().value : ''; | |
let email = (this['spTdfEmail'] as any) ? (this['spTdfEmail'] as any).getRenderedComponent().value : ''; | |
let phone = (this['spTdfPhone'] as any) ? (this['spTdfPhone'] as any).getRenderedComponent().value : ''; | |
let isValid = false; | |
time = this.trimSpaces(time); | |
firstName = this.trimSpaces(firstName); | |
lastName = this.trimSpaces(lastName); | |
email = this.trimSpaces(email); | |
phone = this.trimSpaces(phone); | |
phone = this.normalizePhone(phone); | |
if (!!day && !!time && !!firstName && !!lastName && !!email && !!phone && phone.length === 14) { | |
isValid = true; | |
} | |
return isValid; | |
} | |
spTdfdatepicker: Field = null; | |
spTdfTime: Field = null; | |
spTdfFirstname: Field = null; | |
spTdfLastname: Field = null; | |
spTdfEmail: Field = null; | |
spTdfPhone: Field = null; | |
spTdfMessage: Field = null; | |
render() { | |
const { handleSubmit } = this.props; | |
const startDate = new Date(); | |
const times = ['Morning', 'Afternoon', 'Evening']; | |
const renderSelectElm = () => { | |
return times.map( | |
timeOption => { | |
if (this.props.initialValues.time === timeOption) { | |
return <option defaultValue={timeOption} | |
key={timeOption}> | |
{timeOption} | |
</option>; | |
} | |
return <option value={timeOption} key={timeOption}>{timeOption}</option>; | |
}, | |
this | |
); | |
}; | |
return ( | |
<form onSubmit={handleSubmit} className="test-drive-form" > | |
<div className="form-group has-float-label"> | |
<Field className="form-control date-picker" | |
ref={(input) => { this.spTdfdatepicker = input; }} | |
id="spTdfdatepicker" | |
withRef={true} name="day" | |
component={DatePicker} | |
shouldDisableDate={this.disablePrevDates(startDate)} | |
hintText="Please, select a day" | |
locale="en-US" | |
okLabel="Select" | |
onChange={event => this.validate(event)} | |
onFocus={() => this.updateFocus('date-picker', true)} | |
onBlur={() => this.updateFocus('date-picker', false)}> | |
</Field> | |
{/*Having to implement onFocus on each subsequent field to remove focus appearance from DatePicker | |
because the DatePicker is not firing its own onBlur event.*/} | |
<i className="fa fa-calendar sp-tdf-iconDate" | |
onClick={() => { this.labelIconClick('spTdfdatepicker');}} /> | |
<label className="title" htmlFor="day">Date</label> | |
<label className="error-message" ref="sp-tdf-lb-date">required</label> | |
</div> | |
<div className="form-group has-float-label"> | |
<Field name="time" component="select" type="text" withRef={true} | |
ref={(input) => { this.spTdfTime = input; }} | |
className="form-control" placeholder="Time" | |
onChange={event => this.validate(event)} | |
onFocus={() => this.updateFocus('date-picker', false)}> | |
{renderSelectElm()} | |
</Field> | |
<label className="title" htmlFor="time">Time</label> | |
<label className="error-message" ref="sp-tdf-lb-time">required</label> | |
</div> | |
<div className="form-group has-float-label"> | |
<Field className="form-control" | |
ref={(input) => { this.spTdfFirstname = input; }} | |
withRef={true} name="firstName" component="input" | |
type="text" placeholder="First Name" | |
onChange={event => this.validate(event)} | |
onFocus={() => this.updateFocus('date-picker', false)}/> | |
<label className="title" htmlFor="firstName" onClick={(e) => { | |
this.labelIconClick('spTdfFirstname'); | |
}} >First Name</label> | |
<label className="error-message" ref="sp-tdf-lb-firstName" >required</label> | |
</div> | |
<div className="form-group has-float-label"> | |
<Field className="form-control" | |
ref={(input) => { this.spTdfLastname = input; }} | |
withRef={true} name="lastName" component="input" | |
type="text" placeholder="Last Name" | |
onChange={event => this.validate(event)} | |
onFocus={() => this.updateFocus('date-picker', false)}/> | |
<label className="title" htmlFor="lastName" onClick={(e) => { | |
this.labelIconClick('spTdfLastname'); | |
}} >Last Name</label> | |
<label className="error-message" ref="sp-tdf-lb-lastName">required</label> | |
</div> | |
<div className="form-group has-float-label"> | |
<Field className="form-control" | |
ref={(input) => { this.spTdfEmail = input; }} | |
withRef={true} name="email" component="input" | |
type="email" placeholder="Email Address" | |
onChange={event => this.validate(event)} | |
onFocus={() => this.updateFocus('date-picker', false)}/> | |
<label className="title" htmlFor="email" onClick={(e) => { | |
this.labelIconClick('spTdfEmail'); | |
}}>Email</label> | |
<label className="error-message" ref="sp-tdf-lb-email">required</label> | |
</div> | |
<div className="form-group has-float-label"> | |
<Field name="phone" component="input" | |
ref={(input) => { this.spTdfPhone = input; }} | |
withRef={true} normalize={this.normalizePhone} | |
type="text" className="form-control" placeholder="(XXX) XXX-XXXX" | |
onChange={event => this.validate(event)} | |
onFocus={() => this.updateFocus('date-picker', false)}/> | |
<label className="title" htmlFor="phone" onClick={(e) => { | |
this.labelIconClick('spTdfPhone'); | |
}}>Phone</label> | |
<label className="error-message" ref="sp-tdf-lb-phone">required</label> | |
</div> | |
<div className="form-group has-float-label"> | |
<Field name="message" component="textarea" | |
ref={(input) => { this.spTdfMessage = input; }} | |
withRef={true} type="text" className="form-control" | |
rows="3" placeholder="Message: 'I can be there at 1pm'" | |
onChange={event => this.validate(event)} | |
onFocus={() => this.updateFocus('date-picker', false)}/> | |
<label className="title" htmlFor="message" onClick={(e) => { | |
this.labelIconClick('spTdfMessage'); | |
}}>Message</label> | |
</div> | |
<br/> | |
<LoadingButtonUI | |
buttonText="Request Test Drive" | |
disabled={ !this.state.valid } | |
isPending={this.props.isCalculating} | |
additionalClassParams="btn-block" | |
isSubmit={true} | |
/> | |
</form> | |
); | |
} | |
} | |
let TestDriveForm = reduxForm({ | |
form: 'testDriveForm' | |
})(TestDriveReduxForm); | |
const mapTimeToDisplayValue = (timeOfDay: string): string => { | |
return timeOfDay ? timeOfDay.replace(/\b[a-z]/g, (f) => { return f.toUpperCase(); }) : 'Morning'; | |
}; | |
const getTestDriveInitialValues = (state: IStateTree) => { | |
if (testDriveSelectors.isTestDriveCompleted(state)) { | |
const testDrive = testDriveSelectors.getTestDrive(state); | |
const testDriveDay = testDriveSelectors.getDay(state); | |
const day = testDriveDay ? testDriveDay : new Date(); | |
const time = mapTimeToDisplayValue(testDriveSelectors.getTimeOfDay(state)); | |
return assign({}, testDrive, day, time); | |
} else { | |
// use lead form data to pre-populate the testdrive | |
const shopper = selectors.getShopperInfo(state); | |
return { | |
firstName: shopper.firstName, | |
lastName: shopper.lastName, | |
email: shopper.email, | |
phone: shopper.phone, | |
day: new Date(), | |
time: mapTimeToDisplayValue('') | |
}; | |
} | |
}; | |
TestDriveForm = (connect( | |
(state: IStateTree) => ({ | |
initialValues: assign({}, getTestDriveInitialValues(state)), | |
isCalculating: testDriveSelectors.isCalculating(state) | |
}) | |
)as any) (TestDriveForm); | |
export default TestDriveForm; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// from import history from './../../../history'; | |
import { createMemoryHistory } from 'history'; | |
const history = createMemoryHistory(); | |
export default history; | |
// from import { TEST_DRIVE_CONFIRMATION } from '../redux/testDriveRoutes'; | |
export const TEST_DRIVE_CONFIRMATION = '/testDriveConfirmation'; | |
// from import * as testDriveSelectors from '../redux/testDriveSelectors'; | |
export const getDay = (state: IStateTree): Date => { | |
return getTestDrive(state).day; | |
}; | |
export const getFirstName = (state: IStateTree): string => { | |
return getTestDrive(state).firstName; | |
}; | |
export const getLastName = (state: IStateTree): string => { | |
return getTestDrive(state).lastName; | |
}; | |
export const getMessage = (state: IStateTree): string => { | |
return getTestDrive(state).message; | |
}; | |
export const getEmail = (state: IStateTree): string => { | |
return getTestDrive(state).email; | |
}; | |
export const getTimeOfDay = (state: IStateTree): string => { | |
const testDrive = getTestDrive(state); | |
if (testDrive.time) { | |
return testDrive.time.toLowerCase(); | |
} | |
else { | |
return null; | |
} | |
}; | |
/* | |
* When true is returned, it means all required fields are filled, but | |
* it does not mean the schedule is completed. | |
*/ | |
export const isTestDriveCompleted = (state: IStateTree): boolean => { | |
const testDrive = getTestDrive(state); | |
return (!!testDrive.day && !!testDrive.time && !!testDrive.firstName && | |
!!testDrive.lastName && !!testDrive.email && !!testDrive.phone); | |
}; | |
/** | |
* [7/20/2018]The testDrive object in the state never really has the values from Test Drive page | |
* when the state is in dataIslandModel, and the selectors above do not return the expected values. | |
* Story US100566 was not planned to fix this issue (Rocket, and maybe other teams too, did not | |
* know the existence of such issues). For this reason, the following selectors are created | |
* to work for dataIslandModel. I believe the state of the Test Drive page has never been translated | |
* from the redux-form to the app state. Perhaps there should be a tech debt story to handle this issue. | |
*/ | |
const getTestDriveFromForm = (state: IStateTree): ITestDrive => { | |
if (state.form && state.form.testDriveForm && state.form.testDriveForm.values) { | |
return state.form.testDriveForm.values; | |
} | |
return {}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment