Redux: Best Practice für den Umgang mit dem Datenfluss für Anmelde-/Registrierungsseiten mit Weiterleitung

Erstellt am 22. Juli 2015  ·  66Kommentare  ·  Quelle: reduxjs/redux

BEARBEITEN: Was ich hier zu verstehen versuche, ist, wie mit dem Datenfluss umgegangen wird, wenn Sie eine Seite mit einem Formular haben. Nachdem Sie das Formular abgeschickt haben , leiten Sie entweder auf eine andere Seite um oder geben eine Fehlermeldung aus .

Die Herausforderung besteht darin, wo diese Informationen im Datenfluss _zwischengespeichert_ werden sollen. Denn nachdem Sie die Nachricht umgeleitet oder gerendert haben, ist der Status (oder die von Ihnen gespeicherten Informationen) nicht mehr relevant und sollte zurückgesetzt oder bereinigt werden.


In einer Backend-Webanwendung haben Sie normalerweise Seiten wie Login, Registrierung, Passwort zurücksetzen usw.

Wenn der Benutzer zur App navigiert und nicht angemeldet ist, wird er zur Anmeldeseite weitergeleitet. Das geht ganz einfach mit dem Hook RR onEnter .

Dann füllt der Benutzer das Formular aus und sendet es ab. Wenn die Anfrage erfolgreich war, möchten Sie zu diesem Zeitpunkt normalerweise zum Anwendungsstamm oder zu einer anderen "gesicherten" Seite umleiten. Wenn dies fehlschlägt, möchten Sie auf der Seite bleiben und eine Fehlermeldung anzeigen.

(Dies gilt nicht nur für die Anmeldeseite, sondern auch für andere zuvor erwähnte Seiten. Ich werde mich vorerst nur auf die Anmeldung konzentrieren.)

Bisher haben Sie diesen Flow (was ein häufiger Anwendungsfall ist):

  • Benutzer gehen zu /
  • Weiterleitung zu /login
  • Formular ausfüllen und absenden
  • wenn ok Weiterleitung auf / (oder eine andere Seite)
  • Wenn dies fehlschlägt, bleiben Sie im Anmeldeformular und zeigen Sie die Fehlermeldung an

Wenn wir diesen Flow mit Redux implementieren, habe ich wohl folgendes Setup:

// actions
function login (form) {
  return dispatch => doLogin(form)
    .then(() => dispatch({ type: 'LOGGED_IN' }))
    .catch(() => dispatch({ type: 'LOGIN_FAILED' }))
}

// reducer
const initialState = {
  // some fields...,
  loggedIn: false,
  shouldRedirect: false,
  errorMessage: null
}
function application (state = initialState, action) {
  switch action.type {
    case 'LOGGED_IN':
      return Object.assign({}, state, { loggedIn: true, shouldRedirect: true })
    case 'LOGIN_FAILED':
      return Object.assign({}, state, { loggedIn: false, shouldRedirect: false, errorMessage: action.error.message })
  }
  return state
}

// component
@connect(state => { application: state.application })
class Login extends React.Component {
  componentWillUpdate () {
    const { router } = this.context
    const { application } = this.props
    if (application.shouldRedirect)
      router.transition(...)  
  }

  onSubmit () {
    const actions = bindActionCreators(applicationActions, dispatch)
    actions.login({...})
  }

  render () {
    const { errorMessage } = this.props
    return (
      <div>
        {errorMessage ? <p>{errorMessage}</p> : null}
        <Form {...props} onSubmit={onSubmit}>
          {...}
        </Form>
      </div>
    )
  }
}

Wenn dieser Ablauf korrekt ist - hoffentlich :) - können Sie sehen, dass ich 2 Flags im Anwendungsstatus habe: shouldRedirect und errorMessage .
Diese Flags werden nur beim Absenden des Formulars verwendet.

Erste Frage: Ist es in Ordnung, diese Flags im Anwendungsstatus zu haben?

Eine andere zu berücksichtigende Sache ist, dass ich diese Flags "zurücksetzen" muss, wenn ich umleite, denn wenn ich zu einer anderen Seite gehen möchte (z. B. Anmeldung, ...), sollte ich einen sauberen Zustand haben ( { shouldRedirect: false, errorMessage: null } ).

Also möchte ich vielleicht eine andere Aktion wie versenden

// actions
function resetSubmitState () {
  return { type: 'RESET_SUBMIT' }
}

// component
componentWillUpdate () {
  const { store, router } = this.context
  const { application } = this.props
  if (application.shouldRedirect) {
    store.dispatch(applicationActions.resetSubmitState())
    router.transition(...)  
  }
}

Hatte jemand dieses Problem? Übersehe ich etwas oder ist das der "richtige" Weg?

Gibt es noch andere Alternativen?

Jedes Feedback ist sehr willkommen! :)

discussion docs

Hilfreichster Kommentar

Ich denke, die Frage ist ein bisschen tiefer als nur die Verallgemeinerung von „Formularen einreichen“ – es geht mehr darum, was der Status eigentlich ist und welche Daten in store erscheinen sollten und welche nicht.

Ich stelle mir den View Teil des Flussdatenflusses gerne als reine Funktion vor render(state): DOM , aber das ist nicht ganz richtig.
Nehmen wir als Beispiel eine einfache Formularkomponente, die zwei Eingabefelder darstellt:

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
      </form>
    )
  }
}

Während der Eingabe in diese Felder wurden einige interne Zustandsänderungen durchgeführt, sodass wir am Ende so etwas haben werden:

{
  name: 'John'
  surname: 'Snow'
}

Wir haben also einen Status, der nicht mit dem Anwendungsstatus verknüpft ist, und niemand kümmert sich darum. Wenn wir dieses Formular das nächste Mal erneut aufrufen, wird es gelöscht angezeigt und der „John Snow“ wird für immer verschwunden sein.
Sieht so aus, als hätten wir DOM modifiziert, ohne den Flussdatenfluss zu berühren.

Nehmen wir an, dass dies ein Abonnementformular für einen Benachrichtigungsbrief ist. Jetzt sollten wir mit der Außenwelt interagieren und wir werden es mit unserer Spezialaktion tun.

import { subscribe } from 'actions'

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    subscribe(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
      </form>
    )
  }
}

Wenn der Benutzer auf die Schaltfläche „Senden“ drückt, erwartet er eine Antwort von der Benutzeroberfläche, ob es erfolgreich war oder nicht, und was als nächstes zu tun ist. Wir möchten also den Anforderungsstatus verfolgen, um den Ladebildschirm zu rendern, umzuleiten oder Fehlermeldungen anzuzeigen.
Die erste Idee ist, einige interne Aktionen wie loading , submitted , failed , um den Anwendungsstatus dafür verantwortlich zu machen. Weil wir den Zustand darstellen wollen und uns keine Gedanken über Details machen.

  render () {
    //provided by connector of whatever
    const { props: { isLoading, error } } = this
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}
        disabled={isLoading}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {error ? <p>{ error }</p> : null}
      </form>
    )
  }

Aber hier erscheint der Teufel: Jetzt haben wir diesen Seitenzustand beibehalten und der nächste render(state) -Aufruf wird das Formular in einem schmutzigen Zustand anzeigen, also müssen wir eine cleanup -Aktion erstellen, um es jedes Mal aufzurufen, wenn wir das Formular einhängen.
Und für jedes Formular, das wir haben, fügen wir die gleichen Muster von loading , submitted , failed und cleanup Aktionen mit Läden/Reduzierern hinzu … bis wir aufhören und nachdenken über den Zustand, den wir eigentlich rendern wollen.

Gehört submitted zum Anwendungsstatus oder ist es nur ein Komponentenstatus, wie der name ? Interessiert sich der Benutzer wirklich dafür, dass dieser Zustand in einem Cache gespeichert wird, oder möchte er das saubere Formular sehen, wenn er es erneut besucht? Die Antwort: Es kommt darauf an.

Aber bei reinen Submit-Formularen könnten wir davon ausgehen, dass der Formularstatus nichts mit dem Anwendungsstatus zu tun hat: Wir wollen wissen, ob es sich bei der Anmeldeseite um Benutzer logged in handelt oder nicht, oder wir wollen nicht um etwas darüber zu erfahren, ob er das Passwort geändert hat oder fehlgeschlagen ist: Wenn die Anfrage erfolgreich ist, leiten Sie ihn einfach woanders um.

