Upgrade to React 18 (#4272)

This commit is contained in:
Jon Koops 2023-02-13 08:18:16 +01:00 committed by GitHub
parent a07931e1ef
commit 73ecede289
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 575 additions and 838 deletions

View file

@ -10,14 +10,6 @@ updates:
labels:
- area/dependencies
ignore:
- dependency-name: react
update-types: ["version-update:semver-major"]
- dependency-name: react-dom
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react-dom"
update-types: ["version-update:semver-major"]
- dependency-name: vite
update-types: ["version-update:semver-major"]
- dependency-name: "@vitejs/plugin-react"

View file

@ -14,16 +14,16 @@
"i18next-http-backend": "^2.1.1",
"keycloak-js": "999.0.0-dev",
"keycloak-masthead": "999.0.0-dev",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.1",
"react-i18next": "^12.1.5",
"react-router-dom": "6.8.1",
"ui-shared": "999.0.0-dev"
},
"devDependencies": {
"@types/react": "^17.0.53",
"@types/react-dom": "^17.0.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^2.2.0",
"vite": "^3.2.5",
"vite-plugin-checker": "^0.5.5"

View file

@ -11,5 +11,8 @@ declare module "i18next" {
resources: {
translation: typeof translation;
};
// TODO: This flag should be removed and code that errors out should be made functional.
// This will have to be done incrementally as the amount of errors the default produces is just too much.
allowObjectInHTMLChildren: true;
}
}

View file

@ -19,10 +19,11 @@ await Promise.all([
]);
const router = createBrowserRouter(routes);
const container = document.getElementById("app");
render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
document.getElementById("app")
container
);

View file

