redux를 적용하기 전에 LoginPage와 RegisterPage를 작성하도록 하겠습니다.
공통적으로 사용할 button, input을 작성하도록 하겠습니다.
import React from 'react';
import styled from 'styled-components';
import palette from '../../lib/styles/palettes';
const StyledInput = styled.input`
width: 100%;
height: 40px;
border: 2px solid #aaa;
border-radius: 4px;
margin: 8px 0;
outline: none;
padding: 8px;
box-sizing: border-box;
transition: .3s;
&:focus {
border-color: ${ palette.blue[2] };
box-shadow: 0 0 8px 0 ${ palette.blue[2] };
}
`;
const Input = props => <StyledInput { ...props } />
export default Input;
const Input = props => <StyledInput { ...props } />
여기서 props는 name, onChange, value 등 컴포넌트를 호출했을 때 받은 인자들이고 이 인자들을 그대로 StyledInput에 동일하게 넣어주겠다는 의미입니다.
import React from 'react';
import styled from 'styled-components';
import palette from '../../lib/styles/palettes';
const StyledButton = styled.button`
width: 100%;
height: 50px;
border-radius: 4px;
border: 2px solid ${palette.blue[1]};
margin: 8px 0;
padding: 8px;
box-sizing: border-box;
transition: .3s;
background-color: #ffffff;
color: ${palette.blue[1]};
font-size: 20px;
`;
const BorderButton = props => <StyledButton { ...props } />
export default BorderButton;
import React from 'react';
import styled, { css } from 'styled-components';
import palette from '../../lib/styles/palettes';
const StyledButton = styled.button`
width: 100%;
height: 50px;
border-radius: 4px;
border: none;
margin: 8px 0;
outline: none;
padding: 8px;
box-sizing: border-box;
transition: .3s;
background-color: ${palette.blue[1]};
color: #ffffff;
font-size: 20px;
&:hover {
width: 100%;
height: 50px;
border-radius: 4px;
border: none;
margin: 8px 0;
outline: none;
padding: 8px;
box-sizing: border-box;
transition: .3s;
background-color: ${palette.blue[2]};
color: #ffffff;
font-size: 20px;
}
${
props =>
props.red &&
css`
background: ${ palette.red[0] };
&: hover {
background: ${ palette.red[1] };
}
`
}
`;
const FullButton = props => <StyledButton { ...props } />
export default FullButton;
공통적으로 사용할 컴포넌트를 작성했으니 다음의 디렉토리를 만들어 LoginPage를 작성하도록 하겠습니다.
import React from 'react';
import styled from 'styled-components';
import palette from '../../lib/styles/palettes';
const AuthTemplateBlock = styled.div`
left: 0;
top: 0;
bottom: 0;
right: 0;
background: ${ palette.gray[2] };
padding-top: 130px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const WhiteBox = styled.div`
box-shadow: 0 0 8px rgba(0, 0, 0, 0.025);
padding: 2rem;
width: 80%;
background: white;
border-radius: 2px;
`;
const AuthTemplate = ({ children }) => {
return(
<AuthTemplateBlock>
<WhiteBox>
{ children }
</WhiteBox>
</AuthTemplateBlock>
);
};
export default AuthTemplate;
AuthTemplateBlock는 AuthForm 컴포넌트를 감싸주기 위한 컴포넌트입니다.
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import palette from '../../lib/styles/palettes';
import FullButton from '../common/FullButton';
import BorderButton from '../common/BorderButton';
import Input from '../common/Input'
const AuthFormBlock = styled.div`
h3 {
margin: 0;
color: ${ palette.gray[8] };
margin-bottom: 1rem;
}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const Header = styled.div`
margin-top: 20px;
margin-bottom: 30px;
width: 60%;
font-size: 25px;
padding-left: 10%;
color: ${palette.blue[2]};
font-weight: bold;
`;
const ErrorMessage = styled.div`
color: red;
text-align: center;
font-size: 14px;
margin-top: 1rem;
`;
const textMap = {
login: 'login',
register: 'register'
};
const AuthForm = ({
type,
form,
onSubmit,
error
}) => {
const text = textMap[type];
return(
<AuthFormBlock>
{ text === 'login' ? (
<>
<Header>
물건 대여 플랫폼에 <br />
오신 것을 환영합니다!
</Header>
<form onSubmit={ onSubmit }>
<Input
autoComplete="email"
name="email"
placeholder="E-mail"
value={ form.email }
/>
<Input
autoComplete="new-password"
name="password"
placeholder="Password"
type="password"
value={ form.password }
/>
{ error && <ErrorMessage>{ error }</ErrorMessage>}
<FullButton>
Login
</FullButton>
<BorderButton>
<Link to="/auth/RegisterPage">
Sign Up
</Link>
</BorderButton>
</form>
</>
) : (
<>
<Header>
물건 대여 플랫폼에 <br />
처음 오셨군요!
</Header>
<form onSubmit={ onSubmit }>
<Input
autoComplete="email"
name="email"
placeholder="이메일을 입력해주세요"
value={ form.email }
/>
<Input
autoComplete="new-password"
name="password"
placeholder="비밀번호를 입력해주세요"
type="password"
value={ form.password }
/>
<Input
autoComplete="new-password"
name="passwordConfirm"
placeholder="비밀번호를 재입력해주세요"
type="password"
value={ form.passwordConfirm }
/>
<Input
autoComplete="nickname"
name="nickname"
placeholder="닉네임을 입력해주세요"
value={ form.nickname }
/>
<Input
autoComplete="phoneNumber"
name="phoneNumber"
placeholder="'-'를 제외하고 핸드폰 번호를 입력해주세요"
value={ form.phoneNumber }
/>
{ error && <ErrorMessage>{ error }</ErrorMessage>}
<FullButton>
Sign Up
</FullButton>
</form>
</>
)}
</AuthFormBlock>
);
};
export default AuthForm;
로그인과 회원가입 때 공통적으로 사용될 form입니다. 여기서는 삼중연산자를 사용하는데 type을 인자로 받아 login, register인 경우를 나누어 경우에 맞는 form을 호출합니다.
import React, { useState } from 'react';
import { withRouter } from 'react-router-dom';
import AuthForm from './AuthForm';
const LoginForm = ({ history }) => {
const [error, setError] = useState(null);
const onSubmit = e => {
// Login Button EventListener
e.preventDefault();
};
return (
<AuthForm
type='login'
form={ form }
onSubmit={ onSubmit }
error={ error }
/>
);
};
export default withRouter(LoginForm);
import React from 'react';
import AuthTemplate from '../components/auth/AuthTemplate';
import LoginForm from '../components/auth/LoginForm';
import HeaderTemplate from '../components/common/HeaderTemplate';
const LoginPage = () => {
return(
<>
<HeaderTemplate />
<AuthTemplate>
<LoginForm />
</AuthTemplate>
</>
);
};
export default LoginPage;
로그인 페이지를 작성했으니 라우팅 기능을 이용할 수 있도록 App.js에 넣어주도록 하겠습니다.
import React from 'react';
import { Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import LoginPage from './pages/LoginPage';
import MyPage from './pages/MyPage';
import RegisterPage from './pages/RegisterPage';
const App = () => {
return(
<>
...
<Route
component={ LoginPage }
path="/auth/LoginPage"
exact
/>
</>
);
};
export default App;
http://localhost:3000/auth/LoginPage로 들어가 로그인 페이지를 확인해보도록 하겠습니다.
잘 나오는 모습을 볼 수 있습니다. 그러면 이를 이용하여 회원가입 페이지도 만들어 보도록 하겠습니다.
import React from 'react';
import AuthForm from './AuthForm';
import { withRouter } from 'react-router-dom';
const RegisterForm = ({ history }) => {
// Handler that registers form
const onSubmit = e => {
e.preventDefault();
};
return (
<AuthForm
type='register'
form={ form }
onSubmit={ onSubmit }
/>
);
};
export default withRouter(RegisterForm);
import React from 'react';
import AuthTemplate from '../components/auth/AuthTemplate';
import RegisterForm from '../components/auth/RegisterForm';
import HeaderTemplate from '../components/common/HeaderTemplate';
const RegisterPage = () => {
return (
<>
<HeaderTemplate />
<AuthTemplate>
<RegisterForm />
</AuthTemplate>
</>
);
};
export default RegisterPage;
import React from 'react';
import { Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import LoginPage from './pages/LoginPage';
import MyPage from './pages/MyPage';
import RegisterPage from './pages/RegisterPage';
const App = () => {
return(
<>
...
<Route
component={ LoginPage }
path="/auth/LoginPage"
exact
/>
<Route
component={ RegisterPage }
path="/auth/RegisterPage"
exact
/>
</>
);
};
export default App;
그럼 로그인, 회원가입 페이지에 대한 css를 완성했으니 redux를 적용하여 각 페이지마다 존재하는 Input의 state를 관리해보도록 하겠습니다.
redux에 관한 포스팅을 간략하게 적어놓은 글입니다.
https://velog.io/@biuea/LifeSports-ApplicationReactNative-Node.js-7.-Redux
다음의 패키지를 설치하도록 하겠습니다.
npm install immer react-redux redux redux-actions redux-devtools-extension
changeField, initializeForm 액션함수를 정의해주도록 하겠습니다. 이 액션함수들은 텍스트를 입력했을 때, 텍스트 컴포넌트의 state를 스토어에 저장해주고 state를 초기화해주는 역할을 합니다.
import { createAction, handleActions } from "redux-actions";
import produce from 'immer';
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
export const changeField = createAction(
CHANGE_FIELD, ({
form,
key,
value
}) => ({
form,
key,
value,
}),
);
export const initializeForm = createAction(INITIALIZE_FORM, form => form);
const initialState = {
register: {
email: '',
password: '',
passwordConfirm: '',
nickname: '',
phoneNumber: '',
},
login: {
email: '',
password: '',
}
};
const auth = handleActions(
{
[CHANGE_FIELD]: (state, { payload: { form, key, value }}) =>
produce(state, draft => {
draft[form][key] = value;
}),
[INITIALIZE_FORM]: (state, { payload: form }) => ({
...state,
[form]: initialState[form],
}),
},
initialState,
);
export default auth;
import { combineReducers } from "redux";
import auth from './auth';
const rootReducer = combineReducers(
{
auth,
},
);
export default rootReducer;
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './modules';
import { BrowserRouter } from 'react-router-dom';
const store = createStore(
rootReducer,
composeWithDevTools()
);
ReactDOM.render(
<Provider store={ store }>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
reportWebVitals();
auth에 관한 액션 함수를 rootReducer에 넣어주고 최종적으로 store를 생성하여 감싸주는 형태를 만들었습니다. 이를 사용하기 위해서 앞서 작성해놓았던 LoginForm, RegisterForm에서 액션함수를 호출해보도록 하겠습니다.
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { changeField } from '../../modules/auth';
import AuthForm from './AuthForm';
const LoginForm = ({ history }) => {
const [error, setError] = useState(null);
const dispatch = useDispatch();
const { form } = useSelector(({ auth }) => ({
form: auth.login,
}));
// For changing input, handler
const onChange = e => {
const {
value,
name
} = e.target;
dispatch(
changeField({
form: 'login',
key: name,
value
})
);
};
const onSubmit = e => {
// Login Button EventListener
e.preventDefault();
};
return (
<AuthForm
type='login'
form={ form }
onChange={ onChange }
onSubmit={ onSubmit }
error={ error }
/>
);
};
export default withRouter(LoginForm);
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { changeField } from '../../modules/auth';
import AuthForm from './AuthForm';
import { withRouter } from 'react-router-dom';
const RegisterForm = ({ history }) => {
const dispatch = useDispatch();
const { form } = useSelector(({ auth }) => ({
form: auth.register,
}));
// For changing input, handler
const onChange = e => {
const { value, name } = e.target;
dispatch(
changeField({
form: 'register',
key: name,
value
})
);
};
// Handler that registers form
const onSubmit = e => {
e.preventDefault();
if(password !== passwordConfirm) {
return;
}
};
return (
<AuthForm
type='register'
form={ form }
onChange={ onChange }
onSubmit={ onSubmit }
/>
);
};
export default withRouter(RegisterForm);
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import palette from '../../lib/styles/palettes';
import FullButton from '../common/FullButton';
import BorderButton from '../common/BorderButton';
import Input from '../common/Input'
...
const AuthForm = ({
type,
form,
onChange,
onSubmit,
error
}) => {
const text = textMap[type];
return(
<AuthFormBlock>
{ text === 'login' ? (
<>
<Header>
물건 대여 플랫폼에 <br />
오신 것을 환영합니다!
</Header>
<form onSubmit={ onSubmit }>
<Input
autoComplete="email"
name="email"
placeholder="E-mail"
onChange={ onChange }
value={ form.email }
/>
<Input
autoComplete="new-password"
name="password"
placeholder="Password"
type="password"
onChange={ onChange }
value={ form.password }
/>
{ error && <ErrorMessage>{ error }</ErrorMessage>}
<FullButton>
Login
</FullButton>
<BorderButton>
<Link to="/auth/RegisterPage">
Sign Up
</Link>
</BorderButton>
</form>
</>
) : (
<>
<Header>
물건 대여 플랫폼에 <br />
처음 오셨군요!
</Header>
<form onSubmit={ onSubmit }>
<Input
autoComplete="email"
name="email"
placeholder="이메일을 입력해주세요"
onChange={ onChange }
value={ form.email }
/>
<Input
autoComplete="new-password"
name="password"
placeholder="비밀번호를 입력해주세요"
type="password"
onChange={ onChange }
value={ form.password }
/>
<Input
autoComplete="new-password"
name="passwordConfirm"
placeholder="비밀번호를 재입력해주세요"
type="password"
onChange={ onChange }
value={ form.passwordConfirm }
/>
<Input
autoComplete="nickname"
name="nickname"
placeholder="닉네임을 입력해주세요"
onChange={ onChange }
value={ form.nickname }
/>
<Input
autoComplete="phoneNumber"
name="phoneNumber"
placeholder="'-'를 제외하고 핸드폰 번호를 입력해주세요"
onChange={ onChange }
value={ form.phoneNumber }
/>
{ error && <ErrorMessage>{ error }</ErrorMessage>}
<FullButton>
Sign Up
</FullButton>
</form>
</>
)}
</AuthFormBlock>
);
};
export default AuthForm;
위의 코드 처럼 Input마다 onChange를 넣어주도록 하겠습니다. 그러면 state가 store에 잘 저장이 되는지 확인해보기 위해서 아래의 크롬도구를 설치하도록 하겠습니다.
잘 저장이 되는 모습을 볼 수 있습니다. 다음 포스트에서는 auth-service와 연결하여 실제 회원가입, 로그인을 진행하도록 하겠습니다.