- YouTube
www.youtube.com
Axios, node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트. 동일한 코드베이스로 브라우저와 node.js에서 실행할 수 있음. 서버 사이드에서는 네이티브 node.js의 http 모듈을 사용하고, 클라이언트(브라우저)에서는 XMLHttpRequests를 사용
JavaScript fetch는 왜 await를 두 번 할까
fetch 함수를 쓸 때 왜 await를 두 번 써야 할까요?
간단히 설명하면, 서버와 통신할 때 데이터가 두 단계로 나눠져서 오기 때문입니다.
첫 번째 await는 서버에서 응답이 올 때까지 기다립니다. 이때 HTTP 헤더(응답의 기본 정보)를 받게 됩니다. 이 때 서버가 에러를 보내도 이 단계는 정상적으로 완료됩니다.
두 번째 await는 실제 데이터(response body)를 JSON으로 변환하는 과정입니다. 서버에서 받은 데이터를 우리가 JavaScript에서 쓸 수 있는 형태로 바꿔 줍니다.
이렇게 나눠진 구조 덕분에 큰 데이터를 받을 때도 효율적으로 처리할 수 있으며, 데이터를 다 받기 전에도 상태 코드만 보고 에러를 확인할 수 있다는 장점도 있습니다.
실무에서는 편의성 때문에 axios를 더 많이 쓰긴 하지만, 종종 fetch를 써야할 상황에서는 이런 지식들을 알아두시면 도움이 될 것 같네요.
https://www.youtube.com/watch?v=Ki64Cnyf_cA
Vitest 짧막 팁 2가지
1. Vitest의 vi.mock
은 ESModule import를 넣을 수 있다.
장점: IDE에서 import 경로를 추적할 수 있어서 경로를 업데이트 할 때 같이 바꿔주기 때문에 실수를 줄여줍니다.
- jest.mock('axiost')
+ vi.mock(import('axios'))
2. Vitest의 vi.mock
은 두 번째 인자로 부분 모킹이 가능하다.
장점: 모듈에 타입 지원이 됩니다.
- jest.mock('ant-design', () => ({
- ...jest.requireActual('ant-design'),
- Typography: jest.fn().mockImplementation(({ children }) => <div>{children}</div>),
- }));
+ vi.mock(import('ant-design'), async (importOriginal) => ({
+ ...(await importOriginal()), // ((parameter) importOriginal: <typeof import("ant-design")>() => Promise<typeof import("ant-design")>)
+ Typography: vi.fn().mockImplementation(({ children }) => <div>{children}</div>),
+ }))
https://vitest.dev/api/vi.html#vi-mock
[240112] 모두의연구소가 전하는 “모두를 위한 SW/AI 뉴스”
모두의연구소는 지식을 나누며 함께 성장하는 국내 최대 AI 커뮤니티입니다.
파이썬에서 Excel을 읽는 가장 빠른 방법
엑셀은 데이터를 저장하고, 다루고, 전달하는 가장 일반적인 방법 중 하나입니다. 그래서 파이썬에서 엑셀 파일을 읽는 일이 자주 발생합니다. 저는 최근에 이를 필요로 하여 파이썬에서 엑셀 파일을 읽는 여러 방법을 테스트하고 벤치마킹했습니다.
https://hakibenita.com/fast-excel-python
Node.js에서 Timeout 사용하는 방법
이 글에서는 Node.js의 비동기 특성 때문에 애플리케이션의 반응성을 보장하기 위해 타임아웃을 설정하는 것의 중요성을 다룹니다. Node.js 타임아웃은 무한 대기를 방지하고, 기대보다 오래 걸리는 작업을 처리할 수 있게 해줍니다. 우리는 Node.js와 Express에서 타임아웃을 설정하는 방법, 그리고 Axios와 Sequelize 같은 인기 라이브러리에서 타임아웃을 사용하는 방법을 살펴볼 것입니다.
https://blog.appsignal.com/2023/11/08/how-to-use-timeouts-in-nodejs.html
OpenAI GPT 스토어 개장: 새로운 차원의 AI 서비스 시대 개막
OpenAI가 GPT 스토어를 개장했습니다. GPT 스토어는 OpenAI DevDay때 언급된 프로덕트중 하나였고 샘 알트만 사태 이후 잠정적으로 연기되었다고 오늘 개장했습니다. 현재 300만개가 넘는 GPTs가 있었는데 이제 퍼블릭하게 사용할 수 있게 되었습니다. GPT Store는 일단 자주 사용했던 GPT부터 상단에 게재했고 추후에 많이 사용하게 된다면 수익도 얻을 수 있게 한다고 합니다. 다만 미국부터 먼저 수익을 얻을 수 있게 하다보니 한국은 꽤 시일이 걸릴거라 생각합니다. 아직 GPT Store에는 괜찮은 GPT가 많다고 보기 어렵지만 추후에 활성화된다면 앱스토어 수준으로 성장하지 않을까 예상하고 있습니다.
https://openai.com/blog/introducing-the-gpt-store
OpenAI, ChatGPT Team 요금제 출시: 스타트업부터 대기업까지 다양한 비즈니스 활용 가능
OpenAI DevDay이후에 OpenAI는 ChatGPT 엔터프라이즈를 출시했고 다양한 기업에서 조직을 운영할 때 사용하고 있습니다. 오늘, ChatGPT Team 요금제가 나와서 옵션을 제공할 수 있습니다. ChatGPT Team은 GPT-4 및 DALL-E 3와 같은 고급 모델과 고급 데이터 분석과 같은 도구에 대한 액세스를 제공하며 또한 팀을 위한 전용 협업 작업 공간과 팀 관리를 위한 관리자 도구도 포함되어 있습니다. ChatGPT Enterprise와 마찬가지로 비즈니스 데이터는 사용자가 소유하고 관리하며, 비즈니스 데이터나 대화에 대한 학습을 하지 않으며, 모델도 사용자의 사용을 통해 학습하지 않습니다. 그렇기에 다른 기업들도 안전하게 사용할 수 있습니다. 실제 ChatGPT Plus의 경우 보안에 취약하다보니 선뜻 쓰기 어려웠고 엔터프라이즈는 대기업만 사용할 수 있었는데 스타트업에서도 ChatGPT Team 요금제롤 적용해서 사용할 것이라는 생각이 듭니다.
https://openai.com/blog/introducing-chatgpt-team
----
『플러터 생존 코딩』 저자 오준석 주강사와 함께하는 조기 마감 인기과정!
🩵 오름캠프 ‘플러터 모바일 앱 개발 과정’ 사전 모집 중 ► https://bit.ly/3O1PSFU
TextEncoder is not defined 문제 해결하기
최근에 팀 내 저장소 패키지 버전 올리는 작업을 하던 중 테스트 코드가 갑자기 깨져버렸습니다. 문제는 Axios 1.6.8을 1.7.0로 업데이트하면서 발생했습니다.
Axios 1.7.0으로 업데이트한 후, Jest에서 TextEncoder is not defined
에러가 발생했습니다. 이 문제는 jsdom 라이브러리가 실제 브라우저 환경을 재현하기 위해 Node.js global을 덮어씌우는 과정에서 TextEncoder
가 추가되지 않아 발생하는 문제였습니다.
2019년에 jsdom 저장소에서 이미 이슈(https://github.com/jsdom/jsdom/issues/2524) 로 제기된 문제지만, 아직까지 해결되지 않고 있습니다. 그러나 다행히도 최근 Axios 1.7.1 버전에서는 TextEncoder
미지원 환경에 대한 fallback 처리가 추가되어, 현재는 문제없이 사용이 가능합니다.
한편, MSW 2.x 버전에서도 동일한 이슈가 발생한다고 합니다. 이 경우, MSW 공식 문서(https://mswjs.io/docs/migrations/1.x-to-2.x/#requestresponsetextencoder-is-not-defined-jest) 에서 제공하는 해결 방법을 따라하시면 해결이 가능합니다. 참고로 가이드엔 jest.polyfill.js
로 나와있는데 폴더에 setupTests.js
파일이 있으시면 아래 코드를 추가해서 간단하게 해결이 가능합니다.
const { TextDecoder, TextEncoder } = require('node:util')
Object.defineProperties(globalThis, {
TextDecoder: { value: TextDecoder },
TextEncoder: { value: TextEncoder }
})
시뮬레이션을 실행하려고 해도 스크립트 문제, iPhone 버전 범위 문제, 시뮬레이터 문제가 계속 발생합니다. 어떤 도움이라도 감사합니다. ReactNative를 처음 접했습니다. 저희 팀에서 저를 도울 수 있는 사람이 없습니다. #프로젝트 환경 mac M2 ruby -v ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin23] node -v v20.10.0 pod --version 1.15.2 package.json { "name": "labts", "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", "test": "jest" }, "dependencies": { "@react-native-community/async-storage": "^1.12.1", "@react-native-community/cli": "13.6.9", "@react-navigation/bottom-tabs": "^6.6.0", "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.10.0", "@tanstack/react-query": "^5.51.5", "@types/react-native-vector-icons": "^6.4.18", "axios": "^1.7.2", "date-fns": "^3.6.0", "immer": "^10.1.1", "react": "18.2.0", "react-native": "0.74.3", "react-native-calendars": "^1.1305.0", "react-native-date-picker": "^5.0.4", "react-native-dotenv": "^3.4.11", "react-native-get-random-values": "^1.11.0", "react-native-image-crop-picker": "^0.41.2", "react-native-image-zoom-viewer": "^3.0.1", "react-native-paper": "^5.12.3", "react-native-permissions": "^4.1.5", "react-native-safe-area-context": "^4.10.8", "react-native-screens": "^3.32.0", "react-native-splash-screen": "^3.3.0", "react-native-tab-view": "^3.5.2", "react-native-vector-icons": "^10.1.0", "react-native-vision-camera": "^4.5.1", "uuid": "^10.0.0", "yarn": "^1.22.22" }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@react-native/babel-preset": "0.74.85", "@react-native/eslint-config": "0.74.85", "@react-native/metro-config": "0.74.85", "@react-native/typescript-config": "0.74.85", "@types/react": "^18.2.6", "@types/react-native-dotenv": "^0.2.2", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", "babel-plugin-module-resolver": "^5.0.2", "eslint": "^8.19.0", "jest": "^29.6.3", "prettier": "2.8.8", "react-test-renderer": "18.2.0", "typescript": "5.0.4" }, "engines": { "node": ">=18" } } PodFile require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native/scripts/react_native_pods.rb", {paths: [process.argv[1]]}, )', __dir__]).strip platform :ios, '12.0' use_frameworks! #use_modular_headers! prepare_react_native_project! linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green use_frameworks! :linkage => linkage.to_sym end target 'nexlabts' do config = use_native_modules! use_react_native!( :path => config[:reactNativePath], # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) target 'nexlabtsTests' do inherit! :complete # Pods for testing end post_install do |installer| # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false, # :ccache_enabled => true ) end end 제가 아래 에러 3가지에 대해 제가 해본 방법들입니다. 1. node 재설치 node_module 폴더 삭제, package-rock.json 삭제 후 재설치 npm install --legacy-peer-deps yarn install 2. Xcode가 node 읽을 수 있도록 설정 sudo ln -s "$(which node)" /usr/local/bin/node 3. Podfile 내 platform 설정 수정 platform :ios, '12.0' or platform :ios, '14.0' 4. Pods 재설치 rm -rf ~/Library/Developer/Xcode/DerivedData or rm -rf ~/Library/Developer/Xcode/DerivedData/* rm -rf Pods rm Podfile.lock pod install --repo-update Xcode \> Product \> Clean Build Folder. cd ./ios pod cache clean -all pod install --repo-update cd ../ npx react-native run-ios --no-packager --simulator="iPhone 15" or npx react-native run-ios --simulator="iPhone 15" or yarn start > i(run ios) Err 1. cocoaPods 설치할 때 [!] CocoaPods could not find compatible versions for pod "React-RuntimeHermes": In Podfile: React-RuntimeHermes (from ../node_modules/react-native/ReactCommon/react/runtime) Specs satisfying the React-RuntimeHermes (from ../node_modules/react-native/ReactCommon/react/runtime) dependency were found, but they required a higher minimum deployment target. Err2. iOS 실행할때 run-ios --no-packager --simulator="iPhone 15" Build description signature: fc1341421f84b87c5245d346c2c17b66 Build description path: /Users/nowonjae/Library/Developer/Xcode/DerivedData/nexlabts-argvodqcybjfcybstpulfpghnzvm/Build/Intermediates.noindex/XCBuildData/fc1341421f84b87c5245d346c2c17b66.xcbuilddata /Users/nowonjae/Desktop/project/NeXLabRN/ios/nexlabts.xcodeproj:1:1: error: Unable to open base configuration reference file '/Users/nowonjae/Desktop/project/NeXLabRN/ios/Pods/Target Support Files/Pods-nexlabts/Pods-nexlabts.release.xcconfig'. (in target 'nexlabts' from project 'nexlabts') warning: Unable to read contents of XCFileList '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Unable to read contents of XCFileList '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-input-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase 'Bundle React Native code and images' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase '[CP] Embed Pods Frameworks' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-input-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase '[CP] Copy Pods Resources' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') --- xcodebuild: WARNING: Using the first of multiple matching destinations: { platform:iOS Simulator, id:B5AA2E84-4F83-4749-A986-A1FCE5E398A3, OS:17.5, name:iPhone 15 } { platform:iOS Simulator, id:B5AA2E84-4F83-4749-A986-A1FCE5E398A3, OS:17.5, name:iPhone 15 } ** BUILD FAILED ** ] Err3. Xcode 로 Build 할때 (Any iOS Simulator Device (arm64, x86_64)) Command PhaseScriptExecution failed with a nonzero exit code
위 에러 해결되었습니다. 크몽에 범코딩님께 에러 문의 및 코칭 요청드렸고 피드백으로 에러 발생 원인은 중복실행 문제로. 아이폰 직접 연결 빌딩시 해결될 것으로 판단된다고 피드백 주셔서, 진행해보니 정상적으로 잘 빌딩되었습니다. 관련해서 추가 참고한 블로그입니다. https://pocket-dev.tistory.com/35#google_vignette
```jsx import { Popover, Modal, Button, Image, Result } from "antd"; const [prevImg, setPrevImg] = useState(["any"]); const [loading, setLoading] = useState(false); const [removeImgFiles, setRemoveImgFiles] = useState([]); const combinePrevImages = (prevImages, newImages) => { const combinedImages = [...prevImages, ...newImages]; return combinedImages; }; useEffect(() => { const postSeg = async () => { try { const res = await axios.post( "apiurl", { filepath: filePath, clips: sortableList.map(list => `${list.seg.start}-${list.seg.end}`), frame: frameValue }, { proxy: false } ); return res.data; } catch (error) { console.error("Error posting segments:", error); return []; } finally { setLoading(false); } }; const postSegments = async () => { if (segments[0]?.start === 0 && segments[0]?.end === 0) return; if (sortableList && filePath) { setLoading(true); const res = await postSeg(); const combinedPrevImg = combinePrevImages(prevImg, res.results); setPrevImg(combinedPrevImg); console.log("Post Request Success"); } }; postSegments(); }, [filePath, segments, frameValue]); const handleModalOpen = useCallback(() => setModalOpen(true), []); const handleModalClose = useCallback(() => { setRemoveImgFiles([]); setModalOpen(false); }, []); const handleDeleteButtonClick = async () => { if (removeImgFiles.length > 0) { setPrevImg([...removeImgFiles]); setRemoveImgFiles([]); } else { const result = await showSwal({ title: "Are you sure delete?", showCancelButton: true, confirmButtonText: "Confirm", cancelButtonText: "Cancel", confirmButtonColor: "#3085d6", cancelButtonColor: "#d33" }); if (result.isConfirmed) { setRemoveImgFiles([...prevImg]); setPrevImg([]); } const success = await Promise.all(removeImgFiles.map(deleteFiles)); return success; } } const handleRemoveFinish = async () => { if (removeImgFiles) { for (const filePath of removeImgFiles) { try { await removeFile(filePath); } catch (e) { console.log("File Remove Error", e); } } } setRemoveImgFiles([]); handleModalClose(); }; return ( <motion.div initial={{ x: width }} animate={{ x: 0 }} exit={{ x: width }} transition={mySpring} > <div style={{ fontSize: 12, padding: "0 5px", color: "var(--gray12)", display: "flex", justifyContent: "space-between", alignItems: "center" }} > <FaAngleRight title={t("Close sidebar")} size={20} className="angle-right" role="button" onClick={toggleSegmentsList} /> {header} <FaExpandArrowsAlt title={t("Image Inspection")} size={18} className="expand-arrow-alt" style={{ cursor: "pointer" }} role="button" onClick={!loading ? handleModalOpen : handleModalClose} /> <Modal title={t("Image Inspection")} centered onCancel={handleModalClose} open={modalOpen} footer={[]} width="100%" > <div className="imagecontainer"> <Button danger className="toggle-remove" onClick={handleDeleteButtonClick}> {removeImgFiles.length > 0 ? "Add" : "Remove"} </Button> {prevImg?.length > 10 && prevImg.map(img => ( <Popover key={img}> {removeImgFiles?.includes(img) ? ( <span> <Result className="result" icon={<FaSmile />} subTitle="delete" /> </span> ) : <Image key={uuidv4()} src={img} preview={{ src: img }} alt={uuidv4()} /> )} </Popover> ))} </div> <Button block onClick={handleRemoveFinish}> Finish </Button> </Modal> </div> ) ``` api 호출을 통해 frameValue 개수(여기서는 12개씩) 만큼 이미지를 렌더링 하고 있는데 두번째 호출부터는 Button이 렌더링되지 않아서 어디가 잘못됐는지 알고싶습니다.. 필요한 부분이 imagecontainer 클래스네임인 div를 렌더링 해야합니다.
안녕하세요! Next.js SSR + react-query 조합을 사용하려고 하는데요, page 컴포넌트 내 getServerSideProps 함수에서 prefetching을 받아온 후에 serializing 에러가 발생합니다. (Next.js는 13버젼입니다.) 에러 내용은 다음과 같습니다. Error: Error serializing `.dehydratedState.queries[0].state.data.headers` returned from `getServerSideProps` in "/top". Reason: `object` ("[object AxiosHeaders]") cannot be serialized as JSON. Please only return JSON serializable data types. 해당 에러 내용으로 구글링을 해보니, 대부분 getServerSideProps 함수 반환 코드에서 return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; 와 같이 dehydrate(queryClient)값을 JSON화 -> Object화를 하라고 하는데요, 이와 같이 사용해도 또 다시 아래와 같은 에러가 납니다. TypeError: Converting circular structure to JSON --> starting at object with constructor 'ClientRequest' | property 'socket' -> object with constructor 'Socket' --- property '_httpMessage' closes the circle Backend API는 Express.js를 사용하고 있으며, res.status(200).json({ data: ~ })와 같은 방식으로 응답을 주고 있습니다. 어떻게 해결할 수 있을까요? 코드 첨부가 안되네요, 아래는 page 컴포넌트가 위치한 파일의 전체 코드입니다. import type { ReactElement } from 'react'; import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query'; import { format } from 'date-fns'; import TopMusicContainer from '~containers/TopMusicContainer'; import Layout from '~layouts/Layout'; import type { NextPageWithLayout } from '~pages/_app'; import TopMusicService from '~services/topMusicService'; import * as MusicType from '~types/musicType'; export async function getServerSideProps() { const queryClient = new QueryClient(); await queryClient.prefetchQuery(['fetchTopMusic'], () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }); return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; } const Top: NextPageWithLayout = (): JSX.Element => { const { data, isLoading } = useQuery({ queryKey: ['fetchTopMusic'], queryFn: () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }, }); return ( <section> <TopMusicContainer /> </section> ); }; Top.getLayout = function getLayout(page: ReactElement) { return <Layout>{page}</Layout>; }; export default Top;
안녕하세요. Axios response를 그대로 사용하시면 함수 등 JSON으로 변환이 불가능한 것들이 포함되어 있어서 axios response에서 data만 추출해야 하는 것으로 알고 있는데요. 혹시 TopMusicService.list(params); 의 리턴 타입이 어떻게 되나요? 참고한 SO 포스트: - https://stackoverflow.com/questions/67204170/getserversideprops-functions-response-cannot-be-serialized-as-json-in-next-js
react + 파이어베이스만 써오다가 react + express 조합을 오랜만에 사용중인데 긴가민가한점이있어서 질문드립니다 ㅠㅠ import axios from 'axios'; const instance = axios.create({ baseURL: process.env.REACT_APP_API_BASE_URL, timeout: 2500, headers: { 'Content-Type': 'application/json', withCredential: true, }, }); instance.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = token; } else { delete config.headers['Authorization']; } return config; }, (error) => { return Promise.reject(error); } ); export default instance; 이렇게 인터셉터를주어서, 요청할 때마다 토큰을 전달하고있습니다. 그리고 프라이빗 라우터 + 유저정보 인증 훅을 사용해서 로그인사용자만 접근가능하게 페이지를 설정해줬습니다. 근데 질문 1. 네트워크탭 Request Headers 에서 Authorization을 확인해보면 토큰정보가 그대로 노출되고있는데요 원래 이렇게되나요? 질문 2. Bearer + token 이렇게 보내는 경우는 포스트맨이나 이런걸로 테스트할때만 Bearer 을 붙여서 보내주면될까요? 질문 3. 검색 키워드가 생각이안나서 질문으로 올립니다. 아주 옛날에는 (4년전) App.tsx에서 if(localstorage.token){ setAuthToekn(token); } 이런식으로 하고, setAuthToken함수는 import axios from 'axios'; const setAuthToken = (token) => { if (token) { axios.defaults.headers.common['x-auth-token'] = token; } else { delete axios.defaults.headers.common['x-auth-token']; } }; export default setAuthToken; 이런식으로 되어있었는데요, 요즘은 제가 짠 코드처럼 axios.인터셉터 식으로 하는게 맞나요? 질문 4. 그럼 요즘도 회원가입/로그인시 로컬스토리지에 유저 정보 (닉네임 이름 이메일, 토큰정보)만 저장해두고 로그인하면, 로그인버튼이 회원 닉네임으로 변하게 해준다던가.. 이런식으로 분기처리를 하나요? ( 저는 이렇게하고있어서요.. 로딩처리를 줄수도있겠지만 깜빡거리는게 싫고, 또 로컬스토리지로안하면 로그인버튼으로 잠깐바꼇다가 회원닉네임이 표시되더라구요) 질문5. (질문4와 이어집니다.) 만약 질문4처럼하면 사용자가 사이트에 계속 로그인중인데, 이 토큰이 끝났는지 판단하려는 코드를 따로 작성해줘야할까요? 예를들면, App.tsx에 서버 api/auth같은거에 요청보내는 로직을 작성해서, 토큰만보내서 유효한 토큰인지 아닌지, 유효하지않은토큰이면 에러를 리턴시켜준다던가, 리프레쉬토큰을 발급시켜서 연장시켜준다던가 이런식으로하면될까요? 마지막으로 질문이 좀 많고 중구난방인데 죄송스럽고 조심스럽네요..
네 아니요 토큰을 헤더에 넣는 건 토큰이 필요할 때 넣는 것이니 postman과 상관없습니다 코드에 생략이 많아서 모르겠네요 아니요 리액트 쿼리나 swr을 많이 사용합니다 네 아직 전반적인 구조가 감이 안잡힌다면 유튜브에서 mern 풀스택 프로젝트 영상 많으니 보시는 것을 추천드립니다
회원가입 마지막 단계 페이지인 관심태그 페이지입니다. 이미 nickName, email, phoneNum, pwd, subGroup, workYear는 recoil의 userState에 저장된 상태입니다. 다음 코드를 실행하면 api 호출에는 성공하는데, isSuccess가 false가 되면서 '이메일을 입력해주세요'라는 message가 뜨고 회원가입 실패 alert 창이 뜹니다. 아무래도 recoil value의 전달에 실패한 것 같은데 왜 그럴까요? 참고로 api 주소는 가짜로 적었습니다. function InterestTag() { const [user, setUser] = useRecoilState(userState); const [interestIdx, setInterestIdx] = useRecoilState(userState); const [buttonColor, setButtonColor] = useState("f1f4f7"); const nickName = useRecoilValue(userState).nickName; const email = useRecoilValue(userState).email; const phoneNum = useRecoilValue(userState).phoneNum; const pwd = useRecoilValue(userState).pwd; const subGroup = useRecoilValue(userState).subGroup; const workYear = useRecoilValue(userState).workYear; const handleTagClick = (interestIdx) => { setInterestIdx(interestIdx); setUser({ ...user, interestIdx: interestIdx, isLogin: true, }); setButtonColor("#36f"); }; console.log(interestIdx); const navigate = useNavigate(); const handleSubmit = () => { axios .post("https://abcd.shop/users", { data: { nickName: nickName, email: email, phoneNum: phoneNum, pwd: pwd, subGroup: subGroup, workYear: workYear, interestIdx: interestIdx, }, }) .then((response) => { console.log(response.data); if (response.data.isSuccess === true) { alert("회원가입 성공"); navigate("/"); } else { alert("회원가입 실패"); } }); };
안녕하세요. axios.post로 전송하는 데이터 형태가 의도된 것인가요? { data: { data: { nickname: ... } } } 형태로 날라가는 것 처럼 보여서요. 백엔드가 저렇게 짜여져있다면 처리가 될텐데 혹시 { data: { nickname: ...} } 형태를 받고 있을 수 있어서 질문 드립니다.
react + spring boot로 진행하는 프로젝트가 현재 cloudtype이라는 플랫폼으로 배포중에 있습니다. 문제는 정확한 서버주소를 호출하는 것 같음에 불구하고 404 에러가 발생하여 해결하지 못하고 있습니다. 현재, 아래 사진과 같은 로그가 클라이언트, 서버에 각각 발생합니다. 서버에서는 다음과 같이 구성되어있고 @RestController @RequestMapping("/v1/login") @RequiredArgsConstructor @CrossOrigin(origins = "https://web-secondchance-front-bug-1cupyg2klvnmgdft.sel5.cloudtype.app") public class KakaoController { private final KakaoService kakaoService; private final Logger LOGGER = LoggerFactory.getLogger(KakaoController.class); @PostMapping("/kakao-login") public ResponseEntity<UserDto> kakaoLogin(@RequestBody KakaoLoginDto kakaoLoginDto) { String code = kakaoLoginDto.getCode(); LOGGER.info("Get Code from FrontEnd : {}", code); LOGGER.info("Request getAccessToken()"); kakaoLoginDto = kakaoService.getAccessToken(code); String accessToken = kakaoLoginDto.getAccess_token(); LOGGER.info("access_token : {}", accessToken); if(accessToken != null){ UserDto userDto = kakaoService.getUserInfo(accessToken); return ResponseEntity.ok(userDto); } else { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); } // accessToken이 null임에도 getUserInfo를 부름. // 안부르게 끔 위의 방법을 포함하여 // 1. map에서 true, false를 사용하여 해봄 // 2. getAccessToken을 map객체로 반환하게끔 하여 accessToken이 있으면 true, 없으면 false로 하여 isEmpty 함수로 체크하여 부름 // 위의 두 방법 전부 소용없음. 그냥 getUserInfo를 부름. } @PostMapping("/kakao-logout") public String kakaoLogout(){ return "ok"; } } 현재 리액트에서는 다음과 같이 axios.post로 접근하여 code를 전달합니다. 무엇이 문제일까요?