해당 프로젝트에는 모달창이 자주 사용되는데, Bootstrap에서 제공하는 modal을 사용해도 되지만 이전에 해당 방법으로 modal을 구현했기에 이번에는 직접 만들어보기로 했다.
Modal 컴포넌트

일단 필수적으로 필요한 div는 modal 이외의 배경을 블랙처리해줄 최상위 div(modal-back)와 바로 아래의 modal이 될 div(modal)를 만들어줘야 한다. 그 안의 내용은 선택사항으로 넣어준다.
-
className='modal-back'
최상위 div의 경우에는 style을 사용하여 modal을 나타나게 하는 버튼을 클릭하지 않았다면(false) display:none
을 해준다. modal을 나타나게 하는 버튼을 클릭했다면(true) display: true
를 해서 modal을 화면에 보이게 한다.
또한, 스타일로 배경색 등을 지정해준다.

position은 fixed로 하여 화면을 스크롤해도 modal이 해당 위치에서 고정되어 스크롤한 화면과 같이 이동하도록 한다.
background-color는 rgba로 하여 3번째 값까지는 0을 넣어주고 마지막 값에는 원하는 투명도를 넣어준다. 만약 해당 값을 0을 넣는다면 배경색은 검정색이 된다.
z-index는 999로 설정하여 modal의 위치가 최상위에 위치하도록 한다. (다른 내용들 위에 위치)
width와 height는 100%로 설정하여 블러처리 배경색(검정색)이 화면을 모두 채울 수 있도록 한다.
-
className='modal'
나의 경우에는 닫기(x)버튼, 내용이 들어갈 div, 확인버튼(내용)으로 구성했다.
-
className='modal-close'
닫기 버튼을 클릭하면 modal 창이 닫힌다. 부모로부터 닫힌 상태(true/false)를 넘겨 받아 닫힘 버튼을 동작할 수 있도록 한다.
-
className='modal-body'
내용이 들어갈 div의 경우에는 logo, title, content로 나눠줬다.
logo의 경우에는 프로젝트 modal 디자인의 이미지를 넣어준 것이다.
title과 content의 경우 부모로부터 props로 내용을 넘겨 받아 뿌려준다.
title과 content는 어느 페이지에서 modal을 사용하냐에 따라 값이 달라지기에 props로 하여 부모에서 Modal 컴포넌트를 사용할때 원하는 값을 넣을 수 있도록 했다.
이로 인해, Modal 컴포넌트의 재사용성이 높아졌다. (만약, 이러한 방식으로 하지 않았다면 modal을 사용하는 페이지마다 modal 코드를 계속 적어 넣어야 했을 것이다..)
-
className='modal-footer'
해당 부분에는 버튼들이 들어가는데 버튼의 종류는 두가지이다.
예/아니오 버튼 한쌍과 확인 버튼이다. 내용에 따라 어떤 버튼을 사용하는지가 달라지는데 버튼을 정의해두지 않으면 매번 modal 코드를 가져와서 붙여넣고 변경해줘야 하기 때문에.. 어떻게 하면 건들지 않을 수 있을까 고민하다가 해당 방법을 생각해냈다.
일단 예/아니오 버튼을 묶은 div와 확인 버튼에 style로 삼항연산자를 넣어주는 것이다. 해당 방법은 닫기 버튼에서 사용한 것과 동일하다. 다만, ynBtn
의 값이 true냐 false냐에 따라서 어떤 버튼이 활성화될지 달라진다.
ynBtn
이 true라면 예/아니오 버튼이 활성화(즉, display: block;
)되면서 확인 버튼은 비활성화 (display:none;
)된다. false라면 확인 버튼이 활성화되고 예/아니오 버튼은 비활성화된다.
true값을 넣을지 false값을 넣을 지는 부모에서 Modal 컴포넌트를 사용할 때 설정해준다.
해당방법을 통해서 Modal 컴포넌트의 재사용성이 높아졌다.
부모 컴포넌트(Modal 컴포넌트를 사용할 곳)

Modal 컴포넌트를 열고 닫을 수 있도록 useState로 상태를 관리한다.

