Ich habe eine Token-Authentifizierung von einem Server. Wenn meine Redux-App zum ersten Mal geladen wird, muss ich eine Anfrage an diesen Server stellen, um zu überprüfen, ob der Benutzer authentifiziert ist oder nicht. Wenn ja, sollte ich ein Token erhalten.
Ich habe festgestellt, dass die Verwendung von Redux Core INIT-Aktionen nicht empfohlen wird. Wie kann ich also eine Aktion auslösen, bevor die App gerendert wird?
componentWillMount()
hat das Ding gemacht. Ich habe eine einfache Funktion definiert, die allemapDispatchToProps()
versandbezogenen Aktionen in App.js aufruft und aufruftcomponentWillMount()
.getAuth
, dass es sich umdispatch
einen Aktionsersteller handeltmapDispatchToProps
, möchten Sie sich als Parameter definieren , dhconst mapDispatchToProps = dispatch => {
und dann etwas tun wie:getAuth: () => { dispatch(getAuth()); }
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)".
Ich war mit keinen Lösungen zufrieden, die dafür vorgeschlagen wurden, und dann kam mir der Gedanke, dass ich über Klassen nachdachte, die gerendert werden mussten. Was ist, wenn ich gerade eine Klasse für den Start erstellt und dann Dinge in die
componentDidMount
Methode verschoben habe und dierender
Anzeige nur einen Ladebildschirm hat?<Provider store={store}> <Startup> <Router> <Switch> <Route exact path='/' component={Homepage} /> </Switch> </Router> </Startup> </Provider>
Und dann haben Sie so etwas:
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);
Schreiben Sie dann einige Redux-Aktionen, um Ihre App asynchron zu initialisieren. Arbeitet ein Vergnügen.
quelle
Alle Antworten hier scheinen Variationen beim Erstellen einer Stammkomponente und beim Auslösen in componentDidMount zu sein. Eines der Dinge, die ich am meisten an Redux mag, ist, dass es das Abrufen von Daten von Komponentenlebenszyklen entkoppelt. Ich sehe keinen Grund, warum es in diesem Fall anders sein sollte.
Wenn Sie Ihren Shop in die Stammdatei importieren
index.js
, können Sie einfach Ihren Aktionsersteller (nennen wir ihninitScript()
) in diese Datei senden, und er wird ausgelöst, bevor etwas geladen wird.Zum Beispiel:
//index.js store.dispatch(initScript()); ReactDOM.render( <Provider store={store}> <Routes /> </Provider>, document.getElementById('root') );
quelle
componentDidMount
Ereignis?componentDidMount
wird also ausgelöst, bevor eine bestimmte Komponente montiert wird. Brennenstore.dispatch()
vor ReactDOM.render () `Feuer vor dem App Mounts. Es ist wie eincomponentWillMount
für die gesamte App. Als Neuling denke ich, dass es besser ist, sich an die Methoden des Komponentenlebenszyklus zu halten, da dadurch die Logik eng an den Verwendungsort gekoppelt bleibt. Da Apps immer komplexer werden, wird es schwieriger, dies fortzusetzen. Mein Rat wäre, es so lange wie möglich einfach zu halten.Wenn Sie React Hooks verwenden, wäre eine einzeilige Lösung:
useEffect(() => store.dispatch(handleAppInit()), []);
Das leere Array würde sicherstellen, dass es beim ersten Rendern nur einmal aufgerufen wird.
Vollständiges Beispiel:
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;
quelle
Update 2020 : Zusammen mit anderen Lösungen verwende ich Redux-Middleware, um jede Anforderung auf fehlgeschlagene Anmeldeversuche zu überprüfen:
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; };
Update 2018 : Diese Antwort gilt für React Router 3
Ich habe dieses Problem mit React -Router onEnter Requisiten gelöst . So sieht Code aus:
// 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> );
quelle
Mit der Redux-Saga- Middleware können Sie das gut machen.
Definieren Sie einfach eine Saga, die nicht auf ausgelöste Aktionen wartet (z. B. mit
take
odertakeLatest
), bevor sie ausgelöst wird. Wenn es sofork
aus der Root-Saga stammt, wird es beim Start der App genau einmal ausgeführt.Das Folgende ist ein unvollständiges Beispiel, das ein wenig Wissen über das
redux-saga
Paket erfordert , aber den Punkt veranschaulicht: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];
Der obige Code löst eine
launchStart
undlaunchComplete
Redux-Aktion aus, die Sie erstellen sollten. Es ist eine gute Praxis, solche Aktionen zu erstellen, da sie nützlich sind, um den Staat zu benachrichtigen, wenn der Start gestartet oder abgeschlossen wird, andere Dinge zu tun.Ihre Wurzelsaga sollte dann diese
launchSaga
Saga teilen: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;
Bitte lesen Sie die wirklich gute Dokumentation der Redux-Saga, um weitere Informationen darüber zu erhalten.
quelle
Hier ist eine Antwort mit der neuesten Version von 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>); }
quelle
Ich habe Redux-Thunk verwendet, um Konten unter einem Benutzer von einem API-Endpunkt auf App Init abzurufen. Es war asynchron, sodass Daten nach dem Rendern meiner App eingingen und die meisten der oben genannten Lösungen keine Wunder für mich taten und einige auch abgeschrieben. Also habe ich mir componentDidUpdate () angesehen. Grundsätzlich musste ich auf APP init Kontenlisten von der API haben, und meine Redux-Speicherkonten wären null oder []. Nachher darauf zurückgegriffen.
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);
quelle
Wenn Sie React Hooks verwenden, können Sie eine Aktion einfach mit React.useEffect auslösen
Ich benutze dieses Muster für Register
onAuthStateChanged
Listenerfunction 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);
quelle
Verwenden von: Apollo Client 2.0, React-Router v4, React 16 (Glasfaser)
Die ausgewählte Antwort verwendet den alten React Router v3. Ich musste "Versand" durchführen, um die globalen Einstellungen für die App zu laden. Der Trick ist die Verwendung von componentWillUpdate, obwohl das Beispiel den Apollo-Client verwendet und das Abrufen der Lösungen nicht gleichwertig ist. Du brauchst keinen Boucle von
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" />
quelle