Авторизация с react-router-redux 5.x и redux-saga

Опубликовано: 10 июня 2017 г.
  • SPA
Всем привет! В этом учебнике я покажу вам как обрабатывать авторизацию в React-приложении используя Redux, react-router 4.x с react-router-redux 5.x и redux-saga.

Вам нужно будет установить последнюю версию react-router-redux:

npm install --save react-router-redux@next
npm install --save history
Авторизация с react-router-redux 5.x и redux-saga

Инициализация

Во-первых, нам нужно создать роутер, сага-мидлевар, стор и запустить сагу.

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import createHistory from 'history/createBrowserHistory';
import createSagaMiddleware from 'redux-saga';
import { Provider } from 'react-redux';
import { routerMiddleware } from 'react-router-redux';
import { applyMiddleware, createStore } from 'redux';

import App from './App';
import Saga from './saga';
import reducer from './reducer';

// создаем мидлевары
const history = createHistory();
const sagaMiddleware = createSagaMiddleware();

const middleware = applyMiddleware(
  routerMiddleware(history),
  sagaMiddleware
);

// создаем стор
const store = createStore(reducer, middleware);

// запуск сага-мидлевара
sagaMiddleware.run(Saga);

ReactDOM.render(
  <Provider store={store}>
    <App history={history} />
  </Provider>,
  document.getElementById('root')
);

Редьюсер

Состояние приложения будет состоять из двух частей: одно для auth и одно для роутера. Редьюсер auth будет обрабатывать события аутентификации.

// reducer.js
import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux';

export const AUTH_REQUEST = 'AUTH_REQUEST';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAILURE = 'AUTH_FAILURE';

export const authorize = (login, password) => ({
  type: AUTH_REQUEST,
  payload: { login, password }
});

const initialState = {
  token: localStorage.getItem('token'),
  error: null
};

const authReducer = (state = initialState, { type, payload }) => {
  switch (type) {
    case AUTH_SUCCESS: {
      return { ...state, token: payload };
    }
    case AUTH_FAILURE: {
      return { ...state, error: payload }
    }
    default:
      return state;
  }
};

const reducer = combineReducers({
  auth: authReducer,
  router: routerReducer
});

export default reducer;
redux-saga

Сага

Сага будет следить за экшеном AUTH_REQUEST action и обрабатывать логику аутентификации.

// saga.js
import { call, put, takeLatest } from 'redux-saga/effects';

import { AUTH_REQUEST, AUTH_SUCCESS, AUTH_FAILURE } from './reducer';

const fetchJSON = (url, options = {}) =>
  new Promise((resolve, reject) => {
    return fetch(url, options)
      .then(response => (response.status !== 200 ? reject(response) : response))
      .then(response => response.json())
      .then(response => resolve(response))
      .catch(error => reject(error));
  });

function* authorize({ payload: { login, password } }) {
  const options = {
    body: JSON.stringify({ login, password }),
    method: 'POST',
    headers: { 'Content-Type': 'application/json' }
  };

  try {
    const { token } = yield call(fetchJSON, '/login', options);
    yield put({ type: AUTH_SUCCESS, payload: token });
    localStorage.setItem('token', token);
  } catch (error) {
    let message;
    switch (error.status) {
      case 500: message = 'Internal Server Error'; break;
      case 401: message = 'Invalid credentials'; break;
      default: message = 'Something went wrong';
    }
    yield put({ type: AUTH_FAILURE, payload: message });
    localStorage.removeItem('token');
  }
}

function* Saga() {
  yield takeLatest(AUTH_REQUEST, authorize);
}

export default Saga;

Роутер

Мы используем роутер с двумя маршрутами, один для компонента Login и один для компонента Main.

//App.js
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';

import Main from './Main';
import Login from './Login';

const App = props => {
  const { history } = props;

  return (
    <ConnectedRouter history={history}>
      <Switch>
        <Route path="/login" component={Login} />
        <Route path="/" component={Main} />
      </Switch>
    </ConnectedRouter>
  );
};

export default App;

Компонент Main

Main является компонентом контейнера без состояния, который будет перенаправлять на Login, если нет токена.

// Main.js
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';

const Main = ({ token }) => {
  if (!token) {
    return <Redirect to="/login" />;
  }
  return <div>Вы вошли в систему.</div>;
};

const mapStateToProps = (state) => ({
  token: state.auth.token
});

export default connect(mapStateToProps)(Main);

Логин

Login - это компонент контейнера с сохранением состояния, который будет вызывать экшен AUTH_REQUEST.

// Login.js
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';

import { authorize } from './reducer';
import { tokenSelector, errorSelector } from './selectors';

class Login extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { login: '', password: '' };
    this.onChange = this.onChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  onChange(input, value) {
    this.setState({ [input]: value });
  }

  onSubmit() {
    const { login, password } = this.state;
    this.props.dispatch(authorize(login, password));
  }

  render() {
    const { error, token } = this.props;

    if (token) {
      return <Redirect to="/" />;
    }

    return (
      <div>
        <input
          type='text'
          placeholder='login'      
          value={this.state.login}
          onChange={this.onChange.bind(this, 'login')}
        />
        <input
          type='password'
          placeholder='password'
          value={this.state.password}
          onChange={this.onChange.bind(this, 'password')}
        />
        <button onClick={this.onSubmit}>Submit</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => ({
  token: state.auth.token,
  error: state.auth.error
});

export default connect(mapStateToProps)(Login);

Вот и все, видите, как просто!

Популярные статьи
2D игра на Unity. Подробное руководство. Часть 1
  • unity
2D игра на Unity. Подробное руководство. Часть 1
Адаптивный слайдер без Javascript на CSS3
  • слайдер
Адаптивный слайдер без Javascript на CSS3
Работа с Unity в 2D
  • unity
Работа с Unity в 2D
2D игра на Unity. Подробное руководство. Часть 3
  • unity
2D игра на Unity. Подробное руководство. Часть 3
2D игра на Unity. Подробное руководство. Часть 4
  • unity
2D игра на Unity. Подробное руководство. Часть 4
2D игра на Unity. Подробное руководство. Часть 5
  • unity
2D игра на Unity. Подробное руководство. Часть 5
2D игра на Unity. Подробное руководство. Часть 6
  • unity
2D игра на Unity. Подробное руководство. Часть 6
Учебник по новому GUI в Unity. Часть 2.
  • unity
Учебник по новому GUI в Unity. Часть 2.
Учебник по новому GUI в Unity. Часть 1.
  • unity
Учебник по новому GUI в Unity. Часть 1.

HTML LESS LESS React
Онлайн журнал для профессиональных веб-дизайнеров и программистов
БлогПлагиныГрафикаИнструменты
CanvasSPAcssjavascriptjqueryphotoshopphpunitywordpress{"fieldValue":[{"image_preview":"","image_demo":"","example":""}],"fieldSettings":{"autoincrement":1}}{"fieldValue":[{"image_preview":"https://cdn-images-1.medium.com/max/800/1*OIBUnA4NokXK14IMR5csTw.jpeg","image_demo":"https://cdn-images-1.medium.com/max/800/1*OIBUnA4NokXK14IMR5csTw.jpeg","example":""}],"fieldSettings":{"autoincrement":1}}Аудио/Видеоаккордеонанимациябазы данныхбраузерные игрыверсткагалереяграфикакартыкнопкименюпараллаксподсказкипопаппрелоадерслайдертаймерформычекбоксыэлементы интерфейса
© 2009-2017 WebSketches.ru