cover

Flujo de datos unidireccional (one-way data flow)

author photo

Héctorbliss

@hectorbliss


robot logo saying hello

No te quedes atrás: Actualízate

Suscríbete para recibir información sobre nuevos frameworks, updates, eventos, tips, hacks y más.

¿Qué es One-Way data flow en React?

Nop, no tiene que ver con el flow bellacoso que te hace “perrar” hasta el piso. 👯‍♀️🪩 No. 👨🏼‍🎤

bellacoso

Es más una forma de diseñar jerarquías de nodos o árboles de componentes en React y sobre la comunicación entre ellos, se trata más de limitar el flujo y la mutación de los datos que vienen del servidor en un solo nivel, el del padre. 👨🏻

¿Se es inclusivo si se dijese: “El estado debe vivir en el componente madre” 🤔

Hace un par de años, yo no terminaba de entender este concepto, era tan obvio que no me hacía sentido, no lograba entender por qué era importante. Claro, le pasamos props a los componentes hijos, ahí está, eso es el one-way data flow ¿no?

Pero ahora, después de mucho código de principiante y de escribir muchísimos componentes que nomás no hayan por donde comunicarse con su padre (como tu amiga, que se quedó esperando que su papá volviera de comprar cigarros 🚬).

Hoy en día mis componentes favoritos son los componentes “dumb” o “presentacionalesque no tienen por qué estar al pendiente de nada, solo de mostrar sus props (datos) más frescos. Deben ser como televisiones que reciben los datos de la señal, los interpretan y los muestran, nunca los modifican. 📺 👀 Fíjate, las TV también tienen un estado interno y local (el canal actual y la cantidad de canales) y eso no lo ponen en un estado global en Redux ¿verdad juanito? 😫

En esta entrada, me dispongo a explicarte por qué pienso que el OWDF (One-way data flow) se trata más de una forma de pensar y un patrón de diseño, que solo pasar props.

Justo por eso hoy en día la documentación de React toca este tema con el titulo: “pensando en React”. 🧠

Vamos a ver si te puedo ayudar a visualizar mejor. 🤓 🖼️

Pensando en React

Imaginemos un árbol de componentes de solo 2 niveles. Un padre <Container> y tres pequeños y saludables hijitos: <Title>, <Avatar> y <Subscribe>. <Container> y su familia son muy felices aunque él es un padre estricto. <Container> sabe muy bien cómo utilizar fetch para adquirir datos desde el servidor, es un componente muy bien conectado y no le representa esfuerzo conseguir recursos para su familia, así ninguno de sus hijos tiene que trabajar para conseguir los recursos que necesitan. <Avatar /> es creativa y le encantan las imágenes, pero aún es muy pequeña. Mientras que <Title> es el mayor de todos, siempre preocupado por quedar bien. <Subscribe> es el más inquieto de los tres, es muy extrovertido y le gusta conocer personas nuevas, pero regularmente se mete en problemas. Todos ellos conforman este ejemplo.

IMG_3176.heic

El último problema en el que <Subscribe> se metió, es que descubrió sus props. Ya le brotó pelo en todo el cuerpo y también descubrió un <input> que es parte de su JSX. Lo anduvo explorando a pesar de que su papá le dijo que jamás se tocará ahí. Pero buscándole, <Subscribe> encontró algunos props que puede utilizar: , value y onChange, pero no sabe cómo usarlos, así que se puso a buscar por internet.

<Subscribe> buscó bastante, y encontró mucho ruido sobre estados globales: Redux, Xstate, Zustand, Jotai y también encontró mucha paja sobre Context. Pero toda esa info le parece abrumadora y prefiere simplemente implementar un estado local y jugar con el onChange, así que decide preguntarle a su amigo alcohólico: useState(). Este amigo, es muy amable, pues le proporciona lo que necesita…

<Subscribe> se ve algo así ahora:

