SetState funcional es el futuro de React

Actualización: di una charla de seguimiento sobre este tema en React Rally. Si bien esta publicación trata más sobre el patrón "funcional setState", la charla trata más sobre la comprensión profunda de setState

React ha popularizado la programación funcional en JavaScript. Esto ha llevado a marcos gigantes que adoptan el patrón de IU basado en componentes que usa React. Y ahora la fiebre funcional se está extendiendo al ecosistema de desarrollo web en general.

Pero el equipo de React está lejos de ceder. Continúan profundizando, descubriendo gemas aún más funcionales escondidas en la biblioteca legendaria.

Así que hoy les revelo un nuevo oro funcional enterrado en React, el secreto React mejor guardado: ¡ Functional setState!

Bien, acabo de inventar ese nombre ... y no es del todo nuevo ni un secreto. No, no exactamente. Mira, es un patrón integrado en React, que solo conocen unos pocos desarrolladores que realmente han profundizado. Y nunca tuvo nombre. Pero ahora lo hace: ¡setState funcional!

Siguiendo las palabras de Dan Abramov al describir este patrón, Functional setState es un patrón en el que

"Declare los cambios de estado por separado de las clases de componentes".

¿Eh?

Vale ... lo que ya sabes

React es una biblioteca de UI basada en componentes. Un componente es básicamente una función que acepta algunas propiedades y devuelve un elemento de IU.

function User(props) { return ( A pretty user );}

Un componente puede necesitar tener y administrar su estado. En ese caso, normalmente escribe el componente como una clase. Entonces tienes su estado en vivo en la constructorfunción de clase :

class User { constructor () { this.state = { score : 0 }; }
 render () { return ( This user scored {this.state.score} ); }}

Para administrar el estado, React proporciona un método especial llamado setState(). Lo usas así:

class User { ... 
 increaseScore () { this.setState({score : this.state.score + 1}); }
 ...}

Observe cómo setState()funciona. Le pasa un objeto que contiene parte (s) del estado que desea actualizar. En otras palabras, el objeto que pasa tendría claves correspondientes a las claves en el estado del componente, luego setState()actualiza o establece el estado fusionando el objeto con el estado. Por lo tanto, "set-State".

Lo que probablemente no sabías

¿Recuerdas cómo dijimos que setState()funciona? Bueno, ¿y si te dijera que en lugar de pasar un objeto, podrías pasar una función ?

Si. setState()también acepta una función. La función acepta el estado anterior y los accesorios actuales del componente que utiliza para calcular y devolver el siguiente estado. Míralo a continuación:

this.setState(function (state, props) { return { score: state.score - 1 }});

Tenga en cuenta que setState()es una función, y le estamos pasando otra función (programación funcionalfuncional setState ). A primera vista, esto puede parecer feo, demasiados pasos solo para establecer el estado. ¿Por qué querrás hacer esto alguna vez?

¿Por qué pasar una función a setState?

La cuestión es que las actualizaciones de estado pueden ser asincrónicas.

Piense en lo que sucede cuando setState()se llama. React fusionará primero el objeto al que pasó al setState()estado actual. Entonces comenzará esa cosa de reconciliación . Creará un nuevo árbol de React Element (una representación de objeto de su interfaz de usuario), comparará el árbol nuevo con el árbol anterior, averiguará qué ha cambiado en función del objeto al que pasó y setState(), finalmente, actualizará el DOM.

¡Uf! ¡Mucho trabajo! De hecho, este es incluso un resumen demasiado simplificado. ¡Pero confía en React!

React no se limita a "establecer el estado".

Debido a la cantidad de trabajo involucrado, es setState()posible que las llamadas no actualicen inmediatamente su estado.

React puede agrupar varias setState()llamadas en una sola actualización para mejorar el rendimiento.

¿Qué quiere decir React con esto?

Primero, " múltiples setState()llamadas" podría significar llamar setState()dentro de una sola función más de una vez, como esta:

