• 크롬 모멘텀(Momentum)에 이미지 업로드 기능 추가하기

    2023. 3. 6.

    by. JJo 😊

    Cloudinary와 React-Dropzone을 연동하여 만들어본 이미지 업로드 기능을 모멘텀 프로젝트에 추가해 보았다!

    현재 모멘텀에 배경으로 깔리는 이미지는 기본으로 세팅해 놓은 기본 샘플 이미지들을 랜덤으로 가져와서 적용되는 방식이다.

    // background.bg
    export const bg = ["bg1.jpg", "bg2.jpg", "bg3.jpg", "bg4.jpg", "bg5.jpg", "bg6.jpg", "bg7.jpg", "bg8.jpg", "bg9.jpg", "bg10.jpg"];
    // Main.jsx
    import { bg } from "../../api/background";
    
    const randomBg = () => {
       return bg[Math.floor(Math.random() * bg.length)];
    };
    
    return (
       <section className={styles.container} style={{ backgroundImage: `url(images/${randomBg()})` }}>
          ...
       </section>
    );

    이 배경들을 사용자가 직접 업로드하여 원하는 이미지를 배경으로 사용할 수 있게 하고 싶었다.

    그래서 구현하고자 하는 사항을 아래처럼 정리해 보았다.

    👉 화면 좌측 하단에 이미지 설정 버튼 추가 > 클릭 시 이미지 업로드를 할 수 있는 팝업 화면 노출

    👉 이미지 업로드 시 바로 배경으로 설정하기

    👉 업로드한 이미지를 삭제할 수 있게 삭제 버튼 기능 넣기

    👉 사용자가 업로드한 이미지가 없을 경우에는 기본 샘플 이미지들이 노출되게 하기


     

    이미지 업로드 기능만 구현했을 때는 모든 상태값들을 Dropzone 컴포넌트 안에서 다 관리했었다. (이미지 업로드 관련 로직이 다 여기에 있었기에..)

    근데 모멘텀에 적용하려고 하니, 상태값 일부를 해당 컴포넌트가 아닌 부모 컴포넌트에서 관리해야 할 필요성이 생겼다. Dropzone 컴포넌트에서 업로드한 이미지 값들을 Main 컴포넌트에서 접근을 하려면 상태값을 끌어올려야 했다.

     

    그래서 이 상태값을 어떻게 관리하면 좋을지 고민되었다. 🤔

    Context API를 사용해서 상태값을 전역으로 관리하기

    상태값을 부모 컴포넌트에서 만들고 하위 컴포넌트에게 props로 전달하기(prop drilling)

    Prop Drilling?

    Prop Drilling 은 props를 오로지 하위 컴포넌트로 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서 React Component 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정이다.

    위 두 가지 방법에 대해 고민하다가 두 번째 방법 (prop drilling)으로 해결하기로 했다.

    prop의 전달이 수많은 컴포넌트를 거치지 않고 단순히 1~2개 정도의 컴포넌트만 거쳐갈 것이고, 관리하려는 상태값이 이미지 데이터 하나이므로 굳이 Context를 쓸 필요가 없어 보였다.

     

    기존 Dropzone 컴포넌트에서 관리했던 localFilesMain 컴포넌트로 옮겼다.

    // Main.jsx
    // localStorage에 값 존재 여부로 초기값 세팅
    const getBg = () => {
       const bgimgs = localStorage.getItem("bg");
       return bgimgs ? JSON.parse(bgimgs) : [];
    };
    const [localFiles, setlocalFiles] = useState(getBg);

    추가로 backgrounds 상태값을 선언하고 useEffectlocalFiles가 변경될 때마다 localFiles에서 이미지 경로값인 secure_url만 추출하여 별도로 관리하게 했다.

    const [backgrounds, setBackgrounds] = useState();
    
    useEffect(() => {
       const bgUrls = localFiles.map(file => file.secure_url);
       setBackgrounds(bgUrls);
    }, [localFiles]);

    Dropzone 컴포넌트에 propslocalFilesonsetlocalFiles 함수를 넘겨주었다.

    <DropZone localFiles={localFiles} onsetlocalFiles={setlocalFiles} />

    그리고 배경 이미지를 랜덤으로 추출하는 기존 로직을 수정하였다.

    그래서 사용자가 업로드한 이미지가 있을 경우엔 업로드된 이미지에서 랜덤으로 나오고, 업로드한 이미지가 없을 경우엔 기본 샘플 이미지가 랜덤으로 나오게끔 수정하였다.

    // 기존 로직
    const randomBg = () => {
       return bg[Math.floor(Math.random() * bg.length)];
    };
    
    // 수정된 로직
    const randomBg = () => {
       return backgrounds?.length ? backgrounds[Math.floor(Math.random() * backgrounds.length)] : `images/${bg[Math.floor(Math.random() * bg.length)]}`;
    };
    
    return (
       <section className={styles.container} style={{ backgroundImage: `url(${randomBg()})` }}>
       ...
       </section>
    );

    구현된 화면 👇👇

     

    이미지 삭제 기능

    이미지를 성공적으로 업로드하면 바로 배경 이미지로 설정이 된다. 이때 설정되는 이미지는 랜덤으로 적용된다.

    그리고 하단의 '나의 이미지 목록'에 업로드된 이미지들이 바로 업데이트가 되고, 업로드한 이미지를 삭제할 수 있도록 상단에 삭제 버튼을 추가하였다. 삭제 버튼을 클릭하면 뒤의 배경도 그에 맞춰 변경되는 걸 확인할 수 있다.

     

    + 추가 ) Error Handling

    이미지 업로드가 실패했을 경우의 상황도 고려하여 오류 처리도 추가했다.

    error 는 오류 상태를 저장하고 errorImgLength는 업로드 실패한 이미지의 개수를 표기해 줄 것이다. 

    const [error, setError] = useState(false);
    const [errorImgLength, setErrorImgLength] = useState(0);
    ...
    
    try {
       const response = await axios.post(url, formData, config)
       const files = await response.data;
       setlocalFiles(prev => [...prev, files])
    } catch (error) {
       setErrorImgLength(prev => prev + 1)
       setError(true);
       return;
    }
    
    ...
    
    return (
       <section>
          {error && <p>업로드 실패 ❌<br /> 업로드 실패한 이미지가 있습니다! 업로드를 다시 시도해주세요.</p>}
          {error && errorImg > 0 && <p>업로드 실패 이미지 개수 ({errorImg})</p>}
       </section>
    )


    일단 리액트 모멘텀 프로젝트를 기획하고 구현하고자 했던 기능들을 추가했지만 아직 부족한 점이 많은 것 같다.

    코드도 좀 더 깔끔하게 리팩토링도 하고 싶고...

    조금 더 공부해서 틈틈이 고도화를 해봐야겠다. 

    728x90

    댓글