chore(frontend): Upgrade to React 19 (#5835)

This commit is contained in:
sp.wack 2024-12-27 19:10:41 +04:00 committed by GitHub
parent 5ed80b5c32
commit 9d984aaa30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 127 additions and 155 deletions

View File

@ -1,14 +1,13 @@
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import Layout from "@theme/Layout";
import { HomepageHeader } from "../components/HomepageHeader/HomepageHeader";
import { Welcome } from "../components/Welcome/Welcome";
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import { HomepageHeader } from '../components/HomepageHeader/HomepageHeader';
import { translate } from '@docusaurus/Translate';
export function Header({ title, summary }): JSX.Element {
return (
<div>
<h1>{title}</h1>
<h2 style={{ fontSize: "3rem" }}>{summary}</h2>
<h2 style={{ fontSize: '3rem' }}>{summary}</h2>
</div>
);
}
@ -23,7 +22,7 @@ export default function Home(): JSX.Element {
message: 'Code Less, Make More',
})}
>
<HomepageHeader />
<HomepageHeader />
</Layout>
);
}

View File

@ -8,7 +8,7 @@
"name": "openhands-frontend",
"version": "0.17.0",
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"@monaco-editor/react": "^4.7.0-rc.0",
"@nextui-org/react": "^2.6.10",
"@react-router/node": "^7.1.1",
"@react-router/serve": "^7.1.1",
@ -28,8 +28,8 @@
"jose": "^5.9.4",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.203.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-highlight": "^0.15.0",
"react-hot-toast": "^2.4.1",
"react-i18next": "^15.2.0",
@ -56,8 +56,8 @@
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.10.2",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@types/react-highlight": "^0.12.8",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/ws": "^8.5.12",
@ -1595,17 +1595,17 @@
}
},
"node_modules/@monaco-editor/react": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz",
"integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==",
"version": "4.7.0-rc.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0-rc.0.tgz",
"integrity": "sha512-YfjXkDK0bcwS0zo8PXptvQdCQfOPPtzGsAzmIv7PnoUGFdIohsR+NVDyjbajMddF+3cWUm/3q9NzP/DUke9a+w==",
"license": "MIT",
"dependencies": {
"@monaco-editor/loader": "^1.4.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mswjs/interceptors": {
@ -2241,6 +2241,23 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@nextui-org/listbox/node_modules/@tanstack/react-virtual": {
"version": "3.10.9",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.9.tgz",
"integrity": "sha512-OXO2uBjFqA4Ibr2O3y0YMnkrRWGVNqcvHQXmGvMu6IK8chZl3PrDxFXdGZ2iZkSrKh3/qUYoFqYe+Rx23RoU0g==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.10.9"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@nextui-org/menu": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/@nextui-org/menu/-/menu-2.2.8.tgz",
@ -2579,6 +2596,23 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@nextui-org/select/node_modules/@tanstack/react-virtual": {
"version": "3.10.9",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.9.tgz",
"integrity": "sha512-OXO2uBjFqA4Ibr2O3y0YMnkrRWGVNqcvHQXmGvMu6IK8chZl3PrDxFXdGZ2iZkSrKh3/qUYoFqYe+Rx23RoU0g==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.10.9"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@nextui-org/shared-icons": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@nextui-org/shared-icons/-/shared-icons-2.1.1.tgz",
@ -5336,23 +5370,6 @@
"react": "^18 || ^19"
}
},
"node_modules/@tanstack/react-virtual": {
"version": "3.10.9",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.9.tgz",
"integrity": "sha512-OXO2uBjFqA4Ibr2O3y0YMnkrRWGVNqcvHQXmGvMu6IK8chZl3PrDxFXdGZ2iZkSrKh3/qUYoFqYe+Rx23RoU0g==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.10.9"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.10.9",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.9.tgz",
@ -5603,30 +5620,23 @@
"undici-types": "~6.20.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
"version": "19.0.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz",
"integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==",
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.5",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
"integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
"version": "19.0.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz",
"integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
"@types/react": "^19.0.0"
}
},
"node_modules/@types/react-highlight": {
@ -11544,6 +11554,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@ -14067,28 +14078,24 @@
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
"scheduler": "^0.25.0"
},
"peerDependencies": {
"react": "^18.3.1"
"react": "^19.0.0"
}
},
"node_modules/react-highlight": {
@ -14926,13 +14933,10 @@
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT"
},
"node_modules/scroll-into-view-if-needed": {
"version": "3.0.10",

View File

@ -7,7 +7,7 @@
"node": ">=20.0.0"
},
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"@monaco-editor/react": "^4.7.0-rc.0",
"@nextui-org/react": "^2.6.10",
"@react-router/node": "^7.1.1",
"@react-router/serve": "^7.1.1",
@ -27,8 +27,8 @@
"jose": "^5.9.4",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.203.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-highlight": "^0.15.0",
"react-hot-toast": "^2.4.1",
"react-i18next": "^15.2.0",
@ -83,8 +83,8 @@
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.10.2",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@types/react-highlight": "^0.12.8",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/ws": "^8.5.12",

View File

@ -1,6 +1,6 @@
import React from "react";
function ArrowIcon(): JSX.Element {
function ArrowIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -1,6 +1,6 @@
import React from "react";
function CogTooth(): JSX.Element {
function CogTooth() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -1,6 +1,6 @@
import React from "react";
function ConfirmIcon(): JSX.Element {
function ConfirmIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -1,6 +1,6 @@
import React from "react";
function PauseIcon(): JSX.Element {
function PauseIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -1,6 +1,6 @@
import React from "react";
function PlayIcon(): JSX.Element {
function PlayIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -1,6 +1,6 @@
import React from "react";
function RejectIcon(): JSX.Element {
function RejectIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -1,6 +1,6 @@
import React from "react";
function StopIcon(): JSX.Element {
function StopIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -8,7 +8,7 @@ import {
FaPython,
} from "react-icons/fa";
export const EXTENSION_ICON_MAP: Record<string, JSX.Element> = {
export const EXTENSION_ICON_MAP: Record<string, React.ReactNode> = {
js: <DiJavascript />,
ts: <DiJavascript />,
py: <FaPython />,

View File

@ -2,13 +2,19 @@ import React from "react";
import { cn } from "#/utils/utils";
interface ContextMenuProps {
ref: React.RefObject<HTMLUListElement | null>;
testId?: string;
children: React.ReactNode;
className?: React.HTMLAttributes<HTMLUListElement>["className"];
}
export const ContextMenu = React.forwardRef<HTMLUListElement, ContextMenuProps>(
({ testId, children, className }, ref) => (
export function ContextMenu({
testId,
children,
className,
ref,
}: ContextMenuProps) {
return (
<ul
data-testid={testId}
ref={ref}
@ -16,7 +22,5 @@ export const ContextMenu = React.forwardRef<HTMLUListElement, ContextMenuProps>(
>
{children}
</ul>
),
);
ContextMenu.displayName = "ContextMenu";
);
}

View File

@ -4,7 +4,7 @@ interface FolderIconProps {
isOpen: boolean;
}
export function FolderIcon({ isOpen }: FolderIconProps): JSX.Element {
export function FolderIcon({ isOpen }: FolderIconProps) {
return isOpen ? (
<FaFolderOpen color="D9D3D0" className="icon" />
) : (

View File

@ -4,7 +4,7 @@ interface InvariantLogoIconProps {
className?: string;
}
function InvariantLogoIcon({ className }: InvariantLogoIconProps): JSX.Element {
function InvariantLogoIcon({ className }: InvariantLogoIconProps) {
return (
<svg
width="39"

View File

@ -24,7 +24,7 @@ import { useGetTraces } from "#/hooks/query/use-get-traces";
type SectionType = "logs" | "policy" | "settings";
function SecurityInvariant(): JSX.Element {
function SecurityInvariant() {
const { t } = useTranslation();
const { logs } = useSelector((state: RootState) => state.securityAnalyzer);
@ -122,7 +122,7 @@ function SecurityInvariant(): JSX.Element {
[],
);
const sections: { [key in SectionType]: JSX.Element } = {
const sections: Record<SectionType, React.ReactNode> = {
logs: (
<>
<div className="flex justify-between items-center border-b border-neutral-600 mb-4 p-4">

View File

@ -24,7 +24,11 @@ import { ImageCarousel } from "../features/images/image-carousel";
import { UploadImageInput } from "../features/images/upload-image-input";
import { LoadingSpinner } from "./loading-spinner";
export const TaskForm = React.forwardRef<HTMLFormElement>((_, ref) => {
interface TaskFormProps {
ref: React.RefObject<HTMLFormElement | null>;
}
export function TaskForm({ ref }: TaskFormProps) {
const dispatch = useDispatch();
const navigation = useNavigation();
const navigate = useNavigate();
@ -161,6 +165,4 @@ export const TaskForm = React.forwardRef<HTMLFormElement>((_, ref) => {
)}
</div>
);
});
TaskForm.displayName = "TaskForm";
}

View File

@ -102,7 +102,7 @@ function AuthProvider({ children }: React.PropsWithChildren) {
[gitHubTokenState],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
return <AuthContext value={value}>{children}</AuthContext>;
}
function useAuth() {

View File

@ -24,11 +24,7 @@ export function ConversationProvider({
const value = useMemo(() => ({ conversationId }), [conversationId]);
return (
<ConversationContext.Provider value={value}>
{children}
</ConversationContext.Provider>
);
return <ConversationContext value={value}>{children}</ConversationContext>;
}
export function useConversation() {

View File

@ -55,9 +55,7 @@ function FilesProvider({ children }: FilesProviderProps) {
[paths, setPaths, files, setFileContent, selectedPath, setSelectedPath],
);
return (
<FilesContext.Provider value={value}>{children}</FilesContext.Provider>
);
return <FilesContext value={value}>{children}</FilesContext>;
}
function useFiles() {

View File

@ -54,11 +54,7 @@ function SettingsProvider({ children }: React.PropsWithChildren) {
[settings, settingsAreUpToDate],
);
return (
<SettingsContext.Provider value={value}>
{children}
</SettingsContext.Provider>
);
return <SettingsContext value={value}>{children}</SettingsContext>;
}
function useSettings() {

View File

@ -151,11 +151,7 @@ export function WsClientProvider({
[status, messageRateHandler.isUnderThreshold, events],
);
return (
<WsClientContext.Provider value={value}>
{children}
</WsClientContext.Provider>
);
return <WsClientContext value={value}>{children}</WsClientContext>;
}
export function useWsClient() {

View File

@ -20,7 +20,7 @@ export function useDownloadProgress(
const [progress, setProgress] =
useState<DownloadProgressState>(INITIAL_PROGRESS);
const progressRef = useRef<DownloadProgressState>(INITIAL_PROGRESS);
const abortController = useRef<AbortController>();
const abortController = useRef<AbortController>(null);
const { conversationId } = useConversation();
// Create AbortController on mount
@ -31,7 +31,7 @@ export function useDownloadProgress(
progressRef.current = INITIAL_PROGRESS;
return () => {
controller.abort();
abortController.current = undefined;
abortController.current = null;
};
}, []); // Empty deps array - only run on mount/unmount

View File

@ -1,6 +1,6 @@
import { RefObject, useEffect, useState } from "react";
export function useScrollToBottom(scrollRef: RefObject<HTMLDivElement>) {
export function useScrollToBottom(scrollRef: RefObject<HTMLDivElement | null>) {
// for auto-scroll
const [autoScroll, setAutoScroll] = useState(true);

View File

@ -1,17 +0,0 @@
import toast from "react-hot-toast";
import { ErrorToast } from "#/components/shared/error-toast";
export const displayErrorToast = (error: string) =>
toast((t) => <ErrorToast id={t.id} error={error} />, {
style: {
background: "#C63143",
color: "#fff",
fontSize: "12px",
fontWeight: "500",
lineHeight: "20px",
borderRadius: "4px",
width: "336px",
},
duration: Infinity,
position: "bottom-right",
});

View File

@ -3,14 +3,12 @@
import React, { PropsWithChildren } from "react";
import { Provider } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
// eslint-disable-next-line import/no-extraneous-dependencies
import { RenderOptions, render } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { I18nextProvider } from "react-i18next";
import { I18nextProvider, initReactI18next } from "react-i18next";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { AppStore, RootState, rootReducer } from "./src/store";
import { vi } from "vitest";
import { AppStore, RootState, rootReducer } from "./src/store";
import { AuthProvider } from "#/context/auth-context";
import { SettingsProvider } from "#/context/settings-context";
import { ConversationProvider } from "#/context/conversation-context";
@ -26,22 +24,20 @@ vi.mock("react-router", async () => {
});
// Initialize i18n for tests
i18n
.use(initReactI18next)
.init({
lng: "en",
fallbackLng: "en",
ns: ["translation"],
defaultNS: "translation",
resources: {
en: {
translation: {},
},
i18n.use(initReactI18next).init({
lng: "en",
fallbackLng: "en",
ns: ["translation"],
defaultNS: "translation",
resources: {
en: {
translation: {},
},
interpolation: {
escapeValue: false,
},
});
},
interpolation: {
escapeValue: false,
},
});
const setupStore = (preloadedState?: Partial<RootState>): AppStore =>
configureStore({
@ -67,16 +63,14 @@ export function renderWithProviders(
...renderOptions
}: ExtendedRenderOptions = {},
) {
function Wrapper({ children }: PropsWithChildren<object>): JSX.Element {
function Wrapper({ children }: PropsWithChildren) {
return (
<Provider store={store}>
<QueryClientProvider client={new QueryClient()}>
<SettingsProvider>
<AuthProvider>
<ConversationProvider>
<I18nextProvider i18n={i18n}>
{children}
</I18nextProvider>
<I18nextProvider i18n={i18n}>{children}</I18nextProvider>
</ConversationProvider>
</AuthProvider>
</SettingsProvider>