84일차 내용 정리

채공부·2025년 9월 22일

유효성 검사 추가

  • UserPwdUpdateForm.jsx 에 disabled 속성 추가
    • password 가 유효하지 않거나 newPassword 가 유효하지 않으면 disabled 를 true 로 설정
    • 즉, 둘중에 하나라도 값이 false 이면, false 인 상태로 되는 것
      = 버튼 비활성화
<button disabled={!valid.password || !valid.newPassword} className="btn btn-sm btn-success" type="submit">수정하기</button>
  • UserPwdUpdateForm.jsx 에 setState 함수 추가
    • 폼 입력란을 한번이라도 입력했는지 여부 (더럽혀졌는지 여부)를 상태값으로 관리
    • 처음 페이지 로드 시 모든 필터가 빨간색으로 표시되는 것을 방지
const [dirty, setdDirty] = setState({
	password:false,
	newPassword:false
})
  • 비밀번호 input 요소에 classnames 추가
    • classnames : 클래스를 편하게 제어하기 위한 플러그인
    • 처음에는 깨끗하게 나오고 , dirty 값으로 비밀번호가 한 번이라도 입력해서 더렵혀지면 다 지우더라도 추가할 수 있도록 한다
import cn from 'classnames';
<input onChange={handleChange} value={state.password} className={cn("form-control", {"is-valid":valid.password, "is-invalid":!valid.password && dirty.password})} type="password" name="password" id="password"/>

<input onChange={handleChange} value={state.newPassword} className={cn("form-control", {"is-valid":valid.newPassword, "is-invalid":!valid.newPassword && dirty.newPassword})} type="password" name="newPassword" id="newPassword" />

➜ 비밀번호 수정 양식은 초기 상태에는 깨끗하지만 한번이라도 입력하면 수정하기 버튼은 비활성화, 모든 조건이 true 일 경우에만 수정 버튼 활성화

  • handleChange 함수 추가
    • Change 이벤트 발생 시 입력란의 dirty 값을 true 로 변경
const handleChange = (e) => {
	setDirty({
		...dirty,
		[e.target.name]:true
	})
}
  • useEffect 함수 안에 특수문자가 있는지 여부를 검증하는 조건 추가
    • 유효성 여부를 바로 state 에 반영
setValid({
	password:state.password.length >= 1,
	newPassword:state.newPassword === state.newPassword2 && reg.test(state.newPassword)
});
  • div 요소에 form-text 입력 조건 표시 추가
<div className="form-text">특수문자를 반드시 포함하고 아래의 확인란과 동일하게 입력하세요</div>
  • 정규표현식 reg 상수 추가
    • 특수문자가 있는지 여부를 검증할 수 있는 정규표현식
const reg = /[\w]/;

비밀번호 수정

  • UseController 에 passwordUpdate 메소드 생성
    • 요청 처리 후에 특별히 응답할 내용이 없을 때 204 응답을 하면 된다
    • ResponseEntity<Void> : HTTP 응답을 표현하는 객체
    • Void : 본문(body)이 없는 응답 ➜ 204 응답
@PatchMapping("/user/password")
public ResponseEntity<Void> passwordUpdate(@RequestBody PwdChangeRequest request) {
	//로그인 된 userName
	String userName = SecurityContextHolder.getContext().getAuthentication().getName();
	request.setUserName(userName);		
	userService.updatePassword(request);		
	// 빈 content 와 함께 204 응답이 된다
	return ResponseEntity.noContent().build();
}
  • UserServiceImpl 에 updatePassword 메소드 수정
    • ExceptionController 사용하지 않고 간단하게 에러 상태 코드를 지정하여 처리 가능
      ➜ 에러 처리 안돼서 밑에서 ExceptionController 생성 후 다시 처리
    • DB에 저장된 암호화된 비밀번호 읽어온다
      만약 기존 비밀번호랑 일치 하지 않으면, 에러 응답 400번
