Skip to content

Commit 95cacbe

Browse files
authored
fix: Resolve dynamic templates to inner component before rendering to… (#2296)
* fix: resolve dynamic templates to inner component before rendering to prevent hydration mismatches and double renders * fix: add isDynamic to effect deps * ci: add fallback dependency installation for lint and format checks
1 parent 3a83d28 commit 95cacbe

6 files changed

Lines changed: 50 additions & 25 deletions

File tree

.changeset/shaky-toes-open.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@faustwp/core': patch
3+
---
4+
5+
Fixed an issue where dynamic template components were rendered via the next/dynamic wrapper directly, causing hydration mismatches and double renders, by resolving the dynamic component to its inner function and storing it in state before rendering.

.github/workflows/check-packages.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ jobs:
2626
uses: actions/checkout@v4
2727
- name: Set up Node.js
2828
uses: ./.github/actions/cache-restore
29+
id: cache-node-modules
30+
- name: Install dependencies
31+
if: steps.cache-node-modules.outputs.cache-hit != 'true'
32+
run: npm ci
2933
- name: Check Linting
3034
run: npm run lint
3135
check_format:
@@ -37,5 +41,9 @@ jobs:
3741
uses: actions/checkout@v4
3842
- name: Set up Node.js
3943
uses: ./.github/actions/cache-restore
44+
id: cache-node-modules
45+
- name: Install dependencies
46+
if: steps.cache-node-modules.outputs.cache-hit != 'true'
47+
run: npm ci
4048
- name: Check Formatting
4149
run: npm run test:format

examples/next/faustwp-getting-started/wp-templates/category.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const GET_CATEGORY_QUERY = gql`
4343

4444
export default function Component(props) {
4545
const { generalSettings, headerMenuItems, footerMenuItems } =
46-
useFaustQuery(GET_LAYOUT_QUERY);
46+
useFaustQuery(GET_LAYOUT_QUERY) ?? {};
4747
const { nodeByUri } = useFaustQuery(GET_CATEGORY_QUERY) ?? {};
4848

4949
const { title: siteTitle, description: siteDescription } =

examples/next/faustwp-getting-started/wp-templates/index.js

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,10 @@
11
import dynamic from 'next/dynamic';
22
import { default as FrontPage } from './front-page.js';
33

4-
const category = dynamic(() => import('./category.js'), {
5-
loading: () => <p>Loading Category Template...</p>,
6-
ssr: false,
7-
});
8-
9-
const tag = dynamic(() => import('./tag.js'), {
10-
loading: () => <p>Loading Tag Template...</p>,
11-
ssr: false,
12-
});
13-
14-
const page = dynamic(() => import('./page.js'), {
15-
loading: () => <p>Loading Page Template...</p>,
16-
ssr: false,
17-
});
18-
19-
const single = dynamic(() => import('./single.js'), {
20-
loading: () => <p>Loading Single Post Template...</p>,
21-
ssr: false,
22-
});
4+
const category = dynamic(() => import('./category.js'));
5+
const tag = dynamic(() => import('./tag.js'));
6+
const page = dynamic(() => import('./page.js'));
7+
const single = dynamic(() => import('./single.js'));
238

249
export default {
2510
category,

examples/next/faustwp-getting-started/wp-templates/page.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const GET_PAGE_QUERY = gql`
2626

2727
export default function Component(props) {
2828
// Loading state for previews
29-
if (props.loading) {
29+
if (props?.loading) {
3030
return <>Loading...</>;
3131
}
3232

packages/faustwp-core/src/components/WordPressTemplate.tsx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ export function WordPressTemplateInternal(
7676
...wordpressTemplateProps
7777
} = props;
7878
const unknownTemplate = getTemplate(seedNode, templates);
79+
const isDynamic = isDynamicComponent(unknownTemplate);
7980
const [data, setData] = useState<any | null>(templateQueryDataProp);
81+
const [resolvedTemplate, setResolvedTemplate] =
82+
useState<WordPressTemplateType | null>(null);
8083
const { setQueries } = useContext(FaustContext) || {};
8184

8285
/**
@@ -88,7 +91,7 @@ export function WordPressTemplateInternal(
8891
return;
8992
}
9093

91-
const template = isDynamicComponent(unknownTemplate)
94+
const template = isDynamic
9295
? await loadDynamicComponent(unknownTemplate)
9396
: unknownTemplate;
9497

@@ -141,6 +144,7 @@ export function WordPressTemplateInternal(
141144
unknownTemplate,
142145
setQueries,
143146
setLoading,
147+
isDynamic,
144148
]);
145149

146150
/**
@@ -152,7 +156,7 @@ export function WordPressTemplateInternal(
152156
return;
153157
}
154158

155-
const template = isDynamicComponent(unknownTemplate)
159+
const template = isDynamic
156160
? await loadDynamicComponent(unknownTemplate)
157161
: unknownTemplate;
158162

@@ -183,13 +187,36 @@ export function WordPressTemplateInternal(
183187

184188
setLoading(false);
185189
})();
186-
}, [data, unknownTemplate, seedNode, isPreview, isAuthenticated, setLoading]);
190+
}, [
191+
data,
192+
unknownTemplate,
193+
seedNode,
194+
isPreview,
195+
isAuthenticated,
196+
setLoading,
197+
isDynamic,
198+
]);
199+
200+
useEffect(() => {
201+
if (!unknownTemplate || !isDynamic) {
202+
return;
203+
}
204+
void loadDynamicComponent(unknownTemplate).then((template) => {
205+
setResolvedTemplate(() => template);
206+
});
207+
}, [unknownTemplate, isDynamic]);
187208

188209
if (!unknownTemplate) {
189210
return null;
190211
}
191212

192-
const Component = unknownTemplate as React.FC<{ [key: string]: any }>;
213+
if (isDynamic && !resolvedTemplate) {
214+
return null;
215+
}
216+
217+
const Component = (
218+
isDynamic ? resolvedTemplate : unknownTemplate
219+
) as React.FC<{ [key: string]: any }>;
193220
const newProps = {
194221
...wordpressTemplateProps,
195222
__TEMPLATE_QUERY_DATA__: templateQueryDataProp,

0 commit comments

Comments
 (0)