Wie wäre es also mit der Behauptung, wir brauchen diesen Zustand nirgendwo anders als bei Component? Lass es uns versuchen.
Um eine ausgefallenere Implementierung bereitzustellen, könnten wir annehmen, dass wir 2015 in der asynchronen Welt leben und überall Promises haben, also lassen Sie unsere Aktion ein Promise zurückgeben:

function subscribe (name, surname, email) {
  return api.request('/subscribtions', 'create', { name, surname, email })
}

Im nächsten Schritt könnten wir damit beginnen, die Aktion in unserem Component zu verfolgen:

onSubmit (event) {
  event.preventDefault()
  const { name, surname, email } = this.state
  subscribe(name, surname, email)
    .then(() => { this.setState({ submitted: true }) })
    .catch(error => { this.setState({ error }) })
}

componentWillUpdate (object nextProps, object nextState) {
  if(nextState.submitted)
    redirect('somewhere')
}

Sieht so aus, als hätten wir alles, was wir brauchen, um Komponenten zu rendern, ohne den Anwendungsstatus zu verschmutzen, und müssen sie nicht mehr reinigen.
Wir sind also fast da. Jetzt könnten wir damit beginnen, den Fluss Component zu verallgemeinern.
Lassen Sie uns zunächst auf Bedenken eingehen: Verfolgung von Aktionen und Reaktionen auf Statusaktualisierungen:
(Lassen Sie es uns in Form von redux tun, um in Repo-Grenzen zu bleiben)

import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'

// Keeps track of action
export default function connectSubmitForm (Form, submitAction) {
  return React.createClass({
    contextTypes: {
      //redux Store
      store: PropTypes.object.isRequired
    },

    getInitialState () {
      return {}
    },

    onSubmit (...args) {
      const { context: { store: { dispatch } } } = this
      const { submitAction: submit }
        = bindActionCreators({ submitAction }, dispatch)
      submit(...args)
        .then(() => this.setState({ submitted: true }))
        .catch(error => this.setState({ error }))
    },

    render () {
      const {
        onSubmit,
        props,
        state: { submitted, error }
      } = this
      return (<Form {...props} onSubmit={onSubmit} submitted={submitted}
        error={error} />)
    }
  })
}

und

// redirect to path if predicate returns true
export default function redirect (path, predicate) {
  return Component =>
    class Composed extends React.Component {

      componentWillMount () {
        if (predicate(props))
          redirectTo(path)
      }

      componentWillReceiveProps (nextProps) {
        if (predicate(nextProps))
          redirectTo(path)
      }

      render () {
        return <Component {...this.props} />
      }
    }
}

//redirect to path if submitted
export default function redirectSubmitted (path) {
  return redirect(path, ({ submitted }) => submitted)
}

Jetzt haben wir auf der einen Seite decorator , die submitted und error Requisiten mit onSubmit Rückruf bereitstellen, und auf der anderen Seite decorator , die irgendwo umleiten, wenn erhält die Eigenschaft submitted === true . Lassen Sie uns sie an unser Formular anhängen und sehen, was wir bekommen.

@redirectSubmitted('/')
class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    this.props.onSubmit(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {this.props.error ? <p>{ this.props.error }</p>: null}
      </form>
    )
  }
}

export default submitForm(Form, subscribe)

Und hier ist es: reines Rendering-Formular ohne Abhängigkeiten von routing Bibliothek, flux Implementierung, Anwendung state , das tut, wofür es erstellt wurde: Senden und Weiterleiten.

Vielen Dank fürs Lesen und Entschuldigung, dass Sie sich die Zeit genommen haben.

Alle 66 Kommentare

Anstatt shouldRedirect darin zu haben, wie wäre es mit einem Session Reducer, der nur isLoggedIn verfolgt? Auf diese Weise können Sie von Ihrer Anmeldeseite zu jeder anderen Seite umleiten, indem Sie isLoggedIn anstelle von shouldRedirect .

Das Zurücksetzen des Zustands könnte auch durch die Verarbeitung der LOGGED_IN -Aktion in Ihrem Reduzierer erfolgen. Wenn diese Aktion eintritt, wissen Sie, dass Sie den Fehlerstatus sicher auf einen null zurücksetzen können.

Macht das Sinn? :D

Es ist sinnvoll, wenn es nur die Anmeldeseite gibt, aber es ist ein etwas allgemeinerer Ansatz. Zum Beispiel für eine Anmeldeseite oder eine Seite zum Zurücksetzen des Passworts. Auch wenn Sie eingeloggt sind, sollten Sie diese Seiten sehen können.

Und diese Flags sollten von all diesen Seiten verwendet werden, da sie alle den gleichen Fluss haben.

Hoffe es ist jetzt klarer :)

Ja, aus meiner Sicht ist "shouldRedirect" einfach zu allgemein, um mit einer bestimmten Aktion in Verbindung gebracht zu werden. Es könnte zu Fehlern führen, bei denen Sie irgendwie vergessen, dieses Flag zu setzen, und dann eine Seite auf eine andere Seite umleitet, auf die sie eigentlich nicht umleiten sollte.

Aber sicher, wenn Sie Ihren Zustand bereinigen möchten, können Sie das einfach mit einer Aktion tun. Oder Sie könnten eine Verlaufsänderung außerhalb der Reaktion abhören und dann die Zustandslöschaktion auslösen. Dies ist möglicherweise vorzuziehen, da Sie diese Logik nicht über Ihre Komponenten verteilen müssen, sondern an einem einzigen Ort aufbewahren können.

Aber das ist nur eine mögliche Lösung für die staatliche Klärung. Ich bin mir noch nicht sicher, wie ich die Umleitung auf eine nette Art und Weise lösen kann.

Die Frage ist auch: Macht es Sinn, diese Flaggen überhaupt im Staat zu haben? Wenn nicht, wie sollte dieser Fluss implementiert werden?

@gaearon Wenn du etwas Zeit hast, würde ich auch gerne dein Feedback hören. Danke! :+1:

In meiner Anwendung leite ich in Middleware um und verwende keine Flags. Aber ich benutze React-Router nicht.

// action

/**
 * Creates login action
 *
 * <strong i="6">@param</strong> {string} email
 * <strong i="7">@param</strong> {string} password
 *
 * <strong i="8">@returns</strong> {{types: *[], promise: *}}
 */
export function login(email, password) {
    return {
        types: [LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE],
        promise: post('/login').send({ email: email, password: password }).promise()
    };
}

// auth middleware 
/**
 * Intercepts LOGIN action and redirects to login screen
 * Otherwise just sends action to next middleware
 *
 * <strong i="9">@returns</strong> {Function}
 */
function authMiddleware({getState, dispatch}) {
    return (next) => (action) => {
        if (typeof action === 'object' && action.hasOwnProperty('type')) {
            if (action.type === LOGIN_SUCCESS) {
                next(action); // send it to next so identity will be set

                // get current route
                const state = getState();
                let path = '/dashboard';

                if (typeof state['router'] === 'object' && typeof state['router']['route'] === 'object' && null !== state['router']['route']) {
                    if (state.router.route.name === 'login' && typeof state.router.route.query['to'] === 'string') {
                        path = state.router.route.query.to;
                    }
                }

                return next(actions.transitionTo(path));
            }
        }

        return next(action);
    };
}

Es ist nicht umgestaltet, es ist nur für Testzwecke meiner Router-Lösung gedacht :).

Dies ist ein Kopieren und Einfügen aus meiner Anwendung und wie ich mit dem Laden von Benutzerdaten und dem Anmelden umgehe. Ich werde versuchen, den Code so gut wie möglich zu kommentieren, und ich werde bei Fragen auf Reatiflux sein.

const AppConnector = createConnector((props$, state$, dispatch$, context$) => {
  // true when the user is logged in - drawn from Store state
  const loggedIn$ = state$
    .map(s => s.apiKey !== null)
    .distinctUntilChanged()

  // this returns an observable of a path to redirect to
  // in my app, I have a url like `/login?nextPath=dashboard`, but if that
  // isn't set, just go home
  const redirectTo$ = routeState$
    .map(s => s.router.query.nextPath || '/home')

  const redirect$ = loggedIn$
    .withLatestFrom(
      context$,
      redirectTo$,
      (loggedIn, context, path) => () => context.router.transitionTo(loggedIn ? path : '/login') // when the loggedIn state changes, i.e. user has logged in or out, respond to that change
    )
    .do(go => go())

  // It's awesome to have this here, because it means that no matter 
  // where the user loads in to the app (all child views of this view), 
  // the app will realise it needs to load data, and loads the according data 
  const userData$ = state$
    .map(getUser)

  const userDataIsEmpty$ = userData$
    .map(user => user.id === null || typeof user.id === 'undefined')
    .filter(s => s === true)

  const loadUserData$ = Rx.Observable.combineLatest(loggedIn$, userDataIsEmpty$, (logged, userDataIsEmpty) => loggedIn && userDataIsEmpty)
    // only when user is logged in but has no user data
    .filter(s => s === true)
    // fetch data with an action creator
    .withLatestFrom(dispatch$, (userData, dispatch) => () => dispatch(UserActions.loadUserData()))
    .forEach(go => go())


  return Rx.Observable.combineLatest(props$, state$, context$, redirect$, (props, state, context) => ({...props, ...state, ...context}))
}, render)

