nailedReact / bokgungom-market

멋쟁이사자처럼 프론트엔드 스쿨 3기 15조 득근득근 복근곰마켓 레포지토리
https://bokgungom-market.vercel.app/
6 stars 5 forks source link

[PROFILE] 로그인 후 프로필 설정 페이지 #70

Closed SEMINSEMINSEMIN closed 1 year ago

SEMINSEMINSEMIN commented 1 year ago

로그인 후 프로필 설정 페이지

이슈

github-actions[bot] commented 1 year ago

Branch feat/issue-70 created for issue: [PROFILE] 로그인 후 프로필 설정 페이지

SEMINSEMINSEMIN commented 1 year ago

useCallback()

문제 상황

Line 13:11: The 'getPrevData' function makes the dependencies of useEffect Hook (at line 30) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'getPrevData' in its own useCallback() Hook react-hooks/exhaustive-deps

import React, { useEffect, useState, useCallback } from "react";
import axios from "axios";
import TopBar from "../../components/TopBar";
import ProfileSetInpsTemp from "../../components/ProfileSetInpsTemp/ProfileSetInpsTemp";
import Button from "../../components/Button";

export default function EditProfile() {
    const [isDisabled, setIsDisabled] = useState(true);
    const [prevData, setPrevData] = useState();

    const token = localStorage.getItem("token");

    const getPrevData = async () => {
        try {
            const res = await axios.get(
                "https://mandarin.api.weniv.co.kr/user/myinfo",
                {
                    headers: {
                        Authorization: `Bearer ${token}`,
                    },
                }
            );

            console.log(res.data);
        } catch {}
    };

    useEffect(() => {
        getPrevData();
    }, [getPrevData]);

    return (
        <>
            <TopBar type={"A4"} />
            <ProfileSetInpsTemp formId={"logined"} />
            <Button
                form={"logined"}
                className={"ms"}
                type={"submit"}
                disabled={isDisabled}
            >
                저장
            </Button>
        </>
    );
}

해결

useCallback 함수 사용을 통해 token이 변하는 경우가 아니면, EditProfile 실행 때마다 getPrevData 함수를 재생성할 필요가 없다는 것을 알림. useEffect가 getPrevData에 의존하고 있으므로, 혹시라도 EditProfile 재렌더링시 getPrevData가 재생성돼 다시 요청을 보내는 걸 막기 위함.

import React, { useEffect, useState, useCallback } from "react";
import axios from "axios";
import TopBar from "../../components/TopBar";
import ProfileSetInpsTemp from "../../components/ProfileSetInpsTemp/ProfileSetInpsTemp";
import Button from "../../components/Button";

export default function EditProfile() {
    const [isDisabled, setIsDisabled] = useState(true);
    const [prevData, setPrevData] = useState();

    const token = localStorage.getItem("token");

    const getPrevData = useCallback(async () => {
        try {
            const res = await axios.get(
                "https://mandarin.api.weniv.co.kr/user/myinfo",
                {
                    headers: {
                        Authorization: `Bearer ${token}`,
                    },
                }
            );

            console.log(res.data);
        } catch {}
    }, [token]);

    useEffect(() => {
        getPrevData();
    }, [getPrevData]);

    return (
        <>
            <TopBar type={"A4"} />
            <ProfileSetInpsTemp formId={"logined"} />
            <Button
                form={"logined"}
                className={"ms"}
                type={"submit"}
                disabled={isDisabled}
            >
                저장
            </Button>
        </>
    );
}
SEMINSEMINSEMIN commented 1 year ago

프로필 수정시 기존 정보를 가져올 것인가에 대하여

과제 자체는 기존 정보를 가져오지 않는 형태로 가는듯 합니다. 저는 처음에는 기존 정보를 가져오는게 사용자 입장에서 수정이 편리할거 같아 가져온 뒤 수정을 하려고 했는데, 유효성 검사가 탭에 따라 달라져서 뭔가 과제랑 방향이 다른 느낌인거 같아서.. 일단 과제에 적힌 내용대로 기존 정보를 가져오지 않은 뒤 하는 방향으로 동일하게 하겠습니다.

코드 백업용

EditProfile.jsx

import React, { useEffect, useState, useCallback } from "react";
import axios from "axios";
import TopBar from "../../components/TopBar";
import ProfileSetInpsTemp from "../../components/ProfileSetInpsTemp/ProfileSetTemp";

export default function EditProfile() {
    const [isDisabled, setIsDisabled] = useState(true);
    const [prevData, setPrevData] = useState({});

    const token = localStorage.getItem("token");

    // 페이지 로드시 기존 정보를 가져오기 위함
    const getPrevData = useCallback(async () => {
        try {
            const res = await axios.get(
                "https://mandarin.api.weniv.co.kr/user/myinfo",
                {
                    headers: {
                        Authorization: `Bearer ${token}`,
                    },
                }
            );

            console.log(res.data);

            // 기존 정보를 가져온 뒤 prevData에 값을 넣음
            setPrevData({
                username: res.data.user.username,
                accountname: res.data.user.accountname,
                intro: res.data.user.intro,
            });
        } catch (err) {
            console.log(err);
        }
    }, [token]);

    useEffect(() => {
        getPrevData();
    }, [getPrevData]);

    console.log("렌더링!");

    return (
        <>
            <TopBar
                type={"A4"}
                right4Ctrl={{ form: "logined", isDisabled: isDisabled }}
            />
            <ProfileSetInpsTemp formId={"logined"} prev={prevData} />
        </>
    );
}

ProfileSetTemp.jsx

