Skip to content

Commit ee6a085

Browse files
Make type guards compatible with server components
1 parent 8f998c9 commit ee6a085

File tree

5 files changed

+46
-9
lines changed

5 files changed

+46
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Unreleased
44

5-
This version provides support for NHS.UK frontend v10.x and fixes a Rollup `'use client'` directive issue.
5+
This version provides support for NHS.UK frontend v10.x, React Server Components (RSC) and fixes a Rollup `'use client'` directive issue.
66

77
For a full list of changes in this release please refer to the [migration doc](https://github.com/NHSDigital/nhsuk-react-components/blob/main/docs/upgrade-to-6.0.md).
88

src/components/content-presentation/panel/Panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const PanelComponent = forwardRef<HTMLDivElement, PanelProps>(
99
({ children, className, ...rest }, forwardedRef) => {
1010
const items = Children.toArray(children);
1111
const title = items.find((child) => childIsOfComponentType(child, PanelTitle));
12-
const bodyItems = items.filter((child) => !childIsOfComponentType(child, PanelTitle));
12+
const bodyItems = items.filter((child) => child !== title);
1313

1414
return (
1515
<div className={classNames('nhsuk-panel', className)} ref={forwardedRef} {...rest}>

src/components/form-elements/error-summary/ErrorSummary.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ const ErrorSummaryComponent = forwardRef<HTMLDivElement, ErrorSummaryProps>(
3434
}, [moduleRef, instance]);
3535

3636
const items = Children.toArray(children);
37-
const title = items.find((child) => childIsOfComponentType(child, ErrorSummaryTitle));
38-
const bodyItems = items.filter((child) => !childIsOfComponentType(child, ErrorSummaryTitle));
37+
38+
const title = items.find((child) =>
39+
childIsOfComponentType(child, ErrorSummaryTitle, { className: 'nhsuk-error-summary__title' }),
40+
);
41+
42+
const bodyItems = items.filter((child) => child !== title);
3943

4044
if (instanceError) {
4145
throw instanceError;

src/components/navigation/header/Header.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,16 @@ const HeaderComponent = forwardRef<HTMLElement, HeaderProps>((props, forwardedRe
6868
}, [menuOpen]);
6969

7070
const items = Children.toArray(children);
71-
const childSearch = items.find((child) => childIsOfComponentType(child, HeaderSearch));
72-
const childNavigation = items.find((child) => childIsOfComponentType(child, HeaderNavigation));
73-
const childAccount = items.find((child) => childIsOfComponentType(child, HeaderAccount));
71+
72+
const childSearch = items.find((child) =>
73+
childIsOfComponentType(child, HeaderSearch, { className: 'nhsuk-header__search' }),
74+
);
75+
76+
const childAccount = items.find((child) =>
77+
childIsOfComponentType(child, HeaderAccount, { className: 'nhsuk-header__account' }),
78+
);
79+
80+
const childNavigation = items.find((child) => child !== childSearch && child !== childAccount);
7481

7582
if (instanceError) {
7683
throw instanceError;

src/util/types/TypeGuards.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,40 @@ import {
77
} from 'react';
88
import { type CardType, type CareCardType } from './NHSUKTypes.js';
99

10+
type WithProps<T extends ReactElement> = T & {
11+
props: HTMLAttributes<T>;
12+
};
13+
1014
/**
11-
* Assert that a child item is of the given component type.
15+
* Assert that a child item is a valid component with props.
16+
*/
17+
const isValidComponent = <T extends ReactNode>(
18+
child: T,
19+
): child is WithProps<Extract<T, ReactElement>> =>
20+
isValidElement(child) && !!child.props && typeof child.props === 'object';
21+
22+
/**
23+
* Assert that a child item is of the given component type, optionally
24+
* checking via props for lazy or deferred server components.
1225
*/
1326
export const childIsOfComponentType = <T extends HTMLElement, P extends HTMLAttributes<T>>(
1427
child: ReactNode,
1528
component: FC<P>,
29+
fallback?: Required<Pick<P, 'className'>>,
1630
): child is ReactElement<P, typeof component> => {
17-
return isValidElement<typeof component>(child) && child.type === component;
31+
if (!isValidComponent(child)) {
32+
return false;
33+
}
34+
35+
// Check type for client only components
36+
if (child.type === component) {
37+
return true;
38+
}
39+
40+
// Check props for lazy or deferred server components
41+
return child.props.className && fallback?.className
42+
? child.props.className.split(' ').includes(fallback.className)
43+
: false;
1844
};
1945

2046
/**

0 commit comments

Comments
 (0)