[React] 구글 Keep만들기3

Jungmin Ji·2023년 8월 18일
0

구글 Keep 만들기

목록 보기
3/5
post-thumbnail

UI 작성

Navbar.tsx

const Navbar = () => {
  const dispatch = useAppDispatch();
  const { pathname, state } = useLocation();

  if (pathname === "/404") {
    return null;
  } // 404일때 안나오게

  return (
    <StyledNav>
      <div className="nav__menu">
        <FiMenu onClick={() => dispatch(toggleMenu(true))} />
      </div>
      <Container>
        <div className="nav__page-title">{getStandardName(state)}</div>
        {state !== "Trash" && state !== "Archive" && (
          <ButtonFill
            onClick={() => dispatch(toggleCreateNoteModal(true))}
            className="nav__btn"
          >
            <span>+</span>
          </ButtonFill>
        )}
      </Container>
    </StyledNav>
  );
}

export default Navbar

menuSlice에서 toggleMenu
modalSlice에서 toggleCreateNoteModal

menuSlice.ts

const menuSlice = createSlice({
    name: "menu",
    initialState,
    reducers: {
      toggleMenu: (state, action) => {
        state.isOpen = action.payload
      }
    }
})
export const { toggleMenu } = menuSlice.actions;

modalSlice.ts

const modalSlice = createSlice({
  name: "modal",
  initialState,
  reducers: {
    toggleTagsModal: (state, { payload }) => {
      const { type, view } = payload;
      if(type === "add") {
        state.viewAddTagsModal = view;
      } else {
        state.viewEditTagsModal = view;
      }
    },
    toggleCreateNoteModal: (state, action) => {
      state.viewCreateNoteModal = action.payload;
    },
    toggleFiltersModal: (state, action) => {
      state.viewFiltersModal = action.payload;
    },
  },
});

export const { toggleTagsModal, toggleCreateNoteModal, toggleFiltersModal } = modalSlice.actions;

actions모아서 내보내도되고... 그냥 가져와도되는데 일단 그냥 쓰도록 한다.

페이지 이름 첫글자 대문자로 만드는 util함수 생성

utils/getStandardName.tsx

const getStandardName = (name: string) => {
    return (
        name?.slice(0, 1).toUpperCase() + name?.slice(1, name.length).toLocaleLowerCase()
    )
}

export default getStandardName;

Navbar에서 {getStandardName(state)}로 페이지이름 출력

UI 작성

Sidebar.tsx

const items = [
  { icon: <FaArchive />, title: "Archive", id: v4() },
  { icon: <FaTrash />, title: "Trash", id: v4() },
];

const Sidebar = () => {
  const dispatch = useAppDispatch();
  const { isOpen } = useAppSelector((state) => state.menu);
  const { tagsList } = useAppSelector((state) => state.tags);
  const { pathname } = useLocation();
  if(pathname === "/404") { return null;}

  return (
    // prop 이름을 openMenu라고 쓰면 다음과 같은 에러가 콘솔에 떠서 $를 붙여서 $openMenu라고 쓴다.
    // If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase openMenu instead.
    <Container $openMenu={isOpen ? "open" : ""}>
      <MainBox $openMenu={isOpen ? "open" : ""}>
        <StyledLogo>
          <h1>Keep</h1>
        </StyledLogo>
        <ItemsBox>
          {/* note item */}
          <li onClick={() => dispatch(toggleMenu(false))}>
            <NavLink
              to={"/"}
              state={`notes`}
              className={({ isActive }) =>
                isActive ? "active-item" : "inactive-item"
              }
            >
              <span>
                <FaLightbulb />
              </span>
              <span>Notes</span>
            </NavLink>
          </li>
          {tagsList?.map(({ tag, id }) => (
            <li key={id} onClick={() => dispatch(toggleMenu(false))}>
              <NavLink
                to={`/tag/${tag}`}
                state={`${tag}`}
                className={({ isActive }) =>
                  isActive ? "active-item" : "inactive-item"
                }
              >
                <span>
                  <FaTag />
                </span>
                <span>{getStandardName(tag)}</span>
              </NavLink>
            </li>
          ))}
          {/* edit tag item */}
          <li
            className="sidebar__edit-item"
            onClick={() =>
              dispatch(toggleTagsModal({ type: "edit", view: true }))
            }
          >
            <NavLink
              to={"/"}
              state={`notes`}
              className={({ isActive }) =>
                isActive ? "active-item" : "inactive-item"
              }
            >
              <span>
                <MdEdit />
              </span>
              <span>Edit Notes</span>
            </NavLink>
          </li>
          {/* archive */}
          {items.map(({ icon, title, id }) => (
            <li key={id} onClick={() => dispatch(toggleMenu(false))}>
              <NavLink
                to={`/${title.toLocaleLowerCase()}`}
                state={`${title}`}
                className={({ isActive }) =>
                  isActive ? "active-item" : "inactive-item"
                }
              >
                <span>{icon}</span>
                <span>{getStandardName(title)}</span>
              </NavLink>
            </li>
          ))}
        </ItemsBox>
      </MainBox>
    </Container>
  );
}