@Override
public void updatePassword(PwdChangeRequest pcr) {
	UserDto dto=dao.getByUserName(pcr.getUserName());
	String encodedPwd=dto.getPassword();
	boolean isVaild=BCrypt.checkpw(pcr.getPassword(), encodedPwd);
	if(!isVaild) {
		throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "기존 비밀번호가 일치하지 않아요!");
	}
	dto.setPassword(encoder.encode(pcr.getNewPassword()));
	dao.updatePassword(dto);
}
  • handleSubmit 함수 안에 try-catch 문 & await, api 코드 추가
    • 비동기 방식으로 처리
try{
	const res = await api.patch("/v1/user/password", state);
	//정상 응답(200) 이면 여기가 실행되고
	console.log(res);
	alert("비밀번호 수정 완료");
}catch(err){
	//정상 응답이 아니면 catch 블럭이 실행된다.
	console.log(err);
};

spring10.exception 패키지 생성 후 spring09 에서 ExceptionController 복사해 붙여넣기

  • ExcetionController 클래스
@Hidden // swagger ui 에서 무시하도록 @Hidden
@RestControllerAdvice
public class ExceptionController {
	
	// @Valid 어노테이션을 이용해서 검증을 하다가 검증을 통과하지 못하면 여기가 실행된다
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleValidation(MethodArgumentNotValidException ex) {
    		// dto 에 필드 검증에 작성한 메세지 정보가 에러 정보에 담겨서 응답되도록 한다
        var errors = ex.getBindingResult().getFieldErrors().stream()
                .collect(Collectors.toMap(
                        FieldError::getField,
                        DefaultMessageSourceResolvable::getDefaultMessage,
                        (a, b) -> a,
                        LinkedHashMap::new
                ));
        Map<String, Object> body = Map.of(
                "code", "VALIDATION_ERROR",
                "errors", errors
        );
        return ResponseEntity.badRequest().body(body);
    }
}
  • exception 패키지 안에 PasswordException 클래스 생성
    • 기존 비밀번호가 일치하지 않았을 때 throw 할 예외 클래스
public class PasswordException extends RuntimeException{
	public PasswordException(String msg) {
		super(msg);
	}
}
  • UserServiceImple 에 updatePassword 메소드 수정
@Override
public void updatePassword(PwdChangeRequest pcr) {
	UserDto dto = dao.getByUserName(pcr.getUserName());
	String encodedPwd = dto.getPassword();
	boolean isValid=BCrypt.checkpw(pcr.getPassword(), encodedPwd);
	if(!isValid) {
		throw new PasswordException("기존 비밀번호가 일치하지 않습니다!");
	}
	dto.setPassword(encoder.encode(pcr.getNewPassword()));
	dao.updatePassword(dto);
}
  • ExceptionController 에 passwordException 메소드 생성
@ExceptionHandler(PasswordException.class)
public ResponseEntity<String> passwordException(PasswordException pe){
	// 예외 객체로부터 메세지 얻어내기
	String msg = pe.getMessage(); 
	// 400번 에러를 발생시키면서 예외 메세지를 응답
	return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(msg);
}
@ExceptionHandler(PasswordException.class)
public ResponseEntity<String> asswordException(PasswordException pe){
	// 예외 객체로부터 메세지 얻어내기
	String msg = pe.getMessage(); 
	// 400번 에러를 발생시키면서 예외 메세지를 응답
	return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(msg);
}

에러메세지

  • UserPwdUpdateForm.jsx 에 useState 함수 추가
    • 에러메세지 상태값으로 관리
    • 에러 발생시 errorMsg 상태값에 에러 메세지 담기
const [errorMsg, setErrorMsg] = useState(null);
  • handleSubmit 코드 수정 및 try-catch 문 & async, await 코드 추가
    • 폼 제출 시마다 에러 메세지 초기화
    • 기존 비밀번호 수정 후 폼 제출하면 에러 메세지 초기화 필요
