DEV Community

Cover image for Grilling Vue 3 Essentials on Pinia Skewers
Philip John Basile
Philip John Basile

Posted on • Updated on

Grilling Vue 3 Essentials on Pinia Skewers

Welcome to our deep dive into the dynamic world of Vue 3, the latest iteration of the progressive JavaScript framework that has taken the development community by storm. Vue 3 brings a plethora of exciting features and improvements that not only enhance the performance but also make the development process more efficient and enjoyable.

In this comprehensive guide, we'll traverse the breadth of Vue 3, from its novel Composition API to the core concepts of reactivity, components, directives, and routing. We will demystify these topics with plenty of code examples and useful tips, ensuring you have a solid understanding by the end of your reading journey.

As we venture further, we'll also touch upon state management in Vue 3 and explore Pinia, a nimble and intuitive alternative to Vuex. We'll show you how to leverage Pinia's power to make your applications more robust and maintainable.

Whether you're a newcomer to Vue or a seasoned developer looking to upgrade your skills, this guide is designed to equip you with the knowledge and confidence to build amazing Vue 3 applications. So, buckle up and let's dive right in!

1. Basic Vue 3 App:

Vue 3 applications are initialized slightly differently from Vue 2. Here's a basic Vue 3 application:

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
Enter fullscreen mode Exit fullscreen mode

This is how you create a new Vue 3 application and mount it to an HTML element with id app.


2. Vue Component:

Here's an example of a simple Vue 3 component:

import { defineComponent } from 'vue';

