블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

'react router'에 해당되는 글 2

  1. 2018.11.30 [React + Meteor] 개발환경 설정 - 5
  2. 2018.11.22 [React + Meteor] 개발환경 설정 - 3
2018. 11. 30. 15:48 Meteor/React + Meteor

React의 route 설정을 리팩토링해보고, createRef() 를 사용하지 않고 form을 변경해 본다. 


  - react-router v4에 맞는 redux 환경 재구성

  - react-final-form  적용하기

  - 사용자별 Link 목록 보여주기

  - 적용소스




React Router v4에 맞는 Redux 설정


react-router-redux는 react router v2와 v3에 맞는 패키지이고 react router v4에 맞는 connected-react-router로 교체해야 한다. recct-router-redux 설정 상태로는 redux action이 작동하지 않는다.

// 설치

$ meteor npm install --save connected-react-router

// 삭제

$ meteor npm uninstall --save react-router-redux

$ meteor npm uninstall --save @types/react-router-redux


imports/ui/store.ts 리팩토링

  - Routes.tsx의 browserHistory 를 store.ts로 옮김

  - connectRouter로 routerReducer를 생성

  - RouterAction, LocationChangeAction을 추가

import { createBrowserHistory } from 'history';

import { connectRouter, RouterAction, LocationChangeAction } from 'connected-react-router';


export const browserHistory = createBrowserHistory();


const rootReducer = combineReducers({

  router: connectRouter(browserHistory),

  links: linkReducer

});


export type RootState = StateType<typeof rootReducer>;


type ReactRouterAction = RouterAction | LocationChangeAction;

export type RootAction = ReactRouterAction | LinkAction;


imports/ui/Routes.tsx 리팩토링

  - react-router v4의 Router를 사용하지 않고, connected-react-router의 ConnectedRouter를 사용

  - 인자로 store.ts 에서 생성한 browserHistory를 사용

import { ConnectedRouter } from 'connected-react-router';

import store, { browserHistory } from './store';


export const Root = (

  <Provider store={store}>

    <ConnectedRouter history={browserHistory}>

      <Switch>

        <Route exact path="/" component={Login} />

        <Route path="/main" component={App} />

        <Route path="/signup" component={Signup} />

        <Route path="/links" component={InfoContainer} />

        <Route path="*" component={NotFound} />

      </Switch>

    </ConnectedRouter>

  </Provider>

);


이제 Redux Chrome Extension에서 LOCATION_CHANGE 액션을 볼 수 있다. 




final-form 통해 입력


form 관련부분을 final-form으로 변경한다. React버전의 react-final-form을 사용한다. 

$ meteor npm install --save final-form 

$ meteor npm install --save react-final-form


로그인 화면부터 react-final-form으로 변경해 본다. 

  - react-final-form의 Form, Field import

  - Form 의 onSubmit={this.onLogin}을 통해 입력한 values 객체를 받기, 보통 폼 필드의 name을 key로하여 json 객체를 받는다. 

  - <input> 태그를 <Field> 태그로 변경

  - <button> 태그에 disabled 속성추가

  - 필요없는 부분 추석처리: React.createRef()

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Meteor } from 'meteor/meteor';

import { Form, Field } from 'react-final-form';


export interface LoginProps {

  history: any;

}


export interface LoginState {

  error: string;

}


export default class Login extends React.Component<LoginProps, LoginState> {

  // email: any = React.createRef();

