Reduce amount of requests done by the onboarding task list (#9194)

* Significantly reduce work of useUserOnboardingContext
* Wrap UserOnboardingButton to avoid unnecessary work when it's not shown
* Remove progress from user onboarding button
This commit is contained in:
Janne Mareike Koschinski 2022-08-22 13:48:54 +02:00 committed by GitHub
parent e8eefeb937
commit 94d292a6ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 117 additions and 81 deletions

View file

@ -15,54 +15,96 @@ limitations under the License.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { ClientEvent, IMyDevice, Room } from "matrix-js-sdk/src/matrix";
import { useCallback, useEffect, useState } from "react";
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { Notifier } from "../Notifier";
import DMRoomMap from "../utils/DMRoomMap";
import { useEventEmitter } from "./useEventEmitter";
export interface UserOnboardingContext {
avatar: string | null;
myDevice: string;
devices: IMyDevice[];
dmRooms: {[userId: string]: Room};
hasAvatar: boolean;
hasDevices: boolean;
hasDmRooms: boolean;
hasNotificationsEnabled: boolean;
}
const USER_ONBOARDING_CONTEXT_INTERVAL = 5000;
/**
* Returns a persistent, non-changing reference to a function
* This function proxies all its calls to the current value of the given input callback
*
* This allows you to use the current value of e.g., a state in a callback thats used by e.g., a useEventEmitter or
* similar hook without re-registering the hook when the state changes
* @param value changing callback
*/
function useRefOf<T extends any[], R>(value: (...values: T) => R): (...values: T) => R {
const ref = useRef(value);
ref.current = value;
return useCallback(
(...values: T) => ref.current(...values),
[],
);
}
function useUserOnboardingContextValue<T>(defaultValue: T, callback: (cli: MatrixClient) => Promise<T>): T {
const [value, setValue] = useState<T>(defaultValue);
const cli = MatrixClientPeg.get();
const handler = useRefOf(callback);
useEffect(() => {
if (value) {
return;
}
let handle: number | null = null;
let enabled = true;
const repeater = async () => {
if (handle !== null) {
clearTimeout(handle);
handle = null;
}
setValue(await handler(cli));
if (enabled) {
handle = setTimeout(repeater, USER_ONBOARDING_CONTEXT_INTERVAL);
}
};
repeater().catch(err => logger.warn("could not update user onboarding context", err));
cli.on(ClientEvent.AccountData, repeater);
return () => {
enabled = false;
cli.off(ClientEvent.AccountData, repeater);
if (handle !== null) {
clearTimeout(handle);
handle = null;
}
};
}, [cli, handler, value]);
return value;
}
export function useUserOnboardingContext(): UserOnboardingContext | null {
const [context, setContext] = useState<UserOnboardingContext | null>(null);
const hasAvatar = useUserOnboardingContextValue(false, async (cli) => {
const profile = await cli.getProfileInfo(cli.getUserId());
return Boolean(profile?.avatar_url);
});
const hasDevices = useUserOnboardingContextValue(false, async (cli) => {
const myDevice = cli.getDeviceId();
const devices = await cli.getDevices();
return Boolean(devices.devices.find(device => device.device_id !== myDevice));
});
const hasDmRooms = useUserOnboardingContextValue(false, async () => {
const dmRooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals() ?? {};
return Boolean(Object.keys(dmRooms).length);
});
const hasNotificationsEnabled = useUserOnboardingContextValue(false, async () => {
return Notifier.isPossible();
});
const cli = MatrixClientPeg.get();
const handler = useCallback(async () => {
try {
const profile = await cli.getProfileInfo(cli.getUserId());
const myDevice = cli.getDeviceId();
const devices = await cli.getDevices();
const dmRooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals() ?? {};
setContext({
avatar: profile?.avatar_url ?? null,
myDevice,
devices: devices.devices,
dmRooms: dmRooms,
});
} catch (e) {
logger.warn("Could not load context for user onboarding task list: ", e);
setContext(null);
}
}, [cli]);
useEventEmitter(cli, ClientEvent.AccountData, handler);
useEffect(() => {
const handle = setInterval(handler, 2000);
handler();
return () => {
if (handle) {
clearInterval(handle);
}
};
}, [handler]);
return context;
return useMemo(
() => ({ hasAvatar, hasDevices, hasDmRooms, hasNotificationsEnabled }),
[hasAvatar, hasDevices, hasDmRooms, hasNotificationsEnabled],
);
}