Gestión del estado con Vue y Pinia

Cómo utilizar Pinia para gestionar el estado en Vue mediante un ejemplo práctico.

gestion-estado-vue-pinia

Vue es un framework que sirve para construir webs interactivas, y Pinia es una librería cuya utilidad es gestionar el estado de las aplicaciones o componentes Vue.

Para ver cómo integrar estas dos librerías, crearemos un ejemplo práctico: un wizard de varios pasos con una gestión cetralizada del estado. Cada paso se basa en un formulario simple y culmina en una confirmación que muestra un mensaje de éxito. La idea es presentar el ejemplo de manera clara y práctica para entender todos los conceptos.

Iniciar el proyecto

Ejecuta los siguientes comandos en la terminal:

Terminal
npm create vue@latest

Te aparecerá un wizard interactivo. Para este ejemplo lo importante es marcar Yes cuando te pregunta si quieres usar Pinia.

Terminal
Need to install the following packages:
Ok to proceed? (y) y
Vue.js - The Progressive JavaScript Framework
Project name: vue-project
Add TypeScript? No / Yes
Add JSX Support? No / Yes
Add Vue Router for Single Page Application development? No / Yes
Add Pinia for state management? No / Yes
Add Vitest for Unit Testing? No / Yes
Add an End-to-End Testing Solution? No
Add ESLint for code quality? No
Scaffolding project in /Users/user/development...
Done.

Una vez creado podemos ejecutar los comandos:

Terminal
cd vue-project
npm install

Estructura de directorios

Crea la siguiente estructura de directorios con los archivos .vue y .js:

Terminal
/src
/components
├── Wizard.vue # Componente principal
├── StepOne.vue # Primer formulario
├── StepTwo.vue # Segundo formulario
├── Confirmation.vue # Resumen antes de enviar
/stores
├── wizardStore.js # Estado centralizado con Pinia
/main.js # Configuración de Vue
/App.vue # Componente raíz

Gestión del estado

Aquí es donde centralizaremos los datos de la aplicación. El estado debe ser accesible para escribir cuando el usuario entre datos en los formularios o para leer, cuando queramos mostrárselos en la página de confirmación.

También añadiremos métodos que podemos llamar desde los otros componentes.

src/stores/wizardStore.js
import { defineStore } from 'pinia';
export const useWizardStore = defineStore('wizard', {
state: () => ({
step: 1, // Controla el paso actual
formData: {
name: '',
email: '',
age: null,
address: '',
}
}),
actions: {
updateData(newData) {
this.formData = {
...this.formData,
...newData
};
},
nextStep() {
if (this.step < 3) this.step++;
},
resetWizard() {
this.step = 1;
this.formData = { name: '', email: '', age: null, address: '' };
}
}
});

El componente Wizard.vue es el componente principal donde se renderizará el paso del wizard que toque según el paso en el que nos encontremos:

src/components/Wizard.vue
<template>
<div class="wizard">
<h2>Registro Paso {{ wizardStore.step }} de 3</h2>
<component :is="currentStepComponent" />
<div class="navigation">
<button v-if="wizardStore.step === 3" @click="submitForm">Confirmar</button>
</div>
</div>
</template>
<script>
import { computed } from 'vue';
import { useWizardStore } from '../stores/wizardStore';
import StepOne from './StepOne.vue';
import StepTwo from './StepTwo.vue';
import Confirmation from './Confirmation.vue';
export default {
components: { StepOne, StepTwo, Confirmation },
setup() {
const wizardStore = useWizardStore();
const currentStepComponent = computed(() => {
return wizardStore.step === 1
? StepOne
: wizardStore.step === 2
? StepTwo
: Confirmation;
});
const nextStep = () => wizardStore.nextStep();
const submitForm = () => alert('Formulario enviado con éxito');
return { wizardStore, currentStepComponent, nextStep, submitForm };
}
};
</script>

Ahora creamos el primer formulario para ver que entramos datos y se guardan en el estado:

src/components/StepOne.vue
<template>
<div>
<h3>Paso 1: Información Personal</h3>
<form @submit.prevent="next">
<label>Nombre:</label>
<input v-model="form.name" type="text" required />
<label>Email:</label>
<input v-model="form.email" type="email" required />
<button type="submit">Siguiente</button>
</form>
</div>
</template>
<script>
import { ref } from 'vue';
import { useWizardStore } from '../stores/wizardStore';
export default {
setup() {
const wizardStore = useWizardStore();
const form = ref({ ...wizardStore.formData });
const next = () => {
wizardStore.updateData(form.value);
wizardStore.nextStep();
};
return { form, next };
}
};
</script>

El segundo formulario pertenece a otro paso del wizard y es parecido al primero:

src/components/StepTwo.vue
<template>
<div>
<h3>Paso 2: Información Adicional</h3>
<form @submit.prevent="next">
<label>Edad:</label>
<input v-model="form.age" type="number" min="1" required />
<label>Dirección:</label>
<input v-model="form.address" type="text" required />
<button type="submit">Siguiente</button>
</form>
</div>
</template>
<script>
import { ref } from 'vue';
import { useWizardStore } from '../stores/wizardStore';
export default {
setup() {
const wizardStore = useWizardStore();
const form = ref({ ...wizardStore.formData });
const next = () => {
wizardStore.updateData(form.value);
wizardStore.nextStep();
};
return { form, next };
}
};
</script>

El último paso del wizard es de la confirmación y sirve para ver cómo leemos los datos del estado y los mostramos por pantalla:

src/components/Confirmation.vue
<template>
<div>
<h3>Confirmación</h3>
<p><strong>Nombre:</strong> {{ wizardStore.formData.name }}</p>
<p><strong>Email:</strong> {{ wizardStore.formData.email }}</p>
<p><strong>Edad:</strong> {{ wizardStore.formData.age }}</p>
<p><strong>Dirección:</strong> {{ wizardStore.formData.address }}</p>
<p>Si todo está correcto, haz clic en "Confirmar".</p>
</div>
</template>
<script>
import { useWizardStore } from '../stores/wizardStore';
export default {
setup() {
const wizardStore = useWizardStore();
return { wizardStore };
}
};
</script>

No nos olvidemos de modificar el archivo App.vue para mostrar nuestro componente prinicpal:

src/App.vue
<script setup>
import Wizard from './components/Wizard.vue'
</script>
<template>
<Wizard />
</template>

Ahora podemos ejecutar la aplicación en nuestro entorno local con el comando:

Terminal
npm run dev

Podemos acceder a http://localhost:5173/ y jugar con los formularios para ver que funciona.

Conclusión

En este artículo hemos visto cómo integrar Pinia en una aplicación Vue para coordinar el estado a lo largo de un wizard de múltiples pasos. La implementación, dividida en formularios simples y una pantalla de confirmación, demuestra cómo se pueden compartir datos entre distintos componentes.

El ejemplo presentado es sencillo permite experimentar con la arquitectura de una aplicación modular. Espero que te sea útil para ver cómo adaptar los conceptos para tus proyectos.