Skip to content

Commit

Permalink
[7.x] [es_ui_shared] Fix eslint exhaustive deps rule (#76392) (#76670)
Browse files Browse the repository at this point in the history
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
sebelga and elasticmachine authored Sep 5, 2020
1 parent 306fd13 commit 0eea810
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,7 @@ export const useGlobalFlyout = () => {
Array.from(getContents()).forEach(removeContent);
}
};
// https://github.com/elastic/kibana/issues/73970
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [removeContent]);
}, [removeContent, getContents]);

return { ...ctx, addContent };
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@

export * from './json_editor';

export { OnJsonEditorUpdateHandler } from './use_json';
export { OnJsonEditorUpdateHandler, JsonEditorState } from './use_json';
151 changes: 75 additions & 76 deletions src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,98 +17,97 @@
* under the License.
*/

import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import { EuiFormRow, EuiCodeEditor } from '@elastic/eui';
import { debounce } from 'lodash';

import { isJSON } from '../../../static/validators/string';
import { useJson, OnJsonEditorUpdateHandler } from './use_json';

interface Props {
onUpdate: OnJsonEditorUpdateHandler;
interface Props<T extends object = { [key: string]: any }> {
onUpdate: OnJsonEditorUpdateHandler<T>;
label?: string;
helpText?: React.ReactNode;
value?: string;
defaultValue?: { [key: string]: any };
defaultValue?: T;
euiCodeEditorProps?: { [key: string]: any };
error?: string | null;
}

export const JsonEditor = React.memo(
({
label,
helpText,
function JsonEditorComp<T extends object = { [key: string]: any }>({
label,
helpText,
onUpdate,
value,
defaultValue,
euiCodeEditorProps,
error: propsError,
}: Props<T>) {
const { content, setContent, error: internalError, isControlled } = useJson<T>({
defaultValue,
onUpdate,
value,
defaultValue,
euiCodeEditorProps,
error: propsError,
}: Props) => {
const isControlled = value !== undefined;
});

const { content, setContent, error: internalError } = useJson({
defaultValue,
onUpdate,
isControlled,
});
const debouncedSetContent = useMemo(() => {
return debounce(setContent, 300);
}, [setContent]);

// https://github.com/elastic/kibana/issues/73971
/* eslint-disable-next-line react-hooks/exhaustive-deps */
const debouncedSetContent = useCallback(debounce(setContent, 300), [setContent]);
// We let the consumer control the validation and the error message.
const error = isControlled ? propsError : internalError;

// We let the consumer control the validation and the error message.
const error = isControlled ? propsError : internalError;
const onEuiCodeEditorChange = useCallback(
(updated: string) => {
if (isControlled) {
onUpdate({
data: {
raw: updated,
format: () => JSON.parse(updated),
},
validate: () => {
try {
JSON.parse(updated);
return true;
} catch (e) {
return false;
}
},
isValid: undefined,
});
} else {
debouncedSetContent(updated);
}
},
[isControlled, debouncedSetContent, onUpdate]
);

const onEuiCodeEditorChange = useCallback(
(updated: string) => {
if (isControlled) {
onUpdate({
data: {
raw: updated,
format() {
return JSON.parse(updated);
},
},
validate() {
return isJSON(updated);
},
isValid: undefined,
});
} else {
debouncedSetContent(updated);
}
},
/* eslint-disable-next-line react-hooks/exhaustive-deps */
[isControlled]
);
return (
<EuiFormRow
label={label}
helpText={helpText}
isInvalid={typeof error === 'string'}
error={error}
fullWidth
>
<EuiCodeEditor
mode="json"
theme="textmate"
width="100%"
height="500px"
setOptions={{
showLineNumbers: false,
tabSize: 2,
}}
editorProps={{
$blockScrolling: Infinity,
}}
showGutter={false}
minLines={6}
value={isControlled ? value : content}
onChange={onEuiCodeEditorChange}
{...euiCodeEditorProps}
/>
</EuiFormRow>
);
}

return (
<EuiFormRow
label={label}
helpText={helpText}
isInvalid={typeof error === 'string'}
error={error}
fullWidth
>
<EuiCodeEditor
mode="json"
theme="textmate"
width="100%"
height="500px"
setOptions={{
showLineNumbers: false,
tabSize: 2,
}}
editorProps={{
$blockScrolling: Infinity,
}}
showGutter={false}
minLines={6}
value={isControlled ? value : content}
onChange={onEuiCodeEditorChange}
{...euiCodeEditorProps}
/>
</EuiFormRow>
);
}
);
export const JsonEditor = React.memo(JsonEditorComp) as typeof JsonEditorComp;
67 changes: 41 additions & 26 deletions src/plugins/es_ui_shared/public/components/json_editor/use_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,28 @@
* under the License.
*/

import { useEffect, useState, useRef } from 'react';
import { useEffect, useState, useRef, useCallback } from 'react';
import { i18n } from '@kbn/i18n';

import { isJSON } from '../../../static/validators/string';

export type OnJsonEditorUpdateHandler<T = { [key: string]: any }> = (arg: {
export interface JsonEditorState<T = { [key: string]: any }> {
data: {
raw: string;
format(): T;
};
validate(): boolean;
isValid: boolean | undefined;
}) => void;
}

export type OnJsonEditorUpdateHandler<T = { [key: string]: any }> = (
arg: JsonEditorState<T>
) => void;

interface Parameters<T extends object> {
onUpdate: OnJsonEditorUpdateHandler<T>;
defaultValue?: T;
isControlled?: boolean;
value?: string;
}

const stringifyJson = (json: { [key: string]: any }) =>
Expand All @@ -43,13 +47,16 @@ const stringifyJson = (json: { [key: string]: any }) =>
export const useJson = <T extends object = { [key: string]: any }>({
defaultValue = {} as T,
onUpdate,
isControlled = false,
value,
}: Parameters<T>) => {
const didMount = useRef(false);
const [content, setContent] = useState<string>(stringifyJson(defaultValue));
const isControlled = value !== undefined;
const isMounted = useRef(false);
const [content, setContent] = useState<string>(
isControlled ? value! : stringifyJson(defaultValue)
);
const [error, setError] = useState<string | null>(null);

const validate = () => {
const validate = useCallback(() => {
// We allow empty string as it will be converted to "{}""
const isValid = content.trim() === '' ? true : isJSON(content);
if (!isValid) {
Expand All @@ -62,35 +69,43 @@ export const useJson = <T extends object = { [key: string]: any }>({
setError(null);
}
return isValid;
};
}, [content]);

const formatContent = () => {
const formatContent = useCallback(() => {
const isValid = validate();
const data = isValid && content.trim() !== '' ? JSON.parse(content) : {};
return data as T;
};
}, [validate, content]);

useEffect(() => {
if (didMount.current) {
const isValid = isControlled ? undefined : validate();
onUpdate({
data: {
raw: content,
format: formatContent,
},
validate,
isValid,
});
} else {
didMount.current = true;
if (!isMounted.current || isControlled) {
return;
}
// https://github.com/elastic/kibana/issues/73971
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [content]);

const isValid = validate();

onUpdate({
data: {
raw: content,
format: formatContent,
},
validate,
isValid,
});
}, [onUpdate, content, formatContent, validate, isControlled]);

useEffect(() => {
isMounted.current = true;

return () => {
isMounted.current = false;
};
}, []);

return {
content,
setContent,
error,
isControlled,
};
};
2 changes: 1 addition & 1 deletion src/plugins/es_ui_shared/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import * as Monaco from './monaco';
import * as ace from './ace';
import * as GlobalFlyout from './global_flyout';

export { JsonEditor, OnJsonEditorUpdateHandler } from './components/json_editor';
export { JsonEditor, OnJsonEditorUpdateHandler, JsonEditorState } from './components/json_editor';

export { SectionLoading } from './components/section_loading';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,16 @@ interface Props {

export const RangeField = ({ field, euiFieldProps = {}, ...rest }: Props) => {
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
const { onChange: onFieldChange } = field;

const onChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement> | React.MouseEvent<HTMLButtonElement>) => {
const event = ({ ...e, value: `${e.currentTarget.value}` } as unknown) as React.ChangeEvent<{
value: string;
}>;
field.onChange(event);
onFieldChange(event);
},
// https://github.com/elastic/kibana/issues/73972
/* eslint-disable-next-line react-hooks/exhaustive-deps */
[field.onChange]
[onFieldChange]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useState, useRef } from 'react';
import React, { useState, useRef, useCallback } from 'react';
import { isPlainObject } from 'lodash';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
Expand Down Expand Up @@ -138,9 +138,9 @@ export const LoadMappingsProvider = ({ onJson, children }: Props) => {
state.json !== undefined && state.errors !== undefined ? 'validationResult' : 'json';
const i18nTexts = getTexts(view, state.errors?.length);

const onJsonUpdate: OnJsonEditorUpdateHandler = (jsonUpdateData) => {
const onJsonUpdate: OnJsonEditorUpdateHandler = useCallback((jsonUpdateData) => {
jsonContent.current = jsonUpdateData;
};
}, []);

const openModal: OpenJsonModalFunc = () => {
setState({ isModalOpen: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { FunctionComponent, useRef, useState } from 'react';
import React, { FunctionComponent, useRef, useState, useCallback } from 'react';
import { EuiConfirmModal, EuiOverlayMask, EuiSpacer, EuiText, EuiCallOut } from '@elastic/eui';

import { JsonEditor, OnJsonEditorUpdateHandler } from '../../../../../shared_imports';
Expand Down Expand Up @@ -66,10 +66,12 @@ export const ModalProvider: FunctionComponent<Props> = ({ onDone, children }) =>
raw: defaultValueRaw,
},
});
const onJsonUpdate: OnJsonEditorUpdateHandler = (jsonUpdateData) => {

const onJsonUpdate: OnJsonEditorUpdateHandler = useCallback((jsonUpdateData) => {
setIsValidJson(jsonUpdateData.validate());
jsonContent.current = jsonUpdateData;
};
}, []);

return (
<>
{children(() => setIsModalVisible(true))}
Expand Down

0 comments on commit 0eea810

Please sign in to comment.