Custom Component 만들기

moontag·2022년 7월 4일
0

React

목록 보기
8/10

Modal

  • 미리보기

  • open/close - useState로 상태 변경

  • 조건부렌더링 - open상태만 modal컴포넌트 렌더링

// styled ...

export const Modal = () => {
  const [isOpen, setIsOpen] = useState(false);

  // onClick 시, isOpen 상태 변경하는 헨들러
  const openModalHandler = () => {
    setIsOpen(!isOpen);
  };

  return (
    <>
      <ModalContainer>
        <ModalBtn
          // 클릭 시, Modal이 열린 상태(isOpen)를 boolean 타입으로 변경하는 메소드
          onClick={openModalHandler}
        >
          {isOpen === true ? "Opened!" : "Open Modal"}
          {/* 조건부 렌더링 - Modal 열린 상태(isOpen true)면 내부 텍스트 'Opened!'/ 닫힌 상태(isOpen false)면 'Open Modal' */}
        </ModalBtn>
        {/* 조건부 렌더링 - Modal 열린 상태(isOpen true)만 모달창과 배경 출력 */}
        {isOpen && (
          <ModalBackdrop onClick={openModalHandler}>
            {/* 부모 클릭이벤트 물려받으므로, 안물려받는 코드 작성 */}
            <ModalView onClick={(e) => e.stopPropagation()}>
              <button onClick={openModalHandler}>&times;</button>
              <span>HELLO MODAL 🔥</span>
              <div>&nbsp;</div>
            </ModalView>
          </ModalBackdrop>
        )}
      </ModalContainer>
    </>
  );
};



Toggle

  • on/off - useState로 상태 변경
  • 조건부 렌더링 - on상태일 때 className 추가
export const Toggle = ({ primary }) => {
  const [isOn, setisOn] = useState(false);

  const toggleHandler = () => {
    setisOn(!isOn);
  };

  return (
    <>
      <ToggleContainer
        // 클릭 시 isOn 상태 변경하는 메소드
        onClick={toggleHandler}
      >
        {/* 조건부 스타일링 - Switch ON 상태만 toggle--checked 클래스를 div 엘리먼트 2개에 모두 추가*/}
        <div className={`toggle-container ${isOn ? "toggle--checked" : ""}`} />
        <div className={`toggle-circle ${isOn ? "toggle--checked" : ""}`} />
      </ToggleContainer>
	
	  {/* 조건부 렌더링 - Switch ON상태면 내부 텍스트를 'ON'으로, 아니면 'OFF'*/}
      <Desc>Toggle Switch {isOn ? "ON" : "OFF"}</Desc>
    </>
  );
};



Tab

  • 현재 탭 - useState로 인덱스 상태 저장
  • 탭 li - map으로 출력
  • map의 idx로 현재 클릭된 인덱스 전달
    map((el, idx)=> ....onClick={()=> handeler함수(idx) })
  • 조건부렌더링 - 현재탭이면 focused 클래스 추가
export const Tab = () => {
  // currentTab: 현재 어떤 Tab이 선택됐는지 확인하기 위한 index상태 (초기값은 0)
  const [currentTab, setCurrentTab] = useState(0);

  const menuArr = [
    { name: "Tab1", content: "Tab menu ONE" },
    { name: "Tab2", content: "Tab menu TWO" },
    { name: "Tab3", content: "Tab menu THREE" },
  ];
  
  // parameter로 현재 선택한 인덱스 값을 전달
  const selectMenuHandler = (index) => {
    setCurrentTab(index);
  };

  // map으로 li 출력
  const menuTab = menuArr.map((el, index) => (
    <li
      key={index}
      //  map의 index 전달
      onClick={() => selectMenuHandler(index)}
	  // 조건부렌더링 - 현재탭이면 focused 클래스 추가
      className={`submenu${currentTab === index ? " focused" : ""}`}
    >
      {el.name}
    </li>
  ));

  return (
    <>
      <div>
        <TabMenu>
          {menuTab}   // 출력
        </TabMenu>
        <Desc>
          {/* 현재 선택된 메뉴의 content를 표시 */}
          <p className="menuContent">{menuArr[currentTab].content}</p>
        </Desc>
      </div>
    </>
  );
};



Tag

  • 태그배열 - useState로 초기배열, 갱신 설정

기능