export const Subscribe = () => { // estado local: const [value, set] = useState(""); // manejador del estado: const handleChange = (e) => set(e.currentTarget.value); // ChangeEvent<HTMLInputElement> // uso del estado: return <input onChange={handleChange} value={value} />; };

Ok, <Subscribe> tiene un estado donde almacenar el texto que el usuario escribe, muy bien. ¿Cómo se los mandamos al servidor?, le pregunta a su amigo sin recibir respuesta.

Como <Subscribe> tampoco puede preguntar a sus hermanos, pues en React los nodos hermanos no pueden comunicarse. Igual que <Subscribe> sus hermanos solo pueden recibir información de sus padres, así que, decide entonces, preguntar a su papá. 👴🏼

<Container />, padre de <Subscribe>, no tiene problema en crearse un método sendSubscriber() y comunicarse con el servidor. El problema se encuentra en que <Container /> no tiene acceso al estado local de su hijo <Subscriber />. Entonces, ¿cómo hacemos para que <Subscriber /> pueda entregar el correo electrónico del usuario al componente padre <Container />? ¿Cómo chingados pasamos el estado?, se preguntó <Subscriber> y echó a llorar.

Intentos chafas

Muy a menudo me toca leer código al que yo llamo: “chafa” o que es muy susceptible a fallos, difícil de mantener y complejo que dificulta el razonamiento sobre el mismo. Este tipo de código representa nuestra forma de pensar 💭 sin, el one-way data flow. 🍝

Este sería un ejemplo de código chafa:

export const Subscribe = ({ onChange }) => { const [state, set] = useState(""); // esto ya no es necesario? const handleChange = (e) => { set(e.currentTarget.value); // esto es onChange?.(e.currentTarget.value); // muy similar 😒 }; return <input onChange={handleChange} value={state} />; };

Parece inofensivo y muy común ¿verdad? Pero hemos agregado un prop a nuestro componente, también hemos colocado una llamada opcional a onChange. Recibiríamos el valor en el padre, con este pequeño cambio ahora podemos comunicar al padre, pero también estamos repitiendo algo de código y hasta se siente como que aquí hay estados de sobra… ¿Apoco no? 👎

Lo peor es que, con todo esto que agregamos, ahora el padre necesita un estado también. 😅

export const Container = () => { const [inputValue, setInputValue] = useState(""); return <Subscribe onChange={(value) => setInputValue(value)} />; };

¡Pero espérate! Que tengo otros ejemplos de código chafa. Como este que agrega asincronía y efectos secundarios a nuestro ejemplo, para que la cabeza duela en serio. 🤕

export const Subscribe = ({ onChange, value }) => { // estado local: const [state, set] = useState(value); // esto es mejor con defaultValues 🚫 // manejador del estado: const handleChange = (e) => { set(e.currentTarget.value); }; useEffect(() => { onChange?.(state); }, [state, onChange]); // esto es peligroso ⚠️ // uso del estado: return <input onChange={handleChange} value={value || state} />; };

Seguro que has visto código así; no lo haga compa. 👩🏾‍🌾

Esto se puede resolver de una manera simple, volvamos a nuestra historia y veamos cómo.

Salvemos el día con el One-way data flow

Después de presentar todas estas opciones, <Subscribe> guardó silencio y permitió que su padre, que ya era un senior, le explicara qué es el One-way data flow:

En todos los casos, se trata de subir el estado un nivel, al padre y también de que solo el padre posea los estados necesarios y lo más importante: que solo el padre los pueda cambiar. 👨🏻‍🔧

Sus hijos, solo recibirán props y composición. ✅

👀 No me refiero a estados locales de componente, como booleanos para tabs o índices, o switches, me refiero a estados que almacenan datos relacionados al usuario, como su correo. 📦 Aprende más sobre composición aquí.

export const Container = () => { const [email, setEmail] = useState(""); // El padre es el dueño del único estado const sendSubscriberToServer = () => { fetch("/subs", { body: { email }, method: "post" }); // Simplificado 👶🏻 }; return <Subscribe value={email} onChange={(value) => setEmail(value)} />; }; // Subscribe.tsx export const Subscribe = ({ onChange, value }) => { // Input controlado, La mutación sucede en niveles superiores return <input onChange={onChange} value={value} />; };

Su padre le explicó de dos maneras, primero la más común y que la mayoría de los developers React profesionales usa: escuchando cada uno de los cambios en el input. A esto se le conoce como componentes controlados. Ahora que el estado le pertenece al padre y solo al padre, ninguno de sus hijos es responsable de conseguir o mantener recursos, solo el padre. 🧔🏻‍♂️💵

Así que, si alguno de sus hijos tiene que reaccionar al usuario, como con un onChange, lo único que necesita saber es cuál es el prop a usar. En este caso cualquier función dentro de onChange.

<Subscribe> puede enviar el email a su padre con su prop onChange en cada momento. No necesita saber nada más, nada sobre la vida mafiosa de su padre, 🔫 ni enterarse de que los recursos que consigue están bañados en sangre… 🩸🧧

👀 Este era el típico demo de React que veías en todas partes antes del 2020 (onChange). Siempre me pareció mucho trabajo 🤖 para formularios grandes. Te voy a enseñar una manera mejor. 😉

¿Cómo lo haríamos con flujo de datos unidireccional?

La segunda forma que le mostró su padre, es la favorita del autor. ✍🏼

Se trata de pensar menos en JS y más en HTML. 🤯

export const Container = () => { const inputRef = useRef(); const sendSubscriberToServer = (event) => { // ... fetch('/subs', {body:{inputRef.current.value}, method:'post'}); } return ( <Subscribe ref={ref} name="email" />); } // Subscribe.tsx export const Subscribe = forwardRef(({...props}, ref)=>{ return <input {...props} ref={ref} /> })

Lo primero que debes observar aquí, es que <Subscribe> ahora es display, nada de manejar su propio estado, ni siquiera tiene que usar props, solo pasar una referencia (useRef). 🤯

Con esto, puedes armar todo el <form> como un display y tener todos los datos en el ref.

¡Pero claro, esto también se puede hacer de manera más tradicional! 👘

Si no te gustan los forwardRef, hagámoslo interceptando el onSubmit por ejemplo:

export const Container = () => { const sendSubscriberToServer = (event) => { event.preventDefault(); // evitamos el *refresh* de la página // ... fetch("/subs", { body: { email: event.currentTarget.name.value }, method: "post", }); }; return ( <form onSubmit={sendSubscriberToServer}> <Subscribe /> </form> ); }; // Subscribe.tsx export const Subscribe = () => <input name="email" type="email" />; // esto es *presentational* incluso siendo un input

Aquí estamos echando mando de nuestra vieja amiga, la etiqueta <form> que nos ayudará a sacarle el valor a <Subscribe> gracias al evento onSubmit. 🫧

Podríamos mudar la etiqueta <form> dentro de <Subscribe>, pero así es mejor, podemos componer el formulario con más inputs reutilizables, dumb y presentational o de display (Display Components).

👀 Yo prefiero llamarle Componentes de display o solo “display”. Y a veces erróneamente les llamo dummys. 😅

Todo esto se complementa de forma maravillosa, cuando agregas composición. Manteniendo el manejo del estado en un solo nivel y utilizando componentes de display, componentes que solo representan sus props. Es como meter tu mano en un guante de lana suave y que se ajusta perfectamente a la forma de tus nudillos, ahora te pones el otro...

Es hora del último ejemplo y ver si todo esto ahora te hace mirar un poco diferente: 👽

export default function OtroEjemplo() { const { members, user, mutateMember } = useQueryService(); // cualquier query lib // Es bonito utilizar los datos de tu modelo directamente (Partial<TuModelo>) const reducerToHandleAllActions = (action) => { // switch... mutateMember(action.payload) }; return ( <Layout user={user}> <Table theme={user.theme} items={members}> {(member) => ( <Row member={member} highlight={member.isVip} actions={ <Actions onAction={reducerToHandleAllActions} ability={user.ability} extraActions={[ <TestingActions onAction={reducerToHandleAllActions} member={member} />, ]} render={(allActions) => ( <BasicActions extraActions={allActions} /> )} /> } /> )} </Table> <PersonalOffer membershipType={user.membership?.type} /> <Alerts isEveryMemberConfirmed={members.every((m) => m.confirmed)} /> </Layout> ); }

Mi intención no es confundirte con este código, si no que puedas ver este grupo de componentes ya no solo como JSX y props. Si no, como componentes que se emplean de forma eficiente, porque utilizan un flujo de datos de una sola dirección explotando la composición para este fin. Pero lo más importante y lo que quiero que te lleves, es que:

solo su padre maneja el estado de todos los componentes en un solo lugar. ✅ 😎

¿Maravilloso no? 🤓

Ahora ya sabes a qué nos referimos cuando hablamos del One-way data flow.

Tiene más que ver con mantener el estado (y sus mutaciones) en un solo nivel. ✅

Estos ejemplos están extremadamente simplificados a propósito y con fines educativos. Estoy dejando por fuera muchos más conceptos, como cuando se trata de una tabla CRUD, por ejemplo. ¿Cómo sería esa comunicación? si este contenido te parece útil, no dejes de compartirlo con tus amigos. Así me motivo para crear más contenido relacionado y complementar este.

Nos leemos en el siguiente. Abrazo. Bliss. 🤓

Enlaces relacionados

Aprende más sobre composición

Descubre qué es HTMX y el Hyper-Media System

Pensando en React

banner

¿Quieres mantenerte al día sobre los próximos cursos y eventos?

Suscríbete a nuestro newsletter

Jamás te enviaremos spam, nunca compartiremos tus datos y puedes cancelar tu suscripción en cualquier momento 😉

robot logo saying hello
facebook icontwitter iconlinkedin iconinstagram iconyoutube icon

© 2016 - 2023 Fixtergeek