No tienes acceso a esta clase

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

Servicios

12/15
Recursos

Aportes 10

Preguntas 0

Ordenar por:

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

🤵‍♂️ Servicios

Ideas/conceptos claves

Servicio

Es una función que se invocan para realizar cierto procedimiento

send

Función para enviar eventos, para generar las transiciones

Apuntes

  • Dentro de los servicios existen diferentes categorías
    • Promesas ⇒ Se pueden utilizar para realizar request a API’s
    • Callbacks ⇒ Son servicios que se comunican entre el padre e hijo mediante un callback
    • Observables ⇒ Son servicios que mandan un array de eventos, dichos eventos se suelen tener un comportamiento similiar a un eventListener
    • Invocar otras máquinas ⇒ Es un tipo de servicio que hace posible que una máquina padre invoca múltiples máquinas hijas
      • Se crea un puente entre ambas máquinas, que permite que el padre mediante la función send envíe eventos a sus hijos y a la vez los hijos puedan enviar eventos a sus padres con la función sendParent

En la version 5 de XState el invoke quedaría se la siguiente forma:

⚠️ Importante (se debe importar fromPromises de xstate)

import { assign, createMachine, fromPromise } from "xstate";
      invoke: {
        id: "getCountries",
        src: fromPromise(() => fetchCountries()),
        onDone: {
          target: "success",
          actions: assign({ countries: ({ event }) => event.output }),
        },
        onError: {
          target: "failure",
          actions: assign({ error: "fallo el request" }),
        },
      },

Siento que la profe le faltó detalles por explicar. Aquí está la documentación https://xstate.js.org/docs/guides/communication.html#the-invoke-property

Url de la api
https://restcountries.com/v3.1/region/ame
.
.
src/Utils/api.js:

export const fetchCountries = () =>
  fetch('https://restcountries.com/v3.1/region/ame')
    .then((response) => response.json())

src/Machines/bookingMachine.js:

import { createMachine, assign } from "xstate";
import { fetchCountries } from '../Utils/api'

const fillCountries = {
  initial: 'loading',
  states: {
    loading: {
      invoke: {
        id: 'getCountries',
        src: ()=> fetchCountries,
        onDone: {
          target: 'success',
          actions: assign({
            countries: (context,event)=> event.data 
          })
        },
        onError: {
          target: 'failure',
          actions: assign({
            error: 'Fallo el request'
          })
        }
      }
    },
    success: {},
    failure: {
      on: {
        RETRY: { target: 'loading'}
      }
    }
  }
}

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

export default bookingMachine;

src/Containers/StepsLayout.js:

import React from 'react';
import { Welcome } from '../Components/Welcome';
import { Search } from '../Components/Search';
import { Passengers } from '../Components/Passengers';
import { Tickets } from '../Components/Tickets';
import './StepsLayout.css';

export const StepsLayout = ({ state, send }) => {
  const renderContent = () => {
    if (state.matches('initial')) return <Welcome send={send} /> 
    if (state.matches('search')) return <Search state={state} send={send} /> 
    if (state.matches('tickets')) return <Tickets send={send} /> 
    if (state.matches('passengers')) return <Passengers state={state} send={send} /> 
    return null 
  };

  return (
    <div className='StepsLayout'>
      {renderContent()}
    </div>
  );
}; 

src/Components/Search.js:

import React, { useState } from 'react';
import './Search.css';

export const Search = ({ state, send }) => {
  const [flight, setFlight] = useState('');

  const goToPassengers = ()=> {
    send('CONTINUE', { selectedCountry: flight})
  }

  const handleSelectChange = (event) => {
    setFlight(event.target.value);
  };

  const options = state.context.countries

  return (
    <div className='Search'>
      <p className='Search-title title'>Busca tu destino</p>
      <select id="country" className='Search-select' value={flight} onChange={handleSelectChange}>
        <option value="" disabled defaultValue>Escoge un país</option>
        {options.map((option,index) => <option value={option.name.common} key={option.name.common}>{option.name.common}</option>)}
      </select>
      <button onClick={goToPassengers} disabled={flight === ''} className='Search-continue button'>Continuar</button>
    </div>
  );
}; 

src/Components/Nav.js:

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

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

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

Si tienes este error en la consola, es porque al momento de hacer el map, el array de options no tiene ningun valor.

caught TypeError: Cannot read properties of undefined (reading 'map') 

Entonces no puedes mapear algo undefined, entonces lo que debe de hacer para contrasrestar ese error es colocar el signo de pregunta “?” antes del map, de la siguiente forma, este signo hace que asegure que en este caso el array traiga valor antes de mapearlo.

{options?.map((option) => (
          <option value={option.name.common} key={option.name.common}>
            {option.name.common}
          </option>
        ))}
En este caso para llamar a fetchCountries con xState v5 para invocar a fetchCountries he tenido que importar 'fromPromise' de xstate, según aparece en <https://stately.ai/docs/invoke#invoking-promises> el import quedaría ```js import { createMachine, assign, fromPromise } from "xstate"; ```y la llamada en el src ```js src: fromPromise(() => fetchCountries()), ```para asignar el listado en el onDone lo hice así: ```js onDone: { target: 'success', actions: assign({ countries: ({_,event})=> event.output }) }, ```

Dejo el código de la clase con Vite + TypeScript

api.ts

export const fetchCountries = async () => {
	const response = await fetch('https://restcountries.com/v3.1/region/ame');

	const data = await response.json();

	return data;
};
![Captura de pantalla 2023-08-30 225439.png](https://static.platzi.com/media/user_upload/Captura%20de%20pantalla%202023-08-30%20225439-81d0ae5e-297c-4586-9764-f2f821551f4d.jpg)

archivo bookingMachine.ts

export type BookingEvent =
	| { type: 'START' }
	| { type: 'CONTINUE'; selectedCountry: string }
	| { type: 'ADD'; newPassenger: string }
	| { type: 'DONE' }
	| { type: 'FINISH' }
	| { type: 'RETRY' }
	| { type: 'CANCEL' };

const bookingMachine = createMachine(
	{
		context: {
			passengers: [],
			selectedCountry: '',
			countries: [],
			error: '',
		},
		tsTypes: {} as import('./bookingMachine.typegen').Typegen0,
		schema: {
			events: {} as BookingEvent,
			context: {} as BookingContext,
		},
		initial: 'initial',
		id: 'buy plane tickets',
		states: {
			initial: {
				on: {
					START: {
						target: 'search',
					},
				},
			},
			search: {
				on: {
					CONTINUE: {
						target: 'passengers',
						actions: assign({
							selectedCountry: (_context, event) => event.selectedCountry,
						}),
					},
					CANCEL: {
						target: 'initial',
						actions: 'clearState',
					},
				},
				initial: 'loading',
				states: {
					loading: {
						invoke: {
							id: 'getContries',
							src: () => fetchCountries,
							onDone: {
								target: 'success',
								actions: assign({
									countries: (_context, event) => event.data,
								}),
							},
							onError: {
								target: 'failure',
								actions: assign({
									error: 'Fallo el request',
								}),
							},
						},
					},
					success: {},
					failure: {
						on: {
							RETRY: 'loading',
						},
					},
				},
			},
			passengers: {
				on: {
					DONE: 'tickets',
					CANCEL: {
						target: 'initial',
						actions: 'clearState',
					},
					ADD: {
						target: 'passengers',
						actions: (context, event) =>
							context.passengers.push(event.newPassenger),
					},
				},
			},
			tickets: {
				after: {
					5000: {
						target: 'initial',
						actions: 'clearState',
					},
				},
				on: {
					FINISH: {
						target: 'initial',
						actions: 'clearState',
					},
				},
			},
		},
		predictableActionArguments: true,
	},
	{
		actions: {
			clearState: assign({
				passengers: [],
				selectedCountry: '',
			}),
			// addPassenger: assign({
			// 	passengers: (context, event) => [
			// 		...context.passengers,
			// 		event.newPassenger,
			// 	],
			// }),
		},
	},
);

Tickets.ts

Tengan cuidado con las letras mayúsculas, yo tenia Invoke en lugar de invoke y me tomo una eternidad encontrar el error.
No puedo recibir los datos en el countries siguiendo los pasos de la profesora. Si le pongo event.data el countries da undefined.
Excelente curso