새 태그 추가

  1. 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행 안하기

    • onKeyUp 이벤트
    • e.target.value.trim() !== ""
      : trim()으로 앞뒤 공백 제거후 ""빈문자열 아니면 실행
    • e.key === "Enter" or e.keyCode == 13
      : 엔터키 입력시
  2. 태그 추가하고 나서 input 창 비우기

    • e.target.value = "";
  3. 중복 태그인지 검사하여 이미 있으면 추가 안하기

    • if (tags.includes(e.target.value)) {
              window.alert("중복된 태그입니다");
              return setTags([...tags]);
            }
  4. 태그 개수 10개로 제한

    • if (tags.length > 10) {
              window.alert("더이상 태그를 추가할 수 없습니다!");
              e.target.value = "";
            }```

태그 삭제

const removeTags = (indexToRemove) => {
    const res = tags.filter((el, idx) => idx !== indexToRemove);
    setTags(res);
}
...
<span
className="tag-close-icon"
onClick={() => removeTags(index)}
  >
    &times; // x 특수문자 사용법
</span>
export const Tag = () => {
  const initialTags = ["CodeStates", "kimcoding"];

  const [tags, setTags] = useState(initialTags);

  // 삭제 - 클릭된 index 제거
  const removeTags = (indexToRemove) => {
    const res = tags.filter((el, idx) => idx !== indexToRemove);
    setTags(res);
  };

  // 추가 - tags 배열에 새로운 태그 추가
  const addTags = (e) => {
    // 1. 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // 2. 태그가 추가되면 input 창 비우기
    if (e.key === "Enter" && e.target.value.trim() !== "") {
      // 3. 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
      if (tags.includes(e.target.value)) {
        window.alert("중복된 태그입니다");
        return setTags([...tags]);
      }
      // 태그 개수 10개로 제한
      if (tags.length > 10) {
        window.alert("더이상 태그를 추가할 수 없습니다!");
        e.target.value = "";
      }
      // 새 태그 추가 - 배열 뒤에 추가
      else {
        setTags([...tags, e.target.value]);
        e.target.value = "";
      }
    }
  };

  return (
    <>
      <TagsInput>
        <ul id="tags">
          {tags.map((tag, index) => (
            <li key={index} className="tag">
              <span className="tag-title">{tag}</span>
              {/* 삭제 아이콘 click 시, removeTags 메소드 실행 */}
              <span
                className="tag-close-icon"
                onClick={() => removeTags(index)}
              >
                &times;
              </span>
            </li>
          ))}
        </ul>
        <input
          className="tag-input"
          type="text"
          // 엔더키 누르면 태그 추가, {addTags}만 전달해도 됨
          onKeyUp={(e) => addTags(e)}
          placeholder="Press enter to add tags"
        />
      </TagsInput>
    </>
  );
};



Autocomplete

3가지 상태 - useState

  1. hasText - input값의 유무 상태를 확인
  2. inputValue - input값의 상태를 확인
  3. options - input값 포함하는 autocomplete 추천 항목 리스트를 확인

input - onChange 헨들러

  1. input값인 inputValue 갱신
  2. input값 입력으로 글자 있으니 hasText true로 갱신
  3. options 상태따라 dropdown으로 보이는 항목 갱신
    • filter : 추천항목에 입력 값의 글자가 포함된 항목만 dropdown으로 출력
  1. 클릭시, input 입력창 inputValue 갱신
  2. dropdown을 클릭된 항목으로 갱신

삭제버튼

  • input 입력창 ""빈문자열로 초기화하기

화살표키로 dropdown 변경

  • 인덱스 0부터고, 처음시작할땐 아무것도 선택되지 않은 -1을 초기값 지정
  • 아래키 누르면 인덱스 0으로 바뀌면서 options[0] 선택하도록 하기
  • 위키 누르면 index - 1 해주기
export const Autocomplete = () => {
   // hasText - input값의 유무를 확인
  const [hasText, setHasText] = useState(false);
  // inputValue - input값의 상태를 확인
  const [inputValue, setInputValue] = useState("");
  // options - input값 포함하는 autocomplete 추천 항목 리스트를 확인
  const [options, setOptions] = useState(deselectedOptions);

  // useEffect를 아래와 같이 활용 가능
  useEffect(() => {
    if (inputValue === "") {
      setHasText(false);
    }
  }, [inputValue]);

  // input과 dropdown 상태 관리를 위한 handler
  const handleInputChange = (event) => {
    /* - input 값 변경(onChange)시 발생하는 이벤트 핸들러.
     * - input값과 상태를 연결시키는 controlled component.
     * - 추천항목이 dropdown으로 바로 바뀌는 상태로 변경.
     *
     * 1. input값 상태인 inputValue 갱신
     * 2. input값 유무 상태인 hasText 갱신
     * 3. options의 상태(추천항목)따라 dropdown으로 보이는 항목 갱신
     */
    const dropDownList = deselectedOptions.filter((el) =>
      el.includes(event.target.value)
    );
    setInputValue(event.target.value);
    setHasText(true);
    setOptions(dropDownList);
  };
  
	// 상하 화살표 키로 input 값 변경하기
   // 인덱스 0부터고, 처음시작할땐 아무것도 선택되지 않은 -1을 초기값 지정
  // 아래키누르면 0으로 바뀌면서 options[0] 선택하도록
  // 위 키누르면 index - 1 해주기
  const [selected, setSelected] = useState(-1);
  const handleDropDownClick = (clickedOption) => {
    /* - dropdown 항목 클릭 시(onClick)실행되는 이벤트 헨들러
     * - dropdown 항목 클릭 시 input값이 해당 항목의 값으로 변경
     *
     * 1. input값 상태인 inputValue 갱신
     * 2. options의 상태가 클릭된 항목만으로 갱신
     */
    setInputValue(clickedOption);
    setOptions([clickedOption]);
  };

  const handleDeleteButtonClick = () => {
    // - X버튼 클릭 시(onClick) input값 전부 삭제하는 이벤트 헨들러
    // 1. input값 상태인 inputValue를 빈 문자열로 초기화
    setInputValue("");
  };
  
  const handleKeyUp = (e) => {
    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState#example
    // eslint-disable-next-line
    if (
      e.getModifierState("Fn") ||
      e.getModifierState("Hyper") ||
      e.getModifierState("OS") ||
      e.getModifierState("Super") ||
      e.getModifierState("Win")
    )
      return;
    if (
      e.getModifierState("Control") +
        e.getModifierState("Alt") +
        e.getModifierState("Meta") >
      1
    )
      return;

    if (e.code === "ArrowUp" && selected >= 0) {
      setSelected(selected - 1);
    }
    if (e.code === "ArrowDown" && selected < options.length - 1) {
      setSelected(selected + 1);
    }
    if (e.code === "Enter" && selected >= 0) {
      handleDropDownClick(options[selected]);
      setSelected(-1);
    }
  };

  return (
    <div className="autocomplete-wrapper">
      <InputContainer>
        {/* input값(value) - state와 연결
        input값 변경 시 handleInputChange 함수  호출 */}
        {/* 삭제 버튼 - input 값, dropdown 삭제하는 handler 함수 */}
        <input
          type="text"
          value={inputValue}
          onChange={(e) => handleInputChange(e)}
          onKeyUp={(e) => handleKeyUp(e)}
        ></input>
        <div onClick={handleDeleteButtonClick} className="delete-button">
          &times;
        </div>
      </InputContainer>
      {/* 조건부 렌더링 - input 값 없으면? dropdown 미출력 */}
      <DropDown
        options={hasText ? options : []}
        handleComboBox={handleDropDownClick}
      />
    </div>
  );
};

export const DropDown = ({ options, handleComboBox }) => {
  const dropDown = options.map((el, index) => (
    <li onClick={() => handleComboBox(el)} key={index}>
      {el}
    </li>
  ));
  return (
    <DropDownContainer>
      {/* input 값에 맞는 autocomplete 선택 옵션 보여주기*/}
      {dropDown}
    </DropDownContainer>
  );
};



ClickToEdit

  • 부모 => 자식으로 props 전달 (value, handleValueChange)
export const MyInput = ({ value, handleValueChange }) => {
  const inputEl = useRef(null);
  const [isEditMode, setEditMode] = useState(false);
  const [newValue, setNewValue] = useState(value);

  // 수정가능 상태일 시, 포커스 두기
  useEffect(() => {
    if (isEditMode) {
      inputEl.current.focus();
    }
  }, [isEditMode]);

  useEffect(() => {
    setNewValue(value);
  }, [value]);

  // 수정가능 상태 - isEditMode 변경
  const handleClick = () => {
    setEditMode(true);
  };

  // 포커스 해제하면 - 수정불가능 상태로 변경 + newValue값 전달
  const handleBlur = () => {
    handleValueChange(newValue); // newValue값 전달
    setEditMode(false); // 수정불가능 상태
  };

  // input 입력시(onChange) - 저장된 value 입력값으로 갱신
  const handleInputChange = (e) => {
    setNewValue(e.target.value);
  };

  return (
    <InputBox>
      {isEditMode ? (
        <InputEdit
          type="text"
          value={newValue}
          ref={inputEl}
          // 포커스를 잃으면, Edit가 불가능한 상태로 변경되는 메소드
          onBlur={handleBlur}
          // input 값 변경되면, 저장된 value를 업데이트하는 메소드
          onChange={handleInputChange}
        />
      ) : (
        <span
          // input 글자 클릭시 - 수정 가능한 상태로 변경
          onClick={handleClick}
        >
          {newValue}
        </span>
      )}
    </InputBox>
  );
};

const cache = {
  name: "김코딩",
  age: 20,
};

export const ClickToEdit = () => {
  const [name, setName] = useState(cache.name);
  const [age, setAge] = useState(cache.age);

  return (
    <>
      <InputView>
        <label>이름</label>
        <MyInput
          value={name}
          handleValueChange={(newValue) => setName(newValue)}
        />
      </InputView>
      <InputView>
        <label>나이</label>
        <MyInput
          value={age}
          handleValueChange={(newValue) => setAge(newValue)}
        />
      </InputView>
      <InputView>
        <div className="view">
          이름 {name} 나이 {age}
        </div>
      </InputView>
    </>
  );
};

profile
터벅터벅 나의 개발 일상

0개의 댓글