Modal 컴포넌트의 속성들은 다음과 같다.
isOpen
: modal을 열게 한다.
closeModal
: modal을 닫게 한다.
ynBtn
: 예/아니오 버튼을 사용할지 확인버튼을 사용할지 결정한다.
title
: title로 넣을 내용을 작성한다. (content와 스타일이 다르다.)
content
: content로 넣을 내용을 작성한다.
구현 화면

modal이 잘 구현된 것을 확인할 수 있다!
마치며
불과 작년까지만 해도 modal 만들다가 실패해서 bootstrap의 modal을 사용했었는데, 이제는 혼자서도 modal을 구현할 수 있을 정도로 성장한게 보여서 뿌듯했다!
또한 modal을 완전히 컴포넌트화 시키기 위해서 정말 고민을 많이 했었다. title과 content, 버튼이 매 페이지마다 다른데 이걸 어떻게 Modal 컴포넌트를 건드리지 않고 사용할 수 있을까 고민을 많이 했다.
그러다가 props를 떠올렸고, 해당 방법은 매우 성공적이었다.
이전에 공부하면서 props 개념을 이해하려 할때 솔직히 완벽히 이해하지 못했다. 어영부영 이해한 느낌? 하지만 이번에 직접 떠올리고 사용해보면서 완전히 props 개념을 이해하고 활용할 수 있게 되었다.
프로젝트를 진행하면서 점점 성장해나가고 있는 나를 볼 수 있어서 뿌듯했다. 앞으로 더 성장해나가자!!
참고
리액트 모달창 만들기 2가지 방법
프로젝트를 진행하면서 나의 깃허브에 잔디가 심어지지 않고 있다는 것을 발견했다.
프로젝트 저장소는 Organization을 만들어서 해당 조직에 레포를 두고 있다. main 브랜치에 커밋하는 것은 나의 깃허브에 잔디가 심어지지만, 따른 브랜치 (나의 경우 soha 브랜치)에 커밋할 경우에는 잔디가 심어지지 않았다.
따라서, 잔디를 심어주기 위해서 해당 로컬 저장소에 두개의 원격 저장소를 연결하기로 했다!
- 개인 깃허브에 새로운 repository 만들기
깃허브 페이지에서 새로운 레포지토리를 만들어주기만 하면 된다.
해당 순서를 진행하기 전에 우리는 기존에 a 로컬 저장소에 b 원격 저장소가 연결되어있다는 가정하에 해당 방법을 진행한다. 만약, 연결되어 있지 않다면, 일단 a 로컬 저장소에 b 원격 저장소를 연결하고 해당 방법을 진행하자.
- 로컬 저장소에 새로운 원격 저장소 연결하기
git remote add <단축이름> git@github.com:<깃허브 아이디>/<1에서 만든 레포지토리 이름>.git
# 예시
git remote add upstream git@github.com:soi-ha/Hello.git
원격 저장소를 연결해줬다면, 두개의 원격 저장소가 연결되었는지 확인한다.

위와 같이 총 2개(4줄)가 떴다면 잘 연결된 것이다.
- 새로운 레포지토리에 내용 넣어주기
다른 분들을 보니 git clone을 사용해서 내용을 넣어주셨지만, 나는 다른 방법을 사용했다.
로컬 저장소에 간단한 변경사항(그냥 주석 추가 정도)을 준 뒤 변경 내용을 원격 저장소로 push 해주었다.
git status
git add .
git commit -m "커밋 내용"
# 기존 원격저장소
git push <단축이름> <브랜치명>
# ex) git push origin soha
# 추가 연결한 원격저장소
git push <단축이름> <브랜치명>
# ex) git push upstream soha
원격 저장소로 push 할 때, 두 저장소 모두 push 명령어를 작성해줘야 한다. 하나만 push 하면, 해당 저장소에만 push 된다.
한 명령어로 두 원격저장소로 push가 가능하지만 (원격 저장소를 추가 연결할 때 upstream이 아닌 origin으로 이름을 하면 된다. 즉, 단축이름을 동일하게 설정해주면 된다는 뜻이다.) 추후에 헷갈릴 것 같아서 단축 이름을 다르게 해줬다.
그리고 push 할 때, 새로 연결한 원격 저장소의 경우에는 브랜치 명이 따로 저장된 것이 없어서 (기본 이름이 main/master 인 것일 뿐, 첫 push 이전까지는 브랜치로 만들어진 것은 아님) 첫 push할 때 명시해준 브랜치 명이 메인 브랜치가 된다.
위의 예시에서 나는 git push upstream soha
를 해줬는데, 메인 브랜치의 이름이 soha가 되었다.
- 결과
이전에 organization의 soha 브랜치에 push한 내용이 잔디가 안 심어졌는데, 해당 연결 이후에 이전에 push 했던 내용들 모두 잔디가 심어졌다.