...
state = {score : 0};
// multiple setState() callsincreaseScoreBy3 () { this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1});}
...

Ahora, cuando React se encuentra con “ múltiples setState()llamadas”, en lugar de hacer ese “estado de configuración” tres veces enteras , React evitará la enorme cantidad de trabajo que describí anteriormente y se dirá inteligentemente: “¡No! No voy a escalar esta montaña tres veces, llevando y actualizando algo de estado en cada viaje. No, prefiero conseguir un contenedor, empaquetar todas estas partes juntas y hacer esta actualización solo una vez ". Y eso, amigos míos, espor lotes !

Recuerda que lo que pasas setState()es un objeto simple. Ahora, suponga que cada vez que React encuentra " múltiples setState()llamadas", hace el proceso por lotes extrayendo todos los objetos pasados ​​a cada setState()llamada, los fusiona para formar un solo objeto, luego usa ese único objeto para hacer setState().

En JavaScript, los objetos de fusión pueden verse así:

const singleObject = Object.assign( {}, objectFromSetState1, objectFromSetState2, objectFromSetState3);

Este patrón se conoce como composición de objetos.

En JavaScript, la forma en que funciona la “fusión” o la composición de objetos es: si los tres objetos tienen las mismas claves, el valor de la clave del último objeto pasado a Object.assign()gana. Por ejemplo:

const me = {name : "Justice"}, you = {name : "Your name"}, we = Object.assign({}, me, you);
we.name === "Your name"; //true
console.log(we); // {name : "Your name"}

Because you are the last object merged into we, the value of name in the you object — “Your name” — overrides the value of name in the me object. So “Your name” makes it into the we object… you win! :)

Thus, if you call setState() with an object multiple times — passing an object each time — React will merge. Or in other words, it will compose a new object out of the multiple objects we passed it. And if any of the objects contains the same key, the value of the key of the last object with same key is stored. Right?