  // password: any = React.createRef();


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onLogin = ({email, password}) => {

    // e.preventDefault();

    // let email = this.email.current.value.trim();

    // let password = this.password.current.value.trim();

    if (!email || !password) {

      this.setState({error: 'Please input email and password both'});

      return;

    }

    Meteor.loginWithPassword({ email }, password, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  makeForm = ({ handleSubmit, submitting, pristine, values }) => {

    return (

      <form onSubmit={handleSubmit}>

        <Field name="email" component="input" type="email" placeholder="Email" required/>

        <Field name="password" component="input" type="password" placeholder="Passowrd"/>

        {/* <input type="email" ref={this.email} name="email" placeholder="Email" />

        <input type="password" ref={this.password} name="password" placeholder="Password" /> */}

        <button type="submit" disabled={submitting || pristine}>Login</button>

      </form>

    );

  };


  public render() {

    return (

      <div>

        <h1>Login to short Link</h1>

        {this.state.error ? <p>{this.state.error} </p> : undefined}

        <Form onSubmit={this.onLogin} render={this.makeForm} />

        <Link to="/signup">Have a account?</Link>

      </div>

    );

  }

}


Signup.tsx, AddLink.tsx 도 변경한다. 




사용자별 Link 목록 보여주기


사용자를 추가하여 각 사용자가 등록한 목록만 보기 위해서 pub/sub 설정에서 userId  파라미터를 넘겨주어 본인 목록만 조회하여 publish 한다. 

// imports/ui/Info.tsx 맨 하단의 subscribe시에 자신의 아이디를 파라미터로 보낸다. 

export default compose(

  withTracker(() => {

    const connection = Meteor.subscribe('links', {userId: Meteor.userId()});

    return {

      links: Links.find().fetch(),

      loading: !connection.ready()

    };

  }),

  connect(mapProps)

)(Info);


imports/api/links.ts에서 userId를 파라미터로 find한다. 

if (Meteor.isServer) {

  Meteor.publish('links', ({userId}) => {

    console.log('userId:', userId);

    if (!userId) {

      return this.ready();

    }

    return Links.find({owner: userId});

  });

  ...

}




<참조>

- connected-react-router 저장소

- react-final-form 소개 영상

posted by 윤영식
2018. 11. 22. 14:37 Meteor/React + Meteor

Meteor의  accounts-password를 패키지를 통한 로그인 환경 만들기를 해본다. 


  - accounts-password 패키지를 통한 미티어 사용자 가입/로그인 사용 방법 알기

  - React Router 설정

  - 미티어의 Tracker.autorun을 통해 reactivity computation과 resource 사용 방법 알기

  - 소스코드




미티어 사용자 관리


accounts-password 패키지를 설치하고, 가입 및 로그인 화면을 만든다. 

$ meteor add accounts-password

$ meteor npm install --save bcrypt

// Link 사용을 위해 

$ meteor npm install --save react-router-dom


Signup.tsx와 Login.tsx 생성한다. 

  - email, password의 값을 읽기 위하여 typescript방식의 public refs를 정의한다. 

  - Accounts.createUser를 통해 사용자를 등록한다.

// Singup.tsx

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Accounts } from 'meteor/accounts-base'


export interface SignupProps {}

export default class Signup extends React.Component<SignupProps, any> {

  // @see https://goenning.net/2016/11/02/strongly-typed-react-refs-with-typescript/

  public refs: {

    email: HTMLInputElement,

    password: HTMLInputElement

  }


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onCreateAccount = (e: any) => {

    e.preventDefault();

    let email = this.refs.email.value.trim();

    let password = this.refs.password.value.trim();

    Accounts.createUser({email, password}, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  public render() {

    return (

      <div>

        <h1>Signup to short Link</h1>

        { this.state.error ? <p>{this.state.error} </p> : undefined}

        <form onSubmit={this.onCreateAccount}> 

          <input type="email" ref="email" name="email" placeholder="Email"/>

          <input type="password" ref="password" name="password" placeholder="Password"/>

          <button>Create Acount</button>

        </form>

        <Link to="/login">Already have a account?</Link>

      </div>

    );

  }

}


Login.tsx

  - refs 정의

  - Meteor.loginWithPassword를 통해 로그인한다.

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Meteor } from 'meteor/meteor';


export interface LoginProps {

  history: any;

}

export default class Login extends React.Component<LoginProps, any> {

