Skip to content

Commit 4d81667

Browse files
committed
feat: vue3 custom store
1 parent 26b1df9 commit 4d81667

File tree

5 files changed

+234
-13
lines changed

5 files changed

+234
-13
lines changed

src/characters/pages/CharacterId.vue

+93-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,105 @@
11
<script setup lang="ts">
2+
import { useRoute } from "vue-router";
3+
import characterStore from "@/store/characters.store";
24
5+
import rickAndMortyApi from "@/api/rickAndMortyApi";
6+
import type { Character } from "@/interfaces/character";
7+
import { useQuery } from "@tanstack/vue-query";
8+
9+
const route = useRoute();
10+
11+
// in this case the reactivity will be lost
12+
const { id } = route.params as { id: string };
13+
14+
const getCharacterCacheFirst = async (
15+
characterId: string
16+
): Promise<Character> => {
17+
if (characterStore.checkId(characterId)) {
18+
return characterStore.ids.list[characterId];
19+
}
20+
21+
const { data } = await rickAndMortyApi.get<Character>(
22+
`/character/${characterId}`
23+
);
24+
return data;
25+
};
26+
27+
const { data: character } = useQuery<Character>(
28+
["character", id],
29+
() => getCharacterCacheFirst(id),
30+
{
31+
onSuccess: (data) => {
32+
characterStore.loadId(data);
33+
},
34+
}
35+
);
336
</script>
437

538
<template>
639
<div>
7-
<h1>CharacterId</h1>
40+
<h1>Character #{{ id }}</h1>
41+
<h1 v-if="!character">Loading...</h1>
42+
<h1 v-else-if="characterStore.characters.hasError">
43+
{{ characterStore.characters.errorMessage }}
44+
</h1>
45+
<div v-else class="card">
46+
<figure>
47+
<img :src="character.image" :alt="character.name" class="card-image" />
48+
</figure>
49+
<div class="card-content">
50+
<h2>{{ character.name }}</h2>
51+
<p>{{ character.species }}</p>
52+
<p>Origin: {{ character.origin.name }}</p>
53+
<p>Location: {{ character.location.name }}</p>
54+
</div>
55+
</div>
856
</div>
957
</template>
1058

1159
<style scoped>
60+
.card {
61+
display: flex;
62+
flex-direction: column;
63+
align-items: center;
64+
justify-content: center;
65+
margin: 2rem 0;
66+
padding: 1rem;
67+
border: 1px solid #ccc;
68+
border-radius: 0.5rem;
69+
}
70+
71+
figure {
72+
width: 100%;
73+
height: 250px;
74+
background-color: #333;
75+
padding: 10px;
76+
display: flex;
77+
place-content: center;
78+
}
79+
80+
.card-image {
81+
width: 100%;
82+
height: 100%;
83+
object-fit: contain;
84+
}
85+
86+
87+
.card-content {
88+
width: 100%;
89+
display: flex;
90+
flex-direction: column;
91+
align-items: center;
92+
justify-content: center;
93+
margin: 1rem 0;
94+
}
95+
96+
.card-content > * {
97+
margin: 0.5rem 0;
98+
}
1299
100+
.card-footer {
101+
width: 100%;
102+
display: flex;
103+
justify-content: center;
104+
}
13105
</style>

src/characters/pages/CharacterList.vue