In Bezug auf die Beantwortung Ihrer Fragen @emmenko denke ich, dass es nicht akzeptabel ist, shouldRedirect in Ihrem Bundesstaat zu haben, und sollte in einem funktionaleren Ansatz erfolgen. errorMessage im Anwendungsstatus zu haben, ist meiner Meinung nach in Ordnung, stellen Sie nur sicher, dass es sich in einem Anmeldestatus befindet, z. B. state.auth.errorMessage , und nicht in einem errorMessage der obersten Ebene

Ich denke, es ist nicht akzeptabel, shouldRedirect in Ihrem Zustand zu haben, und sollte in einem funktionaleren Ansatz durchgeführt werden

Ich stimme zu. Dinge, die nur einmal nützlich sind, sollten IMO nicht im Zustand sein.

Außerdem glaube ich nicht, dass Sie laut Slack isLoggedIn im Anwendungsstatus speichern sollten, da es sich eher um eine berechnete Eigenschaft handelt, die mit einem einfachen Selektor wie const isUserLoggedIn = state => state.user.id !== null berechnet werden kann

Siehe https://github.com/faassen/reselect für mehr Verwendung von Selektoren

@frederickfogerty danke für das Beispiel.

Frage aber: Dies funktioniert gut für die Anmeldung, da Sie normalerweise ein loggedIn -Flag im state haben. Aber was ist mit anderen Seiten wie Anmeldung, Passwort-Reset usw.?
Diese Seiten verhalten sich genauso wie die Anmeldung: umleiten, wenn ok, andernfalls Fehlermeldung anzeigen.
Wie gehen Sie also mit solchen Fällen um? Ich habe Mühe, den richtigen Fluss für diese Fälle zu finden, und ich bin mir nicht sicher, wo der Staat leben soll.

Dinge, die nur einmal nützlich sind, sollten IMO nicht im Zustand sein.

@gaearon Ich stimme zu, aber ich bin mir immer noch nicht sicher, was der richtige Ansatz dafür ist. Irgendwo muss man ja noch einen Staat haben...

So mache ich es atm, ohne etwas im Zustand zu speichern.

// actions
function login () {
  return { type: 'LOGGED_IN' }  
}

function submitLogin (form, dispatch) {
  return api.doLogin(form)
  .then(() => {
    dispatch(login()) // can dispatch something at this point
    return Promise.resolve()
  })
}

// in the component (login, signup, ...)
onSubmit () {
  actions.submitLogin(form, dispatch) // this returns a promise
  .then(() => this.setState({ shouldRedirect: true }))
  .catch(error => this.setState({ shouldRedirect: false, errorMessage: error }))
}

Ich glaube nicht, dass Sie isLoggedIn im Anwendungszustand speichern sollten, da es sich eher um eine berechnete Eigenschaft handelt

Ja, macht Sinn.

@gaearon Ich denke, die Frage für diesen allgemeinen Workflow (Umleitung oder Fehler anzeigen) ist, ob dies mit "Redux" oder auf andere Weise geschehen soll. Und was wäre diese Alternative?

verwandt und ich werde es hier pinnen ... eigentlich habe ich mich auch mit diesem Problem beschäftigt und versuche derzeit, das folgende Beispiel in Redux neu zu implementieren. Noch nichts zeigbar, da ich das in meiner Freizeit mache:
https://auth0.com/blog/2015/04/09/adding-authentication-to-your-react-flux-app/

Es ist reines Flussmittel:
https://github.com/auth0/react-flux-jwt-authentication-sample

Vielleicht ist es nützlich für Best Practices?!

@emmenko

Wie gehen Sie also mit solchen Fällen um?

Solange sich Ihre Verwendung anmeldet, wird isLoggedIn (das sich nicht im App-Status befindet, aber geringfügig ist) immer noch geändert, und dies wird sie umleiten. Es ist egal, woher diese Zustandsänderung kam :)

Der beste Ansatz, den ich gewählt habe, besteht darin, diese Logik einem Konnektor hinzuzufügen, bei dem die Funktionalität für seine Kinder benötigt wird, z. B. für die Autorisierung, sagen wir, Sie haben eine AppProtected -Komponente, bei der alles, was ein Kind dieser Komponente ist, eine Autorisierung benötigt , dann fügen Sie diese Logik in den Konnektor der AppProtected-Komponente ein.

So mache ich es atm, ohne etwas im Zustand zu speichern.

Ich mag diesen Ansatz definitiv nicht, der einen bidirektionalen Datenfluss zwischen der Benutzeroberfläche und den ACs hat, was dem Fluss widerspricht. Wenn Sie daran gebunden sind, es unbedingt zu tun, könnten Sie so etwas tun

componentWillReceiveProps(nextProps) {
  if (['/login', '/sign-up'].indexOf(this.props.router.path) !== -1 && this.props.isLoggedIn) {
    this.context.router.transitionTo(this.props.router.query.nextPath || '/home')
  }
}
  • Wenn die Anmeldung des Benutzers erfolgreich ist, wird er von der entsprechenden Seite umgeleitet.
  • Wenn die Anmeldung des Benutzers fehlschlägt, wird er nicht angemeldet und bleibt auf derselben Seite (es passiert nichts).

Dies funktioniert für alle von Ihnen genannten Seiten

Tut mir leid, aber ich sehe immer noch nicht, wie ich mit der Weiterleitung / dem Fehler-Rendering umgehe.

Gehen wir einen Schritt zurück und sagen, ich habe die Seite reset-password . Wir gehen auch davon aus, dass wir keinen Zustand berühren sollten, indem wir eine Art One-Shot-Flags speichern (wie zuvor erwähnt).
Was soll passieren, wenn ich zum Absenden des Formulars klicke?

Das möchte ich wissen, wie der Fluss sein sollte und wo er leben sollte.

PS: Danke für dein bisheriges Feedback!

[navigates to reset-password page] -> 
[types in email and hits submit] -> 
[onSubmit fires bound AC] ->
on successful reset this continues with:
[RESET_PASSWORD_SUCCESS] -> 
[resetPassword reducer returns state {passwordReset: true}] ->
[resetPasswordComponent.componentWillReceiveProps will check if state.passwordReset is true, and then will transition to its route]
on a failed reset this continues with:
[RESET_PASSWORD_FAILURE] -> 
[resetPassword reducer returns state {passwordResetError}] -> 
[resetPasswordComponent.componentWillReceiveProps() will check if state.passwordReset is true, and since is it not, nothing will happen - in render(), the error will be displayed]

Dies ist bei weitem nicht so elegant wie bei den Anmelde- und Anmeldeseiten, da es sehr gekoppelt ist

Ich muss schlafen, weitere Fragen beantworte ich morgen

Also lagern wir die Flaggen wieder im Staat ;)

PS: Gute Nacht!

Ich denke, die Frage ist ein bisschen tiefer als nur die Verallgemeinerung von „Formularen einreichen“ – es geht mehr darum, was der Status eigentlich ist und welche Daten in store erscheinen sollten und welche nicht.

Ich stelle mir den View Teil des Flussdatenflusses gerne als reine Funktion vor render(state): DOM , aber das ist nicht ganz richtig.
Nehmen wir als Beispiel eine einfache Formularkomponente, die zwei Eingabefelder darstellt:

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
      </form>
    )
  }
}

Während der Eingabe in diese Felder wurden einige interne Zustandsänderungen durchgeführt, sodass wir am Ende so etwas haben werden:

{
  name: 'John'
  surname: 'Snow'
}

Wir haben also einen Status, der nicht mit dem Anwendungsstatus verknüpft ist, und niemand kümmert sich darum. Wenn wir dieses Formular das nächste Mal erneut aufrufen, wird es gelöscht angezeigt und der „John Snow“ wird für immer verschwunden sein.
Sieht so aus, als hätten wir DOM modifiziert, ohne den Flussdatenfluss zu berühren.

