지금 만들고 있는 프로젝트에서는 로그인이 중요한 기능이 아니어서 이메일과 비밀번호 이름만 입력받아 더미 데이터에 있으면 오케이 하도록 계획했다. react-query의 useQUery 사용하다 기본적인 규칙을 놓친 게 있다는 것을 알게 되었다.
서버에서 가져온 데이터를 querykey를 통해 쉽게 관리할 수 있어서 react-query를 사용한거지만...
짜잔! 이런 문구가 뜬다..
내용을 보면 컴포넌트 함수 내에서 사용해야 되는데 잘못된 곳에서 사용했다는 의미다.
const fetchUser = async() => {
return await axios.get(`http://localhost:4000/users`)
};
export const Login = (loginData: Login) => {
const nav = useNavigate() **잘못된 사용**
return useQuery(['loginUser'], fetchUser, {
onError: (e: any) => {
alert(e.message)
},
onSuccess: ({ data }) => {
const result = data.find((user: Login) => {
user.email === loginData.email && user.password === loginData.password})
if(result) nav('/')
else alert('이메일 및 패스워드가 확인되지 않습니다 다시 입력해주세요!')
}
})
}
->로그인 폼 컴포넌트
로그인을 한 뒤 성공하면 메인 페이지로 가기 때문에 연결성을 위해 함수 컴포넌트 내에서가 아닌 useQuery를 리턴하는 함수 내에서 useNavigate를 선언해 주었다. 그런데 이런한 로직은 기본적인 react Hook 규칙에서 어긋난다.
const [ value, setValue ] = useState('cake')
if(value !== ' ') {
useEffect(() => {
alert(value)
}, [value])
}
만약 최상위가 아닌 조건부나 중첩된 함수 내에서 사용할경우, hook을 읽지 못하고 건너뛰어 버그를 발생시킨다. 만일 조건부를 사용하고 싶다면 아래와 같이 사용해야한다.
const [ value, setValue ] = useState('cake')
useEffect(() => {
if(value !== ' ') {
alert(value)
}
}, [value])
다른 함수에서 사용하고 싶은경우는 customHook함수 내에서 사용가능하다
그러나... 아직 문제는 해결되지 않았다.
로그인을 할 때 데이터를 조회해야 하니깐 get을 써야 되는 게 아닐까? 하지만 실무에서 보안상의 이유로 post를 사용한다고 한다.
여기서는 백엔드에서 만들어 놓은 서버가 없기 때문에
post를 사용할 수는 없고 get을 사용해서 흉내만 냈다.
react-query에서는 get을 useQuery를 사용하고 나머지 데이터를 변경해야 하는 post, patch, delete는 useMutation을 사용해야 한다.
react-query를 생각하지 않고 단순히 axios를 사용해서 서버 데이터를 가져온다 했을 때 reactdom를 렌더링 한 후 호출하는 것이 권장된다. 그래서 렌더링 직후 일어나는 useEffect에서 사용을 하는데 이렇게 하면 아주 작동이 잘 된다.
useEffect(() => {
const res = axios.get(url)
res.then(() => ...).catch(() => ...)
}, [])
useLogin()은 useQuery를 리턴하는 customHook함수
하지만 useQuery는 저 메시지가 그대로 뜬다. 그러나 이것도 useNavigate를 잘못 선언한 것과 같은 이유다!
react-query가 서버에서 받아오 데이터를 관리하는 것을 도와주는 라이브러리이지만 useQuery도 reactHook이다 그래서 hook의 규칙이 적용되어야 한다!!
-> 최상단으로 올려줌
로그인 폼이 나타났을 때 정상적으로 데이터를 가져올 수 있게 되었다!
그러나 여기서는 get을 사용해야 되는데 submit 이벤트에 전달해줘야 하는 함수에는 useQuery를 호출할 수 없다.
export default function LoginForm({onClick}: Props) {
const [ login, setLogin ] = useState({email: "", password: ""})
const queryClient = useQueryClient()
const{ data } = useLogin() // useQuery를 리턴하는 customHook
//먼저 useQuery를 통해 전체 값을 받아옴 //
const changeValue = (e: ChangeEvent<HTMLInputElement>) => {
setLogin({ ...login, [e.target.name]: e.target.value })
}
const submitValue = async(e: ChangeEvent<HTMLFormElement>) => {
e.preventDefault()
const result = data?.data.filter((user: Login) => {
return user.email === login.email && user.password === login.password}) // 받아온 데이터에서 입력값을 필터한 후
if(result !== undefined) {
queryClient.setQueryData(['@user'], result) // 값이 존재하면 그 값을 쿼리키에 세팅해줌
window.location.replace('http://localhost:3000/')
}
else alert('이메일 및 패스워드가 확인되지 않습니다 다시 입력해주세요!')
} // submit이벤트에 전달하는 함수
return (
<>
...
)
}
LoginForm 컴포넌트가 최상단에 useQuery를 호출하여 데이터를 가져온 후 submit 이벤트가 발생하면 입력값을 토대로 서버에서 가져온 데이터에서 값을 찾은 후 queryKey;'@user'에 다시 세팅해 주었다(getQueryData).
하지만!! 로그인한 순간 리렌더링이 발생하기 때문에 로직이 처음부터 재실행 된다. 그래서 로그인 로그아웃은 querkey보다는 localStorage를 관리해야 로그인 상태를 유지할 수 있고 로그아웃 까지 할 수 있었다.
export default function LoginForm({onClick}: Props) {
const [ login, setLogin ] = useState({email: "", password: ""})
const{ data } = useLogin()
const changeValue = (e: ChangeEvent<HTMLInputElement>) => {
setLogin({ ...login, [e.target.name]: e.target.value })
}
const submitValue = async(e: ChangeEvent<HTMLFormElement>) => {
e.preventDefault()
const result = data?.data.filter((user: Login) => {
return user.email === login.email && user.password === login.password})
if(result !== null) {
window.localStorage.setItem('key', result[0].id)
window.localStorage.setItem('email', result[0].email)
window.localStorage.setItem('name', result[0].name)
window.location.replace('http://localhost:3000/')
}
else alert('이메일 및 패스워드가 확인되지 않습니다 다시 입력해주세요!')
}
return (
<>
...
</>
)
}