[Lens] Clean and inline disabling of react-hooks/exhaustive-deps eslint rule (#70010)

This commit is contained in:
Marta Bondyra 2020-08-06 10:10:09 +02:00 committed by GitHub
parent aa75f80afd
commit 626fbc2948
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 282 additions and 238 deletions

View file

@ -132,12 +132,6 @@ module.exports = {
'react-hooks/rules-of-hooks': 'off',
},
},
{
files: ['x-pack/plugins/lens/**/*.{js,mjs,ts,tsx}'],
rules: {
'react-hooks/exhaustive-deps': 'off',
},
},
{
files: ['x-pack/plugins/ml/**/*.{js,mjs,ts,tsx}'],
rules: {

View file

@ -163,7 +163,13 @@ export function App({
filterSubscription.unsubscribe();
timeSubscription.unsubscribe();
};
}, [data.query.filterManager, data.query.timefilter.timefilter]);
}, [
data.query.filterManager,
data.query.timefilter.timefilter,
core.uiSettings,
data.query,
history,
]);
useEffect(() => {
onAppLeave((actions) => {
@ -210,57 +216,61 @@ export function App({
]);
}, [core.application, core.chrome, core.http.basePath, state.persistedDoc]);
useEffect(() => {
if (docId && (!state.persistedDoc || state.persistedDoc.id !== docId)) {
setState((s) => ({ ...s, isLoading: true }));
docStorage
.load(docId)
.then((doc) => {
getAllIndexPatterns(
doc.state.datasourceMetaData.filterableIndexPatterns,
data.indexPatterns,
core.notifications
)
.then((indexPatterns) => {
// Don't overwrite any pinned filters
data.query.filterManager.setAppFilters(doc.state.filters);
setState((s) => ({
...s,
isLoading: false,
persistedDoc: doc,
lastKnownDoc: doc,
query: doc.state.query,
indexPatternsForTopNav: indexPatterns,
}));
})
.catch(() => {
setState((s) => ({ ...s, isLoading: false }));
useEffect(
() => {
if (docId && (!state.persistedDoc || state.persistedDoc.id !== docId)) {
setState((s) => ({ ...s, isLoading: true }));
docStorage
.load(docId)
.then((doc) => {
getAllIndexPatterns(
doc.state.datasourceMetaData.filterableIndexPatterns,
data.indexPatterns,
core.notifications
)
.then((indexPatterns) => {
// Don't overwrite any pinned filters
data.query.filterManager.setAppFilters(doc.state.filters);
setState((s) => ({
...s,
isLoading: false,
persistedDoc: doc,
lastKnownDoc: doc,
query: doc.state.query,
indexPatternsForTopNav: indexPatterns,
}));
})
.catch(() => {
setState((s) => ({ ...s, isLoading: false }));
redirectTo();
});
})
.catch(() => {
setState((s) => ({ ...s, isLoading: false }));
redirectTo();
});
})
.catch(() => {
setState((s) => ({ ...s, isLoading: false }));
core.notifications.toasts.addDanger(
i18n.translate('xpack.lens.app.docLoadingError', {
defaultMessage: 'Error loading saved document',
})
);
core.notifications.toasts.addDanger(
i18n.translate('xpack.lens.app.docLoadingError', {
defaultMessage: 'Error loading saved document',
})
);
redirectTo();
});
}
}, [
core.notifications,
data.indexPatterns,
data.query.filterManager,
docId,
// TODO: These dependencies are changing too often
// docStorage,
// redirectTo,
// state.persistedDoc,
]);
redirectTo();
});
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
core.notifications,
data.indexPatterns,
data.query.filterManager,
docId,
// TODO: These dependencies are changing too often
// docStorage,
// redirectTo,
// state.persistedDoc,
]
);
const runSave = async (
saveProps: Omit<OnSaveProps, 'onTitleDuplicate' | 'newDescription'> & {

View file

@ -160,6 +160,7 @@ export function DatatableComponent(props: DatatableRenderProps) {
formatters[column.id] = props.formatFactory(column.formatHint);
});
const { onClickValue } = props;
const handleFilterClick = useMemo(
() => (field: string, value: unknown, colIndex: number, negate: boolean = false) => {
const col = firstTable.columns[colIndex];
@ -180,9 +181,9 @@ export function DatatableComponent(props: DatatableRenderProps) {
],
timeFieldName,
};
props.onClickValue(desanitizeFilterContext(data));
onClickValue(desanitizeFilterContext(data));
},
[firstTable]
[firstTable, onClickValue]
);
const bucketColumns = firstTable.columns

View file

@ -17,13 +17,11 @@ export function debouncedComponent<TProps>(component: FunctionComponent<TProps>,
return (props: TProps) => {
const [cachedProps, setCachedProps] = useState(props);
const debouncePropsChange = debounce(setCachedProps, delay);
const delayRender = useMemo(() => debouncePropsChange, []);
const debouncePropsChange = useMemo(() => debounce(setCachedProps, delay), [setCachedProps]);
// cancel debounced prop change if component has been unmounted in the meantime
useEffect(() => () => debouncePropsChange.cancel(), []);
delayRender(props);
useEffect(() => () => debouncePropsChange.cancel(), [debouncePropsChange]);
debouncePropsChange(props);
return React.createElement(MemoizedComponent, cachedProps);
};

View file

@ -39,29 +39,29 @@ function LayerPanels(
} = props;
const setVisualizationState = useMemo(
() => (newState: unknown) => {
props.dispatch({
dispatch({
type: 'UPDATE_VISUALIZATION_STATE',
visualizationId: activeVisualization.id,
newState,
clearStagedPreview: false,
});
},
[props.dispatch, activeVisualization]
[dispatch, activeVisualization]
);
const updateDatasource = useMemo(
() => (datasourceId: string, newState: unknown) => {
props.dispatch({
dispatch({
type: 'UPDATE_DATASOURCE_STATE',
updater: () => newState,
datasourceId,
clearStagedPreview: false,
});
},
[props.dispatch]
[dispatch]
);
const updateAll = useMemo(
() => (datasourceId: string, newDatasourceState: unknown, newVisualizationState: unknown) => {
props.dispatch({
dispatch({
type: 'UPDATE_STATE',
subType: 'UPDATE_ALL_STATES',
updater: (prevState) => {
@ -83,7 +83,7 @@ function LayerPanels(
},
});
},
[props.dispatch]
[dispatch]
);
const layerIds = activeVisualization.getLayerIds(visualizationState);

View file

@ -27,16 +27,17 @@ interface DataPanelWrapperProps {
}
export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
const { dispatch, activeDatasource } = props;
const setDatasourceState: StateSetter<unknown> = useMemo(
() => (updater) => {
props.dispatch({
dispatch({
type: 'UPDATE_DATASOURCE_STATE',
updater,
datasourceId: props.activeDatasource!,
datasourceId: activeDatasource!,
clearStagedPreview: true,
});
},
[props.dispatch, props.activeDatasource]
[dispatch, activeDatasource]
);
const datasourceProps: DatasourceDataPanelProps = {

View file

@ -62,34 +62,38 @@ export function EditorFrame(props: EditorFrameProps) {
);
// Initialize current datasource and all active datasources
useEffect(() => {
// prevents executing dispatch on unmounted component
let isUnmounted = false;
if (!allLoaded) {
Object.entries(props.datasourceMap).forEach(([datasourceId, datasource]) => {
if (
state.datasourceStates[datasourceId] &&
state.datasourceStates[datasourceId].isLoading
) {
datasource
.initialize(state.datasourceStates[datasourceId].state || undefined)
.then((datasourceState) => {
if (!isUnmounted) {
dispatch({
type: 'UPDATE_DATASOURCE_STATE',
updater: datasourceState,
datasourceId,
});
}
})
.catch(onError);
}
});
}
return () => {
isUnmounted = true;
};
}, [allLoaded]);
useEffect(
() => {
// prevents executing dispatch on unmounted component
let isUnmounted = false;
if (!allLoaded) {
Object.entries(props.datasourceMap).forEach(([datasourceId, datasource]) => {
if (
state.datasourceStates[datasourceId] &&
state.datasourceStates[datasourceId].isLoading
) {
datasource
.initialize(state.datasourceStates[datasourceId].state || undefined)
.then((datasourceState) => {
if (!isUnmounted) {
dispatch({
type: 'UPDATE_DATASOURCE_STATE',
updater: datasourceState,
datasourceId,
});
}
})
.catch(onError);
}
});
}
return () => {
isUnmounted = true;
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[allLoaded, onError]
);
const datasourceLayers: Record<string, DatasourcePublicAPI> = {};
Object.keys(props.datasourceMap)
@ -156,83 +160,95 @@ export function EditorFrame(props: EditorFrameProps) {
},
};
useEffect(() => {
if (props.doc) {
dispatch({
type: 'VISUALIZATION_LOADED',
doc: props.doc,
});
} else {
dispatch({
type: 'RESET',
state: getInitialState(props),
});
}
}, [props.doc]);
useEffect(
() => {
if (props.doc) {
dispatch({
type: 'VISUALIZATION_LOADED',
doc: props.doc,
});
} else {
dispatch({
type: 'RESET',
state: getInitialState(props),
});
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[props.doc]
);
// Initialize visualization as soon as all datasources are ready
useEffect(() => {
if (allLoaded && state.visualization.state === null && activeVisualization) {
const initialVisualizationState = activeVisualization.initialize(framePublicAPI);
dispatch({
type: 'UPDATE_VISUALIZATION_STATE',
visualizationId: activeVisualization.id,
newState: initialVisualizationState,
});
}
}, [allLoaded, activeVisualization, state.visualization.state]);
useEffect(
() => {
if (allLoaded && state.visualization.state === null && activeVisualization) {
const initialVisualizationState = activeVisualization.initialize(framePublicAPI);
dispatch({
type: 'UPDATE_VISUALIZATION_STATE',
visualizationId: activeVisualization.id,
newState: initialVisualizationState,
});
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[allLoaded, activeVisualization, state.visualization.state]
);
// The frame needs to call onChange every time its internal state changes
useEffect(() => {
const activeDatasource =
state.activeDatasourceId && !state.datasourceStates[state.activeDatasourceId].isLoading
? props.datasourceMap[state.activeDatasourceId]
: undefined;
useEffect(
() => {
const activeDatasource =
state.activeDatasourceId && !state.datasourceStates[state.activeDatasourceId].isLoading
? props.datasourceMap[state.activeDatasourceId]
: undefined;
if (!activeDatasource || !activeVisualization) {
return;
}
if (!activeDatasource || !activeVisualization) {
return;
}
const indexPatterns: DatasourceMetaData['filterableIndexPatterns'] = [];
Object.entries(props.datasourceMap)
.filter(([id, datasource]) => {
const stateWrapper = state.datasourceStates[id];
return (
stateWrapper &&
!stateWrapper.isLoading &&
datasource.getLayers(stateWrapper.state).length > 0
);
})
.forEach(([id, datasource]) => {
indexPatterns.push(
...datasource.getMetaData(state.datasourceStates[id].state).filterableIndexPatterns
);
const indexPatterns: DatasourceMetaData['filterableIndexPatterns'] = [];
Object.entries(props.datasourceMap)
.filter(([id, datasource]) => {
const stateWrapper = state.datasourceStates[id];
return (
stateWrapper &&
!stateWrapper.isLoading &&
datasource.getLayers(stateWrapper.state).length > 0
);
})
.forEach(([id, datasource]) => {
indexPatterns.push(
...datasource.getMetaData(state.datasourceStates[id].state).filterableIndexPatterns
);
});
const doc = getSavedObjectFormat({
activeDatasources: Object.keys(state.datasourceStates).reduce(
(datasourceMap, datasourceId) => ({
...datasourceMap,
[datasourceId]: props.datasourceMap[datasourceId],
}),
{}
),
visualization: activeVisualization,
state,
framePublicAPI,
});
const doc = getSavedObjectFormat({
activeDatasources: Object.keys(state.datasourceStates).reduce(
(datasourceMap, datasourceId) => ({
...datasourceMap,
[datasourceId]: props.datasourceMap[datasourceId],
}),
{}
),
visualization: activeVisualization,
state,
framePublicAPI,
});
props.onChange({ filterableIndexPatterns: indexPatterns, doc });
}, [
activeVisualization,
state.datasourceStates,
state.visualization,
props.query,
props.dateRange,
props.filters,
props.savedQuery,
state.title,
]);
props.onChange({ filterableIndexPatterns: indexPatterns, doc });
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
activeVisualization,
state.datasourceStates,
state.visualization,
props.query,
props.dateRange,
props.filters,
props.savedQuery,
state.title,
]
);
return (
<RootDragDropProvider>

View file

@ -205,6 +205,7 @@ export function SuggestionPanel({
return { suggestions: newSuggestions, currentStateExpression: newStateExpression };
}, [
frame,
currentDatasourceStates,
currentVisualizationState,
currentVisualizationId,
@ -217,7 +218,7 @@ export function SuggestionPanel({
return (props: ReactExpressionRendererProps) => (
<ExpressionRendererComponent {...props} reload$={autoRefreshFetch$} />
);
}, [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$, ExpressionRendererComponent]);
}, [plugins.data.query.timefilter.timefilter]);
const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState<number>(-1);
@ -228,6 +229,7 @@ export function SuggestionPanel({
if (!stagedPreview && lastSelectedSuggestion !== -1) {
setLastSelectedSuggestion(-1);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stagedPreview]);
if (!activeDatasourceId) {

View file

@ -188,6 +188,7 @@ export function ChartSwitch(props: Props) {
...visualizationType,
selection: getSelection(visualizationType.visualizationId, visualizationType.id),
})),
// eslint-disable-next-line react-hooks/exhaustive-deps
[
flyoutOpen,
props.visualizationMap,

View file

@ -85,29 +85,33 @@ export function InnerWorkspacePanel({
const dragDropContext = useContext(DragContext);
const suggestionForDraggedField = useMemo(() => {
if (!dragDropContext.dragging || !activeDatasourceId) {
return;
}
const suggestionForDraggedField = useMemo(
() => {
if (!dragDropContext.dragging || !activeDatasourceId) {
return;
}
const hasData = Object.values(framePublicAPI.datasourceLayers).some(
(datasource) => datasource.getTableSpec().length > 0
);
const hasData = Object.values(framePublicAPI.datasourceLayers).some(
(datasource) => datasource.getTableSpec().length > 0
);
const suggestions = getSuggestions({
datasourceMap: { [activeDatasourceId]: datasourceMap[activeDatasourceId] },
datasourceStates,
visualizationMap:
hasData && activeVisualizationId
? { [activeVisualizationId]: visualizationMap[activeVisualizationId] }
: visualizationMap,
activeVisualizationId,
visualizationState,
field: dragDropContext.dragging,
});
const suggestions = getSuggestions({
datasourceMap: { [activeDatasourceId]: datasourceMap[activeDatasourceId] },
datasourceStates,
visualizationMap:
hasData && activeVisualizationId
? { [activeVisualizationId]: visualizationMap[activeVisualizationId] }
: visualizationMap,
activeVisualizationId,
visualizationState,
field: dragDropContext.dragging,
});
return suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0];
}, [dragDropContext.dragging]);
return suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0];
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[dragDropContext.dragging]
);
const [localState, setLocalState] = useState({
expressionBuildError: undefined as string | undefined,
@ -117,28 +121,32 @@ export function InnerWorkspacePanel({
const activeVisualization = activeVisualizationId
? visualizationMap[activeVisualizationId]
: null;
const expression = useMemo(() => {
try {
return buildExpression({
visualization: activeVisualization,
visualizationState,
datasourceMap,
datasourceStates,
framePublicAPI,
});
} catch (e) {
// Most likely an error in the expression provided by a datasource or visualization
setLocalState((s) => ({ ...s, expressionBuildError: e.toString() }));
}
}, [
activeVisualization,
visualizationState,
datasourceMap,
datasourceStates,
framePublicAPI.dateRange,
framePublicAPI.query,
framePublicAPI.filters,
]);
const expression = useMemo(
() => {
try {
return buildExpression({
visualization: activeVisualization,
visualizationState,
datasourceMap,
datasourceStates,
framePublicAPI,
});
} catch (e) {
// Most likely an error in the expression provided by a datasource or visualization
setLocalState((s) => ({ ...s, expressionBuildError: e.toString() }));
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
activeVisualization,
visualizationState,
datasourceMap,
datasourceStates,
framePublicAPI.dateRange,
framePublicAPI.query,
framePublicAPI.filters,
]
);
const onEvent = useCallback(
(event: ExpressionRendererEvent) => {
@ -162,7 +170,7 @@ export function InnerWorkspacePanel({
const autoRefreshFetch$ = useMemo(
() => plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(),
[plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$]
[plugins.data.query.timefilter.timefilter]
);
useEffect(() => {
@ -173,7 +181,7 @@ export function InnerWorkspacePanel({
expressionBuildError: undefined,
}));
}
}, [expression]);
}, [expression, localState.expressionBuildError]);
function onDrop() {
if (suggestionForDraggedField) {

View file

@ -409,7 +409,16 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
filters,
chartsThemeService: charts.theme,
}),
[core, data, currentIndexPattern, dateRange, query, filters, localState.nameFilter]
[
core,
data,
currentIndexPattern,
dateRange,
query,
filters,
localState.nameFilter,
charts.theme,
]
);
return (

View file

@ -139,10 +139,10 @@ export function FieldSelect({
}, [
incompatibleSelectedOperationType,
selectedColumnOperationType,
selectedColumnSourceField,
operationFieldSupportMatrix,
currentIndexPattern,
fieldMap,
operationByField,
existingFields,
]);
return (

View file

@ -16,28 +16,32 @@ export function Loader(props: { load: () => Promise<unknown>; loadDeps: unknown[
const prevRequest = useRef<Promise<unknown> | undefined>(undefined);
const nextRequest = useRef<(() => void) | undefined>(undefined);
useEffect(function performLoad() {
if (prevRequest.current) {
nextRequest.current = performLoad;
return;
}
useEffect(
function performLoad() {
if (prevRequest.current) {
nextRequest.current = performLoad;
return;
}
setIsProcessing(true);
prevRequest.current = props
.load()
.catch(() => {})
.then(() => {
const reload = nextRequest.current;
prevRequest.current = undefined;
nextRequest.current = undefined;
setIsProcessing(true);
prevRequest.current = props
.load()
.catch(() => {})
.then(() => {
const reload = nextRequest.current;
prevRequest.current = undefined;
nextRequest.current = undefined;
if (reload) {
reload();
} else {
setIsProcessing(false);
}
});
}, props.loadDeps);
if (reload) {
reload();
} else {
setIsProcessing(false);
}
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
props.loadDeps
);
if (!isProcessing) {
return null;

View file

@ -379,7 +379,7 @@ const ColorPicker = ({
}
setState(updateLayer(state, { ...layer, yConfig: newYConfigs }, index));
}, 256),
[state, layer, accessor, index]
[state, setState, layer, accessor, index]
);
const colorPicker = (

View file

@ -180,7 +180,7 @@ export function XYChartReportable(props: XYChartRenderProps) {
// reporting from printing a blank chart placeholder.
useEffect(() => {
setState({ isReady: true });
}, []);
}, [setState]);
return (
<VisualizationContainer className="lnsXyExpression__container" isReady={state.isReady}>