Redux: disparando acciones en respuesta a las transiciones de ruta en react-router

Creado en 7 jul. 2015  ·  26Comentarios  ·  Fuente: reduxjs/redux

Hola chicos,

Estoy usando react-router y redux en mi última aplicación y me enfrento a un par de problemas relacionados con los cambios de estado requeridos en función de los parámetros y consultas de URL actuales.

Básicamente, tengo un componente que necesita actualizar su estado cada vez que cambia la URL. El estado se está pasando a través de accesorios por redux con el decorador como tal

 @connect(state => ({
   campaigngroups: state.jobresults.campaigngroups,
   error: state.jobresults.error,
   loading: state.jobresults.loading
 }))

En este momento estoy usando el método de ciclo componentWillReceiveProps vida this.props.params y this.props.query : el problema principal con este enfoque es que estoy activando una acción en este método para actualizar el estado, que luego pasa y pasa nuevos accesorios al componente que activará el mismo método de ciclo de vida nuevamente, así que básicamente estoy creando un interminable bucle, actualmente estoy configurando una variable de estado para evitar que esto suceda.

  componentWillReceiveProps(nextProps) {
    if (this.state.shouldupdate) {
      let { slug } = nextProps.params;
      let { citizenships, discipline, workright, location } = nextProps.query;
      const params = { slug, discipline, workright, location };
      let filters = this._getFilters(params);
      // set the state accroding to the filters in the url
      this._setState(params);
      // trigger the action to refill the stores
      this.actions.loadCampaignGroups(filters);
    }
  }

¿Existe un enfoque estándar para activar acciones basadas en transiciones de ruta O puedo tener el estado de la tienda directamente conectado al estado del componente en lugar de pasarlo a través de accesorios? Intenté usar el método estático willTransitionTo pero no tengo acceso a this.props.dispatch allí.

docs ecosystem question

Comentario más útil

@deowk Hay dos partes en este problema, diría yo. La primera es que componentWillReceiveProps() no es una forma ideal de responder a los cambios de estado, sobre todo porque te obliga a pensar imperativamente , en lugar de hacerlo de forma reactiva, como hacemos con Redux. La solución es almacenar la información actual de su enrutador (ubicación, parámetros, consulta) _dentro_ de su tienda. Entonces, todo su estado está en el mismo lugar y puede suscribirse a él utilizando la misma API de Redux que el resto de sus datos.

El truco consiste en crear un tipo de acción que se active cada vez que cambie la ubicación del enrutador. Esto es fácil en la próxima versión 1.0 de React Router:

// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));

Ahora el estado de su tienda siempre estará sincronizado con el estado del enrutador. Eso soluciona la necesidad de reaccionar manualmente a los cambios de parámetros de consulta y setState() en su componente anterior, solo use el Conector de Redux.

<Connector select={state => ({ filter: getFilters(store.router.params) })} />

La segunda parte del problema es que necesita una forma de reaccionar a los cambios de estado de Redux fuera de la capa de vista, por ejemplo, para disparar una acción en respuesta a un cambio de ruta. Puede continuar utilizando componentWillReceiveProps para casos simples como el que describe, si lo desea.

Sin embargo, para algo más complicado, recomiendo usar RxJS si está abierto a ello. Esto es exactamente para lo que están diseñados los observables: flujo de datos reactivos.

Para hacer esto en Redux, primero cree una secuencia observable de estados de tienda. Puede hacer esto usando observableFromStore() redux-rx.

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

Entonces es solo una cuestión de usar operadores observables para suscribirse a cambios de estado específicos. Aquí hay un ejemplo de redireccionamiento desde una página de inicio de sesión después de un inicio de sesión exitoso:

const didLogin$ = state$
  .distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
  .filter(state => state.loggedIn && state.router.path === '/login');

didLogin$.subscribe({
   router.transitionTo('/success');
});

Esta implementación es mucho más simple que la misma funcionalidad usando patrones imperativos como componentDidReceiveProps() .

¡Espero que ayude!

Todos 26 comentarios

@deowk Puede comparar this.props con nextProps para ver si los datos relevantes cambiaron. Si no cambió, entonces no tienes que activar la acción nuevamente y puedes escapar del bucle infinito.

@johanneslumpe : En mi caso, campaigngroups es una colección bastante grande de objetos, ¿parece un poco ineficiente usar una comparación profunda de objetos como condición de esta manera?

@deowk si usa datos inmutables, puede simplemente comparar referencias