  public refs: {

    email: HTMLInputElement,

    password: HTMLInputElement

  }


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onLogin = (e: any) => {

    e.preventDefault();

    let email = this.refs.email.value.trim();

    let password = this.refs.password.value.trim();

    Meteor.loginWithPassword({ email }, password, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  public render() {

    return (

      <div>

        <h1>Login to short Link</h1>

        {this.state.error ? <p>{this.state.error} </p> : undefined}

        <form onSubmit={this.onLogin}>

          <input type="email" ref="email" name="email" placeholder="Email" />

          <input type="password" ref="password" name="password" placeholder="Password" />

          <button>Login</button>

        </form>

        <Link to="/signup">Have a account?</Link>

      </div>

    );

  }

}


NotFound.tsx 파일도 생성한다. 

import * as React from 'react';

export default () => <div>Not Found Page</div>




React Router 설정


라우팅 설정을 통해 가입, 로그인후 Link화면으로 이동하는 설정한다. 


history 모듈을 설치한다.

$ meteor npm install --save history


client/main.tsx에 Route설정을 한다.

import * as React from 'react';

import { Meteor } from 'meteor/meteor';

import { render } from 'react-dom';

import { Provider } from 'react-redux';

import { Route, Router, Switch } from 'react-router-dom';

import { createBrowserHistory } from 'history';

 

import App from '../imports/ui/App'

import Login from '../imports/ui/pages/Login';

import Signup from '../imports/ui/pages/Signup';

import InfoContainer from '../imports/ui/Info';

import NotFound from '../imports/ui/pages/NotFound';

import store from '../imports/ui/store';


const browserHistory = createBrowserHistory();

const Root = (

  <Provider store={store}>

    <Router history={browserHistory}>

      <Switch>

        <Route exact path="/" component={Login} />

        <Route path="/main" component={App} />

        <Route path="/signup" component={Signup} />

        <Route path="/links" component={InfoContainer} />

        <Route path="*" component={NotFound} />

      </Switch>

    </Router>

  </Provider>

);


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


links는 인증된 사용자만 볼 수 있으므로 Tracker.autorun이라는 Reactivity Computation 영역에서 Meteor.user()라는 Reactivity Source가 로그인 상태인지 검사한다. 

  - 로그인 상태가 아니면 무조건 login화면으로 이동

  - 로그인 상태이면서 인증이 필요없는 화면인 경우 link화면으로 이동

// client/main.tsx 

import { Tracker } from 'meteor/tracker';


Tracker.autorun(() => {

  const isAuthenticated = !!Meteor.user();

  if (isAuthenticated) {

    const pathname = browserHistory.location.pathname;

    const unauthenticatedPages: any = ['/', '/signup'];

    const isUnauthenticatedPage = unauthenticatedPages.includes(pathname);

    if (isUnauthenticatedPage) {

      browserHistory.push('/links'); // main

    } else {

      browserHistory.push(pathname);

    }

  } else {

    browserHistory.push('/'); // login

  }

});


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


imports/ui/Info.tsx 에 logout 기능 추가

...

import { Accounts } from 'meteor/accounts-base';


interface InfoProps {

  links: any;

  loading: boolean

}


class Info extends React.Component<InfoProps, any> {


  linkList() {

    const { links, loading } = this.props;

    if (loading) {

      return <div>loading...</div>

    } else {

      return <LinkList links={links} />

    }

  }


  onLogout = () => {

    Accounts.logout();

  }


  render() {

    return (

      <div>

        <button onClick={this.onLogout}>Log out</button>

        <AddLink />

        {this.linkList()}

      </div>

    );

  }

}




가입 및 로그인 테스트 확인하기


가입을 하고, 로그인을 한다. 

Chrome Extension 으로 MiniMongoExplorer를 설치하고 user Collection의 내용을 확인한다. 


또는 Chrome DevTool의 Application에서 로그인 사용자의 userId와 console에서 확인해 볼 수 있다. 또한 Link페이지에서 로그아웃을 해보고 콘솔을 확인해 본다. 




<참조>

- 크롬 확장툴 MiniMongoExplorer

- 세번째 글 소스

posted by 윤영식
prev 1 next