mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Refactor use_observable to update state with useReducer
(#132703)
Issue: https://github.com/elastic/seceng/issues/3807 Similar issue: https://github.com/elastic/kibana/pull/132069 Why? When calling setState inside an async function, react doesn't automatically batch updates, leading to an inconsistent application state. Read more: https://github.com/reactwg/react-18/discussions/21
This commit is contained in:
parent
35a4274048
commit
510b8b2cac
1 changed files with 37 additions and 14 deletions
|
@ -6,12 +6,36 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useReducer } from 'react';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { useIsMounted } from '../use_is_mounted';
|
||||
import { Task } from '../types';
|
||||
|
||||
interface State<T> {
|
||||
loading: boolean;
|
||||
error?: unknown;
|
||||
result?: T;
|
||||
}
|
||||
|
||||
export type Action<T> =
|
||||
| { type: 'setResult'; result: T }
|
||||
| { type: 'setError'; error: unknown }
|
||||
| { type: 'load' };
|
||||
|
||||
const createReducer =
|
||||
<T>() =>
|
||||
(state: State<T>, action: Action<T>) => {
|
||||
switch (action.type) {
|
||||
case 'setResult':
|
||||
return { ...state, result: action.result, loading: false };
|
||||
case 'setError':
|
||||
return { ...state, error: action.error, loading: false };
|
||||
case 'load':
|
||||
return { loading: true, result: undefined, error: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fn function returning an observable
|
||||
|
@ -22,31 +46,30 @@ export const useObservable = <Args extends unknown[], Result>(
|
|||
fn: (...args: Args) => Observable<Result>
|
||||
): Task<Args, Result> => {
|
||||
const isMounted = useIsMounted();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<unknown | undefined>();
|
||||
const [result, setResult] = useState<Result | undefined>();
|
||||
const subRef = useRef<Subscription | undefined>();
|
||||
const reducer = createReducer<Result>();
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
loading: false,
|
||||
error: undefined,
|
||||
result: undefined,
|
||||
});
|
||||
|
||||
const start = useCallback(
|
||||
(...args: Args) => {
|
||||
if (subRef.current) {
|
||||
subRef.current.unsubscribe();
|
||||
}
|
||||
setLoading(true);
|
||||
setResult(undefined);
|
||||
setError(undefined);
|
||||
dispatch({ type: 'load' });
|
||||
|
||||
subRef.current = fn(...args).subscribe(
|
||||
(r) => {
|
||||
if (isMounted()) {
|
||||
setResult(r);
|
||||
setLoading(false);
|
||||
dispatch({ type: 'setResult', result: r });
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
if (isMounted()) {
|
||||
setError(e);
|
||||
setLoading(false);
|
||||
dispatch({ type: 'setError', error: e });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -64,9 +87,9 @@ export const useObservable = <Args extends unknown[], Result>(
|
|||
);
|
||||
|
||||
return {
|
||||
error,
|
||||
loading,
|
||||
result,
|
||||
result: state.result,
|
||||
error: state.error,
|
||||
loading: state.loading,
|
||||
start,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue