Приватный роут в react-router-dom. Пример использования react-redux

Приватный роут в react-router-dom. Пример использования react-redux

Йо-йо! Недавно (июнь 2019) доделывал сайт для бонусной системы одной сети пиццерий в своём городе. Для хранения данных я использовал redux. Авторизация или регистрация использовали один из action creator’ов и так мои компоненты могли понять, что пользователь зарегистрирован или авторизирован на сайте.

Приватный route

Это такой глупый компонент, в который мы передаём true или false, и он либо рендерит какой-то компонент, либо делает redirect.

Вот мой PrivateRoute:

import React from 'react';
import { Route, Redirect } from 'react-router-dom';

const PrivateRoute = ({ component: Component, auth, ...rest }) => (
  <Route
    {...rest}
    render={props => (
      auth === true)
        ? <Redirect to="/lk" />
        : <Component {...props} />
    )}
  />
);

export default PrivateRoute;

А вот компонент в с Router’ом

import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import React, {Component, Suspense, lazy } from 'react';
import PrivateRoute from './routs/PrivateRoute';

import './css/App.css';
import logo from './img/logo.png';
import PageMain from './pages/Main/PageMain';

const PageRegister = lazy(() => import('./components/Registration/RegistrationContainer'));
const PageAuth = lazy(()=>import('./components/Auth/AuthContainer'));
const PageLk = lazy(()=> import('./pages/Lk/Page__Lk'));

const auth = // некая переменная или функция которая возвращает значение true или false в зависимости авторизирован пользователь или нет


class App extends Component{  
  render(){
    return (   
          <div className="container">            
            <div className="row">
              <Router>
                <div className="col-12 header">
                  <div className="row">
                    <div className="col-md-6">
                      <Link to="/" className="text-white">
                        <img title="На главную" className="img-fluid" src={logo} alt="logtype"/>
                      </Link>
                    </div>
                    <div className="col-md-6">
                      <p className="text-white header__phone">Какой-то текст</p>  
                    </div>
                  </div>
                  </div>
                    <Suspense fallback={<div className="preloader">Загрузка...</div>}>
                        <Switch>
                          <Route exact path="/" component={ (()=>(<PageMain />)) } />
                          <Route path="/register" component={ ()=>(<PageRegister/>) } />
                          <Route path="/auth" component={ ()=>(<PageAuth/>) } />
                          <PrivateRoute auth={auth} path="/lk" component={ ()=>(<PageLk/>) } />
                        </Switch>
                    </Suspense>
                </Router>
            </div>        
          </div>
    );
  }
}

export default App;

Если вы хотите подробнее познакомиться c router то прочитайте статью react-router-dom.

Redux и react-router-dom

Теперь нам нужно понимать, авторизирован пользователь или нет. Для этого я использую redux. Создал папку store в корне проекта и в нём папку auth. В папке два файла actions.js и redusers.js

В файле actions.js я создал несколько action creater’ов

export const AUTH_CHANGE_PHONE_TEXT = 'AUTH_CHANGE_PHONE_TEXT';
export const AUTH_CHANGE_PASSWORD_TEXT = 'AUTH_CHANGE_PASSWORD_TEXT';
export const AUTH_CHANGE_VALID_PHONE_TEXT = 'AUTH_CHANGE_VALID_PHONE_TEXT';
export const AUTH_CHANGE_VALID_PASSWORD_TEXT = 'AUTH_CHANGE_VALID_PASSWORD_TEXT';
export const AUTH_SET_IS_AUTH_VALUE = 'AUTH_SET_IS_AUTH_VALUE';

export const setPhoneText = (phone) => ({
    type: AUTH_CHANGE_PHONE_TEXT,
    payload: phone
});
export const setValidPhone = (bool) => ({
    type: AUTH_CHANGE_VALID_PHONE_TEXT,
    payload: bool
});
export const setPasswordText = (password) => ({
    type: AUTH_CHANGE_PASSWORD_TEXT,
    payload: password
});
export const setValidPassword = (bool) => ({
    type: AUTH_CHANGE_VALID_PASSWORD_TEXT,
    payload: bool
});
// Значение которое будет отвечать за то авторизирован пользователь или нет
export const setIsAuthValue = (bool) => ({
    type: AUTH_SET_IS_AUTH_VALUE,
    payload: bool
});

В файле redusers.js

import { 
    AUTH_CHANGE_PHONE_TEXT, 
    AUTH_CHANGE_PASSWORD_TEXT, 
    AUTH_CHANGE_VALID_PHONE_TEXT, 
    AUTH_CHANGE_VALID_PASSWORD_TEXT,
    AUTH_SET_IS_AUTH_VALUE 
} from './actions';


const defaultState = {
    phone: '',
    password: '',
    isValidPhone: false,
    isValidPassword: false,
    isAuthValue: false
}

export const authReduser = ( state = defaultState, action ) => {
    switch( action.type ){
        case AUTH_CHANGE_PHONE_TEXT:
            return { ...state, phone: action.payload }
        case AUTH_CHANGE_PASSWORD_TEXT:
            return { ...state, password: action.payload }
        case AUTH_CHANGE_VALID_PHONE_TEXT: 
            return { ...state, isValidPhone: action.payload }
        case AUTH_CHANGE_VALID_PASSWORD_TEXT:
            return { ...state, isValidPassword: action.payload }
        case AUTH_SET_IS_AUTH_VALUE:
            return { ...state, isAuthValue: action.payload }
        default: return state;
    }
}

