Инициализация приложения
Под инициализацией приложения подразумевается включение прелоадера (анимации загрузки) до того момента, когда данные с сервера о пользователе подгрузятся. Это поможет избежать всяких «перемигиваний».
- Делаем компоненту App (в App.js) классовой. И конектим в нее санку
getAuthUserData, которая до этого у нас конектилась и запускалась из пропсов в Header.jsx.
class App extends React.Component { componentDidMount() { this.props.getAuthUserData(); } render() { return ( <div className="app-wrapper"> <HeaderContainer /> <Navbar /> <div className="app-wrapper-content"> <Route path='/dialogs' render={ () => <DialogsContainer /> } /> <Route path='/profile/:userId?' render={ () => <ProfileContainer /> } /> <Route path='/users' render={ () => <UsersContainer /> } /> <Route path='/news' render={ () => <News /> } /> <Route path='/music' render={ () => <Music /> } /> <Route path='/setings' render={ () => <Setings /> } /> <Route path='/login' render={ () => <Login /> } /> </div> </div> ); } } export default withRouter (connect(mapStateToProps, {getAuthUserData}) (App));
Обратите внимание, что применен в последней строке не просто HOC connect, а также HOC withRouter, иначе не заработает и причину будет сложно найти.
- Теперь эту строку стоит записать более правильно, используя compose:
export default compose ( withRouter, connect(mapStateToProps, {getAuthUserData})) (App);
- У нас все заработало, но работает с теми же проблемами, что и раньше. Нам нужна проверка, чтобы знать, мы уже проинициализировались или не проинициализировались, только после чего загружать дальше.
Для этого создаем app-reducer.js:import { getAuthUserData } from "./auth-reducer"; const INITIALIZED_SUCCESS = 'INITIALIZED_SUCCESS'; let initialState = { initialized: false } const appReducer = (state = initialState, action) => { switch(action.type) { case INITIALIZED_SUCCESS: return { ...state, initialized: true } default: return state; } } export const initializedSuccess = () => ({ type: INITIALIZED_SUCCESS}); //[1] export const initializeApp = () => (dispatch) => { //[2] let promise = dispatch(getAuthUserData()); // [3] promise.then( () => { dispatch(initializedSuccess()); // [4] }) } export default appReducer;
[1] это экшн-криэйтер и возвращаемый экшн; [2] это наша санка; [3] здесь мы и диспатчим санку с другого редьюсера, и объявляем наш промис; [4] здесь после полного завершения диспатча начинается диспатч нашего экшнкриэйтера.
- Но если, например, у нас перед выполнением промиса будет несколько других, то можно немного переписать код, применив Promise.all:
export const initializeApp = () => (dispatch) => { let promise = dispatch(getAuthUserData()); Promise.all([promise]) .then( () => { dispatch(initializedSuccess()); }) }
Если промисов будет больше одного, то их нужно также поместить в массив [promise].
- Не забываем добавить наш новый редьюсер app-reducer.js «закомбайнить» в файле, где мы «комбайним» все дедьюсеры. В нашем случае это файл redux-store.js. Назовем его при этом app:
... let reducers = combineReducers({ profilePage: profileReducer, dialogsPage: dialogsReducer, sidebar: sidebarReducer, usersPage: usersReducer, auth: authReducer, app: apphReducer, // <--- наш редьюсер form: formReducer }); //объединяем редьюсеры ...
- Теперь осталось доделать наше взаимодействие с компонентой App. Для этого в файле App.js создаем mapStateToProps и конектим его вместо null, а также заменяем в mapDispatchToProps getAuthUserData на нашу новую санку initializeApp, импортируя ее из нашего нового редьюсера (напомим, что getAuthUserData теперь вошла внутрь нее):
const mapStateToProps = (state) => ({ initialized: state.app.initialized }) export default compose ( withRouter, connect(mapStateToProps, {initializeApp})) (App);
- Теперь также нашу новую санку прописываем в componentDidMount() вместо той же getAuthUserData(). А также в рендере добавляем условие, при котором, пока наш initialized из стейта false, отображается прелоадер. А после завершения выполнения санки
initializeApp значение его смениться на true и загрузится основной код из рендера.
lass App extends React.Component { componentDidMount() { this.props.initializeApp(); } render() { if (!this.props.initialized) { return <Preloader/> } return ( <div className="app-wrapper"> <HeaderContainer /> <Navbar /> ...