처음에는 main container의 상태로 관리가 안될것 같아서 redux로 상태관리를 하려고 했다. 그래도 이런 휘발성 데이터를 redux로 관리하는 것은 아닌것 같아 state를 좀 나누는 방향으로 진행했다.
const [projectState, setProjectState] = useState({
range: { startDate: new Date(), dueDate: new Date(),
checkRanges: [
{ title: string,
checked: false,
range: { startDate: new Date(), dueDate: new Date(),
}, ...
]
});
return (
<>
<Route path='/project' render={() => <ProjectPage { ...projectState } />} />
</>
)
interface DateRange {
startDate: Date;
dueDate: Date;
}
interface ProjectProps {
title: string;
range: DateRange;
checkRanges: {
index?: number;
title: string;
checked: boolean;
date: DateRange;
}
propsHandler: (form: ProjectProps) => void;
}
// ProjectPage Component
const [checkDates, setCheckDates] = useState(new Array(props.checkRanges.length).fill(false)); // 하위항목들의 체크여부
const [checkRangeEnabled, setCheckRangeEnabled] = useState(false); // main range의 체크 여부
const [submitProject, setSubmitProject] = useState(false); // form submit
// 전체 form 유효성 검사
const [formValidator, setFormValidator] = useState({
title: true,
range: false,
isCheckAll: true,
checkRanges: new Array(props.checkRanges.length).fill({ checked: false, range: false }),
});
function titleHandler(newTitle: string) {
props.propsHandler({ ...props, title: newTitle });
setFromHandler({ ...formValidator, title: newTitle.trim() ? true : false });
}
// 하나의 datepicker만 검사할 경우 아래와 같음.
function dateHandler(range: DateRange, index: number) {
if (index < 0) {
props.propsHandler({ ...props, range: range });
} else {
const newCheckedRanges = { ...props, checkRanges };
newCheckedRanges[index].date = date;
props.propsHandler({ ...props, checkRanges: newCheckedRanges });
}
}
function radioHandler(isAll: boolean) {
props.propsHandler({ ..props, isCheckAll: isAll });
}
function checkRangeChangeHandler(check: boolean, index: number) {
const newCheckRanges = [ ...checkDates ];
newCheckRanges[index] = check;
setCheckDates(newCheckRanges);
}
// 여러개의 checkbox가 있고, 각각의 checkbox를 체크하면 datepicker가 활성화. 그 이후 유효성 검사를 하는 케이스.
// 코드가 너무 더러움. 리팩토링이 가능할지 확인을 해봐야됨.
function checkerDateValidateHandler(
check: boolean,
index: number,
range: DateRange,
dateType: string,
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
if (!check) return;
const dateRange = { ...range, [dateType]: new Date(event.target.value) };
const checkedDates = [ ...formValidator.checkRanges ];
if (dateType === 'startDate') {
checkedDates[index] = { checked: check, dates: moment(event.target.value).isBefore(dateRange.dueDate) };
} else {
checkedDates[index] = { checked: check, dates: moment(event.target.value).isAfter(dateRange.startDate) };
}
const checkDateValidArray = checkedDates.map((item) => {
const isCheckDateValid = item.checked && item.dates ? true : false;
const itemValid = item.checked && isCheckDateValid ? true : false;
let dateValid = -1;
if (!item.checked) {
dateValid = -1;
} else if (itemValid) {
dateValid = 1;
} else if (!itemValid) {
dateValid = 0;
}
return dateValid;
});
setFormValidator({ ...formValidator, checkDates: checkedDates, dates: !checkDateValidArray.includes(0) });
}
function emptyDateValidation(
range: DateRange,
dateType: string,
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
() => null;
}
function dateValidation(
range: DateRange,
dateType: string,
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
const dates = { ...range, [dateType]: new Date(event.target.value) };
dateType === 'startDate'
? setFormValidator({ ...formValidator, dates: moment(event.target.value).isBefore(dates.dueDate) })
: setFormValidator({ ...formValidator, dates: moment(event.target.value).isAfter(dates.startDate) });
}
useEffect(() => {
setSubmitProject(formValidator.title && formValidator.range);
}, [formValidator]);
// check ranges
const checkRangeComponents = props.checkRanges.map((item, idx = -1) => {
return (
<div key={idx}>
<CheckerSubject
enabled={!checkRangeEnabled}
title={item.title}
checked={formValidator.checkRanges[idx].checked ? checkDates : true}
index={idx}
checkHandler={checkRangeChangeHandler}
/>
<SubjectRangeComponent
range={
formValidator.checkRanges[idx].checked
? props.checkRanges[idx].date
: { startRange: new Date(), dueDate: new Date() }
}
dateChange={dateHandler}
enable={!enable ? !dateChecked[idx] : true}
index={idx}
dateValidation={emptyDateValidation} // 빈 핸들러
check={checkDates[idx]
/>
</div>
)
}
// contaner
return (
{/* 타이틀 */}
<Box mb={2} mt={2}>
<Grid container alignItems="center">
<Box mr={2}>
<Grid item>
<SubtitleComponent title={'과제명'} />
</Grid>
</Box>
<HiddenBlock item ishidden={formValidator.title}>
<Typography variant="subtitle2" color="error">
과제명은 필수 입력 사항입니다.
</Typography>
</HiddenBlock>
</Grid>
</Box>
<TitleComponent title={props.title} onChange={titleChangeHandler} />
{/* 과제범위 */}
<SubjectRangeComponent
left={'전체'}
right={'범위지정'}
radioHandler={subjectRangeRadioHandle}
subjectRange={props.range}
dateHandler={dateHandler}
enable={checkRangeEnabled}
index={-1}
dateValidation={dateValidation}
errorVisible={formValidator.dates}
/>
// check ranges
<Box mt={1}>{checkers}</Box>
)
onChange={(e) => {
if (!props.checkersDateValidator) {
const startDate = new Date(e.target.value);
const dueDate = dates.dueDate;
props.dateChange({ startDate: startDate, dueDate: dueDate }, props.index);
setDates({ startDate: startDate, dueDate: dueDate });
props.dateValidation(dates, 'startDate', e);
} else {
const startDate = new Date(e.target.value);
const dueDate = dates.dueDate;
props.dateChange({ startDate: startDate, dueDate: dueDate }, props.index);
setDates({ startDate: startDate, dueDate: dueDate });
const check = props.check ? true : false;
props.checkersDateValidator(check, props.index, dates, 'startDate', e);
}
}}
일단은 돌아가는 코드를 짰다. 하지만 불필요한 빈 핸들러도 있고, 이벤트 처리를 인라인 조건문으로 처리하는 것을 도저히 참을 수 없다.
function checkerDateValidateHandler(
check: boolean,
index: number,
range: DateRange,
dateType: string,
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
if (!check) return; <= what's this???????
const dateRange = { ...range, [dateType]: new Date(event.target.value) };
const checkedDates = [ ...formValidator.checkRanges ];
...
}
아래 코드를
if (!check) return;
아래 코드처럼
if (index < 0) { ... }
조건문으로 나눠서 구현 쌉가능...
SubjectRangeProject의 index 프로퍼티를 -1 또는 배열 인덱스로 쓰고 있었음. 메인 range로 쓸 때는 -1을 넣어주고, 리스트 컴포넌트로 사용할땐 배열 인덱스로 쓰고 있으니 index가 0보다 적을때 / 0 이상 일때로 조건을 나눠 이벤트 처리를 하면 좀 더 간결하게 이벤트 핸들링이 가능할것 같다. 아니 가능하다.
블로그 포스팅하면서 솔루션 찾음...
리팩토링 방향을 찾은건 다행이긴 함...