-
Notifications
You must be signed in to change notification settings - Fork 78
Expand file tree
/
Copy pathuseAdaptedSelector.ts
More file actions
130 lines (118 loc) · 4.54 KB
/
useAdaptedSelector.ts
File metadata and controls
130 lines (118 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useState, useEffect, useRef } from 'react';
import memoizeOne from 'memoize-one';
import { useAdapter } from '../adapter/CallAdapterProvider';
import { CallAdapterState } from '../adapter/CallAdapter';
import {
CallErrors,
CallState,
CallClientState,
DeviceManagerState,
CallNotifications
} from '@internal/calling-stateful-client';
import { CommunicationIdentifierKind } from '@azure/communication-common';
import { EnvironmentInfo } from '@azure/communication-calling';
/**
* @private
*/
export const useAdaptedSelector = <SelectorT extends (state: CallClientState, props: any) => any>(
selector: SelectorT,
selectorProps?: Parameters<SelectorT>[1]
): ReturnType<SelectorT> => {
return useSelectorWithAdaptation(selector, adaptCompositeState, selectorProps);
};
/**
* @private
*/
export const useSelectorWithAdaptation = <
SelectorT extends (state: ReturnType<AdaptFuncT>, props: any) => any,
AdaptFuncT extends (state: CallAdapterState) => any
>(
selector: SelectorT,
adaptState: AdaptFuncT,
selectorProps?: Parameters<SelectorT>[1]
): ReturnType<SelectorT> => {
const adapter = useAdapter();
// Keeps track of whether the current component is mounted or not. If it has unmounted, make sure we do not modify the
// state or it will cause React warnings in the console. https://skype.visualstudio.com/SPOOL/_workitems/edit/2453212
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => {
mounted.current = false;
};
});
const callId = adapter.getState().call?.id;
const [props, setProps] = useState(selector(adaptState(adapter.getState()), selectorProps ?? { callId }));
const propRef = useRef(props);
propRef.current = props;
useEffect(() => {
const onStateChange = (state: CallAdapterState): void => {
if (!mounted.current) {
return;
}
const newProps = selector(adaptState(state), selectorProps ?? { callId: state.call?.id });
if (propRef.current !== newProps) {
setProps(newProps);
}
};
adapter.onStateChange(onStateChange);
return () => {
adapter.offStateChange(onStateChange);
};
}, [adaptState, adapter, selector, selectorProps]);
return props;
};
const memoizeState = memoizeOne(
(
userId: CommunicationIdentifierKind,
deviceManager: DeviceManagerState,
calls: { [key: string]: CallState },
latestErrors: CallErrors,
latestNotifications?: CallNotifications,
displayName?: string,
alternateCallerId?: string,
environmentInfo?: EnvironmentInfo
): CallClientState => ({
userId,
incomingCalls: {},
incomingCallsEnded: {},
callsEnded: {},
deviceManager,
callAgent: { displayName },
calls,
latestErrors,
latestNotifications: latestNotifications ?? ({} as CallNotifications),
alternateCallerId,
environmentInfo
})
);
const memoizeCalls = memoizeOne((call?: CallState): { [key: string]: CallState } => (call ? { [call.id]: call } : {}));
const adaptCompositeState = (compositeState: CallAdapterState): CallClientState => {
return memoizeState(
compositeState.userId,
compositeState.devices,
memoizeCalls(compositeState.call),
// This is an unsafe type expansion.
// compositeState.latestErrors can contain properties that are not valid in CallErrors.
//
// But there is no way to check for valid property names at runtime:
// - The set of valid property names is built from types in the @azure/communication-calling.
// Thus we don't have a literal array of allowed strings at runtime.
// - Due to minification / uglification, the property names from the objects at runtime can't be used
// to compare against permissible values inferred from the types.
//
// This is not a huge problem -- it simply means that our adapted selector will include some extra operations
// that are unknown to the UI component and data binding libraries. Generic handling of the errors (e.g.,
// just displaying them in some UI surface) will continue to work for these operations. Handling of
// specific operations (e.g., acting on errors related to permission issues) will ignore these operations.
compositeState.latestErrors as CallErrors,
undefined ||
/* @conditional-compile-remove(breakout-rooms) */ (compositeState.latestNotifications as CallNotifications),
compositeState.displayName,
compositeState.alternateCallerId,
compositeState.environmentInfo
);
};