В папке store так же создаём файл redusers.js и actions.js. В actions.js ничего не пишем, а в файле redusers.js:

import { combineReducers } from "redux";
import { authReduser } from './auth/redusers';
// Прочие импорты

export default combineReducers({
    auth: authReduser,
    // Прочие reduser
});

Если вам сложно разобраться с redux, рекомендую посмотреть курс на youtube от codedojo.

Создаём store

Переходим в компонент с роутером и модифицируем его

// Импортируем redux и наши redusers
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootRedusers from './store/redusers'

Создаём store

const store = createStore(rootRedusers);

Оборачиваем весь наш компонент в <Provider>

return(
  <Provider store={store}>
    // Остальной код компонента
  </Provider>
)

Передаём store в RivateRoute

Изменяем

 <PrivateRoute auth={auth} path="/lk" component={ ()=>(<PageLk/>) } />

На

<PrivateRoute store={store} path="/lk" component={ ()=>(<PageLk/>) } />

Идём опять в наш файл с PrivateRoute и переписываем его

import React from 'react';
import { Route, Redirect } from 'react-router-dom';

const PrivateRoute = ({ component: Component, store, ...rest }) => (
  <Route
    {...rest}
    render={props => (
      'auth' in store.getState() && store.getState().auth.isAuthValue === true)
        ? <Component {...props} />
        : <Redirect to="/auth" />
    )}
  />
);

export default PrivateRoute;

Тут через store.getState() мы проверяем есть ли на данный момент ‘auth’ в storе. В начале жизни нашего приложения его может не быть. И ещё мы проверяем наше isAuthValue на true

Как менять isAuthValue

У меня есть AuthContainer, который лежит по адресу: /components/Auth/AuthContainer’. И там же лежит компонент Auth, в котором и происходит сама регистрация, лежит вёрстка и т.д.

Создание контейнера

AuthContainer — умный компонент, который и передаёт данные в Auth. У меня получился вот такой код компонента:

import React, { Component } from 'react';
import Auth from './Auth';
// Нужно, чтобы передать наш store и методы в AuthContainer
import { connect } from 'react-redux';
// Импортирую экшены, которые буду использовать в своём приложении
import { 
    setPhoneText, 
    setPasswordText, 
    setValidPassword,
    setValidPhone,
    setIsAuthValue 
} from '../../store/auth/actions';


class AuthContainer extends Component {
    render() {
        // Передаю props'ы в Auth
        return <Auth 
                    phone={this.props.phone} 
                    password={this.props.password} 
                    isValidPhone={this.props.isValidPhone}
                    isValidPassword={this.props.isValidPassword}
                    isAuthValue={this.props.isAuthValue}
                    setPhoneText={this.props.setPhoneText} 
                    setPasswordText={this.props.setPasswordText} 
                    setValidPhone={this.props.setValidPhone}
                    setValidPassword={this.props.setValidPassword}
                    setIsAuthValue={this.props.setIsAuthValue}
                />
    }
}

// Перечисляю, что из состояний store будет передано компоненту AuthContaine и далее Auth
const mapStateToProps = state => {
    return {
        phone: state.auth.phone,
        password: state.auth.password,
        isValidPhone: state.auth.isValidPhone,
        isValidPassword: state.auth.isValidPassword,
        isAuthValue: state.auth.isAuthValue
    };
};
// Перечисляю методы которые будут переданы в AuthConatainer, а потом в Auth
const mapDispatchToProps = {
    setPhoneText,
    setPasswordText,
    setValidPassword,
    setValidPhone,
    setIsAuthValue
}
// Через функцию connect передаю всё в компонент AuthContainer
export default connect(mapStateToProps, mapDispatchToProps)(AuthContainer);

Обратите внимание, что в итоге компонент AuthConatainer будет использоваться внутри <Provider>, иначе ничего не сработает.

Использование reduser’а

После того как мы передали в Auth reduser’ы из AuthContainer, у нас появилась функция this.props.setIsAuthValue(bool).

В тот момент, когда мы убедились, что пользователь авторизировался, мы можем вызвать this.props.setIsAuthValue(true). Таким образом значение isAuthValue измениться на true. Кстати, если вы делаете кросс-доменные запросы для авторизации, как я, то вам может быть полезной статья «fetch и cors, пример на reactjs«

Для лучшего UX после авторизации я перенаправлял пользователей в личный кабинет. Для этого мне понадобилось изменить state компонента, чтобы вызвать повторный reder, проверить значение isAuthValue. Примерно это может быть вот так:

render(
    if(this.props.isAuthValue === false){
        return(<div><!-- форма регистрации --></div>)
    } else {
        return(<Redirect to='ДРУГАЯ СТРАНИЦА'/>)
    }
)

P.S.

На самом деле, если разобраться, это всё довольно не сложно. При частой практике вы будете очень быстро справляться с react-redux и react-dom-router будет помогать делать шустрые SPA и не станет усложнением. Однажды я расскажу как настроить виртуальный сервер windows и apache так, чтобы работал router как нужно.

Про комментарии

Праведный «срач» поддерживается, а также вопросы и собственный опыт.