@ -100,7 +100,7 @@ export const PermissionRequest = ({
<Td>
{shareRequest.scopes.map((scope) => (
<Chip key={scope.toString()} isReadOnly>
{scope}
{scope as string}
</Chip>
))}
</Td>

View file

@ -173,9 +173,6 @@ describe("Client authentication subtab", () => {
"Successfully created the permission",
true
);
cy.wait(["@load"]);
sidebarPage.waitForPageLoad();
authenticationTab.formUtils().cancel();
});

View file

@ -46,7 +46,8 @@ class CreateRealmRolePage {
}
updateDescription(description: string) {
cy.get(this.realmRoleDescriptionInput).clear().type(description);
cy.get(this.realmRoleDescriptionInput).clear();
cy.get(this.realmRoleDescriptionInput).type(description);
return this;
}

View file

@ -75,8 +75,8 @@
"i18next-http-backend": "^2.1.1",
"keycloak-js": "999.0.0-dev",
"lodash-es": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.43.1",
@ -93,14 +93,13 @@
"@cypress/webpack-preprocessor": "^5.16.3",
"@testing-library/cypress": "^9.0.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/react": "^13.4.0",
"@types/dagre": "^0.7.48",
"@types/file-saver": "^2.0.5",
"@types/flat": "^5.0.2",
"@types/lodash-es": "^4.17.6",
"@types/react": "^17.0.53",
"@types/react-dom": "^17.0.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^2.2.0",
"cypress": "^12.5.1",
"del": "^7.0.0",

View file

@ -12,7 +12,7 @@ import {
PageHeaderToolsItem,
} from "@patternfly/react-core";
import { HelpIcon } from "@patternfly/react-icons";
import { useState } from "react";
import { ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { HelpHeader, useHelp } from "./components/help-enabler/HelpHeader";
@ -57,9 +57,12 @@ const ServerInfoDropdownItem = () => {
return (
<DropdownItem
key="server info"
component={(props: any) => (
<Link {...props} to={toDashboard({ realm })} />
)}
component={
// The type definition in PatternFly is incorrect, so we need to cast here.
((props: any) => (
<Link {...props} to={toDashboard({ realm })} />
)) as unknown as ReactNode
}
>
{t("realmInfo")}
</DropdownItem>

View file

@ -141,7 +141,9 @@ export const CibaPolicy = ({ realm, realmUpdated }: CibaPolicyProps) => {
/>
}
validated={errors.attributes?.cibaExpiresIn ? "error" : "default"}
helperTextInvalid={errors.attributes?.cibaExpiresIn?.message}
helperTextInvalid={
errors.attributes?.cibaExpiresIn?.message as string
}
isRequired
>
<InputGroup>
@ -183,7 +185,7 @@ export const CibaPolicy = ({ realm, realmUpdated }: CibaPolicyProps) => {
/>
}
validated={errors.attributes?.cibaInterval ? "error" : "default"}
helperTextInvalid={errors.attributes?.cibaInterval?.message}
helperTextInvalid={errors.attributes?.cibaInterval?.message as string}
isRequired
>
<InputGroup>

View file

@ -59,12 +59,14 @@ const ClientDetailLink = (client: ClientRepresentation) => {
};
const ClientName = (client: ClientRepresentation) => (
<TableText wrapModifier="truncate">{emptyFormatter()(client.name)}</TableText>
<TableText wrapModifier="truncate">
{emptyFormatter()(client.name) as string}
</TableText>
);
const ClientDescription = (client: ClientRepresentation) => (
<TableText wrapModifier="truncate">
{emptyFormatter()(client.description)}
{emptyFormatter()(client.description) as string}
</TableText>
);

View file

@ -74,7 +74,8 @@ export const LogoutPanel = ({
/>
}
helperTextInvalid={
errors.attributes?.[beerify("frontchannel.logout.url")]?.message
errors.attributes?.[beerify("frontchannel.logout.url")]
?.message as string
}
validated={
errors.attributes?.[beerify("frontchannel.logout.url")]?.message
@ -117,7 +118,8 @@ export const LogoutPanel = ({
/>
}
helperTextInvalid={
errors.attributes?.[beerify("backchannel.logout.url")]?.message
errors.attributes?.[beerify("backchannel.logout.url")]
?.message as string
}
validated={
errors.attributes?.[beerify("backchannel.logout.url")]?.message

View file

@ -55,7 +55,7 @@ export const NameDescription = ({ prefix }: NameDescriptionProps) => {
validated={
errors.description ? ValidatedOptions.error : ValidatedOptions.default
}
helperTextInvalid={errors.description?.message}
helperTextInvalid={t("common:maxLength", { length: 255 })}
>
<KeycloakTextArea
id="kc-description"
@ -65,12 +65,7 @@ export const NameDescription = ({ prefix }: NameDescriptionProps) => {
? ValidatedOptions.error
: ValidatedOptions.default
}
{...register("description", {
maxLength: {
value: 255,
message: t("common:maxLength", { length: 255 }),
},
})}
{...register("description", { maxLength: 255 })}
/>
</FormGroup>
</>

View file

@ -102,7 +102,7 @@ export default function PolicyDetails() {
reset({ ...policy, policies });
setPolicy(policy);
},
[]
[id, policyType, policyId]
);
const onSubmit = async (policy: Policy) => {

View file

@ -37,7 +37,7 @@ export type AlertEntry = {
description?: string;
};
export const AlertProvider = ({ children }: PropsWithChildren<unknown>) => {
export const AlertProvider = ({ children }: PropsWithChildren) => {
const { t } = useTranslation();
const setTimeout = useSetTimeout();
const [alerts, setAlerts] = useState<AlertEntry[]>([]);

View file

@ -31,7 +31,7 @@ export const HelpContext = createNamedContext<HelpContextProps | undefined>(
export const useHelp = () => useRequiredContext(HelpContext);
export const Help = ({ children }: PropsWithChildren<unknown>) => {
export const Help = ({ children }: PropsWithChildren) => {
const [enabled, setHelp] = useStoredState(localStorage, "helpEnabled", true);
function toggleHelp() {

View file

@ -1,3 +1,5 @@
/* eslint-disable react/jsx-no-useless-fragment */
// See: https://github.com/i18next/react-i18next/issues/1543
import { Title } from "@patternfly/react-core";
import { HTMLProps } from "react";
@ -12,16 +14,18 @@ export const ScrollPanel = (props: ScrollPanelProps) => {
const { title, children, scrollId, ...rest } = props;
return (
<section {...rest} className="kc-form-panel__panel">
<Title
headingLevel="h1"
size="xl"
className="kc-form-panel__title"
id={scrollId}
tabIndex={0} // so that jumpLink sends focus to the section for a11y
>
{title}
</Title>
{children}
<>
<Title
headingLevel="h1"
size="xl"
className="kc-form-panel__title"
id={scrollId}
tabIndex={0} // so that jumpLink sends focus to the section for a11y
>
{title}
</Title>
{children}
</>
</section>
);
};

View file

@ -5,6 +5,7 @@ import {
IActions,
IActionsResolver,
IFormatter,
IRow,
ITransform,
Table,
TableBody,
@ -95,7 +96,7 @@ function DataTable<T>({
cells={columns.map((column) => {
return { ...column, title: t(column.displayKey || column.name) };
})}
rows={rows}
rows={rows as IRow[]}
actions={actions}
actionResolver={actionResolver}
aria-label={t(ariaLabelKey)}

View file

@ -19,7 +19,7 @@ export const RealmsContext = createNamedContext<RealmsContextProps | undefined>(
undefined
);
export const RealmsProvider = ({ children }: PropsWithChildren<unknown>) => {
export const RealmsProvider = ({ children }: PropsWithChildren) => {
const { keycloak, adminClient } = useAdminClient();
const [realms, setRealms] = useState<RealmRepresentation[]>([]);
const [refreshCount, setRefreshCount] = useState(0);

View file

@ -14,9 +14,7 @@ export const RecentRealmsContext = createNamedContext<string[] | undefined>(
undefined
);
export const RecentRealmsProvider = ({
children,
}: PropsWithChildren<unknown>) => {
export const RecentRealmsProvider = ({ children }: PropsWithChildren) => {
const { realms } = useRealms();
const { realm } = useRealm();
const [storedRealms, setStoredRealms] = useStoredState(

View file

@ -17,9 +17,7 @@ export const AccessContext = createNamedContext<AccessContextProps | undefined>(
export const useAccess = () => useRequiredContext(AccessContext);
export const AccessContextProvider = ({
children,
}: PropsWithChildren<unknown>) => {
export const AccessContextProvider = ({ children }: PropsWithChildren) => {
const { whoAmI } = useWhoAmI();
const { realm } = useRealm();
const [access, setAccess] = useState<readonly AccessType[]>([]);

View file

@ -16,9 +16,7 @@ export const RealmContext = createNamedContext<RealmContextType | undefined>(
undefined
);
export const RealmContextProvider = ({
children,
}: PropsWithChildren<unknown>) => {
export const RealmContextProvider = ({ children }: PropsWithChildren) => {
const { adminClient } = useAdminClient();
const routeMatch = useMatch({
path: DashboardRouteWithRealm.path,

View file

@ -15,9 +15,7 @@ export const useServerInfo = () => useRequiredContext(ServerInfoContext);
export const useLoginProviders = () =>
sortProviders(useServerInfo().providers!["login-protocol"].providers);
export const ServerInfoProvider = ({
children,
}: PropsWithChildren<unknown>) => {
export const ServerInfoProvider = ({ children }: PropsWithChildren) => {
const { adminClient } = useAdminClient();
const [serverInfo, setServerInfo] = useState<ServerInfoRepresentation>({});

View file

@ -62,9 +62,7 @@ export const WhoAmIContext = createNamedContext<WhoAmIProps | undefined>(
export const useWhoAmI = () => useRequiredContext(WhoAmIContext);
export const WhoAmIContextProvider = ({
children,
}: PropsWithChildren<unknown>) => {
export const WhoAmIContextProvider = ({ children }: PropsWithChildren) => {
const { adminClient } = useAdminClient();
const [whoAmI, setWhoAmI] = useState<WhoAmI>(new WhoAmI());
const [key, setKey] = useState(0);

View file

@ -16,7 +16,7 @@ const SubGroupsContext = createNamedContext<SubGroupsProps | undefined>(
undefined
);
export const SubGroups = ({ children }: PropsWithChildren<unknown>) => {
export const SubGroups = ({ children }: PropsWithChildren) => {
const [subGroups, setSubGroups] = useState<GroupRepresentation[]>([]);
const clear = () => setSubGroups([]);

View file

@ -3,6 +3,9 @@ import "i18next";
declare module "i18next" {
interface CustomTypeOptions {
// TODO: These flags should be removed and code that errors out should be made functional.
// This will have to be done incrementally as the amount of errors the defaults produce is just too much.
returnNull: false;
allowObjectInHTMLChildren: true;
}
}

View file

@ -85,7 +85,7 @@ export const OpenIdConnectSettings = () => {
/>
}
validated={errors.discoveryError ? "error" : "default"}
helperTextInvalid={errors.discoveryError?.message}
helperTextInvalid={errors.discoveryError?.message as string}
>
<JsonFileUpload
id="kc-import-config"

View file

@ -10,17 +10,15 @@ import { initI18n } from "./i18n";
import "./index.css";
async function initialize() {
const { keycloak, adminClient } = await initAdminClient();
const { keycloak, adminClient } = await initAdminClient();
await initI18n(adminClient);
await initI18n(adminClient);
render(
<StrictMode>
<App keycloak={keycloak} adminClient={adminClient} />
</StrictMode>,
document.getElementById("app")
);
}
const container = document.getElementById("app");
initialize();
render(
<StrictMode>
<App keycloak={keycloak} adminClient={adminClient} />
</StrictMode>,
container
);

View file

@ -30,7 +30,6 @@ import {
TableVariant,
validateCellEdits,
} from "@patternfly/react-table";
import type { EditableTextCellProps } from "@patternfly/react-table/dist/esm/components/Table/base";
import { cloneDeep, isEqual, uniqWith } from "lodash-es";
import { useEffect, useMemo, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
@ -184,16 +183,11 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
}),
cells: [
{
title: (
value: string,
rowIndex: number,
cellIndex: number,
props
) => (
title: (value, rowIndex, cellIndex, props) => (
<EditableTextCell
value={value}
rowIndex={rowIndex}
cellIndex={cellIndex}
value={value!}
rowIndex={rowIndex!}
cellIndex={cellIndex!}
props={props}
isDisabled
handleTextInputChange={handleTextInputChange}
@ -205,16 +199,11 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
},
},
{
title: (
value: string,
rowIndex: number,
cellIndex: number,
props: EditableTextCellProps
) => (
title: (value, rowIndex, cellIndex, props) => (
<EditableTextCell
value={value}
rowIndex={rowIndex}
cellIndex={cellIndex}
value={value!}
rowIndex={rowIndex!}
cellIndex={cellIndex!}
props={props}
handleTextInputChange={handleTextInputChange}
inputAriaLabel={messageBundle[1]}

View file

@ -29,9 +29,7 @@ export const UserProfileContext = createNamedContext<
UserProfileProps | undefined
>("UserProfileContext", undefined);
export const UserProfileProvider = ({
children,
}: PropsWithChildren<unknown>) => {
export const UserProfileProvider = ({ children }: PropsWithChildren) => {
const { adminClient } = useAdminClient();
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();

View file

@ -82,7 +82,7 @@ export const AttributeGeneralSettings = () => {
fieldId="kc-attribute-name"
isRequired
validated={form.formState.errors.name ? "error" : "default"}
helperTextInvalid={form.formState.errors.name?.message}
helperTextInvalid={t("validateName")}
>
<KeycloakTextInput
isRequired
@ -91,12 +91,7 @@ export const AttributeGeneralSettings = () => {
data-testid="attribute-name"
isDisabled={editMode}
validated={form.formState.errors.name ? "error" : "default"}
{...form.register("name", {
required: {
value: true,
message: t("validateName"),
},
})}
{...form.register("name", { required: true })}
/>
</FormGroup>
<FormGroup

View file

@ -1,3 +1,4 @@
import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import {
FormGroup,
Select,
@ -16,7 +17,7 @@ import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
export type LdapSettingsGeneralProps = {
form: UseFormReturn;
form: UseFormReturn<ComponentRepresentation>;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
vendorEdit?: boolean;

View file

@ -1,7 +1,7 @@
/**
* @vitest-environment jsdom
*/
import { renderHook } from "@testing-library/react-hooks";
import { renderHook } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import useSetTimeout from "./useSetTimeout";

View file

@ -1,7 +1,7 @@
/**
* @vitest-environment jsdom
*/
import { act, renderHook } from "@testing-library/react-hooks";
import { act, renderHook } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import useToggle from "./useToggle";

View file

@ -43,12 +43,12 @@
"dependencies": {
"@patternfly/react-core": "^4.276.6",
"keycloak-js": "999.0.0-dev",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^17.0.53",
"@types/react-dom": "^17.0.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^2.2.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"vite": "^3.2.5",

View file

@ -37,12 +37,12 @@
"dependencies": {
"@patternfly/react-core": "^4.267.6",
"react-hook-form": "7.43.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^17.0.53",
"@types/react-dom": "^17.0.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^2.2.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"vite": "^3.2.5",

View file

@ -31,7 +31,7 @@ export type AlertType = {
description?: string;
};
export const AlertProvider = ({ children }: PropsWithChildren<unknown>) => {
export const AlertProvider = ({ children }: PropsWithChildren) => {
const { t } = useTranslation();
const [alerts, setAlerts] = useState<AlertType[]>([]);

View file

@ -54,7 +54,7 @@ export const SelectControl = <
isRequired={controller.rules?.required === true}
label={label || name}
fieldId={name}
helperTextInvalid={errors[name]?.message}
helperTextInvalid={errors[name]?.message as string}
validated={
errors[name] ? ValidatedOptions.error : ValidatedOptions.default
}

1185
package-lock.json generated

File diff suppressed because it is too large Load diff