export default defineComponent({
  name: 'MyComponent',
  data() {
    return {
      message: 'Hello Vue 3!'
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

This code defines a new component called MyComponent. The data function returns an object with the component's data.


3. Vue Props:

Props are custom attributes you can register on a Vue component:

import { defineComponent } from 'vue';

export default defineComponent({
  name: 'ChildComponent',
  props: {
    message: String,
  },
})
Enter fullscreen mode Exit fullscreen mode

In this code, we're defining a prop message which expects a String value.


4. Vue Directives:

Vue.js uses double braces {{ }} as place-holders for data. Vue.js directives are HTML attributes with the prefix v-

<template>
  <div>
    <!-- This will print the message data property -->
    <p>{{ message }}</p>

    <!-- This will bind the title attribute of the p element to the message data property -->
    <p v-bind:title="message"></p>

    <!-- This will conditionally render the p element, only if showMessage is true -->
    <p v-if="showMessage">{{ message }}</p>

    <!-- This will handle the click event, calling the updateMessage method -->
    <button v-on:click="updateMessage">Update</button>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

5. Vue Options API vs Composition API:

In Vue 3, we have the new Composition API as an alternative to the Options API for organizing logic code in a component. Here's a comparison:

Options API:

import { defineComponent } from 'vue';

export default defineComponent({
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  },
});
Enter fullscreen mode Exit fullscreen mode

Composition API:

import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const count = ref(0);

    function increment() {
      count.value++;
    }

    return {
      count,
      increment,
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Both codes do the same thing - increment a counter, but the Composition API provides a more flexible way to compose component logic.


6. Vue 3 Lifecycle Hooks:

Lifecycle hooks in Vue 3 have slightly different names from Vue 2. They are prefixed with "on" instead of using the "before" prefix and camelCase convention. For instance, beforeCreate becomes onBeforeMount in Vue 3.

import { onMounted, onUpdated, onUnmounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('Component is mounted');
    });

    onUpdated(() => {
      console.log('Component is updated');
    });

    onUnmounted(() => {
      console.log('Component is unmounted');
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

7. Vue 3 Computed Properties:

Computed properties allow you to declare a property that is used in the template and is dependent on other properties.

import { computed } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    const fullName = computed(() => `${firstName.value} ${lastName.value}`);

    return {
      firstName,
      lastName,
      fullName
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

In the template, you can use {{ fullName }} and it will always display the current firstName and lastName concatenated.


8. Vue 3 Watchers:

Watchers are useful for executing logic when a reactive property changes.

import { watch, ref } from 'vue';

export default {
  setup() {
    const count = ref(0);

    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`);
    });

    return { count };
  }
}
Enter fullscreen mode Exit fullscreen mode

This will log a message every time count is changed.


9. Vue 3 Teleport:

Vue 3 introduces the <teleport> component that allows you to define a component that is placed in one part of the DOM but is moved to another part of the DOM at render time.

<teleport to="body">
  <div v-if="isModalOpen" class="modal">
    <!-- modal content -->
  </div>
</teleport>
Enter fullscreen mode Exit fullscreen mode

This would render the modal element as a direct child of the body element, even if the teleport component is nested deep within the DOM tree.


10. Vue 3 Suspense:

Vue 3 adds the <suspense> component that provides a better user experience while waiting for a component to load. It displays some fallback content until the primary content is ready to be rendered.

<suspense>
  <template #default>
    <AsyncComponent/>
  </template>
  <template #fallback>
    <div>Loading...</div>
  </template>
</suspense>
Enter fullscreen mode Exit fullscreen mode

In this example, "Loading..." will be displayed until AsyncComponent finishes loading.


11. Vue 3 Provide/Inject:

Provide and inject allow an ancestor component to serve as a dependency injector for all its descendants, regardless of how deep the component hierarchy is, as long as they are in the same parent chain.

import { provide, inject } from 'vue';

const ThemeSymbol = Symbol();

// Ancestor component
export default defineComponent({
  setup() {
    provide(ThemeSymbol, 'dark');
  },
});

// Descendant component
export default defineComponent({
  setup() {
    const theme = inject(ThemeSymbol);
    // theme now equals 'dark'
  },
});
Enter fullscreen mode Exit fullscreen mode

12. Vue 3 Emitting Custom Events:

In Vue 3, custom events can be emitted using the emit function, which is provided as the second argument to the setup function.

export default defineComponent({
  setup(props, { emit }) {
    const onClick = () => {
      emit('my-event', 'payload for the event');
    }

    return { onClick };
  },
});
Enter fullscreen mode Exit fullscreen mode

You can listen to this event in a parent component like so:

<MyComponent @my-event="handleMyEvent"/>
Enter fullscreen mode Exit fullscreen mode

13. Vue 3 Directives:

Let's take a look at a custom directive example in Vue 3. Let's make a directive that changes the color of the text in an element:

import { directive, createApp } from 'vue'

const colorDirective = directive({
  beforeMount(el, binding, vnode, prevVnode) {
    el.style.color = binding.value;
  },
})

createApp(App)
  .directive('color', colorDirective)
  .mount('#app')
Enter fullscreen mode Exit fullscreen mode

We can use this directive in a component like this:

<div v-color="'red'">This is a red text</div>
Enter fullscreen mode Exit fullscreen mode

14. Vue 3 Plugins:

Creating a plugin in Vue 3 involves exposing an install function. Here's a basic example:

export default {
  install(app, options) {
    app.config.globalProperties.$myPlugin = {
      // plugin logic goes here
    };
  },
};
Enter fullscreen mode Exit fullscreen mode

You can then use your plugin like this:

import MyPlugin from './my-plugin';

createApp(App)
  .use(MyPlugin, options)
  .mount('#app');
Enter fullscreen mode Exit fullscreen mode

15. Vue 3 Router:

Vue Router is the official router for Vue.js. Here's how you define routes and use them in Vue 3:

import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    component: About,
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;
Enter fullscreen mode Exit fullscreen mode

In your main.js:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
Enter fullscreen mode Exit fullscreen mode

16. Vue 3 State Management with Vuex:

Vuex is the official state management library for Vue.js. Here's a basic example:

Install Vuex with npm:

npm install vuex@next --save
Enter fullscreen mode Exit fullscreen mode

Store creation:

import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    increment(context) {
      context.commit('increment')
    }
  },
  modules: {
  }
})
Enter fullscreen mode Exit fullscreen mode

Usage in component:

import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()

    const count = computed(() => store.state.count)

    function increment() {
      store.dispatch('increment')
    }

    return { count, increment }
  }
}
Enter fullscreen mode Exit fullscreen mode

17. Vue 3 and Axios:

Axios is often used in Vue applications to make HTTP requests. Here's how you might use it to fetch data from an API:

First, install Axios:

npm install axios
Enter fullscreen mode Exit fullscreen mode

Then, use it in your component:

import { reactive, onMounted } from 'vue';
import axios from 'axios';

export default {
  setup() {
    const state = reactive({
      posts: [],
      error: null
    });

    onMounted(async () => {
      try {
        const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
        state.posts = response.data;
      } catch (error) {
        state.error = error;
      }
    });

    return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

18. Vue 3 Render Function:

Sometimes, you might need more control over the rendering process. For these cases, Vue provides a render function API:

import { h } from 'vue';

export default {
  render() {
    return h('div', {}, 'Hello Vue 3 with Render function');
  }
}
Enter fullscreen mode Exit fullscreen mode

19. Vue 3 Testing with Vue Test Utils:

Vue Test Utils is the official unit testing utility library for Vue.js. Here's a simple test for a component using Jest and Vue Test Utils:

First, install necessary packages:

npm install --save-dev @vue/test-utils jest
Enter fullscreen mode Exit fullscreen mode

Then, a simple test could look like this:

import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'

test('increments count when button is clicked', async () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.find('div').text()).toMatch('1')
})
Enter fullscreen mode Exit fullscreen mode

20. Vue 3 Mixins:

Mixins are a way to distribute reusable functionalities for Vue components. Here's how you define and use a mixin:

// Define a mixin object
const myMixin = {
  created() {
    this.hello()
  },
  methods: {
    hello() {
      console.log('hello from mixin!')
    }
  }
}

// Component that uses the mixin
export default {
  mixins: [myMixin]
}
Enter fullscreen mode Exit fullscreen mode

Now, whenever this component is created, it will log 'hello from mixin!'.


21. Vue 3 Slots:

Slots provide a way to define placeholders in a component template that can be filled with content from a parent component. Here's a simple example:

In the child component:

<template>
  <div>
    <slot></slot>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

In the parent component:

<template>
  <ChildComponent>
    <p>This is some original content</p>
  </ChildComponent>
</template>
Enter fullscreen mode Exit fullscreen mode

22. Vue 3 Scoped Slots:

Scoped slots are a way to create slots that have access to properties from the child component.

In the child component:

<template>
  <div>
    <slot name="header" :user="user">
      {{ user.firstName }}
    </slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        firstName: 'John',
        lastName: 'Doe'
      }
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

In the parent component:

<template>
  <ChildComponent>
    <template #header="{ user }">
      <p>{{ user.firstName }} {{ user.lastName }}</p>
    </template>
  </ChildComponent>
</template>
Enter fullscreen mode Exit fullscreen mode

23. Vue 3 Transition & Animation:

Vue provides various ways to apply transition effects to elements when they are added, updated, or removed from the DOM. Here's an example:

<template>
  <div>
    <button @click="show = !show">
      Toggle
    </button>

    <transition name="fade">
      <p v-if="show">Hello</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  }
}
</script>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>
Enter fullscreen mode Exit fullscreen mode

24. Vue 3 Custom Events:

Vue 3 components can emit custom events with the emit method:

export default {
  setup(props, { emit }) {
    const onClick = () => {
      emit('myEvent', 'Hello, Vue 3!')
    };

    return { onClick };
  }
};
Enter fullscreen mode Exit fullscreen mode

In a parent component, you can listen to this event with v-on or @:

<ChildComponent @myEvent="handleMyEvent"/>
Enter fullscreen mode Exit fullscreen mode

25. Vue 3 Server-Side Rendering (SSR):

Vue.js also supports building server-rendered applications using vue-server-renderer. However, it's recommended to use Nuxt.js when building server-rendered applications with Vue.js as it abstracts away a lot of the complexities of managing server-rendered state and provides a higher-level, more opinionated framework with conventions.


26. Vue 3 Renderless Components:

A renderless component is a component that doesn't render any of its own HTML but instead provides functionality to other components. This is an advanced technique used to create reusable functions as components:

import { ref } from 'vue';

export default {
  name: 'RenderlessCounter',
  setup(_, { slots }) {
    const count = ref(0);

    const increment = () => {
      count.value += 1;
    };

    return () => slots.default({ count: count.value, increment });
  }
};
Enter fullscreen mode Exit fullscreen mode

Then you can use this renderless component like so:

<RenderlessCounter v-slot="{ count, increment }">
  <button @click="increment">
    You clicked me {{ count }} times
  </button>
</RenderlessCounter>
Enter fullscreen mode Exit fullscreen mode

27. Vue 3 Composition API vs Options API:

Vue 3 introduced a new way of writing components called the Composition API, which is an alternative to the Options API, used in Vue 2. Here's the same component written with both APIs:

Composition API:

import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);

    function increment() {
      count.value++;
    }

    return { count, increment };
  },
};
Enter fullscreen mode Exit fullscreen mode

Options API:

export default {
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    increment() {
      this.count++;
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

28. Vue 3 and TypeScript:

Vue 3 has better TypeScript support than Vue 2. You can write components like this:

import { defineComponent } from 'vue';

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: String,
  },
  setup(props) {
    // TypeScript can infer the type of 'props'
    console.log(props.msg);
  },
});
Enter fullscreen mode Exit fullscreen mode

29. Vue 3 and the Vue CLI:

The Vue CLI is a powerful tool that can help you bootstrap your Vue.js applications. It comes with a graphical user interface, and it's flexible, supporting a variety of configurations:

Install Vue CLI globally with npm:

npm install -g @vue/cli
Enter fullscreen mode Exit fullscreen mode

Create a new project:

vue create my-project
Enter fullscreen mode Exit fullscreen mode

Serve the project:

cd my-project
npm run serve
Enter fullscreen mode Exit fullscreen mode

30. Vue 3 Filters:

Vue 2 filters are removed in Vue 3. The recommended migration strategy is to use methods or computed properties instead:

import { ref, computed } from 'vue';

export default {
  setup() {
    const message = ref('hello');
    const uppercaseMessage = computed(() => message.value.toUpperCase());

    return {
      message,
      uppercaseMessage,
    };
  },
};
Enter fullscreen mode Exit fullscreen mode

31. Vue 3 Functional Components:

Functional components are a type of component that doesn't have any state and consists purely of a render function. Functional components in Vue 3 are written like this:

export default {
  functional: true,
  render() {
    // component logic
  }
}
Enter fullscreen mode Exit fullscreen mode

32. Vue 3 Custom Composition Functions:

With the Composition API, you can create custom composition functions. Here's an example of a custom hook that fetches data from an API:

import { ref, onMounted } from 'vue';
import axios from 'axios';

export function useFetchData(url) {
  const data = ref(null);
  const isLoading = ref(true);

  onMounted(async () => {
    try {
      const response = await axios.get(url);
      data.value = response.data;
    } catch (error) {
      console.error(error);
    } finally {
      isLoading.value = false;
    }
  });

  return { data, isLoading };
}
Enter fullscreen mode Exit fullscreen mode

Then you can use this function in a component:

import { useFetchData } from './useFetchData';

export default {
  setup() {
    const { data, isLoading } = useFetchData('/api/data');

    return { data, isLoading };
  }
};
Enter fullscreen mode Exit fullscreen mode

33. Vue 3 Teleport:

The <teleport> component lets you control where your component's template is rendered in the DOM. It's great for modals, pop-ups, and other UI elements that need to break out of their container. Here's an example of how to use it:

<teleport to="#end-of-body">
  <div class="modal">
    This will be rendered at the end of the body!
  </div>
</teleport>
Enter fullscreen mode Exit fullscreen mode

34. Vue 3 Suspense:

The <Suspense> component lets you "wait" for some condition and display some fallback content while waiting. It's mostly used with async components. Here's an example:

<Suspense>
  <template #default>
    <AsyncComponent />
  </template>

  <template #fallback>
    <div>Loading...</div>
  </template>
</Suspense>
Enter fullscreen mode Exit fullscreen mode

35. Vue 3 Reactivity with Proxies:

Vue 3 uses JavaScript Proxies for its reactivity system, which allows it to track changes to objects and arrays better than Vue 2. This is a low-level detail, and you normally won't have to worry about it, but it's part of what makes Vue 3 more powerful and flexible than Vue 2.

The teaching could go on indefinitely as Vue is a broad and deep framework that offers a multitude of functionalities and patterns. But hopefully, this guide has given you a solid foundation on which to build your Vue knowledge. Remember to practice what you've learned, as practical application is key to solidifying these concepts! Let me know if you have more questions or need information on other topics.


36. Pinia

Pinia is an alternative state management library for Vue.js. It aims to provide a simpler and more straightforward API than Vuex, and it's fully compatible with the Vue 3 Composition API.

First, let's install Pinia:

npm install pinia
Enter fullscreen mode Exit fullscreen mode

Creating a store:

In Pinia, instead of defining one big Vuex store, you define multiple smaller stores. Here's an example of a store:

import { defineStore } from 'pinia';

export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    reset() {
      this.count = 0;
    }
  },
});
Enter fullscreen mode Exit fullscreen mode

Accessing the store:

In a Vue component, you can access the store like this:

import { useCounterStore } from './stores/counter';

export default {
  setup() {
    const counter = useCounterStore();

    return {
      count: counter.count,
      increment: counter.increment,
      reset: counter.reset,
    };
  },
};
Enter fullscreen mode Exit fullscreen mode

Using the store in the template:

And you can use the store in your template like this:

<template>
  <p>Count: {{ count }}</p>
  <button @click="increment">Increment</button>
  <button @click="reset">Reset</button>
</template>
Enter fullscreen mode Exit fullscreen mode

Setting up Pinia:

You also need to install Pinia in your application:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
app.use(createPinia());
app.mount('#app');
Enter fullscreen mode Exit fullscreen mode

That's the basics of Pinia! It provides a simpler API and is more focused on the Composition API, making it a great choice for Vue 3 applications. You can create more complex stores with actions that commit mutations and get state from other stores, similar to Vuex.


37. Fetching data within a Pinia store:

Pinia stores can handle async actions, which makes them great for fetching data:

import { defineStore } from 'pinia';
import axios from 'axios';

export const useUserStore = defineStore({
  id: 'user',
  state: () => ({
    user: null,
  }),
  actions: {
    async fetchUser(id) {
      const response = await axios.get(`/api/users/${id}`);
      this.user = response.data;
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

38. Computed properties in Pinia store:

Pinia also supports computed properties in the store. Let's modify the previous example to include a computed full name:

import { defineStore } from 'pinia';
import axios from 'axios';

export const useUserStore = defineStore({
  id: 'user',
  state: () => ({
    user: null,
  }),
  actions: {
    async fetchUser(id) {
      const response = await axios.get(`/api/users/${id}`);
      this.user = response.data;
    },
  },
  getters: {
    fullName() {
      return this.user ? `${this.user.firstName} ${this.user.lastName}` : '';
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

39. Accessing one store from another:

Sometimes, you need to access data from one store inside another store. You can do this by importing the other store inside the actions or getters:

import { defineStore } from 'pinia';
import { useUserStore } from './user';

export const usePostsStore = defineStore({
  id: 'posts',
  state: () => ({
    posts: [],
  }),
  actions: {
    userPosts() {
      const userStore = useUserStore();
      return this.posts.filter((post) => post.userId === userStore.user.id);
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

40. Testing Pinia stores:

Testing Pinia stores is straightforward because you can import the store functions directly into your test and call their actions and getters:

import { useCounterStore } from './counter';

test('increment increases count', () => {
  const counter = useCounterStore();

  counter.increment();

  expect(counter.count).toBe(1);
});
Enter fullscreen mode Exit fullscreen mode

41. Server-side rendering (SSR) with Pinia:

Pinia supports server-side rendering (SSR) out of the box. It can serialize the state of your stores and then deserialize it on the client side, so the client starts with the same state as the server.

Pinia provides a more idiomatic way to handle state in Vue 3 with the Composition API, and it's rapidly gaining popularity. Its flexible and modular nature allows for a cleaner and more maintainable codebase for complex applications. As always, feel free to ask if you have any questions or need more information on a specific topic!


42. Resetting a store:

To reset a store in Pinia, you can define a reset action in your store:

import { defineStore } from 'pinia';

export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    reset() {
      // Resets the state to its initial state
      Object.assign(this.$state, this.$initState);
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

The $initState property holds a copy of the initial state, and Object.assign is used to replace the current state with the initial state.


43. Handling errors in a Pinia store:

When handling asynchronous actions in Pinia, you'll want to have a way to handle errors. You can do this with try/catch:

import { defineStore } from 'pinia';
import axios from 'axios';

export const useUserStore = defineStore({
  id: 'user',
  state: () => ({
    user: null,
    error: null,
  }),
  actions: {
    async fetchUser(id) {
      try {
        const response = await axios.get(`/api/users/${id}`);
        this.user = response.data;
      } catch (error) {
        this.error = error;
      }
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

44. Loading state in a Pinia store:

Often, you'll want to display a loading spinner while fetching data. You can add a loading state to your store:

import { defineStore } from 'pinia';
import axios from 'axios';

export const useUserStore = defineStore({
  id: 'user',
  state: () => ({
    user: null,
    loading: false,
    error: null,
  }),
  actions: {
    async fetchUser(id) {
      this.loading = true;
      try {
        const response = await axios.get(`/api/users/${id}`);
        this.user = response.data;
      } catch (error) {
        this.error = error;
      } finally {
        this.loading = false;
      }
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

45. Reusing logic across stores:

Since Pinia stores are just JavaScript functions, you can extract common logic into separate functions and reuse them across stores. For instance, if multiple stores have a loading state, you could create a useLoading function:

export function useLoading() {
  return {
    loading: false,
    startLoading() {
      this.loading = true;
    },
    stopLoading() {
      this.loading = false;
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

Then use it in a store:

import { defineStore } from 'pinia';
import { useLoading } from './useLoading';
import axios from 'axios';

export const useUserStore = defineStore({
  id: 'user',
  state: () => ({
    ...useLoading(),
    user: null,
    error: null,
  }),
  actions: {
    async fetchUser(id) {
      this.startLoading();
      try {
        const response = await axios.get(`/api/users/${id}`);
        this.user = response.data;
      } catch (error) {
        this.error = error;
      } finally {
        this.stopLoading();
      }
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

46. Mocking external dependencies in tests:

When testing Pinia stores, you may need to mock external dependencies, like API calls. You can use libraries like jest.mock to achieve this. Here's an example:

import { useUserStore } from './user';
import axios from 'axios';

jest.mock('axios');

test('fetchUser fetches a user', async () => {
  const user = { id: 1, name: 'John Doe' };
  axios.get.mockResolvedValue({ data: user });

  const userStore = useUserStore();

  await userStore.fetchUser(user.id);

  expect(userStore.user).toEqual(user);
});
Enter fullscreen mode Exit fullscreen mode

And there you have it! We've journeyed together through the fundamentals of Vue 3 and explored the intriguing world of Pinia. We've unpacked the power of the Composition API, delved into creating dynamic components, and understood how to manage state using Pinia. This guide provides a solid foundation, but remember that the world of web development is vast and continually evolving.

To deepen your Vue 3 and Pinia knowledge, I encourage you to build your own projects and experiment with these concepts. There's no better way to learn than by doing.

For more in-depth exploration, the official Vue 3 documentation is an invaluable resource and is filled with comprehensive guides, examples, and tips. The Vue community is vibrant and active, with plenty of tutorials, articles, and forums available. The official Pinia documentation is also a great starting point for understanding advanced state management in Vue 3.

Participate in code challenges and contribute to open-source projects. Both will expose you to different coding styles and real-world scenarios.

Remember, the journey of learning never ends and every step you take is progress. Keep exploring, keep experimenting, and most importantly, enjoy the process! Happy cooking errr.. coding!! :)

If you enjoy my technology-focused articles and insights and wish to support my work, feel free to visit my Ko-fi page at https://ko-fi.com/philipjohnbasile. Every coffee you buy me helps keep the tech wisdom flowing and allows me to continue sharing valuable content with our community. Your support is greatly appreciated!

Top comments (1)

Collapse
 
websilone profile image
Boris LOUBOFF

Hi @philipjohnbasile,
thank you for this complete article.
One remark though : are you sure about the #45 "Reusing logic across stores" ?
Because as you did it, we will end up with functions inside the state object...
I tested it, it "works" (meaning there are no errors) but in the philosophy of Pinia, state should only be composed with writable objects.
Here is the part of the doc that talks about that : pinia.vuejs.org/cookbook/composabl...

We should better use a "setup store" here to correctly create state and actions properties from the useLoading composable.