Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] DropDownOption 에 대해서 context 로 분리 #93

Merged
merged 29 commits into from
Jul 28, 2024

Conversation

SeieunYoo
Copy link
Collaborator

@SeieunYoo SeieunYoo commented Jul 21, 2024

🎉 변경 사항

  • 기존에 DropDownOption 을 cloneElement 로 받아서 props 를 전달했는데 context 로 분리해서 cloneElement 로직을 삭제합니다.
  1. 드롭다운에서 빠진 aria 속성 추가. (trigger 나 id 관련 등등,,)
  • id prop 이 추가되어서 드롭다운이 여러 개가 생길 때 id를 전달해서 제어할 수 있습니다.
  1. useSafeContext 훅 추가
  • context 가 쓰일 곳이 많아질 것 같아 context 의 null 이나 undefined 를 확인하는 훅을 추가합니다.
import type { Context } from "react";
import { useContext } from "react";

// Context 를 확장하는 타입 T 에서 반환할 때 null 이나 undefined 를 제외하여 타입 추론을 강화합니다.
type SafeContextType<T> =
  T extends Context<infer U> ? Exclude<U, null | undefined> : never;

const useSafeContext = <T extends Context<any>>(
  context: T
): SafeContextType<T> => {
  const contextValue = useContext(context);

// context가 undefined 로 뜬다면 Provider 와 함께 쓰라는 에러 추가
  if (contextValue === undefined) {
    throw new Error(`useSafeContext must be used within a context Provider`);
  }

  return contextValue as SafeContextType<T>;
};

export default useSafeContext;

🚩 관련 이슈

🙏 여기는 꼭 봐주세요!

Copy link
Contributor

@SeieunYoo SeieunYoo self-assigned this Jul 21, 2024
Copy link
Contributor

Copy link
Contributor

Copy link
Collaborator

@eugene028 eugene028 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context 사용하니까 엄청 깔끔해졌군요 😁

.changeset/lazy-radios-battle.md Outdated Show resolved Hide resolved
packages/wow-ui/src/components/DropDown/index.tsx Outdated Show resolved Hide resolved
packages/wow-ui/src/components/DropDown/index.tsx Outdated Show resolved Hide resolved
Copy link
Collaborator

@ghdtjgus76 ghdtjgus76 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿굿 수고하셨습니다..!

packages/wow-ui/src/hooks/useDropDownState.ts Show resolved Hide resolved
packages/wow-ui/src/hooks/useDropDownState.ts Show resolved Hide resolved
packages/wow-ui/src/components/DropDown/index.tsx Outdated Show resolved Hide resolved
packages/wow-ui/src/hooks/useDropDownState.ts Outdated Show resolved Hide resolved
packages/wow-ui/src/components/DropDown/index.tsx Outdated Show resolved Hide resolved
Comment on lines 122 to 134
handleSelect(value);
};

const setOptionRef = (index: number) => (el: HTMLDivElement | null) => {
optionsRef.current[index] = el;
};

