Project/SeSAC 2차 팀 프로젝트

[새싹x코딩온] 웹 개발자 부트캠프 과정 2차 팀프로젝트 회고 #6 | 배송지 관리

다니니니 2024. 9. 22. 02:05
728x90



결제 페이지와 마이페이지에 배송지 관리 기능을 추가했다.

회원가입때 입력했던 주소 정보를 기본 배송지로 불러오고,

사용자가 배송지를 추가하고, 배송지 정보를 수정하고, 필요없는 배송지 정보는 삭제할 수 있도록 구현했다.

 

 

1. 배송지 읽어오기

 

배송지 데이터는 사용자의 정보를 바탕으로 DB의 회원 테이블과 연결된 추가 배송지 테이블과 연결해서 해당되는 사용자의 배송지 정보를 서버에서 모두 전달해준다. 서버로부터 받은 데이터를 화면에 렌더링 해줬다.

이 과정에서 리덕스를 사용해서 배송지 정보에 대한 상태관리를 전반적으로 수행하고자 했다..

결제 페이지에서는 배송지를 출력하는 곳에 배송지 변경 버튼을 누르면, changeAddress 라는 함수가 호출된다.

이 함수는 axios요청으로 서버에서 배송지 정보를 비동기적으로 요청하고, 서버로부터 응답을 받으면 받아온 배송지 데이터를 리덕스 상태에 추가해서 사용자에게 업데이트된 배송지 목록을 제공한다.

  // 배송지 변경 버튼 클릭
  const changeAddress = async (e) => {
    const addressContainer = addressModelRef.current;
    addressContainer.style.display = 'block';
    stopScroll();
    try {
      const res = await getAddressList();
      if (res.status === 200) {
        dispatch(fetchAddList([...res.data]));
      }
    } catch (err) {
      console.error(err);
    }
  };

마이페이지에서도 이와 비슷하게 호출한다. 다만 결제페이지에서는 버튼을 클릭했을 때, 호출하는 반면

마이페이지에서는 배송지 관리라는 페이지가 마운트될때 useEffect를 통해서 해당함수가 호출되도록 했다. 

  const dispatch = useDispatch();
  useEffect(() => {
    fetchAddress();
  }, []);
  const fetchAddress = async () => {
    try {
      const res = await getAddressList();
      if (res.status === 200) {
        dispatch(fetchAddList([...res.data]));
      }
    } catch (err) {
      console.error(err);
    }
  };

위의 호출방법으로 인해서 리덕스에 저장된 배송지 정보들을

AddressSelect라는 컴포넌트에서 useSelector를 통해서 불러오고,

이것을 바탕으로 정보들을 렌더링했다. 

// 전체 배송지 선택 컴포넌트
export default function AddressSelect() {
  const { addressList } = useSelector((state) => state.address);

  const [add, setAdd] = useState(false);
  const [edit, setEdit] = useState(false);

  const dispatch = useDispatch();

  const addAddress = () => {
    setAdd(true);
    setEdit(false);
    dispatch(readAddr({}));
  };

  const cancelAdd = () => {
    setAdd(false);
  };

  const addRegisterDone = () => {
    setAdd(false);
    setEdit(false);
  };

  const editAddress = () => {
    setEdit(true);
    setAdd(true);
  };

  return (
    <div className="address-container">
      <div className="address-bx">
        <div className="address-title">
          {add ? (
            <button onClick={cancelAdd} className="add-cancel">
              <FontAwesomeIcon icon={faChevronLeft} />
            </button>
          ) : (
            ''
          )}
          <h2>
            {add ? (edit ? '배송지 수정' : '배송지 추가') : '배송지 선택'}
          </h2>
          <button onClick={closeModal} className="close-modal">
            <FontAwesomeIcon icon={faXmark} />
          </button>
        </div>
        <ul className="address-list">
          {add ? (
            edit ? (
              <AddressInput addDone={addRegisterDone} status={'edit'} />
            ) : (
              <AddressInput addDone={addRegisterDone} status={'add'} />
            )
          ) : (
            addressList.map((data) => (
              <AddressInfo
                infos={data}
                key={data.addId}
                add={addRegisterDone}
                edit={editAddress}
              />
            ))
          )}
        </ul>
        <div className="address-add">
          {add ? '' : <button onClick={addAddress}>배송지 추가</button>}
        </div>
      </div>
    </div>
  );
}