Nehmen wir an, dass dies ein Abonnementformular für einen Benachrichtigungsbrief ist. Jetzt sollten wir mit der Außenwelt interagieren und wir werden es mit unserer Spezialaktion tun.

import { subscribe } from 'actions'

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    subscribe(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
      </form>
    )
  }
}

Wenn der Benutzer auf die Schaltfläche „Senden“ drückt, erwartet er eine Antwort von der Benutzeroberfläche, ob es erfolgreich war oder nicht, und was als nächstes zu tun ist. Wir möchten also den Anforderungsstatus verfolgen, um den Ladebildschirm zu rendern, umzuleiten oder Fehlermeldungen anzuzeigen.
Die erste Idee ist, einige interne Aktionen wie loading , submitted , failed , um den Anwendungsstatus dafür verantwortlich zu machen. Weil wir den Zustand darstellen wollen und uns keine Gedanken über Details machen.

  render () {
    //provided by connector of whatever
    const { props: { isLoading, error } } = this
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}
        disabled={isLoading}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {error ? <p>{ error }</p> : null}
      </form>
    )
  }

Aber hier erscheint der Teufel: Jetzt haben wir diesen Seitenzustand beibehalten und der nächste render(state) -Aufruf wird das Formular in einem schmutzigen Zustand anzeigen, also müssen wir eine cleanup -Aktion erstellen, um es jedes Mal aufzurufen, wenn wir das Formular einhängen.
Und für jedes Formular, das wir haben, fügen wir die gleichen Muster von loading , submitted , failed und cleanup Aktionen mit Läden/Reduzierern hinzu … bis wir aufhören und nachdenken über den Zustand, den wir eigentlich rendern wollen.

Gehört submitted zum Anwendungsstatus oder ist es nur ein Komponentenstatus, wie der name ? Interessiert sich der Benutzer wirklich dafür, dass dieser Zustand in einem Cache gespeichert wird, oder möchte er das saubere Formular sehen, wenn er es erneut besucht? Die Antwort: Es kommt darauf an.

Aber bei reinen Submit-Formularen könnten wir davon ausgehen, dass der Formularstatus nichts mit dem Anwendungsstatus zu tun hat: Wir wollen wissen, ob es sich bei der Anmeldeseite um Benutzer logged in handelt oder nicht, oder wir wollen nicht um etwas darüber zu erfahren, ob er das Passwort geändert hat oder fehlgeschlagen ist: Wenn die Anfrage erfolgreich ist, leiten Sie ihn einfach woanders um.

Wie wäre es also mit der Behauptung, wir brauchen diesen Zustand nirgendwo anders als bei Component? Lass es uns versuchen.
Um eine ausgefallenere Implementierung bereitzustellen, könnten wir annehmen, dass wir 2015 in der asynchronen Welt leben und überall Promises haben, also lassen Sie unsere Aktion ein Promise zurückgeben:

function subscribe (name, surname, email) {
  return api.request('/subscribtions', 'create', { name, surname, email })
}

Im nächsten Schritt könnten wir damit beginnen, die Aktion in unserem Component zu verfolgen:

onSubmit (event) {
  event.preventDefault()
  const { name, surname, email } = this.state
  subscribe(name, surname, email)
    .then(() => { this.setState({ submitted: true }) })
    .catch(error => { this.setState({ error }) })
}

componentWillUpdate (object nextProps, object nextState) {
  if(nextState.submitted)
    redirect('somewhere')
}

Sieht so aus, als hätten wir alles, was wir brauchen, um Komponenten zu rendern, ohne den Anwendungsstatus zu verschmutzen, und müssen sie nicht mehr reinigen.
Wir sind also fast da. Jetzt könnten wir damit beginnen, den Fluss Component zu verallgemeinern.
Lassen Sie uns zunächst auf Bedenken eingehen: Verfolgung von Aktionen und Reaktionen auf Statusaktualisierungen:
(Lassen Sie es uns in Form von redux tun, um in Repo-Grenzen zu bleiben)

import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'

// Keeps track of action
export default function connectSubmitForm (Form, submitAction) {
  return React.createClass({
    contextTypes: {
      //redux Store
      store: PropTypes.object.isRequired
    },

    getInitialState () {
      return {}
    },

    onSubmit (...args) {
      const { context: { store: { dispatch } } } = this
      const { submitAction: submit }
        = bindActionCreators({ submitAction }, dispatch)
      submit(...args)
        .then(() => this.setState({ submitted: true }))
        .catch(error => this.setState({ error }))
    },

    render () {
      const {
        onSubmit,
        props,
        state: { submitted, error }
      } = this
      return (<Form {...props} onSubmit={onSubmit} submitted={submitted}
        error={error} />)
    }
  })
}

und

// redirect to path if predicate returns true
export default function redirect (path, predicate) {
  return Component =>
    class Composed extends React.Component {

      componentWillMount () {
        if (predicate(props))
          redirectTo(path)
      }

      componentWillReceiveProps (nextProps) {
        if (predicate(nextProps))
          redirectTo(path)
      }

      render () {
        return <Component {...this.props} />
      }
    }
}

//redirect to path if submitted
export default function redirectSubmitted (path) {
  return redirect(path, ({ submitted }) => submitted)
}

Jetzt haben wir auf der einen Seite decorator , die submitted und error Requisiten mit onSubmit Rückruf bereitstellen, und auf der anderen Seite decorator , die irgendwo umleiten, wenn erhält die Eigenschaft submitted === true . Lassen Sie uns sie an unser Formular anhängen und sehen, was wir bekommen.

@redirectSubmitted('/')
class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    this.props.onSubmit(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {this.props.error ? <p>{ this.props.error }</p>: null}
      </form>
    )
  }
}

export default submitForm(Form, subscribe)

Und hier ist es: reines Rendering-Formular ohne Abhängigkeiten von routing Bibliothek, flux Implementierung, Anwendung state , das tut, wofür es erstellt wurde: Senden und Weiterleiten.

Vielen Dank fürs Lesen und Entschuldigung, dass Sie sich die Zeit genommen haben.

Fair genug! Um ehrlich zu sein, bin ich immer noch daran interessiert, den Status lokaler Komponenten in Redux zu speichern: #159.

@stremlenye Das ist ziemlich raffiniert. Ich danke Ihnen für das Teilen!

@iclanzan danke fürs lesen :)
Eigentlich denke ich jetzt über den Vorschlag von @gaearon nach, den er im vorherigen Kommentar erwähnt hat. Werfen Sie einen Blick auf die Diskussion Nr. 159, um einige wichtige Ideen zu erhalten.

Abschließend – wenn RR 1.0 herauskommt, werden wir dies im Rezept „Verwendung mit React Router“ behandeln.
Dies kann in https://github.com/rackt/redux/issues/637 nachverfolgt werden.

@stremlenye dein Ansatz ist ziemlich genial! Wäre es sinnvoll, anstelle eines Decorators eine React-Router-Komponente höherer Ordnung zu verwenden, in der alle _unathorisierten_ verschachtelt sind und die für die Umleitung verantwortlich wäre, sobald der Authentifizierungsspeicher einen Benutzer enthält?

<ReduxRouter>
    <Route component={ Auth }>
      <Route path="/" component={ Login } />
      <Route path="/reset-password" component={ ResetPassword } />
    </Route>
  </ReduxRouter>

So etwas in der Art, wo Auth sich nur darum kümmert zu erkennen, ob der Benutzer eingeloggt ist. Auf diese Weise kann ich immer noch meinen Shop manipulieren, indem ich Aktionen von der Komponente Login abschicke. Nicht so rein, aber weniger Code und leichter zu verstehen :)

@tomazzaman Danke :)
Die meisten React-Anwendungen, die ich geschrieben habe, enthielten so etwas wie ApplicationContainer , das für den gleichen Zweck ohne neue Route -Implementierung verwendet werden könnte.
Wenn Sie RR-v1.0.0-rc1 verwenden, können Sie die gleichen Ziele mit onEnter Hook erreichen: Überprüfen Sie einfach, ob der aktuelle Status mit authentifiziert übereinstimmt oder nicht.

