У меня есть аутентификация токена с сервера, поэтому, когда мое приложение Redux загружается изначально, мне нужно сделать запрос на этот сервер, чтобы проверить, аутентифицирован ли пользователь или нет, и если да, я должен получить токен.
Я обнаружил, что использование действий INIT ядра Redux не рекомендуется, поэтому как я могу отправить действие до того, как приложение будет отрисовано?
componentWillMount()
сделано. Я определил простую функцию, вызывающую все действия, связанные с отправкой, вmapDispatchToProps()
App.js и вызвал ееcomponentWillMount()
.[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.
Uncaught Error: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(App)".
Я не был доволен какими-либо решениями, которые были предложены для этого, и тогда мне пришло в голову, что я думал о классах, которые необходимо отрендерить. Как насчет того, чтобы я просто создал класс для запуска, а затем вставил все в
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 для асинхронной инициализации вашего приложения. Работает удовольствие.
источник
Все ответы здесь кажутся вариантами создания корневого компонента и его запуска в componentDidMount. Одна из вещей, которые мне больше всего нравятся в redux, - это то, что он отделяет получение данных от жизненного цикла компонентов. Я не вижу причин, почему в данном случае должно быть иначе.
Если вы импортируете свой магазин в корневой
index.js
файл, вы можете просто отправить создателя действий (назовем егоinitScript()
) в этот файл, и он сработает до того, как что-нибудь загрузится.Например:
//index.js store.dispatch(initScript()); ReactDOM.render( <Provider store={store}> <Routes /> </Provider>, document.getElementById('root') );
источник
componentDidMount
события?componentDidMount
сработает перед установкой определенного компонента. Увольнениеstore.dispatch()
до того ReactDOM.render () `пожары до того , как приложение монтирует. Это вроде какcomponentWillMount
для всего приложения. Как новичок, я думаю, что лучше придерживаться методов жизненного цикла компонентов, поскольку они тесно связаны с логикой того, где они используются. По мере того, как приложения становятся все более сложными, делать это становится все труднее. Я бы посоветовал сделать это простым как можно дольше.Если вы используете React Hooks, одно однострочное решение
useEffect(() => store.dispatch(handleAppInit()), []);
Пустой массив гарантирует, что он вызывается только один раз при первом рендеринге.
Полный пример:
import React, { useEffect } from 'react'; import { Provider } from 'react-redux'; import AppInitActions from './store/actions/appInit'; import store from './store'; export default function App() { useEffect(() => store.dispatch(AppInitActions.handleAppInit()), []); return ( <Provider store={store}> <div> Hello World </div> </Provider> ); }
источник
Обновление 2020 : наряду с другими решениями я использую промежуточное ПО Redux для проверки каждого запроса на наличие неудачных попыток входа в систему:
export default () => next => action => { const result = next(action); const { type, payload } = result; if (type.endsWith('Failure')) { if (payload.status === 401) { removeToken(); window.location.replace('/login'); } } return result; };
Обновление 2018 : это ответ для React Router 3
Я решил эту проблему, используя response -router onEnter props . Вот как выглядит код:
// 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> );
источник
С промежуточным программным обеспечением redux-saga вы можете сделать это хорошо.
Просто определите сагу, которая не отслеживает отправленное действие (например, с помощью
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
иlaunchComplete
redux, которое вы должны создать. Рекомендуется создавать такие действия, поскольку они пригодятся, чтобы уведомить государство о выполнении других действий при запуске или завершении запуска.Затем ваша корневая сага должна разветвляться на эту
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 для получения дополнительной информации.
источник
Вот ответ с использованием последней версии React (16.8), Hooks:
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>); }
источник
Я использовал redux-thunk для извлечения учетных записей пользователя из конечной точки API при инициализации приложения, и это было асинхронно, поэтому данные поступали после рендеринга моего приложения, и большинство вышеперечисленных решений не творили для меня чудес, а некоторые из них обесценивается. Поэтому я посмотрел на componentDidUpdate (). Таким образом, в основном при инициализации приложения мне приходилось иметь списки учетных записей из API, и мои учетные записи в хранилище redux были бы нулевыми или []. К этому прибегли уже после.
class SwitchAccount extends Component { constructor(props) { super(props); this.Format_Account_List = this.Format_Account_List.bind(this); //function to format list for html form drop down //Local state this.state = { formattedUserAccounts : [], //Accounts list with html formatting for drop down selectedUserAccount: [] //selected account by user } } //Check if accounts has been updated by redux thunk and update state componentDidUpdate(prevProps) { if (prevProps.accounts !== this.props.accounts) { this.Format_Account_List(this.props.accounts); } } //take the JSON data and work with it :-) Format_Account_List(json_data){ let a_users_list = []; //create user array for(let i = 0; i < json_data.length; i++) { let data = JSON.parse(json_data[i]); let s_username = <option key={i} value={data.s_username}>{data.s_username}</option>; a_users_list.push(s_username); //object } this.setState({formattedUserAccounts: a_users_list}); //state for drop down list (html formatted) } changeAccount() { //do some account change checks here } render() { return ( <Form > <Form.Group > <Form.Control onChange={e => this.setState( {selectedUserAccount : e.target.value})} as="select"> {this.state.formattedUserAccounts} </Form.Control> </Form.Group> <Button variant="info" size="lg" onClick={this.changeAccount} block>Select</Button> </Form> ); } } const mapStateToProps = state => ({ accounts: state.accountSelection.accounts, //accounts from redux store }); export default connect(mapStateToProps)(SwitchAccount);
источник
Если вы используете React Hooks, вы можете просто отправить действие с помощью React.useEffect
Я использую этот шаблон для
onAuthStateChanged
слушателя регистраfunction App(props) { const [user, setUser] = React.useState(props.authUser); React.useEffect(() => setUser(props.authUser), [props.authUser]); React.useEffect(props.dispatchOnAuthListener, []); return <>{user.loading ? "Loading.." :"Hello! User"}<>; } const mapStateToProps = (state) => { return { authUser: state.authentication, }; }; const mapDispatchToProps = (dispatch) => { return { dispatchOnAuthListener: () => dispatch(registerOnAuthListener()), }; }; export default connect(mapStateToProps, mapDispatchToProps)(App);
источник
Использование: 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" />
источник