2. 배송지 추가(배송지 생성)와 배송지 수정

 


사용자에게 편리한 배송지 생성 및 수정 기능을 제공하기 위해 AddressInfo라는 배송지를 입력할 수 있는

컴포넌트를 만들었다. 이 컴포넌트에서 사용자 입력을 기반으로 배송지를 추가하거나 수정할 수 있다.

import React from 'react';
import { AddressInput } from './Register';
import { useForm } from 'react-hook-form';
import { insertAddress, getAddressList, updateAddress } from '../api/address';
import { useDispatch, useSelector } from 'react-redux';
import { fetchAddList } from '../store/addressSlice';
import { showAlert } from '../utils/alert';

// 배송지 입력 컴포넌트
export default function AddressInfo({ addDone, status }) {
  const { addrValue } = useSelector((state) => state.address);
  console.log(addrValue);

  const {
    addName,
    zipCode,
    address,
    detailedAddress,
    isDefault,
    phoneNum,
    receiver,
    addId,
  } = addrValue;

  const {
    register,
    handleSubmit,
    formState: { errors },
    watch,
    setValue,
    setError,
  } = useForm({
    defaultValues: {
      addName: addName || '',
      receiver: receiver || '',
      phoneNum: phoneNum || '',
      zipCode: zipCode || '',
      address: address || '',
      detailedAddress: detailedAddress || '',
      isDefault: isDefault || false,
    },
  });

  const dispatch = useDispatch();

  const registerAddress = async (data) => {
    try {
      if (status === 'add') {
        const res = await insertAddress(data);
        if (res.status === 200) {
          showAlert('success', '배송지가 저장되었습니다.');
          const addRes = await getAddressList();
          dispatch(fetchAddList([...addRes.data]));
        }
      } else if (status === 'edit') {
        const res = await updateAddress(addId, data);
        if (res.data.result) {
          showAlert('success', '배송지가 수정되었습니다.');
          const addRes = await getAddressList();
          dispatch(fetchAddList([...addRes.data]));
        }
      }
      addDone();
    } catch (err) {
      console.error(err);
    }
  };
  const onInValid = (err) => {
    console.log('onInValid >> ', err);
  };

  return (
    <div className="address-insert">
      <form onSubmit={handleSubmit(registerAddress, onInValid)}>
        <div className="address-addNameInput">
          <label htmlFor="addName">배송지명</label>
          <input
            type="text"
            id="addName"
            name="addName"
            {...register('addName', {
              required: '배송지명을 입력해주세요',
              pattern: {
                message: '배송지명은 한글 2-6자 사이여야 합니다.',
                value: /^[가-힣]{2,6}$/,
              },
            })}
          />
          <span className="error-msg">{errors.addName?.message}</span>
        </div>
        <div className="address-receiverInput">
          <label htmlFor="receiver">받는 사람</label>
          <input
            type="text"
            id="receiver"
            name="receiver"
            {...register('receiver', {
              required: '받는 사람을 입력해주세요',
              pattern: {
                message: '이름은 한글 2-6자 사이여야 합니다.',
                value: /^[가-힣]{2,6}$/,
              },
            })}
          />
          <span className="error-msg">{errors.receiver?.message}</span>
        </div>
        <div className="address-phoneInput">
          <label htmlFor="phoneNum">전화번호</label>
          <input
            type="text"
            id="phoneNum"
            name="phoneNum"
            {...register('phoneNum', {
              required: '휴대전화번호를 입력해주세요!',
              pattern: {
                message:
                  '휴대전화번호는 0-9의 숫자로 10자리 또는 11자리 숫자로만 이루어져야 합니다.',
                value: /^[0-9]{10,11}$/,
              },
            })}
          />
          <span className="error-msg">{errors.phoneNum?.message}</span>
        </div>
        <AddressInput register={register} setValue={setValue} errors={errors} />
        <div className="address-isDefault">
          <input
            type="checkbox"
            id="isDefault"
            name="isDefault"
            {...register('isDefault')}
          />
          <label htmlFor="isDefault" value={isDefault}>
            기본 배송지로 저장
          </label>
        </div>
        <button className="address-save">배송지 저장</button>
      </form>
    </div>
  );
}

 

useForm 훅을 사용하여 폼 관리를 간소화 했다. 초기값으로는 리덕스 상태에서 가져온 배송지 정보를 설정해서, 기존의

