Cómo utilizar Pinia para gestionar el estado en Vue mediante un ejemplo práctico.
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.
Ejecuta los siguientes comandos en la 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.
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:
cd vue-projectnpm install
Crea la siguiente estructura de directorios con los archivos .vue
y .js
:
/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
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.
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:
<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:
<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:
<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:
<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:
<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:
npm run dev
Podemos acceder a http://localhost:5173/
y jugar con los formularios para ver que funciona.
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.