checkPropTypes.js:20 Warning: Failed prop type: You provided a checked
prop to a form field without an onChange
handler. This will render a read-only field. If the field should be mutable use defaultChecked
. Otherwise, set either onChange
or readOnly
.
이 에러가 콘솔창에 떠있었다.
쿼리스트링을 객체로 변환시키는 함수도 필요하다.
지금은 무조건 append를 사용해서 선택한 값을 쿼리스트링으로 넣게만 되어있다. 이렇게 되면 단점이
중복된 쿼리스트링이 계속해서 입력된다.
유형별로 관리가 되는 것이 아니라, 각각을 쿼리스트링으로 넣었다. 예를 들어 숙박유형 = 게스트 하우스&호텔, 인원=성인1명&아이1명 과 같이 쿼리스트링이 들어가 항목별로 관리가 되어야 하는데, 숙박유형=게스트하우스, 숙박유형=호텔 과 같은 방식으로 들어간다. 그러다 보니 api에서 제대로 호출이 되지 않았다.* 백엔드에서 이 부분을 api로 어떻게 만들었는지는 잘 모르겠다. 하지만 api로 여러 유형을 선택한 것을 처리하려면 분명 이렇게 고쳐야 할 것이므로, 그리고 스테이폴리오에서도 이렇게 했기 때문에 방식을 그대로 따라해보기로했다.
category='게스트하우스&'호텔'
이 아니라,
category='게스트하우스'&category='호텔'
이 된다.
또 다른 문제는.. 선택한 값이 체크리스트에 반영되어 있지 않다는 것이다.
게스트 하우스만 체크했다면 다시 눌렀을 때 체크된 게스트 하우스를 해제하고 호텔을 체크할 수도 있어야 한다. 하지만 되지 않는 다는 것 ㅠㅠ
category=게스트하우스&호텔&아웃도어&adult=1
에서 게스트하우스를 다시 눌러 체크해제 하면
category=호텔&아웃도어&adult=1
이 되어야 하는데,
category=게스트하우스&category=호텔&category=아웃도어&adult=1
에서
category=게스트하우스&category=호텔&category=아웃도어&adult=1&category='게스트하우스'
가 된다.
const handleFilter = stateObj => {
const URLSearch = new URLSearchParams(location.search);
Object.entries(stateObj).map(([key, value]) => {
if (typeof value === 'boolean') {
value && URLSearch.append('category', key);
} else {
value && URLSearch.append(key, value);
}
});
navigate(`/list?` + URLSearch.toString());
closeHandler();
};
이렇게 관리하고 있었다.
const [selectedType, setSelectedType] = useState({
게스트하우스: false,
호텔: false,
});
const handleChange = e => {
const { name } = e.target;
setSelectedType(current => ({ ...current, [name]: !current[name] }));
};
체크박스의 선택/해제 상태를 관리하기 위해 상탯값을 boolean으로 정의했던 것 같다. (근데 왜 체크박스도 제대로 선택/해제가 되지 않았지..?)
<li key={idx}>
<label onChange={handleChange} name={item.name}>
<span>{item.type}</span>
<input
type="checkbox"
value="space"
name={item.name}
checked={selectedType[item.name]}
/>
</label>
</li>
일단 현재 코드에서 빠르게 위에서 언급한 문제만 고쳐보도록 하자.
그 외의 것들은 이후에 다시 리팩토링해보자.
const handleFilter = stateObj => {
const URLSearch = new URLSearchParams(location.search);
Object.entries(stateObj).map(([key, value]) => {
if (typeof value === 'array') {
URLSearch.set(key, value.join('&'));
} else {
URLSearch.set(key, value);
}
});
navigate(`/list?` + URLSearch.toString());
closeHandler();
};
선택할 때도 해당하는 이름이 배열에 없을 때만 넣어주도록 했다.
const [selectedType, setSelectedType] = useState({
category: [],
});
const handleChange = e => {
const { name } = e.target;
if (!selectedType.category.includes(name)) {
setSelectedType({
...selectedType,
category: [...selectedType.category, name],
});
}
};
정상적으로 쿼리스트링이 생성되고 업데이트 된다.
쿼리스트링은 정상적으로 작동하지만, 선택한 체크박스는 여전히 드롭다운 창을 닫았다가 다시 열었을 때 선택여부가 유지되지 않는다.
아마도 상위의 state가 업데이트되면서 해당 하위컴포넌트가 리렌더링되고, 선택한 체크박스의 상탯값이 초기화되는 것 같다.
전역 상태관리로 진행해보았다.
필터링 항목의 모든 값을 하나의 객체로 관리하고,
import { atom } from 'recoil';
export const filterConditionState = atom({
key: 'filterConditionState',
default: {
city: '',
count: {
adult: 0,
child: 0,
baby: 0,
},
priceRange: {
min: 0,
max: 0,
},
category: [],
theme: [],
},
});
이 전역 상태에 각각의 필터링 항목에서 선택한 값을 업데이트 해주었다.
예를들면, 숙박인원을 선택할 때 인원을 추가하는 로직은 다음과 같다.
export default function SelectPeople({ closeHandler, handleFilter }) {
const [filterCondition, setFilterCondition] =
useRecoilState(filterConditionState);
const { handleSearchParams } = useQueryString();
const plusQuantity = name => {
const updatedCount = {
...filterCondition.count,
[name]: filterCondition.count[name] + 1,
};
setFilterCondition({
...filterCondition,
count: updatedCount,
});
};
그리고 업데이트 된 전역 상탯값을 읽어와 value를 업데이트한다.
<InputNum>
<input
type="number"
value={filterCondition.count[item.name] || 0}
readOnly
/>
<span>명</span>
</InputNum>
드롭다운창을 열고 닫아도 선택했던 체크박스가 그대로 유지된다!
const handleSearchParams = (page, obj, type) => {
Object.entries(obj).map(([key, value]) => {
if (value.length) {
if (type === 'multiple') {
return URLSearch.set(key, value.join('&'));
} else {
return URLSearch.set(key, value);
}
}
});
navigate(`/${page}?` + URLSearch.toString());
};
value들을 전부 &로 조인해버리니 쿼리스트링을 만드는 데는 문제가 없었지만, URLSearchParams만으로 쿼리스트링을 관리하기에 조금 부족함이 생겼다.
예를들어 보자.
{category:[guesthouse, hotel]}
을 쿼리스트링으로 만들 때를 생각해보자.
guesthouse&hotel
로 만들었다. URLSearchParams.set('category', 문자열)
[guesthouse, hotel]
이어야 관리하기 편하겠지만 guesthouse&hotel
이라는 연결된 문자열을 가져오게 된다.따라서 현재 선택된 값들, 즉 쿼리스트링의 값들을 읽어와 사용해야 하는 경우가 생긴다면 이렇게 연결된 문자열을 분리해 각 값을 관리하는 로직이 별도로 필요하다.
아직 이 로직이 필요하지는 않지만, 확장성 있는 코드를 위해 여유가 된다면 만들어주는 것도 좋을 것 같다.
기존 코드가 선택한 값을 객체로 관리한 뒤 해당 객체를 쿼리스트링으로 변환시켜주었는데, 이게 꼭 필요할지 의문이 든다. 객체는 key에 해당하는 value를 구분해 넣을 수 있다는 장점이 있는데, 독립된 컴포넌트에서 관리하는 상탯값은 하나의 key안에 포함되는 value들이다.
따라서 value만 적절한 형태로 관리하고, 쿼리스트링으로 변환할 때 하나의 key를 인자로 전달해주면 충분할 것 같다.
아무래도 생각했던 대로 상위 컴포넌트의 state가 리렌더링되면서 하위 컴포넌트의 state가 초기화되어 선택한 체크박스가 드롭다운 창을 닫았다 열었을 때 유지되지 않는 것이 맞는 것 같다.
전역 상태관리로 문제를 해결하긴 했지만, 좋은 방법은 아닌 것 같다.
드롭다운창을 닫았을 때 부모컴포넌트에서 업데이트되는 state가 있는지 확인하고, 다시 해결해봐야겠다.