styled-components를 이용해 Login 페이지와 Signup 페이지의 UI를 먼저 만들어 놓는다.
// src/components/auth/FormBox/js
import styled from "styled-components";
import { BaseBox } from "../shared";
//styled()를 통해 해당 Component를 상속할 수 있다.
const Container = styled(BaseBox)`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 35px 40px 25px 40px;
margin-bottom: 10px;
form { //하위 요소를 component를 만들지 않고 바로 style할 수 있다.
margin-top: 35px;
width: 100%;
display: flex;
justify-items: center;
flex-direction: column;
align-items: center;
}
`;
//childrern은 하위 요소들을 다 포함 한다.
function FormBox({ children }) {
return <Container>{children}</Container>;
}
export default FormBox;
App.js에서 최상위에 HelmetProvider라는 componenet를 생성한다.
//App.js
import { HelmetProvider } from "react-helmet-async";
function App() {
const isLoggedIn = useReactiveVar(isLoggedInVar);
const darkMode = useReactiveVar(isDarkModeVar);
return (
<HelmetProvider>
<ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
<GlobalStyles />
<Router>
<Switch>
.
.
.
Helmet component를 이용한 title component를 만들어 다른 components에서 사용한다.
//PageTittle.js
import { PropTypes } from "prop-types";
import { Helmet } from "react-helmet-async";
function PageTitle({ title }) {
return (
<Helmet>
<title>{title} | Instaclone</title>
</Helmet>
);
}
PageTitle.propTypes = {
title: PropTypes.string.isRequired,
};
export default PageTitle;
useForm을 이용하면 Form에 관련된 유효성검사나 error처리 같은 여러 기능들을 사용할 수 있다.
useForm에서 mode를 설정할 수 있는데 크게 3가지가 있다.
// Login.js
function Login() {
const { register, handleSubmit, formState } = useForm({
mode: "onChange",
});
const onSubmitValid = (data) => {
console.log(data);
};
const onSubmitInvalid = (data) => {
console.log(data, "invalid");
};
return (
<AuthLayout>
<PageTitle title="Login" />
<FormBox>
<div>
<FontAwesomeIcon icon={faInstagram} size="3x" />
</div>
<form onSubmit={handleSubmit(onSubmitValid, onSubmitInvalid)}>
<Input {...register("username", {
required: "Username is required",
minLength: {
value: 5,
message: "Username should be longer than 5 chars.",
},
})} type="text" placeholder="Username"
hasError={Boolean(formState.errors?.username?.message)} />
<FormError message={formState.errors?.username?.message} />
<Input {...register("password", {
required: "Password is required",
})} type="password" placeholder="Password"
hasError={Boolean(formState.errors?.password?.message)} />
<FormError message={formState.errors?.password?.message} />
<Button type="submit" value="Log in" disabled={!formState.isValid} />
</form>
BE와 연결하기 위해 apollo client를 사용한다.
client를 생성하고 app.js에서 최상위에 ApolloProvider 태그를 만들어준다.
//apollo.js
export const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache(),
})
//App.js
function App() {
const isLoggedIn = useReactiveVar(isLoggedInVar);
const darkMode = useReactiveVar(isDarkModeVar);
return (
<ApolloProvider client={client}>
<HelmetProvider>
<ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
<GlobalStyles />
<Router>
.
.
.
graphql로 BE와 대화하기 위해 mutation을 만들고 useMutation을 통해 login함수와 loading변수를 가져온다.
useFrom의 getValues를 통해 form의 value들을 가져오고 setError를 통해 예외처리를 한다. 그리고 다시 clearErrors를 통해 에러를 없애준다.
//Login.js
const LOGIN_MUTATION = gql`
mutation login($username: String!, $password: String!){
login(username: $username, password: $password){
ok,
token,
error,
}
}
`;
function Login() {
const { register, handleSubmit, formState, setError, clearErrors, getValues } = useForm({
mode: "onChange",
});
const onCompleted = (data) => {
const { login: {
ok, token, error
} } = data;
if (!ok) {
return setError("result", {
message: error
});
}
if (token) {
logUserIn(token);
}
};
const [login, { loading }] = useMutation(LOGIN_MUTATION, {
onCompleted,
});
const onSubmitValid = (data) => {
if (loading) {
return;
}
const { username, password } = getValues();
login({
variables: { username, password }
});
};
.
.
.
token을 localStorage에 저장하고 확인할 수 있도록 한다. 새로고침을 해도 로그인이 유지되도록 한다.
//apollo.js
const TOKEN = "token";
export const isLoggedInVar = makeVar(Boolean(localStorage.getItem(TOKEN)));
export const logUserIn = (token) => {
localStorage.setItem(TOKEN, token);
isLoggedInVar(true);
};
export const logUserOut = () => {
localStorage.removeItem(TOKEN);
isLoggedInVar(false);
};
회원갑입도 위와 동일하게 만들어준다. 그리고 회원가입 성공시 Login창으로 message, username, password를 가지고 넘어간다.
history를 통해 보내준다.
//Signup.js
const history = useHistory();
const onCompleted = (data) => {
const { username, password } = getValues();
const {
createAccount: { ok, error },
} = data;
if (!ok) {
return setError("result", {
message: error
});
} // (path, 넘길 인자들)
history.push(routes.home, {
message: "Account created. Please log in.",
username,
password,
});
};
location을 통해 받는다.
//Login.js
const location = useLocation();
const { register,
handleSubmit,
formState,
setError,
clearErrors,
getValues
} = useForm({
mode: "onChange",
defaultValues: {
username: location?.state?.username || "",
password: location?.state?.password || "",
},
});