diff --git a/public/app/plugins/datasource/graphite/state/context.tsx b/public/app/plugins/datasource/graphite/state/context.tsx index 46225d9fa0c..2a4f303e62b 100644 --- a/public/app/plugins/datasource/graphite/state/context.tsx +++ b/public/app/plugins/datasource/graphite/state/context.tsx @@ -31,6 +31,7 @@ export const GraphiteQueryEditorContext = ({ children, }: PropsWithChildren) => { const [state, setState] = useState(); + const [needsRefresh, setNeedsRefresh] = useState(false); const dispatch = useMemo(() => { return createStore((state) => { @@ -68,6 +69,19 @@ export const GraphiteQueryEditorContext = ({ [dispatch, query] ); + useEffect( + () => { + if (needsRefresh && state) { + setNeedsRefresh(false); + onChange({ ...query, target: state.target.target }); + onRunQuery(); + } + }, + // adding state to dependencies causes infinite loops + // eslint-disable-next-line react-hooks/exhaustive-deps + [needsRefresh, onChange, onRunQuery, query] + ); + if (!state) { dispatch( actions.init({ @@ -78,9 +92,10 @@ export const GraphiteQueryEditorContext = ({ // list of queries is passed only when the editor is in Dashboards. This is to allow interpolation // of sub-queries which are stored in "targetFull" property used by alerting in the backend. queries: queries || [], - refresh: (target: string) => { - onChange({ ...query, target: target }); - onRunQuery(); + refresh: () => { + // do not run onChange/onRunQuery straight away to ensure the internal state gets updated first + // to avoid race conditions (onChange could update props before the reducer action finishes) + setNeedsRefresh(true); }, }) ); diff --git a/public/app/plugins/datasource/graphite/state/helpers.ts b/public/app/plugins/datasource/graphite/state/helpers.ts index 49474f6c56e..edca103c492 100644 --- a/public/app/plugins/datasource/graphite/state/helpers.ts +++ b/public/app/plugins/datasource/graphite/state/helpers.ts @@ -161,7 +161,7 @@ export function handleTargetChanged(state: GraphiteQueryEditorState): void { ); if (state.queryModel.target.target !== oldTarget && !state.paused) { - state.refresh(state.target.target); + state.refresh(); } } diff --git a/public/app/plugins/datasource/graphite/state/store.ts b/public/app/plugins/datasource/graphite/state/store.ts index 47cf379913b..4aeb9ea3e9c 100644 --- a/public/app/plugins/datasource/graphite/state/store.ts +++ b/public/app/plugins/datasource/graphite/state/store.ts @@ -25,7 +25,7 @@ export type GraphiteQueryEditorState = { // external dependencies datasource: GraphiteDatasource; target: GraphiteTarget; - refresh: (target: string) => void; + refresh: () => void; queries?: DataQuery[]; templateSrv: TemplateSrv; range?: TimeRange; @@ -134,7 +134,7 @@ const reducer = async (action: Action, state: GraphiteQueryEditorState): Promise } if (actions.unpause.match(action)) { state.paused = false; - state.refresh(state.target.target); + state.refresh(); } if (actions.addFunction.match(action)) { const newFunc = state.datasource.createFuncInstance(action.payload.name, { @@ -175,7 +175,7 @@ const reducer = async (action: Action, state: GraphiteQueryEditorState): Promise handleTargetChanged(state); } if (actions.runQuery.match(action)) { - state.refresh(state.target.target); + state.refresh(); } if (actions.toggleEditorMode.match(action)) { state.target.textEditor = !state.target.textEditor; diff --git a/public/app/plugins/datasource/graphite/types.ts b/public/app/plugins/datasource/graphite/types.ts index bcec810aefa..64387b1b56e 100644 --- a/public/app/plugins/datasource/graphite/types.ts +++ b/public/app/plugins/datasource/graphite/types.ts @@ -77,5 +77,6 @@ export type GraphiteQueryEditorDependencies = { range?: TimeRange; templateSrv: TemplateSrv; queries: DataQuery[]; - refresh: (target: string) => void; + // schedule onChange/onRunQuery after the reducer actions finishes + refresh: () => void; };