Skip to content

Commit

Permalink
Convert Form components to typescript
Browse files Browse the repository at this point in the history
Refs #4505
  • Loading branch information
fzaninotto authored and JulienMattiussi committed Aug 24, 2020
1 parent 37c18b0 commit d6e1c61
Show file tree
Hide file tree
Showing 16 changed files with 328 additions and 217 deletions.
21 changes: 14 additions & 7 deletions packages/ra-core/src/form/FormWithRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ import { setAutomaticRefresh } from '../actions/uiActions';
*
* @param {Prop} props
*/
const FormWithRedirect: FC<FormWithRedirectOwnProps & FormProps> = ({
const FormWithRedirect: FC<
FormWithRedirectOwnProps & Omit<FormProps, 'onSubmit'>
> = ({
debug,
decorators,
defaultValue,
Expand Down Expand Up @@ -150,17 +152,17 @@ const FormWithRedirect: FC<FormWithRedirectOwnProps & FormProps> = ({
export interface FormWithRedirectOwnProps {
defaultValue?: any;
record?: Record;
save: (
save?: (
data: Partial<Record>,
redirectTo: RedirectionSideEffect,
options?: {
onSuccess?: (data?: any) => void;
onFailure?: (error: any) => void;
}
) => void;
redirect: RedirectionSideEffect;
saving: boolean;
version: number;
redirect?: RedirectionSideEffect;
saving?: boolean;
version?: number;
warnWhenUnsavedChanges?: boolean;
sanitizeEmptyValues?: boolean;
}
Expand All @@ -172,7 +174,12 @@ const defaultSubscription = {
invalid: true,
};

const FormView = ({ render, warnWhenUnsavedChanges, ...props }) => {
const FormView = ({
render,
warnWhenUnsavedChanges,
setRedirect,
...props
}) => {
// if record changes (after a getOne success or a refresh), the form must be updated
useInitializeFormWithRecord(props.record);
useWarnWhenUnsavedChanges(warnWhenUnsavedChanges);
Expand All @@ -182,7 +189,7 @@ const FormView = ({ render, warnWhenUnsavedChanges, ...props }) => {
dispatch(setAutomaticRefresh(props.pristine));
}, [dispatch, props.pristine]);

const { redirect, setRedirect, handleSubmit } = props;
const { redirect, handleSubmit } = props;

/**
* We want to let developers define the redirection target from inside the form,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as React from 'react';
import { FC, HtmlHTMLAttributes, ReactElement } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { makeStyles } from '@material-ui/core/styles';
import { Record } from 'ra-core';

import Labeled from '../input/Labeled';
import { ClassesOverride } from '../types';

const sanitizeRestProps = ({ basePath, record, ...rest }) => rest;

Expand All @@ -14,7 +17,7 @@ const useStyles = makeStyles(
{ name: 'RaFormInput' }
);

const FormInput = props => {
const FormInput: FC<FormInputProps> = props => {
const { input, classes: classesOverride, ...rest } = props;
const classes = useStyles(props);
return input ? (
Expand Down Expand Up @@ -59,11 +62,21 @@ const FormInput = props => {
};

FormInput.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
input: PropTypes.object,
// @ts-ignore
input: PropTypes.node,
};

export interface FormInputProps extends HtmlHTMLAttributes<HTMLDivElement> {
basePath: string;
classes?: ClassesOverride<typeof useStyles>;
input: ReactElement;
margin?: 'none' | 'normal' | 'dense';
record: Record;
resource: string;
variant?: 'standard' | 'outlined' | 'filled';
}

// wat? TypeScript looses the displayName if we don't set it explicitly
FormInput.displayName = 'FormInput';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('<FormTab label="foo" />', () => {
it('should render a TabbedForm with FormTabs having custom props without warnings', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});

const record = { name: 'foo' };
const record = { id: 'gazebo', name: 'foo' };
const { container } = renderWithRedux(
<TabbedForm>
<FormTab
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import * as React from 'react';
import { FC, ReactElement } from 'react';
import PropTypes from 'prop-types';
import { Link, useLocation } from 'react-router-dom';
import MuiTab from '@material-ui/core/Tab';
import classnames from 'classnames';
import { useTranslate } from 'ra-core';
import { useTranslate, Record } from 'ra-core';

import FormInput from './FormInput';

const sanitizeRestProps = ({
contentClassName,
label,
icon,
value,
translate,
...rest
}) => rest;

const hiddenStyle = { display: 'none' };

const FormTab = ({
const FormTab: FC<FormTabProps> = ({
basePath,
className,
contentClassName,
Expand Down Expand Up @@ -46,15 +38,15 @@ const FormTab = ({
className={classnames('form-tab', className)}
component={Link}
to={{ ...location, pathname: value }}
{...sanitizeRestProps(rest)}
{...rest}
/>
);

const renderContent = () => (
<span style={hidden ? hiddenStyle : null} className={contentClassName}>
{React.Children.map(
children,
input =>
(input: ReactElement) =>
input && (
<FormInput
basePath={basePath}
Expand Down Expand Up @@ -83,12 +75,28 @@ FormTab.propTypes = {
label: PropTypes.string.isRequired,
margin: PropTypes.oneOf(['none', 'dense', 'normal']),
path: PropTypes.string,
// @ts-ignore
record: PropTypes.object,
resource: PropTypes.string,
value: PropTypes.string,
variant: PropTypes.oneOf(['standard', 'outlined', 'filled']),
};

export interface FormTabProps {
basePath?: string;
className?: string;
contentClassName?: string;
hidden?: boolean;
icon?: ReactElement;
intent?: 'header' | 'content';
label: string;
margin?: 'none' | 'normal' | 'dense';
record?: Record;
resource?: string;
value?: string;
variant?: 'standard' | 'outlined' | 'filled';
}

FormTab.displayName = 'FormTab';

export default FormTab;
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,17 @@ describe('<SimpleForm />', () => {
});

it('should pass submitOnEnter to <Toolbar />', () => {
const handleSubmit = () => {};
const Toolbar = ({ submitOnEnter }) => (
const Toolbar = ({ submitOnEnter }: any): any => (
<p>submitOnEnter: {submitOnEnter.toString()}</p>
);

const { queryByText, rerender } = renderWithRedux(
<SimpleForm
submitOnEnter={false}
handleSubmit={handleSubmit}
toolbar={<Toolbar />}
/>
<SimpleForm submitOnEnter={false} toolbar={<Toolbar />} />
);

expect(queryByText('submitOnEnter: false')).not.toBeNull();

rerender(
<SimpleForm
submitOnEnter
handleSubmit={handleSubmit}
toolbar={<Toolbar />}
/>
);
rerender(<SimpleForm submitOnEnter toolbar={<Toolbar />} />);

expect(queryByText('submitOnEnter: true')).not.toBeNull();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import { Children } from 'react';
import { Children, FC, ReactElement, HtmlHTMLAttributes } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { FormWithRedirect } from 'ra-core';
import { FormWithRedirect, Record, RedirectionSideEffect } from 'ra-core';
import { FormProps, FormRenderProps } from 'react-final-form';

import FormInput from './FormInput';
import Toolbar from './Toolbar';
Expand Down Expand Up @@ -39,11 +40,11 @@ import CardContentInner from '../layout/CardContentInner';
* @prop {ReactElement} toolbar The element displayed at the bottom of the form, containing the SaveButton
* @prop {string} variant Apply variant to all inputs. Possible values are 'standard', 'outlined', and 'filled' (default)
* @prop {string} margin Apply variant to all inputs. Possible values are 'none', 'normal', and 'dense' (default)
* @prop {boolean} sanitizeEmptyValues Wether or not deleted record attributes should be recreated with a `null` value (default: true)
* @prop {boolean} sanitizeEmptyValues Whether or not deleted record attributes should be recreated with a `null` value (default: true)
*
* @param {Prop} props
*/
const SimpleForm = props => (
const SimpleForm: FC<SimpleFormProps> = props => (
<FormWithRedirect
{...props}
render={formProps => <SimpleFormView {...formProps} />}
Expand All @@ -52,8 +53,8 @@ const SimpleForm = props => (

SimpleForm.propTypes = {
children: PropTypes.node,
defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), // @deprecated
initialValues: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
// @ts-ignore
record: PropTypes.object,
redirect: PropTypes.oneOfType([
PropTypes.string,
Expand All @@ -70,7 +71,25 @@ SimpleForm.propTypes = {
sanitizeEmptyValues: PropTypes.bool,
};

const SimpleFormView = ({
export interface SimpleFormProps
extends Omit<FormProps, 'onSubmit'>,
Omit<HtmlHTMLAttributes<HTMLFormElement>, 'onSubmit' | 'children'> {
basePath?: string;
className?: string;
initialValues?: any;
margin?: 'none' | 'normal' | 'dense';
record?: Record;
redirect?: RedirectionSideEffect;
resource?: string;
sanitizeEmptyValues?: boolean;
submitOnEnter?: boolean;
toolbar?: ReactElement;
undoable?: boolean;
variant?: 'standard' | 'outlined' | 'filled';
warnWhenUnsavedChanges?: boolean;
}

const SimpleFormView: FC<SimpleFormViewProps> = ({
basePath,
children,
className,
Expand All @@ -96,7 +115,7 @@ const SimpleFormView = ({
<CardContentInner>
{Children.map(
children,
input =>
(input: ReactElement) =>
input && (
<FormInput
basePath={basePath}
Expand Down Expand Up @@ -133,6 +152,7 @@ SimpleFormView.propTypes = {
handleSubmit: PropTypes.func, // passed by react-final-form
invalid: PropTypes.bool,
pristine: PropTypes.bool,
// @ts-ignore
record: PropTypes.object,
resource: PropTypes.string,
redirect: PropTypes.oneOfType([
Expand All @@ -148,62 +168,54 @@ SimpleFormView.propTypes = {
validate: PropTypes.func,
};

export interface SimpleFormViewProps extends FormRenderProps {
basePath?: string;
className?: string;
margin?: 'none' | 'normal' | 'dense';
handleSubmitWithRedirect?: (redirectTo: RedirectionSideEffect) => void;
record?: Record;
redirect?: RedirectionSideEffect;
resource?: string;
save?: () => void;
saving?: boolean;
toolbar?: ReactElement;
undoable?: boolean;
variant?: 'standard' | 'outlined' | 'filled';
submitOnEnter?: boolean;
__versions?: any; // react-final-form internal prop, missing in their type
}

SimpleFormView.defaultProps = {
submitOnEnter: true,
toolbar: <Toolbar />,
};

const sanitizeRestProps = ({
anyTouched,
array,
asyncBlurFields,
asyncValidate,
asyncValidating,
autofill,
blur,
change,
clearAsyncError,
clearFields,
clearSubmit,
clearSubmitErrors,
destroy,
active,
dirty,
dirtyFields,
dirtyFieldsSinceLastSubmit,
dirtySinceLastSubmit,
dispatch,
error,
errors,
form,
handleSubmit,
hasSubmitErrors,
hasValidationErrors,
initialize,
initialized,
initialValues,
modified = null,
modifiedSinceLastSubmit,
modifiedsincelastsubmit,
pristine,
pure,
redirect,
reset,
resetSection,
save,
setRedirect,
submit,
save = null,
submitError,
submitErrors,
submitAsSideEffect,
submitFailed,
submitSucceeded,
submitting,
touch,
translate,
triggerSubmit,
undoable,
untouch,
touched = null,
valid,
validate,
validating,
__versions,
values,
visited = null,
__versions = null,
...props
}) => props;

Expand Down
Loading

0 comments on commit d6e1c61

Please sign in to comment.