2016-06-16 46 views
6

Czy można wywołać akcję async redux znaną jako thunk na określonej trasie i nie przeprowadzać przejścia, dopóki odpowiedź nie powiodła się lub się nie powiodła?React-Router: jak czekać na akcję asynchroniczną przed zmianą trasy

Use Case

Musimy wczytać dane z serwera i wypełnić formularz z wartościami początkowymi. Te wartości początkowe nie istnieją, dopóki dane nie zostaną pobrane z serwera.

niektóre składnia jak to byłoby świetnie:

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

Kiedy nastąpi oczekiwana zmiana trasy? Czy ktoś kliknął link lub przycisk? Albo coś innego? – jmancherje

+0

Cóż, dowolne z nich: przycisk, programowo, ładowanie strony itd. Wszystkie te przypadki. – AndrewMcLagan

Odpowiedz

8

Aby odpowiedzieć na pytanie, zapobiegając oryginalnego przejście do nowej trasy aż odpowiedzią udało lub nie powiodło się:

Bo jesteś za pomocą redux thunk możesz mieć powodzenie lub niepowodzenie w twórcy akcji wywołać przekierowanie. Nie wiem, co się specyficzny twórca działanie/działania wygląda ale coś takiego może działać:

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') 
     }); 
    } 
} 

obserwacji. Wspólny wzór ładowania danych na wejściu trasy/na componentWillMount składnika i wyświetlania tarczy:

Z Redux docs na działaniach asynchronicznych http://redux.js.org/docs/advanced/AsyncActions.html

  • Działanie informowanie reduktory, które rozpoczęły się prośba .

Reduktory mogą obsłużyć tę akcję poprzez przełączenie flagi isFetching w stanie . W ten sposób interfejs użytkownika wie, że nadszedł czas, aby pokazać spinner.

  • Działanie informujące reduktorów o pomyślnym zakończeniu żądania.

Reduktory mogą obsłużyć to działanie, łącząc nowe dane w stan , którym zarządzają i resetują pobieranie. Interfejs użytkownika ukryje pokrętło i wyświetli pobrane dane.

  • Działanie informujące reduktorów o niepowodzeniu żądania.

Reduktory mogą obsłużyć to działanie, resetując isFetching. Ponadto niektóre reduktory mogą chcieć zapisać komunikat o błędzie, aby można było wyświetlić interfejs użytkownika .

Postępowałem zgodnie z tym ogólnym schematem poniżej, wykorzystując twoją sytuację jako przybliżoną wytyczną.Nie trzeba korzystać z obietnic

// 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) 
+1

To wydaje się być anty-wzorem. Sądziłem, że wszystkie trasy, które pasują do wzorca, mogą spowodować ponowne załadowanie stanu, aby automatycznie zaktualizować całe drzewo stanu. – AndrewMcLagan

+2

Można również uruchomić procedurę obsługi zdarzenia onEnter na trasie lub pobranie składnika componentWillMount pobrać dane i mieć pokrętło, podczas gdy dane formularza są pobierane zamiast zamrożenia nawigacji. To bardzo powszechne użycie dla Redux Thunk – jmancherje

+0

Wydaje mi się, że mam taki pomysł :-) czy to jest odpowiedź? – AndrewMcLagan

2

przede wszystkim, chcę powiedzieć, że nie is a debate around tematem pobierania danych z reagują-router za onEnter haków czy nie jest dobra praktyka, mimo to jak coś na przykład:

Możesz przekazać sklep redux do swojego Router. Niech dodaje się komponent głównej, gdzie Router jest zamontowany:

... 
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> 
    ); 
    } 
} 
... 

A twoje trasy będzie coś takiego:

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> 
); 
}; 

Proszę zauważyć, że plik router eksportowania thunk ze sklepu jako argumentu , jeśli spojrzysz w górę, zobacz, jak wywołaliśmy router, przekazujemy do niego obiekt sklepu.

Niestety, w chwili pisania react-router docs zwróć mi 404, więc nie mogę wskazać ci dokumentów, w których opisane są (nextState, transition, callback). Ale o tym, z mojej pamięci:

  • nextState opisuje trasę react-router będzie przejście do;

  • transition funkcja do preformowania może być innym przejściem niż z nextState;

  • callback spowoduje przejście trasy do celu.

Innym myśleć zwrócić uwagę jest to, że z Redux-thunk, twoje działanie wysyłka może powrócić obietnicę, sprawdź to w docs here. Możesz znaleźć here dobry przykład, jak skonfigurować swój sklep redux za pomocą redux-thunk.