export default Sidebar

Tag를 위한 Modal생성하기

reducer생성1(addTags, deleteTags)

tagsSlice.ts

const tagsSlice = createSlice({
    name: 'tags',
    initialState,
    reducers: {
      addTags: (state, { payload }) => {
        if(state.tagsList.find(({tag}) => tag === payload.tag)) { // 있는 태그는 추가하지않고 이미있다는 알림 
          toast.warning("이미 존재하는 태그입니다. ");
        } else {
          state.tagsList.push(payload); // 내부에서 immer라는 모듈로 처리되기때문에 불변성안지키면서 push 할수있음
          toast.info("새로운 태그가 등록되었습니다.");
        }
      },
      deleteTags: (state, { payload }) => {
        state.tagsList = state.tagsList.filter(({id}) => id !== payload); //같지 않은것만 넣어줌. 즉, 같은건 지워짐
        toast.info("태그가 삭제되었습니다.");
      }
    },
})

export const { addTags, deleteTags } = tagsSlice.actions;

reducer생성2(removeTags)

할당된 태그를 노트에서 지운다.
notesListSlice.ts

const notesListSlice = createSlice({
    name: 'notesList',
    initialState,
    reducers: {
      removeTags: (state, { payload }) => {
        state.mainNotes = state.mainNotes.map((note) => ({
          ...note, // spread operate
          tags: note.tags.filter(({ tag }) => tag !== payload.tag), // payload에 같은 태그는 삭제하고 넣어준다
        }));
      }
    }
})

export const { removeTags } = notesListSlice.actions;

UI 작성

TagsModal.tsx


interface TagsModalProps {
  type: string
}
const TagsModal = ({ type }: TagsModalProps) => { // add 또는 edit
  const dispatch = useAppDispatch();
  const { tagsList } = useAppSelector((state) => state.tags); // 처음에 만들어놓은 tag들
  const [inputText, setInputText] = useState('');

  const submitHandler = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if(!inputText) {return;}

    dispatch(addTags({tag: inputText.toLowerCase(), id: v4() }));
    setInputText('');
  }
  const deleteTagsHandler = (tag: string, id: string) => {
    dispatch(deleteTags(id));
    dispatch(removeTags({ tag }));
  }
  return (
    <FixedContainer>
      <Box>
        <div className='editTags__header'>
          <div className='editTags__title'>
            {type === "add" ? "ADD" : "Edit"} Tags
          </div>
          <DeleteBox 
            className='editTags__close' 
            onClick={() => dispatch(toggleTagsModal({type, view: false}))}
          >
            <FaTimes />
          </DeleteBox>
        </div>
        <form onSubmit={submitHandler}>
          <StyledInput
            type="text"
            value={inputText}
            placeholder="new tag..."
            onChange={(e) => setInputText(e.target.value)}
          />
        </form>
        <TagsBox>
          {tagsList.map(({tag, id}) => (
            <li key={id}>
              <div className='editTags__tag'>
                {getStandardName(tag)}
                <DeleteBox onClick={() => deleteTagsHandler(tag, id)}>
                  <FaTimes />
                </DeleteBox>
              </div>
            </li>
          ))}
        </TagsBox>
      </Box>
    </FixedContainer>
  )
}

export default TagsModal

map, filter, reduce 메소드는 불변성을 지켜준다.
원본 자체를 바꾸는게 아니라 새롭게 배열을 만들어서 반환하기때문에...

App.tsx

import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

function App() {
  const { viewEditTagsModal } = useAppSelector((state) => state.modal);
  return (
    <div className="app">
      {viewEditTagsModal && <TagsModal type='edit' />} 
      <ToastContainer 
        position='bottom-right'
        theme='light'
        pauseOnHover
        autoClose={1500}
      />

viewEditTagsModal가 true일때 편집하는 <TagsModal>을 띄워준다.
아래에 <ToastContainer> 컴포넌트도 넣어줌

profile
FE DEV/디블리셔

0개의 댓글

관련 채용 정보