해커톤 특집 틈새 프로젝트
해커톤을 진행하던 와중, 해커톤을 통해 만들어지는 피신을 조회하고 가입할 수 있는 인트라넷을 구현해서 배포해보자는 제안을 받았다. 처음에는 되면 좋고 안되면 말고 같은 가벼운 생각으로 승낙했는데, 어떻게 하다보니 속도가 붙어서 정말 배포도 꿈이 아닌 단계까지 와버렸다. 마감을 앞두니 초인적인 힘이 생기는 걸까.
구현 과정
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import {
Entrance,
Login,
Main,
MyPiscine,
RegisterPiscine,
MySubjectView,
Register,
} from "pages";
const Routes = () => {
return (
<>
<Router>
<Switch>
<Route exact path="/" component={Entrance} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<Route exact path="/main" component={Main} />
<Route exact path="/myPiscine/:index" component={MyPiscine} />
<Route
exact
path="/myPiscine/view/:index"
component={MySubjectView}
/>
<Route
exact
path="/registerPiscine/:index"
component={RegisterPiscine}
/>
</Switch>
</Router>
</>
);
};
export default Routes;
구조
라우트 컴포넌트에서 모든 경우에 대한 라우트를 처리하고, 각 컴포넌트 마다 Link
로 URL을 이동할 수 있게끔 했다.
맨 처음에는 컴포넌트에서 로그인을 시도하는데, 기존에 만들어둔 42 API를 이용해서 유효한 인트라 아이디만 로그인 컴포넌트에서 패턴을 시도할 수 있게끔 해주었다.
중요
42 API 적용
내용이 너무 길어지기에 링크를 첨부했다.
Link로 값을 보내야 할 때
Link 컴포넌트는 클릭하면 다른 주소로 이동시키는 컴포넌트인데, 브라우저의 주소를 바꾸는 역할을 한다. 보통 라우터와 같이 쓰이면서 주소가 이동되면 라우터는 이동된 주소에 할당된 컴포넌트를 렌더링하게 된다.
Router Props, link로 전달하는 props
패키지 적용
PatternLock
이라는 패키지를 사용해서 간단한 로그인을 구현해보기로 했다. npm 문서를 보면서 라이브러리를 적용해보았는데, 잘 정리되어 있어서 어렵지 않게 적용하는데 성공했다.
중첩 &&, 삼항 연산자 기법
로그인 컴포넌트에서 총 3가지의 상황에 맞는 화면을 렌더링하여야 한다.
- 입력받은 인트라 아이디를 42API에 보내 응답을 받기 전의 경우 - 아무것도 보여주지 않거나 로딩창을 렌더링 해야 한다.
- 42 API에 대한 응답을 받았고, 해당 아이디가 유효한 아이디의 경우(error가 아닌 경우) - PatternLock 컴포넌트를 렌더링 해야 한다.
- 42 API에 대한 응답을 받았고, 해당 아이디가 유효하지 않은 아이디인 경우(error인 아닌 경우) - 로그인 창으로 다시 돌아가는 Link 컴포넌트를 렌더링 해야 한다.
return (
<div className="login-page">
{idResult && (
<>
{idResult !== 'error' ? (
<>
<h1 className="title">WMPB</h1>
<h1>id : {id}</h1>
<PatternLock
width={300}
pointSize={15}
size={3}
path={path}
onChange={pattern => {
handleChangePath(pattern);
}}
onFinish={handleFinish}
/>
</>
) : (
<Link to="/">
<div>존재하지 않는 아이디! 클릭해서 돌아가세요!</div>
</Link>
)}
</>
)}
</div>
);
내 코드에서 이를 식별하는 state는 idResult이다. 맨처음 초기화된 값은 "", false이기 때문에 1의 경우에는 && 뒤의 jsx 코드를 렌더링하지 않는다.
idResult가 false가 아니라면 인트라 아이디든 error든 무언가가 들어갔다는 의미, 그것을 또다시 삼항 연산자로 구분하게 한다.
위의 조건문을 충족한다면 존재하는 인트라 아이디기에 2의 경우에 해당하여 로그인 창을 렌더링하고, 그렇지 않다면 존재하지 않는 아이디기에 3의 경우에 해당하여 다시 돌아가라는 Link 가 렌더링 되게 된다.
이슈
fetch의 동기 처리(await)이 제대로 안 걸리는 이슈
import { React, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import PatternLock from 'react-pattern-lock';
import getToken from 'utils/getToken';
import getUserId from 'utils/getUserId';
import './Login.scss';
const Login = ({ location }) => {
const id = location.state.userId;
const [path, setPath] = useState([]);
const [token, setToken] = useState('');
const [idResult, setIdResult] = useState('');
const handleChangePath = pattern => {
setPath(pattern);
};
const handleFinish = () => {
const message = `설정한 패턴은 ${path}입니다!`;
alert(message);
setPath([]);
setIsFinish(true);
};
const tempSetToken = asdf => {
setToken(asdf);
return asdf;
};
// 삭제해도 되는 코드
useEffect(() => {
const fetchId = async () => {
const response = await getToken();
const check = await tempSetToken(response.access_token);
console.log('check');
console.log(check);
console.log('token');
console.log(token);
const result = await getUserId(id, token);
console.log(result);
setIdResult(result);
};
fetchId();
}, []);
// 삭제해도 되는 코드
useEffect(() => {
console.log(path);
}, [path]);
return (
<div className="login-page">
{idResult && (
<>
{idResult !== 'error' ? (
<>
<h1 className="title">WMPB</h1>
<h1>id : {id}</h1>
<PatternLock
width={300}
pointSize={15}
size={3}
path={path}
onChange={pattern => {
handleChangePath(pattern);
}}
onFinish={handleFinish}
/>
</>
) : (
<Link to="/">
<div>존재하지 않는 아이디! 클릭해서 돌아가세요!</div>
</Link>
)}
</>
)}
</div>
);
};
export default Login;
import { React, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import PatternLock from 'react-pattern-lock';
import getToken from 'utils/getToken';
import getUserId from 'utils/getUserId';
import './Login.scss';
const Login = ({ location }) => {
const id = location.state.userId;
const [path, setPath] = useState([]);
const [token, setToken] = useState('');
const [idResult, setIdResult] = useState('');
const handleChangePath = pattern => {
setPath(pattern);
};
const handleFinish = () => {
const message = `설정한 패턴은 ${path}입니다!`;
alert(message);
setPath([]);
setIsFinish(true);
};
const tempSetToken = asdf => {
setToken(asdf);
return asdf;
};
// 삭제해도 되는 코드
useEffect(() => {
const fetchId = async () => {
const response = await getToken();
const check = await tempSetToken(response.access_token);
console.log('check');
console.log(check);
console.log('token');
console.log(token);
const result = await getUserId(id, token);
console.log(result);
setIdResult(result);
};
fetchId();
}, []);
// 삭제해도 되는 코드
useEffect(() => {
console.log(path);
}, [path]);
return (
<div className="login-page">
{idResult && (
<>
{idResult !== 'error' ? (
<>
<h1 className="title">WMPB</h1>
<h1>id : {id}</h1>
<PatternLock
width={300}
pointSize={15}
size={3}
path={path}
onChange={pattern => {
handleChangePath(pattern);
}}
onFinish={handleFinish}
/>
</>
) : (
<Link to="/">
<div>존재하지 않는 아이디! 클릭해서 돌아가세요!</div>
</Link>
)}
</>
)}
</div>
);
};
export default Login;
tempSetToken라는 함수는 setToken으로 token에 토큰의 값을 설정해줌과 동시에 check에 동일한 값을 리턴해준다. 같은 함수에서, 심지어 리턴하기 전에 값을 변경했기에 token의 값은 당연히 갱신되었을 거라고 생각했지만 콘솔에는 await으로 리턴받은 check에만 토큰이 들어있고 token은 텅텅 비어있었다.
import { React, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import PatternLock from 'react-pattern-lock';
import getToken from 'utils/getToken';
import getUserId from 'utils/getUserId';
import './Login.scss';
const Login = ({ location }) => {
const id = location.state.userId;
const [path, setPath] = useState([]);
const [idResult, setIdResult] = useState('');
const [isFinish, setIsFinish] = useState(false);
let test;
const handleChangePath = pattern => {
setPath(pattern);
};
const handleFinish = () => {
const message = `설정한 패턴은 ${path}입니다!`;
alert(message);
setPath([]);
setIsFinish(true);
};
const modifyToken = token => {
test = token;
return token;
};
// 삭제해도 되는 코드
useEffect(() => {
const fetchId = async () => {
const response = await getToken();
const token = await modifyToken(response.access_token);
console.log('token');
console.log(token);
console.log('test');
console.log(test);
const result = await getUserId(id, token);
console.log(result);
setIdResult(result);
};
fetchId();
}, []);
// 삭제해도 되는 코드
useEffect(() => {
console.log(path);
}, [path]);
return (
<div className="login-page">
{idResult && (
<>
{idResult !== 'error' ? (
<>
<h1 className="title">WMPB</h1>
<h1>id : {id}</h1>
<PatternLock
width={300}
pointSize={15}
size={3}
path={path}
onChange={pattern => {
handleChangePath(pattern);
}}
onFinish={handleFinish}
/>
</>
) : (
<Link to="/">
<div>존재하지 않는 아이디! 클릭해서 돌아가세요!</div>
</Link>
)}
</>
)}
</div>
);
};
export default Login;
위와 같이 단순한 함수에 await으로 토큰을 리턴 받게끔 하니 정상적으로, 동기적으로 잘 작동하였다.
위 이슈를 겪으면서 코드 흐름 상에는 더 우위(위쪽)에 있다고 하더라도 await으로 값을 받는 것을 확정하지 않으면 순서에서 밀릴 수 있는 것인가 의문이 들었는데, 간단한 테스트로 그건 아니라는 것이 밝혀졌다.
setState는 즉시 적용되지 않는다.
const id = location.state.userId;
const [path, setPath] = useState([]);
const [idResult, setIdResult] = useState('');
const [isFinish, setIsFinish] = useState(false);
let test;
const handleChangePath = pattern => {
setPath(pattern);
};
const handleFinish = () => {
const message = `설정한 패턴은 ${path}입니다!`;
alert(message);
setPath([]);
setIsFinish(true);
};
const modifyToken = token => {
test = token;
return token;
};
// 삭제해도 되는 코드
useEffect(() => {
const fetchId = async () => {
const response = await getToken();
const token = await modifyToken(response.access_token);
console.log('token');
console.log(token);
console.log('test');
console.log(test);
const result = await getUserId(id, token);
console.log(result);
setIdResult(result);
};
fetchId();
}, []);
// 삭제해도 되는 코드
위 코드에서 test라는 변수를 만들고 token이 await으로 토큰을 리턴받기 전에 test에도 토큰을 할당해주었는데, 콘솔을 찍어보니 test에도 토큰의 값이 들어간 것을 확인했다.
setState, 즉 useState에서 state의 값을 변경하는 함수가 즉각적으로 실행되지 않기 때문에 발생한 이슈였던 것이다. 아니, 어쩌면 state로 값이 변하게 되면서 리렌더링을 하게 되는 과정에서 useEffect가 다시 실행되는 등 무언가가 꼬였을 수도 있다.
이 이슈를 겪으며 state를 변경할 때는 컴포넌트를 리렌더링하는 된다는 점을 명심하게 되었다.
Author And Source
이 문제에 관하여(해커톤 특집 틈새 프로젝트), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sham/해커톤-특집-틈새-프로젝트저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)