const renderTrigger = (trigger: ReactElement) => (
const renderCustomTrigger = (trigger: ReactElement) => (
<>
{cloneElement(trigger, {
onClick: toggleDropdown,
"aria-expanded": open,
"aria-haspopup": "true",
id: `${dropdownId}-trigger`,
"aria-controls": `${dropdownId}`,
})}
</>
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3;
renderCustomTrigger에서도 cloneElement가 아니라 context api 방식으로 해결해볼 수 있을 거 같다는 생각이 드는데, 별도의 컴포넌트로 분리 후 context api 적용하도록 개선해보면 좋지 않을까 싶습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context api 를 적용하려면 trigger 외부에 div 같은 엘리먼트로 하나 감싸서 프로퍼티를 전달해주어야 하는데 요렇게 되면 의도하지 않은 외부 div 가 돔에 생겨서 스타일링에 영향을 줄 수도 있을 것 같은데 어떻게 생각하시나용?!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 그럴 수도 있겠네요!
당장은 best practice가 뭔지 잘 모르겠어서 조금 더 고민해봐야 할 거 같아요!
추후에 더 좋은 방법이 생각난다면 개선해봐도 좋을 거 같은...

Copy link
Contributor

Copy link
Contributor

@SeieunYoo
Copy link
Collaborator Author

DropDown 리뷰 반영하면서 코드들 찾아보다 드롭다운 옵션 컴포넌트의 전체 value 관리 관련해서 좋은 해결 방법을 찾아서 추가로 리팩토링 해두었어요!
image

요 방식은 value 를 관리하는 Collection 이라는 context 를 추가로 만드는 방법입니다. Option 컴포넌트에서 collection 관련 context 를 불러와서 value 값을 업데이트해주는 방식입니다.

그래서 기존에 children 을 flat 해서 value 와 text 를 관리하는 로직을 삭제했고 useDropDownState 에서는 selectedValue,open,focusedValue 만 관리하도록 수정해두었습니다!

나머지 코드 리뷰들은 이 리팩토링을 통해 해결되었어요!!

+) 추가적으로 export 되서는 안되지만 내부 개발에서 사용되는 컴포넌트들이 생겨서 빌드 스크립트에서 제외해주기 위해 임시방편으로 f8527b2 컴포넌트 네임을 통해 제외시켜주도록 했습니다!! cli 로 얼른 만들어야겠어요.. 🙏

Copy link
Contributor

Copy link
Contributor

Copy link
Contributor

Copy link
Contributor

Copy link
Collaborator

@ghdtjgus76 ghdtjgus76 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿굿 수고하셨어요!

Comment on lines 37 to 68
const updateFocusedValue = useCallback(
(direction: number) => {
const values = Array.from(itemMap.keys());
setFocusedValue((prevValue) => {
const currentIndex = values.indexOf(prevValue ?? "");
const nextIndex =
(currentIndex + direction + values.length) % values.length;
return values[nextIndex] ?? "";
});
},
[itemMap, setFocusedValue]
);

const handleKeyDown = useCallback(
(event: KeyboardEvent<HTMLDivElement>) => {
if (!open) return;

const { key } = event;

if (key === "ArrowDown") {
updateFocusedValue(1);
event.preventDefault();
} else if (key === "ArrowUp") {
updateFocusedValue(-1);
event.preventDefault();
} else if (key === "Enter" && focusedValue !== null) {
handleSelect(focusedValue, itemMap.get(focusedValue)?.text);
event.preventDefault();
}
},
[open, focusedValue, updateFocusedValue, handleSelect, itemMap]
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4;

<Flex
    direction="column"
    role="listbox"
    style={{ visibility: open ? "visible" : "hidden" }}
    visibility={open ? "visible" : "hidden"}
    className={dropdownContentStyle({
    type: trigger ? "custom" : "default",
  })}
>
  {children}
</Flex>

현재 DropDown 컴포넌트 내부에 있는 Flex 컴포넌트와 children을 DropDownOptionList로 만들고 focus 관련 로직을 그 하위에서 관리해주면 어떨까 합니다!
뭔가 focus 관련 로직은 Wrapper 쪽이 아니라 OptionList 쪽에서 관리해주는 것이 이해하기 쉬울 거 같아요
DropDownWrapper에서는 정말 wrapper의 역할만 하는 느낌으로 변경하면 조금 더 깔끔하지 않을까 하는,,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋아요!! DropDownOptionList 컴포넌트에서 focus 처리하도록 수정해두었어요 6ca0434

Comment on lines 11 to 13
type ContextValue = {
itemMap: Map<ItemData["value"], { text: ItemData["text"] }>;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3;
혹시 text 부분은 객체로 사용한 이유가 있을까요?
객체로 나타내지 않고 string으로도 나타내볼 수 있을 거 같다는 생각이 듭니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음에는 ref 랑 text 같이 전달해서 구현하다가 text 만 넘기는 걸로 수정돼서 객체로 구현할 필요가 없을 것 같네요!! 6440016 요기서 text 만 넘기도록 수정했습니다

Copy link
Contributor

@SeieunYoo SeieunYoo requested a review from ghdtjgus76 July 28, 2024 05:53
Copy link
Collaborator

@ghdtjgus76 ghdtjgus76 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

진짜 수고하셨습니다... 👏

@SeieunYoo SeieunYoo merged commit 940b704 into main Jul 28, 2024
3 checks passed
@SeieunYoo SeieunYoo deleted the refactor/dropdown-context branch July 28, 2024 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Refactor] DropDown context 로 분리
3 participants