Ich habe auch darüber nachgedacht, und es scheint, dass @stremlenye großartige Arbeit geleistet hat, um zu zeigen, wie es gemacht werden kann. Das Problem ist, dass man dazu neigt, in der Schleife "alles muss durch die Aktion - Reduzierer - Update-Komponente-mit-Store-State-Schleife" stecken zu bleiben (ich auch) und dass wir dann dazu neigen, den Laden mit allerlei zu verschmutzen Status, der nur in sehr spezifischen Szenarien in der App verwendet wird (auf einem Bildschirm, Sachen wie „habe ich das Zurücksetzen des Passworts erfolgreich ausgeführt“), während dies einfach gehandhabt werden kann, indem man einfach die asynchronen Aufrufe innerhalb der Komponente durchführt und die Komponente selbst aktualisiert Verwenden this.setState , anstatt den globalen Zustand zu verschmutzen + alle diese Zustandsvariablen zurücksetzen zu müssen, falls wir diese Komponente später ein zweites Mal öffnen würden. Ganz zu schweigen von der Verunreinigung der Zustandsreduzierer mit dem gesamten Anfangs-/Erfolgs-/Fehlerbehandlungscode.

Ich werde versuchen, meinen Code auch hier anzupassen, um zu sehen, ob er die Dinge bereinigt.

@ir-fuel Wenn Sie Zeit haben, könnten Sie ein kleines Beispiel dafür geben, was Sie auf den GitHub bekommen werden?

Nun, es wird schwierig sein, das zu tun, aber ich kann ein kleines Beispiel dafür geben, was ich getan habe. Ich habe eine Komponente, bei der ein Benutzer darum bitten kann, sein Passwort zurückzusetzen. Der eigentliche Reset-Vorgang läuft nun so ab:

  submitForgotPassword = (username) => {
    this.setState({isLoading:true})
    doForgotPassword(username).then((result) => {
      this.setState({passwordIsReset:true,isLoading:false})
    }).catch((result) => {
      this.setState({passwordIsReset:false,isLoading:false,errorMessage:result.error})
    })
  }

doForgotPassword ist eine Funktion, die einen Ajax-Aufruf an meinen Server ausführt.

Also keine Roundtrips zu den Speichern/Reduzierern, kein Verschmutzen des globalen Status mit Dingen, die Sie nur innerhalb einer Komponente interessieren und die nur dazu verwendet werden, den Status in dieser Komponente vorübergehend anzuzeigen, und keine Notwendigkeit, eine Reihe von Statusvariablen in Ihren Reduzierern zurückzusetzen.

und render sieht in etwa so aus

  render() {

    let divClassName = ''

    if(this.state.isLoading) {
      divClassName = 'disable-controls'
    }

    let child = null

    if(this.state.passwordIsReset)
      child = (<ForgotPasswordSuccess/>)
    else
      child = (<ForgotPasswordForm submit={this.submitForgotPassword} errorMessage={this.state.errorMessage}/>)

    return (
      <div className={divClassName}>
        {child}
      </div>
    )
  }

Ich hoffe, das ist klar.

Sieht gut aus :+1:
Der einzige Trick, der behandelt werden sollte (aus meiner Sicht), besteht darin, die Middleware so anzupassen, dass sie das Versprechen aus der aufgehobenen Aktion zurückgibt (wahrscheinlich nur den Rückgabewert der Aktion). Auf diese Weise erlauben wir Aktionen, den Zustand in einem normalen Fluss zu ändern, wenn dies erforderlich ist, und behalten die Möglichkeit, sich mit dem Promise -Zeug im Komponentenbereich zu verbinden.

Ich denke, es ist in Ordnung, Ihren App-Status als bereits angemeldeten Status zu betrachten. Wenn kein Benutzer angemeldet ist, warum benötigen Sie einen App-Status? Sicher, Sie könnten einen in der Login-Komponente haben.

Die App-Logik auf der Anmeldeseite sollte nicht so komplex sein, um den Top-Status einer App zu besitzen, oder?

Der @SkateFreak- Login-Flow ist Teil einer App. Hast du nicht an einen Anmeldeflussstatus (Fortschritt, Fehler), Anmeldeflussstatus usw. gedacht, für den du besser auch React verwenden solltest (wahrscheinlich mit Redux, wenn du es bereits für andere App-Komponenten verwendest).

@sompylasar Ich bin für einen App-Status, ich denke nur, dass ich ihm eine einfachere App ohne Redux-Store anbieten kann, solange sich der Benutzer AUSSERHALB meiner App befindet. Mein Redux-Shop startet, sobald Sie meine App BETRETEN.

Dieser Ansatz hat bis zu diesem Moment für mich funktioniert, aber vielleicht verstehe ich die Notwendigkeit eines App-Zustands in der Anmeldephase in meinen zukünftigen Projekten

Sie können Ihren Shop auf der Stammebene Ihres Shops in den „App“-Teil und den „Benutzer/Login“-Teil aufteilen, jeweils mit einem eigenen Reducer. In diesem Fall werden Sie also niemals Ihre App-Reduzierer mit Login-Reduzierungscode verschmutzen und umgekehrt.

