Подтвердить что ты не робот

React/Redux - отправка действий при загрузке приложения/init

У меня есть аутентификация на токене с сервера, поэтому, когда приложение Redux загружается изначально, мне нужно сделать запрос на этот сервер, чтобы проверить, прошел ли аутентификация пользователя или нет, и если да, я должен получить токен.

Я обнаружил, что использование действий INIT Redux Core не рекомендуется, так как я могу отправить действие, прежде чем приложение будет показано?

4b9b3361

Ответ 1

Обновление: этот ответ для React Router 3

Я решил эту проблему, используя реквизиты реакции-маршрутизатора onEnter. Вот как выглядит код:

// this function is called only once, before application initially starts to render react-route and any of its related DOM elements
// it can be used to add init config settings to the application
function onAppInit(dispatch) {
  return (nextState, replace, callback) => {
    dispatch(performTokenRequest())
      .then(() => {
        // callback is like a "next" function, app initialization is stopped until it is called.
        callback();
      });
  };
}

const App = () => (
  <Provider store={store}>
    <IntlProvider locale={language} messages={messages}>
      <div>
        <Router history={history}>
          <Route path="/" component={MainLayout} onEnter={onAppInit(store.dispatch)}>
            <IndexRoute component={HomePage} />
            <Route path="about" component={AboutPage} />
          </Route>
        </Router>
      </div>
    </IntlProvider>
  </Provider>
);

Ответ 2

Вы можете отправить действие в методе Root componentDidMount и в методе render вы можете проверить статус аутентификации.

Что-то вроде этого:

class App extends Component {
  componentDidMount() {
    this.props.getAuth()
  }

  render() {
    return this.props.isReady
      ? <div> ready </div>
      : <div>not ready</div>
  }
}

const mapStateToProps = (state) => ({
  isReady: state.isReady,
})

