9달 전 · 삭제된 사용자 님의 댓글 업데이트
안녕하세요.. 정말 이것저것 다 해봤는데 안되네요 ㅠ
안녕하세요. 리액트와 리액트 쿼리를 이용해 프로젝트를 진행 중 입니다. 저는 일단. src/hooks/useProduct.jsx 라는 커스텀 훅 을 만들었습니다. 제 계획은 1. 맨 처음 최상단 Home.jsx 에서 useProduct 라는 커스텀 훅 호출 2. 자식컴포넌트에 data를 전달 3. Search.jsx에서 받은 filter값으로 커스텀 훅 안의 query 재실행 4. 바뀐 상태는 Home.jsx 가 다시 받고 자식 컴포넌트에게 전달. 1~3까지는 콘솔찍어가면서 잘 되는데 4.는 되지 않습니다. (쿼리 데이터가 바뀌어도 4이 재 실행이 안됨..) ㅠㅠㅠㅠㅠㅠㅠ 쿼리 옵션도 줘보고 전역 상태도 만들어보고 이것저것 다 해봤는데 ㅠㅠ 혹시 아시는분 계신가요..? 어떠한 답변도 정말 감사히 받겠습니다 간략 코드첨부) Home.jsx const Home = ()=>{ const { data, error, isLoading } = useProduct(); return ( <Search /> <List data={data?.product} /> ); }; export default Home; Search.jsx const Search = () => { ```일부코드생략``` const { updateQuery } = useProduct (); const handleSubmit = () => { const searchData = { productId }; updateQuery(productId); }; } return( ```일부코드생략``` <button onClick={handleSubmit}> 조회 </button> ) } export default Search; useProduct.jsx const useProduct = () => { const [queryData, setQueryData] = useState(); const { data, error, isLoading } = useQuery({ queryKey: ["product", queryData], queryFn: () => fetchProduct(queryData), }); const updateFilters = (queryData) => { setQueryData(() => (queryData); }; return { data, error, isLoading, updateQuery , }; export default useProduct ;
개발자
#react
#react-query
#react-query-v5
답변 3
댓글 6
조회 110
10달 전 · 털먹는토끼 님의 댓글 업데이트
리액트 쿼리 에러 핸들링 이슈
react query를 활용한 에러 핸들링이 안돼서 질문 올립니다... //mainpage.tsx const { data, refetch, isFetching } = FetchData(url); const handleSearch = () => { refetch(); } <QueryErrorResetBoundary> {({ reset }) => ( <ErrorBoundary onReset={reset} FallbackComponent={FallbackUI}> {resultVisible ? ( <Result searchData={searchData} isFetching={isFetching} /> ) : ( <EmptyResult /> )} </ErrorBoundary> )} </QueryErrorResetBoundary> react-error-boundary 라이브러리를 이용해 ErrorBoundary 컴포넌트를 가져왔습니다. ErrorBoundary 하위 컴포넌트 내에서 에러가 발생하면 FallbackComponent의 FallbackUI 컴포넌트가 렌더링 되도록 했습니다. //FallbackUI 컴포넌트 //에러가 발생히면 이 컴포넌트가 렌더링되어야합니다. const FallbackUI = ({ error, resetErrorBoundary }) => { return ( <div> <span>{error.message}...</span> <button onclick={resetErrorBoundary} /> 돌아가기 버튼 </div> ); }; export default FallbackUI; FallbackUI 에서 QueryErrorResetBoundary 에서 제공하는 resetErrorBoundary를 받아와 에러 발생 후 '돌아가기 버튼'을 클릭하면 쿼리오류를 처리하고 리셋해주도록 구현했습니다. //url을 파라미터값으로 받아와 api호출하는 함수 //위에 있는 mainpage.tsx 에서 사용하는 함수입니다. const FetchData = (url: string) => { const { error, data, refetch, isFetching } = useQuery({ queryKey: ["repoData"], queryFn: async () => { const res = await axios.get(url); console.log(res.data); return res.data; }, refetchOnWindowFocus: false, enabled: false, }); return { error, data, refetch, isFetching }; }; 리액트 쿼리를 이용해 api를 호출하는 함수입니다. //main.tsx const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 0, throwOnError: true, }, }, }); ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <QueryClientProvider client={queryClient}> <BrowserRouter> <GlobalStyles /> <Provider store={store}> <App /> </Provider> </BrowserRouter> </QueryClientProvider> </React.StrictMode> ); 마지막으로 전역으로 에러 관리를 하도록 세팅해놨습니다. 그리고 QueryClient에 throwOnError 속성이 있어야 에러를 ErrorBoundary로 전달할 수 있다해서 추가해줬습니다. 이렇게 세팅해놨는데 에러발생하면 그냥 하얀색 화면만 나오고 fallbackUI가 나오지 않습니다... 원인이 뭘까요...ㅠㅠㅠ 혹시 몰라서 콘솔 에러코드도 올립니다.. 추가로 궁금한 점 1. useQuery 에서 반환하는 객체중 error 객체는 어떤 존재인지 궁금합니다. 2. useQuery 속성과 QueryClient 속성 모두 throwOnError : true 를 가지고 있던데 어떤 차이점인지 궁금합니다. 답변주시면 정말 감사하겠습니다!!!
개발자
#react
#react-query
#error-handler
#error-boundary
답변 1
댓글 1
조회 194
일 년 전 · 방재호 님의 답변 업데이트
[Js] 함수 매개변수 전달에 대하여...
안녕하세요! 이제 막 1년 좀 넘은 주니어 개발자 입니다. 공부 중에 한 가지 궁금한 점이 생겨서 선생님들께 여쭤보려합니다. js 함수를 작성하는 중에 매개변수로 event를 전달하려 했더니 event에 취소선이 생성됩니다... 예시) - html - <div class='.card'> ... <button id="deleteCard" onclick="deleteCard(event)">삭제</button> </div> - js - function deleteCard(event){ let card = $(event.target).closest('.card').remove(); }; 위와 같이 작성했는데 함수 실행은 되긴 하는데 매개변수 event에 취소선이 생기니 잘못된 것 같아서 여쭤봅니다... 취소선이 생기는 이유와 문제가 된다면 어떤 점에서 문제가 생기는지 개선점은 뭐가 있는지 궁금해서 문의 남깁니다.
개발자
#javascript
#매개변수
답변 1
댓글 0
조회 77
일 년 전 · 이지현 님의 새로운 댓글
리액트 타입스크립트 obj[key] 에러
타입스크립트 이제 막 적용해보는 초보입니다.. 코드 내 monitorMenu[type].container 에서 [type]에 에러가 뜨는데 타입 지정이 잘못 된 건지 찾아봐도 원인을 모르겠습니다ㅜㅜ 물어볼 곳이 없어 질문합니다,,! 도움 부탁드립니다...ㅠㅠ // code import React, { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import DashboardContainer from '../containers/DashboardContainer'; import IntegratedContainer from '../containers/IntegratedContainer'; interface IMonitorMenu { [key: string]: { container: string | JSX.Element; }; } const Monitor = () => { const user: any = []; const { type } = useParams<{ type?: string }>(); const navigate = useNavigate(); const [menu, setMenu] = useState('integrated'); const onClickMenu = (name: string) => { navigate(`/monitor/${name}`); }; const monitorMenu: IMonitorMenu = { integrated: { container: <IntegratedContainer /> }, dashboard: { container: <DashboardContainer /> }, }; return ( <div> ... <div>{monitorMenu[type].container}</div> </div> ); }; export default Monitor; // error ERROR in src/pages/Monitor.tsx:37:25 TS2538: Type 'undefined' cannot be used as an index type.
개발자
#react
#typescript
답변 1
댓글 1
조회 92
일 년 전 · 김태우 님의 답변 업데이트
Next.js 에러 핸들링
Next.js 에러 핸들링을 위해 루트에 error.tsx파일을 생성했습니다. 특정페이지에서 서버 컴포넌트를 만든 후 `throw new Error` 를 통해 에러 발생시 위에서 생성했던 에러페이지(root/error.tsx)로 대체됩니다. 그러나 클라이언트 컴포넌트("use client")에서 테스트 하기위해, button태그의 onClick을 통해 `throw new Error`를 실행시켰습니다. 이때 콘솔에만 출력되고 따로 에러페이지로 대체 되진않았습니다. 클라이언트 컴포넌트에선 해당 동작을 통해서는 에러페이지로 대체되는 핸들링이 되지않는걸까요?? Next: v14
개발자
#next.js
답변 1
댓글 0
조회 672
일 년 전 · 민경배 님의 답변 업데이트
리액트 내비게이션 초기 데이터값 설정
import React, { useEffect, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { TbArrowBigUpFilled } from "react-icons/tb"; import useScroll from "../utils/useScroll"; import useProductsQuery from "../useProductsQuery"; const Nav = () => { const { isNavFixed } = useScroll(); const [activeButton, setActiveButton] = useState(0); const handleButtonClick = (index) => { setActiveButton(index); }; const categoryNames = { materials: "소재별 품목", purposes: "용도별 품목", }; const [isScrollMenuVisible, setScrollMenuVisible] = useState(false); const handleMouseEnter = () => { setScrollMenuVisible(true); }; const handleMouseLeave = () => { // console.log("마우스 이탈"); setScrollMenuVisible(false); }; const location = useLocation(); const isloginorsignup = location.pathname === "/login" || location.pathname === "/signup"; const { isLoading, isError, errorMessage, materials, purposes } = useProductsQuery(); const [hoveredOnedepth, setHoveredOnedepth] = useState(null); const [onedepthChildren, setOnedepthChildren] = useState([]); const [hoveredDivision, setHoveredDivision] = useState(null); const [twodepthDivision, setTwodepthDivision] = useState([]); const handleOnedepthMouseEnter = (item) => { setHoveredOnedepth(item); // console.log(setHoveredOnedepth) setOnedepthChildren(item.children); }; const handletwodepthMouseEnter = (child) => { setHoveredDivision(child); // console.log(setTwodepthDivision); setTwodepthDivision(child.divisions); // console.log(child.divisions); }; if (isLoading) return <>로딩 중...</>; if (isError) return <>{errorMessage}</>; return ( <> {!isloginorsignup ? ( <> {/* 대메뉴 */} <nav id="nav" className={`nav ${isNavFixed ? "fixed" : ""}`}> <div className="nav_wrap"> <div className="twobutton_wrap"> {["materials", "purposes"].map((category, index) => ( <Link type="button" className={`twobutton ${ activeButton === index ? "active" : "" }`} key={index} onClick={() => handleButtonClick(index)} to={`/${category}`} > {categoryNames[category]} </Link> ))} </div> {/* 내비게이션 1depth */} <div className="scrollnav"> <ul className="materialnav" style={{ display: activeButton === 0 ? "flex" : "none", }} > {materials.map((material) => ( <li key={material.id} className="materialnav_li" onMouseEnter={() => { handleOnedepthMouseEnter(material); handleMouseEnter(); }} onMouseLeave={handleMouseLeave} > <Link to="/">{material.name}</Link> </li> ))} </ul> <ul className="usagenav" style={{ display: activeButton === 1 ? "flex" : "none", }} > {purposes.map((purpose) => ( <li key={purpose.id} className="usagenav_li" onMouseEnter={() => { handleOnedepthMouseEnter(purpose); handleMouseEnter(); }} onMouseLeave={handleMouseLeave} > <Link to="/">{purpose.name}</Link> </li> ))} </ul> </div> <div className="call_wrap"> <div className="call"> <div className="call_title">견적 상담 · 문의 대표전화</div> <div className="top_button"> <TbArrowBigUpFilled className="top_icon" /> <p>TOP</p> </div> </div> </div> </div> </nav> {/* 2&3depth 호버 메뉴 */} <div className={`scrollmenu ${isScrollMenuVisible ? "show" : ""} ${ isNavFixed ? "fixed" : "" }`} style={{ visibility: isScrollMenuVisible ? "visible" : "hidden" }} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > {activeButton === 0 && ( <div className="scrollmenu_top"> <ul className="depth2_wrap"> {onedepthChildren.map((child) => ( <li key={child.id} className="depth2_menu" onMouseEnter={() => { handletwodepthMouseEnter(child); }} > <div className="depth2_menu_img_wrap"> <div className="depth2_menu_img"> <img src={child.images[0]._links.href} alt={child.name} /> </div> </div> <p>{child.name}</p> </li> ))} </ul> </div> )} <div className="scrollmenu_middle"> <div className="depth3_wrap"> {twodepthDivision.map((division) => ( <Link key={division.id} to="/" className="depth3_menu"> <span className="depth3_menu_img"> {/* images 속성이 없거나 비어 있어도 오류를 방지 */} {division.images && division.images.length > 0 && ( <img src={division.images[0]._links.href} alt={division.division_name} /> )} </span> <p>{division.division_name}</p> </Link> ))} </div> </div> <div className="scrollmenu_bottom"></div> </div> </> ) : null} </> ); }; export default Nav; 현재는 depth2_menu 호버해야만 depth3_menu가 보이는데 초기에 scrollmenu가 보일때부터 depth2_menu가 (onedepthChildren.map의 첫번째 순서의 데이터가) 호버되어있어 그에 매칭되는 division의 내용들이 depth3_menu에 보이게 할수있는 방법 있을까요 ?
개발자
#react
답변 1
댓글 0
조회 113
일 년 전 · LHS 님의 질문
Next.js의 dynamic import를 이용한 Ref 전달
현재 next.js 13버전과 toast ui calendar를 사용해서 프로젝트를 진행하고있습니다. toast ui calendar는 서버사이드에서는 렌더링이 되지 않기 때문에 dynamic 함수를 사용해서 import를 했습니다. dynamic 함수를 사용해서 import한 component에 ref를 넘기는 방법이 궁금합니다! https://careerly.co.kr/qnas/2078 위의 질문글을 보고 현재 아래 코드와 같이 작성을했는데 이렇게 작성을 하게된다면 결국 TuiCalendar라는 컴포넌트에는 ref={ref}, forwardRef를 사용해서 넘기는것이 아닌 forwardedRef라는 props로 ref를 넘기는형태가 되는건데 dynamic 함수를 사용해서 받아온 컴포넌트에 ref를 넘기는것은 이방법밖에 없을까요? 또한 "use client" import { useRef } from "react"; import Child from "./child"; export default function Parent (props : any) { const testRef = useRef(3) return ( <> <button onClick={() => console.log(testRef.current)}>버튼</button> <Child testRef={testRef} /> </> ) } 이것과 같이 ref를 그냥 props로 넘기는것과 ref + forwardRef 로넘기는 것의 차이점을 알고싶습니다!
개발자
#next.js
#react
답변 0
댓글 0
추천해요 2
조회 478
일 년 전 · 김주호 님의 새로운 답변
리액트 책을 보면서 공부중인데 아래의 코드가 제대로 작동을 않합니다
import { useState } from "react"; type CounterProps = { initialValue: number; }; const Counter = (props: CounterProps) => { const { initialValue } = props; // 계정을 유지하는 하나의 상태를 useState()로 선언한다. 인수에는 초깃값을 지정한다. // count는 현재 상태, setCount는 상태를 업데이트하는 함수다。 const [count, setCount] = useState(initialValue); return ( <div> <p>Count: {count}</p> {/* setCount를 호출해서 상태를 업데이트한다 */} <button onClick={() => setCount(count - 1)}>-</button> <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button> </div> ); }; export default Counter; 책에 있는거 그대로 코딩 했는데 Property 'initialValue' is missing in type '{}' but required in type 'CounterProps' 이런 에러가 발생해요 왜 그런걸까요
개발자
#react
답변 1
댓글 0
조회 76
일 년 전 · 익명 님의 질문 업데이트
React 버튼 클릭 시 api 호출 후 페이지 이동하면 api호출이 취소됩니다..
제목 그대로 버튼 클릭 시 onClick함수에 react-query로 axios api 통신하는 함수 2개를 호출 후 페이지를 이동하는데 페이지가 이동하면서 api 호출이 취소됩니다.. 비동기라 그런것인지 통신 후 페이지가 이동되게 할 수는 없을까요?
개발자
#react
#react-query
#axios
답변 1
댓글 3
추천해요 2
조회 481
일 년 전 · 정하승 님의 질문 업데이트
api의 첫번째 호출 이후부터 antd Button 렌더링 안되는 이슈가 있습니다.
```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를 렌더링 해야합니다.
개발자
#react
답변 0
댓글 0
조회 81
2년 전 · 도연 님의 댓글 업데이트
안녕하세요 NextAuth를 사용해서 구글 로그인을 구현하고 있는 중 어떻게 해결해야 할지 모르겠습니다ㅜㅜ...
아래 블로그를 참고하여 route.ts, AuthContext.tsx를 만들고 siginIn 이벤트는 사용하는 컴포넌트의 버튼 요소에 걸어주었습니다. 하지만 버튼을 누르면 404 페이지가 나오면서 url이 http://localhost:3000/api/auth/error로 뜹니다... .env 파일을 수정하고 NextAuthOptions를 사용해서 NEXTAUTH_URL, NEXTAUTH_SECRET 설정도 해보았고, 아래 코드처럼 스택 오버플로우 사이트를 참고해서 적어보기도 했지만 여전히 같은 에러가 반복됩니다... <button onClick={(e) => { e.preventDefault(); signIn('google', { callbackUrl: '/' }); }}> 구글 </button> 404 에러를 해결할 수 있는 방법이 있을까요... https://velog.io/@uni/NextAuth.js-%EA%B5%AC%EA%B8%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8-Next%EB%B2%84%EC%A0%84-13.4.2
개발자
#next.js-13
#nextauth
답변 2
댓글 6
조회 699
2년 전 · 허니 님의 새로운 답변
타입스크립트 이벤트 타이핑 방식에 관하여
안녕하세요 타입스크립트 코드 작성 중 궁금한 부분이 생겨 이렇게 질문 글 올려봅니다. 만들어 놓은 custom hook 중 모달 or 레이어 창을 닫는 close라는 함수가 hook 안에 있습니다. 함수는 아래 코드와 같이 생겼습니다. 이 함수는 usehook의 의도에 맞게 코드 전역에서 사용 되는데 컴포넌트 내부에서 이 함수를 사용할 때 이벤트 객체의 타입이 사용하는 곳마다 다르기 때문에, close 함수의 파라미터인 이벤트 객체의 타입을 결정하는데 고민이 있었습니다. 어느 곳은 input 같은 태그에서 onClick 함수 내에서 사용하기에 React.MouseEvent, 어느 곳은 eventListener에서 사용하기 때문에 그냥 MouseEvent로 추론 되는 등 여러가지 이벤트 타입이 존재했습니다. 그래서 모든 이벤트에서 상속 받는 BaseSyntheticEvent를 close 함수를 정의하는 곳에서 이벤트 객체의 타입으로 지정을 해주었는데 이게 좋은 방법이 맞을까요? 혹은 이벤트 객체 타입이 아니더라도 비슷한 경우에는 어떻게 작성하는 것이 좋은 방법일까요?
개발자
#react
#typescript
답변 1
댓글 0
조회 102
2년 전 · 커리어리 Q&A 운영자 님의 새로운 댓글
radio 버튼 클릭시 변경하는 form 값이 한박자 늦게 변경되는 문제가 있습니다.
radio버튼에서 onClick으로 react hook form에 있는 값을 바꾸고 있습니다. styled-compontent에서 props값을 전달하면 될것 같은데 한 박자 늦게 바뀝니다. 제 생각에는 랜더링이 onClick이 반영되기 전에 실행되고 있는 것 같은데 한 박자 늦게 랜더링을 시키는 방법이 따로 있는 걸까요? 아니면 styled-component의 props를 버튼 클릭했을 때 연동시킬 수 있는 방법이나 키워드가 따로 있나요?
개발자
#react
#form
#onclick
답변 1
댓글 1
조회 228
2년 전 · 엄홍재 님의 새로운 답변
nextjs 초보 fetch & DELETE 동작이 안되요.
app/api/list/[id]/route.js export async function DELETE(request, {params}) { const id = params.id; const {searchParams} = request.nextUrl; const sort = searchParams.get('sort'); return NextResponse.json({message: 'test', id, sort}); } 위처럼 되어있고, app/list/ListItem.js <button className="blue"> <span onClick={() => { fetch('/api/list/' + post._id, { method: 'DELETE' }).then(() => { console.log('call delete'); }); }}>삭제</span> </button> 위 처럼 코드를 작성했는데, DELETE route가 실행이 되지 않습니다. 어떻게 해야 하나요?... POST의 경우에는 아래와 같은데 동작이 잘 되서요.. app/write/route.js export async function POST(request) { const data = await request.formData(); let body = Object.fromEntries(data); const db = (await connectDB).db('exam'); await db.collection('post').insertOne(body); return new Response('POST'); } app/write/page.js <form action="/api/write" method="POST"> <input type="text" name="title" placeholder="제목"/> <br/> <input type="text" name="content" placeholder="내용"/> <button type="submit">NEW</button> </form>
개발자
#nextjs
답변 1
댓글 0
조회 169
2년 전 · 석이 님의 답변 업데이트
해당 스프링부트 코드 해석 좀 부탁드립니다..
안드로이드에쓸 스프링부트 코드를 따왔는데 제가 잘이해사 안되서 상세히 알려주실 선배님조언 구합니다 package com.example.teamproject import MyAdapter import android.content.Context import android.content.Intent import android.icu.lang.UCharacter.GraphemeClusterBreak.L import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.example.teamproject.databinding.ActivitySearchBinding import com.example.teamproject.login.LoginActivity import com.example.teamproject.model.RstrModel import com.example.teamproject.review.ReviewActivity import com.google.android.material.bottomnavigation.BottomNavigationView import retrofit2.Call import retrofit2.Callback import retrofit2.Response class SearchActivity : AppCompatActivity() { lateinit var binding: ActivitySearchBinding private lateinit var adapter: MyAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivitySearchBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayShowTitleEnabled(false) binding.toolbar.title = "검색" val loginSharedPref = applicationContext.getSharedPreferences("login_prof", Context.MODE_PRIVATE) val userId = loginSharedPref.getString("m_id", null) val userService = (applicationContext as MyApplication).userService // 하단바 초기값 설정 val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottommenu) bottomNavigationView.selectedItemId = R.id.second_tab // 하단바 선택시 이벤티 binding.bottommenu.setOnItemSelectedListener {item -> when(item.itemId) { R.id.first_tab -> { val intent = Intent(this@SearchActivity, MainActivity::class.java) startActivity(intent) } R.id.third_tab -> { val intent = Intent(this@SearchActivity, ReviewActivity::class.java) startActivity(intent) } R.id.fourth_tab -> { if ( userId == null){ val intent = Intent(this@SearchActivity, LoginActivity::class.java) startActivity(intent) } else { val intent = Intent(this@SearchActivity, MyDining::class.java) startActivity(intent) } } R.id.fifth_tab -> { if ( userId == null){ val intent = Intent(this@SearchActivity, LoginActivity::class.java) startActivity(intent) } else { val intent = Intent(this@SearchActivity, MyProfilePage::class.java) startActivity(intent) } } } true } binding.searchBtn.setOnClickListener { var rstr_nm = binding.searchText.text.toString() val getnamecount = userService.getNamecount(rstr_nm) getnamecount.enqueue(object: Callback<Int>{ override fun onResponse(call: Call<Int>, response: Response<Int>) { if ( response.isSuccessful ){ val ncount = response.body() if (ncount != null) { if ( ncount >= 1){ val getname = userService.getName(rstr_nm) getname.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val rstrlist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, rstrlist) binding.recyclerView.addItemDecoration( DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL) ) } } override fun onFailure( call: Call<List<RstrModel>>, t: Throwable ) { call.cancel() } }) } else { Toast.makeText(this@SearchActivity, "검색 결과가 없습니다!", Toast.LENGTH_SHORT).show() } } } } override fun onFailure(call: Call<Int>, t: Throwable) { call.cancel() } }) } binding.area1.setOnClickListener { val rstr_addr = binding.area1.text.toString() val getarea = userService.getArea(rstr_addr) getarea.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.area2.setOnClickListener { val rstr_addr = binding.area2.text.toString() val getarea = userService.getArea(rstr_addr) getarea.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.area3.setOnClickListener { val rstr_addr = binding.area3.text.toString() val getarea = userService.getArea(rstr_addr) getarea.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.area4.setOnClickListener { val rstr_addr = binding.area4.text.toString() val getarea = userService.getArea(rstr_addr) getarea.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.area5.setOnClickListener { val rstr_addr = binding.area5.text.toString() val getarea = userService.getArea(rstr_addr) getarea.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.area6.setOnClickListener { val rstr_addr = binding.area6.text.toString() val getarea = userService.getArea(rstr_addr) getarea.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.area7.setOnClickListener { val rstr_addr = binding.area7.text.toString() val getarea = userService.getArea(rstr_addr) getarea.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.type1.setOnClickListener { val rstr_list = binding.type1.text.toString() val gettype = userService.getType(rstr_list) gettype.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.type2.setOnClickListener { val rstr_list = binding.type2.text.toString() val gettype = userService.getType(rstr_list) gettype.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.type3.setOnClickListener { val rstr_list = binding.type3.text.toString() val gettype = userService.getType(rstr_list) gettype.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.type4.setOnClickListener { val rstr_list = binding.type4.text.toString() val gettype = userService.getType(rstr_list) gettype.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.type5.setOnClickListener { val rstr_list = binding.type5.text.toString() val gettype = userService.getType(rstr_list) gettype.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } binding.type6.setOnClickListener { val rstr_list = binding.type6.text.toString() val gettype = userService.getType(rstr_list) gettype.enqueue(object: Callback<List<RstrModel>>{ override fun onResponse( call: Call<List<RstrModel>>, response: Response<List<RstrModel>> ) { if ( response.isSuccessful ){ val arealist = response.body() binding.recyclerView.adapter = MyAdapter(this@SearchActivity, arealist) binding.recyclerView.addItemDecoration(DividerItemDecoration(this@SearchActivity, LinearLayoutManager.VERTICAL)) } } override fun onFailure(call: Call<List<RstrModel>>, t: Throwable) { call.cancel() } }) } } }
개발자
#스프링부트
#안드롱디ㅡ
#안드로이드
#spring\
#spring
답변 1
댓글 0
보충이 필요해요 3
조회 181
2년 전 · 커리어리 AI 봇 님의 새로운 답변
리액트로 스프링과 웹소켓 채팅방을 구현했는데 자동 랜더링이 안됩니다..
안녕하세요! 현재 웹소켓으로 스프링과 채팅기능을 구현중에 있습니다 채팅방에서 채팅을 보내고 받는 건 가능한 상태인데 같이 채팅방에 입장해서 A가 B한테 보냈을 때 B가 페이지를 새로고침 하지 않으면 채팅이 자동 랜더링이 되지 않는 상황인데 여러 방법을 참고하고 해봤지만.. 성공하지 않았습니다 어떻게 풀어나가야 할지 잘 모르겠습니다 ㅠㅠ 코드가 길지만 ... 혹시 답변이 가능할까해서 참고해봅니다 좋은 키워드도 추천해주시면 감사하겠습니다!!... export const ChatRoomPage = () => { //메뉴 모달 const [isModalOpen, setIsModalOpen] = useState(false); const [isExitModalOpen, setIsExitModalOpen] = useState(false); const [backgroundPosition, setBackgroundPosition] = useState('static'); const location = useLocation(); const params = location.pathname; const segments = params.split('/'); const RoomUniqueId = segments[4]; const RoomId = segments[5]; const [messageData, setMessageData] = useState([]); const [messageList, setMessageList] = useState([]); const [message, setMessage] = useState(''); const accesskey = Cookies.get('Access_key'); // 채팅방 입장시 안내 문구 기능 const [showModal, setShowModal] = useState(false); const client = useRef({}); useEffect(() => { console.log('유즈이펙트 쉴행'); setShowModal(true); connect('L'); return () => disconnect(); }, []); const connect = type => { client.current = new StompJs.Client({ brokerURL: 'ws://222.102.175.141:8081/ws-stomp', connectHeaders: { Access_key: `Bearer ${accesskey}`, }, debug: function (str) { console.log('str ::', str); }, onConnect: () => { if (type === 'L') { subscribe(); publish(); } else { subscribe1(); publish1(); } }, }); client.current.webSocketFactory = function () { return new SockJS('http://222.102.175.141:8081/ws-stomp'); }; client.current.activate(); return () => disconnect(); }; const subscribe = () => { client.current.subscribe(`/sub/chat/messageList/${localStorage.memberUniqueId}`, message => { // console.log('messageData11 : ', JSON.parse(`${message.body}`)); setMessageData(JSON.parse(`${message.body}`)); const data = JSON.parse(`${message.body}`); setMessageList(data.data.chatMessageList); }); }; const publish = () => { client.current.publish({ destination: `/pub/chat/messageList/${localStorage.memberUniqueId}`, body: JSON.stringify({ chatRoomId: RoomId, chatRoomUniqueId: RoomUniqueId, page: 0, }), }); }; const closeModal = () => { setIsModalOpen(false); setBackgroundPosition('static'); }; const openModal = () => { setIsModalOpen(true); setBackgroundPosition('fixed'); }; const handleBackdropClick = e => { console.log('e ::', e); if (e.target === e.currentTarget) { closeModal(); } }; const ExitopenModal = () => { setIsExitModalOpen(true); }; const ExitcloseModal = () => { setIsExitModalOpen(false); }; const ReportButtonHandler = () => { alert('곧 업데이트 예정입니다!'); }; // 채팅 보내기 const sendMessage = message => { console.log('message :: ', message); connect(); setMessage(''); return () => disconnect(); }; const subscribe1 = () => { client.current.subscribe(`/sub/chat/message/${RoomUniqueId}`, message => { setMessageData({ ...messageList, message }); }); }; const publish1 = () => { client.current.publish({ destination: `/pub/chat/message/${RoomUniqueId}`, body: JSON.stringify({ memberId: `${localStorage.memberId}`, memberName: `${localStorage.memberName}`, memberUniqueId: `${localStorage.memberUniqueId}`, memberProfileImage: `${localStorage.profileImage}`, chatRoomId: RoomId, chatRoomUniqueId: RoomUniqueId, message: message, }), }); }; const disconnect = () => { client.current.deactivate(); }; console.log('messageList :: ', messageList); return ( <> <div style={{ width: '100%', height: '100%', position: backgroundPosition, }} > <Background> <Topbar> <Link to={`${PATH_URL.PARTY_CHAT}/${localStorage.memberUniqueId}`}> <TopBackDiv> <LeftBack /> </TopBackDiv> </Link> <TopbarName>모임이름</TopbarName> <ModalBtn onClick={() => { openModal(); }} > <RoomMenuIcon /> </ModalBtn> </Topbar> <Container> <Contents> <ParticipantDiv>ㅇㅇㅇ님이 참여했습니다.</ParticipantDiv> {messageList?.map((data, index) => { return ( <OtherDiv key={index}> <div style={{ position: 'relative', }} > <OtherImg> <OtherProfile> <img src={data.memberProfileImage} alt="profile" style={{ width: '100%', height: '100%', borderRadius: '8px', }} /> </OtherProfile> <OtherHostIcon> <PartHostIcon /> </OtherHostIcon> </OtherImg> <OthertInfo> <OtherName>{data.sender}</OtherName> <OtherContents> <OtherChatText>{data.message}</OtherChatText> <OtherChatTime>12:19 pm</OtherChatTime> </OtherContents> </OthertInfo> </div> </OtherDiv> ); })}
개발자
#채팅
#웹소켓
#채팅기능
답변 2
댓글 0
조회 595
2년 전 · 커리어리 AI 봇 님의 새로운 답변
안드로이드 블루투스 권한 요청
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) if (bluetoothAdapter == null) { Toast.makeText(this, "블루투스를 지원하지않는 기기입니다", Toast.LENGTH_SHORT).show() finish() } setContentView(binding.root) binding.btn3.setOnClickListener { checkBlueToothConnectPermission() } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_BLUETOOTH_CONNECT) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 권한이 허용된 경우 처리할 코드 } else { Toast.makeText(this, "BLUETOOTH_CONNECT 권한이 필요합니다.", Toast.LENGTH_SHORT).show() } } } fun checkBlueToothConnectPermission() {//앱에서 블루투스 연결 권한이 있는지 확인하고 없는경우 권한을 요청하는데 사용됨 if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED ) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.BLUETOOTH_CONNECT)){ AlertDialog.Builder(this) .setTitle("권한 요청") .setMessage("블루투스 연결 권한이 필요합니다.") .setPositiveButton("확인") { _, _ -> ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.BLUETOOTH_CONNECT), PERMISSION_REQUEST_BLUETOOTH_CONNECT ) } .setNegativeButton("취소", null) .show() }else{ ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.BLUETOOTH_CONNECT), PERMISSION_REQUEST_BLUETOOTH_CONNECT ) } } } 버튼을 눌러서 권한요청을하는데 거부를 했다면 다시한번 눌렀을때 다시권한요청을 하고싶은데 어떻게 하시는지 아시는분 계신가요ㅜ 한번 거부가 되면 요청다이얼로그가 뜨지않는걸 알고 대화상자로 처리할라고하는데 대화상자도 안뜨네요 ㅜ
개발자
#안드로이드
#권한처리
#블루투스
답변 2
댓글 1
조회 366
2년 전 · 커리어리 AI 봇 님의 새로운 답변
리액트에서 closest 써도 컨벤션에 어긋나지 않을까요?
아래처럼 상위 노드를 찾기 위해 이벤트 버블링을 하려고 하는데요.. closest와 querySelector와 같은 메서드를 사용하면 구조에 종속적인 코드가 되기 때문에, 구조에 종속되지 않는 방법을 찾아보는 것이 좋다는 내용을 어디서 봐서 ㅠㅠ 고민이 됩니다. closest를 사용하지 않고 반복문으로 생성되는 컴포넌트마다 onClick 함수를 추가하는 방식이 더 나을까요? 의견 좀 주세여 ㅠㅠ
개발자
#queryselector
답변 3
댓글 0
조회 108
2년 전 · 커리어리 AI 봇 님의 새로운 답변
페이지 이동시에 필요한 정보들을 어떻게 전달할지 고민중입니다.
페이지 이동시에 필요한 정보들이 있는 경우 현재 recoil을 사용하여 1. Link 태그 onClick시 recoil을 이용해 상태 저장 2. 새로운 페이지 이동에서 useRecoilValue로 사용중인데 이게 맞는 방법인지 모르겠어서 여쭤보려고 합니다. 보통 하위 페이지가 아닌 다른 페이지 이동할 때 전달해야 하는 값이 있다면 어떤 방법을 사용하는게 가장 좋나요?
개발자
#react
#recoil
#navigation
답변 2
댓글 0
조회 451
2년 전 · 김석현 님의 답변 업데이트
useInput 훅의 onChange 연속 2번 사용하는법
function useInput(initialState) { const [state, setState] = useState(initialState); const onChange = (e) => { console.log(e, 'onchange') const { target: { name, value }, } = e; setState({ ...state, [name]: value, }); }; return { state, onChange, }; } 이러한 useInput 훅이 있습니다. 어떠한 페이지에서 const {state, onChange} = useInput(); 이라고 코드를 선언 한 후에 onChange를 두번 연속으로 코드를 작성하면 정상적으로 동작할까 궁금해서 해봤는데 마지막에 선언된 값만 state에 들어가더라구요 이유가 무엇인가요?? const onClick = () => { onChange({ target: {name : "AAA", value: "1번" }}); onChange({ target: {name : "BBB", value: "2번" }}); } 결과 : 2번값만 들어가있음
개발자
#react
#useinput
답변 1
댓글 0
추천해요 1
조회 304
2년 전 · 진라 님의 새로운 댓글
react 그리드에서 수정된 부분이 있으면 조회버튼을 눌렀을 때 alert창 띄우기 기능 (useState 비동기 질문)
안녕하세요. react 초보 개발자입니다. 자바스크립트도 처음이라 비동기 관련해서 제 의도와는 다르게 작동할 때마다 당황하는 중입니다🤣 쌩초보임을 감안해 너그럽게 댓글 달아주시면 감사하겠습니다 :) 화면 설명 먼저 드리자면 조회 버튼을 누르면 그리드에 데이터가 뜨고, 데이터를 수정한 후에 저장할 수 있는 화면입니다! 제목과 같이 그리드에서 수정된 부분이 있을 경우에, 저장하지 않고 조회버튼을 누르면 '수정중입니다. 조회를 진행하시겠습니까?' 와 같은 alert창을 띄우는 기능을 구현중에 있습니다. 참고로 그리드는 aggrid 사용중입니다. 1. 우선 변경값 존재여부를 확인하는 변수 isChanged 를 useState로 선언해주었습니다. const [isChanged, setIsChanged] = useState<boolean>(false); 2. 그리드에서 수정된 부분이 있나 체크하는 건 onCellValueChanged 함수를 통해 값이 변경되면 isChanged가 true로 바뀌게 했습니다. const onCellValueChanged = (params: any) => { setIsChanged(true); }; 3. 저장로직에는 setIsChanged(false)가 있습니다. 또한 저장이 제대로 끝난 후에 조회로직을 탑니다. const onClickSave = async () => { // (생략) 저장 로직 // 저장 완료 후 setIsChanged(false); onClickSearch(); } 4. 조회로직에는 isChanged의 값을 확인하고, 변경값이 있다면 alert창이 뜨고, 확인을 누르면 조회를 진행합니다. const onClickSearch = async () => { if (isChanged) { // (생략) 메세지 띄우는 로직. alert창에서 확인을 누르면 조회 진행 setIsChanged(false); } // (생략) 조회 로직 } * 문제점 정상적으로 저장을 한 후에 조회로직을 타는데, isChanged가 false로 변경되기전에 조회로직을 타서 alert창이 뜹니다. 이 문제는 어떻게 해결하는 게 좋을까요..?
개발자
#react
답변 2
댓글 6
추천해요 1
조회 375
2년 전 · 성진영 님의 답변 업데이트
javascript+react에서 글자 복사
React 초보입니다. 컨텐츠를 복사할 때 사용자에게 알림을 주려고 합니다. 아래의 코드는 command+c를 누를 때 alert를 주는 코드예시입니다. metaKey가 true이고 c를 누를 때를 잘 고려했다고 생각했는데 사수분이 코드리뷰를 하시고는 다시 고쳐보라고 하셨습니다. 두 가지 힌트를 주셨는데요 1. 마우스로 복사할 때는 어떻게 될까? 2. 윈도우에서는 어떻게 될까? 1번에 대해서는 마우스로 복사할 때는 onClick으로 우클릭을 감지한다고 치고.. 복사하기를 클릭하는건 어떻게 판별할 수 있을까요? 2번은 제가 mac으로 개발중이라 고려하지 못했는데 아마 metaKey가 아닌거겠죠???
개발자
#react
#javascript
#copy-text
답변 2
댓글 2
조회 412
2년 전 · 😎 님의 답변 업데이트
input 유효성 검사
제목 그대로 input 입력 유효성 검사를 하려고 하는데, 제출하기 버튼 클릭 시 팝업이 뜨기 전 alert("입력해주세요")를 나오게 하려고 합니다. 현재 제출하기 버튼에 onclick="팝업" 이런 상태인데, 유효성 검사를 마친 후 팝업이 뜨게 할 수 있으려면 어떻게 해야할까요?
개발자
#javascript
답변 1
댓글 0
조회 136
2년 전 · 손정현 님의 새로운 답변
react에서 동일한 형태 메뉴 버튼이 여러개 있을때 이벤트 리슨하는 방법
메뉴 바에 동일한 메뉴 버튼이 여러개 있는데, 클릭할 때 클릭한 메뉴 아래에 선을 만들고 싶습니다. 그런데 아래와 같이 코드를 넣으니 모든 메뉴 아래에 선이 생깁니다. 어떻게 해결 가능할까요? const [menuUnderbar, setMenuUnderbar] = useState(false); const underbarToggle = (e) => { setMenuUnderbar(true); }; .... <MenuButton onClick={underbarToggle} className={menuUnderbar === true ? "selected" : null} >
개발자
#react
답변 1
댓글 0
추천해요 1
조회 352
2년 전 · 커리어리 AI 봇 님의 새로운 답변
렌더링 이후 스크롤을 맨 아래로 내리고 싶어요.
안녕하세요 React를 이용해 웹페이지를 만들고 있습니다. 현재 구현하고 싶은 기능은 버튼을 클릭하면 state를 변경하고 state가 true이면 하단에 여러 이미지들이 보이고 스크롤을 아래로 내리는 작업을 동시에 하려고 합니다. onClick 부분에 setIsOpen(true)로 변경하여 보이도록 하고 window.scrollTo({ top: document.body.scrollHeight })를 이용해서 구현했는데 사진은 보이는데 스크롤이 안내려갑니다. 무엇을 수정해야할까요??? 아래는 코드 예시입니다.
개발자
#react
#scroll
답변 3
댓글 0
추천해요 6
조회 2,535
2년 전 · 커리어리 Q&A 운영자 님의 질문 업데이트
react의 setState가 정상동작하지 않아요.
react를 이용해 이것저것 테스트해보고 있는데요. 버튼을 눌렀을 때 state를 1 증가시키는 예제를 해보고 있습니다. const oneCountUp = () => { setCount(count + 1); }; 위처럼 1 증가시키는 함수를 정의해서 onClick에서 위의 함수를 호출하는 방식으로 했습니다. 근데 호기심에 3을 증가시켜보자 하고 oneCountUp을 3번 호출했습니다. 근데 여전히 1만 올라갑니다. 왜 그런걸까요??? import React, { useState } from 'react'; export default function CountTest() { const [count, setCount] = useState(1); const oneCountUp = () => { setCount(count + 1); }; return ( <div> <button onClick={() => { oneCountUp(); oneCountUp(); oneCountUp(); }} > 1 증가 </button> <p>{count}</p> </div> ); }
개발자
#react
#usestate
답변 1
댓글 0
추천해요 1
조회 229
2년 전 · 성원 님의 새로운 답변
React에서는 이벤트 버블링이 발생하지 않나요?
javascript를 배울 때 부모, 자식 컴포넌트가 같은 이벤트를 핸들링하고 자식 컴포넌트의 이벤트가 실행되면 버블링이 발생하는걸로 알고있습니다. ex) p태그를 클릭하면 p -> div -> form으로 alert이 세번 실행됨. <form onclick="alert('form')">FORM <div onclick="alert('div')">DIV <p onclick="alert('p')">P</p> </div> </form> React에서는 왜 이벤트 버블링이 발생하지 않나요? (몇 번 e.stopPropagation으로 막아주긴 했는데 기본적으로는 동작하지 않는 것 같아요) React로 실무를 하고있지만 한번도 이벤트 버블링을 신경쓴적이 없어서 문득 궁금해졌네요.
개발자
#react
#프론트엔드
답변 1
댓글 0
추천해요 3
조회 280
2년 전 · 커리어리 Q&A 운영자 님의 새로운 댓글
React.memo로 감싼 컴포넌트가 계속 렌더링 됩니다.
부모 컴포넌트에서 map으로 자식 컴포넌트를 렌더링 하고 있습니다. 자식 컴포넌트가 받는 props에 변화가 없어서 React.memo로 감쌌는데 컴포넌트가 계속 렌더링 되고 있습니다. props도 확인했습니다.. props는 {userId:number, onClick: () => {history.push(...)}} 이런식으로 넘겨주고 있습니다. 해결하고 싶은데 도움주시면 감사하겠습니다!
개발자
#react
답변 1
댓글 1
추천해요 2
조회 119