El contexto en XState se puede considerar como una extensión significativa de la máquina de estados. En los estados previos, quizá recuerdes que dentro de cada estado había una propiedad llamada context, que en su momento era un objeto vacío. Este contexto es fundamental, ya que se utiliza para almacenar y manipular datos, lo que en nuestro caso puede incluir acciones como registrar el país seleccionado o los nombres de los pasajeros.
¿Cómo se establece un contexto inicial?
Para definir un contexto inicial en una máquina de estados, dentro del objeto principal de la máquina, se debe crear la propiedad context. Vamos a ver un ejemplo de cómo lograrlo:
const machineDefinition ={context:{passengers:[],// Almacena los nombres de los pasajerosselectedCountry:''// Guarda el país seleccionado}};
¿Cómo se lee el contexto?
Lectura del contexto en XState se puede hacer fácilmente imprimiendo las propiedades state.value y state.context. Esto te permitirá tener una visión clara del estado actual de la máquina de estados.
console.log(state.context);
¿Cómo actualizar el contexto?
Modificar el contexto implica varios pasos. Veamos un ejemplo donde actualizamos el país seleccionado y los pasajeros mediante eventos:
Actualizar el país seleccionado
Primero, se envía un evento con el nuevo país seleccionado.
Se utiliza una función assign para actualizar el selectedCountry en el contexto.
¿Cómo se captura la data ingresada en el contexto?
Al interactuar con la interfaz de usuario, por ejemplo en formularios, necesitamos capturar los datos y enviarlos al contexto:
Cuando se selecciona un país, se ejecuta un evento continue que actualiza el selectedCountry.
Para los pasajeros, el evento add es crucial para que la nueva información se agregue al array existente en el contexto.
// Captura del add eventsend('ADD',{newPassenger: value });
¿Cómo se visualizan los cambios en el contexto?
Para verificar que tu contexto ha sido actualizado correctamente, podrías querer imprimir o mostrar en algún componente el contenido actual de contexto, especialmente en vistas donde la interacción del usuario puede modificarlo.
Desafíos para Inspirarte
¡Te retamos! Un ejercicio propuestas es que cuando un usuario cancele una acción, el contexto debería restablecerse a su estado inicial. Además, cuando se está en la vista de pasajeros, muestra el listado de nombres antes del campo de entrada. Intenta implementar esta lógica y observa cómo mejora la dinámica de tu aplicación. ¡No olvides que el aprendizaje y la práctica son clave para mejorar tus habilidades en XState! Sigue explorando y experimentando modos para optimizar tu código y el manejo de estados en tus aplicaciones.
const bookingMachine =createMachine({...,states:{...,search:{on:{CONTINUE:{target:"passengers",// 1️⃣ Primera Forma: Pasando un objeto a la función assign,// En el mismo se debe especificar las propiedades a modificaractions:assign({selectedCountry:(context, event)=> event.selectedCountry,}),},// 2️⃣ Segunda forma: Llamando a una accción definida en el machineCANCEL:{target:"initial",actions:"cleanContext"},},},passengers:{on:{ADD:{target:"passengers",// 3️⃣ Tercera forma: Modificando directamente el objeto de contexto// pasando una función al objeto assignactions:assign((context, event)=> context.passengers.push(event.newPassenger)),},},},},},{actions:{cleanContext:assign({selectedCountry:"",passengers:[],}),},});
Para asignar correctamente el contexto en la versión 5 de XState:
Como solución al reto creo que lo mas eficiente seri únicamente que cuando se regrese al estado inicial la información se resetee
Únicamente en el bookingMachine.Js
State Machines sería como una alternativa a redux?
No necesariamente, xstate puede trabajar con Redux.
Yo veo a las maquinas de estado como una alternativa al la manera en que desarrollamos interfaces. Es un metodo mucho mas declarativo.
Aqui dejo un video de David Khourshid donde explica mejor para que sirven las maquinas de estados en el desarrollo de interfaces.
https://www.youtube.com/watch?v=RqTxtOXcv8Y&t=1207s
Muy buena pregunta, estaba con la misma duda.
Hay un error con el video :(
Si estás viendo este vídeo muchas cosas han cambiado por lo que te recomiendo que tengas la documentación a la mano, en el assign sería
assign({
value:({event})=>event.vale
})
pero bueno así también puedes practicar como ir leyendo la documentación jajja
importReact,{ useState }from'react';importbookingMachinefrom'../Machines/bookingMachine';import'./Passengers.css';exportconstPassengers=({ state, send })=>{const[value, changeValue]=useState('');constonChangeInput=(e)=>{changeValue(e.target.value);}constgoToTicket=()=>{send('DONE')}constsubmit=(e)=>{ e.preventDefault();send('ADD',{newPassenger: value })changeValue('');}return(<form onSubmit={submit} className='Passengers'><p className='Passengers-title title'>Agrega a las personas que van a volar ✈️</p>{bookingMachine.context.passengers.map((passenger,index)=>(<p key={`${passenger}${index}`}>{passenger}</p>))}<input
id="name" name="name" type="text" placeholder='Escribe el nombre completo' required
value={value} onChange={onChangeInput}/><div className='Passengers-buttons'><button
className='Passengers-add button-secondary' type="submit">AgregarPasajero</button><button
className='Passenger-pay button' type="button" onClick={goToTicket}>Ver mi ticket
</button></div></form>);}
# 🧠 XState: Manejo de Contexto
Mi proyecto en github
El **contexto** es la memoria de nuestra máquina de estados. A diferencia del estado (que indica *dónde* estamos), el contexto almacena *datos* (quiénes somos, qué elegimos).
### 🚀 Definición Inicial
Se define dentro del objeto principal de la máquina. Es el "estado de los datos" al arrancar.
- **Ejemplo:**
const bookingMachine =createMachine({context:{passengers:[],// Lista vacíaselectedCountry:''// Sin selección},// ... estados});
### 📖 Lectura del Contexto
Para saber qué hay guardado, accedemos a la propiedad .context del estado actual.
- **Tip:** console.log(state.context); es tu mejor amigo para depurar.
### 🔄 Actualización con assign
El contexto es **inmutable**. No se cambia directamente, se usa la función assign de XState para generar un nuevo objeto de contexto.
importReact,{ useState }from'react';import'./Passengers.css';exportconstPassengers=({ state, send })=>{const[value, changeValue]=useState('');const onChangeInput =(e)=>{changeValue(e.target.value);}const goToTicket =()=>{send({type:'DONE'});}const submit =(e)=>{ e.preventDefault();send({type:'ADD',newPassenger: value });changeValue('');}return(<form onSubmit={submit} className='Passengers'><p>Agrega a las personas que van a volar ✈️</p><input
id="name" name="name" type="text" placeholder='Escribe el nombre completo' required
value={value} onChange={onChangeInput}/><div><button
className='Passengers-add button-secondary' type="submit">AgregarPasajero</button><button
className='Passenger-pay button' type="button" onClick={goToTicket}>Ver mi ticket
</button></div></form>);};
Nota si estas usando xState v5 cambia
actions:assign({selectedCountry:(ctx, event)=> event.selectedCountry,// 👈 tomamos del evento}),// cambia en v5actions:assign({selectedCountry:({ event })=> event.selectedCountry,}),
Mi solucion para el reto es resetear el contexto al ingresar al initial
Para actualizar los pasajeros tenemos una podemos mandar un objeto mediante el send desde Search y se recibirá en la máquina dentro de event cuando hagamos las funciones.
En la máquina, mediante un assign le mandamos un objeto con las propiedades del contexto que queremos cambiar. Esta se actualiza utilizando una función, en la que recibimos el selectedCountry en event.
En Passengers , creamos una variable passengersList para guardar el state.context.passengers . Este lo renderizamos con un map .
importReact,{ useState }from"react";import"./Passengers.css";exportconstPassengers=({ state, send })=>{const[value, changeValue]=useState("");const passengersList = state.context.passengers;constonChangeInput=(e)=>{changeValue(e.target.value);};constgoToTicket=()=>{send("DONE");};constsubmit=(e)=>{ e.preventDefault();send("ADD",{newPassenger: value });changeValue("");};return(<form onSubmit={submit} className="Passengers"><p className="Passengers-title title">Agrega a las personas que van a volar ✈️
</p>{passengersList.map((passenger, index)=>(<p key={index}>{passenger}</p>))}<input
id="name" name="name" type="text" placeholder="Escribe el nombre completo" required
value={value} onChange={onChangeInput}/>...</form>);};
Para el reset, podemos decir que cada vez que entremos a initial vamos a resetear las propiedades del contexto con un entry.
Tengo una consulta, en el flujo final donde renderizo el nombre del pasajero, tambien, me gustaria renderizar el nombre del pais seleccionado, a continuacion adjunto mi codigo:
Utilizo: state.context.selectedCountry Pero no logro que el pais sea renderizado. Gracias
Tienes que revisar con un console.log, a ver si estan llegado datos en ese state.context.selectedCountry. Puede de que el context para contry no llegue a esa vista
Mi solución fue simple, solo agregar el evento Entry en el estado initial para restablecer los valores siempre que estemos en ese estado
passengers:{on:{DONE:"tickets",ADD:{target:"passengers",actions:assign({passengerNames:({context, event})=>[...context.passengerNames, event.newPassenger]})},``` Asi quedaria mi solucion sin usar push porque push cambia la array original y el contexto es inmutable.
Dejo código de la clase con TypeScript y Vite
Definimos contexto
Leer contexto
Modificar contextoPrimero definimos evento y enviamos la información
Finalmente se define la modificacion
Utilizando assing de xstate
o de forma directa
Alternativa a listar, lo puse como otros inputs
En los estados de search y Passenger agregué lo siguientes en cada evento CANCEL:
En el componente Passenger agregué esta linea dentro del formulario y encima del input. Recojo los datos del contexto almacenados en passenger y con un .map agrego etiquetas <p> con cada nombre de pasajeros que esté en el array.
exportconstPassengers=({ state, send })=>{return(<form onSubmit={submit} className='Passengers'><p className='Passengers-title title'>Agrega a las personas que van a volar ✈️</p>{state.context.passengers.length>0&& state.context.passengers.map((passenger)=>(<p>{passenger}</p>))}...</form>);};
Cuando se hace una función en un arreglo para renderizar un elemento de JSX, es importante asignar al elemento un key para ayudar a react a identificar cuales elementos son añadidos, cambiados o eliminados.