seminar-2022
seminar-2022 copied to clipboard
React 세미나2 로그인 안된 상태에서는 접근금지
import './New.css';
import {useCounterContext} from "./CounterContext";
import Header from "./Header";
import {useNavigate} from "react-router-dom";
import React, {useState} from "react";
function New() {
const {addMenu, menus, loginStatus} = useCounterContext();
const [enteredNum, setEnterdNum] = useState("");
const changeEnteredNum = (e) => {
const value: string = e.target.value;
const removedCharacterValue = value.replace(/[^0-9]/g, '');
const removedCommaValue: number = Number(removedCharacterValue.replaceAll(",", ""));
setEnterdNum(removedCommaValue.toLocaleString());
};
const navigate = useNavigate();
const Goout = () => {
navigate("/stores/1");
};
const addbutton = () => {
const inputName = document.getElementById('name').value;
const inputType = document.getElementById('typeselect').value;
const inputImage = document.getElementById('image').value;
const inputDes = document.getElementById('description').value;
if (inputName === "" || enteredNum === ""|| inputType=== "") {
alert("빼먹은 부분이 없느지 확인해주세요");
} else {
let isexist = menus.some((menu)=>menu.name===inputName);
if (isexist === false) {
if (inputName.length <= 20) {
if (Number(enteredNum.replaceAll(",", ""))>=100 && Number(enteredNum.replaceAll(",", ""))<=1000000) {
addMenu(inputName, enteredNum, inputImage, inputType, inputDes);
Goout();
}
else {
alert("가격이 범위에 맞지 않습니다. (100<price<1000000)");
}
}
else {
alert("이름이 너무 깁니다.");
}
}
else {
alert("동일한 이름의 제품이 이미 존재합니다.");
}
}
};
if (loginStatus === true)
{
return 1;
}
else
{
alert("GoOut");
navigate(-1);
return 0;
}
{/*return (
<>
<Header/>
<section className="Newsection">
<div className="title">새 메뉴 추가</div>
<div className="nameadd-str">이름</div>
<input type="text" placeholder="맛있는와플" id="name" className="nameadd-box"/>
<div className="typeadd-str">종류</div>
<select id="typeselect" className="typeadd-box">
<option value='' selected>상품의 종류를 선택하세요</option>
<option value='coffee'>커피</option>
<option value='waffle'>와플</option>
<option value='beverage'>음료</option>
</select>
<div className="priceadd-str">가격</div>
<input type="text" value={enteredNum} onChange={changeEnteredNum} placeholder="5,000"
className="priceadd-box"/>
<span className="unit">원</span>
<div className="imageadd-str">상품 이미지</div>
<input type="text" placeholder="이미지 주소를 입력해주세요" id="image" className="imageadd-box"/>
<div className="desadd-str">설명</div>
<textarea placeholder="상품에 대한 자세한 설명을 입력해주세요" id="description" className="desadd-box"></textarea>
</section>
<button className="add" onClick={addbutton}>
추가
</button>
<button className="close" onClick={Goout}>
취소
</button>
</>
);*/}
}
export default New;
menus/new에 loginstatus가 false면 접근하지 못하게 하려고 합니다. 일단 new 다 만들어두었고 그 기능만 추가하면 됩니다. 하지만 테스트하는 과정에서 alert는 두번 뜨고 navigate(-1)은 작동을 안하는 것을 확인했습니다. 어떻게 고쳐야하나요?
질문과는 관련 없지만 github 에 코드를 올릴 땐 color syntax 를 이용할 수 있습니다!
적용 전 | 적용 후 |
---|---|
|
|
이거 적용하면 코드가 훨씬 더 보기 편해져요 :) 자세한 내용은 문서 참고해주세요!
보통 navigate(-1)
하면 잘 되어야할텐데 이상하네요... 코드를 실행해봐야 정확한 원인을 알 수 있겠지만 일단은 -1 대신에 다른 숫자(-2,-3 등)를 넣어보면서 직접 디버깅해보시는 게 좋겠습니다. 또 alert는 코드 실행을 멈추는 side effect(?)도 있기 때문에 디버깅은 console.log
사용하시기 바랍니다.
안녕하세요! 비슷한 문제를 겪었는데 이게 정확한 해결방법이 아닌 미봉책 같지만 그래도 공유드립니다..!
- alert가 두 번 뜨는 문제 -> index.js에서 App 컴포넌트의 부모인 React.StrictMode를 없애(거나 주석처리해)주면 alert가 한 번만 뜨더라구요.
- navigate(-1)이 안 되는 문제 -> useNavigate 대신 Navigate를 사용해서 해당 함수 부분의 return 문에
return (
<Navigate to={-1} />
);
과 같이 써주면 이전 페이지로 이동되긴 하였습니다.
둘 다 왜 저런 문제가 발생하였는지는 모른 채로 우선 해결방법만 공유드리는데, 혹시 이유를 아신다면 추가로 코멘트 부탁드립니다!
위 코멘트에 이어서 질문과 관련된 코멘트를 해보면, navigate
함수를 render 레벨에서 사용하고 계신 것 같습니다. react-router-dom
소스코드를 뜯어보면, 이 부분에 의해 render 중에 실행한 navigate
함수는 아무 역할도 하지 않고 215번 줄에 의해 return 됩니다. 아마 콘솔을 열어보시면 해당 소스코드 211번 줄에 나와 있는 아래 문구가 보일 것 같은데, 맞을까요?
You should call navigate() in a React.useEffect(), not when your component is first rendered.
해결을 위해서는 경고문구에도 나와 있듯 navigate 로직을 render 레벨에서 effect 훅 내부 (혹은 그에 준하는 곳) 로 옮겨야 할 것 같습니다. 사실 이 이슈 때문이 아니더라도, 리액트에서 render 레벨에서는 side effect 가 발생하지 않는 코드를 짜야 하기 때문에, render 레벨 대신 effect 훅 내부에 해당 로직이 있는 게 맞습니다.
즉, 가령 아래처럼 되어야 할 거예요.
기존 | 변경 |
---|---|
|
|
좀더 예쁘게 짜면 이런 식으로 될 것 같네요 👀 (설명을 위해 주석 떡칠했습니다)
function CreateWafflePage() { // 뭐하는 컴포넌트인지는 모르겠지만.. new 는 동사니까 컴포넌트 이름에 적합한 네이밍은 아니긴 합니다. 역할에 맞게 이름을 적절하게 변경하면 좋을 것 같아요
// ...
useEffect(() => {
// 자바스크립트의 환장할 truthy falsy 때문에 호불호가 갈리긴 하지만, 간결함을 위해 (=== true) 는 생략하는 게 좀더 일반적입니다.
// 이 변수 이름도, ~status 라는 suffix 가 붙어있다면 boolean 값보다는 enum 이나 string 인 게 나아 보입니다. boolean 변수의 네이밍은 is~ prefix 가 붙는 게 일반적입니다. ex) isLoggedIn
if (loginStatus) return;
// 원래 여기 alert 가 있었는데, alert 는 사용하지 않는 게 좋습니다. 사용자에게 피드백을 주고 싶다면 toast 등 다른 형태의 ui를 이용하는 게 낫습니다.
navigate(-1);
}, [loginStatus, navigate]); // 굳이 매 렌더 이후마다 실행될 필요는 없는 로직이므로 dependency array 를 처리해 줍니다.
// ...
if (!loginStatus) return null; // 기존처럼 0 을 리턴하면 화면에 0 이 보여집니다. 화면에 아무것도 보여주지 않는 걸 의도하신 것 같은데, 이땐 null 을 리턴하면 됩니다. (null 대신 undefined, false 등등도 가능하지만, null이 제일 일반적입니다. 최근에 나온 react 18부턴 undefined 를 리턴할 수 있게 되어, 그냥 return; 으로 처리하는 것도 좋은 방법이 될 수 있습니다.)
// ...
}
@fluentmin
- alert가 두 번 뜨는 문제 -> index.js에서 App 컴포넌트의 부모인 React.StrictMode를 없애(거나 주석처리해)주면 alert가 한 번만 뜨더라구요.
저도 옛날에는 항상 귀찮아서 react.strictmode 를 항상 비활성화했었는데, 리액트 제대로 쓰려면 결국 strictmode 를 켜놓는게 많은 도움이 되더라구요! 그래서 웬만하면 켜두시는 걸 추천합니다 👍
https://ko.reactjs.org/docs/strict-mode.html
위 공식문서에도 나와 있듯 두번 호출되는 건 리액트에서 의도적으로 그렇게 처리한 거고, 개발 환경에서만 두 번씩 보여지며 프로덕션 빌드 시에는 한 번만 보여집니다!
@woohm402 남겨주신 코멘트와 링크들 살펴보며 몰랐던 부분들 많이 배웠습니다. 감사합니다! 리액트 공식문서와도 친해져야겠네요..ㅎㅎ