Skip to content

Instantly share code, notes, and snippets.

@abrahamhurtado
Last active March 15, 2019 02:31
Show Gist options
  • Select an option

  • Save abrahamhurtado/9944627d605957b38398a7655a82031f to your computer and use it in GitHub Desktop.

Select an option

Save abrahamhurtado/9944627d605957b38398a7655a82031f to your computer and use it in GitHub Desktop.
Como manejar estados de React

State en React

State es un feature fundamental de React. Cada componente puede almacenar su propio estado de manera isolada al resto y a partir de dicho estado, modificar el HTML que se muestra en pantalla.

State es lo que permite interacción en nuestras apps de React.

Pero State no tiene nada de especial, es simplemente un objeto plano de JavaScript donde describimos los datos que influyen en el render de nuestro componente.

React ya tiene mecanismos para manejar State: la función setState que está disponible en componentes escritos con clases y el Hook de useState, para componentes escritos como funciones.

Existen otras alternativas muy populares para manejar State en React son librerías como Redux o MobX. La utilidad de estas librerías es innegable pero muchas veces su inclusión en proyectos de React es casi dogmática, sin considerar la familiaridad del equipo de desarrollo o el overhead de conceptos nuevos.

Dan Abramov, creador de Redux, está de acuerdo en que quizás no necesites Redux.

Como usar State en React.

La manera básica

Aislado en un componente.

import React from 'react';
import { render } from 'react-dom';

class Counter extends React.Component {
  constructor () {
    super();
    this.state = {
      count: 0
    };
    this.increment = this.increment.bind(this);
    this.decrement = this.decrement.bind(this);
  }
  increment () {
    this.setState({
      count: this.state.count + 1
    })
  }
  decrement () {
    this.setState({
      count: this.state.count - 1
    })
  }
  render () {
    return (
      <div>
        You clicked { this.state.count } times!
        <button type="button" onClick={ this.increment }>+</button>
        <button type="button" onClick={ this.decrement }>-</button>
      </div>
    )
  }
}

Edita en CodeSandbox

Lifting state up

Bueno, ¿y si quiero compartir el estado con otro componente?

Si tienes dos componentes uno al lado del otro y quieres compartir estado con ambos, lo mejor es declarar el estado un nivel más arriba y pasarlo como props a los componentes originales, junto con los métodos para manipular el State.

Por ejemplo, acá tenemos dos calculadoras de temperaturas (de Celsius a Fahrenheit y de Celsius a Kelvin), cada una maneja su propio estado.

import React from 'react'

