diff --git a/package.json b/package.json index e19c1ba6af..dc98f8a5fe 100644 --- a/package.json +++ b/package.json @@ -180,12 +180,14 @@ "prettier": "^1.19.1", "pretty-quick": "^2.0.0", "puppeteer": "^1.20.0", - "react": "16.10.x", + "react": "^16.13.0", "react-docgen-typescript-loader": "^3.6.0", "react-docgen-typescript-webpack-plugin": "^1.1.0", - "react-dom": "^16.12.0", - "react-is": "^16.12.0", + "react-dom": "^16.13.0", + "react-is": "^16.13.0", "redux-devtools-extension": "^2.13.8", + "redux-immutable-state-invariant": "^2.1.0", + "redux-logger": "^3.0.6", "sass-graph": "^3.0.4", "sass-loader": "^7.1.0", "seedrandom": "^3.0.5", diff --git a/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts b/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts index 31c1b93c2e..67e18aafb5 100644 --- a/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts +++ b/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts @@ -23,7 +23,7 @@ import { MockGlobalSpec, MockSeriesSpec } from '../../../../mocks/specs'; import { SettingsSpec, XYChartElementEvent, PartitionElementEvent } from '../../../../specs'; import { updateParentDimensions } from '../../../../state/actions/chart_settings'; import { onMouseDown, onMouseUp, onPointerMove } from '../../../../state/actions/mouse'; -import { upsertSpec, specParsed, specParsing } from '../../../../state/actions/specs'; +import { upsertSpec, specParsed } from '../../../../state/actions/specs'; import { chartStoreReducer, GlobalChartState } from '../../../../state/chart_state'; import { PartitionSpec } from '../../specs'; import { partitionGeometries } from './geometries'; @@ -35,7 +35,6 @@ describe('Picked shapes selector', () => { return createStore(storeReducer); } function addSeries(store: Store, spec: PartitionSpec, settings?: Partial) { - store.dispatch(specParsing()); store.dispatch(upsertSpec(MockGlobalSpec.settings(settings))); store.dispatch(upsertSpec(spec)); store.dispatch(specParsed()); diff --git a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts index c7d81e0199..4a079bf7cd 100644 --- a/src/chart_types/xy_chart/state/chart_state.interactions.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.interactions.test.ts @@ -17,17 +17,16 @@ * under the License. */ -import { createStore, Store } from 'redux'; +import { Store } from 'redux'; import { ChartTypes } from '../..'; +import { MockStore } from '../../../mocks/store'; import { ScaleType } from '../../../scales/constants'; import { SettingsSpec, XYBrushArea } from '../../../specs'; import { SpecTypes, DEFAULT_SETTINGS_SPEC, TooltipType, BrushAxis } from '../../../specs/constants'; -import { updateParentDimensions } from '../../../state/actions/chart_settings'; import { onExternalPointerEvent } from '../../../state/actions/events'; import { onPointerMove, onMouseDown, onMouseUp } from '../../../state/actions/mouse'; -import { upsertSpec, specParsed } from '../../../state/actions/specs'; -import { chartStoreReducer, GlobalChartState } from '../../../state/chart_state'; +import { GlobalChartState } from '../../../state/chart_state'; import { getSettingsSpecSelector } from '../../../state/selectors/get_settings_specs'; import { Position } from '../../../utils/commons'; import { BarSeriesSpec, BasicSeriesSpec, AxisSpec, SeriesTypes } from '../utils/specs'; @@ -99,14 +98,8 @@ const settingSpec: SettingsSpec = { }; function initStore(spec: BasicSeriesSpec) { - const storeReducer = chartStoreReducer('chartId'); - const store = createStore(storeReducer); - - store.dispatch(upsertSpec(settingSpec)); - store.dispatch(upsertSpec(spec)); - store.dispatch(specParsed()); - store.dispatch(updateParentDimensions({ width: 100, height: 100, top: chartTop, left: chartLeft })); - + const store = MockStore.default({ width: 100, height: 100, top: chartTop, left: chartLeft }, 'chartId'); + MockStore.addSpecs([settingSpec, spec], store); return store; } @@ -214,8 +207,8 @@ describe('Chart state pointer interactions', () => { ...settingSpec, onElementOut: onOutListener, }; - store.dispatch(upsertSpec(settingsWithListeners)); - store.dispatch(specParsed()); + + MockStore.addSpecs([ordinalBarSeries, settingsWithListeners], store); // registering the out/over listener caller store.subscribe(() => { onElementOutCaller(store.getState()); @@ -238,8 +231,7 @@ describe('Chart state pointer interactions', () => { type: TooltipType.None, }, }; - store.dispatch(upsertSpec(updatedSettings)); - store.dispatch(specParsed()); + MockStore.addSpecs([ordinalBarSeries, updatedSettings], store); store.dispatch(onPointerMove({ x: 10, y: 10 + 70 }, 0)); const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); // no tooltip values exist if we have a TooltipType === None @@ -253,8 +245,7 @@ describe('Chart state pointer interactions', () => { type: TooltipType.Follow, }, }; - store.dispatch(upsertSpec(updatedSettings)); - store.dispatch(specParsed()); + MockStore.addSpecs([ordinalBarSeries, updatedSettings], store); store.dispatch(onPointerMove({ x: 10, y: 10 + 70 }, 1)); const { geometriesIndex } = computeSeriesGeometriesSelector(store.getState()); expect(geometriesIndex.size).toBe(2); @@ -293,8 +284,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { onElementOut: onOutListener, onPointerUpdate: onPointerUpdateListener, }; - store.dispatch(upsertSpec(settingsWithListeners)); - store.dispatch(specParsed()); + MockStore.addSpecs([spec, settingsWithListeners], store); const onElementOutCaller = createOnElementOutCaller(); const onElementOverCaller = createOnElementOverCaller(); const onPointerMoveCaller = createOnPointerMoveCaller(); @@ -737,8 +727,11 @@ function mouseOverTestSuite(scaleType: ScaleType) { }); }); describe('can format tooltip values on rotated chart', () => { + let leftAxis: AxisSpec; + let bottomAxis: AxisSpec; + let currentSettingSpec: SettingsSpec; beforeEach(() => { - const leftAxis: AxisSpec = { + leftAxis = { chartType: ChartTypes.XYAxis, specType: SpecTypes.Axis, hide: true, @@ -751,7 +744,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { tickPadding: 0, tickSize: 0, }; - const bottomAxis: AxisSpec = { + bottomAxis = { chartType: ChartTypes.XYAxis, specType: SpecTypes.Axis, hide: true, @@ -764,11 +757,11 @@ function mouseOverTestSuite(scaleType: ScaleType) { tickPadding: 0, tickSize: 0, }; - store.dispatch(upsertSpec(leftAxis)); - store.dispatch(upsertSpec(bottomAxis)); - store.dispatch(specParsed()); + currentSettingSpec = getSettingsSpecSelector(store.getState()); }); + test('chart 0 rotation', () => { + MockStore.addSpecs([spec, leftAxis, bottomAxis, currentSettingSpec], store); store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 89 }, 0)); const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); expect(tooltipInfo.tooltip.header?.value).toBe('bottom 0'); @@ -776,13 +769,12 @@ function mouseOverTestSuite(scaleType: ScaleType) { }); test('chart 90 deg rotated', () => { - const settings = getSettingsSpecSelector(store.getState()); const updatedSettings: SettingsSpec = { - ...settings, + ...currentSettingSpec, rotation: 90, }; - store.dispatch(upsertSpec(updatedSettings)); - store.dispatch(specParsed()); + MockStore.addSpecs([spec, leftAxis, bottomAxis, updatedSettings], store); + store.dispatch(onPointerMove({ x: chartLeft + 0, y: chartTop + 89 }, 0)); const tooltipInfo = getTooltipInfoAndGeometriesSelector(store.getState()); expect(tooltipInfo.tooltip.header?.value).toBe('left 1'); @@ -810,19 +802,15 @@ function mouseOverTestSuite(scaleType: ScaleType) { }, onBrushEnd: brushEndListener, }; - store.dispatch(upsertSpec(updatedSettings)); - store.dispatch( - upsertSpec({ - ...spec, - data: [ - [0, 1], - [1, 1], - [2, 2], - [3, 3], - ], - } as BarSeriesSpec), - ); - store.dispatch(specParsed()); + MockStore.addSpecs([{ + ...spec, + data: [ + [0, 1], + [1, 1], + [2, 2], + [3, 3], + ], + } as BarSeriesSpec, updatedSettings], store); const start1 = { x: 0, y: 0 }; const end1 = { x: 75, y: 0 }; @@ -894,8 +882,7 @@ function mouseOverTestSuite(scaleType: ScaleType) { }, onBrushEnd: brushEndListener, }; - store.dispatch(upsertSpec(updatedSettings)); - store.dispatch(specParsed()); + MockStore.addSpecs([spec, updatedSettings], store); const start1 = { x: 0, y: 25 }; const end1 = { x: 0, y: 75 }; @@ -967,19 +954,15 @@ function mouseOverTestSuite(scaleType: ScaleType) { }, onBrushEnd: brushEndListener, }; - store.dispatch(upsertSpec(updatedSettings)); - store.dispatch( - upsertSpec({ - ...spec, - data: [ - [0, 1], - [1, 1], - [2, 2], - [3, 3], - ], - } as BarSeriesSpec), - ); - store.dispatch(specParsed()); + MockStore.addSpecs([{ + ...spec, + data: [ + [0, 1], + [1, 1], + [2, 2], + [3, 3], + ], + } as BarSeriesSpec, updatedSettings], store); const start1 = { x: 0, y: 0 }; const end1 = { x: 0, y: 75 }; @@ -1041,19 +1024,15 @@ function mouseOverTestSuite(scaleType: ScaleType) { }, onBrushEnd: brushEndListener, }; - store.dispatch(upsertSpec(updatedSettings)); - store.dispatch( - upsertSpec({ - ...spec, - data: [ - [0, 1], - [1, 1], - [2, 2], - [3, 3], - ], - } as BarSeriesSpec), - ); - store.dispatch(specParsed()); + MockStore.addSpecs([{ + ...spec, + data: [ + [0, 1], + [1, 1], + [2, 2], + [3, 3], + ], + } as BarSeriesSpec, updatedSettings], store); const start1 = { x: 0, y: 0 }; const end1 = { x: 75, y: 75 }; diff --git a/src/chart_types/xy_chart/state/chart_state.specs.test.ts b/src/chart_types/xy_chart/state/chart_state.specs.test.ts index f84c479e8c..24699fd54e 100644 --- a/src/chart_types/xy_chart/state/chart_state.specs.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.specs.test.ts @@ -20,7 +20,7 @@ import { createStore, Store } from 'redux'; import { MockSeriesSpec } from '../../../mocks/specs'; -import { upsertSpec, specParsed, specParsing } from '../../../state/actions/specs'; +import { MockStore } from '../../../mocks/store'; import { GlobalChartState, chartStoreReducer } from '../../../state/chart_state'; import { getLegendItemsSelector } from '../../../state/selectors/get_legend_items'; @@ -34,67 +34,65 @@ describe('XYChart - specs ordering', () => { beforeEach(() => { const storeReducer = chartStoreReducer('chartId'); store = createStore(storeReducer); - store.dispatch(specParsing()); }); it('the legend respect the insert [A, B, C] order', () => { - store.dispatch(specParsing()); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); - store.dispatch(specParsed()); + MockStore.addSpecs([ + MockSeriesSpec.bar({ id: 'A', data }), + MockSeriesSpec.bar({ id: 'B', data }), + MockSeriesSpec.bar({ id: 'C', data }), + ], store); const legendItems = getLegendItemsSelector(store.getState()); const names = [...legendItems.values()].map((item) => item.label); expect(names).toEqual(['A', 'B', 'C']); }); it('the legend respect the insert order [B, A, C]', () => { - store.dispatch(specParsing()); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); - store.dispatch(specParsed()); + MockStore.addSpecs([ + MockSeriesSpec.bar({ id: 'B', data }), + MockSeriesSpec.bar({ id: 'A', data }), + MockSeriesSpec.bar({ id: 'C', data }), + ], store); const legendItems = getLegendItemsSelector(store.getState()); const names = [...legendItems.values()].map((item) => item.label); expect(names).toEqual(['B', 'A', 'C']); }); it('the legend respect the order when changing properties of existing specs', () => { - store.dispatch(specParsing()); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); - store.dispatch(specParsed()); + MockStore.addSpecs([ + MockSeriesSpec.bar({ id: 'A', data }), + MockSeriesSpec.bar({ id: 'B', data }), + MockSeriesSpec.bar({ id: 'C', data }), + ], store); let legendItems = getLegendItemsSelector(store.getState()); let names = [...legendItems.values()].map((item) => item.label); expect(names).toEqual(['A', 'B', 'C']); - store.dispatch(specParsing()); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', name: 'B updated', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); - store.dispatch(specParsed()); + MockStore.addSpecs([ + MockSeriesSpec.bar({ id: 'A', data }), + MockSeriesSpec.bar({ id: 'B', name: 'B updated', data }), + MockSeriesSpec.bar({ id: 'C', data }), + ], store); legendItems = getLegendItemsSelector(store.getState()); names = [...legendItems.values()].map((item) => item.label); expect(names).toEqual(['A', 'B updated', 'C']); }); it('the legend respect the order when changing the order of the specs', () => { - store.dispatch(specParsing()); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); - store.dispatch(specParsed()); - + MockStore.addSpecs([ + MockSeriesSpec.bar({ id: 'A', data }), + MockSeriesSpec.bar({ id: 'B', data }), + MockSeriesSpec.bar({ id: 'C', data }), + ], store); let legendItems = getLegendItemsSelector(store.getState()); let names = [...legendItems.values()].map((item) => item.label); expect(names).toEqual(['A', 'B', 'C']); - store.dispatch(specParsing()); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'B', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'A', data }))); - store.dispatch(upsertSpec(MockSeriesSpec.bar({ id: 'C', data }))); - store.dispatch(specParsed()); + MockStore.addSpecs([ + MockSeriesSpec.bar({ id: 'B', data }), + MockSeriesSpec.bar({ id: 'A', data }), + MockSeriesSpec.bar({ id: 'C', data }), + ], store); legendItems = getLegendItemsSelector(store.getState()); names = [...legendItems.values()].map((item) => item.label); diff --git a/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts b/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts index fc3f056016..1322a91f31 100644 --- a/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts +++ b/src/chart_types/xy_chart/state/chart_state.tooltip.test.ts @@ -64,6 +64,16 @@ describe('XYChart - State tooltips', () => { }), ), ); + store.dispatch( + upsertSpec( + MockSeriesSpec.bar({ + data: [ + { x: 1, y: 10 }, + { x: 2, y: 5 }, + ], + }), + ), + ); store.dispatch(specParsed()); const state = store.getState(); const tooltipValues = getTooltipInfoAndGeometriesSelector(state); diff --git a/src/components/chart.tsx b/src/components/chart.tsx index 22548e06e3..195bdb80a9 100644 --- a/src/components/chart.tsx +++ b/src/components/chart.tsx @@ -20,7 +20,7 @@ import classNames from 'classnames'; import React, { createRef } from 'react'; import { Provider } from 'react-redux'; -import { createStore, Store, Unsubscribe } from 'redux'; +import { createStore, Store, Unsubscribe, StoreEnhancer, applyMiddleware, Middleware } from 'redux'; import uuid from 'uuid'; import { isHorizontalAxis } from '../chart_types/xy_chart/utils/axis_type_utils'; @@ -53,6 +53,29 @@ interface ChartState { legendPosition: Position; } +const getMiddlware = (id: string): StoreEnhancer => { + const middlware: Middleware[] = []; + + if (process.env.DEBUG_REDUX === 'true') { + /* eslint-disable @typescript-eslint/no-var-requires, import/no-extraneous-dependencies */ + middlware.push(require('redux-immutable-state-invariant').default()); + // https://github.com/LogRocket/redux-logger#options-description + middlware.push(require('redux-logger').createLogger({})); + /* eslint-enable */ + } + + if (typeof window !== 'undefined' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + return (window as any) + .__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ + trace: true, + name: `@elastic/charts (id: ${id})`, + })(applyMiddleware(...middlware)); + } + + return applyMiddleware(...middlware); +}; + export class Chart extends React.Component { static defaultProps: ChartProps = { renderer: 'canvas', @@ -70,11 +93,8 @@ export class Chart extends React.Component { const id = props.id ?? uuid.v4(); const storeReducer = chartStoreReducer(id); - const enhancers = typeof window !== 'undefined' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true, name: `@elastic/charts (id: ${id})` })() - : undefined; - - this.chartStore = createStore(storeReducer, enhancers); + const enhancer = getMiddlware(id); + this.chartStore = createStore(storeReducer, enhancer); this.state = { legendPosition: Position.Right, }; diff --git a/src/mocks/store/store.ts b/src/mocks/store/store.ts index 52222f53d9..f8fdb1407f 100644 --- a/src/mocks/store/store.ts +++ b/src/mocks/store/store.ts @@ -21,7 +21,7 @@ import { createStore, Store } from 'redux'; import { Spec } from '../../specs'; import { updateParentDimensions } from '../../state/actions/chart_settings'; -import { specParsing, upsertSpec, specParsed } from '../../state/actions/specs'; +import { upsertSpec, specParsed } from '../../state/actions/specs'; import { chartStoreReducer, GlobalChartState } from '../../state/chart_state'; /** @internal */ @@ -37,7 +37,6 @@ export class MockStore { } static addSpecs(specs: Spec | Array, store: Store) { - store.dispatch(specParsing()); if (Array.isArray(specs)) { const actions = specs.map(upsertSpec); actions.forEach(store.dispatch); diff --git a/src/specs/specs_parser.test.tsx b/src/specs/specs_parser.test.tsx index ad04d10255..23b447ab5f 100644 --- a/src/specs/specs_parser.test.tsx +++ b/src/specs/specs_parser.test.tsx @@ -127,6 +127,49 @@ describe('Specs parser', () => { const state = chartStore.getState(); expect((state.specs.bars as BarSeriesSpec).xAccessor).toBe(1); }); + test('should remove a spec when replaced with a new', () => { + const storeReducer = chartStoreReducer('chart_id'); + const chartStore = createStore(storeReducer); + + expect(chartStore.getState().specsInitialized).toBe(false); + const component = ( + + + + + + ); + const wrapper = mount(component); + + expect(chartStore.getState().specs.one).toBeDefined(); + + wrapper.setProps({ + children: ( + + + + ), + }); + const state = chartStore.getState(); + expect(state.specs.one).toBeUndefined(); + expect(state.specs.two).toBeDefined(); + }); test('set initialization to false on unmount', () => { const storeReducer = chartStoreReducer('chart_id'); const chartStore = createStore(storeReducer); diff --git a/src/specs/specs_parser.tsx b/src/specs/specs_parser.tsx index b91fd8dead..3b546734bd 100644 --- a/src/specs/specs_parser.tsx +++ b/src/specs/specs_parser.tsx @@ -21,11 +21,10 @@ import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; -import { specParsing, specParsed, specUnmounted } from '../state/actions/specs'; +import { specParsed, specUnmounted } from '../state/actions/specs'; const SpecsParserComponent: React.FunctionComponent = (props) => { const injected = props as DispatchProps; - injected.specParsing(); useEffect(() => { injected.specParsed(); }); @@ -39,7 +38,6 @@ const SpecsParserComponent: React.FunctionComponent = (props) => { }; interface DispatchProps { - specParsing: () => void; specParsed: () => void; specUnmounted: () => void; } @@ -47,7 +45,6 @@ interface DispatchProps { const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => bindActionCreators( { - specParsing, specParsed, specUnmounted, }, diff --git a/src/state/actions/specs.ts b/src/state/actions/specs.ts index c77b4a6585..5ee1786f7e 100644 --- a/src/state/actions/specs.ts +++ b/src/state/actions/specs.ts @@ -28,16 +28,9 @@ export const REMOVE_SPEC = 'REMOVE_SPEC'; /** @internal */ export const SPEC_PARSED = 'SPEC_PARSED'; -/** @internal */ -export const SPEC_PARSING = 'SPEC_PARSING'; - /** @internal */ export const SPEC_UNMOUNTED = 'SPEC_UNMOUNTED'; -interface SpecParsingAction { - type: typeof SPEC_PARSING; -} - interface SpecParsedAction { type: typeof SPEC_PARSED; } @@ -71,11 +64,6 @@ export function specParsed(): SpecParsedAction { return { type: SPEC_PARSED }; } -/** @internal */ -export function specParsing(): SpecParsingAction { - return { type: SPEC_PARSING }; -} - /** @internal */ export function specUnmounted(): SpecUnmountedAction { return { type: SPEC_UNMOUNTED }; @@ -83,7 +71,6 @@ export function specUnmounted(): SpecUnmountedAction { /** @internal */ export type SpecActions = - | SpecParsingAction | SpecParsedAction | SpecUnmountedAction | UpsertSpecAction diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts index 7b4469395d..1212400a12 100644 --- a/src/state/chart_state.ts +++ b/src/state/chart_state.ts @@ -37,7 +37,7 @@ import { CHART_RENDERED } from './actions/chart'; import { UPDATE_PARENT_DIMENSION } from './actions/chart_settings'; import { SET_PERSISTED_COLOR, SET_TEMPORARY_COLOR, CLEAR_TEMPORARY_COLORS } from './actions/colors'; import { EXTERNAL_POINTER_EVENT } from './actions/events'; -import { SPEC_PARSED, SPEC_UNMOUNTED, UPSERT_SPEC, REMOVE_SPEC, SPEC_PARSING } from './actions/specs'; +import { SPEC_PARSED, SPEC_UNMOUNTED, UPSERT_SPEC, REMOVE_SPEC } from './actions/specs'; import { interactionsReducer } from './reducers/interactions'; import { getInternalIsInitializedSelector, InitStatus } from './selectors/get_internal_is_intialized'; import { getLegendItemsSelector } from './selectors/get_legend_items'; @@ -178,6 +178,7 @@ export interface GlobalChartState { * true when all all the specs are parsed ad stored into the specs object */ specsInitialized: boolean; + specParsing: boolean; /** * true if the chart is rendered on dom */ @@ -220,6 +221,7 @@ export interface GlobalChartState { export const getInitialState = (chartId: string): GlobalChartState => ({ chartId, specsInitialized: false, + specParsing: false, chartRendered: false, chartRenderedCount: 0, specs: { @@ -266,15 +268,6 @@ export const chartStoreReducer = (chartId: string) => { const initialState = getInitialState(chartId); return (state = initialState, action: StateActions): GlobalChartState => { switch (action.type) { - case SPEC_PARSING: - return { - ...state, - specsInitialized: false, - chartRendered: false, - specs: { - [DEFAULT_SETTINGS_SPEC.id]: DEFAULT_SETTINGS_SPEC, - }, - }; case SPEC_PARSED: const chartType = findMainChartType(state.specs); @@ -283,6 +276,7 @@ export const chartStoreReducer = (chartId: string) => { return { ...state, specsInitialized: true, + specParsing: false, chartType, internalChartState, }; @@ -290,6 +284,7 @@ export const chartStoreReducer = (chartId: string) => { return { ...state, specsInitialized: true, + specParsing: false, chartType, }; @@ -300,8 +295,22 @@ export const chartStoreReducer = (chartId: string) => { chartRendered: false, }; case UPSERT_SPEC: + if (!state.specParsing) { + return { + ...state, + specsInitialized: false, + chartRendered: false, + specParsing: true, + specs: { + [DEFAULT_SETTINGS_SPEC.id]: DEFAULT_SETTINGS_SPEC, + [action.spec.id]: action.spec, + }, + }; + } return { ...state, + specsInitialized: false, + chartRendered: false, specs: { ...state.specs, [action.spec.id]: action.spec, diff --git a/src/state/spec_factory.ts b/src/state/spec_factory.ts index e851e77d94..6a451b750d 100644 --- a/src/state/spec_factory.ts +++ b/src/state/spec_factory.ts @@ -42,6 +42,7 @@ function usePrevious(value: string) { export function specComponentFactory( defaultProps: Pick, ) { + /* eslint-disable no-shadow, react-hooks/exhaustive-deps, unicorn/consistent-function-scoping */ const SpecInstance = (props: U & DispatchProps) => { const prevId = usePrevious(props.id); const { removeSpec, upsertSpec, ...SpecInstance } = props; @@ -55,10 +56,11 @@ export function specComponentFactory( () => () => { removeSpec(props.id); }, - [], // eslint-disable-line react-hooks/exhaustive-deps + [], ); return null; }; + /* eslint-enable */ SpecInstance.defaultProps = defaultProps; return SpecInstance; } diff --git a/yarn.lock b/yarn.lock index aa7ef073ed..a8cf59eb0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8642,6 +8642,11 @@ dedent@0.7.0, dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ= + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -11991,7 +11996,7 @@ into-stream@^5.0.0: from2 "^2.3.0" p-is-promise "^3.0.0" -invariant@2.2.4, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: +invariant@2.2.4, invariant@^2.1.0, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -17200,15 +17205,15 @@ react-docgen@^4.1.0: node-dir "^0.1.10" recast "^0.17.3" -react-dom@^16.12.0: - version "16.12.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11" - integrity sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw== +react-dom@^16.13.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" + integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.18.0" + scheduler "^0.19.1" react-dom@^16.8.3: version "16.10.2" @@ -17315,6 +17320,11 @@ react-is@^16.12.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== +react-is@^16.13.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.3, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.10.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.2.tgz#984120fd4d16800e9a738208ab1fba422d23b5ab" @@ -17461,15 +17471,6 @@ react-virtualized@^9.21.2: prop-types "^15.6.0" react-lifecycles-compat "^3.0.4" -react@16.10.x, react@^16.8.3: - version "16.10.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.10.2.tgz#a5ede5cdd5c536f745173c8da47bda64797a4cf0" - integrity sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - react@^0.14.0: version "0.14.9" resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" @@ -17478,6 +17479,24 @@ react@^0.14.0: envify "^3.0.0" fbjs "^0.6.1" +react@^16.13.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +react@^16.8.3: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.10.2.tgz#a5ede5cdd5c536f745173c8da47bda64797a4cf0" + integrity sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + reactcss@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" @@ -17790,6 +17809,21 @@ redux-devtools-extension@^2.13.8: resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg== +redux-immutable-state-invariant@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1" + integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg== + dependencies: + invariant "^2.1.0" + json-stringify-safe "^5.0.1" + +redux-logger@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8= + dependencies: + deep-diff "^0.3.5" + redux@^4.0.0, redux@^4.0.1, redux@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" @@ -18545,10 +18579,10 @@ scheduler@^0.16.2: loose-envify "^1.1.0" object-assign "^4.1.1" -scheduler@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" - integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ== +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1"