Fix infinite render loop
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Sat, 13 Oct 2018 20:55:56 +0000 (22:55 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Sat, 13 Oct 2018 20:55:56 +0000 (22:55 +0200)
Feature #13862

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

package.json
src/components/chips-input/chips-input.tsx
src/views/run-process-panel/inputs/string-array-input.tsx
src/views/run-process-panel/run-process-inputs-form.tsx
src/views/run-process-panel/run-process-second-step.tsx
yarn.lock

index d02cc38dfe0535b5c4925ddf4f6fc863cdd25281..8ed84dd9b818b121be780d176760b7cc3fc40922 100644 (file)
@@ -12,6 +12,7 @@
     "@types/react-dropzone": "4.2.2",
     "@types/react-highlight-words": "0.12.0",
     "@types/redux-form": "7.4.5",
+    "@types/reselect": "2.2.0",
     "@types/shell-quote": "1.6.0",
     "axios": "0.18.0",
     "classnames": "2.2.6",
index 171641aee871bc1b61c386c7c30f3274166c8890..96e7b70a104286e4c5a059ba85eb4cc941af6ce3 100644 (file)
@@ -69,11 +69,10 @@ export const ChipsInput = withStyles(styles)(
         }
 
         updateCursorPosition = () => {
-            console.log('cursorPoristion');
             if (this.timeout) {
                 clearTimeout(this.timeout);
             }
-            this.timeout = setTimeout(() => this.forceUpdate());
+            this.timeout = setTimeout(() => this.setState({ ...this.state }));
         }
 
         getInputStyles = (): React.CSSProperties => ({
@@ -91,7 +90,6 @@ export const ChipsInput = withStyles(styles)(
         }
 
         render() {
-            console.log(`Render: ${this.props.values}`);
             return <>
                 <div className={this.props.classes.chips}>
                     <Chips
@@ -114,7 +112,6 @@ export const ChipsInput = withStyles(styles)(
 
         componentDidUpdate(prevProps: ChipsInputProps<Value>) {
             if (prevProps.values !== this.props.values) {
-                console.log('didUpdate');
                 this.updateCursorPosition();
             }
         }
index 7454e2ad1175ff95576eb5b1d60d564e4e53b020..3b29d1a8cf9fa48c822b3b1019b5e9f56cc5e877 100644 (file)
@@ -7,7 +7,9 @@ import { isRequiredInput, StringArrayCommandInputParameter } from '~/models/work
 import { Field } from 'redux-form';
 import { ERROR_MESSAGE } from '~/validators/require';
 import { GenericInputProps, GenericInput } from '~/views/run-process-panel/inputs/generic-input';
-import { ChipsInput } from '../../../components/chips-input/chips-input';
+import { ChipsInput } from '~/components/chips-input/chips-input';
+import { identity } from 'lodash';
+import { createSelector } from 'reselect';
 
 export interface StringArrayInputProps {
     input: StringArrayCommandInputParameter;
@@ -17,19 +19,39 @@ export const StringArrayInput = ({ input }: StringArrayInputProps) =>
         name={input.id}
         commandInput={input}
         component={StringArrayInputComponent}
-        validate={[
-            isRequiredInput(input)
-                ? (value: string[]) => value.length > 0 ? undefined : ERROR_MESSAGE
-                : () => undefined,
-        ]} />;
+        validate={validationSelector(input)} />;
+
+
+const validationSelector = createSelector(
+    isRequiredInput,
+    isRequired => isRequired
+        ? [required]
+        : undefined
+);
+
+const required = (value: string[]) =>
+    value.length > 0
+        ? undefined
+        : ERROR_MESSAGE;
 
 const StringArrayInputComponent = (props: GenericInputProps) =>
     <GenericInput
         component={Input}
         {...props} />;
 
-const Input = (props: GenericInputProps) =>
-    <ChipsInput
-        values={props.input.value}
-        onChange={props.input.onChange}
-        createNewValue={v => v} />;
+class Input extends React.PureComponent<GenericInputProps>{
+    render() {
+        return <ChipsInput
+            values={this.props.input.value}
+            onChange={this.handleChange}
+            createNewValue={identity} />;
+    }
+
+    handleChange = (values: {}[]) => {
+        const { input, meta } = this.props;
+        if (!meta.touched) {
+            input.onBlur(values);
+        }
+        input.onChange(values);
+    }
+}
index 14d8f6383a16e2bc55b0e359b7fd898528b39cd2..912be0d8fd81c6d62c2938b1bf391ee3d607ed12 100644 (file)
@@ -17,6 +17,7 @@ import { Grid, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/c
 import { EnumInput } from './inputs/enum-input';
 import { DirectoryInput } from './inputs/directory-input';
 import { StringArrayInput } from './inputs/string-array-input';
+import { createStructuredSelector, createSelector } from 'reselect';
 
 export const RUN_PROCESS_INPUTS_FORM = 'runProcessInputsForm';
 
@@ -24,12 +25,24 @@ export interface RunProcessInputFormProps {
     inputs: CommandInputParameter[];
 }
 
+const inputsSelector = (props: RunProcessInputFormProps) =>
+    props.inputs;
+
+const initialValuesSelector = createSelector(
+    inputsSelector,
+    inputs => inputs.reduce(
+        (values, input) => ({ ...values, [input.id]: input.default }),
+        {}));
+
+const propsSelector = createStructuredSelector({
+    initialValues: initialValuesSelector,
+});
+
+const mapStateToProps = (_: any, props: RunProcessInputFormProps) =>
+    propsSelector(props);
+
 export const RunProcessInputsForm = compose(
-    connect((_: any, props: RunProcessInputFormProps) => ({
-        initialValues: props.inputs.reduce(
-            (values, input) => ({ ...values, [input.id]: input.default }),
-            {}),
-    })),
+    connect(mapStateToProps),
     reduxForm<WorkflowInputsData, RunProcessInputFormProps>({
         form: RUN_PROCESS_INPUTS_FORM
     }))(
index 2585136e6ea20a86fb86d3584a471ddf98ede586..0b8563822e4906be2dc62147cee5a0f7139f1bcf 100644 (file)
@@ -12,6 +12,7 @@ import { RootState } from '~/store/store';
 import { isValid } from 'redux-form';
 import { RUN_PROCESS_INPUTS_FORM } from './run-process-inputs-form';
 import { RunProcessAdvancedForm } from './run-process-advanced-form';
+import { createSelector, createStructuredSelector } from 'reselect';
 
 export interface RunProcessSecondStepFormDataProps {
     inputs: CommandInputParameter[];
@@ -23,10 +24,15 @@ export interface RunProcessSecondStepFormActionProps {
     runProcess: () => void;
 }
 
-const mapStateToProps = (state: RootState): RunProcessSecondStepFormDataProps => ({
-    inputs: state.runProcessPanel.inputs,
-    valid: isValid(RUN_PROCESS_BASIC_FORM)(state) &&
-        isValid(RUN_PROCESS_INPUTS_FORM)(state),
+const inputsSelector = (state: RootState) =>
+    state.runProcessPanel.inputs;
+
+const validSelector = (state: RootState) =>
+    isValid(RUN_PROCESS_BASIC_FORM)(state) && isValid(RUN_PROCESS_INPUTS_FORM)(state);
+
+const mapStateToProps = createStructuredSelector({
+    inputs: inputsSelector,
+    valid: validSelector,
 });
 
 export type RunProcessSecondStepFormProps = RunProcessSecondStepFormDataProps & RunProcessSecondStepFormActionProps;
index 58386c55c0c4882c8711d83aac302254bfc4f6f5..f3bf49b139b2ab3ebe97bddb21b642dd9f5698d4 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
     "@types/react" "*"
     redux "^3.6.0 || ^4.0.0"
 
+"@types/reselect@2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@types/reselect/-/reselect-2.2.0.tgz#c667206cfdc38190e1d379babe08865b2288575f"
+  dependencies:
+    reselect "*"
+
 "@types/shell-quote@1.6.0":
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/@types/shell-quote/-/shell-quote-1.6.0.tgz#537b2949a2ebdcb0d353e448fee45b081021963f"
@@ -6904,7 +6910,7 @@ requires-port@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
 
-reselect@4.0.0:
+reselect@*, reselect@4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"