From 306aef519cf08da3f41a64754c44a33da2be90f3 Mon Sep 17 00:00:00 2001 From: Gildas Garcia Date: Fri, 26 Jul 2019 17:13:21 +0200 Subject: [PATCH] Migrate from redux-form to react-final-form --- cypress/integration/create.js | 4 +- cypress/integration/list.js | 2 +- examples/demo/package.json | 1 - examples/demo/src/App.js | 2 - examples/demo/src/layout/Login.js | 152 +++---- examples/demo/src/orders/OrderList.js | 1 + examples/demo/src/reviews/AcceptButton.js | 1 - examples/demo/src/reviews/RejectButton.js | 51 ++- examples/demo/src/reviews/reviewActions.js | 57 --- examples/demo/src/reviews/reviewSaga.js | 15 - examples/demo/src/sagas.js | 3 - examples/simple/package.json | 4 - .../simple/src/comments/PostReferenceInput.js | 2 +- examples/simple/src/i18n/en.js | 4 +- examples/simple/src/i18n/fr.js | 4 +- examples/simple/src/index.js | 5 +- examples/simple/src/posts/PostCreate.js | 206 +++++----- examples/simple/src/posts/PostEdit.js | 7 +- examples/simple/webpack.config.js | 39 +- package.json | 3 +- packages/ra-core/package.json | 5 +- .../input/ReferenceArrayInputController.tsx | 3 +- .../input/ReferenceInputController.spec.tsx | 5 +- .../input/ReferenceInputController.tsx | 3 +- .../input/useReferenceInputController.ts | 4 +- .../src/controller/useCreateController.ts | 47 ++- .../src/controller/useEditController.spec.tsx | 11 - .../src/controller/useEditController.ts | 10 +- packages/ra-core/src/createAdminStore.ts | 7 +- .../ra-core/src/form/FormDataConsumer.tsx | 22 +- packages/ra-core/src/form/FormField.spec.tsx | 12 +- packages/ra-core/src/form/FormField.tsx | 32 +- .../ra-core/src/form/formMiddleware.spec.ts | 63 --- packages/ra-core/src/form/formMiddleware.ts | 45 --- packages/ra-core/src/form/index.ts | 13 +- packages/ra-core/src/form/validate.ts | 14 + packages/ra-core/src/reducer/admin/saving.ts | 2 - packages/ra-core/src/reducer/index.ts | 2 - .../src/sideEffect/redirection.spec.ts | 9 - .../ra-core/src/sideEffect/redirection.ts | 44 +-- .../ra-core/src/sideEffect/useRedirect.ts | 16 +- .../ra-core/src/util/TestContext.spec.tsx | 79 ++-- packages/ra-core/src/util/TestContext.tsx | 2 - packages/ra-ui-materialui/package.json | 7 +- .../ra-ui-materialui/src/auth/LoginForm.tsx | 210 +++++----- .../ra-ui-materialui/src/detail/Create.js | 3 + packages/ra-ui-materialui/src/form/FormTab.js | 2 +- .../ra-ui-materialui/src/form/SimpleForm.js | 196 +++++---- .../ra-ui-materialui/src/form/TabbedForm.js | 371 +++++++++--------- .../ra-ui-materialui/src/input/ArrayInput.js | 16 +- .../src/input/AutocompleteArrayInput.js | 2 +- .../src/input/AutocompleteInput.js | 2 +- .../src/input/CheckboxGroupInput.js | 2 +- .../ra-ui-materialui/src/input/DateInput.js | 2 +- .../ra-ui-materialui/src/list/FilterForm.js | 70 ++-- .../src/list/FilterFormInput.js | 2 +- yarn.lock | 109 +++-- 57 files changed, 912 insertions(+), 1095 deletions(-) delete mode 100644 examples/demo/src/reviews/reviewActions.js delete mode 100644 examples/demo/src/reviews/reviewSaga.js delete mode 100644 examples/demo/src/sagas.js delete mode 100644 packages/ra-core/src/form/formMiddleware.spec.ts delete mode 100644 packages/ra-core/src/form/formMiddleware.ts diff --git a/cypress/integration/create.js b/cypress/integration/create.js index 3cc8a78cbdc..ac788d379a6 100644 --- a/cypress/integration/create.js +++ b/cypress/integration/create.js @@ -164,9 +164,9 @@ describe('Create Page', () => { value: 'Test teaser', }, { - type: 'checkbox', + type: 'input', name: 'commentable', - value: false, + value: 'false', }, { type: 'rich-text-input', diff --git a/cypress/integration/list.js b/cypress/integration/list.js index fec39761f4c..04001f4b4e1 100644 --- a/cypress/integration/list.js +++ b/cypress/integration/list.js @@ -40,7 +40,7 @@ describe('List Page', () => { ); }); - it('should filter directly while typing (with some debounce)', () => { + it.only('should filter directly while typing (with some debounce)', () => { ListPagePosts.setFilterValue('q', 'quis culpa impedit'); cy.get(ListPagePosts.elements.recordRows).should(el => expect(el).to.have.length(1) diff --git a/examples/demo/package.json b/examples/demo/package.json index 98d39522594..f60e7017510 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -24,7 +24,6 @@ "react-router-dom": "^5.0.1", "react-scripts": "^3.0.0", "recompose": "~0.26.0", - "redux-form": "~8.2.0", "redux-saga": "^1.0.0", "source-map-explorer": "^2.0.0" }, diff --git a/examples/demo/src/App.js b/examples/demo/src/App.js index ee48c970b0f..84e819a1b63 100644 --- a/examples/demo/src/App.js +++ b/examples/demo/src/App.js @@ -4,7 +4,6 @@ import { Admin, Resource } from 'react-admin'; import './App.css'; import authProvider from './authProvider'; -import sagas from './sagas'; import themeReducer from './themeReducer'; import { Login, Layout } from './layout'; import { Dashboard } from './dashboard'; @@ -65,7 +64,6 @@ class App extends Component { title="" dataProvider={dataProvider} customReducers={{ theme: themeReducer }} - customSagas={sagas} customRoutes={customRoutes} authProvider={authProvider} dashboard={Dashboard} diff --git a/examples/demo/src/layout/Login.js b/examples/demo/src/layout/Login.js index 7c5f9354d77..16ce041011f 100644 --- a/examples/demo/src/layout/Login.js +++ b/examples/demo/src/layout/Login.js @@ -1,8 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { propTypes, reduxForm, Field } from 'redux-form'; +import { Field, Form } from 'react-final-form'; import { useDispatch, useSelector } from 'react-redux'; -import compose from 'recompose/compose'; import Avatar from '@material-ui/core/Avatar'; import Button from '@material-ui/core/Button'; @@ -14,7 +13,7 @@ import { createMuiTheme, makeStyles } from '@material-ui/core/styles'; import { ThemeProvider } from '@material-ui/styles'; import LockIcon from '@material-ui/icons/Lock'; -import { Notification, useTranslate, translate, userLogin } from 'react-admin'; +import { Notification, useTranslate, userLogin } from 'react-admin'; import { lightTheme } from './themes'; @@ -58,7 +57,6 @@ const useStyles = makeStyles(theme => ({ }, })); -// see http://redux-form.com/6.4.3/examples/material-ui/ const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, @@ -73,7 +71,7 @@ const renderInput = ({ /> ); -const Login = ({ handleSubmit, location }) => { +const Login = ({ location }) => { const translate = useTranslate(); const classes = useStyles(); const dispatch = useDispatch(); @@ -84,90 +82,98 @@ const Login = ({ handleSubmit, location }) => { userLogin(auth, location.state ? location.state.nextPathname : '/') ); + const validate = (values, props) => { + const errors = {}; + const { translate } = props; + if (!values.username) { + errors.username = translate('ra.validation.required'); + } + if (!values.password) { + errors.password = translate('ra.validation.required'); + } + return errors; + }; + return ( -
- -
- - - -
-
-
Hint: demo / demo
-
-
- -
-
- -
+ ( + +
+ +
+ + + +
+ +
+ Hint: demo / demo +
+
+
+ +
+
+ +
+
+ + + + +
+
- - - - - -
+ )} + /> ); }; Login.propTypes = { - ...propTypes, authProvider: PropTypes.func, previousRoute: PropTypes.string, }; -const enhance = compose( - translate, - reduxForm({ - form: 'signIn', - validate: (values, props) => { - const errors = {}; - const { translate } = props; - if (!values.username) { - errors.username = translate('ra.validation.required'); - } - if (!values.password) { - errors.password = translate('ra.validation.required'); - } - return errors; - }, - }) -); - -const EnhancedLogin = enhance(Login); - // We need to put the ThemeProvider decoration in another component // Because otherwise the withStyles() HOC used in EnhancedLogin won't get // the right theme const LoginWithTheme = props => ( - + ); diff --git a/examples/demo/src/orders/OrderList.js b/examples/demo/src/orders/OrderList.js index 3cf1fd2039f..fb30f96ecb3 100644 --- a/examples/demo/src/orders/OrderList.js +++ b/examples/demo/src/orders/OrderList.js @@ -69,6 +69,7 @@ class TabbedDatagrid extends React.Component { render() { const { classes, filterValues, ...props } = this.props; + console.log({ filterValues }); return ( { AcceptButton.propTypes = { record: PropTypes.object, - comment: PropTypes.string, }; export default AcceptButton; diff --git a/examples/demo/src/reviews/RejectButton.js b/examples/demo/src/reviews/RejectButton.js index ea1cb3640fc..a907e983225 100644 --- a/examples/demo/src/reviews/RejectButton.js +++ b/examples/demo/src/reviews/RejectButton.js @@ -1,27 +1,47 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { formValueSelector } from 'redux-form'; import Button from '@material-ui/core/Button'; import ThumbDown from '@material-ui/icons/ThumbDown'; -import { useTranslate } from 'react-admin'; -import { reviewReject as reviewRejectAction } from './reviewActions'; +import { useTranslate, useMutation } from 'react-admin'; +const options = { + undoable: true, + onSuccess: { + notification: { + body: 'resources.reviews.notification.rejected_success', + level: 'info', + }, + redirectTo: '/reviews', + }, + onFailure: { + notification: { + body: 'resources.reviews.notification.rejected_error', + level: 'warning', + }, + }, +}; /** * This custom button demonstrate using a custom action to update data */ -const RejectButton = ({ record, reviewReject, comment }) => { +const RejectButton = ({ record }) => { const translate = useTranslate(); - const handleReject = () => { - reviewReject(record.id, { ...record, comment }); - }; + + const [reject, { loading }] = useMutation( + { + type: 'UPDATE', + resource: 'reviews', + payload: { id: record.id, data: { status: 'rejected' } }, + }, + options + ); return record && record.status === 'pending' ? ( - - -); +const LoginForm: SFC = ({ redirectTo }) => { + const dispatch = useDispatch(); + const isLoading = useSelector( + (state: ReduxState) => state.admin.loading > 0 + ); + const translate = useTranslate(); + const classes = useStyles({}); -const mapStateToProps = (state: ReduxState) => ({ - isLoading: state.admin.loading > 0, -}); + const validate = (values: FormData) => { + const errors = { username: undefined, password: undefined }; -const enhance = compose( - withStyles(styles), - withTranslate, - connect(mapStateToProps), - reduxForm({ - form: 'signIn', - validate: (values: FormData, props: TranslationContextProps) => { - const errors = { username: '', password: '' }; - const { translate } = props; - if (!values.username) { - errors.username = translate('ra.validation.required'); - } - if (!values.password) { - errors.password = translate('ra.validation.required'); - } - return errors; - }, - }) -); + if (!values.username) { + errors.username = translate('ra.validation.required'); + } + if (!values.password) { + errors.password = translate('ra.validation.required'); + } + return errors; + }; + + const submit = values => { + dispatch(userLogin(values, redirectTo)); + }; -const EnhancedLoginForm = enhance(LoginForm); + return ( +
( + +
+
+ +
+
+ +
+
+ + + +
+ )} + /> + ); +}; -EnhancedLoginForm.propTypes = { +LoginForm.propTypes = { redirectTo: PropTypes.string, }; -export default EnhancedLoginForm; +export default LoginForm; diff --git a/packages/ra-ui-materialui/src/detail/Create.js b/packages/ra-ui-materialui/src/detail/Create.js index 5a9b8dd729d..28aaed99491 100644 --- a/packages/ra-ui-materialui/src/detail/Create.js +++ b/packages/ra-ui-materialui/src/detail/Create.js @@ -118,6 +118,7 @@ export const CreateView = props => { resource, save, title, + version, ...rest } = props; useCheckMinimumRequiredProps('Create', ['children'], props); @@ -155,6 +156,7 @@ export const CreateView = props => { : children.props.redirect, resource, save, + version, })}
{aside && @@ -163,6 +165,7 @@ export const CreateView = props => { record, resource, save, + version, })}
diff --git a/packages/ra-ui-materialui/src/form/FormTab.js b/packages/ra-ui-materialui/src/form/FormTab.js index 5431b784635..deee870e2c1 100644 --- a/packages/ra-ui-materialui/src/form/FormTab.js +++ b/packages/ra-ui-materialui/src/form/FormTab.js @@ -20,7 +20,7 @@ const hiddenStyle = { display: 'none' }; class FormTab extends Component { renderHeader = ({ className, label, icon, value, translate, ...rest }) => { - const to = { pathname: value, search: 'skipFormReset' }; // FIXME use location state when https://github.com/supasate/connected-react-router/issues/301 is fixed + const to = { pathname: value }; return ( props; -export class SimpleForm extends Component { - handleSubmitWithRedirect = (redirect = this.props.redirect) => - this.props.handleSubmit(values => this.props.save(values, redirect)); +export const SimpleForm = ({ + basePath, + children, + className, + invalid, + form, + pristine, + record, + redirect: defaultRedirect, + resource, + saving, + setRedirect, + submitOnEnter, + toolbar, + undoable, + version, + handleSubmit, + ...rest +}) => { + useEffect(() => { + form.batch(() => { + Object.keys(record).forEach(key => { + form.change(key, record[key]); + }); + }); + }, []); // eslint-disable-line - render() { - const { - basePath, - children, - className, - invalid, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - toolbar, - undoable, - version, - ...rest - } = this.props; + const handleSubmitWithRedirect = useCallback( + (redirect = defaultRedirect) => () => { + setRedirect(redirect); + handleSubmit(); + }, + [setRedirect, defaultRedirect, handleSubmit] + ); - return ( -
- - {Children.map(children, input => ( - - ))} - - {toolbar && - React.cloneElement(toolbar, { - basePath, - handleSubmitWithRedirect: this.handleSubmitWithRedirect, - handleSubmit: this.props.handleSubmit, - invalid, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - undoable, - })} -
- ); - } -} + return ( +
+ + {Children.map(children, input => ( + + ))} + + {toolbar && + React.cloneElement(toolbar, { + basePath, + handleSubmitWithRedirect, + handleSubmit, + invalid, + pristine, + record, + redirect: defaultRedirect, + resource, + saving, + submitOnEnter, + undoable, + })} +
+ ); +}; SimpleForm.propTypes = { basePath: PropTypes.string, children: PropTypes.node, className: PropTypes.string, defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - handleSubmit: PropTypes.func, // passed by redux-form + handleSubmit: PropTypes.func, // passed by react-final-form invalid: PropTypes.bool, pristine: PropTypes.bool, record: PropTypes.object, @@ -138,18 +158,48 @@ SimpleForm.defaultProps = { toolbar: , }; -const enhance = compose( - connect((state, props) => ({ - form: props.form || REDUX_FORM_NAME, - initialValues: getDefaultValues(state, props), - saving: props.saving || state.admin.saving, - })), - translate, // Must be before reduxForm so that it can be used in validation - reduxForm({ - destroyOnUnmount: false, - enableReinitialize: true, - keepDirtyOnReinitialize: true, - }) -); +const EnhancedSimpleForm = ({ initialValues, ...props }) => { + let redirect; + // We don't use state here for two reasons: + // 1. There no way to execute code only after the state has been updated + // 2. We don't want the form to rerender when redirect is changed + const setRedirect = newRedirect => { + redirect = newRedirect; + }; + + const saving = useSelector(state => state.admin.saving); + const translate = useTranslate(); + const submit = values => { + const finalRedirect = + typeof redirect === undefined ? props.redirect : redirect; + props.save(values, finalRedirect); + }; + + const finalInitialValues = { + ...initialValues, + ...props.record, + }; + + return ( +
( + + )} + /> + ); +}; -export default enhance(SimpleForm); +export default EnhancedSimpleForm; diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.js b/packages/ra-ui-materialui/src/form/TabbedForm.js index 8762d2bdaf0..fd1df5f2b83 100644 --- a/packages/ra-ui-materialui/src/form/TabbedForm.js +++ b/packages/ra-ui-materialui/src/form/TabbedForm.js @@ -1,27 +1,21 @@ -import React, { Children, Component, isValidElement } from 'react'; +import React, { Children, useCallback, useEffect, isValidElement } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { - reduxForm, - getFormAsyncErrors, - getFormSyncErrors, - getFormSubmitErrors, -} from 'redux-form'; -import { connect } from 'react-redux'; +import { Form } from 'react-final-form'; +import arrayMutators from 'final-form-arrays'; +import { useSelector } from 'react-redux'; import { withRouter, Route } from 'react-router-dom'; -import compose from 'recompose/compose'; import Divider from '@material-ui/core/Divider'; -import { withStyles, createStyles } from '@material-ui/core/styles'; -import { getDefaultValues, translate, REDUX_FORM_NAME } from 'ra-core'; +import { makeStyles } from '@material-ui/core/styles'; +import { useTranslate } from 'ra-core'; import Toolbar from './Toolbar'; import CardContentInner from '../layout/CardContentInner'; import TabbedFormTabs from './TabbedFormTabs'; -const styles = theme => - createStyles({ - errorTabButton: { color: theme.palette.error.main }, - }); +const useStyles = makeStyles(theme => ({ + errorTabButton: { color: theme.palette.error.main }, +})); const sanitizeRestProps = ({ anyTouched, @@ -38,9 +32,13 @@ const sanitizeRestProps = ({ clearSubmitErrors, destroy, dirty, + dirtyFields, + dirtySinceLastSubmit, dispatch, form, handleSubmit, + hasSubmitErrors, + hasValidationErrors, initialize, initialized, initialValues, @@ -53,6 +51,8 @@ const sanitizeRestProps = ({ staticContext, submit, submitAsSideEffect, + submitError, + submitErrors, submitFailed, submitSucceeded, submitting, @@ -63,6 +63,7 @@ const sanitizeRestProps = ({ untouch, valid, validate, + validating, _reduxForm, ...props }) => props; @@ -72,113 +73,109 @@ export const getTabFullPath = (tab, index, baseUrl) => tab.props.path ? `/${tab.props.path}` : index > 0 ? `/${index}` : '' }`; -export class TabbedForm extends Component { - handleSubmitWithRedirect = (redirect = this.props.redirect) => - this.props.handleSubmit(values => this.props.save(values, redirect)); +export const TabbedForm = ({ + basePath, + children, + className, + classes = {}, + form, + handleSubmit, + invalid, + location, + match, + pristine, + record, + redirect: defaultRedirect, + resource, + saving, + setRedirect, + submitOnEnter, + tabs, + tabsWithErrors = [], + toolbar, + translate, + undoable, + value, + version, + ...rest +}) => { + useEffect(() => { + form.batch(() => { + Object.keys(record).forEach(key => { + form.change(key, record[key]); + }); + }); + }, []); // eslint-disable-line - render() { - const { - basePath, - children, - className, - classes = {}, - invalid, - location, - match, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - tabs, - tabsWithErrors, - toolbar, - translate, - undoable, - value, - version, - ...rest - } = this.props; + const handleSubmitWithRedirect = useCallback( + (redirect = defaultRedirect) => () => { + setRedirect(redirect); + handleSubmit(); + }, + [setRedirect, defaultRedirect, handleSubmit] + ); - const url = match ? match.url : location.pathname; - return ( - - {React.cloneElement( - tabs, - { - classes, - currentLocationPath: location.pathname, - url, - tabsWithErrors, - }, - children + const url = match ? match.url : location.pathname; + return ( + + {React.cloneElement( + tabs, + { + classes, + currentLocationPath: location.pathname, + url, + tabsWithErrors, + }, + children + )} + + + {/* All tabs are rendered (not only the one in focus), to allow validation + on tabs not in focus. The tabs receive a `hidden` property, which they'll + use to hide the tab using CSS if it's not the one in focus. + See https://github.com/marmelab/react-admin/issues/1866 */} + {Children.map( + children, + (tab, index) => + tab && ( + + {routeProps => + isValidElement(tab) + ? React.cloneElement(tab, { + intent: 'content', + resource, + record, + basePath, + hidden: !routeProps.match, + }) + : null + } + + ) )} - - - {/* All tabs are rendered (not only the one in focus), to allow validation - on tabs not in focus. The tabs receive a `hidden` property, which they'll - use to hide the tab using CSS if it's not the one in focus. - See https://github.com/marmelab/react-admin/issues/1866 */} - {Children.map( - children, - (tab, index) => - tab && ( - - {routeProps => - isValidElement(tab) - ? React.cloneElement(tab, { - intent: 'content', - resource, - record, - basePath, - hidden: !routeProps.match, - /** - * Force redraw when the tab becomes active - * - * This is because the fields, decorated by redux-form and connect, - * aren't redrawn by default when the tab becomes active. - * Unfortunately, some material-ui fields (like multiline TextField) - * compute their size based on the scrollHeight of a dummy DOM element, - * and scrollHeight is 0 in a hidden div. So they must be redrawn - * once the tab becomes active. - * - * @ref https://github.com/marmelab/react-admin/issues/1956 - */ - key: `${index}_${!routeProps.match}`, - }) - : null - } - - ) - )} - - {toolbar && - React.cloneElement(toolbar, { - basePath, - className: 'toolbar', - handleSubmitWithRedirect: this.handleSubmitWithRedirect, - handleSubmit: this.props.handleSubmit, - invalid, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - undoable, - })} - - ); - } -} + + {toolbar && + React.cloneElement(toolbar, { + basePath, + className: 'toolbar', + handleSubmitWithRedirect, + handleSubmit, + invalid, + pristine, + record, + redirect: defaultRedirect, + resource, + saving, + submitOnEnter, + undoable, + })} + + ); +}; TabbedForm.propTypes = { basePath: PropTypes.string, @@ -186,7 +183,7 @@ TabbedForm.propTypes = { className: PropTypes.string, classes: PropTypes.object, defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - handleSubmit: PropTypes.func, // passed by redux-form + handleSubmit: PropTypes.func, // passed by react-final-form invalid: PropTypes.bool, location: PropTypes.object, match: PropTypes.object, @@ -217,74 +214,84 @@ TabbedForm.defaultProps = { toolbar: , }; -const collectErrors = (state, props) => { - const syncErrors = getFormSyncErrors(props.form)(state); - const asyncErrors = getFormAsyncErrors(props.form)(state); - const submitErrors = getFormSubmitErrors(props.form)(state); +// const collectErrors = (state, props) => { +// const syncErrors = getFormSyncErrors(props.form)(state); +// const asyncErrors = getFormAsyncErrors(props.form)(state); +// const submitErrors = getFormSubmitErrors(props.form)(state); - return { - ...syncErrors, - ...asyncErrors, - ...submitErrors, - }; -}; +// return { +// ...syncErrors, +// ...asyncErrors, +// ...submitErrors, +// }; +// }; -export const findTabsWithErrors = ( - state, - props, - collectErrorsImpl = collectErrors -) => { - const errors = collectErrorsImpl(state, props); +// export const findTabsWithErrors = (state, props) => { +// // const errors = collectErrorsImpl(state, props); - return Children.toArray(props.children).reduce((acc, child) => { - if (!isValidElement(child)) { - return acc; - } +// return Children.toArray(props.children).reduce((acc, child) => { +// if (!isValidElement(child)) { +// return acc; +// } - const inputs = Children.toArray(child.props.children); +// const inputs = Children.toArray(child.props.children); - if ( - inputs.some( - input => isValidElement(input) && errors[input.props.source] - ) - ) { - return [...acc, child.props.label]; - } +// if ( +// inputs.some( +// input => isValidElement(input) && errors[input.props.source] +// ) +// ) { +// return [...acc, child.props.label]; +// } - return acc; - }, []); -}; +// return acc; +// }, []); +// }; + +const EnhancedTabbedForm = ({ initialValues, ...props }) => { + let redirect; + // We don't use state here for two reasons: + // 1. There no way to execute code only after the state has been updated + // 2. We don't want the form to rerender when redirect is changed + const setRedirect = newRedirect => { + redirect = newRedirect; + }; + const saving = useSelector(state => state.admin.saving); + const translate = useTranslate(); + const classes = useStyles(); -const enhance = compose( - withRouter, - connect((state, props) => { - const children = Children.toArray(props.children).reduce( - (acc, child) => [ - ...acc, - ...(isValidElement(child) - ? Children.toArray(child.props.children) - : []), - ], - [] - ); + const submit = values => { + const finalRedirect = + typeof redirect === undefined ? props.redirect : redirect; + props.save(values, finalRedirect); + }; - return { - form: props.form || REDUX_FORM_NAME, - initialValues: getDefaultValues(state, { ...props, children }), - saving: props.saving || state.admin.saving, - tabsWithErrors: findTabsWithErrors(state, { - form: REDUX_FORM_NAME, - ...props, - }), - }; - }), - translate, // Must be before reduxForm so that it can be used in validation - reduxForm({ - destroyOnUnmount: false, - enableReinitialize: true, - keepDirtyOnReinitialize: true, - }), - withStyles(styles) -); + const finalInitialValues = { + ...initialValues, + ...props.record, + }; + + return ( +
( + + )} + /> + ); +}; -export default enhance(TabbedForm); +export default withRouter(EnhancedTabbedForm); diff --git a/packages/ra-ui-materialui/src/input/ArrayInput.js b/packages/ra-ui-materialui/src/input/ArrayInput.js index 800ff7e9570..31728fc4cea 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput.js +++ b/packages/ra-ui-materialui/src/input/ArrayInput.js @@ -1,7 +1,7 @@ import React, { cloneElement, Children, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { isRequired, FieldTitle, withDefaultValue } from 'ra-core'; -import { FieldArray } from 'redux-form'; +import { isRequired, FieldTitle } from 'ra-core'; +import { FieldArray } from 'react-final-form-arrays'; import FormControl from '@material-ui/core/FormControl'; import InputLabel from '@material-ui/core/InputLabel'; @@ -40,7 +40,7 @@ import sanitizeRestProps from './sanitizeRestProps'; * * expects a single child, which must be a *form iterator* component. * A form iterator is a component accepting a fields object - * as passed by redux-form's component, and defining a layout for + * as passed by react-final-form's component, and defining a layout for * an array of fields. For instance, the component * displays an array of fields in an unordered list (
    ), one sub-form by * list item (
  • ). It also provides controls for adding and removing @@ -60,14 +60,14 @@ export const ArrayInput = ({ ...rest }) => { const renderFieldArray = useCallback( - fieldProps => { - return cloneElement(Children.only(children), { + fieldProps => + cloneElement(Children.only(children), { ...fieldProps, record, resource, source, - }); - }, + ...children.props, + }), [resource, source, JSON.stringify(record), children] // eslint-disable-line ); @@ -117,4 +117,4 @@ ArrayInput.defaultProps = { options: {}, fullWidth: true, }; -export default withDefaultValue(ArrayInput); +export default ArrayInput; diff --git a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.js b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.js index 34864a7a563..f19b9fd9077 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.js +++ b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.js @@ -243,7 +243,7 @@ export class AutocompleteArrayInput extends React.Component { } = inputProps; if (typeof meta === 'undefined') { throw new Error( - "The TextInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." + "The TextInput component wasn't called within a react-final-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." ); } diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.js b/packages/ra-ui-materialui/src/input/AutocompleteInput.js index 96e64eef7bd..966c3491fa5 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.js +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.js @@ -260,7 +260,7 @@ export class AutocompleteInput extends React.Component { } = inputProps; if (typeof meta === 'undefined') { throw new Error( - "The TextInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." + "The TextInput component wasn't called within a react-final-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." ); } diff --git a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js index 87867b7cb69..277c5ed7774 100644 --- a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js +++ b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js @@ -173,7 +173,7 @@ export class CheckboxGroupInput extends Component { } = this.props; if (typeof meta === 'undefined') { throw new Error( - "The CheckboxGroupInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." + "The CheckboxGroupInput component wasn't called within a react-final-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." ); } diff --git a/packages/ra-ui-materialui/src/input/DateInput.js b/packages/ra-ui-materialui/src/input/DateInput.js index 000231ede82..f1aae212b95 100644 --- a/packages/ra-ui-materialui/src/input/DateInput.js +++ b/packages/ra-ui-materialui/src/input/DateInput.js @@ -63,7 +63,7 @@ export const DateInput = ({ if (typeof meta === 'undefined') { throw new Error( - "The DateInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." + "The DateInput component wasn't called within a react-final-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." ); } const { touched, error } = meta; diff --git a/packages/ra-ui-materialui/src/list/FilterForm.js b/packages/ra-ui-materialui/src/list/FilterForm.js index abdee9bf29e..7b5efaa2b93 100644 --- a/packages/ra-ui-materialui/src/list/FilterForm.js +++ b/packages/ra-ui-materialui/src/list/FilterForm.js @@ -1,8 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { reduxForm } from 'redux-form'; +import { Form, FormSpy } from 'react-final-form'; import classnames from 'classnames'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import compose from 'recompose/compose'; import withProps from 'recompose/withProps'; import lodashSet from 'lodash/set'; @@ -10,17 +10,16 @@ import lodashGet from 'lodash/get'; import FilterFormInput from './FilterFormInput'; -const styles = theme => - createStyles({ - form: { - marginTop: '-10px', - paddingTop: 0, - display: 'flex', - alignItems: 'flex-end', - flexWrap: 'wrap', - }, - clearFix: { clear: 'right' }, - }); +const useStyles = makeStyles({ + form: { + marginTop: '-10px', + paddingTop: 0, + display: 'flex', + alignItems: 'flex-end', + flexWrap: 'wrap', + }, + clearFix: { clear: 'right' }, +}); const sanitizeRestProps = ({ anyTouched, @@ -35,10 +34,14 @@ const sanitizeRestProps = ({ clearSubmitErrors, destroy, dirty, + dirtyFields, + dirtySinceLastSubmit, dispatch, displayedFilters, filterValues, handleSubmit, + hasSubmitErrors, + hasValidationErrors, hideFilter, initialize, initialized, @@ -53,6 +56,8 @@ const sanitizeRestProps = ({ setFilters, submit, submitAsSideEffect, + submitError, + submitErrors, submitFailed, submitSucceeded, submitting, @@ -61,6 +66,7 @@ const sanitizeRestProps = ({ untouch, valid, validate, + validating, _reduxForm, ...props }) => props; @@ -95,7 +101,7 @@ export class FilterForm extends Component { const { classes = {}, className, resource, ...rest } = this.props; return ( -
    @@ -108,7 +114,7 @@ export class FilterForm extends Component { /> ))}
    -
    + ); } } @@ -147,16 +153,26 @@ export const mergeInitialValuesWithDefaultValues = ({ }, }); -const enhance = compose( - withStyles(styles), - withProps(mergeInitialValuesWithDefaultValues), - reduxForm({ - form: 'filterForm', - enableReinitialize: true, - destroyOnUnmount: false, // do not destroy to preserve state across navigation - onChange: (values, dispatch, props) => - props && props.setFilters(values), - }) -); +const EnhancedFilterForm = props => { + const classes = useStyles(); -export default enhance(FilterForm); + return ( +
    {}} + render={formProps => ( + <> + { + props && props.setFilters(values); + }} + /> + + + )} + /> + ); +}; + +export default withProps(mergeInitialValuesWithDefaultValues)( + EnhancedFilterForm +); diff --git a/packages/ra-ui-materialui/src/list/FilterFormInput.js b/packages/ra-ui-materialui/src/list/FilterFormInput.js index 30ef57797db..9be6f55db89 100644 --- a/packages/ra-ui-materialui/src/list/FilterFormInput.js +++ b/packages/ra-ui-materialui/src/list/FilterFormInput.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Field } from 'redux-form'; +import { Field } from 'react-final-form'; import IconButton from '@material-ui/core/IconButton'; import ActionHide from '@material-ui/icons/HighlightOff'; import { makeStyles } from '@material-ui/core/styles'; diff --git a/yarn.lock b/yarn.lock index 4eb2aed32dd..adc72a95163 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1514,14 +1514,6 @@ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220" integrity sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q== -"@types/hoist-non-react-statics@^3.3.0": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== - dependencies: - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - "@types/isomorphic-fetch@0.0.34": version "0.0.34" resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.34.tgz#3c3483e606c041378438e951464f00e4e60706d6" @@ -1589,16 +1581,6 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== -"@types/react-redux@^7.1.0", "@types/react-redux@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.1.tgz#eb01e89cf71cad77df9f442b819d5db692b997cb" - integrity sha512-owqNahzE8en/jR4NtrUJDJya3tKru7CIEGSRL/pVS84LtSCdSoT7qZTkrbBd3S4Lp11sAp+7LsvxIeONJVKMnw== - dependencies: - "@types/hoist-non-react-statics" "^3.3.0" - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - redux "^4.0.0" - "@types/react-router-dom@^4.3.1": version "4.3.4" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.4.tgz#63a7a8558129d2f4ff76e4bdd099bf4b98e25a0d" @@ -1638,14 +1620,6 @@ dependencies: "@types/react" "*" -"@types/redux-form@^7.5.2": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@types/redux-form/-/redux-form-7.5.5.tgz#71f19679331cf01c2237551bce8b7a5c0052ca23" - integrity sha512-UoW4dvUYOT9yoMeCZpBwpQatBGWcF/olK6wnMfneocgF3emF4jcReOdlUkx9YzDZYGBzNd9L8y3MWYZ2SuoDfg== - dependencies: - "@types/react" "*" - redux "^3.6.0 || ^4.0.0" - "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -5905,11 +5879,6 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - es6-templates@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/es6-templates/-/es6-templates-0.2.3.tgz#5cb9ac9fb1ded6eb1239342b81d792bbb4078ee4" @@ -6758,6 +6727,18 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +final-form-arrays@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/final-form-arrays/-/final-form-arrays-3.0.1.tgz#862e5e946d391039ebcdfadbe55fc3a63849e559" + integrity sha512-GKXecufCNCjDcz1+3peL21LuuTlApoxCcnpOnmfeJfC3xAlFKGdytYMfifP7W1IEWTGC8twTv3zItESkej8qpg== + +final-form@^4.18.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.18.2.tgz#3e7447ba36049a747d4becc61eb35e65a90f22a1" + integrity sha512-VQx/5x9M4CiC8fG678Dm1IS3mXvBl7ZNIUx5tUZCk00lFImJzQix4KO0+eGtl49sha2bYOxuYn8jRJiq6sazXA== + dependencies: + "@babel/runtime" "^7.3.1" + finalhandler@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" @@ -8088,7 +8069,7 @@ immer@1.10.0: resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== -immutable@3.8.2, immutable@^3.8.1: +immutable@^3.8.1: version "3.8.2" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM= @@ -9996,10 +9977,10 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.11, lodash-es@^4.2.1: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" - integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== +lodash-es@^4.2.1: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" + integrity sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg== lodash._objecttypes@~2.4.1: version "2.4.1" @@ -12923,6 +12904,21 @@ react-error-overlay@^5.1.6: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d" integrity sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q== +react-final-form-arrays@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/react-final-form-arrays/-/react-final-form-arrays-3.1.0.tgz#8ebaab391a665e7e17e463b88cdf3b1078e3f4f0" + integrity sha512-eJdAlhTKzlDD/d1wedD592a99eJNGO0e9GzY++RLN99P23cMGKSzCmsiGWLPwpY0H/C/LmNSL4XzWyH/aZembA== + dependencies: + "@babel/runtime" "^7.4.5" + +react-final-form@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-6.3.0.tgz#b85ae123a3796b3e0c8f56033c8a5037f4497e76" + integrity sha512-jijhXR1fFGUBQwNOSqF4MK8XJO7Ynl1p8vcFsnQS0INSkGI52+4IagjUgtHj3w8EviIHPFK/Eflji6FELUl07w== + dependencies: + "@babel/runtime" "^7.4.5" + ts-essentials "^2.0.8" + react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" @@ -13307,24 +13303,6 @@ redent@^2.0.0: indent-string "^3.0.0" strip-indent "^2.0.0" -redux-form@~8.2.0: - version "8.2.4" - resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-8.2.4.tgz#6c21c49e31b473cebe718def03487fc9d4665923" - integrity sha512-+MMD5XWVUgrtgXBuQiIYtHstPT7Lz0Izuc6vqBr1S9+7HbGgvJg4V5qDr2nigE1V5hKgsqnoAmWOWtzXc9ErZA== - dependencies: - "@babel/runtime" "^7.2.0" - es6-error "^4.1.1" - hoist-non-react-statics "^3.2.1" - invariant "^2.2.4" - is-promise "^2.1.0" - lodash "^4.17.11" - lodash-es "^4.17.11" - prop-types "^15.6.1" - react-is "^16.7.0" - react-lifecycles-compat "^3.0.4" - optionalDependencies: - immutable "3.8.2" - redux-saga@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.0.5.tgz#03317261bc5fa7ee2ecb778f4e4848f573557bbb" @@ -13332,10 +13310,10 @@ redux-saga@^1.0.0: dependencies: "@redux-saga/core" "^1.0.3" -"redux@>=0.10 <5", "redux@^3.6.0 || ^4.0.0", "redux@^3.7.2 || ^4.0.0", redux@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" - integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== +"redux@>=0.10 <5", "redux@^3.7.2 || ^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5" + integrity sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg== dependencies: loose-envify "^1.4.0" symbol-observable "^1.2.0" @@ -13350,6 +13328,14 @@ redux@^3.4.0: loose-envify "^1.1.0" symbol-observable "^1.0.3" +redux@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" + integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -15113,6 +15099,11 @@ tryer@^1.0.1: resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +ts-essentials@^2.0.8: + version "2.0.12" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" + integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== + ts-invariant@^0.4.0: version "0.4.4" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86" @@ -15213,7 +15204,7 @@ typescript-tuple@^2.1.0: dependencies: typescript-compare "^0.0.2" -typescript@^3.4.5: +typescript@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==