참고
여러 개의 Git Repository로 소스코드 Push하기
git 여러개 원격 저장소 사용해서 프로젝트 한개에 연결하기
<textarea>
태그에는 기본적으로 우측 하단에 줄 세개가 존재한다. 이번 프로젝트를 진행하면서 디자인에 해당 줄은 들어가지 않기 때문에 줄을 없애는 방법을 서칭했고 찾아냈다.
방법은 매우매우매우 간단했다.
.textarea {
resize: none;
}
해당 속성만 입력해주면 줄이 깔끔하게 사라지는 것을 볼 수 있다.
-
resize:none;
적용 전

-
resize:none;
적용 후

프로젝트를 통해 새로운 것을 또 알아간다..!
참고
(즐거웅코드) CSS textarea 우측 하단 세줄 표시 없애기
이번 프로젝트에서 프로필 관리의 기능 중에 프로필 이미지를 입력하면 해당 이미지가 어떻게 적용되는지 미리보는 기능이 필요했다. 해당 기능은 글 가장 하단의 블로그 내용을 참고하여 구현하였다.
일단 이미지 파일을 받기 위해서 input 태그의 type을 file로 하여 이미지를 받는다.
이때, label도 같이 작성해주는데 input 태그의 기본 스타일을 안보이게 하고 내가 원하는 스타일의 버튼을 label에 적용해주기 위해서이다. 그렇게에 label의 htmlFor의 값과 input의 id 값을 동일하게 작성해줘야 한다. 그래야 label을 클릭했을 때, input 태그를 누르는 것과 동일한 액션을 취한다.
<label htmlFor="mypage-profile-img">
프로필 수정
</label>
<input
type="file"
accept="image/*"
id="mypage-profile-img"
input 태그의 속성으로 accept을 작성해준다. accept 속성은 input 태그의 type이 file인 경우에만 작성이 가능하며, 해당 속성은 파일의 타입을 명시한다.
위의 코드에서 작성한 accept의 값인 image/*
는 모든 타입의 이미지 파일의 업로드가 허용됨을 뜻한다.
스타일 수정
input 태그를 보이지 않게 하고, label에 원하는 스타일을 적용시킨다.
input 태그에 display:none;
를 작성하여 보이지 않게 하였다.
-
프로필 관리 (이미지 업로드 전)

미리보기 기능 구현
saveImgFile
함수는 FileReader API를 사용하여 이미지를 업로드하는 input 태그의 onChange 이벤트로 넣을 함수이다.
이때, readAsDataURL
은 파일을 URL로 만들어준다(FileReader API).
FileReader API?
웹 API로, 비동기적으로 데이터를 읽기 위하여 File(읽을 파일을 가리킴) 혹은 Blob 객체를 이용해 파일의 내용을 읽고 사용자의 컴퓨터에 저장하는 것을 가능하게 함
즉, 클라이언트단에서 file이랑 blob 사용할 수 있게 해주는 것
onChange?
input 태그의 포커스에서 벗어났을 때 (즉, 입력이 종료했을 때) 발생하는 이벤트
-
saveImgFile
imgRef에서 가장 최근의 파일을 불러와 file에 저장
reader은 FileReader 객체 생성
reader에 readAsDataURL 함수를 통해 file을 URL로 반환(즉, 해당 이미지 파일의 링크가 생성됨)
읽기 성공, 실패 여부 상관없이 동작이 끝났을때 마다(reader.onloadend), 파일의 내용을 반환(reader.result)
import { useState, useRef } from 'react';
import defaultImg from 'default 이미지 경로';
const [imgFile, setImgFile] = useState('');
const imgRef = useRef();
...
// 이미지 업로드 input 태그에 넣을 onChange
const saveImgFile = () => {
const file = imgRef.current.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
setImgFile(reader.result);
};
};
...
// 이미지 미리보기
<img
src={imgFile ? imgFile : defaultImg}
alt="프로필 이미지"
/>
...
// 이미지 업로드
<label htmlFor="mypage-profile-img">
프로필 수정
</label>
<input
type="file"
accept="image/*"
id="mypage-profile-img"
onChange="{saveImgFile}"
ref="{imgRef}"
/>
이미지 미리보기
img 태그의 src 속성 값으로 삼항연산자를 사용했다. imgFile의 값이 true일 경우 imgFile
을 src 속성의 값으로 한다. 그러나 false일 경우에는 defaultImg
를 src 속성의 값으로 한다.
이 말인 즉, 이미지를 업로드 하면 해당 이미지를 보여주고 이미지를 업로드 하지 않았다면 기본 이미지(defaultImg)를 보여준다는 뜻이다.
이미지 업로드
input 태그에 위의 코드에 새로운 속성을 더 추가를 해준다. onChange 속성을 추가하여 input 태그의 입력이 종료했을 경우 saveImgFile
함수를 실행한다. ref 속성의 값은 imgRef
를 작성한다.
-
이미지 업로드

-
이미지 미리보기 구현 완료

마치며
다른 분의 코드를 참고하여 구현하긴 했지만, 정상적으로 기능이 작동하도록 만들어서 뿌듯했다.
구현을 완료하고 이미지가 이쁘게 잘 들어가는 모습을 보니 매우 기쁘달까..! 이렇게 새로운 것을 알게되어서 재밌다.
개발 공부는 힘들고 어렵지만 완벽히 습득을 하고 나면 정말 무궁무진하게 활용할 수 있어서 질리지 않고 재밌는 것 같다.
참고
React : 이미지 업로드하고 미리보기
FileReader로 브라우저에서 file 다루기
파일API : FileReader() 객체
이번에도 어김없이 프로젝트를 진행하며 pull을 하려던 중, 다음과 같은 에러(?)가 발생했다…
저번에 작성했던 글의 방식대로 시도해봤으나 실패..^^..
그래서 열심히 서칭한 끝에 찾아낸 방법은 다음과 같다.
git reset –hard [원격저장소]
pull을 하려는 브랜치로 이동한 뒤, 다음과 같이 명령어를 작성한다.
git fetch --all
git reset --hard [원격저장소]
# git reset --hard origin/main
해당 명령어는, 모든 원격 저장소를 fetch한 후, –hard 옵션을 주어 원격저장소의 내용으로 reset 시키는 것이다.
즉, main을 origin/main의 내용으로 강제 리셋시키는 것이다.

해당 방법을 사용하면 웬만한 pull 에러는 다 해결할 수 있다. 지난번의 에러도 해당 방법으로 해결이 가능하다.
단, 문제점은 로컬 저장소의 내용이 날아갈 수 있다..^^.. (갈 수 있는게 아니라 날아간다.)
나의 main 브랜치의 경우에는 로컬 main에 작성하는 내용이 없어서 원격 저장소의 main 내용을 reset을 통해 로컬 저장소로 가져와도 됐다. (main 브랜치는 각자 개발한 내용을 모으는 브랜치로, 따로 로컬에서 작성하는 내용이 없음)
그러니 해당 방법을 사용할 경우, 로컬의 내용이 날아가도 되거나… 어딘가에 따로 저장을 해두고 하자.. (아님 다른 방법을 찾자..)
참고
해결방법 참고 블로그
Git도구(head,index,워킹디렉토리)와 reset