Jesús García

Gestión de estado y temas adicionales

Gestión de estado

y temas adicionales

  1. Gestión de estado -
    • Context
    • MobX
    • Redux 
  2. Performance
  3. Error Boundaries
  4. Portals

Gestión de estado

Tenemos varias alternativas:

  • Context y estado local

  • Observables - MobX

  • Redux

Context y estado local

Cada vez más viable gracias a la nueva API de Context, los hooks - en particular useReducer y, en el futuro, suspense para data-fetching

Aparentemente es la dirección que está tomando React

describing immutable updates with a pure function and data — I think it’s as relevant as ever! Less confident about needing a library for this.

- Dan Abramov

Observables - MobX

Hacer React... reactivo

  • State
  • Derivations + Reactions
  • Actions
class ObservableTodoStore {
	@observable todos = [];
    @observable pendingRequests = 0;

    constructor() {
        mobx.autorun(() => console.log(this.report));
    }

	@computed get completedTodosCount() {
    	return this.todos.filter(
			todo => todo.completed === true
		).length;
    }

	@computed get report() {
		if (this.todos.length === 0)
			return "<none>";
		return `Next todo: "${this.todos[0].task}". ` +
			`Progress: ${this.completedTodosCount}/${this.todos.length}`;
	}

	addTodo(task) {
		this.todos.push({
			task: task,
			completed: false,
			assignee: null
		});
	}
}


const observableTodoStore = new ObservableTodoStore();
@observer
class TodoList extends React.Component {
  render() {
    const store = this.props.store;
    return (
      <div>
        { store.report }
        <ul>
        { store.todos.map(
          (todo, idx) => <TodoView todo={ todo } key={ idx } />
        ) }
        </ul>
        { store.pendingRequests > 0 ? <marquee>Loading...</marquee> : null }
        <button onClick={ this.onNewTodo }>New Todo</button>
        <small> (double-click a todo to edit)</small>
        <RenderCounter />
      </div>
    );
  }

  onNewTodo = () => {
    this.props.store.addTodo(prompt('Enter a new todo:','coffee plz'));
  }
}

@observer
class TodoView extends React.Component {
  render() {
    const todo = this.props.todo;
    return (
      <li onDoubleClick={ this.onRename }>
        <input
          type='checkbox'
          checked={ todo.completed }
          onChange={ this.onToggleCompleted }
        />
        { todo.task }
        { todo.assignee
          ? <small>{ todo.assignee.name }</small>
          : null
        }
        <RenderCounter />
      </li>
    );
  }

  onToggleCompleted = () => {
    const todo = this.props.todo;
    todo.completed = !todo.completed;
  }

  onRename = () => {
    const todo = this.props.todo;
    todo.task = prompt('Task name', todo.task) || todo.task;
  }
}

ReactDOM.render(
  <TodoList store={ observableTodoStore } />,
  document.getElementById('reactjs-app')
);

