Skip to content

Commit 2ea1239

Browse files
committed
presence: Convert PresenceHeartbeat to a Hooks-based function
The single `useEffect` seems easier to reason about than the collection of lifecycle methods.
1 parent ea170ba commit 2ea1239

File tree

1 file changed

+33
-68
lines changed

1 file changed

+33
-68
lines changed

src/presence/PresenceHeartbeat.js

Lines changed: 33 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,47 @@
11
// @flow strict-local
2-
import { PureComponent } from 'react';
3-
import type { ComponentType } from 'react';
2+
import * as React from 'react';
43
import { AppState } from 'react-native';
54

6-
import { assumeSecretlyGlobalState, type Dispatch } from '../reduxTypes';
7-
import { connect } from '../react-redux';
5+
import { useGlobalSelector, useDispatch } from '../react-redux';
86
import { getHasAuth } from '../account/accountsSelectors';
97
import { reportPresence } from '../actions';
108
import Heartbeat from './heartbeat';
119

12-
type OuterProps = $ReadOnly<{||}>;
13-
14-
type SelectorProps = $ReadOnly<{|
15-
hasAuth: boolean,
16-
|}>;
17-
18-
type Props = $ReadOnly<{|
19-
...OuterProps,
20-
21-
// from `connect`
22-
dispatch: Dispatch,
23-
...SelectorProps,
24-
|}>;
10+
type Props = $ReadOnly<{||}>;
2511

2612
/**
2713
* Component providing a recurrent `presence` signal.
2814
*/
29-
// The name "PureComponent" is potentially misleading here, as this component is
30-
// in no reasonable sense "pure" -- its entire purpose is to emit network calls
31-
// for their observable side effects as a side effect of being rendered.
32-
//
33-
// (This is merely a misnomer on React's part, rather than a functional error. A
34-
// `PureComponent` is simply one which never updates except when its props have
35-
// changed -- which is exactly what we want.)
36-
class PresenceHeartbeatInner extends PureComponent<Props> {
37-
/** Callback for Heartbeat object. */
38-
onHeartbeat = () => {
39-
if (this.props.hasAuth) {
40-
// TODO(#5005): should ensure this gets the intended account
41-
this.props.dispatch(reportPresence(true));
42-
}
43-
};
44-
45-
heartbeat: Heartbeat = new Heartbeat(this.onHeartbeat, 1000 * 60);
46-
47-
componentDidMount() {
48-
AppState.addEventListener('change', this.updateHeartbeatState);
49-
this.updateHeartbeatState(); // conditional start
50-
}
51-
52-
componentWillUnmount() {
53-
AppState.removeEventListener('change', this.updateHeartbeatState);
54-
this.heartbeat.stop(); // unconditional stop
55-
}
56-
57-
// React to any state change.
58-
updateHeartbeatState = () => {
59-
// heartbeat.toState is idempotent
60-
this.heartbeat.toState(AppState.currentState === 'active' && this.props.hasAuth);
61-
};
15+
// TODO(#5005): either make one of these per account, or make it act on all accounts
16+
export default function PresenceHeartbeat(props: Props): React.Node {
17+
const dispatch = useDispatch();
18+
const hasAuth = useGlobalSelector(getHasAuth); // a job for withHaveServerDataGate?
6219

63-
// React to props changes.
64-
//
65-
// Not dependent on `render()`'s return value, although the docs may not yet
66-
// be clear on that. See: https://github.com/reactjs/reactjs.org/pull/1230.
67-
componentDidUpdate() {
68-
this.updateHeartbeatState();
69-
}
20+
React.useEffect(() => {
21+
if (!hasAuth) {
22+
return;
23+
}
7024

71-
render() {
72-
return null;
73-
}
25+
const onHeartbeat = () => {
26+
// TODO(#5005): should ensure this gets the intended account
27+
dispatch(reportPresence(true));
28+
};
29+
const heartbeat = new Heartbeat(onHeartbeat, 1000 * 60);
30+
31+
// React to any state change.
32+
const updateHeartbeatState = () => {
33+
// heartbeat.toState is idempotent
34+
heartbeat.toState(AppState.currentState === 'active');
35+
};
36+
37+
const sub = AppState.addEventListener('change', updateHeartbeatState);
38+
updateHeartbeatState(); // conditional start
39+
40+
return () => {
41+
sub.remove();
42+
heartbeat.stop(); // unconditional stop
43+
};
44+
}, [dispatch, hasAuth]);
45+
46+
return null;
7447
}
75-
76-
/** (NB this is a per-account component.) */
77-
// TODO(#5005): either make one of these per account, or make it act on all accounts
78-
const PresenceHeartbeat: ComponentType<OuterProps> = connect(state => ({
79-
hasAuth: getHasAuth(assumeSecretlyGlobalState(state)), // a job for withHaveServerDataGate?
80-
}))(PresenceHeartbeatInner);
81-
82-
export default PresenceHeartbeat;

0 commit comments

Comments
 (0)