# 비동기 액션 생성하기

## 컴포넌트: UserProfile.vue

<figure><img src="/files/cratZLEp3hEb15YQIx0P" alt=""><figcaption><p>default</p></figcaption></figure>

<figure><img src="/files/ReYxjDkQKk1xKjRbIu90" alt=""><figcaption><p>hover</p></figcaption></figure>

### API 사용하기

dicebear의 API를 사용합니다.

<figure><img src="/files/8lV5doW7D7EFwNFDmljR" alt=""><figcaption><p><a href="https://www.dicebear.com/styles/">https://www.dicebear.com/styles/</a></p></figcaption></figure>

```
pnpm add 
@dicebear/core
@dicebear/collection
axios

```

### \[Optional] api layer 작성하기

dicebear에서 제공하는 sdk를 사용할 것이기 때문에 axios를 직접 사용하지는 않습니다.

```javascript
import axios from "axios";

export const httpInstance = axios.create({
  baseURL: "/api",
})

export const http =  {
  get: (url,params) => httpInstance.get(url, params),
  post: (url, params) => httpInstance.post(url, params)
}
```

### UserStore 작성하기

### stores/user.js

```javascript
import { defineStore } from "pinia";
import { createAvatar } from '@dicebear/core';
import { 
  adventurer,
  adventurerNeutral,
  avataaars,
  avataaarsNeutral,
  bigEars,
  bigEarsNeutral,
  bigSmile,
  bottts,
  botttsNeutral,
  croodles,
  croodlesNeutral
 } from '@dicebear/collection';

const profiles = [
  adventurer,
  adventurerNeutral,
  avataaars,
  avataaarsNeutral,
  bigEars,
  bigEarsNeutral,
  bigSmile,
  bottts,
  botttsNeutral,
  croodles,
  croodlesNeutral
];

```

import 구문 부터 살펴보면 dicebear에서 제공하는 여러 스타일을 가져오기 위해 여러 모듈들을 임포트합니다.

이어서 작성합니다.

```javascript
const getRandomUserData = async (userId) => {
  const avatar = await createAvatar(profiles[Math.floor(profiles.length * Math.random())])
  return {
    name: `Dante${userId}`,
    age: Math.floor(Math.random() * 100),
    userId,
    avatar: await avatar.toDataUri()
  }
};
```

`userId`가 입력되면 임의의 유저 데이터를 받아오기 위한 헬퍼 함수를 작성합니다.

이어서 작성합니다.

```javascript

export const useUserStore = defineStore("user", {
  state:() => ({
    userData: null,
    loading: false,
    error: null
  }),
  getters: {
    isUserLoggedIn: state => !!state.userData,
  },
  actions: {
    async fetchUserData(userId) {
      this.loading = true;

      const response = await new Promise(resolve => {
        setTimeout(() => {
          resolve(getRandomUserData(userId))
        },700)
      });
      this.userData = response;
      this.loading = false;
    },
  }
})
```

액션에 비동기함수 fetchUserData를 선언합니다. 로딩 시간을 길게 주어 loading state가 올바르게 표기되는지 확인하기 위해 Promise 내부에 임의로 700ms 의 대기시간을 넣었습니다.

### UserProfile.vue 작성하기

```html
<template>
  <div class="flex items-center p-2 bg-white rounded-lg shadow-sm hover:bg-gray-100">
    <img 
      v-if="userData && userData.avatar"
      :src="userData.avatar" 
      alt="User Avatar" 
      class="w-8 h-8 rounded-full"
    />
    <span v-if="userData" class="ml-3 text-sm font-medium text-gray-700">
      {{ userData.name }}
    </span>
    <span v-if="userData.loading" class="ml-3 text-sm font-medium text-gray-700">
      Loading...
    </span>
  </div>
</template>

<script setup>
import { storeToRefs } from 'pinia';
import { useUserStore } from '../../stores/user'
const userStore = useUserStore();
const { userData } = storeToRefs(userStore);
</script>

```

테일윈드를 이용해 스타일링을 합니다.&#x20;

스토어 선언할 때 작성한 useUserStore를 호출해 userStore를 가져옵니다.&#x20;

storeToRefs를 이용해 userData를 구조분해하여 `userStore.userData`와 같이 작성하지 않아도 reactivity를 유지하게 합니다.

template 내부에 `v-if` 디렉티브 사용한 부분만 간단히 보면 userData가 없는 동안에는 loading 문구를 표시합니다.

```
<span v-if="userData" class="ml-3 text-sm font-medium text-gray-700">
  {{ userData.name }}
</span>
<span v-if="userData.loading" class="ml-3 text-sm font-medium text-gray-700">
  Loading...
</span>
```

이 컴포넌트는 userData를 내부에서 fetch하지 않고 데이터를 보여주는 presentation만 담당합니다.&#x20;

이제 작성한 컴포넌트를 테스트하기 위해 스토리북 코드를 작성하겠습니다. UserProfile.vue와 동일한 경로에 UserProfile.stories.js를 생성합니다.

```javascript
import UserProfile from './UserProfile.vue';
import { useUserStore } from '@/stores/user';

export const Base = {
  render: (args) => ({
    components: { UserProfile },
    setup() {
      const userStore = useUserStore();
      userStore.userData = {
        name: "John Doe",
        email: "john@example.com",
        id: 53,
        avatar: "https://api.dicebear.com/7.x/fun-emoji/svg"
      }
      return { args }
    },
    template: /* html */`
      <UserProfile 
        v-bind="args"
      />
    `,
  }),
  args: {
    alt: "storybook Profile"
  }
}

export default {
  component: UserProfile
}
```

일부분씩 뜯어서 보겠습니다.

#### 최하단의 export default&#x20;

```
export default {
  component: UserProfile
}
```

<figure><img src="/files/4c91a1fEqqeOCJybtxK3" alt=""><figcaption><p>UserProfile component</p></figcaption></figure>

UserProfile 스토리가 생긴것을 볼 수 있습니다.

### 개별 스토리&#x20;

```
export const Base = {...
```

UserProfile 내부에 Base story를 생성합니다.

### 테스트 store 주입

```
setup() {
      const userStore = useUserStore();
      userStore.userData = {
        name: "John Doe",
        email: "john@example.com",
        id: 53,
        avatar: "https://api.dicebear.com/7.x/fun-emoji/svg"
      }
      return { args }
    },
template: /* html */`
  <UserProfile 
    v-bind="args"
  />
`,
```

`userStore.userData = {` 를 사용해 테스트에 사용할 스토어를 초기화 합니다. 모킹 데이터를 넣어준 것입니다.

### 결과

&#x20;![](/files/heeutG65nwSghJ46yXjD)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://pinia.chocopam.com/essential-of-pinia/use-store-in-vue-component/undefined.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