+17-9
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,37 @@ import rickAndMortyApi from "@/api/rickAndMortyApi";
66
import CardList from "@/characters/components/CardList.vue";
77
88
import type { Character, ResponseCharacter } from "@/interfaces/character";
9+
import characterStore from "@/store/characters.store";
910
1011
const props = defineProps<{ title: string; visible: boolean }>();
1112
1213
13-
const getCharacters = async (): Promise<Character[]> => {
14+
const getCharactersCacheFirst = async (): Promise<Character[]> => {
15+
if (characterStore.characters.count) return characterStore.characters.list;
1416
const { data } = await rickAndMortyApi.get<ResponseCharacter>("/character");
1517
return data.results;
1618
};
1719
18-
const {
19-
isError,
20-
isLoading,
21-
error,
22-
data: characters,
23-
} = useQuery(["characters"], getCharacters);
20+
// const { isError, isLoading, error, data,} =
21+
useQuery(["characters"], getCharactersCacheFirst, {
22+
onSuccess: (characters) => {
23+
characterStore.loadCharacters(characters);
24+
},
25+
// onError: (error) => {
26+
// },
27+
});
28+
2429
2530
</script>
2631

2732
<template>
28-
<h1 v-if="isLoading">Loading...</h1>
33+
<h1 v-if="characterStore.characters.isLoading">Loading...</h1>
34+
<h1 v-else-if="characterStore.characters.hasError">
35+
{{ characterStore.characters.errorMessage }}
36+
</h1>
2937
<template v-else>
3038
<h2>{{ props.title }}</h2>
31-
<CardList :characters="characters!"/>
39+
<CardList :characters="characterStore.characters.list"/>
3240
</template>
3341
</template>
3442

src/characters/router/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { RouteRecordRaw } from "vue-router";
22

3+
const ROUTE_NAME = "characters";
4+
35
export const characterRoute: RouteRecordRaw = {
4-
path: "/characters",
5-
redirect: "/characters/list",
6+
path: `/${ROUTE_NAME}`,
7+
redirect: `/${ROUTE_NAME}/list`,
68
name: "Characters",
79
component: () => import("@/characters/layout/CharacterLayout.vue"),
810
children: [

src/main.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import router from "./router";
44
import { VueQueryPlugin } from "@tanstack/vue-query";
55

66
import "./assets/main.css";
7+
import "@/store/characters.store"
78

89
const app = createApp(App);
910

@@ -12,7 +13,7 @@ VueQueryPlugin.install(app, {
1213
queryClientConfig: {
1314
defaultOptions: {
1415
queries: {
15-
cacheTime: 1000 * 120,
16+
cacheTime: 2000,
1617
refetchOnReconnect: 'always',
1718
}
1819
}

src/store/characters.store.ts

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { reactive } from "vue";
2+
3+
import type { Character, ResponseCharacter } from "@/interfaces/character";
4+
import rickAndMortyApi from "@/api/rickAndMortyApi";
5+
import { isAxiosError } from "axios";
6+
7+
interface Store {
8+
characters: {
9+
list: Character[];
10+
count: number;
11+
isLoading: boolean;
12+
hasError: boolean;
13+
errorMessage: string | null;
14+
}
15+
16+
ids: {
17+
isLoading: boolean;
18+
hasError: boolean;
19+
errorMessage: string | null;
20+
list: {
21+
[id: string]: Character;
22+
}
23+
}
24+
25+
// Characters methods
26+
startLoadingCharacters: () => void;
27+
loadCharacters: (data: Character[]) => void;
28+
loadCharactersFailed: (error: string) => void;
29+
30+
// Ids methods
31+
startLoadingId: (id: string) => Promise<void>;
32+
loadId: (data: Character) => void;
33+
loadIdFailed: (error: string) => void;
34+
checkId: (id: string) => boolean;
35+
}
36+
37+
// Initial state
38+
const characterStore = reactive<Store>({
39+
characters: {
40+
count: 0,
41+
list: [],
42+
isLoading: true,
43+
hasError: false,
44+
errorMessage: null
45+
},
46+
ids: {
47+
isLoading: true,
48+
hasError: false,
49+
errorMessage: null,
50+
list: {}
51+
},
52+
53+
async startLoadingCharacters() {
54+
try {
55+
this.characters.isLoading = true;
56+
const { data } = await rickAndMortyApi.get<ResponseCharacter>("/character");
57+
this.loadCharacters(data.results);
58+
} catch (error) {
59+
if (isAxiosError(error)) this.loadCharactersFailed(error.message);
60+
} finally {
61+
this.characters.isLoading = false;
62+
}
63+
},
64+
loadCharacters(data: Character[]) {
65+
this.characters = {
66+
...this.characters,
67+
list: data,
68+
count: data.length,
69+
isLoading: false,
70+
}
71+
},
72+
loadCharactersFailed(error: string) {
73+
this.characters = {
74+
count: 0,
75+
hasError: true,
76+
errorMessage: error,
77+
isLoading: false,
78+
list: []
79+
}
80+
},
81+
82+
// Ids methods
83+
async startLoadingId(id: string): Promise<void> {
84+
try {
85+
this.ids = {
86+
...this.ids,
87+
isLoading: true,
88+
hasError: false,
89+
errorMessage: null
90+
}
91+
const { data } = await rickAndMortyApi.get<Character>(`character/${id}`);
92+
this.loadId(data);
93+
} catch (error) {
94+
if (isAxiosError(error)) this.loadIdFailed(error.message);
95+
} finally {
96+
this.ids.isLoading = false;
97+
}
98+
},
99+
loadId(data: Character) {
100+
this.ids.list[data.id] = data;
101+
},
102+
loadIdFailed(error: string) {
103+
this.ids = {
104+
...this.ids,
105+
hasError: true,
106+
errorMessage: error,
107+
isLoading: false,
108+
}
109+
},
110+
checkId(id: string) {
111+
return !!this.ids.list[id];
112+
}
113+
})
114+
115+
116+
characterStore.startLoadingCharacters();
117+
118+
export default characterStore;

0 commit comments

Comments
 (0)