Skip to content

Commit

Permalink
[NEW] useUserData Hook (#20584)
Browse files Browse the repository at this point in the history
  • Loading branch information
ggazzo authored Feb 3, 2021
1 parent 61ca8bf commit 8b23ebd
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 39 deletions.
19 changes: 2 additions & 17 deletions client/components/UserStatus.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import { StatusBullet } from '@rocket.chat/fuselage';
import { useSafely } from '@rocket.chat/fuselage-hooks';

import { useTranslation } from '../contexts/TranslationContext';
import { Presence } from '../lib/presence';
import { usePresence } from '../hooks/usePresence';

export const UserStatus = React.memo(({ small, status, ...props }) => {
const size = small ? 'small' : 'large';
Expand Down Expand Up @@ -34,20 +33,6 @@ export const colors = {
offline: 'neutral-600',
};

export const usePresence = (uid, presence) => {
const [status, setStatus] = useSafely(useState(presence));
useEffect(() => {
const handle = ({ status = 'loading' }) => {
setStatus(status);
};
Presence.listen(uid, handle);
return () => {
Presence.stop(uid, handle);
};
}, [setStatus, uid]);

return status;
};

export const ReactiveUserStatus = React.memo(({ uid, presence, ...props }) => {
const status = usePresence(uid, presence);
Expand Down
14 changes: 14 additions & 0 deletions client/hooks/usePresence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useUserData } from './useUserData';

type Presence = 'online' | 'offline' | 'busy' | 'away' | 'loading';


/**
* Hook to fetch and subscribe users presence
*
* @param uid - User Id
* @returns User Presence - 'online' | 'offline' | 'busy' | 'away' | 'loading'
* @public
*/

export const usePresence = (uid: string, presence: Presence): Presence => useUserData(uid)?.status || presence;
27 changes: 27 additions & 0 deletions client/hooks/useUserData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useMemo } from 'react';
import { useSubscription } from 'use-subscription';

import { Presence, UserPresence } from '../lib/presence';


/**
* Hook to fetch and subscribe users data
*
* @param uid - User Id
* @returns Users data: status, statusText, username, name
* @public
*/
export const useUserData = (uid: string): UserPresence | undefined => {
const subscription = useMemo(() => ({
getCurrentValue: (): UserPresence | undefined => Presence.store.get(uid),
subscribe: (callback: any): any => {
Presence.listen(uid, callback);
return (): void => {
Presence.stop(uid, callback);
};
},
}),
[uid]);

return useSubscription(subscription);
};
42 changes: 21 additions & 21 deletions client/lib/presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import { APIClient } from '../../app/utils/client';
import { IUser } from '../../definition/IUser';

const emitter = new Emitter();
const statuses = new Map();

type User = Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'utcOffset' | 'statusText' | 'avatarETag'>;
const store = new Map<string, UserPresence>();

export type UserPresence = Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'utcOffset' | 'statusText' | 'avatarETag'>;

type UsersPresencePayload = {
users: User[];
users: UserPresence[];
full: boolean;
};

const isUid = (eventType: EventType): eventType is User['_id'] =>
const isUid = (eventType: EventType): eventType is UserPresence['_id'] =>
Boolean(eventType) && typeof eventType === 'string' && !['reset', 'restart', 'remove'].includes(eventType);

const uids = new Set<User['_id']>();
const getPresence = ((): ((uid: User['_id']) => void) => {
const uids = new Set<UserPresence['_id']>();
const getPresence = ((): ((uid: UserPresence['_id']) => void) => {
let timer: ReturnType<typeof setTimeout>;

const fetch = (delay = 250): void => {
Expand All @@ -33,7 +34,7 @@ const getPresence = ((): ((uid: User['_id']) => void) => {
const { users } = await APIClient.v1.get('users.presence', params) as UsersPresencePayload;

users.forEach((user) => {
if (!statuses.has(user._id)) {
if (!store.has(user._id)) {
emitter.emit(user._id, user);
}
currentUids.delete(user._id);
Expand All @@ -52,7 +53,7 @@ const getPresence = ((): ((uid: User['_id']) => void) => {
}, delay);
};

const get = (uid: User['_id']): void => {
const get = (uid: UserPresence['_id']): void => {
uids.add(uid);
fetch();
};
Expand All @@ -62,11 +63,11 @@ const getPresence = ((): ((uid: User['_id']) => void) => {
return;
}

statuses.delete(uid);
store.delete(uid);
});

emitter.on('reset', () => {
statuses.clear();
store.clear();
emitter.once('restart', () => {
emitter.events()
.filter(isUid)
Expand All @@ -77,28 +78,26 @@ const getPresence = ((): ((uid: User['_id']) => void) => {
return get;
})();

type PresenceUpdate = Partial<Pick<User, '_id' | 'username' | 'status' | 'statusText'>>;

const update: Handler<PresenceUpdate> = (update) => {
const update: Handler<UserPresence> = (update) => {
if (update?._id) {
statuses.set(update._id, update.status);
store.set(update._id, update);
uids.delete(update._id);
}
};

const listen = (uid: User['_id'], handler: Handler<PresenceUpdate>): void => {
emitter.on(uid, handler);
const listen = (uid: UserPresence['_id'], handler: Handler<UserPresence>): void => {
emitter.on(uid, update);
emitter.on(uid, handler);
emitter.on('reset', handler);

if (statuses.has(uid)) {
return handler({ status: statuses.get(uid) });
if (store.has(uid)) {
return handler(store.get(uid));
}

getPresence(uid);
};

const stop = (uid: User['_id'], handler: Handler<PresenceUpdate>): void => {
const stop = (uid: UserPresence['_id'], handler: Handler<UserPresence>): void => {
setTimeout(() => {
emitter.off(uid, handler);
emitter.off(uid, update);
Expand All @@ -109,14 +108,14 @@ const stop = (uid: User['_id'], handler: Handler<PresenceUpdate>): void => {

const reset = (): void => {
emitter.emit('reset', {});
statuses.clear();
store.clear();
};

const restart = (): void => {
emitter.emit('restart');
};

const notify = (update: PresenceUpdate): void => {
const notify = (update: UserPresence): void => {
if (update._id) {
emitter.emit(update._id, update);
}
Expand All @@ -132,4 +131,5 @@ export const Presence = {
reset,
restart,
notify,
store,
};
2 changes: 1 addition & 1 deletion client/views/room/contextualBar/OTR/OTR.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import OTRModal from './OTRModal';
import { OTR as ORTInstance } from '../../../../../app/otr/client/rocketchat.otr';
import { useTranslation } from '../../../../contexts/TranslationContext';
import VerticalBar from '../../../../components/VerticalBar';
import { usePresence } from '../../../../components/UserStatus';
import { useReactiveValue } from '../../../../hooks/useReactiveValue';
import { usePresence } from '../../../../hooks/usePresence';

export const OTR = ({
isEstablishing,
Expand Down

0 comments on commit 8b23ebd

Please sign in to comment.