Skip to content
Snippets Groups Projects
Commit 1b84d0fb authored by Maxime Jacquemin's avatar Maxime Jacquemin Committed by Maxime Jacquemin
Browse files

[ivette] Several fixes

- The `useSelection` hooks did not memoized the returned `setSelection`
  function, leading to performance issues if a hook depended on it.

- The standard behavior for `usePromise` is to take a building function
  and dependencies, automatically memoized the constructed promise, and
  then doing as before. However, we keep a `usePromiseNoMemo` with the
  previous behavior. It seems needed for the `theme` handling.

- `useCache` new hook allows to add a cache to a given function. It also
  takes an optional serialization function for complex types for which
  standard equality does not provide the expected behavior.
parent 5f2c6923
No related branches found
No related tags found
No related merge requests found
...@@ -561,13 +561,12 @@ export function useUpdate(...events: Event<any>[]) { ...@@ -561,13 +561,12 @@ export function useUpdate(...events: Event<any>[]) {
/** /**
Hook to re-render when a Promise returns. Hook to re-render when a Promise returns.
The promise will be typically created by using `React.useMemo()`.
The hook returns three informations: The hook returns three informations:
- result: the promise result if it succeeds, undefined otherwise; - result: the promise result if it succeeds, undefined otherwise;
- error: the promise error if it fails, undefined otherwise; - error: the promise error if it fails, undefined otherwise;
- loading: the promise status, true if the promise is still running. - loading: the promise status, true if the promise is still running.
*/ */
export function usePromise<T>(job: Promise<T>) { export function usePromiseNoMemo<T> (job: Promise<T>) {
const [result, setResult] = React.useState<T | undefined>(); const [result, setResult] = React.useState<T | undefined>();
const [error, setError] = React.useState<Error | undefined>(); const [error, setError] = React.useState<Error | undefined>();
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
...@@ -582,6 +581,45 @@ export function usePromise<T>(job: Promise<T>) { ...@@ -582,6 +581,45 @@ export function usePromise<T>(job: Promise<T>) {
return { result, error, loading }; return { result, error, loading };
} }
/* Internal type alias */
type Dependencies = React.DependencyList | undefined
/**
Hook to re-render when a Promise returns.
The promise construction is memoized.
The hook returns three informations:
- result: the promise result if it succeeds, undefined otherwise;
- error: the promise error if it fails, undefined otherwise;
- loading: the promise status, true if the promise is still running.
*/
export function usePromise<T> (job: () => Promise<T>, deps: Dependencies) {
const memoized = React.useMemo<Promise<T>>(job, deps);
return usePromiseNoMemo(memoized);
}
/* Internal type alias */
type Serialize<A> = (a: A) => string;
/**
Hook to add a cache system to a function, allowing to reuse previous results.
As the equality used in JS maps does not allow to effectively implement a
cache for complex type, a serialization function can be procured.
The hook returns the cached version of the function.
*/
export function useCache<K, V>(r: (k: K) => V, s?: Serialize<K>): (k: K) => V {
const [ cache ] = React.useState(new Map<string, V>());
const serialize = s ?? React.useCallback((k: K) => `${k}`, []);
const get = React.useCallback((k: K): V => {
const id = serialize(k);
if (cache.has(id))
return cache.get(id) as V;
const v = r(k);
cache.set(id, v);
return v;
}, [ cache, r, s ]);
return get;
}
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// --- Timer Hooks // --- Timer Hooks
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
......
...@@ -71,7 +71,7 @@ async function getNativeTheme(): Promise<ColorTheme> { ...@@ -71,7 +71,7 @@ async function getNativeTheme(): Promise<ColorTheme> {
export function useColorTheme(): [ColorTheme, (upd: ColorSettings) => void] { export function useColorTheme(): [ColorTheme, (upd: ColorSettings) => void] {
Dome.useUpdate(NativeThemeUpdated); Dome.useUpdate(NativeThemeUpdated);
const { result: current } = Dome.usePromise(getNativeTheme()); const { result: current } = Dome.usePromiseNoMemo(getNativeTheme());
const [pref, setTheme] = Settings.useGlobalSettings(ColorThemeSettings); const [pref, setTheme] = Settings.useGlobalSettings(ColorThemeSettings);
return [current ?? jColorTheme(pref), setTheme]; return [current ?? jColorTheme(pref), setTheme];
} }
......
...@@ -83,7 +83,7 @@ export default function SourceCode(): JSX.Element { ...@@ -83,7 +83,7 @@ export default function SourceCode(): JSX.Element {
const [fontSize] = Settings.useGlobalSettings(Preferences.EditorFontSize); const [fontSize] = Settings.useGlobalSettings(Preferences.EditorFontSize);
// Updating the buffer content. // Updating the buffer content.
const text = React.useMemo(async () => { const { result } = Dome.usePromise(async () => {
const onError = (): string => { const onError = (): string => {
if (file) if (file)
D.error(`Fail to load source code file ${file}`); D.error(`Fail to load source code file ${file}`);
...@@ -91,7 +91,6 @@ export default function SourceCode(): JSX.Element { ...@@ -91,7 +91,6 @@ export default function SourceCode(): JSX.Element {
}; };
return System.readFile(file).catch(onError); return System.readFile(file).catch(onError);
}, [file]); }, [file]);
const { result } = Dome.usePromise(text);
React.useEffect(() => buffer.setValue(result), [buffer, result]); React.useEffect(() => buffer.setValue(result), [buffer, result]);
/* Last location selected by a click in the source code. */ /* Last location selected by a click in the source code. */
......
...@@ -788,7 +788,10 @@ export function setSelection(location: Location, meta = false) { ...@@ -788,7 +788,10 @@ export function setSelection(location: Location, meta = false) {
/** Current selection. */ /** Current selection. */
export function useSelection(): [Selection, (a: SelectionActions) => void] { export function useSelection(): [Selection, (a: SelectionActions) => void] {
const [current, setCurrent] = useGlobalState(GlobalSelection); const [current, setCurrent] = useGlobalState(GlobalSelection);
return [current, (action) => setCurrent(reducer(current, action))]; const callback = React.useCallback((action) => {
setCurrent(reducer(current, action));
}, [ current, setCurrent ]);
return [current, callback];
} }
/** Resets the selected locations. */ /** Resets the selected locations. */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment