diff --git a/.changeset/hip-beers-attend.md b/.changeset/hip-beers-attend.md new file mode 100644 index 0000000000..802bfe7397 --- /dev/null +++ b/.changeset/hip-beers-attend.md @@ -0,0 +1,7 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-shortcuts': patch +--- + +Bumped `react-hook-form` to `^7.12.2` diff --git a/packages/core-components/package.json b/packages/core-components/package.json index e4f99fc9ec..ae6daece4d 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -58,7 +58,7 @@ "react": "^16.12.0", "react-dom": "^16.12.0", "react-helmet": "6.1.0", - "react-hook-form": "^6.15.4", + "react-hook-form": "^7.12.2", "react-markdown": "^5.0.2", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", diff --git a/packages/core-components/src/layout/SignInPage/customProvider.tsx b/packages/core-components/src/layout/SignInPage/customProvider.tsx index e8f7d03a1d..98753ad444 100644 --- a/packages/core-components/src/layout/SignInPage/customProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/customProvider.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { useForm } from 'react-hook-form'; +import { useForm, UseFormRegisterReturn } from 'react-hook-form'; import { Typography, Button, @@ -48,12 +48,21 @@ type Data = { idToken?: string; }; +const asInputRef = (renderResult: UseFormRegisterReturn) => { + const { ref, ...rest } = renderResult; + return { + inputRef: ref, + ...rest, + }; +}; const Component: ProviderComponent = ({ onResult }) => { const classes = useFormStyles(); - const { register, handleSubmit, errors, formState } = useForm({ + const { register, handleSubmit, formState } = useForm({ mode: 'onChange', }); + const { errors } = formState; + const handleResult = ({ userId, idToken }: Data) => { onResult({ userId, @@ -76,11 +85,10 @@ const Component: ProviderComponent = ({ onResult }) => {
{errors.userId && ( {errors.userId.message} @@ -88,18 +96,19 @@ const Component: ProviderComponent = ({ onResult }) => { + !token || + ID_TOKEN_REGEX.test(token) || + 'Token is not a valid OpenID Connect JWT Token', + }), + )} label="ID Token (optional)" margin="normal" autoComplete="off" error={Boolean(errors.idToken)} - inputRef={register({ - required: false, - validate: token => - !token || - ID_TOKEN_REGEX.test(token) || - 'Token is not a valid OpenID Connect JWT Token', - })} /> {errors.idToken && ( {errors.idToken.message} diff --git a/plugins/catalog-import/api-report.md b/plugins/catalog-import/api-report.md index 708ee84fd0..8466749243 100644 --- a/plugins/catalog-import/api-report.md +++ b/plugins/catalog-import/api-report.md @@ -9,7 +9,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { ConfigApi } from '@backstage/core-plugin-api'; -import { Control } from 'react-hook-form'; +import { Controller } from 'react-hook-form'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; @@ -23,9 +23,8 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { SubmitHandler } from 'react-hook-form'; import { TextFieldProps } from '@material-ui/core/TextField/TextField'; import { UnpackNestedValue } from 'react-hook-form'; -import { UseControllerOptions } from 'react-hook-form'; -import { UseFormMethods } from 'react-hook-form'; -import { UseFormOptions } from 'react-hook-form'; +import { UseFormProps } from 'react-hook-form'; +import { UseFormReturn } from 'react-hook-form'; // Warning: (ae-missing-release-tag) "AnalyzeResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -53,7 +52,6 @@ export const AutocompleteTextField: ({ name, options, required, - control, errors, rules, loading, diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index b39c6cab07..2605951a97 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -47,7 +47,7 @@ "js-base64": "^3.6.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-hook-form": "^6.15.4", + "react-hook-form": "^7.12.2", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4", diff --git a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx index 054fd99895..79d859ce32 100644 --- a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ConfigApi } from '@backstage/core-plugin-api'; import { Box, Checkbox, @@ -25,6 +26,7 @@ import { } from '@material-ui/core'; import React from 'react'; import { BackButton } from '../Buttons'; +import { asInputRef } from '../helpers'; import { StepFinishImportLocation } from '../StepFinishImportLocation'; import { StepInitAnalyzeUrl } from '../StepInitAnalyzeUrl'; import { @@ -34,7 +36,6 @@ import { import { StepPrepareSelectLocations } from '../StepPrepareSelectLocations'; import { StepReviewLocation } from '../StepReviewLocation'; import { ImportFlows, ImportState } from '../useImportState'; -import { ConfigApi } from '@backstage/core-plugin-api'; export type StepperProviderOpts = { pullRequest?: { @@ -186,8 +187,8 @@ export function defaultGenerateStepper( defaultBody={body} renderFormFields={({ values, - control, - errors, + setValue, + formState, groupsLoading, groups, register, @@ -198,26 +199,32 @@ export function defaultGenerateStepper( @@ -227,22 +234,22 @@ export function defaultGenerateStepper( {!values.useCodeowners && ( { if (value) { - control.setValue('owner', ''); + setValue('owner', ''); } }} /> diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 07d0d3b9fd..75910db5a2 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -14,13 +14,14 @@ * limitations under the License. */ +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { FormHelperText, Grid, TextField } from '@material-ui/core'; import React, { useCallback, useState } from 'react'; import { useForm } from 'react-hook-form'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; import { NextButton } from '../Buttons'; +import { asInputRef } from '../helpers'; import { ImportFlows, PrepareResult } from '../useImportState'; -import { errorApiRef, useApi } from '@backstage/core-plugin-api'; type FormData = { url: string; @@ -52,7 +53,12 @@ export const StepInitAnalyzeUrl = ({ const errorApi = useApi(errorApiRef); const catalogImportApi = useApi(catalogImportApiRef); - const { register, handleSubmit, errors, watch } = useForm({ + const { + register, + handleSubmit, + formState: { errors }, + watch, + } = useForm({ mode: 'onTouched', defaultValues: { url: analysisUrl, @@ -118,24 +124,25 @@ export const StepInitAnalyzeUrl = ({ return ( + (typeof value === 'string' && + value.match(/^http[s]?:\/\//) !== null) || + 'Must start with http:// or https://.', + }, + }), + )} fullWidth id="url" - name="url" label="Repository URL" placeholder="https://github.com/backstage/backstage/blob/master/catalog-info.yaml" helperText="Enter the full path to your entity file to start tracking your component" margin="normal" variant="outlined" error={Boolean(errors.url)} - inputRef={register({ - required: true, - validate: { - httpsValidator: (value: any) => - (typeof value === 'string' && - value.match(/^http[s]?:\/\//) !== null) || - 'Must start with http:// or https://.', - }, - })} required /> diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/AutocompleteTextField.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/AutocompleteTextField.tsx index 52e11ba215..a2da75edd4 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/AutocompleteTextField.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/AutocompleteTextField.tsx @@ -18,21 +18,15 @@ import { CircularProgress, TextField } from '@material-ui/core'; import { TextFieldProps } from '@material-ui/core/TextField/TextField'; import { Autocomplete } from '@material-ui/lab'; import React from 'react'; -import { - Control, - Controller, - FieldErrors, - UseControllerOptions, -} from 'react-hook-form'; +import { Controller, FieldErrors } from 'react-hook-form'; type Props = { name: TFieldValue; options: string[]; required?: boolean; - control?: Control>; errors?: FieldErrors>; - rules?: UseControllerOptions>['rules']; + rules?: React.ComponentProps['rules']; loading?: boolean; loadingText?: string; @@ -47,7 +41,6 @@ export const AutocompleteTextField = ({ name, options, required, - control, errors, rules, loading = false, @@ -59,18 +52,17 @@ export const AutocompleteTextField = ({ return ( ( + render={({ field: { onChange } }) => ( onChange(v || '')} - onBlur={onBlur} - value={value} autoSelect freeSolo + onChange={(_event: React.ChangeEvent<{}>, value: string | null) => + onChange(value) + } renderInput={params => ( ', () => { @@ -29,7 +30,7 @@ describe('', () => { defaultValues={{ main: 'default' }} render={({ register }) => ( <> - + {' '} )} @@ -54,10 +55,9 @@ describe('', () => { render={({ register }) => ( <> @@ -82,14 +82,14 @@ describe('', () => { const { queryByText, getByRole } = render( defaultValues={{}} - render={({ errors, register }) => ( + render={({ formState, register }) => ( <> - {errors.main && ( + {formState.errors.main && ( Error in required main field diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx index ab3241add4..616e77518c 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx @@ -19,20 +19,20 @@ import { SubmitHandler, UnpackNestedValue, useForm, - UseFormMethods, - UseFormOptions, + UseFormProps, + UseFormReturn, } from 'react-hook-form'; type Props> = Pick< - UseFormOptions, + UseFormProps, 'defaultValues' > & { onSubmit: SubmitHandler; render: ( props: Pick< - UseFormMethods, - 'errors' | 'register' | 'control' + UseFormReturn, + 'formState' | 'register' | 'control' | 'setValue' > & { values: UnpackNestedValue; }, @@ -55,12 +55,12 @@ export const PreparePullRequestForm = < onSubmit, render, }: Props) => { - const { handleSubmit, watch, control, register, errors } = + const { handleSubmit, watch, control, register, formState, setValue } = useForm({ mode: 'onTouched', defaultValues }); return ( - {render({ values: watch(), errors, register, control })} + {render({ values: watch(), formState, register, control, setValue })} ); }; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index d6014b329c..08b8177291 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -20,6 +20,7 @@ import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; +import { asInputRef } from '../helpers'; import { generateEntities, StepPrepareCreatePullRequest, @@ -91,10 +92,10 @@ describe('', () => { renderFormFields={({ register }) => { return ( <> - - - - + + + + ); }} @@ -132,19 +133,17 @@ describe('', () => { renderFormFields={({ register }) => { return ( <> - - + + ); @@ -222,10 +221,10 @@ spec: renderFormFields={({ register }) => { return ( <> - - - - + + + + ); }} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 41db6f600c..52afcdaf83 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -15,6 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, formatEntityRefTitle, @@ -22,7 +23,7 @@ import { import { Box, FormHelperText, Grid, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useCallback, useState } from 'react'; -import { UnpackNestedValue, UseFormMethods } from 'react-hook-form'; +import { UnpackNestedValue, UseFormReturn } from 'react-hook-form'; import { useAsync } from 'react-use'; import YAML from 'yaml'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; @@ -32,7 +33,6 @@ import { PrepareResult } from '../useImportState'; import { PreparePullRequestForm } from './PreparePullRequestForm'; import { PreviewCatalogInfoComponent } from './PreviewCatalogInfoComponent'; import { PreviewPullRequestComponent } from './PreviewPullRequestComponent'; -import { useApi } from '@backstage/core-plugin-api'; const useStyles = makeStyles(theme => ({ previewCard: { @@ -63,7 +63,10 @@ type Props = { defaultBody: string; renderFormFields: ( - props: Pick, 'errors' | 'register' | 'control'> & { + props: Pick< + UseFormReturn, + 'register' | 'setValue' | 'formState' + > & { values: UnpackNestedValue; groups: string[]; groupsLoading: boolean; @@ -192,13 +195,13 @@ export const StepPrepareCreatePullRequest = ({ analyzeResult.generatedEntities[0]?.metadata?.name || '', useCodeowners: false, }} - render={({ values, errors, control, register }) => ( + render={({ values, formState, register, setValue }) => ( <> {renderFormFields({ values, - errors, + formState, register, - control, + setValue, groups: groups ?? [], groupsLoading, })} @@ -241,7 +244,11 @@ export const StepPrepareCreatePullRequest = ({ )} Create PR diff --git a/plugins/catalog-import/src/components/helpers.ts b/plugins/catalog-import/src/components/helpers.ts new file mode 100644 index 0000000000..4240186459 --- /dev/null +++ b/plugins/catalog-import/src/components/helpers.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { UseFormRegisterReturn } from 'react-hook-form'; + +/** + * A helper that converts the result of a render('name', opts) to make it compatible with material-ui. + * + * See also https://github.com/react-hook-form/react-hook-form/issues/4629#issuecomment-815840872 + * TODO: remove when updating to material-ui v5 (https://github.com/mui-org/material-ui/pull/23174) + * + * @param renderResult - the result of a render('name', opts) + */ +export function asInputRef(renderResult: UseFormRegisterReturn) { + const { ref, ...rest } = renderResult; + return { + inputRef: ref, + ...rest, + }; +} diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index f650ccdda8..45d2487070 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -29,7 +29,7 @@ "@types/zen-observable": "^0.8.2", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-hook-form": "^7.1.1", + "react-hook-form": "^7.12.2", "react-router": "6.0.0-beta.0", "react-use": "^17.2.4", "uuid": "^8.3.2", diff --git a/yarn.lock b/yarn.lock index 256a7f0ea2..e643ac9750 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22376,10 +22376,10 @@ react-hook-form@^6.15.4: resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.15.4.tgz#328003e1ccc096cd158899ffe7e3b33735a9b024" integrity sha512-K+Sw33DtTMengs8OdqFJI3glzNl1wBzSefD/ksQw/hJf9CnOHQAU6qy82eOrh0IRNt2G53sjr7qnnw1JDjvx1w== -react-hook-form@^7.1.1: - version "7.2.1" - resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.2.1.tgz#99b3540dd2314499df12e9a53c70587ad63a806c" - integrity sha512-QopAubhVofqQrwlWLr9aK0DF8tNU8fnU8sJIlw1Tb3tGkEvP9yeaA+cx1hlxYni8xBswtHruL1WcDEa6CYQDow== +react-hook-form@^7.12.2: + version "7.12.2" + resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.12.2.tgz#2660afbf03c4ef360a9314ebf46ce3d972296c77" + integrity sha512-cpxocjrgpMAJCMJQR51BQhMoEx80/EQqePNihMTgoTYTqCRbd2GExi+N4GJIr+cFqrmbwNj9wxk5oLWYQsUefg== react-hot-loader@^4.12.21: version "4.13.0"