@deowk Hay dos partes en este problema, diría yo. La primera es que componentWillReceiveProps() no es una forma ideal de responder a los cambios de estado, sobre todo porque te obliga a pensar imperativamente , en lugar de hacerlo de forma reactiva, como hacemos con Redux. La solución es almacenar la información actual de su enrutador (ubicación, parámetros, consulta) _dentro_ de su tienda. Entonces, todo su estado está en el mismo lugar y puede suscribirse a él utilizando la misma API de Redux que el resto de sus datos.

El truco consiste en crear un tipo de acción que se active cada vez que cambie la ubicación del enrutador. Esto es fácil en la próxima versión 1.0 de React Router:

// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));

Ahora el estado de su tienda siempre estará sincronizado con el estado del enrutador. Eso soluciona la necesidad de reaccionar manualmente a los cambios de parámetros de consulta y setState() en su componente anterior, solo use el Conector de Redux.

<Connector select={state => ({ filter: getFilters(store.router.params) })} />

La segunda parte del problema es que necesita una forma de reaccionar a los cambios de estado de Redux fuera de la capa de vista, por ejemplo, para disparar una acción en respuesta a un cambio de ruta. Puede continuar utilizando componentWillReceiveProps para casos simples como el que describe, si lo desea.

Sin embargo, para algo más complicado, recomiendo usar RxJS si está abierto a ello. Esto es exactamente para lo que están diseñados los observables: flujo de datos reactivos.

Para hacer esto en Redux, primero cree una secuencia observable de estados de tienda. Puede hacer esto usando observableFromStore() redux-rx.

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

Entonces es solo una cuestión de usar operadores observables para suscribirse a cambios de estado específicos. Aquí hay un ejemplo de redireccionamiento desde una página de inicio de sesión después de un inicio de sesión exitoso:

const didLogin$ = state$
  .distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
  .filter(state => state.loggedIn && state.router.path === '/login');

didLogin$.subscribe({
   router.transitionTo('/success');
});

Esta implementación es mucho más simple que la misma funcionalidad usando patrones imperativos como componentDidReceiveProps() .

¡Espero que ayude!

Necesitamos esto en nuevos documentos. Muy bien escrito.

@acdlite : Muchas gracias, su consejo realmente me ayudó mucho, pero estoy luchando con lo siguiente:> cambiar el estado en la tienda activando un creador de acciones en respuesta a un cambio de URL.

Quiero activar el creador de acciones en el método estático willTransitionTo, ya que parece ser la única opción en react-router v0.13.3

class Results extends Component  {
..........
}

Results.willTransitionTo = function (transition, params, query) {
 // how do I get a reference to dispatch here?
};

@deowk Supongo que llamas a dispatch directamente en la instancia de redux:

const redux = createRedux(stores);
BrowserHistory.listen(location => redux.dispatch(routeLocationDidUpdate(location)));

@deowk Actualmente envío mis cambios de URL en el enrutador react 0.13.3 de esta manera:

Router.run(routes, Router.HistoryLocation, function(Handler, locationState) {
   dispatch(routeLocationDidUpdate(locationState))
   React.render(<Handler/>, appEl);
});

@acdlite muy bien explicado! : +1:
De acuerdo, también debería estar en los nuevos documentos :)

Como nota, BrowserHistory.history() ha cambiado a BrowserHistory.addChangeListener() .

https://github.com/rackt/react-router/blob/master/modules/BrowserHistory.js#L57

EDITAR:

Que podría verse así:

history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

¿Y qué hay del estado inicial de ese reductor, @pburtchaell?

Tengo la siguiente configuración:

// client.js
class App extends Component {
  constructor(props) {
    super(props);
    this.history = new HashHistory();
  }

  render() {
    return (
      <Provider store={store}>
         {renderRoutes.bind(null, this.history)}
      </Provider>
    );
  }
}

// routes.js
export default function renderRoutes(history) {

  // When the route changes, dispatch that information to the store.
  history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

  return (
    <Router history={history}>
      <Route component={myAppView}>
        <Route path="/" component={myHomeView}  />
      </Route>
    </Router>
  );
};

Esto activa el creador de acciones routeLocationDidUpdate() cada vez que cambia la ruta y cuando la aplicación se carga inicialmente.

@pburtchaell ¿puedes compartir tu routeLocationDidUpdate ?

@gyzerok Seguro. Es un creador de acción.

// actions/location.js
export function routeLocationDidUpdate(location) {
  return {
    type: 'LOCATION_UPDATE',
    payload: {
      ...location,
    },
  };
}

@pburtchaell, en su ejemplo, no entiendo en silencio dónde se obtienen los datos necesarios para una ruta en particular

Aquí hay una versión más completa de mi ejemplo anterior:

https://github.com/acdlite/redux-react-router/blob/master/README.md#bonus -react-to-state-changes-with-redux-rx