const mapDispatchToProps = {
  getAuth,
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

Ответ 3

Я не был доволен любыми решениями, которые были выдвинуты для этого, и тогда мне пришло в голову, что я думал о классах, которые нужно было бы визуализировать. Как насчет того, если бы я просто создал класс для запуска, а затем ввел вещи в метод componentDidMount и просто отобразил экран render экран загрузки?

<Provider store={store}>
  <Startup>
    <Router>
      <Switch>
        <Route exact path='/' component={Homepage} />
      </Switch>
    </Router>
  </Startup>
</Provider>

И тогда у вас есть что-то вроде этого:

class Startup extends Component {
  static propTypes = {
    connection: PropTypes.object
  }
  componentDidMount() {
    this.props.actions.initialiseConnection();
  }
  render() {
    return this.props.connection
      ? this.props.children
      : (<p>Loading...</p>);
  }
}

function mapStateToProps(state) {
  return {
    connection: state.connection
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Startup);

Затем напишите некоторые действия redux, чтобы асинхронно инициализировать ваше приложение. Работает с удовольствием.

Ответ 4

Если вы используете React Hooks, одним решением будет:

useEffect(() => store.dispatch(handleAppInit()), []);

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

Полный пример:

import React, { useEffect } from 'react';
import { Provider } from 'react-redux';

import AppInitActions from './store/actions/appInit';

function App() {
  useEffect(() => store.dispatch(AppInitActions.handleAppInit()), []);
  return (
    <Provider store={store}>
      <div>
        Hello World
      </div>
    </Provider>
  );
}

export default App;

Ответ 5

Все ответы здесь кажутся вариациями при создании корневого компонента и его запуске в componentDidMount. Одна из вещей, которые мне больше всего нравятся в избыточности, это то, что она отделяет выборку данных от жизненных циклов компонентов. Я не вижу причин, почему в этом случае все должно быть иначе.

Если вы импортируете свое хранилище в корневой файл index.js, вы можете просто отправить создателю действия (пусть назовем его initScript()) в этом файле, и он сработает до того, как что-либо загрузится.

Например:

//index.js

store.dispatch(initScript());

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

Ответ 6

Похоже, но альтернатива вышеописанному (это работает только с React-Router v3):

Routes.js

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

import App from '../components/App';
import Home from '../views/Home';
import OnLoadAuth from '../containers/app/OnLoadAuth';

export default = (
  <Route path="/" component={OnLoadAuth(App)}>
    <IndexRoute component={Home} />
    {* Routes that require authentication *}
  </Route>
);

OnLoadAuth.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

import { authenticateUser, fetchingUser } from '../../actions/AuthActionCreators';
import Spinner from '../../components/loaders/Spinner';

export default App => {
  class OnLoadAuth extends Component {
    componentDidMount = () => this.props.authenticateUser();

    render = () => (
      (this.props.isLoading === undefined || this.props.isLoading)
       ? <Spinner />
       : <App {...this.props} />
    )
 }

 return connect(
    state => ({ isLoading: state.auth.fetchingUser }),
      { authenticateUser, fetchingUser }
    )(OnLoadAuth);
};

Ответ 7

С промежуточным программным обеспечением redux-saga вы можете сделать это красиво.

Просто определите сагу, которая не takeLatest отправленные действия (например, с take или takeLatest), прежде чем сработает. Когда fork эд от корня саги, как, что он будет работать ровно один раз при запуске приложения.

Ниже приведен неполный пример, который требует некоторых знаний о пакете redux-saga, но иллюстрирует суть:

Саги /launchSaga.js

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

import { launchStart, launchComplete } from '../actions/launch';
import { authenticationSuccess } from '../actions/authentication';
import { getAuthData } from '../utils/authentication';
// ... imports of other actions/functions etc..

/**
 * Place for initial configurations to run once when the app starts.
 */
const launchSaga = function* launchSaga() {
  yield put(launchStart());

  // Your authentication handling can go here.
  const authData = yield call(getAuthData, { params: ... });
  // ... some more authentication logic
  yield put(authenticationSuccess(authData));  // dispatch an action to notify the redux store of your authentication result

  yield put(launchComplete());
};

export default [launchSaga];

Приведенный выше код отправляет launchStart действие launchStart и launchComplete которое вы должны создать. Хорошей практикой является создание таких действий, когда они пригодятся, чтобы уведомить государство о необходимости выполнять другие действия при каждом запуске или завершении запуска.

Ваша корневая сага должна затем разветкить эту сагу launchSaga:

Саги /index.js

import { fork, all } from 'redux-saga/effects';
import launchSaga from './launchSaga';
// ... other saga imports

// Single entry point to start all sagas at once
const root = function* rootSaga() {
  yield all([
    fork( ... )
    // ... other sagas
    fork(launchSaga)
  ]);
};

export default root;

Пожалуйста, прочитайте действительно хорошую документацию по redux-saga для получения дополнительной информации об этом.

Ответ 8

Вот ответ с использованием последних в React (16.8), крючки:

import { appPreInit } from '../store/actions';
// app preInit is an action: const appPreInit = () => ({ type: APP_PRE_INIT })
import { useDispatch } from 'react-redux';
export default App() {
    const dispatch = useDispatch();
    // only change the dispatch effect when dispatch has changed, which should be never
    useEffect(() => dispatch(appPreInit()), [ dispatch ]);
    return (<div>---your app here---</div>);
}

Ответ 9

Использование: Apollo Client 2.0, React-Router v4, React 16 (Fiber)

Выбранный ответ использовать старый React Router v3. Мне нужно было сделать "диспетчеризацию", чтобы загрузить глобальные настройки для приложения. Хитрость заключается в использовании componentWillUpdate, хотя в примере используется клиент apollo, а не выборка решений эквивалентна. Вам не нужен букет

SettingsLoad.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from "redux";
import {
  graphql,
  compose,
} from 'react-apollo';

import {appSettingsLoad} from './actions/appActions';
import defQls from './defQls';
import {resolvePathObj} from "./utils/helper";
class SettingsLoad extends Component {

  constructor(props) {
    super(props);
  }

  componentWillMount() { // this give infinite loop or no sense if componente will mount or not, because render is called a lot of times

  }

  //componentWillReceiveProps(newProps) { // this give infinite loop
  componentWillUpdate(newProps) {

    const newrecord = resolvePathObj(newProps, 'getOrgSettings.getOrgSettings.record');
    const oldrecord = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    if (newrecord === oldrecord) {
      // when oldrecord (undefined) !== newrecord (string), means ql is loaded, and this will happens
      //  one time, rest of time:
      //     oldrecord (undefined) == newrecord (undefined)  // nothing loaded
      //     oldrecord (string) == newrecord (string)   // ql loaded and present in props
      return false;
    }
    if (typeof newrecord ==='undefined') {
      return false;
    }
    // here will executed one time
    setTimeout(() => {
      this.props.appSettingsLoad( JSON.parse(this.props.getOrgSettings.getOrgSettings.record));
    }, 1000);

  }
  componentDidMount() {
    //console.log('did mount this props', this.props);

  }

  render() {
    const record = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    return record
      ? this.props.children
      : (<p>...</p>);
  }
}

const withGraphql = compose(

  graphql(defQls.loadTable, {
    name: 'loadTable',
    options: props => {
      const optionsValues = {  };
      optionsValues.fetchPolicy = 'network-only';
      return optionsValues ;
    },
  }),
)(SettingsLoad);


const mapStateToProps = (state, ownProps) => {
  return {
    myState: state,
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators ({appSettingsLoad, dispatch }, dispatch );  // to set this.props.dispatch
};

const ComponentFull = connect(
  mapStateToProps ,
  mapDispatchToProps,
)(withGraphql);

export default ComponentFull;

App.js

class App extends Component<Props> {
  render() {

    return (
        <ApolloProvider client={client}>
          <Provider store={store} >
            <SettingsLoad>
              <BrowserRouter>
            <Switch>
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/myaccount"
                component={MyAccount}
                title="form.myAccount"
              />
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/dashboard"
                component={Dashboard}
                title="menu.dashboard"
              />