class CelsiusToFahrenheit extends React.Component {
  constructor () {
    super()
    this.state = {
      temperature: 0
    }
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange (event) {
    this.setState({
      temperature: Number(event.target.value)
    })
  }
  render () {
    const { temperature } = this.state;
    return (
      <div>
        <label>Introduce la temperatura en Celsius</label>
        <input onChange={ this.handleChange } type="number" value={ temperature }/>
        <p>{temperature} grados Celsius son { (temperature * 1.8) + 32 } grados Fahrenheit</p>
      </div>
    )
  }
}

class CelsiusToKelvin extends React.Component {
  constructor () {
    super()
    this.state = {
      temperature: 0
    }
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange (event) {
    this.setState({
      temperature: Number(event.target.value)
    })
  }
  render () {
    const { temperature } = this.state;
    return (
      <div>
        <label>Introduce la temperatura en Celsius</label>
        <input onChange={ this.handleChange } type="number" value={ temperature }/>
        <p>{temperature} grados Celsius son { temperature + 237.15 } grados Kelvin</p>
      </div>
    )
  }
}

Edita en CodeSandbox

Usando la filosofía de "lifting state up", podemos sacar el State de cada una de las calculadoras a un componente superior y sincronizar ambos componentes.

import React from 'react'

class App extends React.Component {
  constructor () {
    super()
    this.state = {
      temperature: 0
    }
    this.handleChange = this.handleChange.bind(this)
  }
  handleChange (event) {
    this.setState({
      temperature: Number(event.target.value)
    })
  }
  render () {
    return (
      <div>
        <CelsiusToFahrenheit temperature={ this.state.temperature } handleChange={ this.handleChange } />
        <CelsiusToKelvin temperature={ this.state.temperature } handleChange={ this.handleChange } />
      </div>
    )
  }
}

class CelsiusToFahrenheit extends React.Component {
  render () {
    const { temperature } = this.props;
    return (
      <div>
        <label>Introduce la temperatura en Celsius</label>
        <input onChange={ this.props.handleChange } type="number" value={ temperature }/>
        <p>{temperature} grados Celsius son { (temperature * 1.8) + 32 } grados Fahrenheit</p>
      </div>
    )
  }
}

class CelsiusToKelvin extends React.Component {
  render () {
    const { temperature } = this.props;
    return (
      <div>
        <label>Introduce la temperatura en Celsius</label>
        <input onChange={ this.props.handleChange } type="number" value={ temperature }/>
        <p>{temperature} grados Celsius son { temperature + 237.15 } grados Kelvin</p>
      </div>
    )
  }
}

Edita en CodeSandbox

Context

El método anterior es útil cuando se sube el State uno o dos niveles arriba, pero si hay State que debería considerarse "global" (por ejemplo, datos de un usuario o el tema de la app) resulta molesto y tedioso pues tienes que pasar las props manualmente de componente a componente.

Justo para este problema existe Context.

// Creamos un Context con un valor inicial, aunque no es necesario
// Una buena práctica sería tener cada uno de nuestros contexto
// en archivos separados e importarlos cuando sea necesario
const ThemeContext = React.createContext({
  theme: "light",
  toggleTheme: () => {}
});

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      theme: "light"
    };
    this.toggleTheme = this.toggleTheme.bind(this);
  }
  toggleTheme() {
    const { theme } = this.state;
    if (theme === "light") {
      this.setState({
        theme: "dark"
      });
    } else {
      this.setState({
        theme: "light"
      });
    }
  }
  render() {
    return (
      <ThemeContext.Provider
        value={{
          theme: this.state.theme,
          toggleTheme: this.toggleTheme
        }}
      >
        <ButtonGroup />
      </ThemeContext.Provider>
    );
  }
}

class ButtonGroup extends React.Component {
  render() {
    return (
      <div>
        {/* Muy importante ver como no estamos pasando ninguna prop a estos componentes */}
        <Article />
        <ThemedButton />
      </div>
    );
  }
}

class Article extends React.Component {
  // el Context que le pasemos a contextType, buscará el Provider más cercano
  // (en este caso, el que está sobre <App />) y obtendrá los valores de ese Context
  static contextType = ThemeContext;
  render() {
    const styles = {
      padding: "1rem 2rem",
      width: "100%",
      marginBottom: 20
    };

    const lightStyles = {
      background: "#f7f7f7",
      color: "#333"
    };

    const darkStyles = {
      background: "#333",
      color: "#f7f7f7"
    };

    // gracias a contextType, tenemos acceso a this.context
    const { theme } = this.context;

    return (
      <div
        style={
          theme === "light"
            ? { ...styles, ...lightStyles }
            : { ...styles, ...darkStyles }
        }
      >
        <h3>Este es un blogpost</h3>
        <p>Este es el cuerpo del blogpost</p>
      </div>
    );
  }
}

class ThemedButton extends React.Component {
  // el Context que le pasemos a contextType, buscará el Provider más cercano
  // (en este caso, el que está sobre <App />) y obtendrá los valores de ese Context
  static contextType = ThemeContext;
  render() {
    const styles = {
      padding: "1rem 2rem",
      boxShadow: "0 1px 3px rgba(0,0,0,.25)",
      borderRadius: 4,
      border: "1px solid #333"
    };

    const lightStyles = {
      background: "#f7f7f7",
      color: "#333"
    };

    const darkStyles = {
      background: "#333",
      color: "#f7f7f7"
    };

    // gracias a contextType, tenemos acceso a this.context
    const { theme, toggleTheme } = this.context;

    return (
      <button
        type="button"
        style={
          theme === "light"
            ? { ...styles, ...lightStyles }
            : { ...styles, ...darkStyles }
        }
        onClick={toggleTheme}
      >
        Actualizar tema
      </button>
    );
  }
}

Si tienes necesidades más complejas de manejar states, quizás sí necesites Redux :P

Edita en CodeSandbox

Más fuentes de información

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment