chore(deps): upgrade react to 19.2.7 and react types to 19.x (#1154)

Bump react and react-dom 19.1.2 -> 19.2.7 and @types/react and
@types/react-dom from v18 to v19. Keeps babel-plugin-react-compiler,
which is a separate build-time tool, not part of the React 19 runtime.

The @types v19 upgrade surfaced 21 type-only errors, all fixed without
runtime behavior changes:

- useRef<T>() now requires an explicit argument: pass undefined
- DOM element refs are RefObject<T | null>: widen PostFormFields and
  AdvancedSettings ref prop types accordingly
- retype reply-modal onInput handler to FormEvent + currentTarget
- add a null guard for the boards-bar search input
- update test mocks for stricter cloneElement/forwardRef typings

Verified: type-check, lint, 1051 tests, and build pass; react-doctor
score unchanged at 54; browser smoke across Chromium/Firefox/WebKit
plus mobile viewport clean.
This commit is contained in:
Tommaso Casaburi
2026-06-05 18:43:28 +07:00
committed by GitHub
parent 70ffeb816e
commit 9f58bf7e22
13 changed files with 69 additions and 97 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ const SearchBar = ({ setShowSearchBar }: { setShowSearchBar: (show: boolean) =>
const handleSearchSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const searchInput = searchInputRef.current?.value;
if (searchInput) {
if (searchInput && searchInputRef.current) {
searchInputRef.current.value = '';
navigate(`/${searchInput}`);
setShowSearchBar(false);
@@ -52,7 +52,15 @@ const testState = vi.hoisted(() => ({
}));
vi.mock('react-i18next', () => ({
Trans: ({ components, i18nKey, values }: { components?: Record<number, React.ReactElement>; i18nKey: string; values?: Record<string, unknown> }) =>
Trans: ({
components,
i18nKey,
values,
}: {
components?: Record<number, React.ReactElement<Record<string, unknown>>>;
i18nKey: string;
values?: Record<string, unknown>;
}) =>
createElement(
'span',
{ 'data-testid': `trans-${i18nKey}` },
@@ -34,7 +34,7 @@ const testState = vi.hoisted(() => ({
}));
vi.mock('react-i18next', () => ({
Trans: ({ components, i18nKey }: { components?: Record<number, React.ReactElement>; i18nKey: string }) =>
Trans: ({ components, i18nKey }: { components?: Record<number, React.ReactElement<Record<string, unknown>>>; i18nKey: string }) =>
createElement(
'span',
{ 'data-testid': `trans-${i18nKey}` },
+4 -4
View File
@@ -156,10 +156,10 @@ interface PostFormFieldsProps {
isBbcodePreviewing: boolean;
postCid: string;
subjectRef: React.Ref<HTMLInputElement>;
optionsRef: React.RefObject<HTMLInputElement>;
flagRef: React.RefObject<HTMLSelectElement>;
flashTagRef: React.RefObject<HTMLSelectElement>;
textRef: React.RefObject<HTMLTextAreaElement>;
optionsRef: React.RefObject<HTMLInputElement | null>;
flagRef: React.RefObject<HTMLSelectElement | null>;
flashTagRef: React.RefObject<HTMLSelectElement | null>;
textRef: React.RefObject<HTMLTextAreaElement | null>;
urlRef: React.Ref<HTMLInputElement>;
url: string;
lengthError: string | null;
+3 -3
View File
@@ -358,9 +358,9 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
}
}, [showReplyModal]);
const handleContentInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
lastSelectionStartRef.current = e.target.selectionStart ?? e.target.value.length;
lastSelectionEndRef.current = e.target.selectionEnd ?? lastSelectionStartRef.current;
const handleContentInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
lastSelectionStartRef.current = e.currentTarget.selectionStart ?? e.currentTarget.value.length;
lastSelectionEndRef.current = e.currentTarget.selectionEnd ?? lastSelectionStartRef.current;
};
const handleContentValueChange = (content: string, selectionStart?: number, selectionEnd?: number, options = optionsRef.current?.value || '') => {
@@ -38,19 +38,8 @@ vi.mock('react-router-dom', async () => {
const ReactModule = await vi.importActual<typeof import('react')>('react');
return {
Link: ReactModule.forwardRef(
(
{
children,
to,
...props
}: {
children?: React.ReactNode;
to: string;
[key: string]: unknown;
},
ref,
) => ReactModule.createElement('a', { ...props, href: to, ref }, children),
Link: ReactModule.forwardRef<HTMLAnchorElement, { children?: React.ReactNode; to: string }>(({ children, to, ...props }, ref) =>
ReactModule.createElement('a', { ...props, href: to, ref }, children),
),
useLocation: () => ({
pathname: testState.locationPath,
@@ -6,16 +6,16 @@ import { canConfigureBrowserPureP2P, isBrowserPureP2PEnabled } from '../../../li
import styles from './advanced-settings.module.css';
interface SettingsProps {
ipfsGatewayUrlsRef?: RefObject<HTMLTextAreaElement>;
mediaIpfsGatewayUrlRef?: RefObject<HTMLInputElement>;
pubsubProvidersRef?: RefObject<HTMLTextAreaElement>;
httpRoutersRef?: RefObject<HTMLTextAreaElement>;
ethRpcRef?: RefObject<HTMLTextAreaElement>;
p2pRpcRef?: RefObject<HTMLInputElement>;
p2pDataPathRef?: RefObject<HTMLInputElement>;
ipfsGatewayUrlsRef?: RefObject<HTMLTextAreaElement | null>;
mediaIpfsGatewayUrlRef?: RefObject<HTMLInputElement | null>;
pubsubProvidersRef?: RefObject<HTMLTextAreaElement | null>;
httpRoutersRef?: RefObject<HTMLTextAreaElement | null>;
ethRpcRef?: RefObject<HTMLTextAreaElement | null>;
p2pRpcRef?: RefObject<HTMLInputElement | null>;
p2pDataPathRef?: RefObject<HTMLInputElement | null>;
onPureP2PBrowserChange?: (enabled: boolean) => void;
pureP2PBrowserEnabled?: boolean;
pureP2PBrowserRef?: RefObject<HTMLInputElement>;
pureP2PBrowserRef?: RefObject<HTMLInputElement | null>;
}
type AccountProtocolOptions = {
+1 -1
View File
@@ -74,7 +74,7 @@ const useDeleteFailedPost = (post?: FailedPost, deleteRedirectPath?: string) =>
const [isRetryRedirectPending, setIsRetryRedirectPending] = useState(false);
const addChallenge = useChallengesStore((state) => state.addChallenge);
const navigate = useNavigate();
const abandonPublishRef = useRef<(() => Promise<void>) | undefined>();
const abandonPublishRef = useRef<(() => Promise<void>) | undefined>(undefined);
const abandonCurrentPublish = useCallback(async () => {
await abandonPublishRef.current?.();
}, []);
+1 -1
View File
@@ -25,7 +25,7 @@ const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
const setPublishPostStore = usePublishPostStore((state) => state.setPublishPostStore);
const resetPublishPostStore = usePublishPostStore((state) => state.resetPublishPostStore);
const addChallenge = useChallengesStore((state) => state.addChallenge);
const abandonPublishRef = useRef<(() => Promise<void>) | undefined>();
const abandonPublishRef = useRef<(() => Promise<void>) | undefined>(undefined);
const [publishPostError, setPublishPostError] = useState<string | null>(null);
const [pendingPublishRequestId, setPendingPublishRequestId] = useState(0);
const startedPublishRequestIdRef = useRef(0);
+1 -1
View File
@@ -38,7 +38,7 @@ const usePublishReply = ({ cid, communityAddress, postCid }: UsePublishReplyOpti
const resetPublishReplyStore = usePublishReplyStore((state) => state.resetPublishReplyStore);
const addChallenge = useChallengesStore((state) => state.addChallenge);
const { blockedReason } = usePublishAuthorDomainGuard();
const abandonPublishRef = useRef<(() => Promise<void>) | undefined>();
const abandonPublishRef = useRef<(() => Promise<void>) | undefined>(undefined);
const startedPublishRequestIdRef = useRef(0);
const [resolvedExternalQuotedCids, setResolvedExternalQuotedCids] = useState<string[] | undefined>();
const [pendingPublishRequestId, setPendingPublishRequestId] = useState(0);
+1 -1
View File
@@ -298,7 +298,7 @@ const PostPage = () => {
const communityAddress = resolvedCommunityAddress ?? commentCommunityAddress;
const communityIdentifier = useCommunityIdentifier(communityAddress);
const consumedThreadTopScrollRef = useRef<string | null>(null);
const previousThreadCidRef = useRef<string>();
const previousThreadCidRef = useRef<string>(undefined);
const lastProcessedUpdateRequestIdRef = useRef(0);
const navigate = useNavigate();