Change to English?
Si
Trabajo

Migra tu proyecto con React JSON API desde Redux a MobX

Algunas notas sobre la gestión global del estado

Migra tu proyecto con React JSON API desde Redux a MobX
23 de mayo de 2017

Después de unos cuantos proyectos usando Redux, me he dado cuenta de que puede no ser la mejor solución para todo tipo de proyectos. No me malinterpretes, Redux funciona muy bien, pero tiendo a encontrarme procrastinando un poco cada vez que tengo que añadir un nuevo valor al árbol de estado. Quizás se deba a la cantidad de código que se requiere para hacerlo.

De todos modos, este post va a ser sobre MobX. Voy a explicar paso a paso los cambios que he tenido que hacer para que funcione. Hay que tener en cuenta que también estoy usando la API JSON, así que también cubriré este aspecto.

En primer lugar, tienes que eliminar todos los paquetes y referencias redux de tu código base. Luego tienes que instalar los siguientes plugins de babel para permitir el uso de decoradores.

yarn add babel-plugin-transform-decorators-legacy -D
yarn add babel-plugin-transform-class-properties -D

Y luego definirlos en el archivo .babelrc:

"plugins": ["transform-decorators-legacy", "transform-class-properties"]

Ahora tenemos que usar babel como parser de ESLint. Añade este paquete:

yarn add babel-eslint -D

Luego, actualiza el parser en el archivo .eslintrc:

"parser": "babel-eslint"

Ahora vamos a instalar los siguientes paquetes de MobX:

yarn add mobx -S
yarn add mobx-jsonapi-store -S
yarn add mobx-react -S
yarn add mobx-react-router -S

Esas son todas las dependencias que necesitamos hasta ahora. Ahora veamos algo de código.

En primer lugar, vamos a crear un store para manejar los estados globales de la aplicación, como el cargador, las alertas de notificaciones y otros flags comunes.

Crea un nuevo archivo llamado store.jsx y añade el siguiente código:

import { autorun, observable } from 'mobx';

class MainStore {
  @observable isLoading = true;
  @observable isFetching = false;
  @observable isDesktop = true;
  @observable notification: { message: '', type: '' };
  @observable apiReady = false;
  @observable isDesktop = false;
  @observable isDesktop = false;
}

const store = new MainStore();

autorun(() => {
  // eslint-disable-next-line
  console.log(store);
});

export default store;

En este archivo, sólo estamos creando las variables de estado y haciéndolas observables. También estamos usando autorun para registrar automáticamente en la consola cualquier cambio en el store (sólo para propósitos de depuración).

Ahora, vamos a refactorizar el archivo index.jsx. Utilizaremos la última versión de react router (v4) y el Material UI framework, así que tenlo en cuenta.

import React from 'react';
import ReactDOM from 'react-dom';
import createBrowserHistory from 'history/createBrowserHistory';
import { Provider } from 'mobx-react';
import { RouterStore, syncHistoryWithStore } from 'mobx-react-router';
import { Router } from 'react-router';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import mainStore from './store';
import App from './components/app';
import './components/bundle.scss';

const browserHistory = createBrowserHistory();
const routingStore = new RouterStore();

const stores = {
  routing: routingStore,
  app: mainStore
};

const history = syncHistoryWithStore(browserHistory, routingStore);

ReactDOM.render(
  <Provider {...stores}>
    <Router history={history}>
      <MuiThemeProvider>
        <App />
      </MuiThemeProvider>
    </Router>
  </Provider>,
  document.getElementById('react-root')
);

Lo importante aquí es el objeto que contiene los stores, uno para el router y el otro que creamos antes. Luego pasamos estos stores al proveedor MobX y ¡ya está!

Ahora veamos el archivo app.jsx, que contendrá las rutas y otros componentes comunes:

import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import { Route } from 'react-router-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import Helmet from 'react-helmet';
import Notification from './common/notification/notification';
import Loader from './common/loader/loader';
import Home from './home/home';
import PageNotFound from './pageNotFound/pageNotFound';

injectTapEventPlugin();

@inject('routing') @observer
class App extends Component {
  render() {
    const title = this.props.routing.title
      ? `${this.props.routing.title} | App name`
      : 'App name';

    return (
      <section className="app-container">
        <Switch>
          <Route exact path="/" component={Home} />
          <Route component={PageNotFound} title="Page not found" />
        </Switch>
        <Helmet title={String(title)} />
        <Loader />
        <Notification />
      </section>
    );
  }
}

export default App;

Todo lo que estamos haciendo aquí es crear las rutas y decirle al componente que sea un observador, para que cada vez que las variables de estado cambien, el componente se actualice. Fíjate también en que estamos leyendo el title de la ruta para saber qué título de página poner usando el paquete Helmet.

Recuerda usar Switch dentro del Router y establecer la ruta raíz con exact, de lo contrario la página no encontrada no funcionará.

Otro ejemplo de cómo usar MobX, es para el componente Loader. Necesitamos saber cuándo mostrar u ocultar el cargador, así que vamos a utilizar la variable isLoading del store que hemos creado. Si la variable es true entonces el cargador será visible, de lo contrario no.

Veamos el componente del cargador:

import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import classnames from 'classnames';
import Icon from '../lib/icon/icon';
import Logo from '../../../assets/svg/logo.svg';

@inject('app') @observer
class Loader extends Component {
  render() {
    return (
      <section
        className={classnames('loader', {
          'fade-in': this.props.app.isLoading,
          'fade-out': !this.props.app.isLoading
        })}
      >
        <div className="loader__circle" />
        <div className="loader__line-mask">
          <div className="loader__line" />
        </div>
        <Icon glyph={Logo} className="loader__logo" />
      </section>
    );
  }
}

export default Loader;

Todo lo que estamos haciendo es establecer una clase fade-in o fade-out dependiendo del valor del estado isLoading.

Ahora, desde cualquier componente de la aplicación, podemos cambiar este valor haciendo this.props.app.isLoading = false y el cargador se ocultará.

Otra cosa que no funcionaba con el nuevo router, es la actualización de la página (con el nuevo componente) al pasar de una página a otra. Después de indagar un poco sobre el tema, encontré esta respuesta de stack-overflow que lo arregló.

Cuando haces @inject('routing') a un componente, obtienes acceso a this.props.routing, por lo que puedes enviar nuevas URLs al historial del navegador. Esto funcionaba bien haciendo algo como esto this.props.routing.push('/leads/231234').

El problema era que la página no se refrescaba, así que tuve que envolver la exportación de los componentes con withRouter(): export default withRouter(Home). Puedes importarlo desde el paquete react-router.

Por último, vamos a ver cómo integrar la API JSON con MobX. Esto es un poco más difícil, pero lo conseguiremos. El paquete que estamos utilizando es mobx-jsonapi-store.

Un ejemplo sencillo de cómo solicitar datos de la API y asignarle un store sería el siguiente:

import { Store, config } from 'mobx-jsonapi-store';
const teststore = new Store();
config.defaultHeaders = {'xxxxxx': 'xxxxxxxxxx'};

teststore.request('http://localhost:3000/xxxxxxxxxxx')
  .then((respuesta) => {
    const Data = teststore.sync(response.data);
    console.log(Datos);
  });

ACTUALIZACIÓN Junio 2022: Ya no me gusta mucho usar MobX en proyectos React. Es mejor mantener todo bajo el mismo paradigma de programación funcional.

Antes de continuar...

Al hacer clic en "Aceptar todas", estás consintiendo que se guarden cookies en tu dispositivo para mejorar el sitio web y analizar cómo es usado.

Idiomas

Change to English?