Eso significa que, dada nuestra increaseScoreBy3función anterior, el resultado final de la función será solo 1 en lugar de 3, porque React no actualizó inmediatamente el estado en el orden que llamamos setState(). Pero primero, React compuso todos los objetos juntos, lo que resulta en esto:, {score : this.state.score + 1}luego solo hizo "set-state" una vez, con el objeto recién compuesto. Algo como esto: User.setState({score : this.state.score + 1}.

Para ser muy claro, pasar un objeto setState()no es el problema aquí. El problema real es pasar el objeto setState()cuando desea calcular el siguiente estado del estado anterior. Así que deja de hacer esto. ¡No es seguro!

Debido a que this.propsy this.statepueden actualizarse de forma asincrónica, no debe confiar en sus valores para calcular el siguiente estado.

Aquí hay un bolígrafo de Sophia Shoemaker que demuestra este problema. Juega con él y presta atención a las soluciones buenas y malas de este bolígrafo:

Conjunto funcional Estado al rescate

Si no ha pasado tiempo jugando con el lápiz anterior, le recomiendo encarecidamente que lo haga, ya que le ayudará a comprender el concepto central de esta publicación.

Mientras jugaba con el lápiz de arriba, sin duda vio que setState funcional solucionó nuestro problema. ¿Pero cómo, exactamente?

Consultemos la Oprah de React - Dan.

Note la respuesta que dio. Cuando haces funcional setState…

Las actualizaciones se pondrán en cola y luego se ejecutarán en el orden en que fueron llamadas.

So, when React encounters “multiple functional setState() calls” , instead of merging objects together, (of course there are no objects to merge) React queues the functions “in the order they were called.”

After that, React goes on updating the state by calling each functions in the “queue”, passing them the previous state — that is, the state as it was before the first functional setState() call (if it’s the first functional setState() currently executing) or the state with the latest update from the previous functional setState() call in the queue.

Again, I think seeing some code would be great. This time though, we’re gonna fake everything. Know that this is not the real thing, but is instead just here to give you an idea of what React is doing.

Also, to make it less verbose, we’ll use ES6. You can always write the ES5 version later if you want.

First, let’s create a component class. Then, inside it, we’ll create a fake setState() method. Also, our component would have a increaseScoreBy3()method, which will do a multiple functional setState. Finally, we’ll instantiate the class, just as React would do.

class User{ state = {score : 0};
 //let's fake setState setState(state, callback) { this.state = Object.assign({}, this.state, state); if (callback) callback(); }
 // multiple functional setState call increaseScoreBy3 () { this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ) }}
const Justice = new User();

Note that setState also accepts an optional second parameter — a callback function. If it’s present React calls it after updating the state.

Now when a user triggers increaseScoreBy3(), React queues up the multiple functional setState. We won’t fake that logic here, as our focus is on what actually makes functional setState safe. But you can think of the result of that “queuing” process to be an array of functions, like this:

const updateQueue = [ (state) => ({score : state.score + 1}), (state) => ({score : state.score + 1}), (state) => ({score : state.score + 1})];

Finally, let’s fake the updating process:

// recursively update state in the orderfunction updateState(component, updateQueue) { if (updateQueue.length === 1) { return component.setState(updateQueue[0](component.state)); }
return component.setState( updateQueue[0](component.state), () => updateState( component, updateQueue.slice(1)) );}
updateState(Justice, updateQueue);

True, this is not as so sexy a code. I trust you could do better. But the key focus here is that every time React executes the functions from your functional setState, React updates your state by passing it a fresh copy of the updated state. That makes it possible for functional setState to set state based on the previous state.

Here I made a bin with the complete code. Tinker around it (possibly make it look sexier), just to get more sense of it.

FunctionalSetStateInAction

A Play with the code in this bin will be fun. Remember! we’re just faking React to get the idea...jsbin.com

Play with it to grasp it fully. When you come back we’re gonna see what makes functional setState truly golden.

The best-kept React secret

So far, we’ve deeply explored why it’s safe to do multiple functional setStates in React. But we haven’t actually fulfilled the complete definition of functional setState: “Declare state changes separately from the component classes.”

Over the years, the logic of setting-state — that is, the functions or objects we pass to setState() — have always lived inside the component classes. This is more imperative than declarative.

Well today, I present you with newly unearthed treasure — the best-kept React secret:

Thanks to Dan Abramov!

That is the power of functional setState. Declare your state update logic outside your component class. Then call it inside your component class.

// outside your component classfunction increaseScore (state, props) { return {score : state.score + 1}}
class User{ ...
// inside your component class handleIncreaseScore () { this.setState( increaseScore) }
 ...}

This is declarative! Your component class no longer cares how the state updates. It simply declares the type of update it desires.

To deeply appreciate this, think about those complex components that would usually have many state slices, updating each slice on different actions. And sometimes, each update function would require many lines of code. All of this logic would live inside your component. But not anymore!

Also, if you’re like me, I like keeping every module as short as possible, but now you feel like your module is getting too long. Now you have the power to extract all your state change logic to a different module, then import and use it in your component.

import {increaseScore} from "../stateChanges";
class User{ ...
 // inside your component class handleIncreaseScore () { this.setState( increaseScore) }
 ...}

Now you can even reuse the increaseScore function in a different component. Just import it.

What else can you do with functional setState?

Make testing easy!

You can also pass extra arguments to calculate the next state (this one blew my mind… #funfunFunction).

Expect even more in…

The Future of React

For years now, the react team has been experimenting with how to best implement stateful functions.

Functional setState seems to be just the right answer to that (probably).

Hey, Dan! Any last words?

If you’ve made it this far, you’re probably as excited as I am. Start experimenting with this functional setStatetoday!

If you feel like I’ve done any nice job, or that others deserve a chance to see this, kindly click on the green heart below to help spread a better understanding of React in our community.

If you have a question that hasn’t been answered or you don’t agree with some of the points here feel free to drop in comments here or via Twitter.

Happy Coding!