Skip to content

Commit 49ae5c4

Browse files
committed
Cache system color scheme, fix memoised ChatToJsx.
This commit improves performance considerably when there's lots of Text. - useColorScheme seems to cause unnecessary performance lag, and is called everytime Text is invoked. It is now only called once globally for the entire application, and passed via React context. - Memoising ChatToJsx properly reduces CPU load from chat parsing. ChatToJsx is no longer improperly memoised by default, and the item renderer in ChatScreen is memoised correctly instead.
1 parent aed52ce commit 49ae5c4

File tree

4 files changed

+104
-77
lines changed

4 files changed

+104
-77
lines changed

src/App.tsx

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import SettingsContext, {
2727
Settings
2828
} from './context/settingsContext'
2929
import ServersContext, { Servers } from './context/serversContext'
30+
import { ColorSchemeContext } from './context/useDarkMode'
3031
import DisconnectDialog from './components/DisconnectDialog'
3132
import ChatScreen from './screens/chat/ChatScreen'
3233
import ServerScreen from './screens/ServerScreen'
@@ -92,7 +93,6 @@ const HomeScreen = ({ navigation }: { navigation: HomeNavigationProp }) => {
9293
}
9394

9495
const App = () => {
95-
const colorScheme = useColorScheme()
9696
const [connection, setConnection] = React.useState<Connection | undefined>()
9797
const [disconnect, setDisconnect] = React.useState<Disconnect | undefined>()
9898

@@ -109,6 +109,8 @@ const App = () => {
109109
setAccountsStore(JSON.stringify(newAccounts))
110110
const setServers = (newServers: Servers) =>
111111
setServersStore(JSON.stringify(newServers))
112+
113+
const colorScheme = useColorScheme()
112114
const systemDefault = colorScheme === null ? true : colorScheme === 'dark'
113115
const darkMode =
114116
settings.darkMode === null ? systemDefault : settings.darkMode
@@ -138,31 +140,33 @@ const App = () => {
138140
<SettingsContext.Provider value={{ settings, setSettings }}>
139141
<ServersContext.Provider value={{ servers, setServers }}>
140142
<AccountsContext.Provider value={{ accounts, setAccounts }}>
141-
{disconnect && <DisconnectDialog />}
142-
<NavigationContainer theme={darkMode ? DarkTheme : undefined}>
143-
<StatusBar
144-
backgroundColor={darkMode ? '#242424' : '#ffffff'}
145-
barStyle={darkMode ? 'light-content' : 'dark-content'}
146-
/>
147-
<Stacks.Navigator
148-
id='StackNavigator'
149-
initialRouteName='Home'
150-
screenOptions={{
151-
headerShown: false,
152-
animation: 'slide_from_right'
153-
}}
154-
>
155-
<Stacks.Screen name='Home' component={HomeScreen} />
156-
<Stacks.Screen
157-
name='Chat'
158-
component={ChatScreen}
159-
getId={({ params }) => {
160-
return (params as { serverName: string }).serverName
161-
}}
143+
<ColorSchemeContext.Provider value={darkMode}>
144+
{disconnect && <DisconnectDialog />}
145+
<NavigationContainer theme={darkMode ? DarkTheme : undefined}>
146+
<StatusBar
147+
backgroundColor={darkMode ? '#242424' : '#ffffff'}
148+
barStyle={darkMode ? 'light-content' : 'dark-content'}
162149
/>
163-
<Stacks.Screen name='Settings' component={SettingScreen} />
164-
</Stacks.Navigator>
165-
</NavigationContainer>
150+
<Stacks.Navigator
151+
id='StackNavigator'
152+
initialRouteName='Home'
153+
screenOptions={{
154+
headerShown: false,
155+
animation: 'slide_from_right'
156+
}}
157+
>
158+
<Stacks.Screen name='Home' component={HomeScreen} />
159+
<Stacks.Screen
160+
name='Chat'
161+
component={ChatScreen}
162+
getId={({ params }) => {
163+
return (params as { serverName: string }).serverName
164+
}}
165+
/>
166+
<Stacks.Screen name='Settings' component={SettingScreen} />
167+
</Stacks.Navigator>
168+
</NavigationContainer>
169+
</ColorSchemeContext.Provider>
166170
</AccountsContext.Provider>
167171
</ServersContext.Provider>
168172
</SettingsContext.Provider>

src/context/useDarkMode.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import { useContext } from 'react'
2-
import { useColorScheme } from 'react-native'
3-
import SettingsContext from '../context/settingsContext'
1+
import { createContext, useContext } from 'react'
42

5-
const useDarkMode = () => {
6-
const colorScheme = useColorScheme()
7-
const { settings } = useContext(SettingsContext)
8-
const systemDefault = colorScheme === null ? true : colorScheme === 'dark'
9-
return settings.darkMode === null ? systemDefault : settings.darkMode
10-
}
3+
export const ColorSchemeContext = createContext<boolean>(false)
4+
5+
const useDarkMode = () => useContext(ColorSchemeContext)
116

127
export default useDarkMode

src/minecraft/chatToJsx.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ const parseChatToJsx = (
234234
)
235235
}
236236

237-
// React Component-ised.
238-
const ChatToJsxNonMemo = (props: {
237+
// React Component-ised. Memoise if being called often.
238+
export const ChatToJsx = (props: {
239239
chat?: MinecraftChat
240240
component: React.ComponentType<TextProps>
241241
colorMap: ColorMap
@@ -251,9 +251,6 @@ const ChatToJsxNonMemo = (props: {
251251
props.componentProps,
252252
props.trim
253253
)
254-
// Memoisation means this is only re-rendered if the props changed.
255-
// TODO: This might be hurting memory usage.
256-
export const ChatToJsx = React.memo(ChatToJsxNonMemo)
257254

258255
export const parseValidJson = (text: string) => {
259256
try {

src/screens/chat/ChatScreen.tsx

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useContext, useEffect, useRef, useState } from 'react'
1+
import React, {
2+
useCallback,
3+
useContext,
4+
useEffect,
5+
useRef,
6+
useState
7+
} from 'react'
28
import {
39
FlatList,
410
StyleSheet,
@@ -49,36 +55,57 @@ interface Message {
4955
text: MinecraftChat
5056
}
5157

52-
const renderItem = (colorMap: ColorMap, handleCe: (ce: ClickEvent) => void) => {
53-
const ItemRenderer = ({ item }: { item: Message }) => (
54-
<View style={styles.androidScaleInvert}>
55-
<ChatToJsx
56-
chat={item.text}
57-
component={Text}
58-
colorMap={colorMap}
59-
clickEventHandler={handleCe}
60-
/>
61-
</View>
62-
)
63-
// LOW-TODO: Performance implications? https://reactnative.dev/docs/optimizing-flatlist-configuration
64-
return ItemRenderer
65-
}
58+
const ItemRenderer = (props: {
59+
item: Message
60+
colorMap: ColorMap
61+
clickEventHandler: (event: ClickEvent) => void
62+
}) => (
63+
<View style={styles.androidScaleInvert}>
64+
<ChatToJsx
65+
chat={props.item.text}
66+
component={Text}
67+
colorMap={props.colorMap}
68+
clickEventHandler={props.clickEventHandler}
69+
/>
70+
</View>
71+
)
72+
73+
const ItemRendererMemo = React.memo(
74+
ItemRenderer,
75+
(prev, next) =>
76+
prev.item.key === next.item.key &&
77+
prev.colorMap === next.colorMap &&
78+
prev.clickEventHandler === next.clickEventHandler
79+
)
80+
6681
const ChatMessageList = (props: {
6782
messages: Message[]
6883
colorMap: ColorMap
6984
clickEventHandler: (ce: ClickEvent) => void
7085
}) => {
86+
// If colorMap/clickEventHandler changes, this will change and cause a re-render.
87+
// If messages changes, FlatList will execute this function for all messages, and
88+
// ItemRendererMemo will check if props have changed instead of this useCallback.
89+
const renderItem = useCallback(
90+
({ item }) => (
91+
<ItemRendererMemo
92+
item={item}
93+
colorMap={props.colorMap}
94+
clickEventHandler={props.clickEventHandler}
95+
/>
96+
),
97+
[props.colorMap, props.clickEventHandler]
98+
)
7199
return (
72100
<FlatList
73101
inverted={Platform.OS !== 'android'}
74102
data={props.messages}
75103
style={[styles.androidScaleInvert, styles.chatArea]}
76104
contentContainerStyle={styles.chatAreaScrollView}
77-
renderItem={renderItem(props.colorMap, props.clickEventHandler)}
105+
renderItem={renderItem}
78106
/>
79107
)
80108
}
81-
const ChatMessageListMemo = React.memo(ChatMessageList) // Shallow prop compare.
82109

83110
const handleError =
84111
(addMessage: (text: MinecraftChat) => void, translated: string) =>
@@ -261,6 +288,29 @@ const ChatScreen = ({ navigation, route }: Props) => {
261288
}
262289
}
263290

291+
const handleClickEvent = useCallback(
292+
(ce: ClickEvent) => {
293+
// TODO: URL prompt support.
294+
if (
295+
ce.action === 'open_url' &&
296+
settings.webLinks &&
297+
(ce.value.startsWith('https://') || ce.value.startsWith('http://'))
298+
) {
299+
Linking.openURL(ce.value).catch(() =>
300+
addMessage(enderChatPrefix + 'Failed to open URL!')
301+
)
302+
} else if (ce.action === 'copy_to_clipboard') {
303+
Clipboard.setString(ce.value)
304+
} else if (ce.action === 'run_command') {
305+
// TODO: This should be a prompt - sendMessage(ce.value, false)
306+
setMessage(ce.value)
307+
} else if (ce.action === 'suggest_command') {
308+
setMessage(ce.value)
309+
} // No open_file/change_page handling.
310+
},
311+
[settings.webLinks]
312+
)
313+
264314
const title =
265315
route.params.serverName.length > 12
266316
? route.params.serverName.substring(0, 9) + '...'
@@ -302,29 +352,10 @@ const ChatScreen = ({ navigation, route }: Props) => {
302352
)}
303353
{!loading && connection && (
304354
<>
305-
<ChatMessageListMemo
355+
<ChatMessageList
306356
messages={messages}
307357
colorMap={darkMode ? mojangColorMap : lightColorMap}
308-
clickEventHandler={ce => {
309-
// TODO: URL prompt support.
310-
if (
311-
ce.action === 'open_url' &&
312-
settings.webLinks &&
313-
(ce.value.startsWith('https://') ||
314-
ce.value.startsWith('http://'))
315-
) {
316-
Linking.openURL(ce.value).catch(() =>
317-
addMessage(enderChatPrefix + 'Failed to open URL!')
318-
)
319-
} else if (ce.action === 'copy_to_clipboard') {
320-
Clipboard.setString(ce.value)
321-
} else if (ce.action === 'run_command') {
322-
// TODO: This should be a prompt - sendMessage(ce.value, false)
323-
setMessage(ce.value)
324-
} else if (ce.action === 'suggest_command') {
325-
setMessage(ce.value)
326-
} // No open_file/change_page handling.
327-
}}
358+
clickEventHandler={handleClickEvent}
328359
/>
329360
<View style={darkMode ? styles.textAreaDark : styles.textArea}>
330361
<TextField

0 commit comments

Comments
 (0)