Skip to content

Commit 2d17627

Browse files
chrisbobbegnprice
authored andcommitted
react: Convert class components to function components, where trivial
None of these have any custom instance methods, or lifecycle methods other than `render`, so they're nice and boring and easy. This doesn't mean all the rest of our React components will be hard to convert. These were just the (very) low-hanging fruit. :) As usual, passing -b to a git-log command is helpful to reduce noise when viewing a class-to-function-component conversion.
1 parent 6c532de commit 2d17627

35 files changed

+549
-636
lines changed

src/boot/TranslationProvider.js

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @flow strict-local */
2-
import React, { PureComponent, type Context } from 'react';
2+
import React, { type Context, useContext } from 'react';
33
import type { ComponentType, ElementConfig, Node } from 'react';
44
import { Text } from 'react-native';
55
import { IntlProvider, IntlContext } from 'react-intl';
@@ -23,14 +23,13 @@ export const TranslationContext: Context<GetText> = React.createContext(undefine
2323
export function withGetText<P: { +_: GetText, ... }, C: ComponentType<P>>(
2424
WrappedComponent: C,
2525
): ComponentType<$ReadOnly<$Exact<$Diff<ElementConfig<C>, {| _: GetText |}>>>> {
26-
return class extends React.Component<$Exact<$Diff<ElementConfig<C>, {| _: GetText |}>>> {
27-
render() {
28-
return (
29-
<TranslationContext.Consumer>
30-
{_ => <WrappedComponent _={_} {...this.props} />}
31-
</TranslationContext.Consumer>
32-
);
33-
}
26+
// eslint-disable-next-line func-names
27+
return function (props: $Exact<$Diff<ElementConfig<C>, {| _: GetText |}>>): Node {
28+
return (
29+
<TranslationContext.Consumer>
30+
{_ => <WrappedComponent _={_} {...props} />}
31+
</TranslationContext.Consumer>
32+
);
3433
};
3534
}
3635

@@ -61,19 +60,14 @@ const makeGetText = (intl: IntlShape): GetText => {
6160
*
6261
* See the `GetTypes` type for why we like the new shape.
6362
*/
64-
class TranslationContextTranslator extends PureComponent<{|
65-
+children: Node,
66-
|}> {
67-
static contextType = IntlContext;
68-
context: IntlShape;
63+
function TranslationContextTranslator(props: {| +children: Node |}): Node {
64+
const intlContextValue = useContext(IntlContext);
6965

70-
render() {
71-
return (
72-
<TranslationContext.Provider value={makeGetText(this.context)}>
73-
{this.props.children}
74-
</TranslationContext.Provider>
75-
);
76-
}
66+
return (
67+
<TranslationContext.Provider value={makeGetText(intlContextValue)}>
68+
{props.children}
69+
</TranslationContext.Provider>
70+
);
7771
}
7872

7973
type Props = $ReadOnly<{|

src/chat/FetchError.js

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @flow strict-local */
22

3-
import React, { PureComponent } from 'react';
3+
import React from 'react';
44
import type { Node } from 'react';
55
import { StyleSheet, View } from 'react-native';
66

@@ -26,18 +26,16 @@ type Props = $ReadOnly<{|
2626
error: mixed,
2727
|}>;
2828

29-
export default class FetchError extends PureComponent<Props> {
30-
render(): Node {
31-
return (
32-
<View style={styles.container}>
33-
{(() => {
34-
if (this.props.error instanceof TimeoutError) {
35-
return <ZulipTextIntl style={styles.text} text="Request timed out." />;
36-
} else {
37-
return <ZulipTextIntl style={styles.text} text="Oops! Something went wrong." />;
38-
}
39-
})()}
40-
</View>
41-
);
42-
}
29+
export default function FetchError(props: Props): Node {
30+
return (
31+
<View style={styles.container}>
32+
{(() => {
33+
if (props.error instanceof TimeoutError) {
34+
return <ZulipTextIntl style={styles.text} text="Request timed out." />;
35+
} else {
36+
return <ZulipTextIntl style={styles.text} text="Oops! Something went wrong." />;
37+
}
38+
})()}
39+
</View>
40+
);
4341
}

src/chat/InvalidNarrow.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @flow strict-local */
22

3-
import React, { PureComponent } from 'react';
3+
import React from 'react';
44
import type { Node } from 'react';
55
import { StyleSheet, View } from 'react-native';
66

@@ -24,12 +24,10 @@ type Props = $ReadOnly<{|
2424
narrow: Narrow,
2525
|}>;
2626

27-
export default class InvalidNarrow extends PureComponent<Props> {
28-
render(): Node {
29-
return (
30-
<View style={styles.container}>
31-
<ZulipTextIntl style={styles.text} text="That conversation doesn't seem to exist." />
32-
</View>
33-
);
34-
}
27+
export default function InvalidNarrow(props: Props): Node {
28+
return (
29+
<View style={styles.container}>
30+
<ZulipTextIntl style={styles.text} text="That conversation doesn't seem to exist." />
31+
</View>
32+
);
3533
}

src/common/CountOverlay.js

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @flow strict-local */
22

3-
import React, { PureComponent } from 'react';
3+
import React from 'react';
44
import type { Node } from 'react';
55
import { View } from 'react-native';
66

@@ -23,26 +23,24 @@ type Props = $ReadOnly<{|
2323
unreadCount: number,
2424
|}>;
2525

26-
export default class CountOverlay extends PureComponent<Props> {
27-
render(): Node {
28-
const { children, unreadCount } = this.props;
26+
export default function CountOverlay(props: Props): Node {
27+
const { children, unreadCount } = props;
2928

30-
return (
31-
<View>
32-
<ComponentWithOverlay
33-
style={styles.button}
34-
// It looks like what we want to match is 25 * 0.75, which is 18.75,
35-
// https://github.com/react-navigation/react-navigation/blob/%40react-navigation/bottom-tabs%405.11.15/packages/bottom-tabs/src/views/TabBarIcon.tsx#L69
36-
overlaySize={18.75}
37-
overlayColor={BRAND_COLOR}
38-
overlayPosition="top-right"
39-
showOverlay={unreadCount > 0}
40-
customOverlayStyle={styles.overlayStyle}
41-
overlay={<UnreadCount count={unreadCount} />}
42-
>
43-
{children}
44-
</ComponentWithOverlay>
45-
</View>
46-
);
47-
}
29+
return (
30+
<View>
31+
<ComponentWithOverlay
32+
style={styles.button}
33+
// It looks like what we want to match is 25 * 0.75, which is 18.75,
34+
// https://github.com/react-navigation/react-navigation/blob/%40react-navigation/bottom-tabs%405.11.15/packages/bottom-tabs/src/views/TabBarIcon.tsx#L69
35+
overlaySize={18.75}
36+
overlayColor={BRAND_COLOR}
37+
overlayPosition="top-right"
38+
showOverlay={unreadCount > 0}
39+
customOverlayStyle={styles.overlayStyle}
40+
overlay={<UnreadCount count={unreadCount} />}
41+
>
42+
{children}
43+
</ComponentWithOverlay>
44+
</View>
45+
);
4846
}

src/common/ErrorMsg.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @flow strict-local */
2-
import React, { PureComponent } from 'react';
2+
import React from 'react';
33
import type { Node } from 'react';
44
import { View } from 'react-native';
55

@@ -29,18 +29,16 @@ type Props = $ReadOnly<{|
2929
*
3030
* @prop error - The error message string.
3131
*/
32-
export default class ErrorMsg extends PureComponent<Props> {
33-
render(): Node {
34-
const { error } = this.props;
32+
export default function ErrorMsg(props: Props): Node {
33+
const { error } = props;
3534

36-
if (!error) {
37-
return null;
38-
}
39-
40-
return (
41-
<View style={styles.field}>
42-
<ZulipTextIntl style={styles.error} text={error} />
43-
</View>
44-
);
35+
if (!error) {
36+
return null;
4537
}
38+
39+
return (
40+
<View style={styles.field}>
41+
<ZulipTextIntl style={styles.error} text={error} />
42+
</View>
43+
);
4644
}

src/common/GroupAvatar.js

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @flow strict-local */
2-
import React, { PureComponent } from 'react';
2+
import React from 'react';
33
import type { Node } from 'react';
44
import { View } from 'react-native';
55
// $FlowFixMe[untyped-import]
@@ -34,28 +34,26 @@ type Props = $ReadOnly<{|
3434
* @prop children - If provided, will render inside the component body.
3535
* @prop onPress - Event fired on pressing the component.
3636
*/
37-
export default class GroupAvatar extends PureComponent<Props> {
38-
render(): Node {
39-
const { children, names, size, onPress } = this.props;
37+
export default function GroupAvatar(props: Props): Node {
38+
const { children, names, size, onPress } = props;
4039

41-
const frameSize = {
42-
height: size,
43-
width: size,
44-
borderRadius: size / 8,
45-
backgroundColor: Color(colorHashFromString(names.join(', ')))
46-
.lighten(0.6)
47-
.hex(),
48-
};
40+
const frameSize = {
41+
height: size,
42+
width: size,
43+
borderRadius: size / 8,
44+
backgroundColor: Color(colorHashFromString(names.join(', ')))
45+
.lighten(0.6)
46+
.hex(),
47+
};
4948

50-
const iconColor = Color(colorHashFromString(names.join(', '))).string();
49+
const iconColor = Color(colorHashFromString(names.join(', '))).string();
5150

52-
return (
53-
<Touchable onPress={onPress}>
54-
<View style={[styles.frame, frameSize]}>
55-
<IconGroup size={size * 0.75} color={iconColor} />
56-
{children}
57-
</View>
58-
</Touchable>
59-
);
60-
}
51+
return (
52+
<Touchable onPress={onPress}>
53+
<View style={[styles.frame, frameSize]}>
54+
<IconGroup size={size * 0.75} color={iconColor} />
55+
{children}
56+
</View>
57+
</Touchable>
58+
);
6159
}

src/common/KeyboardAvoider.js

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @flow strict-local */
2-
import React, { PureComponent } from 'react';
2+
import React from 'react';
33
import type { Node } from 'react';
44
import { Platform, View } from 'react-native';
55
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
@@ -39,24 +39,22 @@ type Props = $ReadOnly<{|
3939
* `KeyboardAvoidingView`'s special props get passed straight through
4040
* to that component.
4141
*/
42-
export default class KeyboardAvoider extends PureComponent<Props> {
43-
render(): Node {
44-
const { behavior, children, style, contentContainerStyle, keyboardVerticalOffset } = this.props;
42+
export default function KeyboardAvoider(props: Props): Node {
43+
const { behavior, children, style, contentContainerStyle, keyboardVerticalOffset } = props;
4544

46-
if (Platform.OS === 'android') {
47-
return <View style={style}>{children}</View>;
48-
}
49-
50-
return (
51-
<KeyboardAvoidingView
52-
behavior={behavior}
53-
contentContainerStyle={contentContainerStyle}
54-
// See comment on this prop in the jsdoc.
55-
keyboardVerticalOffset={keyboardVerticalOffset}
56-
style={style}
57-
>
58-
{children}
59-
</KeyboardAvoidingView>
60-
);
45+
if (Platform.OS === 'android') {
46+
return <View style={style}>{children}</View>;
6147
}
48+
49+
return (
50+
<KeyboardAvoidingView
51+
behavior={behavior}
52+
contentContainerStyle={contentContainerStyle}
53+
// See comment on this prop in the jsdoc.
54+
keyboardVerticalOffset={keyboardVerticalOffset}
55+
style={style}
56+
>
57+
{children}
58+
</KeyboardAvoidingView>
59+
);
6260
}

src/common/SearchEmptyState.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @flow strict-local */
2-
import React, { PureComponent } from 'react';
2+
import React from 'react';
33
import type { Node } from 'react';
44
import { View } from 'react-native';
55

@@ -23,14 +23,12 @@ type Props = $ReadOnly<{|
2323
text: string,
2424
|}>;
2525

26-
export default class SearchEmptyState extends PureComponent<Props> {
27-
render(): Node {
28-
const { text } = this.props;
26+
export default function SearchEmptyState(props: Props): Node {
27+
const { text } = props;
2928

30-
return (
31-
<View style={styles.container}>
32-
<ZulipTextIntl style={styles.text} text={text} />
33-
</View>
34-
);
35-
}
29+
return (
30+
<View style={styles.container}>
31+
<ZulipTextIntl style={styles.text} text={text} />
32+
</View>
33+
);
3634
}

src/common/SectionHeader.js

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/* @flow strict-local */
2-
import React, { PureComponent } from 'react';
3-
import type { Node, Context } from 'react';
2+
import React, { useContext } from 'react';
3+
import type { Node } from 'react';
44
import { View } from 'react-native';
55

6-
import type { ThemeData } from '../styles';
76
import { ThemeContext, createStyleSheet } from '../styles';
87
import ZulipTextIntl from './ZulipTextIntl';
98

@@ -18,16 +17,13 @@ type Props = $ReadOnly<{|
1817
text: string,
1918
|}>;
2019

21-
export default class SectionHeader extends PureComponent<Props> {
22-
static contextType: Context<ThemeData> = ThemeContext;
23-
context: ThemeData;
20+
export default function SectionHeader(props: Props): Node {
21+
const { text } = props;
22+
const themeData = useContext(ThemeContext);
2423

25-
render(): Node {
26-
const { text } = this.props;
27-
return (
28-
<View style={[styles.header, { backgroundColor: this.context.backgroundColor }]}>
29-
<ZulipTextIntl text={text} />
30-
</View>
31-
);
32-
}
24+
return (
25+
<View style={[styles.header, { backgroundColor: themeData.backgroundColor }]}>
26+
<ZulipTextIntl text={text} />
27+
</View>
28+
);
3329
}

src/common/SectionSeparator.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @flow strict-local */
2-
import React, { PureComponent } from 'react';
2+
import React from 'react';
33
import type { Node } from 'react';
44
import { View } from 'react-native';
55

@@ -13,8 +13,6 @@ const styles = createStyleSheet({
1313
},
1414
});
1515

16-
export default class SectionSeparator extends PureComponent<{||}> {
17-
render(): Node {
18-
return <View style={styles.separator} />;
19-
}
16+
export default function SectionSeparator(props: {||}): Node {
17+
return <View style={styles.separator} />;
2018
}

0 commit comments

Comments
 (0)