const handleSubmit = async (e)=>{
	e.preventDefault();
	// 폼 제출시 에러메세지 초기화
	setErrorMsg(null);
    try{
		const res = await api.patch("/v1/user/password", state);
		// 정상 응답(200) 이면 여기가 실행되고
        console.log(res);
        alert("비밀번호를 수정했습니다");
    } catch(err){
        // 정상 응답이 아니면 catch 블럭이 실행된다
        console.log(err);
        // 에러 메세지를 상태값에 넣어준다
        setErrorMsg(err.response.data);
    };   
};
  • 에러 메시지를 출력할 alert 코드 추가
{errorMsg && <p className="alert alert-danger">{errorMsg}</p>}

수정 후 재로그인

  • useDispatch 함수 추가
const dispatch = useDispatch();
  • handleSubmit 함수 try 문 안에 dispatch 코드 추가
    • dispatch 로 로그인 Modal action 발행 해서 Modal창 띄우기
    • 로그인 후에는 회원상세 페이지로 이동
delete localStorage.token;
dispatch({
	type:"LOGIN_MODAL",
	payload:{title:"수정된 비밀번호로 다시 로그인 하세요", show:true, url:"/user"}
});

⚠️ Eclipes에 코드 수정 후 저장할 때마다 에러 발생

  • 예상 원인 : MyBatis 를 프로젝트 중간에 추가해 발생할 에러 가능성

  • application.properties 에 mybatis 관련 코드 2줄 주석 처리

# mybatis.mapper-locations=classpath:mapper/*.xml
# mybatis.type-aliases-package=com.example.spring10.**
  • MyBatisConfig 클래스 생성하여 수동 처리
@Configuration
public class MyBatisConfig {

	@Bean
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
		SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
		factory.setDataSource(dataSource);

		// XML 매퍼들 등록 (classpath* = 의존 JAR까지 포함한 전체 클래스패스 탐색)
		PathMatchingResourcePatternResolver resolver =
              new PathMatchingResourcePatternResolver();
		factory.setMapperLocations(resolver.getResources("classpath:mapper/**/*.xml"));

		// 선택 : 타입 별칭
		factory.setTypeAliasesPackage("com.example.spring10.dto");

		return factory.getObject();
	}
}

회원가입

  • BsNavBar.jsx 버튼에 user 회원가입 경로 작성
<Button onClick = {() => {
    navigate("/user/new")
}} size="sm" variant="primary" className="ms-1">회원가입</Button>
  • pages 폴더 안에 UserForm.jsx 생성
function UserForm() {
	return <>
      
    </>
}
  • index.jsx 에 UserForm 라우트 경로 추가
    • 로그인 없이도 접근가능해야 하므로 <ProtectedRoute> 적용X
{path:"/user/new", element:<UserForm/>}
  • UserForm.jsx 에 spring08 의 /signup-form.html 파일의 form 태그 내용을 복사해 붙여넣기
function UserForm() {
	return <>
		<h1>회원 가입 양식</h1>
		<form action="" method="post" novalidate>
			<div className="mb-2">
				<label className="form-label" htmlFor="userName">아이디</label>
				<input className="form-control" type="text" name="userName" id="userName"/>
				<small className="form-text">영문자 소문자로 시작하고 5글자~10글자 이내로 입력하세요</small>
				<div className="valid-feedback">사용 가능한 아이디 입니다.</div>
				<div className="invalid-feedback">사용할 수 없는 아이디 입니다.</div>
			</div>
			<div className="mb-2">
				<label className="form-label" htmlFor="pwd">비밀번호</label>
				<input className="form-control" type="password" name="pwd" id="pwd"/>
				<small className="form-text">특수 문자를 하나 이상 조합하세요.</small>
				<div className="invalid-feedback">비밀 번호를 확인 하세요</div>
			</div>
			<div className="mb-2">
				<label className="form-label" htmlFor="pwd2">비밀번호 확인</label>
				<input className="form-control" type="password" name="pwd2"  id="pwd2"/>
			</div>
			<div className="mb-2">
				<label className="form-label" htmlFor="email">이메일</label>
				<input className="form-control" type="email" name="email" id="email"/>
				<div className="invalid-feedback">이메일 형식에 맞게 입력하세요.</div>
			</div>
			<button className="btn btn-primary btn-sm" type="submit">가입</button>
		</form>
	</>
}
  • state 에 대한useState 함수 추가
    • 폼에 입력한 내용을 상태값으로 관리
