-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[오다은] Week19 #492
The head ref may contain hidden characters: "part4-\uC624\uB2E4\uC740-week19"
[오다은] Week19 #492
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api 주소 변경으로 인해 기존 GET 함수를 조금씩 수정하였고,
POST, PUT, DELETE 요청을 추가 구현하였습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 ! 설명 감사드립니다. 한 번 꼼꼼히 봐볼게염
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모달의 공통 레이아웃입니다.
제목, 닫기 버튼이 포함되어있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
folder/[[...currentFolderId]]
경로의 currentFolderId를 곳곳에서 사용하고 있어서
Context API로 빼서 관리하였습니다.
const handleDeleteButtonClick = (e: MouseEvent) => { | ||
e.preventDefault(); | ||
setIsVisibleDeleteCardModal(true); | ||
setModal({ isOpen: true, content: `DeleteLinkModal ${id}` }); | ||
}; | ||
const handleAddFolderButtonClick = (e: React.MouseEvent) => { | ||
const handleAddLinkButtonClick = (e: MouseEvent) => { | ||
e.preventDefault(); | ||
setIsVisibleAddInFolderModal(true); | ||
setModal({ isOpen: true, content: `AddLinkModal ${id}` }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
카드 내부에서 모달을 띄우니 보여지는 모든 카드의 모달이 중복으로 띄워지는 오류가 있어서 우선 구분할 수 있게 ${id}라는 조건을 추가해 두었습니다.
임시방편으로 해결하여서 추후에 포탈이나 다른 방식으로 모달을 띄우고 싶네욥!
기주님은 모달 띄우실 때 어떤 방법을 사용하시는 편인가요???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 조건을 추가해서 문제를 해결하셨군요 😊
저 또한 다은님께서 해결하신 방법으로 컨텍스트로 만드는 편입니다 !
또한, 라우팅도 함께 필요한 모달의 경우 NextJs 앱라우터의 패러랠과 인터섹션 라우터로 만들고 있습니다 😊
수고 하셨습니다 ! 스프리트 미션 하시느라 정말 수고 많으셨어요. |
모달을 하나의 컴포넌트에서 관리하다가 기능별로 나누었습니다.오호 굳굳 ! 한 번 보도록 할게욤 😊 React Query를 잘 작성하였는지 피드백 부탁드립니다!넵넵 ! 물론입니다 💪 감사합니다!!🙇♂️🙇♂️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 ! 설명 감사드립니다. 한 번 꼼꼼히 봐볼게염
import axios from 'axios'; | ||
|
||
const BASIC_URL = 'https://bootcamp-api.codeit.kr/api'; | ||
const BASIC_URL = 'https://bootcamp-api.codeit.kr/api/linkbrary/v1'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
base URL은 환경 변수에 저장하시는게 좋습니다!
환경 변수(Environment Variable):
process.env
에 내장되며 앱이 실행될 때 적용할 수 있는 값입니다!
다음과 같이 적용할 수 있습니다:
// .env.development
REACT_APP_BASE_URL="http://localhost:3000"
// .env.production
REACT_APP_BASE_URL="http://myapi.com"
// 사용시
<a href={`${process.env.REACT_APP_BASE_URL}/myroute`}>URL</a>
왜 환경 변수에 저장해야 하나요?
개발(dev
), 테스트(test
), 실제 사용(prod
) 등 다양한 환경에서 앱을 운영하게 되는 경우, 각 환경에 따라 다른 base URL을 사용해야 할 수 있습니다. 만약 코드 내에 하드코딩되어 있다면, 각 환경에 맞춰 앱을 배포할 때마다 코드를 변경해야 하며, 이는 매우 번거로운 작업이 됩니다. 하지만, 환경 변수를 .env.production
, .env.development
, .env.test
와 같이 설정해두었다면, 코드에서는 단지 다음과 같이 적용하기만 하면 됩니다.
const apiUrl = `${process.env.REACT_APP_BASE_URL}/api`;
이러한 방식으로 환경 변수를 사용하면, 배포 환경에 따라 쉽게 URL을 변경할 수 있으며, 코드의 가독성과 유지보수성도 개선됩니다.
실제 코드 응용과 관련해서는 다음 한글 아티클을 참고해보세요! => 보러가기
@@ -1,6 +1,7 @@ | |||
import { AddLink, EditFolderName } from '@/interfaces/api'; | |||
import axios from 'axios'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
axios
instance
를 만들어서 사용해보는게 어떨까요 ?
instance
를 만들어서 export
를 하고 사용해보는 것 정도로 시도해보면 좋을 것 같아요. axios-instance
파일을 만들어서 instance
를 생성하고 export
한 후 사용해보는건 어떨까요?
다음과 같이 만들어볼 수 있어요:
const baseURL = process.env.NEXT_PUBLIC_LINKBRARY_BaseURL;
const instance = axios.create({
baseURL: baseURL,
headers: {
'Content-Type': 'application/json',
},
});
export default instance
`${BASIC_URL}${userIdParam}/folders${queryParam}` | ||
); | ||
const result = response.data.data; | ||
const response = await axios.get(`${BASIC_URL}/folders${queryParam}`, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반환 타입을 제네릭을 통하여 다루면 어떨까요?
const response = await axios.get(`${BASIC_URL}/folders${queryParam}`, { | |
const response = await axios.get<GetFoldersResponse>(`${BASIC_URL}/folders${queryParam}`, { |
이렇게 하면 getFolders
의 반환 타입도 추론될 수 있습니다 😊
export async function putFolderName({ | ||
folderId, | ||
newFolderName, | ||
}: EditFolderName) { | ||
const response = await axios.put( | ||
`${BASIC_URL}/folders/${folderId}`, | ||
{ | ||
name: newFolderName, | ||
}, | ||
{ | ||
headers: { | ||
Authorization: localStorage.accessToken, | ||
}, | ||
} | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만약 instance
를 만드셨다면 accessToken
은 인터셉터로 관리하실 수 있습니다 !
아래는 axios
의 메써드인 interceptors
를 통하여 미들웨어로 인가를 처리하는 예제예요 ! 한 번 확인해보시고 적용하시는 것도 고려해보세요 😊😊😊
instance.interceptors.request.use(
(config) => {
const accessToken = localStorage.getItem('accessToken')?.replace(/"/gi, '');
if (!accessToken) return config;
config.headers.Authorization = accessToken;
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
alert(`ERROR: ${error.response.data.message} `);
return Promise.reject(error);
}
);
const modal = useModal(); | ||
const setModal = useSetModal(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만약 useSetModal
과 useModal
이 합쳐진다면 다음과 같이 작성되겠네요 !:
const modal = useModal(); | |
const setModal = useSetModal(); | |
const { modal, setModal } = useModal(); |
const addFolderMutation = useMutation({ | ||
mutationFn: (newFolderName: string) => addFolder(newFolderName), | ||
onSuccess: () => { | ||
queryClient.invalidateQueries({ queryKey: ['folders'] }); | ||
}, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굳굳 ~! 적절한 뮤테이션과 성공 이벤트네요 !:
invalidateQueries
까지 처리해주셨군요 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다만, queryKey
의 경우 한 곳에서 관리할 수도 있습니다:
export const folderKeys = {
getFolders: ['folder'],
getFolderById: (id: string) => ['folder', id]
};
value={text} | ||
onChange={handleTextChange} | ||
/> | ||
<S.StyledButton text='추가하기' type='submit' /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타입까지 작성하셨네요. 꼼꼼하군요 ! 😊
const putFolderNameMutation = useMutation({ | ||
mutationFn: ({ folderId, newFolderName }: EditFolderName) => | ||
putFolderName({ folderId, newFolderName }), | ||
onSuccess: () => { | ||
queryClient.invalidateQueries({ queryKey: ['folders'] }); | ||
queryClient.invalidateQueries({ | ||
queryKey: ['folder', String(currentFolder.id)], | ||
}); | ||
}, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
크으 ~! 깔끔한 mutation
!! 👍👍
const SHARES = [ | ||
{ | ||
name: '카카오톡', | ||
imageSrc: KakaotalkIcon, | ||
onClick: (e: MouseEvent) => { | ||
shareKakao(e, currentFolder.name, currentFolder.id); | ||
}, | ||
}, | ||
{ | ||
name: '페이스북', | ||
imageSrc: facebookIcon, | ||
onClick: () => {}, | ||
}, | ||
{ | ||
name: '링크 복사', | ||
imageSrc: linkIcon, | ||
onClick: () => { | ||
navigator.clipboard | ||
.writeText(`${window.location.hostname}/shared/${currentFolder.id}`) | ||
.then(() => { | ||
alert('링크가 복사되었습니다.'); | ||
}) | ||
.catch((error) => { | ||
console.error('복사 실패:', error); | ||
}); | ||
}, | ||
}, | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
훌륭한 컴포넌트 매핑입니다 ! 👍👍👍
굳굳 !! 훌륭합니다. 다은님 😊 |
요구사항
기본
주요 변경사항
배포링크 : https://linkbrary-oh.vercel.app/
api 주소가 수정되어서 약간의 response 수정이 있습니다.
모달 컴포넌트 분리
모달 컴포넌트 열고 닫는 기능 Context API로 구현
로그인/회원가입 제외 React Query 적용
멘토에게