Skip to content

Commit

Permalink
refactor: base Date Picker's arch and performance improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
ali-master committed Jun 5, 2021
1 parent c819d0e commit bf4ad6c
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 233 deletions.
230 changes: 39 additions & 191 deletions src/components/WheelPicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,94 +6,66 @@ import MultiPicker from 'rmc-picker/es/MultiPicker';
import { GlobalStyle } from './styles';
// Helpers
import {
convertSelectedDateToAnArray,
convertSelectedDateToObject,
isObjectEmpty,
prefixClassName,
} from '../../helpers';
import {
isValidDate,
jalaliMonths,
pickerData,
daysInMonth as calculateDaysInMonth,
getWeekDay,
getWeekDayName,
convertDateToObject,
convertObjectToDate,
daysInMonth as calculateDaysInMonth,
isValid,
isAfter,
isBefore,
pickerData,
} from '../../helpers/date';
// Hooks
import { usePrevious } from '../../hooks/previous';
// Types
import type {
PickerColumns,
PickerItemModel,
PickerSelectedDateValue,
WheelPickerProps,
DateConfig,
PickerDateModel,
DateConfigTypes,
PickerClassNameFormatter,
WheelPickerProps,
} from './index.types';
import { usePicker } from '../../hooks/usePicker';

export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
const {
selectedDate,
setSelectedDate,
defaultPickerValues,

maxYear,
minYear,

filterAllowedColumnRows,
handlePickerItemTextContent,
handlePickerItemClassNames,
} = usePicker(props);
// Local States
const [daysInMonth, setDaysInMonth] = useState<number>(29);
const [selectedDate, setSelectedDate] = useState<PickerDateModel>({});

// Hooks
const previousSelectedDate = usePrevious<PickerDateModel>(selectedDate);
// Local Variables
/**
* Picker CSS classnames prefix name
*/
const prefix = prefixClassName(props.prefix!);
// Memo list

