webentwicklung-frage-antwort-db.com.de

React-Router: So warten Sie auf eine asynchrone Aktion vor dem Routenübergang

Ist es möglich, eine async-Redux-Aktion, die als thunk bezeichnet wird, auf einer bestimmten Route aufzurufen und den Übergang erst durchzuführen, wenn die Antwort erfolgreich war oder fehlgeschlagen ist?

Anwendungsfall

Wir müssen Daten vom Server laden und ein Formular mit Anfangswerten füllen. Diese Anfangswerte sind nicht vorhanden, bis die Daten vom Server abgerufen werden.

eine Syntax wie diese wäre toll:

<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
9
AndrewMcLagan

Um die ursprüngliche Frage zu beantworten, den Übergang zu einer neuen Route zu verhindern, bis eine Antwort erfolgreich war oder fehlgeschlagen ist:

Da Sie Redux Thunk verwenden, kann dies dazu führen, dass der Ersteller der Aktion den Erfolg oder Misserfolg der Aktion auslöst. Ich weiß nicht, wie Ihr bestimmter Aktions-/Aktionsersteller aussieht, aber so etwas könnte funktionieren:

import { browserHistory } from 'react-router'

export function loadInitialFormValues(formId) {
  return function(dispatch) {
    // hit the API with some function and return a promise:
    loadInitialValuesReturnPromise(formId)
      .then(response => {
        // If request is good update state with fetched data
        dispatch({ type: UPDATE_FORM_STATE, payload: response });

        // - redirect to the your form
        browserHistory.Push('/myForm');
      })
      .catch(() => {
        // If request is bad...
        // do whatever you want here, or redirect
        browserHistory.Push('/myForm')
      });
  }
}

Nachverfolgen. Übliches Muster zum Laden von Daten bei Eingabe einer Route/auf ComponentWillMount einer Komponente und Anzeigen eines Drehfelds:

Aus den redux-Dokumenten zu asynchronen Aktionen http://redux.js.org/docs/advanced/AsyncActions.html

  • Eine Aktion, die die Reduzierer darüber informiert, dass die Anfrage begonnen hat.

Die Reduzierer können diese Aktion durch Umschalten eines isFetching-Flags in .__ behandeln. der Staat. Auf diese Weise weiß die Benutzeroberfläche, dass es an der Zeit ist, einen Spinner zu zeigen.

  • Eine Aktion, die die Reduzierer darüber informiert, dass die Anforderung erfolgreich abgeschlossen wurde.

Die Reduzierer können diese Aktion durchführen, indem sie die neuen Daten in die .__ einführen. Zustand, den sie verwalten und zurücksetzen isFetching. Die Benutzeroberfläche würde das .__ ausblenden. Spinner, und zeigen Sie die abgerufenen Daten an.

  • Eine Aktion, die die Reduzierer darüber informiert, dass die Anforderung fehlgeschlagen ist.

Die Reduzierungen können diese Aktion durch Zurücksetzen von isFetching ..__ ausführen. Darüber hinaus möchten einige Reduzierer die Fehlermeldung speichern, so dass UI kann es anzeigen.

Ich folgte diesem allgemeinen Muster unten und griff Ihre Situation als grobe Richtlinie auf. Sie müssen keine Versprechungen verwenden

// action creator:
export function fetchFormData(formId) {
  return dispatch => {
    // an action to signal the beginning of your request
    // this is what eventually triggers the displaying of the spinner
    dispatch({ type: FETCH_FORM_DATA_REQUEST })

    // (axios is just a promise based HTTP library)
    axios.get(`/formdata/${formId}`)
      .then(formData => {
        // on successful fetch, update your state with the new form data
        // you can also turn these into their own action creators and dispatch the invoked function instead
        dispatch({ type: actions.FETCH_FORM_DATA_SUCCESS, payload: formData })
      })
      .catch(error => {
        // on error, do whatever is best for your use case
        dispatch({ type: actions.FETCH_FORM_DATA_ERROR, payload: error })
      })
  }
}

// reducer

const INITIAL_STATE = {
  formData: {},
  error: {},
  fetching: false
}

export default function(state = INITIAL_STATE, action) {
  switch(action.type) {
    case FETCH_FORM_DATA_REQUEST:
      // when dispatch the 'request' action, toggle fetching to true
      return Object.assign({}, state, { fetching: true })
    case FETCH_FORM_DATA_SUCCESS:
      return Object.assign({}, state, {
        fetching: false,
        formData: action.payload
      })
    case FETCH_FORM_DATA_ERROR:
      return Object.assign({}, state, {
        fetching: false,
        error: action.payload
      })
  }
}