import React, { useRef } from "react";
import axios from "axios";
import ProfileImageSet from "../ProfileImageSet/ProfileImageSet";
import UserInput from "../userinput/UserInput";
import Inp from "../userinput/Inp";
import ProfileSetCont from "./profileSetINpsTemp.style";
import Warning from "../Warning";

export default function ProfileSetTemp({
    formId,
    prev,
    onInValidByUpper,
    onValidByUpper,
    onSubmitByUpper,
}) {
    const accountName = useRef(null);
    const accountId = useRef(null);
    const about = useRef(null);
    const nameAlert = useRef(null);
    const idAlert = useRef(null);
    const submitData = useRef({});

    console.log(prev);
    if (Object.keys(prev).length > 0) {
        accountName.current.value = prev.username;
        accountId.current.value = prev.accountname;
        about.current.value = prev.intro;
    }

    // 이름 인풋창에서 포커스 아웃시 동작하는 함수
    const onNameBlurHandle = (e) => {
        if (
            e.target.validity.patternMismatch ||
            e.target.value.trim().length === 0
        ) {
            // 이름 패턴이 유효하지 않을 경우
            nameAlert.current.style.display = "block";

            onInValidByUpper(); // 제출 버튼 비활성화
        } else {
            // 이름 패턴이 유효할 경우
            nameAlert.current.style.display = "none";

            if (
                // 모든 인풋창에 값이 있고 아이디 경고창이 없을 때(아이디가 유효할 때)
                accountName.current.value &&
                accountId.current.value &&
                idAlert.current.style.display === "none"
            ) {
                onValidByUpper(); // 제출 버튼 활성화
            }
        }
    };

    // 아이디 인풋창에서 포커스 아웃시 동작하는 함수
    const onIdBlurHandle = async (e) => {
        if (e.target.validity.patternMismatch) {
            // 아이디 패턴이 유효하지 않을 경우
            idAlert.current.style.display = "block";

            onInValidByUpper(); // 제출 버튼 비활성화
        } else if (e.target.value === prev.accountname) {
            return;
        } else {
            // 아이디 패턴이 유효할 경우
            console.log(prev.accountname);
            console.log(e.target.value === prev.accountname);
            try {
                // 아이디 중복 검증
                const res = await axios.post(
                    "https://mandarin.api.weniv.co.kr/user/accountnamevalid",
                    {
                        user: {
                            accountname: accountId.current.value,
                        },
                    },
                    {
                        headers: {
                            "Content-type": "application/json",
                        },
                    }
                );

                if (res.data.message === "이미 가입된 계정ID 입니다.") {
                    idAlert.current.textContent = ("*" + res.data.message);
                    idAlert.current.style.display = "block";

                    onInValidByUpper(); // 제출 버튼 비활성화
                } else {
                    // 중복 계정이 아닐 경우
                    idAlert.current.style.display = "none";

                    if (
                        // 모든 인풋에 값이 있고 이름 경고창이 없는 경우(이름이 유효한 경우) 제출 버튼 활성화
                        accountName.current.value &&
                        accountId.current.value &&
                        nameAlert.current.style.display === "none"
                    ) {
                        onValidByUpper();
                    }
                }
            } catch (error) {
                console.log(error);
            }
        }
    };

    // 이미지 파일 변경시 동작하는 함수
    const ImgChangeHandle = async (imgdata) => {
        const formData = new FormData();
        formData.append("image", imgdata);
        try {
            const res = await fetch(
                "https://mandarin.api.weniv.co.kr/image/uploadfile",
                {
                    method: "POST",
                    body: formData,
                }
            );
            const json = await res.json();
            submitData.current["image"] =
                "https://mandarin.api.weniv.co.kr/" + json.filename;
        } catch (err) {
            console.log(err);
        }
    };

    // 폼 제출시 동작하는 함수
    const onSubmitHandle = (e) => {
        e.preventDefault();
        submitData.current["username"] = accountName.current.value;
        submitData.current["accountname"] = accountId.current.value;
        submitData.current["intro"] = about.current.value;
        onSubmitByUpper(submitData);
    };

    return (
        <ProfileSetCont id={formId} onSubmit={onSubmitHandle}>
            <ProfileImageSet onChangeByUpper={ImgChangeHandle} />
            <UserInput inputId={"userName"} label={"사용자 이름"}>
                <Inp
                    className={"inp"}
                    onBlur={onNameBlurHandle}
                    type={"text"}
                    id={"userName"}
                    ref={accountName}
                    placeholder={"2~10자 이내여야 합니다."}
                    minLength={"2"}
                    maxLength={"10"}
                    pattern={"[a-zA-Zㄱ-힣 ]{2,10}"}
                    required
                />
            </UserInput>
            <Warning ref={nameAlert}>* 한글 또는 영어를 2~10자 이내로 입력하세요.</Warning>
            <UserInput inputId={"accountID"} label={"계정 ID"}>
                <Inp
                    className={"inp"}
                    onBlur={onIdBlurHandle}
                    type={"text"}
                    id={"accountID"}
                    ref={accountId}
                    placeholder={
                        "영문, 숫자, 특수문자(.),(_)만 사용 가능합니다."
                    }
                    pattern={"[a-zA-Z0-9._]+"}
                    required
                />
            </UserInput>
            <Warning ref={idAlert}>* 영문, 숫자, 밑줄 및 마침표만 사용할 수 있습니다.</Warning>
            <UserInput inputId={"about"} label={"소개"}>
                <Inp
                    className={"inp"}
                    type={"text"}
                    id={"about"}
                    ref={about}
                    placeholder={"자신과 판매할 상품에 대해 소개해 주세요!"}
                />
            </UserInput>
        </ProfileSetCont>
    );
}