Skip to content

Commit a8d56be

Browse files
Support React strict mode, prepare beta release (#296)
* Run all tests in React strict mode * Add support for React strict mode * Add changelog entry * Update package version to v6.0.0-beta.3
1 parent ad8131a commit a8d56be

File tree

16 files changed

+136
-57
lines changed

16 files changed

+136
-57
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# NHS.UK React components
22

3+
## 6.0.0-beta.3 - 27 October 2025
4+
5+
This version provides support for NHS.UK frontend v10.1 and includes:
6+
7+
- [Smaller radios](https://service-manual.nhs.uk/design-system/components/radios#smaller-radios) and [smaller checkboxes](https://service-manual.nhs.uk/design-system/components/checkboxes#smaller-checkboxes)
8+
- [Numbered pagination](https://service-manual.nhs.uk/design-system/components/pagination#for-navigating-between-pages-of-items)
9+
- React strict mode support
10+
11+
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).
12+
313
## 6.0.0-beta.2 - 13 October 2025
414

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nhsuk-react-components",
3-
"version": "6.0.0-beta.2",
3+
"version": "6.0.0-beta.3",
44
"license": "MIT",
55
"author": {
66
"name": "NHS England"

src/components/content-presentation/notification-banner/NotificationBanner.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import {
44
Children,
5-
createRef,
65
forwardRef,
76
useEffect,
7+
useImperativeHandle,
8+
useRef,
89
useState,
910
type ComponentPropsWithoutRef,
1011
} from 'react';
@@ -27,19 +28,22 @@ const NotificationBannerComponent = forwardRef<HTMLDivElement, NotificationBanne
2728
(props, forwardedRef) => {
2829
const { children, className, title, titleId, success, role, disableAutoFocus, ...rest } = props;
2930

30-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
31+
const moduleRef = useRef<HTMLDivElement>(null);
32+
const importRef = useRef<Promise<NotificationBannerModule | void>>(null);
3133
const [instanceError, setInstanceError] = useState<Error>();
3234
const [instance, setInstance] = useState<NotificationBannerModule>();
3335

36+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
37+
3438
useEffect(() => {
35-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
39+
if (!moduleRef.current || importRef.current || instance) {
3640
return;
3741
}
3842

39-
import('nhsuk-frontend')
43+
importRef.current = import('nhsuk-frontend')
4044
.then(({ NotificationBanner }) => setInstance(new NotificationBanner(moduleRef.current)))
4145
.catch(setInstanceError);
42-
}, [moduleRef, instance]);
46+
}, [moduleRef, importRef, instance]);
4347

4448
const items = Children.toArray(children);
4549

src/components/content-presentation/table/components/__tests__/TableCell.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('Table.Cell', () => {
2929
</table>,
3030
);
3131

32-
expect(console.warn).toHaveBeenCalledTimes(1);
32+
expect(console.warn).toHaveBeenCalled();
3333
expect(console.warn).toHaveBeenLastCalledWith(
3434
'Table.Cell used outside of a Table.Head or Table.Body component. Unable to determine section type from context.',
3535
);

src/components/content-presentation/tabs/Tabs.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import classNames from 'classnames';
44
import { type Tabs as TabsModule } from 'nhsuk-frontend';
55
import {
6-
createRef,
76
forwardRef,
87
useEffect,
8+
useImperativeHandle,
9+
useRef,
910
useState,
1011
type ComponentPropsWithoutRef,
1112
type FC,
@@ -55,19 +56,22 @@ export const TabsContents: FC<TabsContentsProps> = ({ children, id, ...rest }) =
5556
const TabsComponent = forwardRef<HTMLDivElement, TabsProps>((props, forwardedRef) => {
5657
const { children, className, ...rest } = props;
5758

58-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
59+
const moduleRef = useRef<HTMLDivElement>(null);
60+
const importRef = useRef<Promise<TabsModule | void>>(null);
5961
const [instanceError, setInstanceError] = useState<Error>();
6062
const [instance, setInstance] = useState<TabsModule>();
6163

64+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
65+
6266
useEffect(() => {
63-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
67+
if (!moduleRef.current || importRef.current || instance) {
6468
return;
6569
}
6670

67-
import('nhsuk-frontend')
71+
importRef.current = import('nhsuk-frontend')
6872
.then(({ Tabs }) => setInstance(new Tabs(moduleRef.current)))
6973
.catch(setInstanceError);
70-
}, [moduleRef, instance]);
74+
}, [moduleRef, importRef, instance]);
7175

7276
if (instanceError) {
7377
throw instanceError;

src/components/form-elements/button/Button.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import classNames from 'classnames';
44
import { type Button as ButtonModule } from 'nhsuk-frontend';
55
import {
6-
createRef,
76
forwardRef,
87
useEffect,
8+
useImperativeHandle,
9+
useRef,
910
useState,
1011
type ForwardedRef,
1112
type MouseEvent,
@@ -45,19 +46,22 @@ const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>((props, forwa
4546
...rest
4647
} = props;
4748

48-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLButtonElement>());
49+
const moduleRef = useRef<HTMLButtonElement>(null);
50+
const importRef = useRef<Promise<ButtonModule | void>>(null);
4951
const [instanceError, setInstanceError] = useState<Error>();
5052
const [instance, setInstance] = useState<ButtonModule>();
5153

54+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
55+
5256
useEffect(() => {
53-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
57+
if (!moduleRef.current || importRef.current || instance) {
5458
return;
5559
}
5660

57-
import('nhsuk-frontend')
61+
importRef.current = import('nhsuk-frontend')
5862
.then(({ Button }) => setInstance(new Button(moduleRef.current)))
5963
.catch(setInstanceError);
60-
}, [moduleRef, instance]);
64+
}, [moduleRef, importRef, instance]);
6165

6266
if (instanceError) {
6367
throw instanceError;
@@ -104,19 +108,22 @@ const ButtonLinkComponent = forwardRef<HTMLAnchorElement, ButtonLinkProps>(
104108
...rest
105109
} = props;
106110

107-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLAnchorElement>());
111+
const moduleRef = useRef<HTMLAnchorElement>(null);
112+
const importRef = useRef<Promise<ButtonModule | void>>(null);
108113
const [instanceError, setInstanceError] = useState<Error>();
109114
const [instance, setInstance] = useState<ButtonModule>();
110115

116+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
117+
111118
useEffect(() => {
112-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
119+
if (!moduleRef.current || importRef.current || instance) {
113120
return;
114121
}
115122

116-
import('nhsuk-frontend')
123+
importRef.current = import('nhsuk-frontend')
117124
.then(({ Button }) => setInstance(new Button(moduleRef.current)))
118125
.catch(setInstanceError);
119-
}, [moduleRef, instance]);
126+
}, [moduleRef, importRef, instance]);
120127

121128
if (instanceError) {
122129
throw instanceError;

src/components/form-elements/character-count/CharacterCount.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
import classNames from 'classnames';
44
import { type CharacterCount as CharacterCountModule } from 'nhsuk-frontend';
5-
import { createRef, forwardRef, useEffect, useState, type ComponentPropsWithoutRef } from 'react';
5+
import {
6+
forwardRef,
7+
useEffect,
8+
useImperativeHandle,
9+
useRef,
10+
useState,
11+
type ComponentPropsWithoutRef,
12+
} from 'react';
613
import { FormGroup } from '#components/utils/index.js';
714
import { type FormElementProps } from '#util/types/FormTypes.js';
815

@@ -16,19 +23,22 @@ export interface CharacterCountProps
1623

1724
export const CharacterCount = forwardRef<HTMLTextAreaElement, CharacterCountProps>(
1825
({ maxLength, maxWords, threshold, formGroupProps, ...rest }, forwardedRef) => {
19-
const [moduleRef] = useState(() => formGroupProps?.ref || createRef<HTMLDivElement>());
26+
const moduleRef = useRef<HTMLDivElement>(null);
27+
const importRef = useRef<Promise<CharacterCountModule | void>>(null);
2028
const [instanceError, setInstanceError] = useState<Error>();
2129
const [instance, setInstance] = useState<CharacterCountModule>();
2230

31+
useImperativeHandle(formGroupProps?.ref, () => moduleRef.current!, [moduleRef]);
32+
2333
useEffect(() => {
24-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
34+
if (!moduleRef.current || importRef.current || instance) {
2535
return;
2636
}
2737

28-
import('nhsuk-frontend')
38+
importRef.current = import('nhsuk-frontend')
2939
.then(({ CharacterCount }) => setInstance(new CharacterCount(moduleRef.current)))
3040
.catch(setInstanceError);
31-
}, [moduleRef, instance]);
41+
}, [moduleRef, importRef, instance]);
3242

3343
if (instanceError) {
3444
throw instanceError;

src/components/form-elements/checkboxes/Checkboxes.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
import classNames from 'classnames';
44
import { type Checkboxes as CheckboxesModule } from 'nhsuk-frontend';
5-
import { createRef, forwardRef, useEffect, useState, type ComponentPropsWithoutRef } from 'react';
5+
import {
6+
forwardRef,
7+
useEffect,
8+
useImperativeHandle,
9+
useRef,
10+
useState,
11+
type ComponentPropsWithoutRef,
12+
} from 'react';
613
import { CheckboxesDivider, CheckboxesItem } from './components/index.js';
714
import { CheckboxesContext, type ICheckboxesContext } from './CheckboxesContext.js';
815
import { FormGroup } from '#components/utils/index.js';
@@ -19,23 +26,26 @@ export interface CheckboxesProps
1926
const CheckboxesComponent = forwardRef<HTMLDivElement, CheckboxesProps>((props, forwardedRef) => {
2027
const { children, idPrefix, ...rest } = props;
2128

22-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
29+
const moduleRef = useRef<HTMLDivElement>(null);
30+
const importRef = useRef<Promise<CheckboxesModule | void>>(null);
2331
const [instanceError, setInstanceError] = useState<Error>();
2432
const [instance, setInstance] = useState<CheckboxesModule>();
2533

2634
const _boxReferences: string[] = [];
2735
let _boxCount: number = 0;
2836
let _boxIds: Record<string, string> = {};
2937

38+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
39+
3040
useEffect(() => {
31-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
41+
if (!moduleRef.current || importRef.current || instance) {
3242
return;
3343
}
3444

35-
import('nhsuk-frontend')
45+
importRef.current = import('nhsuk-frontend')
3646
.then(({ Checkboxes }) => setInstance(new Checkboxes(moduleRef.current)))
3747
.catch(setInstanceError);
38-
}, [moduleRef, instance]);
48+
}, [moduleRef, importRef, instance]);
3949

4050
const getBoxId = (id: string, reference: string): string => {
4151
if (reference in _boxIds) {

src/components/form-elements/date-input/DateInput.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import classNames from 'classnames';
44
import {
5-
createRef,
65
forwardRef,
6+
useImperativeHandle,
7+
useRef,
78
useState,
89
type ChangeEvent,
910
type ComponentPropsWithoutRef,
@@ -43,7 +44,9 @@ export type DateInputType = 'day' | 'month' | 'year';
4344

4445
const DateInputComponent = forwardRef<HTMLDivElement, DateInputProps>(
4546
({ children, onChange, value, defaultValue, formGroupProps, ...rest }, forwardedRef) => {
46-
const [moduleRef] = useState(() => formGroupProps?.ref || createRef<HTMLDivElement>());
47+
const moduleRef = useRef<HTMLDivElement>(null);
48+
49+
useImperativeHandle(formGroupProps?.ref, () => moduleRef.current!, [moduleRef]);
4750

4851
const [internalDate, setInternalDate] = useState<DateInputValue>({
4952
day: value?.day ?? '',

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import classNames from 'classnames';
44
import { type ErrorSummary as ErrorSummaryModule } from 'nhsuk-frontend';
55
import {
66
Children,
7-
createRef,
87
forwardRef,
98
useEffect,
9+
useImperativeHandle,
10+
useRef,
1011
useState,
1112
type ComponentPropsWithoutRef,
1213
} from 'react';
@@ -19,19 +20,22 @@ export interface ErrorSummaryProps extends ComponentPropsWithoutRef<'div'> {
1920

2021
const ErrorSummaryComponent = forwardRef<HTMLDivElement, ErrorSummaryProps>(
2122
({ children, className, disableAutoFocus, ...rest }, forwardedRef) => {
22-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
23+
const moduleRef = useRef<HTMLDivElement>(null);
24+
const importRef = useRef<Promise<ErrorSummaryModule | void>>(null);
2325
const [instanceError, setInstanceError] = useState<Error>();
2426
const [instance, setInstance] = useState<ErrorSummaryModule>();
2527

28+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
29+
2630
useEffect(() => {
27-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
31+
if (!moduleRef.current || importRef.current || instance) {
2832
return;
2933
}
3034

31-
import('nhsuk-frontend')
35+
importRef.current = import('nhsuk-frontend')
3236
.then(({ ErrorSummary }) => setInstance(new ErrorSummary(moduleRef.current)))
3337
.catch(setInstanceError);
34-
}, [moduleRef, instance]);
38+
}, [moduleRef, importRef, instance]);
3539

3640
const items = Children.toArray(children);
3741

0 commit comments

Comments
 (0)