Skip to content

Commit 0319b83

Browse files
committed
fix: prevent hydration duplication on pagination table content
1 parent 774d88c commit 0319b83

File tree

5 files changed

+35
-23
lines changed

5 files changed

+35
-23
lines changed

packages/components-vue/src/components/loader/ContentFetch.vue

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<BaseErrorBoundary :theme="theme">
33
<LoaderContent
44
v-bind="{
5-
content: patchedIsContent(content) && (!!fallback || firstLoad || hydrated),
5+
content: patchedIsContent(content),
66
errors,
77
loading,
88
refresh,
@@ -17,7 +17,7 @@
1717
:class="$attrs.class"
1818
>
1919
<slot
20-
v-if="patchedIsContent(content) && (!!fallback || firstLoad || hydrated)"
20+
v-if="patchedIsContent(content)"
2121
v-bind="{ content, refresh, loading, errors }"
2222
></slot>
2323
</LoaderContent>
@@ -100,9 +100,9 @@
100100
defineOptions({ name: "LoaderContentFetch", inheritAttrs: false });
101101
102102
const props = defineProps<iLoaderContentFetchProps<T, P>>();
103-
const emit = defineEmits(["refresh"]);
103+
const emit = defineEmits(["refresh", "has-content"]);
104104
105-
const { logger } = useHelpers(useUtils);
105+
const { logger, isBrowser } = useHelpers(useUtils);
106106
const { useFetch } = useFetchUtils();
107107
const { internals } = inject<iVuePluginOptions>("xamu") || {};
108108
const useAsyncData: typeof useAsyncDataFn = internals?.useAsyncData ?? useAsyncDataFn;
@@ -197,7 +197,13 @@
197197
198198
function patchedIsContent(c?: T | null): c is NonNullable<T> {
199199
// isContent needs to run always
200-
return props.isContent?.(c ?? undefined) ?? !!c;
200+
const isValid = props.isContent?.(c ?? undefined) ?? !!c;
201+
const wasFetched = firstLoad.value || !!props.fallback || hydrated.value;
202+
const isContent = isValid && wasFetched;
203+
204+
if (isBrowser) emit("has-content", isContent);
205+
206+
return isContent;
201207
}
202208
function validatePromiseLike(newPromise: any, oldPromise: any) {
203209
/**

packages/components-vue/src/components/pagination/Content.vue

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
client,
1616
}"
1717
@refresh="$emit('refresh', $event)"
18+
@has-content="$emit('has-content', $event)"
1819
>
1920
<slot
2021
v-bind="{
@@ -121,24 +122,15 @@
121122
*/
122123
123124
defineOptions({ name: "PaginationContent", inheritAttrs: false });
125+
defineEmits(["refresh", "has-content"]);
124126
125127
const props = withDefaults(defineProps<iPCProps<T, C, R>>(), {
126128
processContent: (c: T[]) => c,
127129
});
128-
const emit = defineEmits(["refresh", "hasContent"]);
129130
130131
const { first: defaultFirst } = inject<iPluginOptions>("xamu") || {};
131132
const router = getCurrentInstance()?.appContext.config.globalProperties.$router;
132133
133-
/**
134-
* Patched promise
135-
*/
136-
const patchedPromise: iGetPage<T, C> = async (v) => {
137-
const transform: (r: any) => iPage<T, C> | undefined = props.transform || ((v) => v);
138-
const page = await props.page(v);
139-
140-
return transform(page);
141-
};
142134
const propsPagination = ref<iPagination>({
143135
orderBy: props.orderBy,
144136
first: props.first ?? defaultFirst,
@@ -183,11 +175,17 @@
183175
},
184176
});
185177
186-
function isContent(c?: iPage<T, C>): boolean {
187-
const hasContent = !!c?.edges?.length;
178+
/**
179+
* Patched promise
180+
*/
181+
const patchedPromise: iGetPage<T, C> = async (v) => {
182+
const transform: (r: any) => iPage<T, C> | undefined = props.transform || ((v) => v);
183+
const page = await props.page(v);
188184
189-
emit("hasContent", hasContent);
185+
return transform(page);
186+
};
190187
191-
return hasContent;
188+
function isContent(c?: iPage<T, C>): boolean {
189+
return !!c?.edges?.length;
192190
}
193191
</script>

packages/components-vue/src/components/pagination/ContentTable.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
},
3333
}"
3434
>
35-
<template v-if="$slots.headActions" #headActions>
35+
<template v-if="hasContent && $slots.headActions" #headActions>
3636
<div class="flx --flxRow --flx-start-center --gap-10 --gap:md">
3737
<slot name="headActions" v-bind="{ hasContent, refreshData }"></slot>
3838
</div>
@@ -105,7 +105,7 @@
105105
mapNode: (node: T) => node as unknown as TM,
106106
});
107107
108-
const hasContent = ref(false);
108+
const hasContent = ref(true);
109109
const emittedRefresh = ref();
110110
111111
function refreshData() {

packages/components-vue/src/types/plugin.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@ export type vComponent<P extends Record<string, any> = Record<string, any>> =
99
| DefineComponent<P>;
1010

1111
export interface iVuePluginOptions extends iPluginOptions<vComponent> {
12+
/**
13+
* Override internal behavior
14+
* Useful to setup nuxt modules
15+
*/
1216
internals?: {
17+
/**
18+
* Client only component
19+
*/
20+
clientOnly?: vComponent;
1321
/**
1422
* Nuxt Async data
1523
*/

packages/nuxt/src/runtime/plugins/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import type { iVuePluginOptions } from "@open-xamu-co/ui-components-vue/plugin";
44
import type { tLogger } from "@open-xamu-co/ui-common-types";
55

66
import { defineNuxtPlugin, useAppConfig, useAsyncData } from "#imports";
7-
import { NuxtLink, NuxtImg } from "#components";
7+
import { NuxtLink, NuxtImg, ClientOnly } from "#components";
88

99
export default defineNuxtPlugin(({ vueApp }) => {
1010
const options = useAppConfig().xamu as iVuePluginOptions;
1111
const xamu: iVuePluginOptions = {
1212
routerComponent: NuxtLink,
1313
imageComponent: NuxtImg,
14-
internals: { useAsyncData, ofetch: $fetch },
14+
internals: { useAsyncData, ofetch: $fetch, clientOnly: ClientOnly },
1515
// override defaults
1616
...options,
1717
};

0 commit comments

Comments
 (0)