Skip to content

Instantly share code, notes, and snippets.

@rproenza86
Created December 4, 2018 11:30
Show Gist options
  • Save rproenza86/e0efa6ce393c3dbcaa84f1c6ac995f4e to your computer and use it in GitHub Desktop.
Save rproenza86/e0efa6ce393c3dbcaa84f1c6ac995f4e to your computer and use it in GitHub Desktop.
How to use ReactJS Material UI lib DatePicker comp on ReduxForm comp
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;
// 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