const [state, setState] = useState({
	userName:"",
	pwd:"",
    pwd2:"",
    email:""
});
  • valid 에 대한 useState 함수 추가
    • 폼 입력란의 유효성 여부 상태값 관리
const [valid, setValid] = useState({
	userName:false,
	pwd:false,
	email:false
});
  • dirty 에 대한 useState 함수 추가
    • 폼 입력란 dirty 값 관리
const [dirty, setDirty]=useState({
	userName:false,
	pwd:false,
	email:false
});
  • useEffect 함수 추가
    • state 가 변경되었을때 실행할 함수
useEffect(()=>{

}, [state]);
  • handleChange 함수 추가
const handleChange = (e)=>{
	setState({
		...state,
		[e.target.name]:e.target.value
	});
};

유효성 검증

  • UserForm.jsx 에 정규표현식 코드 추가
    • input 요소의 유효성 검증을 위한 정규표현식
// userName 을 검증할 정규표현식
const regUserName = /^[a-z].{4,9}$/;
// 비밀번호 정규 표현식
const regPwd = /[\W]/;
// 이메일 정규 표현식
const regEmail = /@/;
  • useEffect 함수 안에 setValid 코드 추가
useEffect(()=>{
    setValid({
        userName:regUserName.test(state.userName),
        pwd: regPwd.test(state.pwd) && state.pwd === state.pwd2,
        email: regEmail.test(state.email)
    });
}, [state]);
  • handleChange 함수 안에 setDirty 코드 추가
    • Change 이벤트 발생 시 해당 필드의 dirty 상태를 true 로 변경
setDirty({
	...state,
	[e.target.name]:true
});
  • input 요소에 onChange 이벤트 추가 및 classnames 로 유효성 여부 클래스명 설정
<input onChange={handleChange} className={cn("form-control", {"is-valid":valid.userName, "is-invalid":!valid.userName && dirty.userName})} />
<input onChange={handleChange} className={cn("form-control", {"is-valid":valid.pwd, "is-invalid":!valid.pwd && dirty.pwd})} />
<input onChange={handleChange} className={cn("form-control", {"is-valid":valid.email, "is-invalid":!valid.email && dirty.email})} type="email" name="email" id="email"/>
  • 버튼에 disabled 속성 추가
    • 하나라도 유효하지 않으면 submit 버튼 비활성화
<button disabled={!valid.userName || !valid.password || !valid.email}
        className="btn btn-primary btn-sm" type="submit">가입</button>
  • form 요소에 action 코드 추가
<form action="/v1/user" method="post" noValidate>
  • UserDto 에 pwd 필드명 password로 수정
    • UserDto 필드명과 input 요소의 name 불일치 하므로 변경
private String password

예외 처리

  • UserController 에 signup 메소드 생성
    • 회원가입 요청 처리
@PostMapping("/user")
public ResponseEntity<Void> signup(@RequestBody UserDto dto){
	userService.createUser(dto);
	return ResponseEntity.noContent().build();
}
  • UserServiceImpl 에 createUser 메소드에 코드 추가
    • 사용자가 입력한 userName 으로 select 되는 값이 있는지 읽어와 본다