/**
* Default Picker selected columns value which goes from the parent to local changes
*/
const defaultPickerValues = React.useMemo<Array<string>>(() => {
return convertSelectedDateToAnArray(
(isObjectEmpty(selectedDate) ? props.defaultValue : selectedDate)!,
);
}, [props.defaultValue, selectedDate]);
/**
* parse and convert [minDate] to an object
*
* @type {Required<PickerDateModel>}
*/
const minDateObject = React.useMemo(
() => convertDateToObject(props.minDate!),
[props.minDate],
);
console.log('minDateObject', minDateObject);
/**
* parse and convert [maxDate] to an object
*
* @type {Required<PickerDateModel>}
*/
const maxDateObject = React.useMemo(
() => convertDateToObject(props.maxDate!),
[props.minDate],
);
console.log('maxDateObject', maxDateObject);
// Memo list
/**
* Generate Picker's columns with their values
*
* @category watchers
* @return {PickerColumns}
*/
const pickerColumns = React.useMemo<PickerColumns>(() => {
let columns = Object.keys(props.config).map((column) => {
return Object.keys(props.config).map((column) => {
switch (column) {
case 'year':
return {
type: 'year',
value: pickerData.getYears({
min: 10,
max: 10,
min: minYear,
max: maxYear,
}),
};
case 'month':
Expand Down Expand Up @@ -122,12 +94,12 @@ export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
value: pickerData.getSeconds(),
};
default:
throw Error('unknown type');
throw TypeError(
`[PersianMobileDatePicker] ${column}'s type is not valid. Columns types should be one of [year, month, day]`,
);
}
}) as PickerColumns;

return columns;
}, [props.config, pickerData, daysInMonth]);
}, [props.config, daysInMonth, minYear, maxYear]);
/**
* Prepare the default value of DatePicker when the Developer has not passed a defaultValue
*/
Expand All @@ -143,16 +115,16 @@ export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
});
setSelectedDate(defaultDate);
}
}, [pickerColumns, selectedDate]);
}, [pickerColumns, selectedDate, props.defaultValue]);
/**
* * Local Watchers
*/
// Calculate days in selected months
React.useEffect(() => {
if (!isObjectEmpty(selectedDate)) {
if (
previousSelectedDate.month !== selectedDate.month ||
previousSelectedDate.year !== selectedDate.year
previousSelectedDate?.month !== selectedDate?.month ||
previousSelectedDate?.year !== selectedDate?.year
) {
setDaysInMonth(
calculateDaysInMonth(
Expand All @@ -162,82 +134,15 @@ export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
);
}
}
}, [selectedDate]);
}, [selectedDate, previousSelectedDate?.year, previousSelectedDate?.month]);
/**
* Derived Selected Date from Prop's defaultValue
*/
React.useEffect(() => {
if (
isValidDate(
Number(props.defaultValue?.year),
Number(props.defaultValue?.month),
Number(props.defaultValue?.day),
)
) {
setSelectedDate(props.defaultValue!);
if (isValid(props.defaultValue!)) {
setSelectedDate(convertDateToObject(props.defaultValue!));
}
}, [props.defaultValue]);
/**
* Date picker columns config
*
* @returns {DateConfig}
*/
const configs = React.useMemo(() => {
const result = { ...props.config } as Required<DateConfig>;
if (result.month && !result.month.formatter) {
result.month.formatter = (value) => jalaliMonths[value];
}

return result;
}, [props.config]);
// Handlers
/**
* Picker items' text content
*
* @param {PickerItemModel} pickerItem
* @returns {PickerSelectedDateValue} columns text content
*/
function handlePickerItemTextContent(
pickerItem: PickerItemModel,
): PickerSelectedDateValue {
return (
configs[pickerItem.type]?.formatter?.(pickerItem.value) ??
pickerItem.value
);
}
/**
* Handle every single of columns' row Classname by their type and value
*
* @param pickerItem
* @returns {string}
*/
function handlePickerItemClassNames(pickerItem: PickerItemModel): string {
const classNamesFormatter = configs[pickerItem.type]?.classname;
if (classNamesFormatter) {
const targetSelectedDate: PickerClassNameFormatter = { ...selectedDate };
targetSelectedDate[pickerItem.type] = pickerItem.value;
const weekDay = getWeekDay(
targetSelectedDate.year as number,
targetSelectedDate.month as number,
targetSelectedDate.day as number,
);
if (weekDay >= 0) {
targetSelectedDate.weekDay = weekDay;
targetSelectedDate.weekDayName = getWeekDayName(
targetSelectedDate.year as number,
targetSelectedDate.month as number,
targetSelectedDate.day as number,
);
}

// Pass to the classname config's formatter
const classNames = classNamesFormatter(targetSelectedDate);

return Array.isArray(classNames) ? classNames.join(' ') : classNames;
}

return '';
}

/**
* Picker onChange event which includes every columns' selected value
Expand All @@ -252,54 +157,6 @@ export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
props.onChange?.(convertSelectedDate);
}

/**
* Allow to render the Column's row if [min] and [max] is passed.
*
* @param {DateConfigTypes} rowType
* @param {PickerSelectedDateValue} rowValue
* @returns {boolean}
*/
const shouldRenderColumnRow = React.useCallback<
(rowType: DateConfigTypes, rowValue: PickerSelectedDateValue) => boolean
>(
(rowType, rowValue) => {
const $selectedDateObject = { ...selectedDate };
$selectedDateObject[rowType] = rowValue;
const $selectedDate = convertObjectToDate($selectedDateObject);
console.log(
'$selectedDateObject',
$selectedDateObject,
'isValid',
isValid($selectedDate),
);
if (!isValid($selectedDate)) return true;

if (!isObjectEmpty(minDateObject) && !isObjectEmpty(maxDateObject)) {
if (rowType === 'year') {
return (
rowValue >= (minDateObject[rowType] || rowValue) &&
rowValue <= (maxDateObject[rowType] || rowValue)
);
}

return (
isAfter(props.minDate!, $selectedDate) &&
isBefore(props.maxDate!, $selectedDate)
);
} else if (!isObjectEmpty(minDateObject)) {
return isAfter(props.minDate!, $selectedDate);
} else if (!isObjectEmpty(maxDateObject)) {
return isBefore(props.maxDate!, $selectedDate);
}

return true;
},
[selectedDate, props.minDate, props.maxDate],
);

console.log('selectedDate', selectedDate);
console.log('pickerColumns', pickerColumns);

return (
<React.Fragment>
<MultiPicker
Expand All @@ -315,9 +172,9 @@ export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
`indicator ${prefix(`${column.type}-column`)}`,
)}
>
{column.value.map((pickerItem) => {
return (
shouldRenderColumnRow(pickerItem.type, pickerItem.value) && (
{filterAllowedColumnRows(column.value, column.type).map(
(pickerItem) => {
return (
<Picker.Item
key={`${pickerItem.type}_${pickerItem.value}`}
className={`${prefix(
Expand All @@ -327,9 +184,9 @@ export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
>
{handlePickerItemTextContent(pickerItem)}
</Picker.Item>
)
);
})}
);
},
)}
</Picker>
);
})}
Expand All @@ -340,15 +197,6 @@ export const WheelPicker: React.FC<WheelPickerProps> = (props) => {
};

WheelPicker.defaultProps = {
minDay: 1,
maxDay: 31,
minMonth: 1,
maxMonth: 12,
minYear: 1300,
maxYear: 1500,
defaultValue: {
year: 1399,
month: 12,
day: 29,
},
minDecade: 30, // past 30 years
maxDecade: 30, // next 30 years
};
13 changes: 10 additions & 3 deletions src/components/WheelPicker/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface WheelPickerProps {
// Max Day value
maxDay?: number;
// Default column value
defaultValue?: PickerDateModel;
defaultValue?: Date;
// Title
title?: string;
// Triggered when the component DOM is generated, the parameter is the component element
Expand All @@ -35,6 +35,10 @@ export interface WheelPickerProps {
minDate?: Date;
// Max Date value
maxDate?: Date;
// Max decade
maxDecade?: number;
// Min decade
minDecade?: number;
}

export type DateConfigTypes =
Expand All @@ -52,10 +56,12 @@ export type DateConfigFormats =
| 'hh'
| 'mm'
| 'ss';
export type PickerSelectedDateValue = string | number;
export type PickerSelectedDateValue = number;
export interface DateConfigValuesModel {
caption?: string;
formatter?: (value: PickerSelectedDateValue) => PickerSelectedDateValue;
formatter?: (
value: PickerSelectedDateValue,
) => PickerSelectedDateValue | string;
classname?: (value: PickerClassNameFormatter) => string | string[];
shouldRender?: (value: PickerClassNameFormatter) => boolean;
}
Expand All @@ -74,6 +80,7 @@ export type PickerDateModel = {
minute?: PickerSelectedDateValue;
second?: PickerSelectedDateValue;
};
export type RequiredPickerDateModel = Required<PickerDateModel>;

export interface PickerClassNameFormatter extends PickerDateModel {
weekDay?: number;
Expand Down
Loading

0 comments on commit bf4ad6c

Please sign in to comment.