Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[es_ui_shared] Fix eslint exhaustive deps rule #76392

Merged
merged 11 commits into from
Sep 3, 2020
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 { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
Expand Down Expand Up @@ -134,9 +134,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