UserDto result = dao.getByUserName(dto.getUserName());
if(result != null) {
			
}
  • UserNameException 클래스 생성
    • 커스텀 예외 클래스 정의 생성 후 UserServiceImpl 에서 예외 발행처리
    • 이미 있는 아이디일 경우 UserNameException 로 메세지 반환
public class UserNameException extends RuntimeException{
	
	public UserNameException(String msg) {
		super(msg);
	}
}
  • UserServiceImpl 에 createUser 메소드에 if문 안에 throw 추가
throw new UserNameException("이미 사용중인 아이디 입니다");	
  • ExceptionController 에 userNameException 메소드 생성
    • 예외 발생 시 예외 메시지 반환하여 클라이언트에 응답
@ExceptionHandler(UserNameException.class)
public ResponseEntity<String> userNameException(UserNameException une){
	return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(une.getMessage());
}

예외 메시지 출력

  • UserForm.jsx 에 useState 함수 추가
    • 에러 메세지를 상태값으로 관리
const [errorMsg, setErrorMsg] = useState(null);
    • 폼 제출에서 예외 발생 ➜ 예외 메세지 상태값 변경
const handleSubmit = async (e)=>{
  	e.preventDefault();
    try{
 
    }catch(err){
        console.log(err);
        setErrorMsg(err.response.data);
    }
}
  • 에러 메시지를 출력할 alert 코드 추가
{errorMsg && <Alert variant="danger">{errorMsg}</Alert> }
  • handleSubmit 함수 안에 setErrorMsg 추가
    • 에러 메세지 초기화
setErrorMsg(null);

회원가입 후 로그인 처리

  • UserForm.jsx 에 useDispatch 함수 추가
    • 로그인 모달 창 띄우고 폼을 다시 제출 시 에러 메시지 초기화
    • 입력값 유효성 하지 않으면 제출 버튼 비활성화
const dispatch = useDispatch();
  • handleSubmit 함수에 try 문 안에 await & dispatch 코드 추가
await api.post("/v1/user", state);
// 로그인 창을 띄워준다 
dispatch({
type:"LOGIN_MODAL",
payload:{title:"가입한 아이디로 로그인을 하세요", show:true, url:"/user"}
})
  • 가입 버튼에 onSubmit 이벤트 추가
<button onSubmit={handleSubmit} disabled={!valid.userName || !valid.password || !valid.email} />        

회원 정보 수정

  • UserDetail.jsx 에 회원 정보 수정 링크 추가
<NavLink to="/user/edit">가입정보 수정</NavLink>
  • pages 폴더 안에 UserUpdateForm.jsx 생성
function UserUpdateForm() {
    return <>
        <h1>회원정보 수정 양식</h1> 
    </>
}
  • index.jsx 에 UserUpdateForm 라우트 경로 추가
{path:"/user/edit", element:<ProtectedRoute><UserUpdateForm/></ProtectedRoute>}
  • UserUpdateForm.jsx 에 useState 함수 추가
const [dto, setDto] =useState({
    userName:"",
    email:"",
    profileImage:""
});
  • useEffect 함수 추가
    • 가입 정보를 api 요청을 통해서 받아온다
useEffect(()=>{
    api.get("/v1/user")
    .then(res=>setDto(res.data))
    .catch(err=>console.log(err));
},[]);
  • spring08 프로젝트에 user 폴더 안 edit.html 에서 form 요소를 복사해 붙여넣기