Supongo que @pburtchaell usaría una versión asíncrona de ese código para obtener datos (de una llamada REST, etc.). ¿De lo que no estoy seguro es entonces qué? Supongo que luego va a una tienda, pero ¿esa tienda sería una ubicación?

<strong i="7">@connect</strong>

a adentro, digamos en

<Comments />

que ya estaría 'conectado' (suscrito) a una tienda de comentarios? ¿O simplemente agrega registrar estas acciones de ubicación en commentsStore?

Una URL como

/comments/123

siempre va a estar 'acoplado' al componente de comentarios, entonces, ¿cómo lo reutilizo? Lo siento si esto es tonto, muy confuso aquí. Que había un ejemplo sólido que pude encontrar.

También estoy lidiando con esto, averiguando cómo llamar a mi método fetchData(dispatch, params) estático (estático, porque el servidor lo llama para enviar acciones asíncronas FETCH antes del procesamiento inicial).

Dado que existe una referencia a dispatch dentro del contexto, estoy pensando que la forma más limpia de obtener esto para las llamadas del lado del cliente de fetchData es un componente similar a Connector que llama fetchData o shouldFetchData , o haga que la devolución select llamada dispatch junto con state , para que podamos inspeccionar la estado de store y enviar FETCH acciones según sea necesario.

Este último es más conciso pero rompe el hecho de que select debería seguir siendo una función pura. Examinaré el primero.

Mi solución es esta, aunque bastante adaptada a mi configuración (método fetchData(dispatch, state) estático que envía acciones asíncronas _FETCH ) llamará a cualquier devolución de llamada que se le pase con las propiedades apropiadas: https: // gist.github.com/grrowl/6cca2162e468891d8128 - sin embargo, no usa willTransitionTo, por lo que no podrá retrasar la transición de páginas.

¡Definitivamente necesitamos agregar los documentos! Posible en la sección "Uso con react-router".

Tendremos una forma oficial de hacerlo después del lanzamiento 1.0.

Es bueno escuchar a @gaearon.

Entonces es solo una cuestión de usar operadores observables para suscribirse a cambios de estado específicos.

Tengo una transmisión observable configurada para mantener mi tienda sincronizada con cualquier cambio de ruta, y también tengo una configuración de transmisión para ver mi tienda. Estoy interesado en hacer la transición a una nueva ruta cuando un valor cambia en mi tienda, específicamente cuando cambia de undefined a una cadena válida como resultado de enviar con éxito un formulario en otra parte de mi aplicación.

Las dos transmisiones están configuradas y funcionando, y la tienda se está actualizando, pero soy nuevo en Rx y tengo problemas con la consulta observable ... ¿algún consejo? ¿Estoy en el camino correcto? Estoy bastante seguro de que tiene algo que ver con distinctUntilChanged pero está horneando mis fideos en este momento, se agradece cualquier ayuda :)

EDITAR: ¡Ack! Lo siento, respondí mi propia pregunta. Pseudo código a continuación. Avísame si se ve horrible.

const didChangeProject$ = state$
  .distinctUntilChanged(state => state.project._id)
  .filter(state => typeof state.project._id !== 'undefined');

Cierre a favor del # 177. Cuando lleguemos a documentar el enrutamiento, tendremos que cubrir todos los escenarios: redirigir en caso de cambio de estado, redirigir a solicitud de devolución de llamada, etc.

Sé que esto está cerrado ... pero con React 0.14 y las diversas dependencias 1.0 en beta en este momento, ¿hay una actualización para esto y, si no, puede un tutorial sobre las nuevas habilidades en React 0.14, react-router, redux, etc. ¿escrito? Parece que hay muchos de nosotros que estamos tratando de estar al día con los próximos cambios mientras aprendemos / entendemos todo esto al mismo tiempo. Sé que las cosas están en un estado de cambio (juego de palabras ... uh ... no previsto), pero parece que estoy viendo piezas de diferentes ejemplos que no funcionan bien entre sí y después de varios días todavía no puedo hacer que mi aplicación actualizada funcione con enrutamiento. Lo más probable es que sea mi culpa, ya que todavía estoy luchando un poco por comprender cómo funciona todo esto, solo espero que pronto salga un tutorial actualizado con las últimas novedades.

Hasta que haya un tutorial, siéntase libre de mirar examples/real-world que tiene el enrutamiento. No es "lo último y lo mejor" en este momento, pero funciona bien.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

ilearnio picture ilearnio  ·  3Comentarios

caojinli picture caojinli  ·  3Comentarios

jbri7357 picture jbri7357  ·  3Comentarios

parallelthought picture parallelthought  ·  3Comentarios

timdorr picture timdorr  ·  3Comentarios