배송지 수정 시에도 쉽게 정보를 불러올 수 있도록 했다. 이를 통해 사용자 경험을 개선하고, 기존 주소 정보를 불필요하게 반복 입력하지 않도록 했다.

 

배송지 생성 버튼을 클릭하면, 사용자가 입력 데이터가 registerAddress 함수로 전달되서 axios 요청을 통해 서버에 저장된다. 여기서 부모컴포넌트로부터 전달받은 props인 status에 따라 새 배송지 추가인지, 기존 배송지 수정인지 결정하고, 그에 따라 그에 맞는 axios 요청을 하도록 했다.

 

주소 입력을 위한 폼 필드에서 각 입력값에 대한 유효성 검사를 실시해서 사용자로 하여금 올바른 정보를 입력하도록 유도했다. 주소를 입력하는 컴포넌트는 다른 프론트엔드 분이 회원가입 페이지를 구현할 때 만들어놓았던 배송지 입력 컴포넌트를 활용해서 재사용성을 높였다. 

 

3. 배송지 삭제

 

 

사용자가 배송지를 삭제하고자 할 때, 실수로 삭제하는 것을 방지하기 위해 먼저 확인 대화상자를 통해 주소지 삭제 확인 여부를 물었다. 

사용자가 삭제를 확정하면 삭제를 요청하는 axios 호출을 하고,

호출이 성공적으로 이루어지면 최신 배송지 목록을 다시 불러와서 리덕스 상태를 업데이트 했다.

이를 통해 사용자의 배송지 정보를 최신 상태로 유지하고, 사용자가 삭제 후에도 편리하게 다른 배송지를 확인할 수 있도록 했다.

  // 배송지 삭제
  const delAddress = async (e) => {
    const result = await confirmAlert('question', '주소지를 삭제하시겠습니까?');
    if (result) {
      try {
        const res = await deleteAddress(addId);
        if (res.data) {
          const addRes = await getAddressList();
          dispatch(fetchAddList([...addRes.data]));
        }
      } catch (err) {
        console.error(err);
      }
    }
  };

 

4. 배송지 선택

 사용자가 배송을 원하는 배송지를 선택하면 selectAddress 라는 함수가 호출되어

해당 컴포넌트가 가지고 있는 배송지 정보를 결제 페이지 화면에 렌더링 될 수 있도록 구현했다.

이 과정에서 DOM 을 선택하는 메서드를 활용했다.

그리고 배송지 목록을 보여주는 모달창이 배송지 선택 후 자동으로 닫히도록 구현해서 사용자 경험을 개선했다.

 

  // 배송지 선택
  const selectAddress = () => {
    const orderInfo = document.querySelector('.order-addrInfoBx');
    if (orderInfo) {
      const orderAddrBx = document.querySelector('.order-addressSelect');
      const addrName = orderInfo.querySelector(
        '.order-addrName div:last-child',
      );
      const addrReceiver = orderInfo.querySelector(
        '.order-addrReceiver div:last-child',
      );
      const addrPhoneNum = orderInfo.querySelector(
        '.order-addrPhoneNum div:last-child',
      );
      const addr = orderInfo.querySelector('.order-addr div:last-child span');

      addrName.innerText = `${addName} `;
      addrReceiver.innerText = `${receiver} `;
      addrPhoneNum.innerText = `${phoneNum} `;
      addr.innerText = `(${zipCode}) ${address} ${detailedAddress}`;
      orderAddrBx.style.display = 'none';

      goScroll();
    }
  };

 

5. 후기

결제 페이지와 마이 페이지에서 배송지 관리 기능을 유사하게 구현하다 보니, 공통된 컴포넌트를 만들어 재사용성을 높이고 코드 중복을 줄이는데 집중했다. 이를 통해 유지보수와 확장성이 향상된 것 같다.

그러다보니 컴포넌트가 많아짐에 따라 useState를 통한 상태 관리가 비효율적으로 느껴져서 리덕스를 활용하게 되었다.

덕분에 코드가 더 깔끔해지고 유지보수가 용이해졌다. 

 

전혀 다른 스타일의 결제 페이지와 마이 페이지를 하나의 컴포넌트를 통해 스타일만 다르게 해서 적용한 점에 뿌듯함을 느낀다. 동일한 기능을 공유하는 컴포넌트를 만들면서 코드의 재사용성을 높이고, 개발 과정이 효율적이고 일관적이게 진행할 수 있었던 것 같다. 

 

 

728x90