Redux


                                               /*<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==\
                                                ⇓                                                     ⇑ 
                                                ⇓                                                     ⇑ 
                                                ⇓                                                     ⇑ 
                                            Middlewares                                               ⇑*/
                                            const middleware1 = store => next => action => {
                /* Api <==<==<==<==<==<==<==<== Side effects */
                                                console.log(action)
                                                next(action)
                                            }
                                            const middleware2 = store => next => action => {
                                                const {getState, dispatch} = store
                /* Socket <==<==<==<==<==<==<== Side effects */
                                                next(action)
                                            }
                                            const middleware3 = store => next => action => {
                /* Analytics <==<==<==<==<==<== Side effects */
                                                next(action)
                                            }
                                              /*⇓                                                     ⇑
                                                ⇓                                                     ⇑ 
                                                ⇓                                                     ⇑ 
                                                ⇓                                                     ⇑ 
                                                Reducer                                                  */
                                                const reducer = (state, action) => {
                                                    switch (action.type) {
                                                        case 'updateMovidas':
                                                            return {
                                                                ...state,
                                                                mis: {
                                                                    ...state.mis,
                                                                    movidas: action.payload
                                                                }
                                                            }
                                                        case 'otherAction':
                                                            /* return new state */
                                                        default:
                                                            return state;
                                                    }
                                                }
                                               /*⇓                                                     ⇑
                                                 ⇓                                                     ⇑ 
                                                 ⇓                                                     ⇑ 
                                                (Middleware de nuevo)                                  ⇑
                                                 ⇓                                                     ⇑ 
                                                 ⇓                                                     ⇑ 
                                                 ⇓                                                     ⇑ 
                                                Estado                                                 */
                                                const state = 
                                                    {
                                                        mis: {
                                                            movidas: 'guays'
                                                        },
                                                    }
               /* <==<==<==<==<==<==<==<==<==<==<==/                                                   
               ⇓                                                                                       ⇑
               ⇓                                                                                       ⇑
               ⇓                                                                                       ⇑ 
               ⇓                                                                                       ⇑
               ⇓                                                                                       ⇑
               ⇓                                                                                       ⇑     
               ⇓                                                                                       ⇑
               ⇓  Selectores                                                            Acciones    
               */ const selector = state => state.mis.movidas;                          const action = {
                                                                                          type: 'updateMovidas',
                                                                                          payload: 'fenomenal'
                                                                                        }
               /*                                                                                      ⇑                 
               ⇓                                                                                       ⇑     
               ⇓                                                                                       ⇑     
               ⇓                                                                                       ⇑ 
               ⇓                                                                                       ⇑                                        
               */                            
                                        const actionCreator = () => ({
                                            type: 'updateMovidas',
                                            payload: 'fenomenal'
                                        });
            
                                        connect(                                                            
                                            /*⇓ mapStateToProps ⇓*/
                                            state => ({movidas: selector(state)}),                        
                                            /*⇓ mapDispatchToProps ⇓*/
                                            dispatch => ({handler: () => dispatch(actionCreator())})                                      
                                            /* shortHand mapDispatchToProps 
                                               {handler: actionCreator} 
                                            */                                      
                                        )(MyComponent)                                                    
               /*                                                                                      ⇑
               ⇓                                                                                       ⇑
               ⇓                                                                                       ⇑
               => => => => => => => => => => => => =>  ⇓  / => => => => => => => => => => => => => => /
                                                       ⇓  ⇑
                                                       ⇓  ⇑
                                      Props, handlers  ⇓  ⇑  Acciones
                                                       ⇓  ⇑
                                                       ⇓  ⇑ 
                                        Vista                                   */
                                        function MyComponent({movidas, handler}){
                                            return (
                                                <div>
                                                    Mis movidas:
                                                    {movidas}
                                                    <button onClick={handler}/>
                                                <div/>
                                            )
                                        }
  • El estado de la aplicación está en un sólo objeto
  • Se actualiza mediante una función reducer que toma el estado actual y un objeto 'action' y devuelve el nuevo estado
  • Las acciones pueden pasar por middlewares

Redux

The Elm Architecture - inspiró Redux

Performance

Performance

Evitar re-renders innecesarios:

  • shouldComponentUpdate
  • React.PureComponent y React.memo
  • useMemo, useCallback, lazy useState

Performance

Error Boundaries

Los límites de errores son componentes de React que capturan errores de JavaScript en cualquier parte de su árbol de componentes hijo, registran esos errores, y muestran una interfaz de repuesto en lugar del árbol de componentes que ha fallado. Los límites de errores capturan errores durante el renderizado, en métodos del ciclo de vida, y en constructores de todo el árbol bajo ellos.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
      <ErrorBoundary>
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
        <BuggyCounter />
      </ErrorBoundary>
      <hr />
      <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.</p>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
    </div>
  );
}



ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Portals

render() {
  // React *no* crea un nuevo div, convierte el hijo en `domNode`.
  // `domNode` es cualquier nodo DOM válido, independientemente de su ubicación en el DOM.
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

Los portales permiten renderizar hijos en un nodo DOM que existe por fuera de la jerarquía del DOM del componente padre.

Gestión de estado y temas adicionales

By Jesús García Martínez

Gestión de estado y temas adicionales

  • 146