Von: SkateFreak < [email protected] [email protected] >
Antwort an: rackt/redux < [email protected] [email protected] >
Datum: Sonntag, 13. Dezember 2015, 13:48 Uhr
An: rackt/redux < [email protected] [email protected] >
Cc: Joris Mans < [email protected] [email protected] >
Betreff: Betreff: Re: [Redux] Best Practice zur Handhabung des Datenflusses für Anmelde-/Registrierungsseiten mit Weiterleitung (#297)

@sompyl asarhttps://github.com/sompylasar Ich bin für einen App-Status, ich denke nur, dass ich ihm eine einfachere App ohne Redux-Store anbieten kann, solange die Verwendung AUSSERHALB meiner App ist. Mein Redux-Shop startet, sobald Sie meine App BETRETEN.

Dieser Ansatz hat bis zu diesem Moment für mich funktioniert, aber vielleicht verstehe ich die Notwendigkeit eines App-Zustands in der Anmeldephase in meinen zukünftigen Projekten

Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287194 an.

Korrigieren Sie mich, wenn ich falsch liege, aber ist es nicht der Zweck eines Flux-Speichers, Daten und App-Status zwischen Routen und Komponenten auszutauschen? Warum muss ich meinen Status "reduzieren" und meine "Aktionen" verwenden, um einen Top-Status aufrechtzuerhalten, wenn alles keine Rolle spielt, wenn sich der Benutzer anmeldet?

Ich sehe die Vorteile nicht, obwohl es eine sehr organisierte und saubere Art ist, Dinge zu tun. Ich verwalte meinen Status vor der Anmeldung in meiner <Sign /> -Komponente, die SignIn, SignUp und ForgotPassword enthält.

Weil eine Anmeldung ein komplizierterer Prozess sein kann, als nur nach einem Benutzernamen und einem Passwort zu fragen.
Es hängt alles von der Komplexität Ihres Flows ab.

Von: SkateFreak < [email protected] [email protected] >
Antwort an: rackt/redux < [email protected] [email protected] >
Datum: Sonntag, 13. Dezember 2015, 14:00 Uhr
An: rackt/redux < [email protected] [email protected] >
Cc: Joris Mans < [email protected] [email protected] >
Betreff: Betreff: Re: [Redux] Best Practice zur Handhabung des Datenflusses für Anmelde-/Registrierungsseiten mit Weiterleitung (#297)

Korrigieren Sie mich, wenn ich falsch liege, aber ist es nicht der Zweck eines Flux-Speichers, Daten und App-Status zwischen Routen und Komponenten auszutauschen? Warum muss ich meinen Status "reduzieren" und meine "Aktionen" verwenden, um einen Top-Status aufrechtzuerhalten, wenn alles keine Rolle spielt, wenn sich der Benutzer anmeldet?

Ich sehe die Vorteile nicht, obwohl es eine sehr organisierte und saubere Art ist, Dinge zu tun. Ich verwalte meinen Status vor der Anmeldung in meiner Komponente, die SignIn, SignUp und ForgotPassword enthält.

Antworten Sie direkt auf diese E-Mail oder sehen Sie sie auf GitHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287893 an.

Ich stimme zu.
Aber ich sehe SignUp als ein Formular mit einem Status - was ausreichen sollte, egal wie komplex ich bin ...

Andererseits, was weiß ich über Anmeldungen ...
Leute sind verrückt

Derzeit habe ich ein ähnliches, aber anderes Problem.

Ich arbeite an einem Ablauf wie einer superkomplexen Anmeldung, und wir haben einige Fortsetzungsseiten, auf denen der Benutzer die erforderlichen Informationen eingeben kann. Jede Seite ist ein Formular. Der Ablauf sieht so aus:

Formularseite_A -> Formularseite_B -> Formularseite_C -> Erfolgsseite

Das Problem ist, dass wir vor FormPage_C keine API-Anfrage stellen. Wir speichern einfach Daten im Browser auf FormPage_A/B und senden sie alle an die API in FormPage_C.

Die Frage ist also, sollten wir die Formulardaten von FormPage_A und FormPage_B in den Anwendungsstatus versetzen?

Wenn ja, speichern wir anscheinend einige temporäre Daten, die keine Auswirkungen auf die Benutzeroberfläche haben, im Anwendungsstatus. Und da der Anwendungszustand von Redux im Speicher lebt, was passiert, wenn der Benutzer die Seite in FormPage_B aktualisiert?

Wenn nein, wo ist der richtige Ort dafür? Sollten wir eine weitere Ebene wie fake api aus dem Redux-Flow erstellen, um sie zu retten?

@frederickfogerty @gaearon @ir-fuel @stremlenye

Ich würde einfach die Eigenschaften in this.state übergeben, die für Ihre Einreichung relevant sind, als Requisiten an den nächsten Schritt.
FormPage_A.state wird also in FormPage_B.props usw. übergeben und in FormPage_C

oder wenn Sie eine übergeordnete Komponente haben, die dies verwaltet, speichern Sie den Status in der state -Eigenschaft dieses übergeordneten Steuerelements und senden Sie nach dem letzten Schritt alle

Stellen Sie einfach sicher, dass Sie wieder von vorne beginnen, wenn der Benutzer beschließt, die Seite neu zu laden.

@kpaxqin Ich denke, in diesem Fall könnten Sie eine Verwaltungskomponente wie Flow deklarieren, die die Teilmenge der Schritte mit ihren Zuständen und der generischen back-forward -Logik für jeden behandelt.
So können Sie Ihre Schritte so zustandslos wie möglich machen und den Status des gesamten Prozesses in der umschließenden Komponente verfolgen.
Etwas wie:

<Flow onForward={…} onBackward={}>
  <Step1 />
  <Step2 />
  <Step3 />
  …
  <Finish onSubmit={this.props.submit(this.state.model) />
</Flow>

@ir-fuel @stremlenye

Vielen Dank für Ihre Vorschläge.
Es scheint, dass wir alle diese temporären Dinge nicht in den Anwendungsstatus versetzen wollen.

Aber wenn der Benutzer die Seite in der Hälfte des Flusses aktualisiert

Stellen Sie einfach sicher, dass Sie wieder von vorne beginnen, wenn der Benutzer beschließt, die Seite neu zu laden.

Es scheint keine gute Benutzererfahrung zu sein, wir sind eine mobile Website (das ist einer der Gründe, warum wir riesige Formulare in kleine Seiten aufteilen), es ist schmerzhaft, Dinge auf dem Handy einzugeben, und ich glaube, die Benutzer werden verrückt, wenn sie alle verlieren Dinge nach dem Drücken der Schaltfläche "Aktualisieren", vielleicht geben sie es einfach auf.

Ich denke, vielleicht ist es eine akzeptable Wahl, fake api aus dem Redux-Fluss zu bauen, Dinge bei jedem Schritt an fake api zu senden und sie bei Bedarf abzurufen, so wie wir eine API dafür haben und da wir sie verwenden Es ist wie API, das einzige, was damit verbunden ist, sind Aktionsersteller, Seiten und Komponenten werden sauber sein und es ist auch einfach, Fehler zu behandeln (verwenden Sie den gemeinsamen Code für die Behandlung von API-Fehlern).

Es macht für mich Sinn, denn sobald wir diese Daten persistent machen möchten, sollten sie nicht im Komponenten- oder Anwendungsstatus sein, der Nebeneffekt sollte im Aktionsersteller behandelt werden

//fake api
const fakeApi = {
  getStepA: ()=>{
    /* get things from LS */
    return Promise
  },
  setStepA: ()=>{
    /* put things into LS */
    return Promise
  }
}

// action creator
submitA(dispatch, getState) { // thunk function
  fakeApi
    .setStepA()
    .then(dispatchSuccess)
    .then(transitionToStepB)
    .catch(dispatchError);
}

Es macht die Dinge für mich klar, aber es sieht seltsam aus, etwas von unserem Redux-Fluss in unsere App hinzuzufügen.

Es wäre mir wirklich egal, ob der Benutzer Ihre Seite neu lädt.
Auf der anderen Seite teilen Sie uns mit, dass Sie einen komplizierten Anmeldeprozess mit mehreren Formularen haben, und dann sagen Sie, dass es sich um eine mobile Website handelt. Glaubst du nicht, dass es die Benutzer auch in den Wahnsinn treiben und sie aufgeben lässt, wenn sie zu viele Dinge auf einem mobilen Gerät ausfüllen müssen? Ich denke, es ist noch schlimmer, als das Neuladen nicht zu unterstützen. Ich glaube sowieso nicht, dass viele Websites das Neuladen von Seiten während eines Anmeldevorgangs unterstützen.

Ich denke, es macht die Dinge nur viel komplizierter für etwas, das wirklich nicht oft passiert. Ich würde mich mehr darauf konzentrieren, den Anmeldeprozess zu vereinfachen oder ihn in mehrere Schritte über Ihre Website aufzuteilen, damit der Benutzer nicht den Eindruck hat, dass er X-Seiten mit Formularen ausfüllt, bevor er endlich sein Konto aktiviert.

Und wenn Sie das Neuladen während der Anmeldung wirklich unterstützen möchten, lassen Sie einfach die übergeordnete Komponente, die all diese Schritte steuert, ihren Status in localStorage speichern.

@kpaxqin Ich würde den "Fake-API" -Ansatz wählen. Warum gefälschte BTW, es ist nur eine Zwischenspeicher-API, die Sie für Ihren mehrstufigen Prozess benötigen. Nicht jede API sollte sich unbedingt auf einem Server befinden und über HTTP oder ein anderes Netzwerkprotokoll aufgerufen werden.

Ich stimme @ir-fuel darin zu, dass der gesamte mehrstufige Prozesszustand gespeichert werden sollte, nicht nur der aktuelle Schritt.

@ir-fuel Reload könnte ohne explizite Benutzerabsicht erfolgen, beispielsweise kann ein Benutzer zu einer anderen mobilen App wie einem Notebook oder einem Telefonanruf wechseln, und der Browser könnte seinen Status aufgrund von unzureichendem Speicher auf dem Gerät verlieren, so it wird neu geladen, wenn der Benutzer zurückkommt.

Das Problem besteht erneut darin, dass Sie den globalen Anwendungsstatus mit etwas verschmutzen, das Sie nur in einigen wenigen Bildschirmen in Ihrer Webanwendung benötigen.

@sompylasar @kpaxqin Ich denke, @ir-fuel hat den richtigen Ansatz vorgeschlagen – wenn Sie etwas Zwischenprodukt benötigen, das schließlich beibehalten werden soll, persistieren Sie es auf der Client-Seite: viel einfacher zu implementieren, bezieht den Server nicht wirklich in das Persistieren inkonsistenter Daten ein, nicht wahr? keine HTTP-Anfragen ausführen (was auch ein großes Problem auf mobilen Geräten ist) und extrem einfach, den Ansatz in Zukunft zu ändern.

Und über das Persistieren von Daten irgendwo – denken Sie daran, wie Sie sie bereinigen, nachdem der Prozess fehlgeschlagen oder bereits abgeschlossen ist – das war das ursprüngliche Thema eines Themas.
Eine weitere Option, die Sie verwenden könnten – Benutzerdaten in der URL beibehalten. Wenn Ihre Formulare aus einigen Textfeldern bestehen, könnte dies eine gute Kompromisslösung sein.

Es ist nicht so sehr "Client- oder Serverseitig speichern", sondern eher eine Frage von "Handhabe ich dieses 'Ad-hoc' in meiner übergeordneten Komponentenklasse" vs. "Mache ich die gesamte Aktion / den Reduzierer / den gesamten Zustandstanz".
Ich mag es nicht, die Aktion/Reduzierer durchzugehen, wenn es sich um einen vorübergehenden Zustand handelt. Ich mache das nur für den echten Anwendungsstatus.

@ir-fuel :+1:

@stremlenye Bitte lies meine Antwort noch einmal. Ich sage ausdrücklich, dass Sie keine serverseitige API erstellen müssen, um eine Persistenz-API-Schicht zu erstellen.

@ir-fuel Das Nichtdurchlaufen von Aktionen/Reduzierer hebt die Vorhersagbarkeit und Wiederspielbarkeit der Zustandsübergänge auf.

@ir-fuel Sie können Ihren App-Status nach dem Absenden des Formulars einfach löschen, damit er nicht "verschmutzt" wird.

RE: löschen. Machen Sie eine Aktion CLEAR und haben Sie eine Schaltfläche in Ihrer Benutzeroberfläche, um den Registrierungsablauf zurückzusetzen. Sie können es auch über einen Router machen (senden Sie diese Aktion, wenn Sie die anfängliche Anmelde-URL besuchen, und wenn die Anmeldung begonnen hat, ändern Sie die URL in eine andere).

@sompylasar Es ist kein Problem, Ihren Status mit einer expliziten Schaltfläche zurückzusetzen – Probleme treten nach nicht behandelten Fehlern auf: Es ist schwer vorherzusagen, wie der Status beschädigt werden könnte, und eine Wiederherstellungsstrategie für jeden einzelnen Fall zu definieren.

@stremlenye Ja, Softwareentwicklung ist manchmal schwierig. Vorhersagbare Zustände und Übergänge helfen, dies zu mildern.

Nicht nur das, aber wenn Ihr Zustand mehr als ein Haufen JSON-Objekte ist (was ich hasse, weil alles Konventionen unterliegt und nichts erzwungen wird) und Sie etwas in der Zeile von Record von Immutable.js verwenden, dann Sie Sie müssen die Eigenschaften Ihrer Objekte im Voraus deklarieren, und dazu gehören alle temporären Dinge, die Sie jemals dort haben möchten.

Für mich ist es nur ein Unfall, etwas mit Redux zu bauen, ohne Immutable.js zu verwenden.

Ich weiß, dass Sie einen Signup -Eintrag erstellen und ihn nur bei der Anmeldung nicht-null haben könnten, aber das Problem ist grundlegender, weil Sie viele dieser Fälle in Ihrer Webanwendung haben könnten, und das ist es, was stört mich.

@ir-Kraftstoff

Nicht nur das, aber wenn Ihr Zustand mehr als ein Haufen JSON-Objekte ist (was ich hasse, weil alles per Konvention erfolgt und nichts erzwungen wird) und Sie etwas in der Zeile von Immutable.js' Record verwenden, müssen Sie die Eigenschaften deklarieren Ihrer Objekte im Voraus und dazu gehören alle temporären Dinge, die Sie jemals dort haben wollen.

Für mich ist es nur ein Unfall, etwas mit Redux zu bauen, ohne Immutable.js zu verwenden.

Im Moment bin ich nicht davon überzeugt, Immutable.js mit Redux zu verwenden, es schien zu komplex zu integrieren (und nicht leistungsfähig). Ein Tiefkühlen während der Entwicklung und des Testens sowie eine Reihe von Tests sollten ausreichen, um die meisten Fälle abzudecken. Und eine strenge Konvention, um zu vermeiden, dass der Zustand in Reduzierern oder anderen Orten, an denen Sie die Referenz haben, mutiert.

Ich weiß, dass Sie einen Registrierungsdatensatz erstellen könnten, der bei der Registrierung nur nicht null ist

Ja, das hatte ich im Sinn.

aber das Problem ist grundlegender, weil Sie viele dieser Fälle in Ihrer Webapp haben könnten, und das ist es, was mich stört.

Das nennen wir den expliziten Anwendungsstatus – wir kennen zu jedem Zeitpunkt den Status der App, die aus vielen Komponenten besteht. Wenn Sie die Statusstruktur weniger explizit und dynamischer gestalten möchten, folgen Sie https://github.com/tonyhb/redux-ui

Das ist das ganze Problem bei der JS-Entwicklung. "Halten wir uns an Konventionen". Und deshalb beginnen die Leute zu erkennen, dass es in komplexen Umgebungen einfach nicht funktioniert, weil die Leute Fehler machen und mit Lösungen wie TypeScript und Immutable.js kommen.

Warum sollte die Integration zu kompliziert sein? Wenn Sie 'Record' verwenden, verwenden Sie alle diese Objekte (solange Sie sie nur lesen) als normale JS-Objekte mit Punktnotation, um auf die Eigenschaften zuzugreifen, mit dem Vorteil, dass Sie im Voraus angeben, welche Eigenschaften Teil Ihres Objekts sind .

Ich mache meine gesamte Redux-Entwicklung mit Immutable.js, ohne jegliche Probleme. Die Tatsache, dass ein Objekt einfach nicht versehentlich geändert werden kann, ist ein großer Vorteil von Redux, und die „Typensicherheit“ beim Definieren Ihrer Objektprototypen ist ebenfalls praktisch.

Und wenn Sie wirklich das Neuladen während der Anmeldung unterstützen möchten, lassen Sie einfach die übergeordnete Komponente, die all diese Schritte steuert, ihren Status in localStorage speichern.

@ir-Kraftstoff
Für mich sieht das Einfügen von Dingen in localStorage wie ein Nebeneffekt aus, und es scheint keine gute Praxis zu sein, dies in Komponenten zu tun.

Das Problem besteht erneut darin, dass Sie den globalen Anwendungsstatus mit etwas verschmutzen, das Sie nur in einigen wenigen Bildschirmen in Ihrer Webanwendung benötigen.

Vielleicht habe ich die gefälschte API nicht klar genug beschrieben, der wichtigste Grund, sie zu bauen, ist, dass ich diese Dinge only need in a few screens nicht in den Anwendungszustand von Redux versetzen möchte. In FormPage_A/B rufen sie einfach die API auf, um Formulardaten zu speichern, und gehen dann zum nächsten Schritt:

//action
submitFormA (formA) {
  fakeApi
    .saveFormA(formA)
    .then(()=>{ transitionTo(nextPage) })
    .catch()
}

und in FormPage_C (das endgültige Formular):

submitFormC (formC) {
  fakeApi
    .getFormAnB()
    .then(mergeFormData(formC)) //mergeFormData is a curry function
    .then(realApi.submitSignUp)
    .catch()
}

Alle diese temporären Daten befinden sich in einer gefälschten API, und der globale Anwendungsstatus wurde nicht verschmutzt. Wenn wir Daten in einer gefälschten API löschen möchten, um den Fluss neu zu starten, senden Sie einfach eine Aktion, um dies zu tun.

@sompylasar Es ist kein Problem, Ihren Status mit einer expliziten Schaltfläche zurückzusetzen – Probleme treten nach nicht behandelten Fehlern auf: Es ist schwer vorherzusagen, wie der Status beschädigt werden könnte, und eine Wiederherstellungsstrategie für jeden einzelnen Fall zu definieren.

@stremlenye Dem stimme ich zu, aber ich denke, diese besonderen Fehlerfälle sind eher geschäftliche Dinge, sie sind immer hier, egal wo wir den Zustand platzieren, und sie sollten richtig gehandhabt werden, aber einfach nicht klar genug beschrieben werden der Moment. Das einzige, was Entwickler tun können, ist, es einfach zu verfolgen, damit wir es mit weniger Aufwand beheben können.

Und wenn Sie den Zustand in die übergeordnete Komponente einfügen, wird es möglicherweise schwieriger, darüber nachzudenken, wann die Dinge komplex werden. Mit gefälschten APIs mutieren wir diesen Zustand mit Aktionen und es scheint vorhersehbarer zu sein.

Aber ich stimme zu, dass die gefälschte API in den meisten Fällen mit weniger Komplexität zu schwer aussieht.

Habe nicht alle Nachrichten oben durchgesehen. Hier gebe ich nur meine Herangehensweise an: Erkenne Prop-Änderungen in componentWillReceiveProps.

Angenommen, die Zustandsform ist: { loginPending, loginError }, wenn Sie mit der Anmeldung beginnen, setzen Sie loginPending = true. Wenn erfolgreich oder fehlgeschlagen, setze { loginPending: false, loginError: 'some error.' }.

Dann kann ich erkennen, ob ein Anmeldeerfolgsereignis in einer Komponente vorliegt, und die Seite umleiten:

componentWillReceiveProps(nextProps) {
  if (this.props.loginPending && !nextProps.loginPending && !nextProps.loginError) {
    // Login success, redirect the page here.
  }
}

Es funktioniert einfach, wenn auch nicht schön.

Meine bescheidene Implementierung zum Löschen des Formulars bei der Umleitung mit react-router-redux . Reduzierstück:

const initialState = {
    login: initialLoginState,
    signup: initialSignupState,
    // ...
};


export default function(state = initialState, action) {
  switch (action.type) {
    case '@@router/LOCATION_CHANGE':
      return {
        ...state,
        login: initialLoginState,
        signup: initialSignupState
      };
      // ...

Finden Sie diesen Ansatz in Ordnung? Es klappt.

Ich bin damit fertig. Sagen Sie, was Sie denken:

Redux-Aktion registrieren:

import { checkStatus } from 'helpers/fetch_helpers';
import { AUTH } from 'config/constants.endpoints';
import { USER_LOGGED_IN, USER_LOGGED_OUT, USER_REGISTERED } from 'config/constants.actions';
import { replace } from 'react-router-redux';

// other actions...

const userRegistered = (user) => ({
  type: USER_REGISTERED,
  user
});

export const register = (formData, redirectTo = '/') => {
  const fetchParams = {
    method: 'post',
    body: formData
  };

  const handleData = (dispatch) => (response) => response.json().then( (data) => {
    dispatch(userRegistered(data));
    dispatch(replace(redirectTo));
  });

  return (dispatch) => fetch(AUTH, fetchParams)
    .then(checkStatus)
    .then(handleData(dispatch));
};

Anmeldeformular Container:

import React, { Component } from 'react';
import RegisterForm from './register_form';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as userActions from 'store/actions/user';
import { parseFormErrors } from 'helpers/fetch_helpers';

class RegisterFormContainer extends Component {
  state = {
    errors: {}
  }

  async handleSubmit(e) {
    e.preventDefault();

    const form = e.currentTarget;
    const formData = new FormData(form);
    const { register } = this.props.actions;

    try {
      await register(formData);
    } catch (error) {
      const errors = await parseFormErrors(error);

      this.setState({ errors });
    }
  }

  render() {
    return (
      <RegisterForm handleSubmit={ ::this.handleSubmit } errors={ this.state.errors } />
    );
  }
}

const mapDispatchToProps = (dispatch, ownProps) => ({
  actions: bindActionCreators({ register: userActions.register }, dispatch)
});

export default connect(
  null,
  mapDispatchToProps
)(RegisterFormContainer);

und Helfer:

export const checkStatus = (response) => {
  if (response.ok) {
    return response;
  } else {
    const error = new Error(response.statusText);

    error.response = response;
    throw error;
  }
};

export const parseFormErrors = (error) => error.response.json().then( (data) => {
  if (data.errors) {
    const joinedErrors = _.mapValues(data.errors, (errors) => errors.join(' ') );

    return { ...joinedErrors };
  } else {
    console.error('request failed:', error);

    return {};
  }
});

Was wichtig ist - nur eine erfolgreiche Registrierung geht als Aktion in den Redux-Speicher, sonst geht der Fehler in den Container-Komponentenzustand über. Der Benutzer erhält einen Fehler, navigiert dann zu einer anderen Seite und dann erneut zur Formularseite – das Formular ist leer und es liegen keine Fehler vor. Es gibt keine „USER_REGISTRATION_REQUEST“- oder „USER_REGISTRATION_FAILURE“-Aktionen, da sie den Komponentenstatus und nicht den Anwendungsstatus beschreiben.

@sunstorymvp

Sie haben Ihren Code mit async / await gesüßt, und dieser Zucker verbirgt die Tatsache, dass es drei Zustände der Anfrage gibt (vorher, in Bearbeitung, nachher). Diese Zustände werden in der Ansichtskomponente ausgeblendet. Der Anforderungsstart wird ignoriert, und der Anforderungsfehler wird in der Ansicht behandelt und nicht über Redux gesendet, sodass der App-Status hier aus dem Geschäft ist. Wenn aus irgendeinem Grund die Komponente, die diesen Zustand enthält, unmountet und dann wieder gemountet wird, geht der Zustand der Registrierungsanforderung verloren und das Ergebnis wird nicht verarbeitet, was zu Folgefehlern führen kann (z. B. Benutzer hat sich bereits registriert).

Der Benutzer erhält einen Fehler, navigiert dann zu einer anderen Seite und dann erneut zur Formularseite – das Formular ist leer und es liegen keine Fehler vor.

Um dies zu erreichen, können Sie eine Aktion wie USER_REGISTRATION_RESET beim Einhängen der Komponente ausführen, die den Fehler im Speicher zurücksetzen würde. Diese Aktion sollte nur verarbeitet werden, wenn keine ausstehende Registrierungsanforderung vorliegt, da das Ergebnis der ausstehenden Anforderung andernfalls von der App nicht verarbeitet wird.

Danke für die Rückmeldung.

Sie haben Ihren Code mit async / await gesüßt, und dieser Zucker verbirgt die Tatsache, dass es drei Zustände der Anfrage gibt (vorher, in Bearbeitung, nachher).

Diese Bedingungen bestehen unabhängig davon, ob ich async/await verwende oder nicht.

Diese Zustände werden in der Ansichtskomponente ausgeblendet

Wie beabsichtigt, ist das der Komponentenstatus. Warum sollte dies im App-Status sein?

Der Anforderungsstart wird ignoriert

in Bearbeitung...

und der Anforderungsfehler wird in der Ansicht behandelt und nicht über Redux versendet

wie beabsichtigt

Wenn aus irgendeinem Grund die Komponente, die diesen Zustand enthält, ausgehängt und dann wieder eingehängt wird, geht der Zustand der Registrierungsanforderung verloren und das Ergebnis wird nicht behandelt

das ist nicht wahr. Der Benutzer wird registriert und weitergeleitet, wenn die Anfrage erfolgreich ist, unabhängig vom Status der Komponente oder sogar der Existenz. sehen Sie sich register Aktionsersteller (handleData) an.

Um dies zu erreichen, können Sie eine Aktion ausführen ...

Das ist der Punkt. Noch eine Aktion? Warum so viel Boilerplate? Senden-Schaltfläche deaktivieren oder aktivieren, Formularfehler oder Live-Validierung anzeigen, Spinner anzeigen, während die Anfrage offen ist.. und diese Aktionen führen zu neuen Aktionen, die den Standardzustand wiederherstellen. Warum muss dies im App-Zustand sein?

das ist nicht wahr. Der Benutzer wird registriert und weitergeleitet, wenn die Anfrage erfolgreich ist, unabhängig vom Status der Komponente oder sogar der Existenz. Schauen Sie sich den Ersteller der Registrierungsaktion an (handleData).

Ja, aber dies lässt die Möglichkeit von zwei aufeinanderfolgenden register -Anforderungen zu, da der Anforderungsstatus nicht im Anwendungsstatus nachverfolgt wird.

Senden-Schaltfläche deaktivieren oder aktivieren, Formularfehler oder Live-Validierung anzeigen, Spinner anzeigen, während die Anfrage offen ist.. und diese Aktionen führen zu neuen Aktionen, die den Standardzustand wiederherstellen. Warum muss dies im App-Zustand sein?

Für Vorhersagbarkeit und Wiederspielbarkeit ist dies der Zweck von Redux. Wenn Sie nur die Aktionen und den Anfangszustand haben, können Sie die App in einen bestimmten Zustand versetzen, indem Sie nur Aktionen senden. Wenn eine Komponente anfängt, einen Zustand zu enthalten, beginnt der App-Zustand auseinanderzubrechen, es entsteht kein einzelner Zustandsbaum, der den App-Zustand darstellt.

Seien Sie dabei aber nicht zu dogmatisch. Je nach Szenario gibt es viele Gründe, Dinge in den Redux-Zustand zu versetzen, und viele Gründe, etwas im lokalen Komponentenzustand zu belassen. Siehe http://redux.js.org/docs/FAQ.html#organizing -state-only-redux-state und https://news.ycombinator.com/item?id=11890229.

Ok, jetzt ist für mich klar: es kommt darauf an. Danke!

Ich verwende componentWillReceiveProps und habe ein auth Objekt auf meiner Komponente. Bin mir nicht sicher, ob das überhaupt hilft.

// using reduxform
class LoginForm extends Component {
  static propTypes = {
    // this holds info on the current user.
    auth: PropTypes.shape({
      userId: PropTypes.number,
      token: PropTypes.string
    }),
     // react-router-redux.
    history: PropTypes.object.isRequired,
    submitting: PropTypes.bool.isRequired,
    fields: PropTypes.object.isRequired
  };
  componentWillReceiveProps(newProps) {
    const { auth } = newProps;
    auth && auth.token && this.props.history.go('#/');
  }
  render() {
    return (/**...*/) // form logic here.
  }
}

Ist etwas falsch daran, es so zu machen?

Was ist mit einem solchen Ansatz http://codereview.stackexchange.com/questions/138296/implementing-redirect-in-redux-middleware?
Übergabe der Umleitungs-URL an die Aktion und zusätzlicher Aufruf der next -Methode mit push von react-router-redux in der Middleware.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen