Table of Contents
Open Table of Contents
Introducción
Cuando estamos trabajando con frameworks del lado del cliente tales como React, Angular, Vue, etc una de las tareas más comunes que tenemos que realizar es hacer peticiones asíncronas al servidor. Y para poder mostrar los diferentes estados de la petición solemos usar valores booleanos a modo de “flags”, por ejemplo isLoading o hasError, en este artículo veremos una forma más eficiente y sobre todo predecible usando XState y React.
Creando componente GitHubUserProfile
Vamos a crear un componente el cual va a mostrar la información relacionada con un usuario en GitHub, datos como su nombre, bio, login, etc.
import React from 'react'
function GitHubUserProfile(props) {
const { name, login, bio, followers, avatar_url, created_at } = props.user
return (
<div className="ui card">
<div className="image">
<img src={avatar_url} alt={name} />
</div>
<div className="content">
<a target="_blank" rel="noopener noreferrer" href={`https://github.com/${login}`} className="header">
{name}
</a>
{created_at && (
<div className="meta">
<span className="date">Joined in {created_at.substring(0, 4)}</span>
</div>
)}
<div className="description">{bio}</div>
</div>
<div className="extra content">
<a target="_blank" rel="noopener noreferrer" href="https://github.com/gustavocd?tab=followers">
<i className="user icon" />
{followers} Followers
</a>
</div>
</div>
)
}
export default GitHubUserProfile
Creando la state machine con XState
Continuaremos creando la state machine que nos permitirá modelar los diferentes estados de nuestra petición.
import { Machine, assign } from 'xstate'
const GITHUB_URL = 'https://api.github.com/users'
const fetchUser = username =>
fetch(`${GITHUB_URL}/${username}`)
.then(blob => blob.json())
.then(data => {
if (data.message) {
return
}
return data
})
.catch(console.log)
const userProfileMachine = Machine({
id: 'userProfile',
initial: 'idle',
context: {
username: 'gustavocd',
user: null,
error: null,
},
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
invoke: {
id: 'getUser',
src: (context, event) => fetchUser(context.username),
onDone: {
target: 'success',
actions: assign({ user: (context, event) => event.data }),
},
onError: {
target: 'failure',
actions: assign({ error: (context, event) => event.data }),
},
},
},
success: {},
failure: {
on: {
RETRY: 'loading',
},
},
},
})
Implementando la State Machine en el componente App
El paso final es usar nuestra state machine en el componente principal de nuestra app, usaremos un paquete llamado @xstate/react para poder usar react hooks.
import React, { useEffect } from 'react'
import { useMachine } from '@xstate/react'
import { Machine, assign } from 'xstate'
import GitHubUserProfile from './components/GitHubUserProfile'
/*
Here is defined the userProfileMachine,
I didn't rewrite here to simplify the example
*/
function App() {
const [current, send] = useMachine(userProfileMachine)
useEffect(() => {
send('FETCH')
}, [send])
if (current.matches('loading') || current.matches('idle')) {
return <span>Loading...</span>
}
return <GitHubUserProfile user={current.context.user} />
}
export default App
Ejemplo completo en codesandbox
Puedes ver el ejemplo completo en el siguiente: enlace