サーバーからトークン認証を取得しているので、Reduxアプリが最初に読み込まれたときに、このサーバーにリクエストを送信して、ユーザーが認証されているかどうかを確認する必要があります。認証されている場合は、トークンを取得する必要があります。
ReduxコアのINITアクションの使用は推奨されていないことがわかりました。アプリがレンダリングされる前に、アクションをディスパッチするにはどうすればよいですか?
回答:
ルート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)
[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について私が最も楽しんでいることの1つは、コンポーネントのライフサイクルからデータフェッチを切り離すことです。この場合、それが異なるはずである理由はわかりません。
ストアをルート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を使用している場合、1行のソリューションは次のとおりです。
useEffect(() => store.dispatch(handleAppInit()), []);
空の配列は、最初のレンダリングで1回だけ呼び出されることを保証します。
完全な例:
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ルーター3用です
私はreact- routeronEnter小道具を使用してこの問題を解決しました。コードは次のようになります。
// 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の-佐賀ミドルウェアあなたはうまくそれを行うことができます。
トリガーされる前に、ディスパッチされたアクション(withtake
またはなどtakeLatest
)を監視しないサガを定義するだけです。そのfork
ようなルートサガから編集された場合、アプリの起動時に1回だけ実行されます。
以下は不完全な例であり、redux-saga
パッケージに関する少しの知識が必要ですが、要点を示しています。
sagas / 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
サガをフォークする必要があります。
sagas / 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)の最新のフックを使用した回答は次のとおりです。
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()を調べました。したがって、基本的にAPP initでは、APIからのアカウントリストが必要であり、reduxストアアカウントはnullまたは[]になります。後にこれに頼った。
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フックを使用している場合は、React.useEffectを使用してアクションをディスパッチできます。
React.useEffect(props.dispatchOnAuthListener, []);
このパターンをレジスタ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(ファイバー)
選択した答えは、古いReact Routerv3を使用しています。アプリのグローバル設定を読み込むには、「ディスパッチ」を実行する必要がありました。トリックはcomponentWillUpdateを使用することですが、例ではapolloクライアントを使用しており、ソリューションをフェッチしないことは同等です。あなたはのboucleを必要としません
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"
/>
componentWillMount()
しました。mapDispatchToProps()
App.jsのディスパッチ関連のすべてのアクションを呼び出す単純な関数を定義し、で呼び出しましたcomponentWillMount()
。