Rental Application (React & Spring boot Microservice) - 24 : Redux

yellow_note·2021년 9월 7일
0

#1 LoginPage

redux를 적용하기 전에 LoginPage와 RegisterPage를 작성하도록 하겠습니다.

공통적으로 사용할 button, input을 작성하도록 하겠습니다.

  • /src/components/common/Input.js
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에 동일하게 넣어주겠다는 의미입니다.

  • ./src/components/common/BorderButton.js
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;
  • ./src/components/common/FullButton.js
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를 작성하도록 하겠습니다.

  • ./src/components/auth/AuthTemplate.js
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 컴포넌트를 감싸주기 위한 컴포넌트입니다.

  • ./src/components/AuthForm.js
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을 호출합니다.

  • ./src/components/auth/LoginForm.js
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);
  • ./src/pages/LoginPage.js
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에 넣어주도록 하겠습니다.

  • ./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로 들어가 로그인 페이지를 확인해보도록 하겠습니다.

잘 나오는 모습을 볼 수 있습니다. 그러면 이를 이용하여 회원가입 페이지도 만들어 보도록 하겠습니다.

#2 RegisterPage

  • ./src/components/auth/RegisterForm.js
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);
  • ./src/pages/RegisterPage.js
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;
  • ./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
            />
            <Route 
                component={ RegisterPage }
                path="/auth/RegisterPage"
                exact
            />
        </>
    );
};

export default App;

그럼 로그인, 회원가입 페이지에 대한 css를 완성했으니 redux를 적용하여 각 페이지마다 존재하는 Input의 state를 관리해보도록 하겠습니다.

#3 Redux

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를 초기화해주는 역할을 합니다.

  • src/modules/auth.js
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;
  • src/modules/index.js
import { combineReducers } from "redux";
import auth from './auth';

const rootReducer = combineReducers(
    {
        auth,
    },
);

export default rootReducer;
  • src/index.js
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();
  • react native 프로젝트를 하면서 해당 글과 같은 코드들을 사용했습니다.

auth에 관한 액션 함수를 rootReducer에 넣어주고 최종적으로 store를 생성하여 감싸주는 형태를 만들었습니다. 이를 사용하기 위해서 앞서 작성해놓았던 LoginForm, RegisterForm에서 액션함수를 호출해보도록 하겠습니다.

  • LoginForm.js
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);
  • RegisterForm
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);
  • 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 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에 잘 저장이 되는지 확인해보기 위해서 아래의 크롬도구를 설치하도록 하겠습니다.

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?page=1&hl=ko&itemlang=en-GB


잘 저장이 되는 모습을 볼 수 있습니다. 다음 포스트에서는 auth-service와 연결하여 실제 회원가입, 로그인을 진행하도록 하겠습니다.

참고

  • 리액트를 다루는 기술 - 저자: 김민준

0개의 댓글

Powered by GraphCDN, the GraphQL CDN