<h1>회원정보 수정 양식</h1>
<form action="" method="post" 
      enctype="multipart/form-data" className="p-4 border rounded shadow-sm bg-light">

    {/* 아이디 */}
    <div className="mb-3">
        <label for="userName" className="form-label">아이디</label>
        <input type="text" className="form-control" id="userName" name="userName" readonly />
    </div>

    {/* 이메일 */}
    <div className="mb-3">
        <label for="email" className="form-label">이메일</label>
        <input type="email" className="form-control" id="email" name="email" placeholder="이메일을 입력하세요" />
    </div>

    {/* 프로필 이미지 */}
    <div className="mb-3">
        <label className="form-label d-block">프로필 이미지</label>
        <div className="text-center mb-2">
            <a href="javascript:" id="profileLink">
                {/* 기본 아이콘 */}
                <i className="bi bi-person-circle text-secondary" 
                   style="font-size:100px;"></i>

                {/* 업로드 된 이미지 */}
                <img className="rounded-circle border"
                     style="width:100px; height:100px; object-fit:cover;" />
            </a>
        </div>
        <input type="file" className="form-control d-none" 
               name="profileFile" accept="image/*" />
        <div className="form-text">클릭해서 프로필 이미지를 변경하세요</div>
    </div>

    {/* 버튼 영역 */}
    <div className="d-flex justify-content-between">
        <button type="submit" className="btn btn-primary">수정</button>
        <button type="reset" className="btn btn-outline-secondary">취소</button>
    </div>
</form>
  • input 요소에 value 추가
<input type="text" className="form-control" id="userName"
  		value={dto.userName} readonly />
<input type="email" className="form-control" id="email" name="email"
		value={dto.email} placeholder="이메일을 입력하세요" />
  • 프로필 사진 표시에 if 조건 추가
    • && 연산자로 처리
{ !dto.profileImage && <i className="bi bi-person-circle text-secondary" 
	   style={{fontSize:"100px"}}></i>}

{ dto.profileImage && <img src={`/upload/${dto.profileImage}`}
     className="rounded-circle border"
     style={{width:"100px",height:"100px",objectFit:"cover"}} /> }

⚠️ STYLE 문법 주의

// HTML: style="font-size: 100px"
// React: style={{fontSize: "100px"}}
// - 객체 형태로 전달
// - CSS 속성명은 camelCase
  • UserUpdateForm.jsx 에 useRef 함수 추가
    • input type = "file" 요소의 참조값을 얻어내기 위해
const inputRef = useRef();
  • handleClick 함수 추가
    • 이미지 링크를 클릭했을때 실행할 함수
const handleClick = () =>{
	// input type="file" 을 강제 클릭한다
	inputRef.current.click();
};

➜ 프로필 요소의 강제 클릭을 위해 input type="file" 요소의 참조값 필요

  • a 요소에 onClick 이벤트 추가
<a href="javascript:" id="profileLink" onClick={handleClick}>
    { !dto.profileImage && <i className="bi bi-person-circle text-secondary" 
		style={{fontSize:"100px"}}></i>}
	      
	{ dto.profileImage && <img src={`/upload/${dto.profileImage}`}
		className="rounded-circle border"
		style={{width:"100px",height:"100px",objectFit:"cover"}} /> }
</a>
  • handleClick 함수 안에 inputRef 코드 추가
    • input type="file" 을 강제 클릭한다
inputRef.current.click();
  • input 요소에 ref 추가
<input ref={inputRef} type="file" className="form-control d-none" name="profileFile" accept="image/*"/>
  • handleImageChange 함수 추가
    • 파일을 변경했을때
    • 파일 선택 ➜ 출력 사진 변경
const handleImageChange = (e) => {
    // 선택한 파일 객체
    const file = e.target.files[0]
};

➜ 파일 업로드 때문에 json 으로 보낼 수 없어 new FormData(e.target) 로 전송예정이므로 상태값으로 관리할 필요 없다

⭐ src 의 경로 표현에 2가지 방식

  1. 일반 이미지 파일 경로
<img src="/upload/xxx.jpg" />
  1. Data URL 방식 (Base64 인코딩)
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA..." />

➜ person 이미지 폰트 인코딩 해서 ‘dataurl:xxdfdd’ 형태로 변환 가능

  • UserUpdateForm.jsx 에 사람 모양 svg 요소 추가 (vector 이미지, 폰트)
    • <i className="bi bi-person-circle text-secondary"></i>
      와 같은 이미지
<svg ref={personRef} style={personStyle} xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
    <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
    <path fillRule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
