No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Contexto

10/15
Recursos

¿Qué es el contexto en XState y cómo se define?

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 pasajeros
    selectedCountry: '' // 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:

  1. 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.
actions: {
  assignCountry: assign({
    selectedCountry: (context, event) => event.selectedCountry
  })
}
  1. Agregar pasajeros
    • Para esto, se utiliza un nuevo evento llamado add.
    • El enfoque consiste en que al ejecutar el evento, se quede en el mismo estado mientras se añade un nuevo pasajero al contexto.
actions: {
  addPassenger: assign({
    passengers: (context, event) => [...context.passengers, event.newPassenger]
  })
}

Manejo de la entrada y persistencia de datos

¿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 event
send('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.

Aportes 16

Preguntas 2

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

🤿 Contexto

Ideas/conceptos claves

Contexto

Es una parte extendida de la máquina de estados

Apuntes

  • El contexto en general nos ayudará a guardar datos

Establecer un contexto inicial

import { createMachine, assign } from "xstate";

const bookingMachine = createMachine({
		...,
    context: {
			// Objeto de contexto
      passengers: [],
      selectedCountry: "",
    },
    states: {...}
	}
);

Leer Contexto

import { useMachine } from "@xstate/react";

export const BaseLayout = () => {
  const [state, send] = useMachine(bookingMachine);

  console.log("nuestro contexto", state.context);

  return ( ... );
};

Actualizar contexto

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 modificar
            actions: assign({
              selectedCountry: (context, event) => event.selectedCountry,
            }),
          },
          // 2️⃣ Segunda forma: Llamando a una accción definida en el machine
          CANCEL: { target: "initial", actions: "cleanContext" },
        },
      },
      passengers: {
        on: {
          ADD: {
            target: "passengers",
            // 3️⃣ Tercera forma: Modificando directamente el objeto de contexto
            // pasando una función al objeto assign
            actions: 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: ```js actions: assign({ selectedCountry: ({ event }) => event.selectedCountry, }) ```

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

 initial: {
        entry: assign((context, event) => {
            context.passengers = []
            context.selectedCountry = ""

        }),
        on: {
          START: {
            target: "search",
          },
        },
      },
![](https://static.platzi.com/media/user_upload/image-5e08e526-4851-474a-a683-246aabb718d2.jpg)Hay un error con el video :(

mi aporte al reto:
src/Machines/bookingMachine.js:

const bookingMachine = createMachine({
  id: ".... ",
  initial: "....",
  context: {
    passengers: [],
    selectedCountry: '',
  },
  states: {
    initial: {
      ...
    },
    search: {
      ...
    },
    tickets: {
      ...
    },
    passengers: {
      on: {
        DONE: "tickets",
        CANCEL: {
          target: 'initial',
          actions: assign({
            selectedCountry: (context, event) => event.selectedCountry,
            passengers: (context, event) => event.passengers
          })
        },
        ADD: {
          ...
        }
      },
    },
  },
}, 
);

src/Components/Nav.js:

export const Nav = ({ state, send }) => {
  
  const goToWelcome = ()=> {
    send('CANCEL', { selectedCountry: '', passengers: [] })
  }

  return (
    <nav className='Nav'>
      ....
        {!state.matches('initial') && 
          <button onClick={goToWelcome} className='Nav-cancel button-secondary'>Cancelar</button>
        }
    </nav>
  );
}; 

src/Components/Passengers.js:

import bookingMachine from '../Machines/bookingMachine';

export const Passengers = ({ state, send }) => {

  return (
    <form onSubmit={submit} className='Passengers'>
      ....
      {bookingMachine.context.passengers.map((passenger,index) => (
        <p key={`${passenger}${index}`}>{passenger}</p>
      ))}
      ...
    </form>
  );
};

.
.

codigo completo:
src/Machines/bookingMachine.js:

import { createMachine, assign } from "xstate";

const bookingMachine = createMachine({
  id: "buy plane tickets",
  initial: "initial",
  context: {
    passengers: [],
    selectedCountry: '',
  },
  states: {
    initial: {
      on: {
        START: { 
          target: 'search',
        },
      },
    },
    search: {
      on: {
        CONTINUE: {
          target: 'passengers',
          actions: assign({
            selectedCountry: (context, event) => event.selectedCountry
          })
        },
        CANCEL: 'initial',
      },
    },
    tickets: {
      on: {
        FINISH: "initial",
      },
    },
    passengers: {
      on: {
        DONE: "tickets",
        CANCEL: {
          target: 'initial',
          actions: assign({
            selectedCountry: (context, event) => event.selectedCountry,
            passengers: (context, event) => event.passengers
          })
        },
        ADD: {
          target: 'passengers',
          actions: assign(
            (context, event)=> context.passengers.push(event.newPassenger) 
          )
        }
      },
    },
  },
}, 
);

export default bookingMachine;

src/Components/Nav.js:

import React from 'react';
import './Nav.css';

export const Nav = ({ state, send }) => {
  
  const goToWelcome = ()=> {
    send('CANCEL', { selectedCountry: '', passengers: [] })
  }

  return (
    <nav className='Nav'>
      <h1 className='Nav-logo'>Book a fly ✈</h1>
        {!state.matches('initial') && 
          <button onClick={goToWelcome} className='Nav-cancel button-secondary'>Cancelar</button>
        }
    </nav>
  );
}; 

src/Components/Passengers.js:

import React, { useState } from 'react';
import bookingMachine from '../Machines/bookingMachine';
import './Passengers.css';

export const Passengers = ({ state, send }) => {
  const [value, changeValue] = useState('');

  const onChangeInput = (e) => {
    changeValue(e.target.value);
  }

  const goToTicket = ()=> {
    send('DONE')
  }

  const submit = (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"
        >
          Agregar Pasajero
        </button>
        <button
          className='Passenger-pay button'
          type="button"
          onClick={goToTicket}
        >
          Ver mi ticket
        </button>
      </div>
    </form>
  );
}
Mi solución fue simple, solo agregar el evento Entry en el estado initial para restablecer los valores siempre que estemos en ese estado ```js initial: { entry: assign({ passengers: [], selectedCountry: '' }), on: { START: 'search' } }, ```initial: {            entry: assign({ passengers: \[], selectedCountry: '' }),            on: {                START: 'search'            }        },
Insertar passengers en xstate 5 ```js actions: assign( ({ context, event }) => { return { passengers: [...context.passengers, event.newPassenger] } } ) ```
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
Reto Completado ```js import React, { useContext } from "react"; import "./Tickets.css"; import { XStateContext } from "../XStateContext"; export const Tickets = () => { const { send, state } = useContext(XStateContext); const { passengers, selectedCountry } = state.context; const handleFinish = () => { send("FINISH"); }; return (

Gracias por volar con book a fly 💚

{selectedCountry}
{passengers.map((passenger) => { return

{passenger}

; })}
<button onClick={handleFinish} className="Tickets-finalizar button"> Finalizar </button>
); }; ``` ```js import { assign, createMachine } from "xstate"; const contextDefault = () => ({ passengers: [], selectedCountry: "", }) const bookingMachine = createMachine( { id: "buy plane tickets", initial: "initial", context: contextDefault(), states: { initial: { entry: "resetContext" , on: { START: { target: "search", actions: "printStart", }, }, }, search: { entry: "printEntry", exit: "printExit", on: { CONTINUE: { target: "passengers", actions: assign({ selectedCountry: (context, e) => e.selectedCountry, }), }, CANCEL: { target : "initial", actions: "resetContext" } }, }, tickets: { on: { FINISH: { target: "initial", actions: "resetContext" }, }, }, passengers: { on: { DONE: "tickets", CANCEL: { target: "initial", actions: "resetContext" }, ADD: { target: "passengers", actions: assign((context, e) => ({ passengers: [...context.passengers, e.newPassengers] })) } }, }, }, }, { actions: { printStart: () => console.log("Inicio"), printEntry: () => console.log("Entrada"), printExit: () => console.log("Salida"), resetContext: assign(() => contextDefault()) }, } ); export default bookingMachine; ```
```js 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 contexto
Primero 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:

y en el componente Nav agregué lo siguiente:

import React from 'react';
import '@styles/Nav.css';

export const Nav = ({ state, send }) => {

  const goToInitial = () => {  
    send("CANCEL", {newPassenger: [], selectedCountry: ""});
  }

  return (
    <nav className='Nav'>
      <h1 className='Nav-logo'>Book a fly ✈</h1>
      {
        !state.matches("initial") && 
        <button className='Nav-cancel button-secondary' onClick={goToInitial}>Cancelar</button>
      }
    </nav>
  );
}; 

En el reto de agregar los nombres de los pasajeros hice lo siguiente:

{state.context.passenger.map((user) => <p key={user}>{user}</p>)}

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.

Solución al reto 2:

StepsLayout.js

export const StepsLayout = ({ state, send }) => {
  const renderContent = () => {
    ...
    if(state.matches('passengers')) return <Passengers send={send} state={state} />;

    return null;
  };
}; 

Passengers.js

export const Passengers = ({ 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>
  );
};

En bookMachine - passengers

        CANCEL:{
          target: "inicial",
          actions: assign((context, event) => (context.passengers = [], context.selectedCountry = '')),
        },

Aunque no le convence mucho a Visual la coma del medio.

reto 1 y reto 2 cumplidos, el código quedo de la siguiente manera:

bookingMachine.js:

import { assign, createMachine } from "xstate";

const reset = assign({
  passengers: (_, event) => event.passengers = [],
  selectedCountry: (_, event) => event.selectedCountry = "",
});

const bookingMachine = createMachine({
  id: "buy plane tickets",
  initial: "initial",
  context: {...
},
  states: {...
},
    search: {
      on: {
        CONTINUE: {...
          },
        CANCEL: {
          target: "initial",
          actions: reset
        },
      },
    },
    passengers: {
      on: {
        DONE: "tickets",
        CANCEL: {
          target: "initial",
          actions: reset
        },
        BACK: "search",
        ADD: {...
          }
      },
    },
    tickets: {
      on: {
        FINISH: {
          target: "initial",
          actions: reset
        },
        CANCEL: {
          target: "initial",
          actions: reset
        },
        BACK: "passengers",
      },
    },
  },
} ...

Y el reto 2:
StepsLayout.js:

if(state.matches('passengers')) return <Passengers send={send} state={state} />;

Passengers.js:

return (
    <form onSubmit={submit} className='Passengers'>
      <p className='Passengers-title title'>Agrega a las personas que van a volar ✈️</p>
      <ul className='Passengers-list'>
        {state.context.passengers.map((passenger, index) => (
          <li key={index} className='Passengers-list_li'>
            - {passenger}
          </li>
        ))}
      </ul>
      <input 
        id="name" 
        name="name" 
        type="text" 
        placeholder='Escribe el nombre completo' 
        required 
        value={value} 
        onChange={onChangeInput}
      />

Passengers.css:

.Passengers-list {
  align-self: flex-start;
  list-style: none;
  padding: 0;
  text-align: start;
}

.Passengers-list_li {
  margin-bottom: 6px;
}