// route can look something like this to access the formId in the URL if you want
// I use this URL param in the component below but you can access this ID anyway you want:
<Route path="/myForm/:formId" component={SomeForm} />

// form component
class SomeForm extends Component {
  componentWillMount() {
    // get formId from route params
    const formId = this.props.params.formId
    this.props.fetchFormData(formId)
  }

  // in render just check if the fetching process is happening to know when to display the spinner
  // this could also be abstracted out into another method and run like so: {this.showFormOrSpinner.call(this)}
  render() {
    return (
      <div className="some-form">
        {this.props.fetching ? 
          <img src="./assets/spinner.gif" alt="loading spinner" /> :
          <FormComponent formData={this.props.formData} />
        }
      </div>
    )
  }
}

function mapStateToProps(state) {
  return {
    fetching: state.form.fetching,
    formData: state.form.formData,
    error: state.form.error
  }
}

export default connect(mapStateToProps, { fetchFormData })(SomeForm)
10
jmancherje

Zuallererst möchte ich sagen, dass ist eine Debatte um das Thema des Abrufens von Daten mit onEnter des Reaktorrouters, ob es eine gute Praxis ist oder nicht, trotzdem würde so etwas in der Art sein gehen:

Sie können den redux-store an Ihre Router übergeben. Das Folgende sollte Ihre Root-Komponente sein, an der Router angehängt ist:

...
import routes from 'routes-location';

class Root extends React.Component {
  render() {
    const { store, history } = this.props;

    return (
      <Provider store={store}>
        <Router history={history}>
          { routes(store) }
        </Router>
      </Provider>
    );
  }
}
...

Und Ihre Routen werden ungefähr so ​​sein:

import ...
...

const fetchData = (store) => {
  return (nextState, transition, callback) => {
    const { dispatch, getState } = store;
    const { loaded } = getState().myCoolReduxStore;
    // loaded is a key from my store that I put true when data has loaded

    if (!loaded) {
      // no data, dispatch action to get it
      dispatch(getDataAction())
        .then((data) => {
          callback();
        })
        .catch((error) => {
          // maybe it failed because of 403 forbitten, we can use tranition to redirect.
          // what's in state will come as props to the component `/forbitten` will mount.
          transition({
            pathname: '/forbitten',
            state: { error: error }
          });
          callback();
        });
    } else {
      // we already have the data loaded, let router continue its transition to the route
      callback();
    }
  }
};

export default (store) => {
  return (
    <Route path="/" component={App}>
      <Route path="myPage" name="My Page" component={MyPage} onEnter={fetchData(store)} />
      <Route path="forbitten" name="403" component={PageForbitten} />
      <Route path="*" name="404" component={PageNotFound} />
    </Route>
  );
};

Bitte beachten Sie, dass Ihre Router-Datei einen Thunk mit Ihrem Store als Argument exportiert. Wenn Sie nach oben schauen, sehen Sie, wie wir den Router aufgerufen haben. Wir übergeben das Store-Objekt an ihn.

Zum Zeitpunkt des Schreibens Reaktiver-Router-Dokumente geben Sie leider 404 an mich zurück. Daher kann ich Sie nicht auf die Dokumente verweisen, in denen (nextState, transition, callback) beschrieben ist. Aber zu denen aus meiner Erinnerung:

  • nextState beschreibt die Route, zu der react-router wechselt.

  • transition Funktion zur Vorformung, möglicherweise ein anderer Übergang als der von nextState;

  • callback löst den Übergang der Route aus.

Eine weitere Überlegung ist, dass Ihre Dispatch-Aktion mit redux-thunk ein Versprechen zurückgeben kann. Überprüfen Sie dies in den Dokumenten hier . Hier ist ein gutes Beispiel dafür, wie Sie Ihren redux-Store mit redux-thunk konfigurieren.

2
Dragos Rizescu

browserHistory funktioniert jetzt aufgrund dieses Fehlers nicht:

'browserHistory' wird nicht vom 'Reakt-Router' exportiert.

verwenden Sie stattdessen diesen Code:

import { createHashHistory } from 'history'

const history = createHashHistory()

und verwenden 

this.props.history.Push('/some_url')

auf Ihrer fetch oder wo auch immer.

0
mahradbt