</svg>
  • useRef 함수 추가
const personRef = useRef();
  • personStyle 상수 추가
    • svg 인물 이미지 참조값 얻고 스타일 지정해서 숨겨두기
const personStyle = {
    width:"100px",
    height:"100px",
    display:"none"
}
  • 이미지 관련 useState 함수 추가
    • 이미지 데이터를 상태값으로 관리
const [imageUrl, setImageUrl] = useState({
    original:"",
    current:""  
});
  • useEffect 함수 추가
    • 등록된 이미지 있으면 해당 이미지 url
      없으면 personRef 인코딩한 url 을 original, current 에 담기
    • SVG를 Base64 로 인코딩하여 이미지 src 로 사용 가능
useEffect(() => {
    api.get("/v1/user")
    .then(res => {
        setDto(res.data);
        // 만일 등록된 프로필 이미지가 있다면
        if (res.data.profileImage) {
            const url = `/upload/${res.data.profileImage}`;
            setImageUrl({ original: url, current: url });
        } else { // 없으면 svg 를 디코딩한 data url 을 넣어준다.   
            // svg 이미지를 2 진 데이터 문자열로 읽어들여서 
            const svgString = new XMLSerializer().serializeToString(personRef.current);
            // 2진데이터 문자열을 btoa (binary to ascii) 함수를 이용해서 ascii 코드로 변경
            const encodedData = btoa(svgString);
            // 변경된 ascii 코드를 이용해서 dataUrl 을 구성한다 
            const url = "data:image/svg+xml;base64," + encodedData;
            setImageUrl({ original: url, current: url });
        }
    })
    .catch(err => console.log(err));
}, []);
  • a 요소에 이미지 상태값 관리를 imageUrl 코드로 수정
    • imageUrl.current : 등록(수정)된 사진 없으면 인물 아이콘
      ➜ 존재한다면 해당 사진
<a href="javascript:" onClick={handleClick}>
    {imageUrl.current && <img src={imageUrl.current}
        className="rounded-circle border"
        style={{ width: "100px", height: "100px", objectFit: "cover" }} />}
</a>
  • handleImageChange 함수 안에 파일 관련 코드 추가
    • e.target.files[0] 로 선택된 파일 객체 얻고 FileReader 로 파일을 Data URL 로 변환
    • 상태값 current 에 담아 이미지 미리보기 업데이트
//파일을 변경했을때
const handleImageChange = (e)=>{
    // 선택한 파일 객체
    const file=e.target.files[0];
    // FileReader 객체로 읽어들이기
    const reader=new FileReader();
    reader.readAsDataURL(file);
    reader.onload=(event)=>{
        // 읽은 데이터
        const data=event.target.result;
        setImageUrl({
            ...imageUrl,
            current:data
        });
    }
};
  • handleReset 함수 추가
    • 취소 버튼을 눌렀을 때
const handleReset = ()=>{
    setImageUrl({
        ...imageUrl,
        current:imageUrl.original //최초의 이미지를 출력할 수 있도록
    })
}
  • 취소 버튼에 onClick 이벤트 추가
    • 취소 버튼을 누르면 imageUrl.original 출력
<button onClick={handleReset} type="reset" className="btn btn-outline-secondary">취소</button>
  • handleSubmit 함수 추가
const handleSubmit = async (e)=>{
    e.preventDefault();
    // 폼에 입력한 내용을 이용해서 FormData 객체를 생성한다
    const formData=new FormData(e.target);
    try{
        const res = await api.patch("/v1/user", formData, {
            headers:{"Content-Type":"multipart/form-data"}
        });
        // 나머지 처리는 다음시간에
    } catch(err){
        console.log(err);
    }
};
  • form 요소에 onSubmit 이벤트 추가 및 action 속성 경로 수정
<form onSubmit={handleSubmit} action="/v1/user">
profile
학원 공부 내용 정리

0개의 댓글